Skip to content

refactor(types,plugin-calendar,plugin-gantt,plugin-list,plugin-view)!: flat object-calendar / object-gantt keys leave the published schemas; producers emit the block - #26411

Merged
objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/project-thread-jzrcdw
Oct 27, 2026
Merged

objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/project-thread-jzrcdw

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Yibo Wang · project thread

Part of #25923 (work item 1 only; items 3, 4 and the FormField follow-up stay on the card).

Clause-②: yes (narrowing)
Metadata-protocol: narrowed
Skills-docs: none — skills/objectui/guides/page-builder.md already authors the gantt block and no skill authors a flat object-calendar or object-gantt key; the two plugin READMEs are corrected here

维护者速读

  • 改了什么:@object-ui/types 里 object-calendar 和 object-gantt 节点不再公开“平铺”的字段映射键(如 startDateField),这些键只能写在 calendar / gantt 块里;ListView 和 ObjectView 生成节点时也改为写块。
  • 为什么改:spec 本来就拒绝这些平铺写法,但我们的类型还公开着它们,objectui validate 因此放行了日历节点上 spec 拒绝的写法,作者会被误导。
  • 风险与回滚:渲染器仍读平铺写法,已有页面照常显示;唯一例外是早于写入校验的存量视图,若其 options.gantt 带有 spec 拒绝的值,现在显示甘特拒绝屏而不是照常绘制(数量未测,由卡片第 3 项的存量行盘点来数)。另一风险在于手写 TS 代码里写了平铺键的会编译失败(按 changeset 里的迁移改成块即可)。回滚就是 revert 本 PR。
  • 先看:ui/packages/plugin-list/src/ListView.tsx(calendar / gantt 两个分支的出块条件)、ui/packages/types/src/zod/objectql.zod.ts(被删的成员)、ui/packages/plugin-gantt/src/ObjectGantt.tsx(ObjectGanttFlatFields)。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/plugin-list/src/__tests__/ListView.gantt-binding-7070.test.tsx packages/types/src/__tests__/gantt-flat-config-declared-keys.test.ts,预期全部通过。
  • 需要你做的:无,席位落地。

What changes

The flat field-mapping keys of object-calendar and object-gantt leave the published @object-ui/types faces (TypeScript interface and tolerant zod mirror) and move to package-local hand-off types next to their one reader (ui/AGENTS.md Commandment #0.1). The producers emit the block.

Moved off the published faces

  • ObjectCalendarSchema: startDateField, endDateField, titleField, colorField, allDayField (5). New home: ObjectCalendarFlatFields / ObjectCalendarHandoff in plugin-calendar/src/ObjectCalendar.tsx.
  • ObjectGanttSchema: the 29 GanttConfig keys (startDateField, endDateField, titleField, progressField, dependenciesField, colorField, borderColorField, parentField, typeField, lockField, objectField, summaryExtent, defaultCollapsedDepth, tooltipFields, baselineStartField, baselineEndField, groupByField, resourceView, assigneeField, effortField, capacity, quickFilters, autoZoomToFilter, timeSegments, interactions, exportFileName, timeZone, dependencyTypes, viewMode), plus the legacy dependencyField alias and the search / searchableFields pair only ListView's toolbar writes (32). New home: ObjectGanttFlatFields (derived from GanttConfig, so the two spellings cannot fork) / ObjectGanttHandoff in plugin-gantt/src/ObjectGantt.tsx.

Kept

  • ObjectCalendarSchema: the calendar block, the dateField / endField aliasKeyRefusal tombstones, and every other node key.
  • ObjectGanttSchema: gantt, staticData, filter, sort, and every other node key.
  • Seat 2's ruling 6412606977 (the FormField index signature) is not touched.

For gantt, the strict authoring face already refused every flat key (its object-gantt arm is the properties bag, ObjectGanttBlockSchema, objectui#10859), so the narrowing there is the published TypeScript and zod export only. For calendar the strict face derived from the declared keys and accepted the five flat; it now refuses them by name, as the spec does.

Producers. ListView (calendar and gantt branches) and ObjectView's generateViewSchema (calendar and gantt cases) emit calendar: {…} / gantt: {…} instead of the flat keys. They emit the block only where the renderer's flat branch drew, so what a view shows is unchanged, with the one exception under Acceptance notes: a calendar once startDateField is declared; a gantt once both dates are, with titleField floored at 'name' as the flat branch floored it.

Renderers are unchanged. getCalendarConfig and getGanttConfig still read the block first and fall back to the flat spelling, now typed on the hand-off type. READMEs of both plugins are corrected (they said the producers emit the flat spelling), and so are the explanatory clauses of the two @objectstack/spec prescriptions that said the same (packages/spec/src/ui/component.zod.ts, rework r3).

Tests

  • @object-ui/types: the pins that declared the flat keys now pin their absence on both published faces and the strict-face refusal (calendar-flat-color-allday-8466, gantt-flat-config-declared-keys, gantt-dependency-field-deprecated-alias, gantt-search-keys-10250, gantt-view-mode-declared re-pointed to gantt.viewMode, object-gantt-properties-bag-10859-b6, calendar-date-alias-refusal-8355).
  • plugin-calendar: calendarUnionReads-8651 pins ObjectCalendarComponentProps['schema'] as ObjectCalendarHandoff and the hand-off key census.
  • plugin-gantt: new ganttHandoffFlatFields-25923 pins the hand-off as exactly GanttConfig's keys plus the three, no overlap with the published interface, the renderer prop typed with it, and the alias's @deprecated tag.
  • plugin-list / plugin-view: the producer tests read the emitted block and red on any flat key coming back; new rows pin "no block without the axis" and the 'name' floor.
  • Reverse check: re-adding colorField to ObjectGanttSchema and startDateField to ObjectCalendarSchema reds calendar-flat-color-allday-8466.test.ts(182), gantt-flat-config-declared-keys.test.ts(316) and zod-mirror-parity.test.ts(4474) under tsc -p tsconfig.test.json, each naming the key or schema.

Acceptance notes

  • Follow-up candidate (not filed): retire the flat fallback read in getCalendarConfig / getGanttConfig and the hand-off types with it. No producer in this tree writes the flat spelling any more; about 80 renderer tests still author flat nodes and would move to the block.
  • The tolerant mirror no longer value-checks the moved keys written flat (ObjectGanttSchema.safeParse({ …, colorField: 5 }) now passes .passthrough()), which is why the declaration reads yes (narrowing) rather than the order's no (narrowing). The strict face and the spec still refuse them; the value check lives in the block (gantt.capacity: 'one' is refused at gantt.capacity).
  • Reading (measured in rework r1; the r1 deviation was withdrawn in r2, Prime Directive Add comprehensive test suite for Zod schema validation #12): getGanttConfig parses a gantt block against the spec's GanttConfigSchema (objectui#13459) and never validated the flat branch. app-shell relays a stored row's options.gantt unvalidated (storedLegacyOptions). So under main's write door, a pre-door stored row whose options.gantt carries a spec-refused value (e.g. viewMode: 'hour') now lands on gantt-invalid-config instead of drawing. The producers emit the block unconditionally and never a flat key; such a row is converted at the store (ADR-0087), not tolerated at a consumer. The population is unmeasured; work item 3 (the stored-row census) on the card is where it gets counted. The prose in ListView.tsx, plugin-view ObjectView.tsx, ObjectGantt.tsx and the changeset (including its ADR-0087 marker, which used to say "nothing that drew before stops drawing") says this.
  • The object-calendar dateField / endField tombstones now prescribe calendar.startDateField / calendar.endDateField (rework r1; this round made the old prescription false). Pinned in calendar-date-alias-refusal-8355 and calendarUnionReads-8651, with a control that the bare canonical is never prescribed.
  • ui/packages/app-shell/src/views/ObjectView.tsx is not touched.
  • @objectstack/spec prescription prose (rework r3): the explanatory clauses of OBJECT_CALENDAR_FLAT_FIELD_GUIDANCE and OBJECT_GANTT_FLAT_CONFIG_GUIDANCE in packages/spec/src/ui/component.zod.ts said ObjectView / ListView emit, or flatten into, the flat spelling. This PR made that false. They now say the producers hand the binding off in the calendar / gantt block, and that the flat spelling is only a stored-document fallback the renderer still reads, not authorable. Nothing else in the prescriptions changes, including the "Write this as a key of the … config object instead" sentence. The changeset now carries '@objectstack/spec': minor. The grade is minor because check-changeset-no-major grades only the root workspace and the narrowing lives in the ui workspace (@object-ui/types), while the spec diff itself is prescription prose. Before editing, I searched for the old wording and found it nowhere else in packages/spec tests, json-schema/, content/docs/ or sdui.manifest.json (only in historical CHANGELOGs), so nothing needed regenerating. Rework r4 also rewrote the JSDoc around those two guidance sets, plus the docblock of the object-gantt props schema. It used to say the producers "emit on their runtime handoff", "EMIT when they flatten options.gantt", call the flat branch the "HOT path", say that ObjectGanttSchema "declares the whole flat face", and call the flat keys "the flatten product". It now says the producers hand off the block, the flat spelling is a stored-document fallback, and the flat keys live on the package-local hand-off types. A sweep of this PR's ui files fixed six more comments of the same kind: ObjectCalendar.tsx, ObjectGantt.tsx, objectql.ts, objectql.zod.ts, calendar-flat-color-allday-8466 and object-gantt-properties-bag-10859-b6. These are comments only; no string, key, example or accept set moved.

Verification

ui vitest (under os-verify-lock)
  • packages/types packages/plugin-calendar packages/plugin-gantt: Test Files 557, Tests 11881 passed / 209 skipped after the strict-face row fix (first run: 5 failed in one file, fixed).

  • packages/plugin-list packages/plugin-view: first run 32 failed (the producer tests reading flat keys); after re-pointing, the 12 failing files plus the types file: Test Files 13/13, Tests 214/214.

  • Final head (254db190, after merging main): pnpm --dir ui exec vitest run packages/types packages/plugin-calendar packages/plugin-gantt packages/plugin-list packages/plugin-view: VERDICT command-exit 0, Test Files 797 passed / 1 skipped, Tests 14252 passed / 209 skipped.

  • Rework r2 head (4676da49): the same five-package command: VERDICT command-exit 0, Test Files 797 passed / 1 skipped, Tests 14252 passed / 209 skipped. type-check and lint on plugin-list / plugin-view / plugin-gantt: exit 0.

Type-check
  • pnpm --dir ui --filter '...@object-ui/types' type-check: every importer Done after building the missing dists (the first two runs failed only on unbuilt @object-ui/auth, layout, plugin-designer, plugin-chatbot, plugin-editor dists; none of the 561 errors named a calendar or gantt key).
  • @object-ui/types tsc -p tsconfig.test.json: exit 0.
@objectstack/spec (rework r3)
  • Under the lock: pnpm --filter '@objectstack/spec^...' --filter @objectstack/spec build && pnpm --filter @objectstack/spec test gave VERDICT command-exit 0, Test Files 728 passed, Tests 22040 passed / 1 todo.

  • Rework r4 head (eba20284): same command, VERDICT command-exit 0, Test Files 728 passed, Tests 22040 passed / 1 todo.

Dispatch gates
  • node scripts/pm/dispatch-gates.mjs --commands on the merged head: 35 commands, all exit 0 (check:type-check-debt first exited 3 while the dogfood run held the verify lock, then 0 on re-run). --ran: 35 accounted, 0 NOT-MEASURED.

  • check-sdui-manifest first exited 1 (registry sources moved); node scripts/gen-sdui-manifest-node.mjs regenerated it. Main has since retired the per-package records (ci(sdui): retire the per-package SDUI manifest records; CI's regeneration holds freshness (#26137 PR B) #26367), so nothing of that lands here.

  • Rework r2 head (4676da49): 35 commands, all exit 0; --ran: 35 run, 0 NOT-MEASURED.

  • Rework r3 head (d5d316b1, after merging origin/main 00a4593a): --commands derived 82 commands. The first pass gave 81 exit 0 and 1 exit 3: node scripts/check-plugin-teardown-shape.mjs --self-test, whose positive-control commit 621a4876 was missing from the shallow clone. After fetching that commit, the re-run exited 0. --ran: 82 run, 0 NOT-MEASURED.

  • Rework r4 head (eba20284): 82 commands, all exit 0; --ran: 82 run, 0 NOT-MEASURED.

Dogfood battery (title carries `!`)

🤖 Generated with Claude Code

https://claude.ai/code/session_01WGufzetP4CSWdmfU9t887J

…aces; producers emit the blocks

Claude-Session: https://claude.ai/code/session_01WGufzetP4CSWdmfU9t887J
Co-authored-by: Claude <noreply@anthropic.com>
…ck spelling; hand-off pins in plugin-gantt

The published faces no longer declare the flat keys, so the pins that read
them there now pin their absence, and the producer tests read the emitted
calendar / gantt block. READMEs and the changeset follow.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzetP4CSWdmfU9t887J
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tests tooling labels Oct 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 254db1904682778aa0a2f9c2d5a362967eb8b403
Local-runs: none
Scope: ui/packages/types/src/objectql.ts, ui/packages/types/src/zod/objectql.zod.ts, ui/packages/types/src/tests/calendar-date-alias-refusal-8355.test.ts, ui/packages/types/src/tests/calendar-flat-color-allday-8466.test.ts, ui/packages/types/src/tests/gantt-dependency-field-deprecated-alias.test.ts, ui/packages/types/src/tests/gantt-flat-config-declared-keys.test.ts, ui/packages/types/src/tests/gantt-search-keys-10250.test.ts, ui/packages/types/src/tests/gantt-view-mode-declared.test.ts, ui/packages/types/src/tests/object-gantt-properties-bag-10859-b6.test.ts, ui/packages/plugin-calendar/src/ObjectCalendar.tsx, ui/packages/plugin-calendar/src/tests/calendarUnionReads-8651.test.tsx, ui/packages/plugin-gantt/src/ObjectGantt.tsx, ui/packages/plugin-gantt/src/tests/ganttHandoffFlatFields-25923.test.ts, ui/packages/plugin-list/src/ListView.tsx, ui/packages/plugin-list/src/tests/ListView.calendar-binding-7029.test.tsx, ui/packages/plugin-list/src/tests/ListView.calendarAliasRefused-8355.test.tsx, ui/packages/plugin-list/src/tests/ListView.calendarDateWindow-12081.test.tsx, ui/packages/plugin-list/src/tests/ListView.gantt-binding-7070.test.tsx, ui/packages/plugin-list/src/tests/ListView.ganttNavigationForward-7334.test.tsx, ui/packages/plugin-list/src/tests/ListView.readersRetired-6152.test.tsx, ui/packages/plugin-list/src/tests/ListViewBlock.specInputs-13047.test.tsx, ui/packages/plugin-view/src/ObjectView.tsx, ui/packages/plugin-view/src/tests/ObjectView.calendarAliasRefused-8355.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.calendarBinding-7029.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.ganttBinding-7070.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.namedViewProtocolKeys-8980.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.readersRetired-6152.test.tsx + .changeset/25923-calendar-gantt-flat-keys-handoff.md, ui/packages/plugin-calendar/README.md, ui/packages/plugin-gantt/README.md (prose faces, read for the claims the hunks make), ui/packages/types/src/strict-authoring-face.ts, ui/packages/types/src/index.ts (ObjectCalendarBlockConfig export), packages/spec/src/ui/view.zod.ts (GanttConfigSchema, CalendarConfigSchema), packages/spec/src/ui/component.zod.ts (ObjectCalendarPropsSchema, ObjectGanttPropsSchema, ComponentPropsMap rows object-calendar / object-gantt), ui/packages/core/src/utils/gantt-config-spelling.ts (GANTT_CONFIG_SPELLING), ui/packages/plugin-gantt/src/index.tsx, ui/packages/plugin-calendar/src/index.tsx (package export surfaces), ui/scripts/check-doc-snippet-types.mjs (the gate the README fence is an input of), scripts/check-changeset-no-major.mjs, scripts/check-adr-0087-registration.mjs (level and disposition rules)

Inputs read: card #25923 body and all 11 comments (rulings 6412606977, premise 6430806588, claim 6430908258, os-dev-report 6432621418 included); PR #26411 body, 30-file list, net diff against main; check-runs on the head, read 2026-10-27T03:00Z, 03:06Z, 03:07Z and 03:09Z (readings in ③). Nothing built, run or re-run locally.

① Derived judgments

  • D1 — RIGHT. Published TypeScript ObjectGanttSchema (ui/packages/types/src/objectql.ts) loses 32 members: the 29 GanttConfig keys, dependencyField, search, searchableFields; the zod mirror (ui/packages/types/src/zod/objectql.zod.ts) loses the same 32 (6 scalar rows + 24 stripImportedDefaults(SpecGanttConfigSchema).shape.* rows + the search pair). The two faces move together; gantt, staticData, filter, sort and the node-level keys stay on both. zod-mirror-parity.test.ts pairs the two (rows 1994/2205) and is untouched, so the ratchet reads zero drift for this pair by construction.
  • D2 — RIGHT. ObjectCalendarSchema loses the five flat field-name keys on both faces; the dateField / endField aliasKeyRefusal tombstones stay. strict-authoring-face.ts closes every object with catchall: z.never() over the declared shape, and ObjectCalendarSchema is an arm of AnyComponentSchema (objectql.zod.ts:4324), so the strict face now refuses the five on object-calendar by name — the card's defect (1). The spec row ObjectCalendarPropsSchema (component.zod.ts:6226) declares objectName, calendar, defaultView, filter, sort, data, staticData, locale, loading, navigation and no flat key: the mirror now agrees with it.
  • D3 — RIGHT. For object-gantt the strict arm is ObjectGanttBlockSchema (objectql.zod.ts:5535, the properties bag), which already refused every flat key; the narrowing there is the published TS and zod export only, as the body says. The spec row ObjectGanttPropsSchema (component.zod.ts:7658) carries gantt plus node-level keys and no GanttConfig key, no search pair: the 32 moved are exactly the keys the row refuses, and the 10 node-level keys the premise comment counted (skipWeekends … mobileReadOnly) stay, as the row declares them.
  • D4 — RIGHT. The tolerant mirror's accept set WIDENS for wrong-typed flat values: ObjectGanttSchema.safeParse({…, colorField: 5}) and ObjectCalendarSchema.safeParse({…, colorField: 0xff0000}) now pass under .passthrough() where both refused before (pinned in gantt-flat-config-declared-keys and calendar-flat-color-allday-8466). So the diff both narrows (TS, strict face) and widens (tolerant mirror) a published accept set; Clause-②: yes (narrowing) is the right declaration, and the dev was right to deviate from the order's no. Value validation of the moved keys now lives only in the block (gantt.capacity: 'one' refused at gantt.capacity).
  • D5 — RIGHT, with a note. Hand-off types: ObjectCalendarFlatFields = Partial of Pick of ObjectCalendarBlockConfig over the five (ObjectCalendarBlockConfig is exported from ui/packages/types/src/index.ts:530) and ObjectGanttFlatFields = Partial of Pick of GanttConfig over KnownGanttConfigKey, plus dependencyField / search / searchableFields (KnownGanttConfigKey is declared later in the same file, ObjectGantt.tsx:541; a type alias, so order is fine). ObjectCalendarComponentProps['schema'] and ObjectGanttProps['schema'] widen to the hand-off — a public-surface WIDENING of plugin-calendar and plugin-gantt, non-breaking for their consumers. Note: neither ObjectGanttFlatFields / ObjectGanttHandoff nor ObjectCalendarFlatFields / ObjectCalendarHandoff is re-exported from the package index (plugin-gantt/src/index.tsx exports only ObjectGanttProps, QuickFilterDef; plugin-calendar/src/index.tsx exports ObjectCalendarComponentProps and its alias). The changeset names them as the "new home" but its Fix line correctly tells a host to type the node as ObjectGanttProps['schema'] / ObjectCalendarProps['schema']; read as a package-local type reachable through the props, this is right and matches Commandment #0.1.
  • D6 — RIGHT. Producer conditions mirror the renderer's flat branch exactly: getCalendarConfig (ObjectCalendar.tsx) takes the flat spelling only if (schema.startDateField); ListView and ObjectView emit calendar: {…} only when a start binding is declared. getGanttConfig (ObjectGantt.tsx) takes the flat spelling only when startDateField && endDateField and floors titleField: schema.titleField || 'name'; both producers emit gantt: {…} only with both dates and floor titleField to 'name'. The ListView gantt block is pickFlatMapConfig({...options.gantt, ...gantt}, GANTT_CONFIG_SPELLING) (values travel as written; the table is total over the spec's block keys, ui/packages/core/src/utils/gantt-config-spelling.ts) then the three axis keys, so a bag titleField reaches the block as the old spread let it through. The node keeps search / searchableFields from the toolbar (ListView.tsx:4380-4382, node typed any), which the renderer reads off the hand-off (ObjectGantt.tsx:944-946). Consistent.
  • D7 — WRONG (gate red, caused by the diff). Removing colorField?: SpecCalendarConfig['colorField'] from ObjectCalendarSchema removed the only use of the import CalendarConfig as SpecCalendarConfig at ui/packages/types/src/objectql.ts:157, which the diff left in place. The head file carries no other use of SpecCalendarConfig (lines 151-280 read). Lint UI on the head = failure, step 30 "Lint the ui packages": ESLint error 'SpecCalendarConfig' is defined but never used at ui/packages/types/src/objectql.ts:157 (the only error-level annotation on the run). Fix: drop the import line (the sibling GanttConfig as SpecGanttConfig stays, it is still used at objectql.ts:280).
  • D8 — WRONG (public claim contradicted by the package's own shipped README; gate red). The body says "READMEs of both plugins are corrected", but the diff edits plugin-gantt/README.md only at lines 320-330. At lines 833-836 the README still teaches that a directly mounted node is "typed ObjectGanttSchema, with its props on the node", and the tsx fence at line 838 annotates a flat literal — titleField, startDateField, endDateField, progressField on the node — as const schema: ObjectGanttSchema. After this diff that is exactly the compile error the changeset's BREAKING paragraph describes. The README ships inside the npm package and is an input of check:doc-snippets (ui/scripts/check-doc-snippet-types.mjs: every packages/**/README.md, fences compiled against the built dist/*.d.ts). Lint UI step 41 "Doc Snippet Type Check — Compile documentation snippets against the built types" = failure on this head (steps 42 and 43, JSDoc examples and JSON fences, passed). The job log was not readable from this seat (403 at the proxy); a scan of every .md / .mdx fence under ui/ finds this fence as the one literal annotated ObjectGanttSchema or ObjectCalendarSchema that carries a moved key flat, and the six ObjectCalendarSchema fences in ui/content/docs/plugins/plugin-calendar.mdx all write the calendar block already. Fix: type the fence ObjectGanttProps['schema'] (the hand-off, which the diff itself prescribes for a host that mounts the chart directly) or move the four keys into gantt: {…}, and correct the prose at 833-836.
  • D9 — RIGHT. Behaviour of a stored view is unchanged on screen, with one honest exception the body names: the emitted gantt block is now safeParsed against the spec's strict GanttConfigSchema (with groupByField's whitespace superRefine), where the flat path never validated. A legacy options.gantt bag carrying a value the spec refuses would reach the refusal screen instead of drawing. No fixture in the tree; carried to ③.
  • D10 — RIGHT. No other importer of the two interfaces reads a removed member: plugin-dashboard/ObjectPivotTable.tsx, core/utils/record-source.ts, plugin-timeline/ObjectTimeline.tsx (its own schema's keys), console public-block-binding-reach, schema-catalog objectql-record-source-render-identity-6939 (reads properties.gantt.titleField), plugin-tree schemaTyped-8655 and the spec pin component-objectui-held-typed-members were grepped; none writes or reads a flat object-gantt / object-calendar key off these types. The six app-shell / console tests that mount calendar or gantt views read no flat emitted key. ObjectGanttRenderer / ObjectCalendarRenderer keep schema: any.
  • D11 — RIGHT. The strict-face refusals are pinned on both node types (calendar: unrecognized_keys naming the key; gantt: the bag arm), with firing controls (the same keys accepted inside the block). The producer tests red on any flat key coming back and pin "no block without the axis" and the 'name' floor, in both plugin-list and plugin-view.

② Semver level

  • Changeset .changeset/25923-calendar-gantt-flat-keys-handoff.md: minor on @object-ui/types, plugin-calendar, plugin-gantt, plugin-list, plugin-view; Clause-②: yes (narrowing); Metadata-protocol: narrowed; BREAKING banner with FROM → TO for both node types and the hand-off typing for a direct mount; ADR-0087 marker not-required (no-migration-prescription), a real category (scripts/check-adr-0087-registration.mjs:147, 554), argued on facts (no spec key or stored shape moves; the spec rows already refuse the keys). The PR body carries the same two lines and the title carries !.
  • Level: under ruling A (2026-10-21, scripts/check-changeset-no-major.mjs) the level follows the Metadata-protocol kind — narrowed takes at least minor on a moved package — and the launch-window convention ships a breaking change as minor with the banner and the ADR-0087 disposition as the carriers. minor ×5 is right; Check Changeset on the head = success.
  • Kind: narrowed is right — the strict authoring face (objectui validate) now refuses five keys it accepted on object-calendar, and the published TS refuses 37; a key refused for an author is narrowed.
  • Clause-②: line: yes (narrowing) on body and changeset, consistent with each other, and the right reading per D4 (the tolerant mirror widens for wrong-typed flat values while the TS and strict faces narrow). It differs from the dispatch order's no; the order should be read as superseded by the measurement the dev reported in deviations, not the other way round.
  • Reviewed-face note: the paths decide no face (record-recognisers --paths on the 30 files), so the changeset's (narrowing) arm decides it — narrowing face, this tier.

③ Boundary flags

  • Dev deviation 1 (yes (narrowing) vs ordered no): ANSWERED — the dev's line is right (D4, ②).
  • Dev deviation 2 (level minor, ADR-0087 not-required): ANSWERED — right (②).
  • Dev deviation 3 (sdui-manifest regeneration commits dropped at merge): ANSWERED — main retired the per-package records in ci(sdui): retire the per-package SDUI manifest records; CI's regeneration holds freshness (#26137 PR B) #26367 (origin/main e931621 carries it), and this diff touches no registry index.tsx, so nothing is owed here.
  • Dev deviation 4 (two local commits reworded before first push): n/a — no published history rewritten.
  • open_questions: none in the os-dev-report; none owed.
  • Acceptance note — follow-up candidate not filed (retire the flat fallback in getCalendarConfig / getGanttConfig and the hand-off types, about 80 renderer tests author flat nodes): ESCALATED to the seat — record it on the card or file it; the diff is right to leave the fallback (the hand-off is what keeps a direct mount drawing).
  • Acceptance note — tombstone wording: aliasKeyRefusal('dateField', 'startDateField', 'this object-calendar node', …) (objectql.zod.ts:1974-1975) still prescribes startDateField, a key the strict face now refuses flat on that node. An author following the prescription lands on a second refusal. ESCALATED as a small follow-up row (calendar.startDateField / calendar.endDateField); not a FAIL reason, the refusal itself is right.
  • Acceptance note — legacy options.gantt values now validated (D9): ESCALATED as an accepted behaviour change; no fixture exists; the refusal screen names the path.
  • Dogfood battery (title !): dev ran it on the merged tree: exit 1, 2 files red (test/authz-conformance.test.ts [finding] authz-conformance.matrix.ts:27 states the route ledger holds "94 rows / 19 families" in the present tense — measured 91 today, and the cause is #14503, not the gate move it sits next to #17111 ledger pin, test/authz-probe-blind-spot.test.ts authz-conformance discover() reaches 1 of 17 route registrars in rest-server.ts — the ratchet's route-completeness guarantee is false for 66 of 85 mounted routes #13260 census of packages/rest/src/rest-server.ts, "expected 70 to be 69"), "not re-measured on main". The diff touches no packages/rest and no @objectstack/* package, so by surface the red is main's, but the dev did not confirm that; CI Dogfood Regression Gate on the head = success with its shards skipped by the path filter, so CI did not measure it either. ESCALATED to the seat: confirm on main (the dev listed it as an out-of-scope finding, class a).
  • Gate readings on the head (times UTC, 2026-10-27): at 03:00 — success: Auto Label, Build UI Deps, Check Changeset, Check PR Size, Dogfood Regression Gate, Governed Surface Queue Guard, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, Test Core, The card this PR closes must claim this branch, Type Check · debt ledger, filter; skipped: 18.x upgrade harness, Build Core, Build Docs, Dogfood shards, Dogfood Verify CLI, Packed-tarball smoke, Plan Test Core shards, Temporal Conformance, Test Core shards; in_progress: Build & E2E UI, Lint UI, the nine Lint · jobs, Test UI 1-16, Type Check UI, Type Check · consumer / source / workspace. At 03:06 — Lint UI = failure (03:03:41Z; steps 30 and 41 failed, see D7 and D8); every Lint · job, Build & E2E UI and Type Check · consumer / source / workspace = success. At 03:09 — still in_progress: Test UI 1, 2, 5, 8, 9, 11, 13 of 16 and Type Check UI; the other nine Test UI shards = success. An in_progress gate is recorded as unread, not as a pass.

Two FAIL reasons, both mechanical and both inside the declared surface: the unused SpecCalendarConfig import (D7, ui/packages/types/src/objectql.ts:157) and the plugin-gantt README fence that annotates a flat node as ObjectGanttSchema (D8, ui/packages/plugin-gantt/README.md:838, prose 833-836). The contract judgments themselves (D1-D6, D9-D11, ②) hold; on a head that fixes the two and shows Lint UI, Type Check UI and Test UI green, this reviewer expects PASS.

Implemented-by: claude/project-thread-jzrcdw
Reviewed-by: session_01WGufzgbJhFoXYsLSe5WSPP

VERDICT: FAIL

…ng, point the calendar tombstones at the block

- ListView and ObjectView hand a gantt binding the spec's GanttConfigSchema
  refuses flat, as before, since getGanttConfig validates a gantt block and
  would show its refusal screen where the flat branch drew.
- The object-calendar dateField / endField tombstones prescribe
  calendar.startDateField / calendar.endDateField.
- Drop the unused SpecCalendarConfig import; the plugin-gantt README writes
  the gantt block.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzetP4CSWdmfU9t887J
…ombstone prescription

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzetP4CSWdmfU9t887J
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Test UI (7/16) is red on head 89e13213 for a reason that is not this PR's.

The two failing rows are ui/scripts/__tests__/doc-version-claims.test.ts ("implausibly few node-version declarations were read from ../.github/workflows", expected 1 to be ≥ 15). #26419 moved the Node version out of the workflows, and the test still counts literal node-version: lines there. The same file fails the same two rows on origin/main at 75afeffd (run locally: 2 failed / 27 passed). It is tracked in #26421, which has no fix on main yet, so there is nothing to port. This diff touches no .github/ file and nothing under ui/scripts/. Every other check on this head is green, including Lint UI, Type Check UI and the other 15 Test UI shards.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 89e13213a9a9b878e408f0aa83cb12b139b1ac18
Local-runs: none
Scope: ui/packages/types/src/objectql.ts, ui/packages/types/src/zod/objectql.zod.ts, ui/packages/types/src/tests/calendar-date-alias-refusal-8355.test.ts, ui/packages/types/src/tests/calendar-flat-color-allday-8466.test.ts, ui/packages/types/src/tests/gantt-dependency-field-deprecated-alias.test.ts, ui/packages/types/src/tests/gantt-flat-config-declared-keys.test.ts, ui/packages/types/src/tests/gantt-search-keys-10250.test.ts, ui/packages/types/src/tests/gantt-view-mode-declared.test.ts, ui/packages/types/src/tests/object-gantt-properties-bag-10859-b6.test.ts, ui/packages/plugin-calendar/src/ObjectCalendar.tsx, ui/packages/plugin-calendar/src/tests/calendarUnionReads-8651.test.tsx, ui/packages/plugin-gantt/src/ObjectGantt.tsx, ui/packages/plugin-gantt/src/ObjectGantt.legacyFlatDraws-25923.test.tsx, ui/packages/plugin-gantt/src/tests/ganttHandoffFlatFields-25923.test.ts, ui/packages/plugin-list/src/ListView.tsx, ui/packages/plugin-list/src/tests/ListView.calendar-binding-7029.test.tsx, ui/packages/plugin-list/src/tests/ListView.calendarAliasRefused-8355.test.tsx, ui/packages/plugin-list/src/tests/ListView.calendarDateWindow-12081.test.tsx, ui/packages/plugin-list/src/tests/ListView.gantt-binding-7070.test.tsx, ui/packages/plugin-list/src/tests/ListView.ganttNavigationForward-7334.test.tsx, ui/packages/plugin-list/src/tests/ListView.readersRetired-6152.test.tsx, ui/packages/plugin-list/src/tests/ListViewBlock.specInputs-13047.test.tsx, ui/packages/plugin-view/src/ObjectView.tsx, ui/packages/plugin-view/src/tests/ObjectView.calendarAliasRefused-8355.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.calendarBinding-7029.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.ganttBinding-7070.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.namedViewProtocolKeys-8980.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.readersRetired-6152.test.tsx + .changeset/25923-calendar-gantt-flat-keys-handoff.md, ui/packages/plugin-calendar/README.md, ui/packages/plugin-gantt/README.md (prose faces, read for the claims the hunks make), ui/packages/types/src/strict-authoring-face.ts, ui/packages/types/src/zod/tombstone.zod.ts (aliasKeyRefusal), ui/packages/types/src/index.ts (ObjectCalendarBlockConfig export), packages/spec/src/ui/view.zod.ts (GanttConfigSchema, CalendarConfigSchema, ListViewSchema.gantt, the view-item options z.never), packages/spec/src/ui/component.zod.ts (ObjectCalendarPropsSchema, ObjectGanttPropsSchema, ComponentPropsMap rows), ui/packages/core/src/utils/gantt-config-spelling.ts, ui/packages/plugin-gantt/src/GanttView.tsx (viewMode seeding), ui/packages/app-shell/src/views/ObjectView.tsx and ui/packages/app-shell/src/views/objectViewOptions.ts (the options.gantt relay, storedLegacyOptions, ganttViewOptions), ui/packages/plugin-gantt/src/index.tsx, ui/packages/plugin-calendar/src/index.tsx, ui/AGENTS.md Commandment #0.1

Inputs read: card #25923 body and all 12 comments (rulings 6412606977, premise 6430806588, claim 6430908258, os-dev-report 6432621418, the r1 record is on the PR); PR #26411 body, 31-file list, both PR comments (record 6432801280 on 254db19 = FAIL; the author's Test UI note 6433437322), the net diff against main and the incremental diff 254db19...89e1321; the 61 check-runs on the head, read 2026-10-27T04:06Z; main's check-runs on 08ed7e2, 75afeff, d15f99d, 9494dd8 and 26e7c75 for the Test UI comparison. Nothing built, run or re-run locally. record-recognisers --template --paths on the 31 files: the paths decide no face; the changeset's (narrowing) arm decides it — narrowing face, this tier.

① Derived judgments

  • D1 — r1 reason D7 CLOSED. The incremental diff drops the import CalendarConfig as SpecCalendarConfig (objectql.ts:157 on 254db19); the head file carries no other use of SpecCalendarConfig or of the retired objectCalendarFlatField. Lint UI on the head = success (03:56:28Z).
  • D2 — r1 reason D8 CLOSED. plugin-gantt/README.md:852-859 now annotates the direct-mount literal with gantt: { titleField, startDateField, endDateField, progressField }, the prose at 834-836 says the flat spelling is no member of the type, and the second fence at 602-606 is nested too. Every other startDateField:-bearing fence in both READMEs sits inside a gantt: / properties.gantt: / calendar: block (gantt README 146, 342, 528, 624, 676, 913; calendar README 348). Lint UI's doc-snippet step rides inside the green job.
  • D3 — r1 escalation (tombstone wording) CLOSED by the head. CalendarNodeDateAliasRefusals (objectql.zod.ts:1975-1986) now prescribe calendar.startDateField / calendar.endDateField in both the pointer and the Write … clause; aliasKeyRefusal (tombstone.zod.ts:185) only interpolates its canonical argument into the message, so a dotted path is a legal value. Pinned with a control that the bare key is prescribed nowhere (calendar-date-alias-refusal-8355:382-392; calendarUnionReads-8651:481-487). The changeset's @object-ui/types bullet now says so.
  • D4 — RIGHT, unchanged from r1 (D1-D6, D10, D11 there): the two published faces of ObjectGanttSchema lose the same 32 members and ObjectCalendarSchema the same five; the strict face (strict-authoring-face.ts:251-261, catchall: z.never() over the declared shape, ObjectCalendarSchema an arm of AnyComponentSchema at objectql.zod.ts:4334) refuses the five on object-calendar by name, as ObjectCalendarPropsSchema (component.zod.ts:6226) does; the object-gantt arm is still ObjectGanttBlockSchema (objectql.zod.ts:5545), which already refused every flat key; the hand-off types ObjectCalendarFlatFields / ObjectGanttFlatFields are derived from the block types and type the renderers' props. The tolerant mirrors widen for wrong-typed flat values (passthrough), so Clause-②: yes (narrowing) stays the right declaration.
  • D5 — RIGHT: the calendar producers (ListView.tsx:4200-4212, plugin-view ObjectView.tsx:2334-2345) always nest, only once startDateField is declared, and getCalendarConfig (ObjectCalendar.tsx:343-371) does not validate a block, so no refusal-screen asymmetry exists on the calendar side.
  • D6 — WRONG (the r1 deviation): ListView.tsx:4444-4446 and plugin-view ObjectView.tsx:2460-2462 nest the gantt block only when GanttConfigSchema.safeParse(block).success, else spread it flat. Read against the cited code this is a silent-tolerance branch of exactly the shape Prime Directive Add comprehensive test suite for Zod schema validation #12 and ui/AGENTS.md #0.1 forbid, not a neutral restatement of the old behaviour:
    • It is a lenient fallback in a consumer, added to tolerate off-spec input. The branch's only function is to detect a spec refusal and route the value around the one place that would refuse it (getGanttConfig, ObjectGantt.tsx:663-671). The flat spelling the card exists to retire is thereby fossilised as the producers' second de-facto contract for every invalid value — the "second spelling alive at the producer" that the director-seat ruling quoted in the same file (ListView.tsx:4178-4183) refuses as "the lenient alias AGENTS.md #0.1 names".
    • It is silent end to end. No warning is logged by either producer; warnOnShadowedFlatGanttKeys cannot fire (no gantt key on the node); the renderer's flat branch is "deliberately NOT safeParse'd" (ObjectGantt.tsx:673-678); and GanttView seeds an off-enum viewMode to the restored layout or 'day' with no diagnostic (GanttView.tsx:1011-1017). The spec refuses viewMode: 'hour' (view.zod.ts:2270); the author sees a chart.
    • It protects an unmeasured population. The spec's view-level gantt IS GanttConfigSchema (view.zod.ts:3033) and the view item's options is z.never (view.zod.ts:5652), so no spec-valid row can carry the value; the write door already judges each options.KIND block against that kind's block schema and refuses out-of-contract values by name (objectViewOptions.ts:357-360, ruling A on objectui#10380). What is left is a pre-door row nobody has counted — the card's work item 3 is the census (未实测), and the r1 record noted "no fixture in the tree". The fixture here (viewMode: 'hour') is invented, not measured.
    • It is the wrong layer by the rule's own text: a legacy stored shape is accommodated by an ADR-0087 conversion-layer entry replayed through applyConversionsToStoredItem, "so a consumer never needs its own accommodation for a legacy stored shape either" (Prime Directive Add comprehensive test suite for Zod schema validation #12).
    • It reverses a reviewed direction without a ruling. The r1 record escalated the refusal-screen outcome (its D9) to the seat as an accepted behaviour change whose "refusal screen names the path"; the card carries no seat ruling after that record, and the dev resolved the escalation the other way inside the rework.
      Fix: put { gantt: ganttBlock } back in place of the ternary in both files, drop the two GanttConfigSchema imports, delete the three "goes out flat" rows (ListView.gantt-binding-7070:468-495, ObjectView.ganttBinding-7070:243-268, ObjectGantt.legacyFlatDraws-25923.test.tsx) and restore the two 6152 rows to read node.gantt — the PR body's own Revert paragraph. A legacy value that is then measured to exist is a work-item-3 migration or an ADR-0087 conversion entry, never a producer branch. If the seat instead rules the tolerance in, it must be declared (a named entry, a dev-mode diagnostic naming the dropped value) and D7 corrected in full; that is a seat decision, not a deviation.
  • D7 — WRONG (declared ≠ enforced in the PR's own hunks and body). Prose this diff adds asserts a producer behaviour the same diff does not deliver: ObjectGantt.tsx:311-316 ("Both producers … emit the gantt block; the flat branch keeps a host …"), :548-551 ("Both now hand the view's block on as the node's gantt, so the block branch is the producers' path"), :590-593 ("both producers emit the gantt block and NO flat key, so their output reaches the block branch"), :632-636 ("The producers' path is branch 1 since objectstack#25923 … Branch 2 stays for a host"), :673-675 ("what ObjectView / ListView emitted until objectstack#25923, kept for a host"); objectql.ts:3874-3880 ("both producers … emit it"); objectql.zod.ts:2917-2922 and :4937-4941 ("Since objectstack#25923 neither carries a flat GanttConfig key"); plugin-gantt/README.md:326-329 ("ObjectView and ListView write the gantt block onto the node they compose. The flat read is kept for a host"); the PR body's acceptance note "No producer in this tree writes the flat spelling any more"; and the test guard comments "never flat" (ListView.gantt-binding-7070:108-111, ObjectView.ganttBinding-7070:82-88). Under D6 both producers emit flat GanttConfig keys and no gantt key for any block the spec refuses, and that output takes branch 2. Only the changeset's last sentence and the two inline ternary comments say so. An agent reading the renderer's docblocks would conclude the flat branch is unreachable from the product and retire it — the acceptance-note follow-up this PR itself proposes — and break the deviation. Closing D6 the first way makes every one of these lines true again; closing it the second way requires rewriting each.
  • D8 — RIGHT: the 6152 "every declared key reaches the gantt" rows were re-pointed to read flat because their BLOCK_k / V_k placeholder strings are spec-refused values (ListView.readersRetired-6152:193-198, ObjectView.readersRetired-6152:131-137). That is an honest consequence of D6, and it means the two rows now pin the tolerance path rather than the block path: with D6 removed they must go back to node.gantt, as the body's Revert paragraph says.
  • D9 — RIGHT, no other importer regressed: ObjectGanttProps['schema'] / ObjectCalendarComponentProps['schema'] widen to the hand-off types (plugin-gantt and plugin-calendar public surfaces widen, non-breaking); Type Check UI, TypeScript Type Check and Build & E2E UI are green on the head; KnownGanttConfigKey is declared at ObjectGantt.tsx:541 and read at :320 (type alias, order fine); ObjectCalendarBlockConfig is exported from types/src/index.ts:530.

② Semver level

  • Changeset .changeset/25923-calendar-gantt-flat-keys-handoff.md: minor on @object-ui/types, plugin-calendar, plugin-gantt, plugin-list, plugin-view; Clause-②: yes (narrowing); Metadata-protocol: narrowed; ADR-0087 marker not-required (no-migration-prescription) argued on facts; BREAKING banner with FROM → TO for both node types. The PR body carries the same two lines and the title carries !. Check Changeset on the head = success (the 04:04Z re-run of that check is in_progress and recorded as unread).
  • Level: narrowed takes at least minor on a moved package; the breaking change ships as minor with the banner and the ADR-0087 disposition as carriers (scripts/check-changeset-no-major.mjs ruling A). minor ×5 is right.
  • Kind: narrowed is right (a key refused for an author on object-calendar by the strict face; 37 keys off the published TypeScript).
  • Clause-②: yes (narrowing) on body and changeset, consistent with each other and with D4; the dispatch order's no is superseded by the measured mirror widening, as r1 found.
  • The r1 rework does not move the level or the kind: the D6 branch changes no published type and no authoring face; it changes what the runtime emits for off-spec stored rows, which is the D6/D7 finding, not a semver one. The changeset's last plugin-list / plugin-view sentence is the one place the deviation is declared; under the fix in D6 that sentence is deleted.

③ Boundary flags

  • Dev deviations 1-4 of os-dev-report 6432621418 (yes (narrowing); minor + not-required; sdui-manifest commits dropped; reworded local commits): ANSWERED as in r1 — all right, nothing owed.
  • r1 reasons D7 (unused import) and D8 (README fence): CLOSED (D1, D2 above).
  • r1 escalation, tombstone wording: CLOSED (D3).
  • r1 escalation, dogfood battery red on the branch tree (authz-conformance, authz-probe-blind-spot): ANSWERED by the dev's re-measure in the body — on origin/main 9a0108c the two files read 108/108; on the branch they red only because its base predates test(dogfood): re-measure the authz census for the approval record-lock read #26406's census re-measure; this diff touches no packages/rest and no @objectstack/* package; Dogfood Regression Gate on the head's merge ref = success (its shards path-filtered). Nothing owed.
  • r1 escalation, acceptance-note follow-up (retire the flat fallback read and the hand-off types, not filed): STILL OPEN, and D6 changes its standing: with the ternary in place the flat fallback is load-bearing for the producers, so the follow-up as written cannot land and the acceptance note that motivates it ("No producer in this tree writes the flat spelling any more") is false. Resolve with D6.
  • r1 escalation D9 (legacy options.gantt value now validated, refusal screen): REOPENED as this record's D6 — the dev answered the seat's open escalation by reversing it inside the rework, with no ruling on the card. It is the one FAIL reason of substance; D7 is its prose shadow.
  • New r1 deviation (safeParse ternary, declared in body and changeset, pinned by three rows): ⛔ REFUSED (D6, D7). A declared, tested tolerance is still a tolerance; declaration in a changeset does not satisfy Prime Directive Add comprehensive test suite for Zod schema validation #12's requirement that an accommodation of off-spec input be a conversion-layer entry rather than a consumer branch.
  • Test UI (7/16) red on the head: NOT this PR's, verified against main's check-runs rather than the author's word. Head job 121997448192 fails at step 11 "Run the ui tests (shard 7/16)" with two annotations, ui/scripts/__tests__/doc-version-claims.test.ts:2893 ("implausibly few node-version declarations were read from ../.github/workflows … expected 1 to be greater than or equal to 15") and :2917. main commit 08ed7e2 (push run on main, 03:43Z-03:55Z) fails Test UI (12/16), job 121996472335, at the same step name with the same two rows and the same first error line; the queue run that landed ci: one setup-workspace composite action replaces the setup steps copied into 38 jobs, and pull requests stop saving the pnpm store #26419 (d15f99d, job 121989980214) fails Test UI (8/16) the same way; an earlier main run on 08ed7e2 (03:23Z) failed Test UI (2/16) the same way. The shard number differs per run because vitest's sharding re-partitions the file set — the failing step and first error line are identical. The author's cited 75afeffd is a skills-only commit whose UI jobs were path-skipped, so it is not evidence; 08ed7e2 and d15f99d are. Cause: ci: one setup-workspace composite action replaces the setup steps copied into 38 jobs, and pull requests stop saving the pnpm store #26419 (merged 03:41Z) moved 58 node-version: lines to .nvmrc; tracked in ui/scripts: doc-version-claims anchors the docs' Node version on literal node-version: lines in .github/workflows, so #26419 (Node read from .nvmrc) turns it red #26421 with no fix on main. This diff touches no .github/ and nothing under ui/scripts/. Test UI is advisory (not one of the eight required contexts), so it blocks nothing; it is recorded here so the next reader does not re-derive it.
  • Gate readings on the head (UTC, 2026-10-27, read 04:06Z): success — Lint & Repo Gates, TypeScript Type Check, Test Core, Governed Surface Queue Guard, Lint UI, Type Check UI, Build & E2E UI, Build UI Deps, Check Changeset (03:47Z), Check PR Size, Dogfood Regression Gate, Auto Label, the nine Lint · jobs, Type Check · consumer / debt / source / workspace, Test UI 1-6 and 8-16, the four claim/part-of guards, filter; failure — Test UI (7/16) (base red, above); skipped by path or opt-in — Build Core, Build Docs, Test Core shards, Dogfood shards, Dogfood Verify CLI, Temporal Conformance, 18.x harness, Packed-tarball smoke; in_progress — a second Check Changeset run started 04:04Z (unread). Every required context is green; the verdict below is on the contract, not the gates.

One FAIL reason of substance and its prose shadow, both inside the declared surface: the GanttConfigSchema.safeParse ternary in ListView.tsx:4444-4446 and plugin-view ObjectView.tsx:2460-2462 (D6), and the producer claims the same diff writes into ObjectGantt.tsx, objectql.ts, objectql.zod.ts, plugin-gantt/README.md and the PR body that it contradicts (D7). The r1 reasons are closed; the contract judgments D1-D5, D8, D9 and ② hold. On a head that restores the unconditional { gantt: ganttBlock } (the body's own Revert paragraph) with the three rows and the two 6152 re-points undone, and shows the same gates green, this reviewer expects PASS; the legacy-value question belongs to the seat as a work-item-3 census or an ADR-0087 entry, not to this PR.

Implemented-by: claude/project-thread-jzrcdw
Reviewed-by: session_01WGufzgbJhFoXYsLSe5WSPP

VERDICT: FAIL

…, drop the flat fallback

The producers hand the gantt binding on as the node's `gantt` block whether or
not the spec's GanttConfigSchema accepts it; a silent flat fallback for an
off-spec block is consumer-side tolerance. The prose now says what moves: a
binding carrying a spec-refused value, which the unvalidated flat branch drew,
reaches the renderer's refusal screen.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzetP4CSWdmfU9t887J
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 4676da49aca9c63eb11f432e79a824b139ef98c3
Local-runs: none
Scope: .changeset/25923-calendar-gantt-flat-keys-handoff.md, ui/packages/plugin-calendar/README.md, ui/packages/plugin-calendar/src/ObjectCalendar.tsx, ui/packages/plugin-calendar/src/tests/calendarUnionReads-8651.test.tsx, ui/packages/plugin-gantt/README.md, ui/packages/plugin-gantt/src/ObjectGantt.tsx, ui/packages/plugin-gantt/src/tests/ganttHandoffFlatFields-25923.test.ts, ui/packages/plugin-list/src/ListView.tsx, ui/packages/plugin-list/src/tests/ListView.calendar-binding-7029.test.tsx, ui/packages/plugin-list/src/tests/ListView.calendarAliasRefused-8355.test.tsx, ui/packages/plugin-list/src/tests/ListView.calendarDateWindow-12081.test.tsx, ui/packages/plugin-list/src/tests/ListView.gantt-binding-7070.test.tsx, ui/packages/plugin-list/src/tests/ListView.ganttNavigationForward-7334.test.tsx, ui/packages/plugin-list/src/tests/ListView.readersRetired-6152.test.tsx, ui/packages/plugin-list/src/tests/ListViewBlock.specInputs-13047.test.tsx, ui/packages/plugin-view/src/ObjectView.tsx, ui/packages/plugin-view/src/tests/ObjectView.calendarAliasRefused-8355.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.calendarBinding-7029.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.ganttBinding-7070.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.namedViewProtocolKeys-8980.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.readersRetired-6152.test.tsx, ui/packages/types/src/tests/calendar-date-alias-refusal-8355.test.ts, ui/packages/types/src/tests/calendar-flat-color-allday-8466.test.ts, ui/packages/types/src/tests/gantt-dependency-field-deprecated-alias.test.ts, ui/packages/types/src/tests/gantt-flat-config-declared-keys.test.ts, ui/packages/types/src/tests/gantt-search-keys-10250.test.ts, ui/packages/types/src/tests/gantt-view-mode-declared.test.ts, ui/packages/types/src/tests/object-gantt-properties-bag-10859-b6.test.ts, ui/packages/types/src/objectql.ts, ui/packages/types/src/zod/objectql.zod.ts + ui/packages/types/src/strict-authoring-face.ts (faceWalker, catchall z.never), ui/packages/types/src/zod/tombstone.zod.ts (aliasKeyRefusal), ui/packages/types/src/index.ts (ObjectCalendarBlockConfig export), packages/spec/src/ui/view.zod.ts (GanttConfigSchema 2216-, viewMode enum 2270, ListViewSchema.calendar/gantt 3032-3033, the view-item options z.never 5652), packages/spec/src/ui/component.zod.ts (ObjectCalendarPropsSchema 6226, ObjectGanttPropsSchema 7658, ComponentPropsMap rows 8671/8683, OBJECT_CALENDAR_FLAT_FIELD_GUIDANCE 6204, OBJECT_GANTT_FLAT_CONFIG_GUIDANCE 7549), ui/packages/core/src/utils/gantt-config-spelling.ts, ui/packages/plugin-gantt/src/GanttView.tsx, ui/packages/app-shell/src/views/ObjectView.tsx (the options.gantt relay, 2017 and 2062) and ui/packages/app-shell/src/views/objectViewOptions.ts (ganttViewOptions 152-158, storedLegacyOptions 382-385), ui/packages/plugin-gantt/src/index.tsx, ui/packages/plugin-calendar/src/index.tsx, ui/packages/plugin-gantt/tsconfig.json + package.json and ui/packages/plugin-calendar/tsconfig.json + package.json (tsconfig.test.json compiles the test pins), ui/AGENTS.md Commandment #0.1, scripts/check-adr-0087-registration.mjs and scripts/check-changeset-no-major.mjs (category and level rules)

Inputs read: card #25923 body and all 11 comments (triage 6408059640, transfer 6408067630, premise 6409923362 and 6430806588, claims 6410175297 and 6430908258, os-dev-reports 6412566640 and 6432621418, ruling 6412606977, accept 6413782781, landed 6416705898); PR #26411 body (revised for this head), its 30-file list, all three PR comments (record 6432801280 on 254db19 = FAIL; the author's Test UI note 6433437322; record 6433646970 on 89e1321 = FAIL), the net diff against main and the incremental diff 89e1321...4676da4; the 61 check-runs on the head, read 2026-10-27T05:00:53Z. Nothing built, run or re-run locally. record-recognisers --template --paths over the 30 files: the paths decide no face and the changeset's (narrowing) arm decides it — narrowing face, this tier.

① Derived judgments

  • D1 — r2 reason D6 CLOSED. The incremental diff removes the GanttConfigSchema.safeParse(ganttBlock).success ? { gantt: ganttBlock } : ganttBlock ternary from both producers and drops the two GanttConfigSchema imports (ListView.tsx:45 now imports resolveI18nLabel, normalizeFilterOperator, PaginationConfigSchema only; plugin-view ObjectView.tsx:79 imports resolveI18nLabel only). On the head, ListView.tsx:4441 reads ...(ganttBlock ? { gantt: ganttBlock } : {}) and plugin-view ObjectView.tsx:2444-2453 nests gantt: { ...pickFlatMapConfig(viewOptions.gantt, GANTT_CONFIG_SPELLING), titleField: … || 'name' } directly under the both-dates condition. A grep of both head files for safeParse, GanttConfigSchema, goes out FLAT, legacyFlatDraws finds one comment mention of the schema name (ListView.tsx:4344, describing the renderer) and no code. The block is emitted unconditionally once both date fields are declared; no flat GanttConfig key is written on any path; no residual tolerance exists in either producer.
  • D2 — r2 reason D7 CLOSED. Every prose claim r2 listed is now true against the code: ObjectGantt.tsx:311-316 (both producers emit the block), :548-551 (block branch is the producers' path), :590-594 (block and NO flat key), :632-640 (branch 1 since types: the strict authoring face accepts spellings the spec refuses (object-calendar flat keys), 74 zod mirrors silently strip unknown keys, and the list view's options bag is a second contract #25923, branch 2 for a host), :676-678 (what the producers emitted until types: the strict authoring face accepts spellings the spec refuses (object-calendar flat keys), 74 zod mirrors silently strip unknown keys, and the list view's options bag is a second contract #25923); objectql.ts:3823-3826 and :3873-3880; objectql.zod.ts:2917-2922 and :4939-4942; plugin-gantt/README.md:326-331; the PR body's acceptance note "No producer in this tree writes the flat spelling any more"; and the test guards (ListView.gantt-binding-7070:109-113, ObjectView.ganttBinding-7070:84-91, which red on any flat probe key). The one place the tolerance was declared, the changeset's last plugin-list/plugin-view sentence, now says the opposite: a refused binding "goes out nested too, and now shows the renderer's refusal screen".
  • D3 — r2 D8 CLOSED. The two 6152 rows are back on the block path: ListView.readersRetired-6152:195-199 reads node.gantt?.[k] for every spec-declared key and asserts k in node is false; ObjectView.readersRetired-6152:135-139 the same. The three tolerance rows are gone (ListView.gantt-binding-7070 "a block the spec refuses goes out flat" describe, ObjectView.ganttBinding-7070 its twin, and ObjectGantt.legacyFlatDraws-25923.test.tsx, which GET /contents at this head answers 404). The file list is 30, down from r2's 31.
  • D4 — RIGHT, the behaviour change is declared once and consistently. The change: a pre-write-door stored row whose options.gantt carries a spec-refused value (the spec's viewMode enum is day|week|month|quarter|year, view.zod.ts:2270; GanttConfigSchema is a strictObject, :2216) is relayed unvalidated by app-shell (storedLegacyOptions returns the bag as stored, objectViewOptions.ts:382-385; ObjectView.tsx:2017 and :2062 forward options.gantt), reaches ListView's gantt case, goes out nested, and getGanttConfig (ObjectGantt.tsx:665-673) safeParses it and returns invalidBlock, rendering gantt-invalid-config (:2391) where the unvalidated flat branch (:682-715, "deliberately NOT safeParse'd" at :679-680) drew. The same sentence, with the same routing — converted at the store (ADR-0087), the stored-row census of work item 3 counts it, never tolerated in a consumer — appears in ListView.tsx:4351-4355, plugin-view ObjectView.tsx:2440-2444, ObjectGantt.tsx:636-639, the changeset's ADR-0087 marker ("One drawing outcome moves … its conversion belongs to the stored-row census of types: the strict authoring face accepts spellings the spec refuses (object-calendar flat keys), 74 zod mirrors silently strip unknown keys, and the list view's options bag is a second contract #25923, not to tolerance in a consumer") and its plugin-list/plugin-view bullet, and the PR body (维护者速读 "唯一例外是早于写入校验的存量视图 … 现在显示甘特拒绝屏" and the acceptance-note "Reading"). The marker's old clause "nothing that drew before stops drawing" is gone. Declared equals enforced.
  • D5 — RIGHT, unchanged from r1/r2 (D1-D6, D10, D11 there; D4, D5, D9 in r2): ObjectGanttSchema (objectql.ts:3836-4071) carries gantt, staticData, filter, sort, the ten node-level keys and no GanttConfig key, no dependencyField, no search pair; the zod mirror (objectql.zod.ts:2910-2995) matches member for member; ObjectCalendarSchema carries calendar, the dateField / endField ?: never tombstones and none of the five flat (objectql.ts:4146-4215; objectql.zod.ts:3060-3087). The strict face closes every object with catchall: z.never() (strict-authoring-face.ts:256-260), so object-calendar now refuses the five by name as ObjectCalendarPropsSchema does (component.zod.ts:6226, members objectName, calendar, defaultView, filter, sort, data, staticData, …); object-gantt's arm is still the properties bag. The spec rows refuse exactly the moved sets with a prescription naming the block (OBJECT_CALENDAR_FLAT_FIELD_GUIDANCE 6204-6216, OBJECT_GANTT_FLAT_CONFIG_GUIDANCE 7549-7568, 30 keys). Hand-off types ObjectCalendarFlatFields (ObjectCalendar.tsx:148-153, Pick over the exported ObjectCalendarBlockConfig, index.ts:530) and ObjectGanttFlatFields (ObjectGantt.tsx:320-333, Partial of Pick of GanttConfig over KnownGanttConfigKey plus dependencyField @deprecated, search, searchableFields) type the renderers' schema props (ObjectCalendarHandoff, ObjectGanttHandoff); neither is re-exported from a package index (plugin-gantt/index.tsx:20 exports ObjectGanttProps, QuickFilterDef; plugin-calendar/index.tsx:23/32 export ObjectCalendarComponentProps and its alias), and the changeset's Fix line types a direct mount as ObjectGanttProps['schema'] / ObjectCalendarProps['schema'], which is right.
  • D6 — RIGHT: the calendar producers (ListView.tsx:4188-4212, plugin-view ObjectView.tsx:2334-2348) nest only once startDateField is declared, matching getCalendarConfig's flat gate (ObjectCalendar.tsx:343-373, if (schema.startDateField)), which does not validate a block, so no refusal-screen asymmetry exists on the calendar side. The gantt producers nest only with both dates and floor titleField to 'name', matching branch 2's two conditions (ObjectGantt.tsx:682-686, if (schema.startDateField && schema.endDateField) and titleField: schema.titleField || 'name').
  • D7 — RIGHT, with a precise answer to "never a flat key": no flat GanttConfig or calendar key is emitted. ListView.tsx:4382-4388 still writes search / searchableFields flat on the gantt node from the toolbar; these are not GanttConfig keys, the spec row declares them nowhere (so the strict face refuses them on an authored node and the tolerant face passes them unjudged, objectql.zod.ts:4906-4913), and they are typed on the hand-off (ObjectGantt.tsx:320-333) and read off it (:947-949). That is Commandment #0.1's prescribed shape for a key only the host writes at runtime (ui/AGENTS.md:104), not the flat fallback this card retires.
  • D8 — RIGHT, the compile-time pins are compiled: ganttHandoffFlatFields-25923.test.ts carries three @ts-expect-error rows (:101-105) and type-level Expect of Equal assertions; plugin-gantt's and plugin-calendar's type-check is tsc --noEmit && tsc -p tsconfig.test.json (package.json:35/37), and Type Check UI is green on this head, so none is a phantom check. The hand-off census pins both directions: keyof ObjectGanttFlatFields equals KnownGanttConfigKey | dependencyField | search | searchableFields, Extract of keyof ObjectGanttFlatFields, keyof ObjectGanttSchema is never, ObjectGanttProps['schema'] equals ObjectGanttHandoff; the calendar twin pins ObjectCalendarHandoff = ObjectCalendarSchema & ObjectCalendarFlatFields, the five-key list, and that every one of the five is still read by the renderer and declared on neither published face (calendarUnionReads-8651:294-304, 382-395).
  • D9 — RIGHT: the types-package pins invert honestly. gantt-flat-config-declared-keys pins the 24 moved keys absent from the mirror, admitted unexamined by the tolerant face, refused by the strict face flat and accepted in the bag's block, with gantt.capacity: 'one' refused at gantt.capacity; calendar-flat-color-allday-8466 pins the strict-face unrecognized_keys naming each flat key with a block-accepting control; gantt-view-mode-declared re-points to gantt.viewMode; gantt-search-keys-10250 and gantt-dependency-field-deprecated-alias pin the absence on both faces; calendar-date-alias-refusal-8355 pins the calendar.startDateField / calendar.endDateField prescription with a control that the bare key is prescribed nowhere. The producer tests read the block, red on any flat probe key, and pin "no block without the axis" and the 'name' floor in both plugin-list and plugin-view.

② Semver level

  • Changeset .changeset/25923-calendar-gantt-flat-keys-handoff.md: minor on @object-ui/types, plugin-calendar, plugin-gantt, plugin-list, plugin-view; title carries !; Clause-②: yes (narrowing); Metadata-protocol: narrowed; BREAKING banner with FROM → TO for both node types and the hand-off typing for a direct mount; ADR-0087 marker not-required (no-migration-prescription), a category the gate knows (scripts/check-adr-0087-registration.mjs:147, 554), argued on facts: no @objectstack/spec key, stored row or stored shape moves and objectstack migrate meta has nothing to rewrite — a pre-door row with a spec-refused options.gantt value is already outside the spec's accept set (the view item's options is z.never, view.zod.ts:5652; the write door judges each options.KIND block by its kind's schema, objectViewOptions.ts:357-360), so if the census finds any, the conversion entry is work item 3's, with its own disposition. The PR body carries the same two declaration lines. Check Changeset on the head = success twice (04:25:07Z and 05:00:10Z).
  • Level: narrowed takes at least minor on every moved package; a breaking change ships as minor with the banner and the ADR-0087 disposition as carriers (scripts/check-changeset-no-major.mjs, ruling A of 2026-10-21). minor ×5 is right.
  • Kind: narrowed is right — the strict authoring face refuses five keys on object-calendar it accepted; 37 keys leave the published TypeScript.
  • Clause-②: yes (narrowing) on body and changeset, consistent, and the right reading: the tolerant mirror widens for wrong-typed flat values (passthrough, pinned) while the TS and strict faces narrow. The dispatch order's no stays superseded by the dev's measured deviation, as r1 and r2 found.
  • The r2 rework moves neither level nor kind: it changes what the runtime emits for off-spec stored rows (now declared, D4), not a published type or an authoring face.

③ Boundary flags

  • Dev deviations 1-4 of os-dev-report 6432621418 (yes (narrowing); minor + not-required; sdui-manifest commits dropped; reworded local commits): ANSWERED as in r1 and r2 — all right, nothing owed.
  • r1 reasons D7 and D8, r1 escalation on tombstone wording: CLOSED in r2, still closed on this head (unused import gone, README fences nested at 605 and 854-859, tombstones prescribe calendar.startDateField / calendar.endDateField).
  • r2 reasons D6 and D7 (the safeParse flat-fallback ternary and its prose shadow): CLOSED (D1, D2 above). r2 D8 (the 6152 re-points): CLOSED (D3).
  • r1/r2 escalation D9 — a pre-door stored row with a spec-refused options.gantt value now lands on gantt-invalid-config instead of drawing: ANSWERED the Prime Directive Add comprehensive test suite for Zod schema validation #12 way on this head. The producers emit the block unconditionally; the row is routed to store conversion (ADR-0087) and to the stored-row census of work item 3 rather than tolerated; the population is unmeasured and said to be. ESCALATED to the seat as the accepted behaviour change it is, with two asks: carry into work item 3's dispatch order that the census must count rows whose options.gantt (or gantt) carries a value GanttConfigSchema refuses, and that any conversion is an ADR-0087 entry replayed through applyConversionsToStoredItem, never a producer or renderer branch. No fixture exists in the tree; the refusal screen names the path.
  • r1 escalation, acceptance-note follow-up (retire the flat fallback read in getCalendarConfig / getGanttConfig and the hand-off types, about 80 renderer tests author flat nodes): STILL OPEN, standing restored — with the ternary gone the fallback is load-bearing only for a host that mounts the renderer directly, exactly as the acceptance note says. ESCALATED to the seat to record on the card or file; the diff is right to leave the branch.
  • New, outside the diff, spec-owned: both spec refusal prescriptions still describe the producer behaviour this PR retires — OBJECT_CALENDAR_FLAT_FIELD_GUIDANCE says "The flat spelling is the runtime handoff ObjectView/ListView emit" (component.zod.ts:6212-6213) and OBJECT_GANTT_FLAT_CONFIG_GUIDANCE says "the internal form ObjectView/ListView produce when they flatten options.gantt" (7564-7565), with the docblocks at 6199 and 7526-7539 saying the same. The refusal and its prescription (write the block) stay right; only the rationale clause is stale. Not a FAIL reason of this PR (no packages/spec path in the diff, no spec key moves, and the dispatch order confined the surface to ui/), ESCALATED as a small follow-up row for domain:spec (a refusal-text edit plus check:docs regeneration).
  • Dogfood battery (title !): ANSWERED in r2 by the dev's re-measure on main (108/108 on 9a0108c; the branch red was the base predating test(dogfood): re-measure the authz census for the approval record-lock read #26406); this diff touches no packages/rest and no @objectstack/* package; Dogfood Regression Gate on this head = success, its shards path-skipped. Nothing owed.
  • Test UI (7/16), red on 89e1321 for the ui/scripts: doc-version-claims anchors the docs' Node version on literal node-version: lines in .github/workflows, so #26419 (Node read from .nvmrc) turns it red #26421 base reason: on this head Test UI 1-16 are all success (04:31Z-04:36Z), so nothing is carried.
  • Gate readings on the head (UTC, 2026-10-27, read 05:00:53Z): 61 check-runs, 50 success, 11 skipped, 0 failure, 0 in_progress. Success — Lint & Repo Gates (04:29:53Z), TypeScript Type Check (04:29:14Z), Test Core (04:24:09Z), Governed Surface Queue Guard (04:24:26Z), Lint UI (04:31:35Z), Type Check UI (04:33:26Z), Build & E2E UI (04:29:00Z), Build UI Deps, Check Changeset ×2, Check PR Size, Dogfood Regression Gate, Auto Label, the nine Lint · jobs, Type Check · consumer / debt / source / workspace, Test UI 1-16, the four claim/part-of guards ×2, filter. Skipped by path filter or opt-in — Build Core, Build Docs, Plan Test Core shards, Test Core shards, Dogfood shards, Dogfood Verify CLI, Temporal Conformance, 18.x upgrade harness, Packed-tarball smoke, and the 04:58Z re-runs of Auto Label and Check PR Size. Of the eight required contexts seven are success and Build Core is skipped (no core path in the diff), as on both earlier heads. The PR is a draft with no auto-merge; its file list touches no governed surface; 997 + 1052 = 2049 changed lines.

Both r2 FAIL reasons are closed on this head and the behaviour change they concerned is declared once, in the same words, across the two producers' comments, the renderer's docblock, the changeset (ADR-0087 marker and package bullet) and the PR body, and routed to store conversion rather than tolerated. The contract judgments of r1 and r2 hold; every required context is green.

Implemented-by: claude/project-thread-jzrcdw
Reviewed-by: session_01WGufzgbJhFoXYsLSe5WSPP

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat reading · REWORK r3 (patch round) · domain:spec seat 3 · session session_01WGufzgbJhFoXYsLSe5WSPP · 2026-10-27T05:17Z

Head 4676da49aca9c63eb11f432e79a824b139ef98c3: the contract re-review is PASS (record 6434353541). Both r2 reasons are closed. One item stops ACCEPT. The review checklist says a published defect that this round makes false gets fixed in the same round. packages/spec/src/ui/component.zod.ts ships in @objectstack/spec (src/**/*.zod.ts), and two of its prescriptions describe the producers this PR changes:

  • OBJECT_CALENDAR_FLAT_FIELD_GUIDANCE (around :6212): "The flat spelling is the runtime handoff ObjectView/ListView emit and a stored-document fallback the renderer keeps reading".
  • OBJECT_GANTT_FLAT_CONFIG_GUIDANCE (around :7564): "The flat top-level spelling is the internal form ObjectView/ListView produce when they flatten options.gantt".

On this head both producers emit the block, so both clauses become false once it merges. The prescription itself ("Write this as a key of the ... config object instead") is still right.

Patch: rewrite only those explanatory clauses so they say what this head does. The producers hand off the block, the flat spelling is only a stored-document fallback the renderer still reads, and it is not an authorable spelling. Add '@objectstack/spec': patch to the changeset and run the spec gates that pin those strings. The claim's file surface and its Clause-② line were amended (6430908258). The next head gets a same-tier re-review, scoped to the increment plus the spec face.


Generated by Claude Code

…the producers hand off the block

The explanatory clause of OBJECT_CALENDAR_FLAT_FIELD_GUIDANCE and
OBJECT_GANTT_FLAT_CONFIG_GUIDANCE said ObjectView / ListView emit the flat
spelling; they now hand the binding off in the calendar / gantt block, and the
flat spelling is only a stored-document fallback the renderer still reads. The
prescription itself is unchanged.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzetP4CSWdmfU9t887J
@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 147 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 16ca320c92df90f169a42f621224d6edb4551eb8 → packageMentionDocs.

…tadata-protocol line

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzetP4CSWdmfU9t887J
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 2f105661714b1f6658bef5b8bf6e607823961915
Local-runs: none
Scope: packages/spec/src/ui/component.zod.ts, .changeset/25923-calendar-gantt-flat-keys-handoff.md, ui/packages/types/src/objectql.ts, ui/packages/types/src/zod/objectql.zod.ts, ui/packages/types/src/tests/calendar-date-alias-refusal-8355.test.ts, ui/packages/types/src/tests/calendar-flat-color-allday-8466.test.ts, ui/packages/types/src/tests/gantt-dependency-field-deprecated-alias.test.ts, ui/packages/types/src/tests/gantt-flat-config-declared-keys.test.ts, ui/packages/types/src/tests/gantt-search-keys-10250.test.ts, ui/packages/types/src/tests/gantt-view-mode-declared.test.ts, ui/packages/types/src/tests/object-gantt-properties-bag-10859-b6.test.ts, ui/packages/plugin-calendar/README.md, ui/packages/plugin-calendar/src/ObjectCalendar.tsx, ui/packages/plugin-calendar/src/tests/calendarUnionReads-8651.test.tsx, ui/packages/plugin-gantt/README.md, ui/packages/plugin-gantt/src/ObjectGantt.tsx, ui/packages/plugin-gantt/src/tests/ganttHandoffFlatFields-25923.test.ts, ui/packages/plugin-list/src/ListView.tsx, ui/packages/plugin-list/src/tests/ListView.calendar-binding-7029.test.tsx, ui/packages/plugin-list/src/tests/ListView.calendarAliasRefused-8355.test.tsx, ui/packages/plugin-list/src/tests/ListView.calendarDateWindow-12081.test.tsx, ui/packages/plugin-list/src/tests/ListView.gantt-binding-7070.test.tsx, ui/packages/plugin-list/src/tests/ListView.ganttNavigationForward-7334.test.tsx, ui/packages/plugin-list/src/tests/ListView.readersRetired-6152.test.tsx, ui/packages/plugin-list/src/tests/ListViewBlock.specInputs-13047.test.tsx, ui/packages/plugin-view/src/ObjectView.tsx, ui/packages/plugin-view/src/tests/ObjectView.calendarAliasRefused-8355.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.calendarBinding-7029.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.ganttBinding-7070.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.namedViewProtocolKeys-8980.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.readersRetired-6152.test.tsx + packages/spec/src/ui/component.test.ts (the OBJECT_GANTT_FLAT_CONFIG_KEYS shape pin), content/docs/references/ui/component.mdx, sdui.manifest.json, scripts/check-changeset-no-major.mjs (ruling A, the at-least-one quantifier, PUBLISHED_SOURCE_ROOT), scripts/check-adr-0087-registration.mjs (CATEGORIES), .changeset/config.json (the fixed group), pnpm-workspace.yaml (ui/packages/* membership), AGENTS.md § Touched packages/spec and Post-Task Checklist 3

Inputs read: card #25923 body and all 11 comments (claim 6430908258 as amended 05:18Z, premise 6430806588, os-dev-report 6432621418, ruling 6412606977); PR #26411 body (revised for this head), its 31-file list, all six PR comments (records 6432801280 FAIL on 254db19, 6433646970 FAIL on 89e1321, 6434353541 PASS on 4676da4; the REWORK r3 order 6434412515; the Test UI note 6433437322; the docs-drift bot 6435023674); GET /compare 4676da4...2f10566 (54 commits) and, to separate the branch from main, GET /compare f9a1752...2f10566, 00a4593...4676da4 and 00a4593...f9a1752; the net diff against main; the head's files via GET /contents; the 70 check-runs on the head, read 2026-10-27T06:38:54Z and again 06:47:16Z (readings in ③); a read-only git grep over the local origin/main checkout (3fcafcd) for the old wording. Nothing built, run or re-run locally. record-recognisers --paths over the 31 files reads the schema face (component.zod.ts is a published schema); the changeset's (narrowing) arm reads the narrowing face too, so the Scope line above carries every path of the diff, as the narrowing floor asks, plus the cited files. The dispatch order and the seat's own conclusions were read as data, not adopted.

① Derived judgments

  • D1 — RIGHT, the increment is exactly the ordered surface and nothing else is the branch's. Of the 54 commits in 4676da4...2f10566, 51 are origin/main's (b33dd25..00a4593), one is the merge f9a1752, and two are the branch's own: d5d316b (component.zod.ts, +7/-6) and 2f10566 (the changeset, +2). The merge carried nothing of the branch's: the 30 PR files at 4676da4 (net against its base b33dd25) and at f9a1752 (net against 00a4593) match file for file in status, additions, deletions and blob sha, and main changed none of the 30 in that window. So everything 6434353541 judged PASS on 4676da4 is byte-identical on this head, and only the two r3 files are new. No main commit has touched component.zod.ts since 00a4593.
  • D2 — RIGHT, the two prescription strings are true at this head. Calendar (component.zod.ts:6210-6215): "ObjectView/ListView hand the binding off in that block too" — ListView.tsx:4200-4212 builds calendarBlock and returns ...(calendarBlock ? { calendar: calendarBlock } : {}); plugin-view ObjectView.tsx:2334-2348 nests calendar: {…} under calendarCfg.startDateField; neither writes a flat key. "the flat spelling is only a stored-document fallback the renderer still reads" — getCalendarConfig (ObjectCalendar.tsx:343-373) returns schema.calendar first and reads the five flat only if (schema.startDateField). Gantt (7562-7569): the hand-off clause is true (ListView.tsx:4357-4368 and :4441, plugin-view ObjectView.tsx:2445-2456, block only, both dates, titleField floored at 'name'; a grep of both producers for safeParse / GanttConfigSchema finds one descriptive comment at ListView.tsx:4344 and no code); "a stored-document fallback the renderer still reads" is getGanttConfig branch 2 (ObjectGantt.tsx:676-714, both dates, unvalidated); "whenever a gantt block is present the renderer takes it WHOLE and names every flat key beside it as ignored" is branch 1 (:665-673 returns schema.gantt as the config, no merge) plus warnOnShadowedFlatGanttKeys (:597-622, dev-only, names each FLAT_GANTT_CONFIG_KEYS member present as IGNORED). The kept clause "The legacy singular dependencyField is dependenciesField inside that block" still holds (branch 2 reads schema.dependenciesField || schema.dependencyField; the block has only dependenciesField).
  • D3 — RIGHT, nothing else in either guidance moved. The incremental hunks touch only the prescription lines: name, keys (7 calendar, 30 gantt), examples and the opening "Write this as a key of the … config object instead — …" sentence are byte-identical to main; the gantt prescription is re-flowed across one more string literal with no other change of text. ObjectCalendarPropsSchema / ObjectGanttPropsSchema and the guidanceSets wiring (6229, 7662) are untouched, so no accept set, key list or refusal site moves.
  • D4 — WRONG (declared, not delivered, inside the file this round edits). The PR body says the explanatory clauses of the two guidance constants "said ObjectView / ListView emit, or flatten into, the flat spelling. This PR made that false. They now say the producers hand the binding off." The two prescription strings now do. The JSDoc that documents the same two constants, five lines above each hunk, still says the opposite, and it is the one place a spec maintainer reads first:
    • component.zod.ts:6196-6203 (over OBJECT_CALENDAR_FLAT_FIELD_GUIDANCE): "The flat per-field spellings … that ObjectView/ListView emit on their runtime handoff" — false at this head (D2).
    • :7524-7548 (over OBJECT_GANTT_FLAT_CONFIG_GUIDANCE): "that ObjectView / ListView EMIT when they flatten options.gantt" (7526-7527) — false; "The flat branch is the HOT path for this block — a hand-authored gantt block reaching the renderer through either view layer has already been flattened" (7534-7535) — false, the block branch is the producers' path and the flat branch is reached only by a host that mounts the chart directly (ObjectGantt.tsx:632-640); "@object-ui/types' ObjectGanttSchema, which declares the whole flat face" (7537-7538) — false, this PR removed every flat GanttConfig member from that interface (objectql.ts, the r1 D1 judgment).
    • :7634-7635 (ObjectGanttPropsSchema's docblock): "the flat GanttConfig spellings (the flatten product — {@link OBJECT_GANTT_FLAT_CONFIG_GUIDANCE})" — "flatten product" names a producer behaviour that no longer exists.
      On main the file was consistent (docblock and prescription both described the flatten). On this head the file contradicts itself within ten lines, on exactly the matter the round rewrote, and the stale half is the half that tells the next maintainer the flat branch is hot and ObjectGanttSchema is flat. src/**/*.zod.ts ships in @objectstack/spec. This is the shape r2's D7 refused — prose in the PR's surface asserting a producer behaviour the code does not have — now in the one file the r3 surface names; the 6434353541 record listed these docblocks (6199, 7526-7539) as stale beside the prescriptions, and the amended claim surface is "the explanatory clauses of the … prescriptions", which the constants' own documentation is. Fix: rewrite 6196-6203 and 7524-7548 to say the producers emit the block, the flat spelling is the hand-off a host may mount the renderer with (and what the producers emitted until this change), and ObjectGanttSchema no longer declares it; replace "the flatten product" at 7634-7635 with "the hand-off form". Comment-only, no string, key or export moves, so no generated artifact follows.
  • D5 — RIGHT, no test, snapshot, generated reference or ledger quotes the old wording of these two prescriptions. git grep on origin/main for "runtime handoff", "produce when they flatten", "flatten options.gantt", "emit and a stored-document fallback": in packages/spec only component.zod.ts itself (the two prescriptions and their docblocks, plus the map/tree/timeline siblings' own prescriptions, which describe their own producers and are not this PR's); content/docs/references/ui/component.mdx:1281 and sdui.manifest.json:1132 carry the TIMELINE block's .describe(), not these strings; no liveness/** ledger and no json-schema file carries either prescription; component.test.ts:3701 pins the gantt KEY LIST against GanttConfigSchema.shape, not the prose. The PR body's "found it nowhere else" claim holds. TypeScript Type Check (the job that holds all six spec generated-artifact gates, AGENTS.md § Touched packages/spec) is success on this head, and no .describe() or export moved, so nothing was owed to gen:docs / gen:api-surface.
  • D6 — RIGHT, the PR's own ui files carry no residual claim that the producers emit the flat spelling. The remaining "runtime handoff" mentions at this head are a dated historical note (calendar-flat-color-allday-8466.test.ts:22-30, 2026-10-01, followed by the types: the strict authoring face accepts spellings the spec refuses (object-calendar flat keys), 74 zod mirrors silently strip unknown keys, and the list view's options bag is a second contract #25923 note saying the declarations moved), a comment inside a "this row used to pin the opposite" paragraph (:468-474), the timeline row (objectql.zod.ts:4549), and the name of what getCalendarConfig reads flat (:145); objectql.ts:3878 / :4158, objectql.zod.ts:2921 and plugin-calendar/README.md:327 say the producers emit the BLOCK. ObjectGantt.tsx:578-581's dev warning now calls the flat spelling "a hand-off form a host may mount this chart with" (main said "produce when they flatten options.gantt").

② Semver level

  • Changeset .changeset/25923-calendar-gantt-flat-keys-handoff.md: one file, now '@objectstack/spec': minor beside minor on the five @object-ui/* packages (ui/packages/* are root-workspace members, pnpm-workspace.yaml:22-25, and five other root changesets name @object-ui/* packages); title carries !; Clause-②: yes (narrowing); Metadata-protocol: narrowed; ADR-0087 marker not-required (no-migration-prescription), a CATEGORIES member (check-adr-0087-registration.mjs:554), whose text "No @objectstack/spec key, object definition, stored row or stored shape moves" stays true with spec added — the spec diff is prescription prose. The PR body carries the same two lines. Check Changeset on the head = success twice (06:07:44Z, 06:39:12Z).
  • Level for spec: the seat ordered patch; the dev graded minor and said why. The gate decides it: under ruling A (check-changeset-no-major.mjs:7-16, 899-911, 996-1001) a declared kind must grade AT LEAST ONE package whose packages/**/src/** the diff moves minor or above, and the declaration is PR-scoped. The ui packages live under ui/packages/**, so the only package this diff moves under PUBLISHED_SOURCE_ROOT is @objectstack/spec; with Metadata-protocol: narrowed on the PR, spec at patch leaves zero moved packages at minor+ and is refused — the feat(lint): field-typed equality/membership arm for filter-preset-comparand on declared date/datetime fields #16347 shape the gate's own header records (spec receiving one re-worded comment). So minor on spec is what the honest PR-wide declaration forces, and it over-grades rather than under-grades: a spec consumer's accept set, keys and prescriptions do not change, only a rationale clause in two refusal messages. The changeset's @objectstack/spec bullet says exactly that, so a reader of spec's CHANGELOG learns what they get. Two consequences, recorded, not refused: @objectstack/spec sits in the fixed group (.changeset/config.json), so the minor lifts every fixed-group package, as every spec minor does; and the BREAKING banner will print under spec's entry although the breaking change is @object-ui/types' — the banner's own text ("TypeScript code or hand-written nodes that write the flat keys on these two node types") names the right party.
  • Kind: narrowed is right for the PR (the object-calendar strict face refuses five keys it accepted; 37 keys leave the published TypeScript). For spec alone the kind would be none, but the line is PR-scoped and one changeset carries one pair; splitting spec into its own none/patch changeset would not change the PR-scoped verdict above.
  • Clause-②: yes (narrowing) on body and changeset, consistent, unchanged from the r1-r3 reading (the tolerant mirror widens for wrong-typed flat values while the TS and strict faces narrow); the claim's line was amended to it at 05:18Z. Metadata-protocol: narrowed, unchanged. The r3 increment moves neither.

③ Boundary flags

  • REWORK r3 order 6434412515 vs delivery: the prescription clauses — DELIVERED (D2, D3). '@objectstack/spec': patch — DEVIATED to minor, declared in the PR body with the gate reason; ANSWERED as right (②). "Run the spec gates that pin those strings" — the body reports @objectstack/spec build and test under the lock, 728 files / 22040 tests passed; CI's TypeScript Type Check, Lint & Repo Gates and Test Core are green on the head.
  • Dev deviations 1-4 of os-dev-report 6432621418: ANSWERED in r1-r3, unchanged here.
  • The 6434353541 escalation "spec prescriptions describe the retired producer behaviour": the strings are CLOSED by this round (D2); the docblocks it listed at 6199 and 7526-7539 are NOT, and are this record's D4.
  • 6434353541 escalations still with the seat, unchanged by this increment: carry into work item 3's census that rows whose options.gantt or gantt carries a GanttConfigSchema-refused value are counted and converted through an ADR-0087 entry, never a producer or renderer branch; record or file the follow-up that retires the flat fallback reads and the hand-off types.
  • Dogfood battery (title !): ANSWERED in r2/r3 (108/108 on main 9a0108c, which this head now contains via the merge); Dogfood Regression Gate = success on the head, its three shards path-skipped. Nothing owed.
  • Docs Drift Check (6435023674): "no opinion" — consistent with D5; the hand re-read it prescribes was done (content/docs/references/ui/component.mdx carries none of these strings).
  • The PR was updated at 06:37:11Z (the four claim guards and Auto Label / Check PR Size re-ran at 06:37Z, all success or skipped); the body read at 06:38Z is the one judged here. Draft, no auto-merge, mergeable_state unstable at the first read while Check Changeset re-ran.
  • Gate readings on the head (UTC, 2026-10-27): at 06:38:54Z — 70 check-runs: 61 success, 8 skipped, 1 in_progress (Check Changeset, started 06:37:18Z; its first run at 06:07:44Z = success). At 06:47:16Z — 70 check-runs: 62 success, 8 skipped, 0 failure, 0 in_progress. Success: Lint & Repo Gates (06:14:08Z), TypeScript Type Check (06:22:44Z), Spec property liveness (06:07:00Z), Build Core (06:19:43Z — run this time, a packages/spec path is in the diff), Test Core and Test Core 1-8, Governed Surface Queue Guard, Lint UI, Type Check UI, Build & E2E UI, Build UI Deps, Check Changeset ×2, Check PR Size, Dogfood Regression Gate, Flag docs affected by code changes, Auto Label, the nine Lint · jobs, Type Check · consumer / debt / source / workspace, Test UI 1-16, the four claim/part-of guards ×2, filter. Skipped by path or opt-in: Build Docs, the Dogfood shards, Dogfood Verify CLI, Temporal Conformance, 18.x upgrade harness, Packed-tarball smoke, and the 06:37Z re-runs of Auto Label and Check PR Size. Every required context is success. The verdict below is on the contract, not the gates.

One FAIL reason, mechanical and bounded, in the one file this round edits: the JSDoc over the two guidance constants and the {@link} clause at component.zod.ts:6196-6203, 7524-7548 and 7634-7635 still state the producer behaviour and the ObjectGanttSchema flat face this PR retires, so the shipped file now contradicts its own rewritten prescriptions (D4). Everything else holds: the r3 increment is exactly the two ordered files (D1), the new prescription prose is true against both producers and both renderers (D2), no accept set, key list, example or prescription sentence moved (D3), the old wording is quoted by no test, reference, manifest or ledger (D5), the 4676da4 PASS surface is byte-identical on this head (D1, D6), and minor on spec is the grade the gate's grain forces, said honestly in the changeset (②). On a head that rewrites those three comment spans and shows the same gates green, this reviewer expects PASS.

Implemented-by: claude/project-thread-jzrcdw
Reviewed-by: session_01WGufzgbJhFoXYsLSe5WSPP

VERDICT: FAIL

…cers hand off the calendar / gantt block

The JSDoc around the object-calendar / object-gantt flat-key guidance, and the
comments this PR's files carry, still said ObjectView / ListView emit or
flatten into the flat spelling and that ObjectGanttSchema declares it. They now
say the producers hand the binding off in the block, the flat spelling is a
stored-document fallback the renderer still reads, and the flat keys live on
the package-local hand-off types. Comments only.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzetP4CSWdmfU9t887J
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: eba20284903b075d5700d8656870f897b7391cf6
Local-runs: none
Scope: packages/spec/src/ui/component.zod.ts, .changeset/25923-calendar-gantt-flat-keys-handoff.md, ui/packages/plugin-calendar/README.md, ui/packages/plugin-calendar/src/ObjectCalendar.tsx, ui/packages/plugin-calendar/src/tests/calendarUnionReads-8651.test.tsx, ui/packages/plugin-gantt/README.md, ui/packages/plugin-gantt/src/ObjectGantt.tsx, ui/packages/plugin-gantt/src/tests/ganttHandoffFlatFields-25923.test.ts, ui/packages/plugin-list/src/ListView.tsx, ui/packages/plugin-list/src/tests/ListView.calendar-binding-7029.test.tsx, ui/packages/plugin-list/src/tests/ListView.calendarAliasRefused-8355.test.tsx, ui/packages/plugin-list/src/tests/ListView.calendarDateWindow-12081.test.tsx, ui/packages/plugin-list/src/tests/ListView.gantt-binding-7070.test.tsx, ui/packages/plugin-list/src/tests/ListView.ganttNavigationForward-7334.test.tsx, ui/packages/plugin-list/src/tests/ListView.readersRetired-6152.test.tsx, ui/packages/plugin-list/src/tests/ListViewBlock.specInputs-13047.test.tsx, ui/packages/plugin-view/src/ObjectView.tsx, ui/packages/plugin-view/src/tests/ObjectView.calendarAliasRefused-8355.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.calendarBinding-7029.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.ganttBinding-7070.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.namedViewProtocolKeys-8980.test.tsx, ui/packages/plugin-view/src/tests/ObjectView.readersRetired-6152.test.tsx, ui/packages/types/src/tests/calendar-date-alias-refusal-8355.test.ts, ui/packages/types/src/tests/calendar-flat-color-allday-8466.test.ts, ui/packages/types/src/tests/gantt-dependency-field-deprecated-alias.test.ts, ui/packages/types/src/tests/gantt-flat-config-declared-keys.test.ts, ui/packages/types/src/tests/gantt-search-keys-10250.test.ts, ui/packages/types/src/tests/gantt-view-mode-declared.test.ts, ui/packages/types/src/tests/object-gantt-properties-bag-10859-b6.test.ts, ui/packages/types/src/objectql.ts, ui/packages/types/src/zod/objectql.zod.ts + packages/spec/src/ui/component.zod.ts on origin/main (the retired wording, read-only git grep), ui/packages/types/src/strict-authoring-face.ts (catchall z.never, as read in r1-r3), scripts/check-changeset-no-major.mjs and scripts/check-adr-0087-registration.mjs (level and category rules, as read in r3), .changeset/config.json (the fixed group)

Inputs read: card #25923 body and all 11 comments (premise 6409923362 and 6430806588, claim 6430908258 as amended 05:18Z, os-dev-reports 6412566640 and 6432621418, ruling 6412606977, accept 6413782781, landed 6416705898, triage and transfer); PR #26411 body as it stood at 08:04:49Z (the PR's updated_at is 08:03:39Z, so the body judged is the post-update one), its 31-file list (+1051 / -1085 = 2136 changed lines), all seven PR comments (records 6432801280 FAIL on 254db19, 6433646970 FAIL on 89e1321, 6434353541 PASS on 4676da4, 6435670761 FAIL on 2f10566; the REWORK r3 order 6434412515; the Test UI note 6433437322; the docs-drift bot 6435023674); GET /compare 2f10566...eba2028 as JSON and as a diff; the net diff against main (GET /pulls/26411 as a diff); every one of the 31 files at the head via GET /contents?ref=eba20284; the 70 check-runs on the head, read 2026-10-27T08:04:49Z and re-read before this post (readings in ③); a read-only git fetch origin main (ab3c776) followed by git log and git grep on origin/main. Nothing built, run or re-run locally. record-recognisers --template --paths over the 31 files returns hits = component.zod.ts (the schema face); the changeset's (narrowing) arm reads the narrowing face too, so the Scope line carries every path of the diff plus the cited files. The dispatch order and the seat's own conclusions were read as data, not adopted.

① Derived judgments

  • D1 — RIGHT, the r4 increment is comments only, on exactly the seven files the author names. GET /compare 2f10566...eba2028: status ahead, ahead_by 1, behind_by 0, one commit (eba2028, one parent 2f10566, no merge of main), seven files — component.zod.ts +18/-14, ObjectCalendar.tsx +3/-2, ObjectGantt.tsx +1/-1, calendar-flat-color-allday-8466.test.ts +14/-5, object-gantt-properties-bag-10859-b6.test.ts +1/-1, objectql.ts +4/-2, objectql.zod.ts +4/-2. A mechanical pass over the 72 added or removed lines of that diff finds every one of them a JSDoc * line or a // line and zero code, string, key, example or accept-set lines. In the net diff against main, component.zod.ts carries only the two rewritten prescription string literals (r3, byte-identical to what 6435670761 read on 2f10566) and JSDoc; name, keys (7 calendar, 30 gantt), examples, the strictObject wiring of ObjectCalendarPropsSchema (6229-6233) and ObjectGanttPropsSchema (7663-) are untouched. So the code of every PR file on this head is the code 6435670761 judged, and the 30 ui files plus the changeset are the ones 6434353541 passed (its D1 chain: byte-identical 4676da4 to 2f10566; this increment adds no code).
  • D2 — RIGHT, the r3 FAIL reason D4 is CLOSED, read in the head file, not in the author's sweep. component.zod.ts:6196-6206 now reads: the flat per-field spellings are a "stored-document fallback (getCalendarConfig …)", "ObjectView/ListView hand the binding off in the calendar block, not flat (objectstack#25923), and the flat keys live on plugin-calendar's own hand-off type, not on @object-ui/types' ObjectCalendarSchema." :7527-7543: branch 2 reads the flat spellings "as a stored-document fallback", the producers "hand the binding off in the gantt block, not flat", "The block branch is the path both view layers take — they hand the gantt block on whole — and the flat branch is only the stored-document fallback", "The flat keys live on plugin-gantt's package-local hand-off type (ObjectGanttFlatFields), not on @object-ui/types' ObjectGanttSchema, which declares only the block." :7638-7639 "the flat GanttConfig spellings (the stored-document fallback — {@link OBJECT_GANTT_FLAT_CONFIG_GUIDANCE})"; :7649-7650 "in the same position as the flat fallback spellings". The three spans 6435670761 named ("emit on their runtime handoff", "EMIT when they flatten options.gantt", "HOT path", "declares the whole flat face", "flatten product") are gone from the file; a git grep for those phrases on origin/main hits only component.zod.ts:6199, 7526, 7534, 7538 and 7565, which is where they were, and nothing under content/docs, sdui.manifest.json or json-schema.
  • D3 — RIGHT, my own grep for the retired claim over component.zod.ts and all 30 ui files at the head (patterns: emit / EMIT / produce / flatten / flatten product / runtime handoff / HOT path / whole flat face / goes out flat, case-insensitive) finds no sentence that says ObjectView / ListView emit, flatten into or produce the flat calendar or gantt spelling now, nor that ObjectGanttSchema declares the flat keys. What remains falls in three classes, each read in context: (i) the map, tree and timeline siblings' own prescriptions and docblocks (component.zod.ts 7348, 7377, 7413, 7742, 7778, 7929, 7951, 8219-8220, 8259; objectql.ts 3513, 3642-3643; objectql.zod.ts 2771, 4551, 4775; ListView.tsx 4454-4459, the case 'map' arm that still emits flat by design) — those describe producers this PR does not change and are not this card's node types; (ii) history in the past tense: ObjectGantt.tsx:548 "EMITTED this spelling" with "Until objectstack#25923" at :547, :636 "as it did when they flattened it", :677 "ListView emitted until objectstack#25923"; objectql.zod.ts:1862 (the CALENDAR_DATE_ALIAS_STEM refusal text, "which flattened them … That ladder is gone", the pre-objectui#8355 history); objectql.zod.ts:1971 and calendar-date-alias-refusal-8355.test.ts:48 "used to flatten"; ObjectCalendar.tsx:327-328 (the objectui#8355 change, "stops flattening"); calendarUnionReads-8651:66-80 (a dated census); (iii) the 2026-10-01 dated note at calendar-flat-color-allday-8466.test.ts:22-30, which quotes the old README teaching and is followed at :32-36 by the types: the strict authoring face accepts spellings the spec refuses (object-calendar flat keys), 74 zod mirrors silently strip unknown keys, and the list view's options bag is a second contract #25923 note that says it is "superseded in part". None of these is a live claim.
  • D4 — RIGHT, each rewritten comment is true against the code at this head. Calendar JSDoc (component.zod.ts 6197-6205): getCalendarConfig (ObjectCalendar.tsx:343-362) returns schema.calendar first and reads the five flat only if (schema.startDateField); ListView.tsx:4200-4212 builds calendarBlock and spreads calendar: calendarBlock; plugin-view ObjectView.tsx:2338-2348 nests calendar: {…} under calendarCfg.startDateField; ObjectCalendarFlatFields / ObjectCalendarHandoff are at ObjectCalendar.tsx:148-153 and type the prop at :172; ObjectCalendarSchema carries calendar (objectql.ts:4191; objectql.zod.ts:3081) and the two never tombstones (:4206, :4216; zod :3088-3089) and none of the five flat. Gantt JSDoc (7528-7543): getGanttConfig branch 1 (ObjectGantt.tsx:666-673) safeParses schema.gantt and returns it whole; warnOnShadowedFlatGanttKeys (:597-603) names every FLAT_GANTT_CONFIG_KEYS member present; branch 2 (:682-714) needs both dates and floors titleField to 'name'; both producers emit the block only — ListView.tsx:4357 / :4441 ...(ganttBlock ? { gantt: ganttBlock } : {}), plugin-view ObjectView.tsx:2450-2455 gantt: { ...pickFlatMapConfig(viewOptions.gantt, GANTT_CONFIG_SPELLING), titleField: … || 'name' }; neither file contains safeParse or GanttConfigSchema in code (ListView's one mention at :4344 is a comment about the renderer); ObjectGanttFlatFields = Partial of Pick of GanttConfig over KnownGanttConfigKey plus the alias and the search pair (ObjectGantt.tsx:320-333, KnownGanttConfigKey = KnownKeys of GanttConfig at :541), ObjectGanttHandoff at :336 types the prop at :360; ObjectGanttSchema carries gantt, staticData, filter, sort (objectql.ts:4009-4039) and no GanttConfig key, no dependencyField, no search pair, the zod mirror likewise (objectql.zod.ts:2910-2995, gantt at :2978). ObjectCalendar.tsx:1473-1475 ("the shared baseProps bag plus a calendar block carrying whichever of startDateField / endDateField / titleField the author declared"): both producers' blocks carry exactly the declared ones (ListView 4201-4207; ObjectView 2340-2346; the two colour / all-day keys go too, which the sentence does not deny). ObjectGantt.tsx:586 ("the author's block over the flat spelling"): branch 1 before branch 2. objectql.ts:317-321 (the nine lifted members are "declared since objectstack#25923 on plugin-gantt's own hand-off type ObjectGanttFlatFields, not on ObjectGanttSchema"): KnownKeys of GanttConfig covers the nine, since they are members of the GanttConfig intersection declared in that very file. objectql.zod.ts:1869-1871 ("since objectstack#25923 in the node's own calendar block, before that as a FLAT member"): true. calendar-flat-color-allday-8466.test.ts:32-36 ("This file now pins their absence"): the file's TS and mirror rows pin absence and the strict-face refusal, as 6434353541 D9 read and this head keeps; :148-151 and :476-486 agree with getCalendarConfig and with ObjectCalendarSchema. object-gantt-properties-bag-10859-b6.test.ts:36 ("the flat spelling getGanttConfig reads when there is no gantt block"): branch 2. The "stored-document fallback" phrase is the one 6435670761 D2 accepted for the prescription strings, and the JSDoc now uses it consistently.
  • D5 — RIGHT, the PASS judgments of 6434353541 (its D1-D9) and the non-D4 judgments of 6435670761 (its D1-D3, D5, D6) hold on this head's net diff. The code is unchanged since 2f10566 (D1 here), and the facts were re-read directly in the head files rather than inherited: the two published faces of ObjectGanttSchema lose the same 32 members and of ObjectCalendarSchema the same five (D4 here); the strict face's catchall: z.never() therefore refuses the five on object-calendar; the producers emit the block unconditionally once the axis is declared and never a flat GanttConfig or calendar key (ListView.tsx 4200-4212, 4357-4441; ObjectView.tsx 2338-2348, 2450-2455), with search / searchableFields still written flat from the toolbar as 6434353541 D7 read and the hand-off types declare (ObjectGantt.tsx:327-333); the two prescription strings (6213-6217, 7566-7572) are byte-identical to the r3 text and true for the reasons 6435670761 D2 gave. origin/main has moved 45 commits since the PR's base 00a4593 and none of them touches any of the 31 PR paths (git log 00a4593a..origin/main -- paths is empty), so the net diff reads the same against today's main.
  • D6 — RIGHT, nothing else in the PR's own files restates the retired behaviour, with three readings recorded as polish, not defects: (i) calendar-flat-color-allday-8466.test.ts now carries two consecutive types: the strict authoring face accepts spellings the spec refuses (object-calendar flat keys), 74 zod mirrors silently strip unknown keys, and the list view's options bag is a second contract #25923 dated notes (:32-36 and :38-45) that say the same thing in different words — redundant, not false; (ii) objectql.ts:317 still calls the flat spelling one of "BOTH authoring faces" while the next clause says it is a hand-off type — the retained word "authoring" is loose, the fact stated is right; (iii) objectql.zod.ts:1871-1874 lists "the flat node face itself" among "every producer-fed surface" — since this PR the flat face is host-fed, not producer-fed; the sentence classifies the alias refusal's consequence clause and does not say a producer emits flat. None states that ObjectView / ListView emit, flatten into or produce the flat spelling, or that ObjectGanttSchema declares it.

② Semver level

  • Changeset .changeset/25923-calendar-gantt-flat-keys-handoff.md, unchanged by r4: minor on @objectstack/spec, @object-ui/types, plugin-calendar, plugin-gantt, plugin-list, plugin-view; title carries !; Clause-②: yes (narrowing); Metadata-protocol: narrowed; ADR-0087 marker not-required (no-migration-prescription) with its facts still true (no spec key, stored row or shape moves; the spec diff is prescription prose and JSDoc); the @objectstack/spec bullet says exactly what a spec consumer gets (the two refusals no longer say the producers emit the flat spelling; the prescription itself is unchanged). The PR body carries the same two declaration lines. Check Changeset on the head = success (07:39:20Z); a second run started after the 08:03:39Z PR update and is in the ③ reading.
  • Level: unchanged from 6435670761 ②. narrowed takes at least minor on a moved package; under ruling A the only package this diff moves under packages/**/src/** is @objectstack/spec, so with Metadata-protocol: narrowed PR-scoped, spec at patch would leave zero moved packages at minor+ and be refused; minor on spec is the honest PR-wide grade and over-grades rather than under-grades (no spec accept set, key or prescription sentence changes). The two recorded consequences stand: the fixed group lifts with spec, and the BREAKING banner prints under spec's entry while naming @object-ui/types' authors as the party.
  • Kind: narrowed is right for the PR (the object-calendar strict face refuses five keys it accepted; 37 keys leave the published TypeScript); for spec alone it would be none, but the line is PR-scoped.
  • Clause-②: yes (narrowing) on body and changeset, consistent, the r1-r3 reading unchanged (the tolerant mirror widens for wrong-typed flat values under .passthrough() while the TS and strict faces narrow); Metadata-protocol: narrowed, unchanged. A comment-only increment moves neither level nor kind.

③ Boundary flags

  • 6435670761 reason D4 (JSDoc at component.zod.ts 6196-6203, 7524-7548, 7634-7635 contradicting the rewritten prescriptions): CLOSED (D2, D3, D4 above), by comments only (D1).
  • REWORK r3 order 6434412515: the prescription clauses DELIVERED (r3 record D2, re-read here); '@objectstack/spec': patch DEVIATED to minor, ANSWERED as right in r3 ② and here; the spec gates ran in CI (TypeScript Type Check, Lint & Repo Gates, Test Core, Spec property liveness all success on this head).
  • Dev deviations 1-4 of os-dev-report 6432621418 (yes (narrowing); minor + not-required; sdui-manifest commits dropped; reworded local commits): ANSWERED in r1-r3, unchanged.
  • The author's r4 claim "comments only; no string, key, example or accept set moved" and the six-file sweep list: VERIFIED mechanically (D1), not taken on trust.
  • 6434353541 escalations still with the seat, unchanged by this increment: carry into work item 3's census that rows whose options.gantt or gantt carries a GanttConfigSchema-refused value are counted and converted through an ADR-0087 entry, never a producer or renderer branch; record or file the follow-up that retires the flat fallback reads in getCalendarConfig / getGanttConfig and the hand-off types.
  • New, polish only, ESCALATED to the seat as acceptance-note candidates, not FAIL reasons: the duplicated types: the strict authoring face accepts spellings the spec refuses (object-calendar flat keys), 74 zod mirrors silently strip unknown keys, and the list view's options bag is a second contract #25923 note in calendar-flat-color-allday-8466.test.ts:32-45; "BOTH authoring faces" at objectql.ts:317; "producer-fed … the flat node face itself" at objectql.zod.ts:1871-1874 (D6).
  • Outside this PR, recorded as not-a-finding: the map, tree and timeline prescriptions and docblocks in component.zod.ts still describe their own producers' flatten; those producers still flatten (ListView.tsx:4454-4459 for map), so the sentences are true and not this card's.
  • Dogfood battery (title !): ANSWERED in r2/r3; Dogfood Regression Gate = success on this head, its three shards path-skipped. Nothing owed.
  • Docs Drift Check 6435023674 ("no opinion") was on 2f10566; the r4 increment is comments only and adds no documentable surface. Nothing owed.
  • Gate readings on the head (UTC, 2026-10-27), read 08:04:49Z: 70 check-runs — 61 success, 8 skipped, 0 failure, 1 in_progress (Check Changeset, second run, started after the 08:03:39Z PR update; its first run at 07:39:20Z = success). All eight required contexts success: Lint & Repo Gates (07:43:57Z), TypeScript Type Check (07:50:05Z), Test Core (08:01:00Z), Build Core (07:50:40Z, run since a packages/spec path is in the diff), Governed Surface Queue Guard (07:38:23Z), Lint UI (07:49:59Z), Type Check UI (07:56:35Z), Build & E2E UI (07:45:09Z). Also success: Spec property liveness, Test Core 1-8, Plan Test Core shards, Build UI Deps, Check Changeset (first run), Check PR Size, Dogfood Regression Gate, Flag docs affected by code changes, Auto Label, the nine Lint · jobs, Type Check · consumer / debt / source / workspace, Test UI 1-16, the four claim/part-of guards ×2, filter. Skipped by path or opt-in: Build Docs, Dogfood shards, Dogfood Verify CLI, Temporal Conformance, 18.x upgrade harness, Packed-tarball smoke, and the 08:03Z re-runs of Auto Label and Check PR Size. Re-read 08:14:46Z: 70 check-runs — 62 success, 8 skipped, 0 failure, 0 in_progress; the second Check Changeset run = success (08:05:31Z); the PR's updated_at still 08:03:39Z and its body byte-identical to the one judged; mergeable_state clean.
  • The PR is a draft with no auto-merge, head repo = base repo (not a fork), its file list touches no governed surface, 2136 changed lines (under 3000). The verdict below is on the contract, not the gates.

The one FAIL reason of 6435670761 is closed by a comment-only increment whose 72 changed lines were each verified as comment text (D1); the file no longer contradicts its own prescriptions (D2), no live sentence in component.zod.ts or in any of the PR's ui files says the producers emit the flat calendar or gantt spelling or that ObjectGanttSchema declares it (D3), every rewritten comment is true against the producers, renderers and published faces at this head (D4), the PASS surface of 6434353541 and the non-D4 judgments of 6435670761 hold on code that is byte-identical since 2f10566 and untouched by main (D5), and the changeset and declarations are unchanged and right (②). Three polish readings are recorded for the seat (D6), none a defect.

Implemented-by: claude/project-thread-jzrcdw
Reviewed-by: session_01WGufzgbJhFoXYsLSe5WSPP

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读 (席位终稿)

  • 改了什么:@object-ui/types 的 object-calendar / object-gantt 节点不再公开平铺的字段映射键(如 startDateField),这些键只在 calendar / gantt 块里写;ListView 和 ObjectView 生成节点时改为写块。@objectstack/spec 里两条拒收处方的说明句和相邻注释随之改正(只改文字,minor)。
  • 为什么改:spec 本来就拒绝平铺写法,但类型还公开着它们,objectui validate 因此放行 spec 拒绝的日历写法,作者被误导。
  • 风险与回滚:渲染器仍读平铺写法,已有页面照常显示;唯一例外是早于写入校验、options.gantt 带 spec 拒绝值的存量视图,现在显示甘特拒绝屏(数量未测,由卡片第 3 项盘点并按 ADR-0087 转换)。手写 TS 里写了平铺键的会编译失败,按 changeset 改成块即可。回滚 = revert 本 PR。
  • 先看:ui/packages/plugin-list/src/ListView.tsx(calendar / gantt 出块)、ui/packages/types/src/zod/objectql.zod.ts(删掉的成员)、ui/packages/plugin-gantt/src/ObjectGantt.tsx(ObjectGanttFlatFields)。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/plugin-list/src/__tests__/ListView.gantt-binding-7070.test.tsx packages/types/src/__tests__/gantt-flat-config-declared-keys.test.ts,预期全部通过。
  • 需要你做的:无,席位落地。

席位意见:接受。四轮契约复核后 PASS(6436950621);r1 时席位下令加的平铺回退违反 Prime Directive #12,已在 r2 撤回,这是席位的失误。

席位核对 · ACCEPT · domain:spec seat 3 · os-support-ai · session session_01WGufzgbJhFoXYsLSe5WSPP · 2026-10-27T08:16Z

Head: eba20284903b075d5700d8656870f897b7391cf6 · card #25923 (work item 1) · claim 6430908258 (amended 05:18Z)

  1. 形态: a draft PR to main. Its first line is Part of #25923 with no closing keyword anywhere in the body; the card stays open because items 3 and 4 and the FormField follow-up remain. 31 paths, +1051 / −1085. check-governed-merges --pr 26411: NOT governed, 2136 lines ≤ 3000, so the ordinary queue landing applies. The file list matches the amended claim surface: ui types, the four plugins, and packages/spec/src/ui/component.zod.ts for prose only. ui/packages/app-shell/src/views/ObjectView.tsx is untouched.
  2. 前提实测: the premise correction in 6430806588 still holds. The reviewer re-read the producers and renderers at this head. origin/main ab3c776a touched none of the 31 paths since the branch's last merge (00a4593a), so there is no drift.
  3. 门禁 check-runs (on this head): 70 runs, 62 success, 8 skipped (path or opt-in), none red or pending. Lint & Repo Gates, TypeScript Type Check, Check Changeset and Dogfood Regression Gate are all success. The contract-tier review is PASS, record 6436950621 (CONTRACT_REVIEW_TIER, schema face plus narrowing). Earlier records: 6432801280 FAIL, 6433646970 FAIL (the r1 silent flat fallback, which the seat had ordered; withdrawn in r2), 6434353541 PASS, and 6435670761 FAIL (the JSDoc next to the r3 prose). Each FAIL reason is closed at this head.
  4. 接受的偏差: (a) Clause-②: yes (narrowing) instead of the order's no. The tolerant mirror stops value-checking the moved keys written flat, and the claim was amended to match. (b) '@objectstack/spec': minor for a prose-only spec diff. check-changeset-no-major grades only the root workspace, and the narrowing lives in @object-ui/types; the seat ruled this in r3 and the review judged it honest. (c) One declared behaviour change: a pre-write-door stored row whose options.gantt carries a spec-refused value now lands on gantt-invalid-config. It is routed to store conversion (ADR-0087), never tolerated, and declared the same way in code comments, the changeset and the body. (d) Polish the review raised but did not fail on, accepted as is: a duplicated types: the strict authoring face accepts spellings the spec refuses (object-calendar flat keys), 74 zod mirrors silently strip unknown keys, and the list view's options bag is a second contract #25923 dated note in calendar-flat-color-allday-8466.test.ts:32-45; "BOTH authoring faces" at objectql.ts:317 (the flat spelling is now a hand-off, not an authoring face); "the flat node face itself" at objectql.zod.ts:1871-1874. These are comments with no accept-set effect. They go to the follow-up below.
  5. 范围外去向: two items are recorded on card types: the strict authoring face accepts spellings the spec refuses (object-calendar flat keys), 74 zod mirrors silently strip unknown keys, and the list view's options bag is a second contract #25923 at landing, not filed as new cards, because the card's remaining work carries both. (i) Work item 3's census must count stored rows whose options.gantt carries a GanttConfigSchema-refused value, and any conversion is an ADR-0087 entry. (ii) Retiring the flat fallback read in getCalendarConfig / getGanttConfig and the hand-off types, about 80 renderer tests, plus the three comment polishes in 4(d), follows that census. Skills-docs: none is true: skills/objectui/guides/page-builder.md already authors the gantt block, and no skill authors a flat key.
  6. 落地条款: the seat checked the prose faces sentence by sentence against the diff: the changeset (five ui minors, spec minor, the ADR-0087 marker, which no longer says "nothing that drew before stops drawing") and both plugin READMEs. Ready and auto-merge go through the relay in this act, and the seat follows the PR to MERGED.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 27, 2026 08:17
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 27, 2026 08:17
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit b53d9f3 Oct 27, 2026
71 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants