Skip to content

Commit e233db9

Browse files
os-steveclaude
andauthored
feat(spec): declare element-level navigation on object-kanban / object-calendar and give object-timeline its ComponentPropsMap row (#17987) (#19219)
Fixes #17987 Clause-②: yes (widening) — one new optional key on two published element faces plus one new `ComponentPropsMap` row. Nothing previously admitted is refused, nothing is renamed or retired. Executes the objectui#8652 maintainer ruling, reply verbatim 「B」 — declare `navigation` on the platform element schemas. Options A / C / D were not taken and are not reopened here. ## What landed 1. **`object-kanban` and `object-calendar` declare `navigation`**, carrying `NavigationConfigSchema` — the def `ListViewSchema.navigation` already declares, taken BY REFERENCE so the vocabulary cannot fork. The retired `navigation.view` tombstone rides along onto both element faces. 2. **`object-timeline` gets its `ComponentPropsMap` row.** It was a registered objectui renderer reachable only through the component type union's open string arm with no row here, so the props gate skipped it: unjudged in both directions. The row declares the key set measured from the renderer's own read points at the `.objectui-sha` pin `53ded82b` — `objectName`, `timeline`, `filter`, `sort`, `limit`, `data`, `items`, `variant`, `dateFormat`, `rowLabel`, `minDate`, `maxDate`, `descriptionField`, `mapping`, `navigation` — with the per-key citations, and what it deliberately does NOT declare, in the schema's own header. 3. **The view-level `navigation` on `ListViewSchema` is untouched.** The element key is an ADDITIONAL carrier for the standalone placement, not a replacement; a test pins that half so a later sweep cannot fold the two into one question. ## Premise re-derived on today's `origin/main` before any edit Base `24d622b94b8afe93d00deda39062bf292e0d5d2f`. The card's own numbers had moved, so every leg was re-measured rather than inherited: | leg | card said | measured at base | | --- | --- | --- | | `ComponentPropsMap` entries | 45 | **48** (49 after this PR) | | `object-kanban` + `navigation` | refused by name | **refused**, `unrecognized_keys: ['navigation']` | | `object-calendar` + `navigation` | refused by name | **refused**, identically | | `object-timeline` row | absent | **absent** | | view face | carries `navigation` | **carries it**, and still does | Controls, in the same run, so the zeros are readings: - **Positive control that fires** — `object-kanban` + `cardFields` and `object-calendar` + `defaultView` both `success: true` at base, i.e. these faces were not refusing everything. - **Bogus-key control** — `zzzDefinitelyNotAKey` refused through the **same** `unrecognized_keys` channel on both faces, before and after: the refusal of `navigation` was the generic unknown-key rule, and it still is for an undeclared key. - **Firing control for the timeline zero** — `object-kanban` / `object-calendar` / `object-grid` / `object-tree` all resolved in the same call, so "no entry" was a reading rather than a dead probe. ## The card's acceptance criterion, run `ComponentPropsMap['object-kanban'].safeParse({ objectName: 'task', navigation: { mode: 'drawer' } })` returns `success: true`, `ComponentPropsMap['object-timeline']` is defined, and the bogus-key control still returns `success: false` through `unrecognized_keys`. All three legs, plus the control, are pinned in `packages/spec/src/ui/component-element-navigation-17987.test.ts` (12 tests at the current head; the sentence above is the card's criterion, and the leg added in round 2 sits beside it). The parsed state is the view face's: `{ mode: 'drawer', preventNavigation: false, openNewTab: false, size: 'auto' }`. The "cannot fork" clause has teeth rather than prose: the element door is asserted to resolve to the **very object** `ListViewSchema.navigation` carries, and the two faces' accept/refuse verdicts are asserted EQUAL over a five-document battery that contains both verdicts (so the equality cannot hold vacuously). ## Reverse verification — the fix committed first, then mutated on disk ⚠️ **The table below is ROUND 1, taken against head `02d81323c9`.** Its numbers and blobs are that head's and are left exactly as measured — ⛔ not restated as if they were the current head's. The round-2 leg follows it. Both legs went through `scripts/ablation-replace.mjs`, so the write is proven by anchor counts and blob hashes rather than by an exit code, and each restore is proven by `blob == HEAD` with `git diff HEAD` empty. | ablation | predicted | observed | | --- | --- | --- | | rename the `navigation` door on all three faces (anchor x3 → x0, blob `9b38bfa56e57` → `4f12296e0afc`) | red on the acceptance legs, controls unmoved | **5 failed / 6 passed** — the bogus-key control, the no-default leg, the timeline-row-defined leg, the view-face leg and the two timeline-key legs all stayed green | | rename the `object-timeline` row key (anchor x1 → x0, blob `9b38bfa56e57` → `e2b329749e22`) | red on every timeline leg | **9 failed / 2 passed** — the two survivors are the view-face leg and the `TimelineConfigSchema` prescription leg, neither of which depends on the row | Restored to `9b38bfa56e57b72e630540950d55f8fd09b6bee2` both times; the suite is green on the restored tree. **Round 2, head `ee50b4cd13`** — the `data` door mutated through the same tool (anchor x1 → x0, blob `d663b3c630d6`): predicted red on exactly the two legs that author `data` and nothing else; observed **2 failed / 10 passed**, with the bogus-key control on that same face staying green and its refusal naming the bogus key and not `data`. Restore proven by `blob == HEAD` with `git diff HEAD` empty. ⭐ The round-2 contract reviewer re-ran this leg independently with a different replacement string and read the same 2 failed / 10 passed. ## Gates `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` derives **107** families for this diff. All 107 were run and reconciled with `--ran`: **107 run, 0 NOT-MEASURED, 0 UNRUN**. - `pnpm --filter @objectstack/spec build` then `check:generated` — green; six artifacts were regenerated by `--fix` (api-surface, api-surface-declarations, export-origins, declaration-map, the reference pages, the strictness ledger) plus `gen:schema`'s authorable-surface and json-schema manifest. - `pnpm --filter @objectstack/spec test` — **500 files / 14611 tests passed** at the ROUND-1 head `02d81323c9`; `typecheck` green. - ⭐ **At the current head `ee50b4cd13`: 500 files / 14612 tests passed** — read twice independently, by the implementer and by the round-2 contract reviewer, and `typecheck` green in both. - `pnpm lint` — repo-wide, exit 0. Not a narrowed run: the whole `eslint . --no-inline-config` universe. - `check:react-declaration-parity` in **CI's own spelling** (`MANIFEST="$PWD/sdui.manifest.json" … --baseline react-declaration-parity.baseline.json --strict`) exits 0: `object-timeline` reads `2 declared by both, 13 spec-only, 0 registry-only`, so the new row needs no baseline entry and the ratchet reports no new divergence. ⚠️ The BARE form in the derived list exits 1 on purpose — with no `MANIFEST` the gate reports "did not run" rather than skipping — and that non-zero in `ran.list` is that refusal, not a divergence. - The heavy runs went through `scripts/pm/os-verify-lock.sh`; the six path-scheduled CI jobs, the artifact-roster families and the wide-population families are CI's farm and are NOT measured here. ## Two files outside the card's declared surface, named rather than widened silently The card's surface is `packages/spec/src/ui/` plus the changeset and the generated pages that follow. Two more tracked files had to move, both mechanically and both printed by the gate that refused without them: - `packages/spec/dropped-refinements.baseline.json` — the build REFUSES until `ui/ObjectTimelineProps` declares the two sites its projection cannot carry (`filter.element`, `timeline.groupByField`), and prints the entry verbatim. It is the same row `ui/ObjectGanttProps` already carries. Its header totals moved with it (204 schemas / 560 sites), which `scripts/dropped-refinements.test.ts` asserts. - `docs/audits/2026-07-unknown-key-strictness-ledger.counts.md` — regenerated by `gen:strictness-ledger`: one more closed object site in `ui/`. ## Acceptance notes — observed in passing, not filed, not fixed here - **`object-grid`, `object-gantt` and `object-tree` declare `navigation` as `z.unknown()`.** Three element faces therefore accept `navigation: { mode: 'drawr' }` and `navigation: 'drawer'` while the three this card touches now judge the value. Narrowing a published accept face is a different act from widening one, it is outside this card's closed scope, and it deserves its own ruling. Reported to the seat with dedupe words rather than filed from here. - **objectui's `ListViewTimelineConfig` declares a nested `timeline.dateField` alias that `TimelineConfigSchema` refuses by name**, and the renderer reads it. That is the same defect shape as this card on a different key, on the VIEW face, so it is neither in scope nor fixable as a rider. Reported likewise. This PR's guidance prescription is careful about it: it names `timeline.startDateField`, the key the alias resolves to and one `TimelineConfigSchema` accepts — the trap #17054 already paid for once, pinned in the new test. - `object-tree` reads "NO component in the manifest" in the parity gate. Pre-existing, untouched, and outside the baseline's ratchet. ## Downstream objectui#8652, objectui#8654 and objectui#8651's `CalendarSchema` arm wait on this. ⚠️ Their unlock criterion is **consumer-installable** — a released `@objectstack/spec` carrying the declaration, installable there, with the pin moved — ⛔ not this merging. <sub>Body edited by the dispatching seat `domain:spec#4` (`session_01AmH9bKvGoLjiY86Q4Z3og2`) after the rework landed at `ee50b4cd13e07a2d3327e78d5f98aefd30ed85ab`: the `object-timeline` key list gained `data`, and the parity figure moved from 12 to 13 spec-only. The implementer named both edits in its report and correctly declined to patch this body itself — a dev writes a PR body once, at the POST that opens it.</sub> --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 805811e commit e233db9

13 files changed

Lines changed: 1026 additions & 25 deletions

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
feat(spec): declare `navigation` on the standalone `object-kanban` / `object-calendar` element faces, and give `object-timeline` the `ComponentPropsMap` row it never had (#17987)
6+
7+
Clause-②: yes (widening) — one new optional key on two published element faces plus one new row, so the accept set grows. Nothing previously admitted is refused, nothing is renamed or retired, and no producer is required to write anything.
8+
9+
**What changes for an author.** A record-click navigation block written on a
10+
STANDALONE element node is now declared where it is read. Before this, the same
11+
document ran correctly in objectui's renderer and was refused by name at the
12+
authoring door:
13+
14+
```
15+
FROM ComponentPropsMap['object-kanban'].safeParse({ objectName: 'task',
16+
navigation: { mode: 'drawer' } })
17+
-> success: false, unrecognized_keys: ['navigation']
18+
TO -> success: true, navigation: { mode: 'drawer', preventNavigation: false,
19+
openNewTab: false, size: 'auto' }
20+
```
21+
22+
`object-calendar` moves identically. The value is `NavigationConfigSchema` —
23+
the same def `ListViewSchema.navigation` already declares, taken by reference,
24+
so a standalone element and a list view speak one vocabulary and the retired
25+
`navigation.view` key (17.5.0) stays retired on every face that carries it.
26+
27+
**`object-timeline` gains a row.** It was registered in objectui and reachable
28+
through the component type union's open string arm with no entry in
29+
`ComponentPropsMap`, so the authoring gate skipped it entirely: a real key and
30+
a typo rode through alike. The row declares the key set measured from the
31+
renderer's own read points at the `.objectui-sha` pin this repo builds against
32+
— `objectName`, `timeline`, `filter`, `sort`, `limit`, `data`, `items`,
33+
`variant`, `dateFormat`, `rowLabel`, `minDate`, `maxDate`, `descriptionField`,
34+
`mapping` and `navigation` — and refuses everything else, the flat `startDateField` /
35+
`titleField` / `scale` handoff spellings with a prescription pointing at the
36+
`timeline` config block that owns them.
37+
38+
**What does NOT change.** The view-level `navigation` on `ListViewSchema` is
39+
untouched: the element key is an ADDITIONAL carrier for the standalone
40+
placement, not a replacement, and both faces keep judging the same block. The
41+
parse is unchanged for every document that did not author these keys, and the
42+
component type union is not narrowed — an `object-timeline` node reaches
43+
`PageComponentSchema` through the open string arm exactly as it did before.
44+
45+
Executes the objectui#8652 maintainer ruling (verbatim `B`).

‎content/docs/references/index.mdx‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Protocol Reference
3-
description: Every schema published by @objectstack/spec — 1531 schemas across 14 protocol modules
3+
description: Every schema published by @objectstack/spec — 1532 schemas across 14 protocol modules
44
---
55

66
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
@@ -32,8 +32,8 @@ counts are sums of the rows they head. Regenerate with
3232
| [Shared Protocol](/docs/references/shared) | 10 | 31 | Primitives used across every protocol — identifiers, HTTP, expressions, error maps, enums. |
3333
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
3434
| [System Protocol](/docs/references/system) | 34 | 273 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
35-
| [UI Protocol](/docs/references/ui) | 16 | 156 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
36-
| **Total** | **195** | **1531** | 14 protocol modules |
35+
| [UI Protocol](/docs/references/ui) | 16 | 157 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
36+
| **Total** | **195** | **1532** | 14 protocol modules |
3737

3838
---
3939

@@ -361,7 +361,7 @@ The runtime environment — logging, jobs, cache, metrics, notifications, i18n a
361361

362362
## UI Protocol
363363

364-
**Source:** `packages/spec/src/ui/` · **Import:** `@objectstack/spec/ui` · **16 pages, 156 schemas**
364+
**Source:** `packages/spec/src/ui/` · **Import:** `@objectstack/spec/ui` · **16 pages, 157 schemas**
365365

366366
Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer.
367367

@@ -372,7 +372,7 @@ Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI lay
372372
| [`app.zod.ts`](/docs/references/ui/app) | `ActionNavItem`, `App`, `AppBranding`, `AppContextSelector`, `ComponentNavItem`, `DashboardNavItem`, `GroupNavItem`, `NavigationArea`, `NavigationContribution`, `NavigationItem`, `ObjectNavItem`, `PageNavItem`, `ReportNavItem`, `UrlNavItem` |
373373
| [`bulk-action.zod.ts`](/docs/references/ui/bulk-action) | `BulkActionDef`, `BulkActionExecution`, `BulkActionOperation`, `BulkActionParam` |
374374
| [`chart.zod.ts`](/docs/references/ui/chart) | `ChartAggregate`, `ChartAggregateFunction`, `ChartAnnotation`, `ChartAxis`, `ChartConfig`, `ChartDrillDown`, `ChartGroupBy`, `ChartInteraction`, `ChartSeries`, `ChartType` |
375-
| [`component.zod.ts`](/docs/references/ui/component) | `AIChatWindowProps`, `ElementButtonProps`, `ElementFilterProps`, `ElementFormProps`, `ElementImageProps`, `ElementMetadataViewerProps`, `ElementNumberProps`, `ElementRecordPickerProps`, `ElementTextInputProps`, `ElementTextProps`, `ObjectCalendarProps`, `ObjectFormProps`, `ObjectGanttProps`, `ObjectGridProps`, `ObjectKanbanProps`, `ObjectMapProps`, `ObjectMasterDetailFormProps`, `ObjectMetricProps`, `ObjectTreeProps`, `PageAccordionProps`, `PageCardProps`, `PageContainerProps`, `PageHeaderProps`, `PageTabsProps`, `RecordActivityProps`, `RecordAlertAction`, `RecordAlertProps`, `RecordChatterProps`, `RecordDetailsProps`, `RecordHighlightsField`, `RecordHighlightsProps`, `RecordHistoryProps`, `RecordPathProps`, `RecordQuickActionsProps`, `RecordReferenceRailProps`, `RecordRelatedListProps`, `ReferenceRailEntry` |
375+
| [`component.zod.ts`](/docs/references/ui/component) | `AIChatWindowProps`, `ElementButtonProps`, `ElementFilterProps`, `ElementFormProps`, `ElementImageProps`, `ElementMetadataViewerProps`, `ElementNumberProps`, `ElementRecordPickerProps`, `ElementTextInputProps`, `ElementTextProps`, `ObjectCalendarProps`, `ObjectFormProps`, `ObjectGanttProps`, `ObjectGridProps`, `ObjectKanbanProps`, `ObjectMapProps`, `ObjectMasterDetailFormProps`, `ObjectMetricProps`, `ObjectTimelineProps`, `ObjectTreeProps`, `PageAccordionProps`, `PageCardProps`, `PageContainerProps`, `PageHeaderProps`, `PageTabsProps`, `RecordActivityProps`, `RecordAlertAction`, `RecordAlertProps`, `RecordChatterProps`, `RecordDetailsProps`, `RecordHighlightsField`, `RecordHighlightsProps`, `RecordHistoryProps`, `RecordPathProps`, `RecordQuickActionsProps`, `RecordReferenceRailProps`, `RecordRelatedListProps`, `ReferenceRailEntry` |
376376
| [`dashboard.zod.ts`](/docs/references/ui/dashboard) | `Dashboard`, `DashboardHeader`, `DashboardHeaderAction`, `DashboardWidget`, `DashboardWidgetOptions`, `GlobalFilter`, `GlobalFilterOptionsFrom`, `WidgetActionType`, `WidgetColorVariant` |
377377
| [`dataset.zod.ts`](/docs/references/ui/dataset) | `Dataset`, `DatasetDimension`, `DatasetMeasure`, `DerivedMeasureOp` |
378378
| [`expression-bindable-text-keys.zod.ts`](/docs/references/ui/expression-bindable-text-keys) | `ExpressionBindableTextKey` |

0 commit comments

Comments
 (0)