Skip to content

Commit 2b80461

Browse files
fix(spec): the four ComponentPropsMap navigation doors list all seven NavigationModeSchema modes (#19675)
Part of #18459 (item N2) > ⚠️ **Closing keyword corrected by the seat from `Fixes` to `Part of`.** Card #18459 carries FOUR items. N1 is measured DEAD on main (PR #18904 replaced the enumerating describe with a pointer at `ListViewSchema.map`, whose schema declares every key `.optional()`), and **N2 is what this PR lands**. ⛔ **N3 and N4 are NOT fixed** — both were re-measured at the live pin and deliberately left on the card, N3 with fresh numbers that supersede the card's own stale proposals. A `Fixes` would have closed the card on merge and destroyed both. The seat releases the card back to the queue on landing with a `Release:` naming what landed and what remains. Clause-②: no ## Summary The card carried **two shipped imprecisions**. Re-measured at the LIVE objectui pin `87af769e9a3ee28ace099fdd653d3ebd79fe82e2` before anything was changed, **one is dead and one survives** — and the surviving one is wider than the card scoped it. This PR fixes the surviving one. The falsification of the other is recorded below rather than acted on. ## Zone 2 — what the re-measurement found ### N1 (lat/lng pair marked required) — FALSIFIED, already fixed. No change here. At PR #18403 the `object-map` `map` row read: `'Map field config, the author face: { latitudeField, longitudeField, locationField?, titleField?, descriptionField?, zoom?, center?, style? }. Taken WHOLE when present ...'` — the enumeration the card objected to. Commit `1aa5026e30` (PR #18904, "declare `style` on `ListMapConfigSchema` and point `object-map.map` back at it") **deleted that enumeration**. On `main` today the row reads `'Map field config, the author face — the same block ListViewSchema.map declares ...'`, and the generated reference renders the TYPE from `ListMapConfigSchema` itself: `| **map** | { latitudeField?: string; longitudeField?: string; locationField?: string; titleField?: string; … } | optional | ...` Every key optional, no required pair claimed. The statement the card called false no longer exists in the source or in the shipped reference. Reproduce: `git log -S "Map field config, the author face: { latitudeField" -- packages/spec/src/ui/component.zod.ts` returns exactly two commits, `a3d4c59d69` (added, PR #18403) and `1aa5026e30` (removed, PR #18904). ### N2 (navigation list omits `new_window`) — CONFIRMED, and it is four rows, not three. At the live pin, `object-grid`, `object-map`, `object-gantt` and `object-tree` each hand `schema.navigation` straight into the shared `useNavigationOverlay` hook (`ObjectGrid.tsx:2321`, `ObjectMap.tsx:1041`, `ObjectGantt.tsx:1680`, `ObjectTree.tsx:891`). That hook binds its own mode union to this package's `NavigationModeSchema` — its docblock says so in as many words, *"the seven modes this hook switches on are exactly the seven the exported union publishes"*, held by a parity test on that side — and its click router carries a `new_window` branch that delegates to `onNavigate` and otherwise falls through to a `window.open`. `object-gantt` implements that action itself as well. The four `.describe()` strings listed six of the seven (no `new_window`), except `object-grid` — the row the other three were copied from — which listed five (no `popover` either). All four doors are `z.unknown()`, so that sentence is the entire published account of the shape, and the reference page renders the type beside it as `any`. ### N3 (two comment citations in `component.test.ts`) — NOT fixed here, and the card's own correction is stale. Both citations still exist (`component.test.ts:3649` `label (:1849)`, `:3750` `ObjectGantt.tsx:501`). But they are HISTORICAL citations anchored to the retired pin `53ded82b`, and the corrections the card proposes (`:1871`, `:500`) were measured at that same retired pin. At the live pin the true positions are **`:503`** for the `GanttConfigSchema.safeParse` and **`:2181`** for the label resolution — so applying the card's numbers would replace one unverifiable pair with another. `component.test.ts` is also outside this dispatch's declared file surface. Left for the card. ### N4 (historical pin-citation spelling) — unchanged, measured. `check:objectui-pin-citations --list` on this head: **48 historical / 29 asserting / 7 assertion** entries. **30 historical citations still name the retired `53ded82b`**, ten of them in `component.zod.ts`, including the three headers this card is about (`component.zod.ts:3561` map, `:3715` gantt, `:3906` tree). The gate permits the historical form by design, so nothing is violated and nothing reds — those anchors simply are not re-checked on a pin bump, which is exactly what the card recorded. N3's re-measurement above shows the drift is real and not hypothetical. Converting them is a re-measurement job, not a spelling change, and is left for the card. ## The change Four `.describe()` strings, plus the reference page regenerated from them with `pnpm --filter @objectstack/spec check:generated --fix` (never by hand): - `ObjectGridPropsSchema.navigation` - `ObjectMapPropsSchema.navigation` - `ObjectGanttPropsSchema.navigation` - `ObjectTreePropsSchema.navigation` Each now lists `page | drawer | modal | split | popover | new_window | none` and names `NavigationModeSchema` as the source of that list, so the next member added to the enum has a named edge into these rows instead of four independently drifting lists. **Why `object-grid` is in scope.** The card names it as the precedent the other three copied and says "fixing it means fixing the family, not just these three rows". It is the same defect class, in the same file, in the same gate family, adds no verification surface, and understates by two rather than one. Correcting three while leaving the source of the pattern intact would leave the family in the state that produced the defect. No accept set moves: the doors stay `z.unknown()`. ## Changeset — measured, not pattern-matched `patch` on `@objectstack/spec`. The changed bytes ARE published bytes: - `packages/spec/package.json` `files[]` contains `src/**/*.zod.ts`, so `src/ui/component.zod.ts` ships verbatim in the tarball. - The compiled strings ship in `dist/ui/index.js` and `dist/ui/index.mjs`. - `npm pack --dry-run --json` in `packages/spec` lists all three of those paths among its 2030 entries. `@objectstack/spec` is `private: false` at `17.4.0` — a released package. - Controls: a positive control (an untouched describe from the same rows) is found in the same dist files; a negative control (a string present nowhere) is found in none. So `skip-changeset` would have been wrong here, and no changeset would have been wrong too. ## Verification Head `55dd769453`. `origin/main` merged in at `f2101d0492` via `scripts/pm/os-regen-merge.sh` before the final measurements; all 15 spec generated artifacts re-checked clean after it. - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derived **102 families** from the actual changed paths. All 102 run, exit codes captured to a file BEFORE any pipe, reconciled with `--ran`: **102 run, 0 NOT MEASURED, 0 UNRUN**. - The single non-zero is `pnpm --filter @objectstack/spec run check:react-declaration-parity` in its bare spelling, which exits 1 by design with no `MANIFEST`. Run as CI runs it — `MANIFEST="$PWD/sdui.manifest.json" ... --baseline react-declaration-parity.baseline.json --strict` — it exits **0**, with no new declaration divergence vs the accepted baseline. - `pnpm --filter @objectstack/spec test` — **512 files, 14953 passed, 1 todo**. - `pnpm --filter @objectstack/spec typecheck` — exit 0 (`tsc --noEmit`, `check:scripts-typecheck`, `check:test-typecheck`). - `pnpm lint` (`eslint . --no-inline-config`, the whole repo, not narrowed) — exit 0. - `pnpm --filter @objectstack/spec check:generated` — all 15 artifacts up to date. - Every heavy run went through `scripts/pm/os-verify-lock.sh`; verdicts read from its `VERDICT command-exit` line. ## Acceptance notes Observed while measuring, deliberately NOT changed here and not filed: 1. **These four describes account for `mode` only.** `useNavigationOverlay` also reads `size`, `openNewTab` and `preventNavigation`, and the TYPED sibling rows (`object-kanban`, `object-calendar`, the list-view entry) name all four members. On a `z.unknown()` door the describe is the whole account, so naming one member of four understates the block — a separate imprecision from the one this card carries. Carrier: the next PR touching these rows, or a decision to point these four doors at `NavigationConfigSchema` the way their typed siblings already do. 2. **A stale comment in the sibling repo.** `plugin-tree/src/ObjectTree.tsx` at the live pin states "measured on the installed spec: `navigation` is declared on exactly one `ComponentPropsMap` entry, `object-grid`". It is now declared on several. A comment in another repository with no contract behind it. Carrier: objectui. 3. N3 and N4 above remain open on the card, with fresh measurements recorded there. --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6afa59d commit 2b80461

3 files changed

Lines changed: 53 additions & 8 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': patch
3+
---
4+
5+
fix(spec): the four `z.unknown()` navigation doors in `ComponentPropsMap` list all seven `NavigationModeSchema` modes (#18459)
6+
7+
Clause-②: no
8+
9+
`object-grid`, `object-map`, `object-gantt` and `object-tree` declare `navigation`
10+
as `z.unknown()`, so the `.describe()` on each is the WHOLE published account of
11+
what a mode may be — nothing else in the protocol narrows those four doors, and
12+
the generated reference renders their type as `any` beside that sentence. Three
13+
of them listed six of the seven `NavigationModeSchema` values (no `new_window`)
14+
and `object-grid`, the precedent the other three copied, listed five (no
15+
`popover` either). An author reading the shipped reference was told a value the
16+
platform honours does not exist.
17+
18+
**Re-measured at the current `.objectui-sha` pin `87af769e9a3e`, not at the pin
19+
the finding was taken at.** All four blocks hand `schema.navigation` straight
20+
into the shared `useNavigationOverlay` hook; that hook types its own mode union
21+
AS this package's `NavigationModeSchema` (its own docblock: *"the seven modes
22+
this hook switches on are exactly the seven the exported union publishes"*,
23+
held by a parity test on the objectui side); its click router carries a
24+
`new_window` branch that delegates to `onNavigate` and otherwise falls through
25+
to a `window.open`, and `object-gantt` additionally implements that action
26+
itself. `popover` is an overlay mode in the same router and every one of the
27+
four passes it an anchor. So all seven modes reach all four doors.
28+
29+
**Why `object-grid` is in the same change.** It is the row the other three were
30+
copied from and it understates by two rather than one; correcting three while
31+
leaving the source of the pattern intact would leave the family in the state
32+
that produced the defect. All four now name the schema as well as the values,
33+
so the next member added to `NavigationModeSchema` has a named edge into these
34+
rows instead of four independently drifting lists.
35+
36+
**Why `Clause-②: no`.** The doors stay `z.unknown()` — a `navigation` value is
37+
accepted before and after this change, whatever its `mode` reads. Nothing is
38+
added to, removed from or narrowed on any authorable surface: the diff is four
39+
description strings and the reference page regenerated from them, and
40+
`check:authorable-surface`, `check:api-surface` and `check:export-origins` all
41+
pass with no artifact to regenerate. What moves is what an author is TOLD, which
42+
is why this ships as a `patch` rather than as no changeset at all: the sentence
43+
is published bytes — `src/ui/component.zod.ts` ships verbatim under this
44+
package's `files[]` entry `src/**/*.zod.ts`, and the compiled string ships in
45+
`dist/ui/index.js` and `dist/ui/index.mjs`.

‎content/docs/references/ui/component.mdx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ Sort field and direction pair
413413
| **filter** | `{ field: string; operator: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] }[]` | optional | Base query filter — the ViewFilterRule array form `[{ field, operator, value }, ...]`, the one filter orthography every `filter` door in this map shares; lowered to the wire `$filter`. The MongoDB-style record form is refused — see migration `element-data-source-and-object-block-filter-rule-array` |
414414
| **sort** | `{ field: string; order: Enum<'asc' \| 'desc'> }[]` | optional | Task order for the fetched bars — the SortItem array form `[{ field, order }, ...]`, the one sort orthography every declared `sort` door on this platform shares; lowered to the wire `$orderby`. The legacy string clause (`name desc`) is refused — see migration `object-block-sort-item-array` |
415415
| **gantt** | `{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … }` | optional | Gantt-timeline configuration, the author face — the same block `ListViewSchema.gantt` declares, and the one the renderer validates this node against. Taken WHOLE when present: the flat top-level spelling beside it is ignored |
416-
| **navigation** | `any` | optional | Task-click navigation config (`{ mode: page \| drawer \| modal \| split \| popover \| none }`); renderer default `drawer` |
416+
| **navigation** | `any` | optional | Task-click navigation config (`{ mode: page \| drawer \| modal \| split \| popover \| new_window \| none }`) — all seven `NavigationModeSchema` values, since the shared `useNavigationOverlay` hook types its own mode union as that schema; renderer default `drawer` |
417417
| **label** | `string \| Record<string, string>` | optional | Gantt label — the second link of the exported PNG/PDF file-name chain, after `gantt.exportFileName` and before the bound object's own label |
418418
| **skipWeekends** | `boolean` | optional | Measure duration and auto-schedule math in WORKING days, skipping Saturdays and Sundays |
419419
| **holidays** | `string[]` | optional | Additional non-working dates for the working calendar, ISO `yyyy-mm-dd` strings; folded into a Set for the duration math |
@@ -542,7 +542,7 @@ Sort field and direction pair
542542
| **bulkActions** | `any[]` | optional | Bulk action names shown on selection |
543543
| **batchActions** | `any[]` | optional | Alternate spelling the renderer reads FIRST (`batchActions ?? bulkActions`) |
544544
| **bulkActionDefs** | `any[]` | optional | Inline bulk-action definitions (full defs, not names) |
545-
| **navigation** | `any` | optional | Row-click navigation config (`{ mode: page \| drawer \| modal \| split \| none }`) |
545+
| **navigation** | `any` | optional | Row-click navigation config (`{ mode: page \| drawer \| modal \| split \| popover \| new_window \| none }`) — all seven `NavigationModeSchema` values, since the shared `useNavigationOverlay` hook types its own mode union as that schema |
546546
| **editable** | `boolean` | optional | Enable inline cell editing |
547547
| **singleClickEdit** | `boolean` | optional | Enter cell edit on single click (default true when editable) |
548548
| **resizable** | `boolean` | optional | Allow column resize (read before `resizableColumns`) |
@@ -666,7 +666,7 @@ View filter rule
666666
| **sort** | `{ field: string; order: Enum<'asc' \| 'desc'> }[]` | optional | Marker order for the fetched records — the SortItem array form `[{ field, order }, ...]`, the one sort orthography every declared `sort` door on this platform shares; lowered to the wire `$orderby`. The legacy string clause (`name desc`) is refused — see migration `object-block-sort-item-array` |
667667
| **map** | `{ latitudeField?: string; longitudeField?: string; locationField?: string; titleField?: string; … }` | optional | Map field config, the author face — the same block `ListViewSchema.map` declares, and the one the renderer validates this node against. Taken WHOLE when present: the flat top-level spelling beside it is ignored |
668668
| **mapStyle** | `string` | optional | MapLibre style URL or spec, overriding the public demo tiles. Read before `map.style`; NOT the base node `style`, which is an inline CSS record |
669-
| **navigation** | `any` | optional | Marker-click navigation config (`{ mode: page \| drawer \| modal \| split \| popover \| none }`) |
669+
| **navigation** | `any` | optional | Marker-click navigation config (`{ mode: page \| drawer \| modal \| split \| popover \| new_window \| none }`) — all seven `NavigationModeSchema` values, since the shared `useNavigationOverlay` hook types its own mode union as that schema |
670670
| **enableClustering** | `boolean` | optional | Group nearby markers into clusters. Absent, the renderer clusters only above 100 markers |
671671

672672
### Nested Shape: `ObjectMapProps.data[provider='object']`
@@ -874,7 +874,7 @@ Sort field and direction pair
874874
| **staticData** | `any[]` | optional | Inline records — read SECOND by `resolveRecordSourceConfig`, wrapped into a `{ provider: 'value' }` config |
875875
| **filter** | `{ field: string; operator: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] }[]` | optional | Base query filter — the ViewFilterRule array form `[{ field, operator, value }, ...]`, the one filter orthography every `filter` door in this map shares; lowered to the wire `$filter`. The MongoDB-style record form is refused — see migration `element-data-source-and-object-block-filter-rule-array` |
876876
| **tree** | `{ parentField?: string; labelField?: string; fields?: string[]; defaultExpandedDepth?: integer }` | optional | Tree/hierarchy configuration, the author face — the same block `ListViewSchema.tree` declares: `{ parentField?, labelField?, fields?, defaultExpandedDepth? }`. `parentField` auto-detects from the object schema when omitted |
877-
| **navigation** | `any` | optional | Row-click navigation config (`{ mode: page \| drawer \| modal \| split \| popover \| none }`) |
877+
| **navigation** | `any` | optional | Row-click navigation config (`{ mode: page \| drawer \| modal \| split \| popover \| new_window \| none }`) — all seven `NavigationModeSchema` values, since the shared `useNavigationOverlay` hook types its own mode union as that schema |
878878

879879
### Nested Shape: `ObjectTreeProps.data[provider='object']`
880880

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2847,7 +2847,7 @@ export const ObjectGridPropsSchema = lazySchema(() => strictObject({
28472847
bulkActions: z.array(z.unknown()).optional().describe('Bulk action names shown on selection'),
28482848
batchActions: z.array(z.unknown()).optional().describe('Alternate spelling the renderer reads FIRST (`batchActions ?? bulkActions`)'),
28492849
bulkActionDefs: z.array(z.unknown()).optional().describe('Inline bulk-action definitions (full defs, not names)'),
2850-
navigation: z.unknown().optional().describe('Row-click navigation config ({ mode: page | drawer | modal | split | none })'),
2850+
navigation: z.unknown().optional().describe('Row-click navigation config ({ mode: page | drawer | modal | split | popover | new_window | none }) — all seven `NavigationModeSchema` values, since the shared `useNavigationOverlay` hook types its own mode union as that schema'),
28512851
editable: z.boolean().optional().describe('Enable inline cell editing'),
28522852
singleClickEdit: z.boolean().optional().describe('Enter cell edit on single click (default true when editable)'),
28532853
resizable: z.boolean().optional().describe('Allow column resize (read before `resizableColumns`)'),
@@ -3650,7 +3650,7 @@ export const ObjectMapPropsSchema = lazySchema(() => strictObject({
36503650
mapStyle: z.string().optional()
36513651
.describe('MapLibre style URL or spec, overriding the public demo tiles. Read before `map.style`; NOT the base node `style`, which is an inline CSS record'),
36523652
navigation: z.unknown().optional()
3653-
.describe('Marker-click navigation config ({ mode: page | drawer | modal | split | popover | none })'),
3653+
.describe('Marker-click navigation config ({ mode: page | drawer | modal | split | popover | new_window | none }) — all seven `NavigationModeSchema` values, since the shared `useNavigationOverlay` hook types its own mode union as that schema'),
36543654
enableClustering: z.boolean().optional()
36553655
.describe('Group nearby markers into clusters. Absent, the renderer clusters only above 100 markers'),
36563656
}));
@@ -3777,7 +3777,7 @@ export const ObjectGanttPropsSchema = lazySchema(() => strictObject({
37773777
gantt: GanttConfigSchema.optional()
37783778
.describe('Gantt-timeline configuration, the author face — the same block `ListViewSchema.gantt` declares, and the one the renderer validates this node against. Taken WHOLE when present: the flat top-level spelling beside it is ignored'),
37793779
navigation: z.unknown().optional()
3780-
.describe('Task-click navigation config ({ mode: page | drawer | modal | split | popover | none }); renderer default `drawer`'),
3780+
.describe('Task-click navigation config ({ mode: page | drawer | modal | split | popover | new_window | none }) — all seven `NavigationModeSchema` values, since the shared `useNavigationOverlay` hook types its own mode union as that schema; renderer default `drawer`'),
37813781
label: I18nLabelSchema.optional()
37823782
.describe('Gantt label — the second link of the exported PNG/PDF file-name chain, after `gantt.exportFileName` and before the bound object\'s own label'),
37833783
skipWeekends: z.boolean().optional()
@@ -3936,7 +3936,7 @@ export const ObjectTreePropsSchema = lazySchema(() => strictObject({
39363936
tree: TreeConfigSchema.optional()
39373937
.describe('Tree/hierarchy configuration, the author face — the same block `ListViewSchema.tree` declares: { parentField?, labelField?, fields?, defaultExpandedDepth? }. `parentField` auto-detects from the object schema when omitted'),
39383938
navigation: z.unknown().optional()
3939-
.describe('Row-click navigation config ({ mode: page | drawer | modal | split | popover | none })'),
3939+
.describe('Row-click navigation config ({ mode: page | drawer | modal | split | popover | new_window | none }) — all seven `NavigationModeSchema` values, since the shared `useNavigationOverlay` hook types its own mode union as that schema'),
39403940
}));
39413941
/** Author state (ADR-0122: the bare name is the author state). */
39423942
export type ObjectTreeProps = z.input<typeof ObjectTreePropsSchema>;

0 commit comments

Comments
 (0)