Repository navigation
plugin-gantt: schema 上的 viewMode 只被 ResourceWorkload 分支读,时间轴静默忽略 —— 且该键在 ObjectGanttSchema 与 spec 的 GanttConfigSchema 都未声明 #5074
Description
Activity
正文里那段分支结构原本贴的是 JSX,被 issues 正文清洗器按 HTML 标签吞掉了(
<+字母),已改写成文字描述并保留 file:line 复核命令。另注:本卡正文末尾的生成者脚注同样被端点剥除,故用本评论补上。
Generated by Claude Code
- addedbugSomething isn't workingSomething isn't working
on Aug 17, 2026 Maintainer ruling recorded — maintainer, 2026-08-18, batch acceptance of the triage seat's decision-inbox recommendations, verbatim: 「同意」. Provenance: triage session
session_pm_triage_20260818T0104Z(objectstack#6015).Ruled: declare
viewModeand wire BOTH branches — the half-wired state (effective only underresourceView) is the worst of the three. Contract-first sequencing: the spec half comes first —GanttConfigSchemain@objectstack/specgains aviewModeenum (objectstack card, spec seat, clause-② semantic face, contract-review tier; being filed by this seat now) — then this card's objectui half:ObjectGanttSchemadeclares it and the timeline branch passes it toGanttView, retiring the(schema as any).viewModecast. Blocked-by: the objectstack spec card's landing (number will be posted here once filed; the rate-limited window delayed creation).The component's other undeclared
as anyreads (readOnly,markers,holidays,skipWeekends,criticalPath,showBaselines,mobileReadOnly,navigation,persistLayout) are ⛔ not ruled here per-key — they go to the #5043 family track as one population.Label
needs-user-decision→pm:blockedin the same stroke (queued on the spec half).
Generated by Claude Code
Spec half filed: objectstack-ai/objectstack#9463 (GanttConfigSchema
viewModeenum, clause-② contract-review tier).Blocked-by: objectstack-ai/objectstack#9463
This card unblocks when #9463 lands; the wiring scope here is unchanged from the ruling comment above. (Blocked-by line carried in this comment per the #8813 body-edit caution; the unlock scan reads comment-level lines under standing instruction ⑥.)
Generated by Claude Code
Unlock notice from the objectstack
domain:specseat (#6017, sessionsession_01Fs18A2DdXLVN2h8PaaFBcP): the spec half is landed — objectstack#9463 closed via objectstack PR #9489, squashed onto objectstackmainas870f710e7(2026-08-18T06:03Z).What the contract now says, for this card's both-branches wiring:
GanttConfigSchema.viewModeis declared:z.enum(['day','week','month','quarter','year']).optional()— the member list measured from THIS repo's renderer (GanttView.tsxGanttViewMode/VIEW_MODES, all five members verified live includingyear; the stale four-member JSDoc atGanttView.tsx:195is filed as objectui#5132).- Deliberately NO spec-side default: an omitted
viewModemust keep letting the persisted layout (persistLayoutKey) seed the granularity before the'day'fallback — a spec default would arrive as an explicit author choice and defeat that seeding. The objectui-side declaration onObjectGanttSchemashould preserve the same absence semantics. - The spec liveness ledger records
viewModeat PLANNED strength with an amend-to-live instruction naming THIS card's landing (the timeline-branch read + retiring the(schema as any).viewModecast atObjectGantt.tsx:1445).
This card's
Blocked-by: objectstack#9463is discharged once your repo's.objectstack-shaspec pin advances past870f710e7— dispatch after the pin covers it. objectui#5057 (the README half) chains behind this card per its own note.
Generated by Claude Code
os-support-ai commented
on Aug 19, 2026 CollaboratorMore actionsTriage (state-machine hygiene): swapped
pm:blocked→needs-user-decision. This card IS the pending ruling (enforce-or-remove for the undeclaredviewModeread, per its own "两条路的取舍是维护者的裁定"), not a card waiting on an upstream —pm:blockedwith no upstream is a state nothing can ever unlock, while #5057 and #5126 both wait on this ruling. #5057's 2026-08-17 PM header already describes it as being in the decision inbox; the label now matches.Four-axis block (required on inbox entry):
- Platform long-term coherence: the component reads ~10 undeclared keys via
as any(card's own census) — ruling onviewModesets the pattern for the family; declaring it means also wiring the timeline branch, removing it means the ResourceWorkload branch loses a granularity input. - Measured business pull:
viewModehalf-works today (resource view honors it, timeline silently ignores it) — an author-visible inconsistency, but the key was never declared authoring surface. - AI-agent error-resistance:
BaseSchema's index signature +passthroughmakes every undeclared read invisible to gates (measured in the card); either outcome must land as a declared, pinned contract, never another tolerated cast. - Startup scope discipline: remove is the cheaper standing obligation; declare only if the granularity control is wanted product surface.
Recommendation: remove the undeclared
viewModeread (both branches keyed off declared config only), letting #5057's README rewrite state one truth — with thereadOnly-family casts left to their own later enforce-or-remove pass, not folded in here.本评论来自分诊座位 Routine (session
session_018euHiF12oV8iSfYdjVioUF).
Generated by Claude Code
- Platform long-term coherence: the component reads ~10 undeclared keys via
os-support-ai commented
on Aug 19, 2026 CollaboratorMore actionsNot dispatched — the ruling's own dispatch precondition fails, measured. PM round 17,
repo:objectuiexecution seat, sessionsession_01RV6yuVCxymHYE16PL9vQkE.pm:queue→pm:blocked.Blocked-by: #5328The ruling is not in question — declare-and-wire stands, and the reasoning for confirming it over the triage seat's "remove" recommendation is exactly right: the spec half is paid, so removing the objectui read would turn a freshly published spec key into a declared-but-unread key one level up. What fails is the precondition attached to it.
The measurement
reading value objectstack packages/spec/src/ui/view.zod.ts→GanttConfigSchemacarries viewMode: z.enum(['day','week','month','quarter','year']).optional()✅870f710e7ancestor of objectstackmain?yes ✅ objectui pnpm-lock.yaml→@objectstack/spec17.0.0, lockfile-exact under^17.0.0objectstack packages/spec/package.jsononmainstill 17.0.0.changeset/gantt-viewmode-declared.mdon objectstackmainstill present — unreleased (303 changesets pending) Counter-probe on the schema read: the same window shows
resourceViewat the expected offset, so the file and range are right —viewModereally is in source.The source half landed; the published half has not. The
17.0.0this repo installs predates the commit, soviewModeis not innode_moduleshere. Declaring it onObjectGanttSchematoday would publish a key the save gate refuses — the preview-works / save-refuses trap, which is the exact defect class this cleanup exists to eliminate. Wiring it before the pin covers it would trade one instance of the disease for another.Two corrections to the precondition as written
The ruling says: "verify the
.objectstack-shaspec pin covers870f710e7(REST compare, not local merge-base); if not yet covered, wait or document the fork window per protocol."- objectui has no
.objectstack-sha— I looked; that file is thecloudrepo's mechanism. objectui consumes the spec as a published npm package, so the pin ispackage.json+pnpm-lock.yaml, and the covering event is a release, not a commit. A RESTcompareon the commit therefore answers a question this repo does not ask. - Because the covering event is a release, "wait" had nothing to wait on.
pm:blockedneeds aBlocked-by: #N, so I filed The locked@objectstack/spec@17.0.0does not carry the spec keys already merged upstream —GanttConfigSchema.viewMode(#5074) is the first card measurably stalled on it #5328 to be that N — it carries the full measurement and a machine-readableRestart-when:line. ⛔ It does not authorise anyone to cut a release; that is a maintainer action, and version releases are never executed by an AI seat.
What happens next
#5074 returns to
pm:queuewhen #5328 closes; the wiring scope is unchanged from the ruling. #5057 and #5126 stay chained behind this card.Not blocked, and dispatched this round instead: #5132 — the one-word
GanttViewModeJSDoc fix in the same file. It touches only the doc comment, so it cannot collide with this card's wiring when that unblocks, and it is worth landing now precisely because the upstream enum's member list was measured from that file.
Generated by Claude Code
- objectui has no
os-support-ai commented
on Aug 22, 2026 CollaboratorMore actionsUnblocked —
GanttConfigSchema.viewModeis now in the pinned buildUnlock scan by the spec@objectui seat after PR #5529 merged. Verified on
origin/main@2d36552d2.Blocker discharged
This card's own
Restart-when:criterion — carried on #5328 — was run against the published package and passed, counter-probed:probe result viewModeonGanttConfigSchematrue ← the criterion resourceView(known-present control)true zzNotAKey(known-absent control)false and the pin is now actually on main: all six
@objectstack/*resolve 17.1.0, 0 entries left at 17.0.0. Merged upstream / published / pinned here — all three true for the first time.Blocked-by: objectui#5328discharged;pm:blocked→pm:queue.pm:blockingis kept — #5057 and #5126 chain behind this card, not behind #5328, so this one still gates them.Premise note for whoever takes it
I did not re-derive the full card. One reading worth having before you start:
viewModeappears throughoutpackages/plugin-gantt/srcas a host prop (tests drive it viarenderView({ viewMode }), and it is persisted inGanttLayout), which is consistent with the card's framing that the authored-metadata path is the missing half. Confirm the current declaration state ofObjectGanttSchemaand the(schema as any)cast on merged main before writing anything — #5529 moved the pin and touched several consumer surfaces, so treat the card's file-level assertions as dated and re-measure.Standing constraint from #5328's dependant list: ⛔ do not work around a spec key by declaring it objectui-side only.
Generated by Claude Code
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Aug 23, 2026 Triage:
domain:ui. The fix lands inpackages/plugin-gantt— theviewModekey read only by theResourceWorkloadbranch while the timeline ignores it. Published library source.⚠️ The card also records thatviewModeis undeclared in bothObjectGanttSchemaand the spec'sGanttConfigSchema. If the chosen fix is to declare it rather than to read it, that half is a contract change and belongs todomain:specas a separate card — ⛔ do not fold an accept-set expansion into a ui-lane renderer fix.pm:blockingis carried; it is a derived cache and was not touched here.objectui three-stream split (maintainer 2026-08-21):
domain:devxengineering surface ·domain:speccontract surface ·domain:uieverything else (published libraries and apps).Routed via the maintainer direct-dispatch channel, this session, verbatim: 「objectui 有 79 张卡在车道视图之外(占该仓 35%),你帮我分批处理」. PM session
session_0124Qg8rLvpXnQDwCmpKUmaJ. Not a Routine triage fire — the triage seat remains the standing producer ofdomain:*and may re-grade.⛔ pm-state, assignee and scope untouched.
Generated by Claude Code
Claim —
domain:uiexecution seat. PM round 28, sessionsession_01EuPCi56cnGyykygi3z9w4m, branchclaude/issue-5074-gantt-viewmode-declare-wire. Dispatching at contract-review tier (clause ②: the change widens whatObjectGanttSchemadeclares).Precondition re-verified before claiming, since the 2026-08-19 dispatch attempt failed on exactly this point:
reading value Blocked-by: #5328discharged 2026-08-22 (comment 5376979824) — viewModepresent on the publishedGanttConfigSchema, counter-probed againstresourceView(present) andzzNotAKey(absent)pinned @objectstack/specnow 17.2.0 — later than the 17.1.0 that discharged the blocker, so the key is a fortiori in node_moduleshereThe ruling being executed is the 2026-08-19 maintainer-confirmed one (comment 5339681862): declare and wire both branches; the triage seat's "remove" recommendation was rejected there and stays rejected. Scope as ruled —
ObjectGanttSchemadeclaresviewMode, the timeline branch passes it toGanttView, the(schema as any).viewModecast atObjectGantt.tsx:1445retires, and absence semantics are preserved (no default; an omittedviewModemust still let the persisted layout seed granularity before the'day'fallback).⛔ Out of scope, restated so the dev cannot drift into them: the other nine undeclared
as anyreads (readOnly,markers,holidays,skipWeekends,criticalPath,showBaselines,mobileReadOnly,navigation,persistLayout) stay with the #5043 family track. ⛔ Do not work around a spec key by declaring it objectui-side only — the objectui declaration must mirror the already-published upstream enum, not invent a parallel one.Per the 2026-08-22 unlock note, the card's file-level assertions are dated (#5529 moved the pin and touched consumer surfaces); the dev re-measures the declaration state and the cast on current
mainbefore writing.
Generated by Claude Code
- added a commit that references this issue
on Aug 23, 2026 { "issue": 5074, "status": "done", "branch": "claude/issue-5074-gantt-viewmode-declare-wire", "pr": "https://github.com/objectstack-ai/objectui/pull/5825", "premise_still_valid": true, "summary": "Executed the 2026-08-19 maintainer ruling (declare-and-wire, comment 5339681862). ObjectGanttSchema now declares viewMode in the TS interface (derived as SpecGanttConfig['viewMode']) and the zod mirror (SpecGanttConfigSchema.shape.viewMode by reference — inner enum reference-identical, pinned by test); no default, absence semantics preserved so persisted layouts keep seeding granularity. getGanttConfig picks viewMode into GanttConfigEx, the timeline branch passes viewMode={ganttConfig?.viewMode} to GanttView (no || 'day' — guard comment marks it load-bearing), the ResourceWorkload branch keeps its explicit 'day' fallback, and the (schema as any).viewMode cast is retired (zero such reads remain repo-wide). Spec precondition re-verified in node_modules before writing: viewMode present on the installed GanttConfigSchema as z.enum(['day','week','month','quarter','year']).optional(), counter-probed against resourceView (present) and zzNotAKey (absent). Repo sweep found no fixture/example/app authoring viewMode on a gantt schema, so the declared-key value narrowing (off-enum values now refused) re-judges nothing in-repo. Out-of-scope surfaces untouched: the nine #5043-family casts, #5057, #5126, #5132, plugin-gantt.mdx.", "tests": "New suites: packages/plugin-gantt/src/ObjectGantt.viewmode.test.tsx (wiring: timeline honours authored viewMode in both authoring styles; receives exactly undefined when omitted; ResourceWorkload branch pinned) and packages/types/src/__tests__/gantt-view-mode-declared.test.ts (key declared; enum reference-identity anti-fork pin; all spec members accepted; off-enum 'hour' refused with invalid_value on the viewMode path; no default materialised; TS mutual-assignability + @ts-expect-error compiled via tsconfig.test.json). Reverse verification on the real commit (git checkout HEAD~1 -- the 3 source files, tests kept; mutation confirmed on disk by git status showing the 3 reverted files): 5 discriminating legs went red — both timeline wiring legs, 'declares the key', 'reference identity', 'refuses off-enum' — observed direction: red, as predicted; 6 legs do not discriminate and are declared as pins (omitted-stays-undefined guards the future || 'day' regression only; ResourceWorkload legs; accepts-every-member; no-default runtime half; TS runtime-erased leg). Restored, git diff HEAD --stat empty, 11/11 green. No dist-based ablation was involved: vitest resolves @object-ui/* to package src via the root config aliases, so both reverse-verification legs read the mutated/restored source directly. Union at final commit c4a12f52e (all via the shared verify lock, verdicts read from gate output, not bare $?): vitest run packages/plugin-gantt/ packages/types/ from repo root = Test Files 92 passed (92), Tests 912 passed (912); pnpm --filter type-check for @object-ui/types and @object-ui/plugin-gantt both clean after building the dependency closure; pnpm lint (turbo run lint, full farm, not narrowed) = 47/47 successful; check-changeset-presence / check-changeset-no-major / check-control-bytes / check-spec-symbol-derivation all green. CI not awaited per dispatch protocol; changeset gantt-viewmode-declared-both-branches-5074.md declares minor for @object-ui/types + @object-ui/plugin-gantt.", "open_questions": [], "out_of_scope_findings": [] }
Generated by Claude Code
Generated by Claude Code
os-dev-report
(Re-post of the previous comment, 5386271475: the sanitizer stripped its HTML-comment marker line, which would hide it from the PM scan. Same JSON, now behind the literal-text marker. Read this one.)
{ "issue": 5074, "status": "done", "branch": "claude/issue-5074-gantt-viewmode-declare-wire", "pr": "https://github.com/objectstack-ai/objectui/pull/5825", "premise_still_valid": true, "summary": "Executed the 2026-08-19 maintainer ruling (declare-and-wire, comment 5339681862). ObjectGanttSchema now declares viewMode in the TS interface (derived as SpecGanttConfig['viewMode']) and the zod mirror (SpecGanttConfigSchema.shape.viewMode by reference — inner enum reference-identical, pinned by test); no default, absence semantics preserved so persisted layouts keep seeding granularity. getGanttConfig picks viewMode into GanttConfigEx, the timeline branch passes viewMode={ganttConfig?.viewMode} to GanttView (no || 'day' — guard comment marks it load-bearing), the ResourceWorkload branch keeps its explicit 'day' fallback, and the (schema as any).viewMode cast is retired (zero such reads remain repo-wide). Spec precondition re-verified in node_modules before writing: viewMode present on the installed GanttConfigSchema as z.enum(['day','week','month','quarter','year']).optional(), counter-probed against resourceView (present) and zzNotAKey (absent). Repo sweep found no fixture/example/app authoring viewMode on a gantt schema, so the declared-key value narrowing (off-enum values now refused) re-judges nothing in-repo. Out-of-scope surfaces untouched: the nine #5043-family casts, #5057, #5126, #5132, plugin-gantt.mdx.", "tests": "New suites: packages/plugin-gantt/src/ObjectGantt.viewmode.test.tsx (wiring: timeline honours authored viewMode in both authoring styles; receives exactly undefined when omitted; ResourceWorkload branch pinned) and packages/types/src/__tests__/gantt-view-mode-declared.test.ts (key declared; enum reference-identity anti-fork pin; all spec members accepted; off-enum 'hour' refused with invalid_value on the viewMode path; no default materialised; TS mutual-assignability + @ts-expect-error compiled via tsconfig.test.json). Reverse verification on the real commit (git checkout HEAD~1 -- the 3 source files, tests kept; mutation confirmed on disk by git status showing the 3 reverted files): 5 discriminating legs went red — both timeline wiring legs, 'declares the key', 'reference identity', 'refuses off-enum' — observed direction: red, as predicted; 6 legs do not discriminate and are declared as pins (omitted-stays-undefined guards the future || 'day' regression only; ResourceWorkload legs; accepts-every-member; no-default runtime half; TS runtime-erased leg). Restored, git diff HEAD --stat empty, 11/11 green. No dist-based ablation was involved: vitest resolves @object-ui/* to package src via the root config aliases, so both reverse-verification legs read the mutated/restored source directly. Union at final commit c4a12f52e (all via the shared verify lock, verdicts read from gate output, not bare $?): vitest run packages/plugin-gantt/ packages/types/ from repo root = Test Files 92 passed (92), Tests 912 passed (912); pnpm --filter type-check for @object-ui/types and @object-ui/plugin-gantt both clean after building the dependency closure; pnpm lint (turbo run lint, full farm, not narrowed) = 47/47 successful; check-changeset-presence / check-changeset-no-major / check-control-bytes / check-spec-symbol-derivation all green. CI not awaited per dispatch protocol; changeset gantt-viewmode-declared-both-branches-5074.md declares minor for @object-ui/types + @object-ui/plugin-gantt.", "open_questions": [], "out_of_scope_findings": [] }
Generated by Claude Code
Generated by Claude Code
- added a commit that references this issue
on Aug 23, 2026
发现于 #5058 的实施(PR #5072),复测 #5057 的「逐键零命中表」时命中。Filed unassigned, not claiming.
测于
origin/main@82a94170c4058d451ce3ac179d99296d90554479。现象
schema.viewMode在packages/plugin-gantt/src有且只有一个读取点,而且它在一个受守卫的分支里:该行位于
:1439的三元真分支内。结构(标签名照抄会被正文清洗器吞掉,故此处用文字描述)::1439判ganttConfig?.resourceView && assigneeAccessor—— 真分支渲染ResourceWorkload元素并把上面那行viewMode传给它;假分支从:1447起渲染GanttView元素,其属性里没有viewMode(复核:sed -n '1447,1500p' … | grep viewMode无输出)。于是
GanttView落到自己的默认粒度。净效果:作者在 gantt 节点上写viewMode: 'month',resourceView+assigneeField→ 资源负载网格按 month 分列,生效;同一个键在同一个组件上一半生效一半不生效,取决于一个看起来无关的配置(
resourceView)。契约侧:这个键两处都没声明
ObjectGanttSchema(packages/types/src/objectql.ts:1856-1870)声明的是type/objectName/startDateField/endDateField/titleField/dependencyField/progressField—— 没有viewMode;GanttConfigSchema(gantt块的 vocabulary)那 17 个键里也没有viewMode(实测node_modules/@objectstack/spec/dist/view.zod-CktyTeqO.d.ts:765起的 shape:startDateFieldendDateFieldtitleFieldprogressFielddependenciesFieldcolorFieldparentFieldtypeFieldbaselineStartFieldbaselineEndFieldgroupByFieldresourceViewassigneeFieldeffortFieldcapacitytooltipFieldsquickFiltersautoZoomToFilter…);(schema as any).viewMode强转,读的是一个未声明的键。之所以没被类型系统挡下,是因为ObjectGanttSchema继承的BaseSchema带索引签名[key: string]: any(packages/types/src/base.ts:318),其 Zod 对应物是.passthrough()(packages/types/src/zod/base.zod.ts:154)—— 两层都不拒未知键。按 #0.1 契约优先,这属于 ADR-0049 「enforce-or-remove」的形状:要么在契约里声明它(并把时间轴那半边接上),要么把这个未声明的读取点移除。两条路的取舍是维护者的裁定,不在本卡内擅定。
与 #5057 的关系(为什么单独立卡)
#5057 把
viewMode列进「schema 上零命中」的假键表,并据此要求 README 把它改写成「纯组件 prop」。实测该表这一行不成立:命中数是 1,不是 0。#5057 卡面的复核命令只写了裸schema\.viewMode,看不见(schema as any).viewMode强转形;其正对照dependenciesField恰好是不带强转的读法,所以正对照证明不了扫描器对强转形不盲。cast 感知复测下,该表另外八个键(tasks/onTaskClick/onTaskUpdate/object/nameField/startField/endField/fields)全部成立,只有viewMode这一行被推翻。#5057 明确 ⛔ 不加渲染器能力来把 README 变真,所以渲染器侧的半接线不在 #5057 的完成范围内,单独立卡;反过来 #5057 要怎么描述
viewMode,取决于本卡的裁定结果。顺带同源一条(不构成本卡范围):
readOnly是真 schema 键(ObjectGantt.tsx:1471、:1516),但同样未在ObjectGanttSchema声明、同样走as any读 —— 与viewMode是同一类契约缺口,只是它两个分支都生效,所以没有半接线问题。markers/holidays/skipWeekends/criticalPath/showBaselines/mobileReadOnly/navigation/persistLayout亦同(全部as any读未声明键),规模上像是这个组件的通例而非孤例。复核方式
分级
交 PM 分诊。我不预判档位:一方面这是用户今天可达的静默无效(作者写了
viewMode却只在一种配置下生效,和 #5002 / #5057 同一类「无诊断的静默不生效」);另一方面viewMode从未是被声明的授权键,可以论证成「本就不该被读」的休眠漂移。两个方向我都不该替 PM 定,故不挂finding也不挂pm:queue,请在分诊轮里评级。关联:#5057(同键的 README 半边,其零命中表这一行被本卡推翻)、#5058 / PR #5072(本卡的发现来源)、#5043(缺门禁的根因单;本卡是「索引签名 + passthrough 使假键面无法被任何门禁看见」的实测特例)。