Repository navigation
Commit 529d971
Part of #21464
Clause-②: yes (narrowing)
## What this does
This is the first stage of the `ComponentPropsMap` `z.unknown()`
close-out (triage `5961300594`, claim `5962381903`). It does two things.
1. **`navigation` is typed on `object-map`, `object-gantt` and
`object-tree`.** Each row took `z.unknown()`, although each renderer
hands the member to the console's shared navigation hook, which reads
`navigation.mode` and falls back to `page`. Any value passed the
component-props gate, and an off-shape one opened the record page in
silence. Each row now takes the list view's `NavigationConfigSchema` by
reference. That is the block `object-grid`, `object-kanban`,
`object-calendar` and `object-timeline` already take.
2. **One enumeration pin holds the whole map.**
`packages/spec/src/ui/component-props-unknown-members.pin.test.ts` walks
every `ComponentPropsMap` row, into arrays, record values, union arms,
lazy schemas and catchalls. It fails on any `z.unknown()` member that is
neither typed nor listed with a recorded reason. It also fails on a
ledger line whose member is no longer `z.unknown()`, so the ledger can
only shrink as later stages type members.
**Why `Part of`, not a closing line:** the census found more than one
reviewable PR's worth (the dispatch's size rule, A7). There are 31
renderer-read members. The precedent, PR #21463, spent about 1,100 lines
on 7 members. The other 28 are recorded in the pin as `staged`, each
with its reader at the pin and a named stage. The staging proposal below
is in the report for a ruling. #21464 remains open for those stages.
### `navigation`, measured at the `.objectui-sha` pin `89cad75d55`
| row | was | now | read point at the pin |
|:--|:--|:--|:--|
| `object-map` | `z.unknown()` | `NavigationConfigSchema`, by reference
| `plugin-map/src/ObjectMap.tsx:1189` hands it to
`useNavigationOverlay`, which reads `navigation?.mode ?? 'page'`
(`react/src/hooks/useNavigationOverlay.ts:364`) |
| `object-gantt` | `z.unknown()` | `NavigationConfigSchema`, by
reference | `plugin-gantt/src/ObjectGantt.tsx:1960` reads
`schema.navigation ?? { mode: 'drawer' }` and hands it to the hook
(`:2025-2026`) |
| `object-tree` | `z.unknown()` | `NavigationConfigSchema`, by reference
| `plugin-tree/src/ObjectTree.tsx:1054` hands it to the hook (`:1046`) |
objectui's own node types and Zod mirrors already declare this member as
`ViewNavigationConfig` or the spec's `NavigationConfigSchema`
(`types/src/objectql.ts`, `types/src/zod/objectql.zod.ts`). The spec row
now agrees with its sibling.
**No measured writer is refused (A3).**
- **objectstack, on the merged head:** 5 files per row name the type.
None authors `navigation` on one of these blocks. The one co-mention
writes app navigation arrays. Control: `objectName` is found in 4 of the
5 files per row.
- **objectui, at the pin:** 88 / 98 / 59 files name `object-map` /
`object-gantt` / `object-tree`. Control: `objectName` is found in 55 /
70 / 40 of them. They contain 77 `navigation:` lines. Every distinct
authored literal parses on all three rows, 36 of 36: `{ mode }` with
each of the seven modes, plus `size: 'lg'` and `openNewTab`.
- The values that do not parse are probes that already expect a refusal.
`navigation: 'anything'` is a both-faces parity probe. `navigation:
'drawer'` sits under a `@ts-expect-error`. `{ mode: 'sideways' }` is an
expected `navigation.mode` issue.
- The `{ mode: 'drawer', width: '600px' }` example belongs to
`object-view` (`plugin-view/src/ObjectView.tsx:1060`), not these rows.
## The census (A1), whole
The census covers 107 `z.unknown()` members across 56 rows, on the
merged head. It is unchanged from the base minus the three this PR
types. Writer counts are file co-mentions (a file naming the type and
authoring `KEY:`), with a control key. They are an upper bound, not a
value census: each later stage runs its own value-level census first.
**31 members a renderer reads with a fixed shape.** Citations are
objectui at `89cad75d55`. Writers are "os N/M (control)" for objectstack
and "ou N/M (control)" for objectui.
| row | key | reader at the pin | by-reference candidate | writers |
decision |
|:--|:--|:--|:--|:--|:--|
| object-map / object-gantt / object-tree | `navigation` | see the table
above | `NavigationConfigSchema` | os 0 on the row · ou 36/36 literals
parse | **typed here** |
| object-metric | `aggregate` |
`plugin-dashboard/src/ObjectMetricWidget.tsx:250`, `.field` /
`.function` at :404 | chart `aggregate` (`chart.zod.ts:644`) | os 7/8
(7) · ou 17/36 (20) | stage `object-metric` |
| object-metric | `trend` | `ObjectMetricWidget.tsx:254` (typed :176) |
none: to the renderer's read | os 0/8 · ou 1/36 | stage `object-metric`
|
| object-metric | `drillDown` | `ObjectMetricWidget.tsx:265`
(`ObjectMetricDrillDownConfig` :218) | chart `drillDown`
(`chart.zod.ts:663`) | os 0/8 · ou 7/36 | stage `object-metric` |
| object-metric | `compareTo` | `ObjectMetricWidget.tsx:267`
(`CompareToConfig` :241) | dashboard widget `compareTo`
(`dashboard.zod.ts:1205`) | os 1/8 · ou 4/36 | stage `object-metric` |
| object-form | `fields[]` | `plugin-form/src/ObjectForm.tsx:961` | none
| os 11/14 (12) · ou 178/210 (188) | stage `object-form` |
| object-form | `customFields` | `ObjectForm.tsx:755`, :1180 | none | os
0/14 · ou 21/210 | stage `object-form` |
| object-form | `sections[]` | `ObjectForm.tsx:364`, :1518 |
`FormViewSchema.sections` | os 3/14 · ou 78/210 | stage `object-form` |
| object-form | `contentLayout` | `plugin-form/src/ModalForm.tsx:854`
(`'simple'` or `'tabbed'`, :151) | none | os 0/14 · ou 7/210 | stage
`object-form` |
| object-form | `submitBehavior` | `ObjectForm.tsx:1312-1313` |
`FormViewSchema.submitBehavior` | os 2/14 · ou 12/210 | stage
`object-form` |
| object-form | `navigateOnSuccess` | `ObjectForm.tsx:1373`, :1412 |
none | os 0/14 · ou 5/210 | stage `object-form` |
| object-form | `mobile` | `ObjectForm.tsx:1857` | none | os 0/14 · ou
7/210 | stage `object-form` |
| object-master-detail-form | `sections[]`, `fields[]` |
`plugin-form/src/MasterDetailForm.tsx:1692-1693`, into the parent form |
as `object-form` | os 2/4, 4/4 (4) · ou 9/29, 18/29 (21) | stage
`object-form` |
| object-grid | `columns[]` | `plugin-grid/src/ObjectGrid.tsx:2158`
(`normalizeColumns`, string or `ListColumn`) | `ListViewSchema.columns`
| os 7/21 (16) · ou 198/292 (248) | stage `list-family` |
| object-grid | `fields[]` | `ObjectGrid.tsx:1946` | none | os 7/21 · ou
191/292 | stage `list-family` |
| object-grid | `selection` | `ObjectGrid.tsx:4799-4810` (`.type`) |
`ListViewSchema.selection` | os 1/21 · ou 19/292 | stage `list-family` |
| object-grid | `selectable` | `ObjectGrid.tsx:4813-4815` | none | os
0/21 · ou 7/292 | stage `list-family` |
| object-grid | `rowActions[]` | `ObjectGrid.tsx:1834-1835` (`string[]`)
| `ListViewSchema.rowActions` | os 1/21 · ou 19/292 | stage
`list-family` |
| object-grid | `bulkActions[]`, `batchActions[]` |
`ObjectGrid.tsx:4763` (`batchActions ?? bulkActions`) |
`ListViewSchema.bulkActions` | os 2/21, 0/21 · ou 27/292, 7/292 | stage
`list-family` |
| object-kanban | `columns[]` |
`plugin-kanban/src/KanbanBoardCore.tsx:95` | none | os 4/12 (9) · ou
87/152 (99) | stage `list-family` |
| object-calendar | `calendar` |
`plugin-calendar/src/ObjectCalendar.tsx:296-297`
(`ObjectCalendarConfig`) | `ListViewSchema.calendar` | os 1/6 (6) · ou
50/83 (62) | stage `list-family` |
| object-gantt | `markers[]` | `plugin-gantt/src/ObjectGantt.tsx:2497`
(`GanttMarker`) | none in the spec | os 0/5 (4) · ou 6/98 (70) | stage
`objectui-held` |
| object-timeline | `items[]` |
`plugin-timeline/src/ObjectTimeline.tsx:587` | none in the spec | os 3/3
(3) · ou 12/45 (34) | stage `objectui-held` |
| object-timeline | `mapping` | `ObjectTimeline.tsx:551`, :576-579
(`TimelineMappingSchema`) | none in the spec | os 2/3 · ou 2/45 | stage
`objectui-held` |
| action:group | `actions[]{}` |
`components/src/renderers/action/action-group.tsx:303`
(`UIActionSchema[]`) | none in the spec (`UIActionSchema` is objectui's
interface) | os 2/3 (label 3) · ou 23/36 (26) | stage `objectui-held` |
| action:menu | `actions[]{}` |
`components/src/renderers/action/action-menu.tsx:339`
(`UIActionSchema[]`) | as above | os 2/4 (3) · ou 23/38 (28) | stage
`objectui-held` |
| object-kanban | `conditionalFormatting` | `KanbanBoardCore.tsx:114`,
evaluated at `KanbanImpl.tsx:179` |
`ListViewSchema.conditionalFormatting` | os 1/12 · ou 9/152 | **held for
a ruling (A3)** |
**76 members with a recorded reason, grouped:**
- **slot (7):** child-component lists. Each is a declared slot position
(§2 checks this), judged at its own node: `page:tabs` / `page:accordion`
`items[].children[]`, `page:card` `children[]` / `footer[]`,
`page:footer` / `page:sidebar` / `page:section` `children[]`.
- **runner (16):** `action:button` and `action:icon` `params`,
`bodyExtra`, `bodyShape`, `operation`, `patch`, `toast`, `resultDialog`
and `onSuccess`. Each `.describe()` says "forwarded to the runner" (§2
checks this).
- **records (18):** record rows and field values, shaped by the bound
object's fields: `staticData[]`, `data[]`, `data.items[]`,
`initialValues{}` / `initialData{}`, and the grid bulk action's
`patch{}` / `params[].default`.
- **shared (35):** members of a schema another file owns. These are the
expression AST beside every `source` (14), the HTTP data source's
`params{}` / `body` (16), the inline JSON Schema document (4), and the
bulk-action option entry, deliberately open (1).
**3 one-off reasons:**
- `element:definition-list` `items[].description` is shown as-is
(`components/src/renderers/basic/data-list.tsx:68`, `toText`).
- `object-grid` `pagination.*` is a deliberately open `z.looseObject`,
whose named members are typed.
- `ai:chat_window` `context{}` has no reader: objectui registers no
renderer for the row
(`components/src/renderers/placeholders.tsx:109-113`). Per A2 it is an
open question, not a verdict (see the report).
## Held for a ruling: `object-kanban` `conditionalFormatting` (A3)
The list view's own `conditionalFormatting`, the shape the grid took in
PR #21463, refuses both rule dialects that objectui's own kanban
fixtures author:
- `{ field, operator, value, backgroundColor }`
(`plugin-kanban/src/__tests__/ObjectKanban.structuredMembersReachTheirSinks-8313.test.tsx:463`)
is refused with `unrecognized_keys` and `invalid_type@style`.
- `{ condition, backgroundColor }` (`:479`) is refused with
`invalid_type@style` and `unrecognized_keys[backgroundColor]`.
- objectui's own
`types/src/__tests__/kanban-conditional-formatting.test.ts:29-52`
asserts that its kanban schema accepts both, and a mix of the two.
So the narrowing is not shipped. It is recorded as
`staged('held-for-decision', …)` and reported with options.
## The pin, red then green (A4), and its ablation
- **Green on the final head:** `vitest run --project local …
component-props-unknown-members.pin.test.ts` reports `Tests 51 passed
(51)`.
- **Ablation.** The mutation reverts `object-map`'s `navigation` to
`z.unknown()`, with no ledger line, through `node
scripts/ablation-replace.mjs` in WRAP mode under the verify lock. It
landed: `anchor x1 → x0`, `replace x0 → x1`, blob `58b6e27fbc →
7e3b411604`.
- The pin went **red**: `Tests 8 failed | 43 passed (51)`.
- §1 received `["object-map navigation"]` against an expected `[]`.
- §1's census read `expected 108 to be 107`.
- The six object-map §4 cases failed (unwrap identity, parse equality,
four refusals).
- **Restore, proven by blob:** `blob after restore 58b6e27… == blob
at HEAD 58b6e27…`, `git diff HEAD empty`. The rerun was green again:
`Tests 51 passed (51)`.
- The pin imports `./component.zod` from source, so no build sits
between the mutation and the run.
## Gates, on `c222ab563b`
- **Fix round after review `5963877733`:** merged `origin/main`
`85e29b8858` and regenerated `content/docs/references/ui/component.mdx`
(the optionality face `fd96a8473d` renders). On `c222ab563b`,
`check:generated`, `check:migration-registry`, spec `test`, spec
`typecheck` and `check:doc-authoring` all exit 0. The other five files
are byte-unchanged since `d3431deed3`, where the readings below were
taken.
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` (no paths; merge base `100c394f6f`, 6 changed paths) derived
114 commands. Every exit code was written to disk before anything read
it. **All 114 exit 0**, reconciled with `--ran`.
- Six spec gates first answered `PREREQUISITE NOT MET` (exit 3), because
`dist/` predated the final pin edit. They were rerun after `pnpm
--filter @objectstack/spec build` at the final head, together with the
rest of the spec family.
- `pnpm --filter @objectstack/spec test`: `Test Files 602 passed | 1
skipped (603)`, `Tests 17832 passed`.
- `pnpm --filter @objectstack/spec typecheck`: exit 0, with
`check:test-typecheck: OK — … test layer compiles under
packages/spec/tsconfig.test.json`. The package `tsconfig.json` excludes
`**/*.test.ts`, so it is the test layer that covers the pin.
- `scripts/step18-rationale-merge.test.ts` (`--project repo`): `Tests 9
passed (9)`.
- `check:generated`: `✓ All 15 generated artifacts are up to date`.
- `check:migration-registry`: `✓ src/migrations/registry.ts is current
(353 semantic, 246 retired-key, 217 retired-def)`.
- `check:liveness`, `check:strictness-ledger` and
`check:authorable-surface` are green. `authorable-surface/ui.json` and
the strictness counts did not move. `component.mdx` is regenerated.
- `check:doc-authoring`: `✓ doc authoring guard: 17287 customer-facing
string(s) … clean`.
- `check:nul-bytes`: `check-nul-bytes: OK (… no raw ASCII control
bytes)`.
- **Semver:** `@objectstack/spec` `minor`, BREAKING banner, `Clause-②:
yes (narrowing)`, and the ADR-0087 marker `registered
ui-object-map-gantt-tree-navigation-typed`.
- `check-adr-0087-registration`: `✓ 1 declared-breaking changeset(s),
each carrying an ADR-0087 disposition`.
- `check-changeset-no-major --base origin/main`: `✓ This diff introduces
no major bump`.
- The level axis, run with this body as the `--event` payload, is quoted
in the report.
## The retirement kit
- **D3 entry:** `ui-object-map-gantt-tree-navigation-typed`, a sibling
of `ui-object-grid-row-members-typed`.
- **Step-18 rationale fragment:** order 64, sorted by id.
- **Regenerated:** `migrations/registry.ts` and
`content/docs/references/ui/component.mdx`.
- **No conversion is registered:** page-component `properties` is not
parsed on the save or load path, and an off-shape value has no rewrite
that honours what the author meant.
## Acceptance notes
- The hot-file check (A8): no open PR touches `ui/component.zod.ts`,
`component.mdx`, `migrations/registry.ts`, `authorable-surface/ui.json`
or the pin. Seven of the eight open PRs were read in full. The bot
Version Packages PR was read only to its first file page, because the
proxy refused its pagination.
- Deployed metadata was not measured.
- The Console Pin Gate is CI's. objectui at the pin already types these
members as `NavigationConfig`.
- The `ai:chat_window` row has zero authored writers in either
repository. objectui excludes it from the palette and treats it as a
tombstone. Whether it retires is in the report's open questions, not in
this PR.
## Resume
A container restart stopped the first run after it pushed `c7bb62ae6f`,
`527957de24` and `9a8037abbe`, and before it opened a PR. This run made
the following changes:
- confirmed the staging decision against the census;
- merged `origin/main` through `os-regen-merge.sh` (no regeneration was
owed);
- re-measured the navigation writers and the kanban refusal;
- corrected the pin's two stage reasons to name the real candidates;
- finished the changeset;
- ran every gate on the final head.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent f9a8eb8 commit 529d971
6 files changed
Lines changed: 637 additions & 12 deletions
File tree
- .changeset
- content/docs/references/ui
- packages/spec/src
- migrations
- entries/semantic
- ui
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
584 | 584 | | |
585 | 585 | | |
586 | 586 | | |
587 | | - | |
| 587 | + | |
588 | 588 | | |
589 | 589 | | |
590 | 590 | | |
| |||
679 | 679 | | |
680 | 680 | | |
681 | 681 | | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
682 | 693 | | |
683 | 694 | | |
684 | 695 | | |
| |||
940 | 951 | | |
941 | 952 | | |
942 | 953 | | |
943 | | - | |
| 954 | + | |
944 | 955 | | |
945 | 956 | | |
946 | 957 | | |
| |||
1005 | 1016 | | |
1006 | 1017 | | |
1007 | 1018 | | |
| 1019 | + | |
| 1020 | + | |
| 1021 | + | |
| 1022 | + | |
| 1023 | + | |
| 1024 | + | |
| 1025 | + | |
| 1026 | + | |
| 1027 | + | |
| 1028 | + | |
| 1029 | + | |
1008 | 1030 | | |
1009 | 1031 | | |
1010 | 1032 | | |
| |||
1164 | 1186 | | |
1165 | 1187 | | |
1166 | 1188 | | |
1167 | | - | |
| 1189 | + | |
1168 | 1190 | | |
1169 | 1191 | | |
1170 | 1192 | | |
| |||
1215 | 1237 | | |
1216 | 1238 | | |
1217 | 1239 | | |
| 1240 | + | |
| 1241 | + | |
| 1242 | + | |
| 1243 | + | |
| 1244 | + | |
| 1245 | + | |
| 1246 | + | |
| 1247 | + | |
| 1248 | + | |
| 1249 | + | |
| 1250 | + | |
1218 | 1251 | | |
1219 | 1252 | | |
1220 | 1253 | | |
| |||
0 commit comments