Skip to content

Commit b53d9f3

Browse files
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)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} --> _Requested by **Yibo Wang** · [project thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBVVaEbKjJHqT747bQHd6Uxp)_ 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 #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 <details><summary>ui vitest (under os-verify-lock)</summary> - `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. </details> <details><summary>Type-check</summary> - `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. </details> <details><summary>@objectstack/spec (rework r3)</summary> - 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. </details> <details><summary>Dispatch gates</summary> - `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 (#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. </details> <details><summary>Dogfood battery (title carries `!`)</summary> - Merged head, under the lock: `pnpm --filter '@objectstack/dogfood^...' build && pnpm --filter @objectstack/dogfood test`: VERDICT command-exit 1, Test Files 297 passed / 2 failed / 3 skipped, Tests 2521 passed / 10 failed / 18 skipped. - The two red files are `test/authz-conformance.test.ts` (#17111 ledger-figure pin, 6 rows) and `test/authz-probe-blind-spot.test.ts` (#13260 census of `packages/rest/src/rest-server.ts` and `rest-route-ledger.ts`, e.g. `expected 70 to be 69`, 4 rows). This diff touches no `packages/rest`, no dogfood input and no `@objectstack/*` package. - Re-measured (rework r1): on `origin/main` `9a0108cd` the two files read 108/108 passed. On the branch (head `89e13213`, base `b33dd25b`) they read 10 failed / 98 passed, because the base predates #26406's census re-measure. CI's Dogfood Regression Gate on the merge ref is green. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WGufzetP4CSWdmfU9t887J --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1c63231 commit b53d9f3

31 files changed

Lines changed: 1051 additions & 1085 deletions
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@object-ui/types': minor
4+
'@object-ui/plugin-calendar': minor
5+
'@object-ui/plugin-gantt': minor
6+
'@object-ui/plugin-list': minor
7+
'@object-ui/plugin-view': minor
8+
---
9+
10+
refactor(types,plugin-calendar,plugin-gantt,plugin-list,plugin-view)!: the flat `object-calendar` and `object-gantt` field-mapping keys leave the published schemas; producers emit the `calendar` / `gantt` block (#25923)
11+
12+
Clause-②: yes (narrowing)
13+
Metadata-protocol: narrowed
14+
15+
<!-- adr-0087: not-required (no-migration-prescription) No `@objectstack/spec` key, object definition, stored row or stored shape moves: the spec's `ComponentPropsMap['object-calendar']` and `['object-gantt']` rows already refuse every key this change takes off `@object-ui/types`, so no metadata that the spec accepts carries them, and `objectstack migrate meta` has nothing to rewrite. What narrows is the `@object-ui/types` mirror of those rows: its TypeScript interfaces, its tolerant zod mirrors and, for `object-calendar`, the strict authoring face derived from them, which now refuses the five flat keys by name as the spec does. The affected party is a TypeScript author or a hand-written node, reached by the compiler and by `objectui validate`, and the renderers still read the flat spelling a host hands them. One drawing outcome moves: `getGanttConfig` validates a `gantt` block and never validated the flat spelling, so a pre-door stored view whose `options.gantt` carries a value the spec refuses now shows the refusal screen instead of drawing; that row is outside what the spec accepts, and its conversion belongs to the stored-row census of #25923, not to tolerance in a consumer. The other categories are closed on facts: the five packages publish (not unpublished); no ADR-0087 id covers this change (not registered or already-registered); and the narrowed symbols are zod-backed metadata shapes (not runtime-interface-only, type-surface-only or runtime-export-retired). -->
16+
17+
`@object-ui/types` published, on `ObjectCalendarSchema` and `ObjectGanttSchema`, a flat spelling of the field mapping that the spec refuses on the node. A declared flat key is one the strict authoring face derives from, so `objectui validate` accepted the `object-calendar` spelling the spec refuses. The keys only the host writes now live on each renderer package's own hand-off type (ui/AGENTS.md Commandment #0.1), and the producers write the block.
18+
19+
- **`@object-ui/types`**: `ObjectCalendarSchema` (TypeScript and zod) no longer declares `startDateField`, `endDateField`, `titleField`, `colorField` or `allDayField` flat; they are written in its `calendar` block. The `dateField` / `endField` tombstones stay and now prescribe `calendar.startDateField` / `calendar.endDateField`. `ObjectGanttSchema` no longer declares the 29 `GanttConfig` keys flat (`startDateField`, `endDateField`, `titleField`, `progressField`, `dependenciesField`, `colorField`, `viewMode`, `summaryExtent`, `interactions` and the rest), nor the legacy `dependencyField` alias or the `search` / `searchableFields` pair; they are written in its `gantt` block (the pair is not authored at all). `staticData`, `filter`, `sort`, `gantt` and the other node keys stay. For `object-gantt` the strict authoring face already refused every flat key (its arm is the `properties` bag), so the narrowing there is the published TypeScript and zod export.
20+
- **`@object-ui/plugin-calendar`**: `ObjectCalendarFlatFields` and `ObjectCalendarHandoff` type the flat read `getCalendarConfig` keeps for a host that mounts the renderer directly. The renderer is unchanged: the `calendar` block wins, and the flat spelling is read only without it.
21+
- **`@object-ui/plugin-gantt`**: `ObjectGanttFlatFields` (derived from `GanttConfig`, plus the alias and the search pair) and `ObjectGanttHandoff` type the flat read the same way. The renderer is unchanged.
22+
- **`@objectstack/spec`**: the `object-calendar` and `object-gantt` flat-key refusals no longer say `ObjectView` / `ListView` emit the flat spelling; they hand the binding off in the block, and the flat spelling is only a stored-document fallback the renderer still reads. The prescription itself is unchanged.
23+
- **`@object-ui/plugin-list`** and **`@object-ui/plugin-view`**: `ListView` and `ObjectView`'s `generateViewSchema` emit `calendar: { … }` and `gantt: { … }` on the node they build instead of the flat keys. They emit the block only where the renderer's flat branch drew: a calendar once `startDateField` is declared, a gantt once both dates are, with `titleField` floored at `'name'` as that branch floored it. A gantt binding carrying a value the spec's `GanttConfigSchema` refuses (a pre-door stored `options.gantt`, say) goes out nested too, and now shows the renderer's refusal screen where the unvalidated flat branch drew it.
24+
25+
**BREAKING** for TypeScript code or hand-written nodes that write the flat keys on these two node types.
26+
27+
- **Before.** `const node: ObjectCalendarSchema = { type: 'object-calendar', objectName: 'event', startDateField: 'start' }` compiled, and the `object-calendar` strict face accepted it.
28+
- **Now.** The flat key is a compile error, and `objectui validate` refuses it by name on `object-calendar` (it already did on `object-gantt`). A renderer handed a flat node by a host still draws it.
29+
- **Fix.** Move the keys into the block: FROM `{ type: 'object-calendar', startDateField: 'start', titleField: 'name' }` TO `{ type: 'object-calendar', calendar: { startDateField: 'start', titleField: 'name' } }`, and FROM `{ type: 'object-gantt', startDateField: 's', endDateField: 'e', progressField: 'p' }` TO `{ type: 'object-gantt', gantt: { startDateField: 's', endDateField: 'e', titleField: 'name', progressField: 'p' } }` (the `gantt` block requires `titleField`). Code that hands a flat node straight to `<ObjectCalendar>` or `<ObjectGantt>` types it as `ObjectCalendarProps['schema']` or `ObjectGanttProps['schema']`, which carry the flat keys.

‎packages/spec/src/ui/component.zod.ts‎

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6195,8 +6195,11 @@ export type ObjectKanbanPropsParsed = z.infer<typeof ObjectKanbanPropsSchema>;
61956195

61966196
/**
61976197
* The flat per-field spellings `ObjectCalendar` keeps reading as a
6198-
* backward-compat fallback (`getCalendarConfig`; read points recorded in
6199-
* `liveness/page.json`, row `regions`) and that `ObjectView`/`ListView` emit on their runtime handoff. Read, but
6198+
* stored-document fallback (`getCalendarConfig`; read points recorded in
6199+
* `liveness/page.json`, row `regions`). `ObjectView`/`ListView` hand the
6200+
* binding off in the `calendar` block, not flat (objectstack#25923), and the
6201+
* flat keys live on `plugin-calendar`'s own hand-off type, not on
6202+
* `@object-ui/types`' `ObjectCalendarSchema`. Read, but
62006203
* NOT authorable — one composition key per concept (Prime Directive #12, the
62016204
* `body` → `children` precedent on {@link PageContainerProps}): the authored
62026205
* spelling is the `calendar` object.
@@ -6209,9 +6212,9 @@ const OBJECT_CALENDAR_FLAT_FIELD_GUIDANCE: readonly KeySetGuidance[] = [
62096212
examples: ['startDateField', 'titleField'],
62106213
prescription:
62116214
'Write this as a key of the `calendar` config object instead — `calendar: { startDateField, '
6212-
+ 'endDateField, titleField, colorField, allDayField }`. The flat spelling is the runtime handoff '
6213-
+ '`ObjectView`/`ListView` emit and a stored-document fallback the renderer keeps reading; it is '
6214-
+ 'not a second authorable spelling (one key per concept, Prime Directive #12).',
6215+
+ 'endDateField, titleField, colorField, allDayField }`. `ObjectView`/`ListView` hand the binding '
6216+
+ 'off in that block too; the flat spelling is only a stored-document fallback the renderer still '
6217+
+ 'reads, not a second authorable spelling (one key per concept, Prime Directive #12).',
62156218
},
62166219
];
62176220

@@ -7522,21 +7525,22 @@ export type ObjectMapProps = z.input<typeof ObjectMapPropsSchema>;
75227525
export type ObjectMapPropsParsed = z.infer<typeof ObjectMapPropsSchema>;
75237526

75247527
/**
7525-
* The flat `GanttConfig` spellings `getGanttConfig`'s branch 2 reads and that
7526-
* `ObjectView` / `ListView` EMIT when they flatten `options.gantt` — objectui's
7527-
* own `FLAT_GANTT_CONFIG_KEYS` (read points recorded in `liveness/page.json`,
7528-
* row `regions`). Read, but
7528+
* The flat `GanttConfig` spellings `getGanttConfig`'s branch 2 reads as a
7529+
* stored-document fallback — objectui's own `FLAT_GANTT_CONFIG_KEYS` (read
7530+
* points recorded in `liveness/page.json`, row `regions`). `ObjectView` /
7531+
* `ListView` hand the binding off in the `gantt` block, not flat
7532+
* (objectstack#25923). Read, but
75297533
* NOT authorable, for the reason the map's twin set above records: objectui#6469
75307534
* inherited the objectui#5018 ruling for this block, so the `gantt` block is the
75317535
* authoring shape, it is taken WHOLE when present, and every flat key beside it
75327536
* is named as ignored in a dev warning.
75337537
*
7534-
* The flat branch is the HOT path for this block — a hand-authored `gantt` block
7535-
* reaching the renderer through either view layer has already been flattened —
7536-
* which is exactly why the prescription matters here: an author who learned the
7537-
* flat spelling from a view config (or from `@object-ui/types`'
7538-
* `ObjectGanttSchema`, which declares the whole flat face) writes it on an SDUI
7539-
* node next, where nothing flattens anything.
7538+
* The block branch is the path both view layers take — they hand the `gantt`
7539+
* block on whole — and the flat branch is only the stored-document fallback.
7540+
* The prescription still matters: an author who met the flat spelling in an
7541+
* older stored document writes it on an SDUI node next. The flat keys live on
7542+
* `plugin-gantt`'s package-local hand-off type (`ObjectGanttFlatFields`), not
7543+
* on `@object-ui/types`' `ObjectGanttSchema`, which declares only the block.
75407544
*
75417545
* Membership is `GanttConfigSchema`'s own shape plus the legacy singular
75427546
* `dependencyField` alias the flat branch still reads beside `dependenciesField`
@@ -7561,9 +7565,10 @@ const OBJECT_GANTT_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [
75617565
examples: ['startDateField', 'endDateField', 'viewMode'],
75627566
prescription:
75637567
'Write this as a key of the `gantt` config object instead — `gantt: { startDateField, endDateField, '
7564-
+ 'titleField, ... }`. The flat top-level spelling is the internal form `ObjectView`/`ListView` '
7565-
+ 'produce when they flatten `options.gantt`, not a second authoring spelling: whenever a `gantt` '
7566-
+ 'block is present the renderer takes it WHOLE and names every flat key beside it as ignored. The '
7568+
+ 'titleField, ... }`. `ObjectView`/`ListView` hand the binding off in that block too; the flat '
7569+
+ 'top-level spelling is only a stored-document fallback the renderer still reads, not a second '
7570+
+ 'authoring spelling: whenever a `gantt` block is present the renderer takes it WHOLE and names '
7571+
+ 'every flat key beside it as ignored. The '
75677572
+ 'legacy singular `dependencyField` is `dependenciesField` inside that block.',
75687573
},
75697574
];
@@ -7631,7 +7636,7 @@ function objectGanttMarker() {
76317636
* `mobileReadOnly`.
76327637
*
76337638
* Measured and deliberately NOT declared: the flat `GanttConfig` spellings (the
7634-
* flatten product — {@link OBJECT_GANTT_FLAT_CONFIG_GUIDANCE}); `title`, which
7639+
* stored-document fallback — {@link OBJECT_GANTT_FLAT_CONFIG_GUIDANCE}); `title`, which
76357640
* this renderer never reads (the export-name chain is `gantt.exportFileName` →
76367641
* `label` → the OBJECT's label → `objectName`); a row cap — the reload's `$top`
76377642
* is the platform ceiling, spread in as core's `nonGridRowCeilingQuery()` (one
@@ -7642,7 +7647,7 @@ function objectGanttMarker() {
76427647
* reads, `search` and `searchableFields` (sent as `$search` / `$searchFields`),
76437648
* which `ListView` writes onto the node it generates from its toolbar Search
76447649
* box (its `case 'gantt'` arm) — a host-generated key pair in the same position
7645-
* as the flatten product, and not declared here.
7650+
* as the flat fallback spellings, and not declared here.
76467651
*
76477652
* VALUE posture: `gantt` is the one config block in this family whose value
76487653
* contract is already the SPEC's — the renderer validates it against

‎ui/packages/plugin-calendar/README.md‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -321,12 +321,14 @@ of its own — the only lever there is `sourceView`, pointed at a view that
321321
declares the block (objectui#8170).
322322

323323
The same five keys written flat on the node are **not** a second way to author
324-
this. That flat spelling is the runtime handoff — `ObjectView` and `ListView`
325-
emit it on the node they build, and `getCalendarConfig` falls back to it only
326-
when the node has no `calendar` block — so `ObjectCalendarSchema` declares it
327-
because the renderer reads it. `@objectstack/spec` refuses it at authoring with
328-
`unrecognized_keys` and names the `calendar` block in the same diagnostic (one
329-
key per concept, its Prime Directive #12).
324+
this. `@objectstack/spec` refuses them at authoring with `unrecognized_keys` and
325+
names the `calendar` block in the same diagnostic (one key per concept, its
326+
Prime Directive #12), and since objectstack#25923 `ObjectCalendarSchema` does
327+
not declare them flat either: `ObjectView` and `ListView` emit the `calendar`
328+
block on the node they build. `getCalendarConfig` still falls back to the flat
329+
spelling when a node has no `calendar` block, for a host that mounts the
330+
renderer directly; that read is typed on this package's own hand-off type,
331+
`ObjectCalendarHandoff`, not on the published schema.
330332

331333
```typescript
332334
import type { ObjectCalendarSchema } from '@object-ui/types';

‎ui/packages/plugin-calendar/src/ObjectCalendar.tsx‎

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
*/
2424

2525
import React, { useEffect, useState, useCallback, useMemo, useRef } from 'react';
26-
import type { ObjectCalendarSchema, DataSource, CalendarConfig } from '@object-ui/types';
26+
import type { ObjectCalendarSchema, ObjectCalendarBlockConfig, DataSource, CalendarConfig } from '@object-ui/types';
2727
import { CalendarView, type CalendarViewEvent } from './CalendarView';
2828
import { getVisibleDateRange } from './visibleDays';
2929
import { usePullToRefresh } from '@object-ui/mobile';
@@ -132,6 +132,26 @@ import {
132132
* Pinned in `__tests__/calendarUnionReads-8651.test.tsx`.
133133
*/
134134

135+
/**
136+
* The five field-name keys written FLAT on the node: the package-local
137+
* hand-off spelling (objectstack#25923, ui/AGENTS.md Commandment #0.1).
138+
*
139+
* `@objectstack/spec` refuses all five on `object-calendar` and prescribes the
140+
* `calendar` block, so the published {@link ObjectCalendarSchema} declares
141+
* none of them, and both producers in this tree (`ListView`, and
142+
* `ObjectView`'s `generateViewSchema`) emit the block. `getCalendarConfig`
143+
* still reads the flat five when a node carries no block, so a host that mounts
144+
* `<ObjectCalendar schema={…}>` with them keeps its calendar; this type is that
145+
* read, kept here, beside the one consumer, and off the authoring face.
146+
* DERIVED from the block's own type, so the two spellings cannot fork.
147+
*/
148+
export type ObjectCalendarFlatFields = Partial<
149+
Pick<ObjectCalendarBlockConfig, 'startDateField' | 'endDateField' | 'titleField' | 'colorField' | 'allDayField'>
150+
>;
151+
152+
/** The node `ObjectCalendar` reads: the published schema plus {@link ObjectCalendarFlatFields}. */
153+
export type ObjectCalendarHandoff = ObjectCalendarSchema & ObjectCalendarFlatFields;
154+
135155
/**
136156
* Props of the `ObjectCalendar` React component.
137157
*
@@ -149,7 +169,7 @@ import {
149169
* no importer breaks. Tripwire: `__tests__/spec-symbol-4650.test.ts`.
150170
*/
151171
export interface ObjectCalendarComponentProps {
152-
schema: ObjectCalendarSchema;
172+
schema: ObjectCalendarHandoff;
153173
dataSource?: DataSource;
154174
className?: string;
155175
/** Pre-fetched records passed by a parent (e.g. ObjectView). When provided, skips internal data fetching. */
@@ -320,13 +340,17 @@ type ObjectCalendarConfig = CalendarConfig & {
320340
* `calendar-date-alias-refusal-8355.test.ts` and `plugin-list`'s
321341
* `ListView.calendarAliasRefused-8355.test.tsx`.
322342
*/
323-
function getCalendarConfig(schema: ObjectCalendarSchema): ObjectCalendarConfig | null {
343+
function getCalendarConfig(schema: ObjectCalendarHandoff): ObjectCalendarConfig | null {
324344
// The declared configuration container — read FIRST, as the spec declares it.
325345
if (schema.calendar) {
326346
return schema.calendar as ObjectCalendarConfig;
327347
}
328348

329-
// The flat spelling, which `ObjectView` and `ListView` emit.
349+
// The flat spelling — the package-local hand-off (`ObjectCalendarFlatFields`).
350+
// No producer in this tree emits it since objectstack#25923 (`ListView` and
351+
// `ObjectView` hand the view's block on as `calendar`), and the spec refuses
352+
// it on an authored node; a host that mounts this component with it is the
353+
// reader this branch keeps working.
330354
//
331355
// ⛔ The two alias rungs are spelled as a cast ON `schema` at each read site,
332356
// never through a renamed local. The repo's read census (objectui#6576's
@@ -1446,8 +1470,9 @@ export const ObjectCalendar: React.FC<ObjectCalendarComponentProps> = ({
14461470
* `plugin-view`'s `ObjectView` — and app-shell reaches the first from two
14471471
* faces, `ObjectView` (the object-view door) and `InterfaceListPage` (the
14481472
* interface-page door). Every one of them hands this component the same
1449-
* shape: the shared `baseProps` bag plus whichever of `startDateField` /
1450-
* `endDateField` / `titleField` the author declared. NOTHING on the node
1473+
* shape: the shared `baseProps` bag plus a `calendar` block carrying whichever
1474+
* of `startDateField` / `endDateField` / `titleField` the author declared
1475+
* (flat on the node until objectstack#25923). NOTHING on the node
14511476
* names the door, so there is nothing to infer from, and a door PROP would
14521477
* have to be declared on the node's published schema and threaded through
14531478
* four packages.

0 commit comments

Comments
 (0)