Skip to content

Commit 529d971

Browse files
feat(spec)!: object-map, object-gantt and object-tree type navigation by reference, and one pin enumerates every z.unknown() member of ComponentPropsMap (#21464, stage 1) (#21502)
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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec)!: `navigation` on an `object-map`, `object-gantt` or `object-tree` page block takes the list view's navigation block instead of any value, and every remaining `z.unknown()` member of `ComponentPropsMap` is enumerated with its recorded reason (#21464)
6+
7+
Clause-②: yes (narrowing)
8+
9+
<!-- adr-0087: registered ui-object-map-gantt-tree-navigation-typed -->
10+
11+
**BREAKING** — an accept-set narrowing on a published authoring surface, shipped as `minor` under the repo's launch-window convention for accept-set narrowings. What reads the row: the component-props gate on `objectstack validate`, `objectstack build` and `objectstack lint`, which reports a refused value as an advisory `component-props-invalid` / `component-props-unknown-key` finding. A stored page still saves and loads, because a page component's `properties` is not parsed on the metadata save or load path.
12+
13+
**`@objectstack/spec`**
14+
15+
- **`navigation` is typed on three rows.** `ComponentPropsMap['object-map']`, `['object-gantt']` and `['object-tree']` declared `navigation` as `z.unknown()`, although each renderer hands it to the console's shared navigation hook, which reads `navigation.mode` and falls back to `page` when it finds none. Any value passed, and an off-shape one was answered with a silent default: `navigation: 42` and a bare mode string such as `'drawer'` both opened the record page, whatever they named. Each row now takes the list view's `NavigationConfigSchema` by reference, the same block `object-grid`, `object-kanban`, `object-calendar` and `object-timeline` already take: `{ mode?, size?, openNewTab?, preventNavigation? }`, with `mode` one of `page`, `drawer`, `modal`, `split`, `popover`, `new_window` or `none`.
16+
- **`ObjectMapProps`, `ObjectGanttProps` and `ObjectTreeProps`** (and their `…Parsed` twins) carry `NavigationConfig` on `navigation` instead of `unknown`.
17+
- **No other member changes.** Every other `z.unknown()` member across `ComponentPropsMap` (107 of them) is now listed, with its recorded reason, by a test that fails on a new one until it carries one. The reasons are composition slots, the action blocks' runner-forwarded members, record rows and field values, members of schemas another file owns, a value shown as-is, a deliberately open bag, and a row no renderer draws. The list also holds 28 members a renderer reads with a fixed shape. 27 of them are typed in later changes, and one, `object-kanban`'s `conditionalFormatting`, waits for a ruling, because the console's own kanban fixtures author two rule dialects the list view's schema refuses.
18+
19+
## FROM → TO
20+
21+
| you wrote on an `object-map` / `object-gantt` / `object-tree` | write instead |
22+
|:--|:--|
23+
| `navigation: 'drawer'` | `navigation: { mode: 'drawer' }` |
24+
| `navigation: { mode: 'tab' }` | a mode the hook knows: `page`, `drawer`, `modal`, `split`, `popover`, `new_window` or `none` |
25+
| `navigation: { mode: 'drawer', target: '_blank' }` | `navigation: { mode: 'new_window' }`, or `openNewTab: true` beside a `page` mode |
26+
27+
The one-line fix: write `navigation` as the block a list view declares, `{ mode, size?, openNewTab?, preventNavigation? }`. No conversion is registered, because an off-shape value has no rewrite that both keeps what the block shows today (the record page) and honours what the author wrote; the D3 entry `ui-object-map-gantt-tree-navigation-typed` carries that judgment.
28+
29+
## Who is affected, measured
30+
31+
- **objectstack.** Measured on this branch after merging `origin/main` `100c394f6f`, over the 5 files per row that name `object-map` / `object-gantt` / `object-tree` in the examples, `packages/apps`, `@objectstack/platform-objects`, the plugins and services, the spec sources, the documentation and the published skills: no block of the three authors `navigation`. The one file that co-mentions a row and a `navigation:` key writes app navigation arrays, not this member. The control: the same census finds `objectName` in 4 of the 5 files per row.
32+
- **objectui.** Measured at the `.objectui-sha` pin, over the 88 / 98 / 59 files that name `object-map` / `object-gantt` / `object-tree` (the control: `objectName` in 55 / 70 / 40 of them): every authored `navigation` is `{ mode }` with one of the seven modes, some with `size: 'lg'` or `openNewTab`, and each of those parses on all three rows. The non-object values are probes that expect a refusal: `navigation: 'anything'` in objectui's mirror tests, which assert that both faces answer alike, and a `navigation: 'drawer'` under a `@ts-expect-error`. Neither authors anything.
33+
- **Deployed metadata** was not measured.

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

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ Sort field and direction pair
584584
| **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` |
585585
| **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` |
586586
| **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 |
587-
| **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` |
587+
| **navigation** | `{ mode?: Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>; preventNavigation?: boolean; openNewTab?: boolean; size?: Enum<'auto' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'>; … }` | optional | Task-click navigation config — the same block `ListViewSchema.navigation` declares (`{ mode, size, openNewTab, preventNavigation }`), `mode` one of the seven `NavigationModeSchema` values. The renderer's own default is `{ mode: 'drawer' }` when the key is absent |
588588
| **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 |
589589
| **skipWeekends** | `boolean` | optional | Measure duration and auto-schedule math in WORKING days, skipping Saturdays and Sundays |
590590
| **holidays** | `string[]` | optional | Additional non-working dates for the working calendar, ISO `yyyy-mm-dd` strings; folded into a Set for the duration math |
@@ -679,6 +679,17 @@ Sort field and direction pair
679679
| **interactions** | `{ move?: boolean; resize?: boolean; progress?: boolean; link?: boolean }` | optional | Per-interaction switches, each defaulting to true: allow bar moves but pin durations (resize: false), or keep the dependency UI read-only (link: false). They only narrow what readOnly and row locks already allow |
680680
| **timeSegments** | `{ dayStart?: string; bands: object[]; showMidnight?: boolean }` | optional | Shift segmentation for the day-mode timeline: splits each shift-day (starting at dayStart) into the configured bands — a two-tier header (date over band), per-band tints and drag/resize snapping to band boundaries. No shift concept is hardcoded; bands are pure config. Off when omitted |
681681

682+
### Nested Shape: `ObjectGanttProps.navigation`
683+
684+
| Property | Type | Required | Description |
685+
| :--- | :--- | :--- | :--- |
686+
| **mode** | `Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>` | optional (default: `"page"`) | |
687+
| **view** | `never` | optional | [REMOVED] `view.list.navigation.view` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it named the form view to open for a record detail, and no layer resolved a view by that name: the value was passed straight into the navigation-MODE argument of the console's `onNavigate`, where anything other than `edit` or `view` matched no branch, so the key selected nothing and could silently deaden the row click. Delete the key; to choose what opens for a record, assign a `record` page to the object and let `isDefault` pick the one that opens — page assignment is the machinery that resolves a detail layout, and a list view's navigation block only decides HOW the detail is surfaced (`mode`, `size`). |
688+
| **preventNavigation** | `boolean` | optional (default: `false`) | Disable standard navigation entirely |
689+
| **openNewTab** | `boolean` | optional (default: `false`) | Force open in new tab (applies to page mode) |
690+
| **size** | `Enum<'auto' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'>` | optional (default: `"auto"`) | Overlay size bucket for drawer/modal detail: 'auto' (default — renderer derives from field count + viewport; AI writes nothing) or a coarse override sm/md/lg/xl/full. Prefer this over the pixel `width`; page mode ignores it. |
691+
| **width** | `string \| number` | optional | [DEPRECATED → size] Pixel/percent width of the drawer/modal (e.g. "600px"). A pixel width cannot be chosen at authoring time without knowing the client viewport — use the `size` bucket. |
692+
682693

683694
---
684695

@@ -940,7 +951,7 @@ View filter rule
940951
| **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` |
941952
| **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 |
942953
| **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 |
943-
| **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 |
954+
| **navigation** | `{ mode?: Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>; preventNavigation?: boolean; openNewTab?: boolean; size?: Enum<'auto' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'>; … }` | optional | Marker-click navigation config — the same block `ListViewSchema.navigation` declares (`{ mode, size, openNewTab, preventNavigation }`), `mode` one of the seven `NavigationModeSchema` values |
944955
| **enableClustering** | `boolean` | optional | Group nearby markers into clusters. Absent, the renderer clusters only above 100 markers |
945956

946957
### Nested Shape: `ObjectMapProps.data[provider='object']`
@@ -1005,6 +1016,17 @@ Sort field and direction pair
10051016
| **center** | `[number, number]` | optional | Initial camera center as [latitude, longitude]. Omit to let the renderer fit the camera to the queried records |
10061017
| **style** | `string` | optional | Map style URL — the MapLibre style document the renderer loads in place of the public demo tiles. The component-level `mapStyle` is read FIRST and wins when both are present; this is NOT the inline CSS `style` record a component node carries |
10071018

1019+
### Nested Shape: `ObjectMapProps.navigation`
1020+
1021+
| Property | Type | Required | Description |
1022+
| :--- | :--- | :--- | :--- |
1023+
| **mode** | `Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>` | optional (default: `"page"`) | |
1024+
| **view** | `never` | optional | [REMOVED] `view.list.navigation.view` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it named the form view to open for a record detail, and no layer resolved a view by that name: the value was passed straight into the navigation-MODE argument of the console's `onNavigate`, where anything other than `edit` or `view` matched no branch, so the key selected nothing and could silently deaden the row click. Delete the key; to choose what opens for a record, assign a `record` page to the object and let `isDefault` pick the one that opens — page assignment is the machinery that resolves a detail layout, and a list view's navigation block only decides HOW the detail is surfaced (`mode`, `size`). |
1025+
| **preventNavigation** | `boolean` | optional (default: `false`) | Disable standard navigation entirely |
1026+
| **openNewTab** | `boolean` | optional (default: `false`) | Force open in new tab (applies to page mode) |
1027+
| **size** | `Enum<'auto' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'>` | optional (default: `"auto"`) | Overlay size bucket for drawer/modal detail: 'auto' (default — renderer derives from field count + viewport; AI writes nothing) or a coarse override sm/md/lg/xl/full. Prefer this over the pixel `width`; page mode ignores it. |
1028+
| **width** | `string \| number` | optional | [DEPRECATED → size] Pixel/percent width of the drawer/modal (e.g. "600px"). A pixel width cannot be chosen at authoring time without knowing the client viewport — use the `size` bucket. |
1029+
10081030

10091031
---
10101032

@@ -1164,7 +1186,7 @@ Sort field and direction pair
11641186
| **staticData** | `any[]` | optional | Inline records — read SECOND by `resolveRecordSourceConfig`, wrapped into a `{ provider: 'value' }` config |
11651187
| **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` |
11661188
| **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 |
1167-
| **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 |
1189+
| **navigation** | `{ mode?: Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>; preventNavigation?: boolean; openNewTab?: boolean; size?: Enum<'auto' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'>; … }` | optional | Row-click navigation config — the same block `ListViewSchema.navigation` declares (`{ mode, size, openNewTab, preventNavigation }`), `mode` one of the seven `NavigationModeSchema` values |
11681190

11691191
### Nested Shape: `ObjectTreeProps.data[provider='object']`
11701192

@@ -1215,6 +1237,17 @@ View filter rule
12151237
| **fields** | `string[]` | optional | Additional fields rendered as flat columns alongside the label |
12161238
| **defaultExpandedDepth** | `integer` | optional | Initial expansion depth (0 = roots only; omit = expand all) |
12171239

1240+
### Nested Shape: `ObjectTreeProps.navigation`
1241+
1242+
| Property | Type | Required | Description |
1243+
| :--- | :--- | :--- | :--- |
1244+
| **mode** | `Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>` | optional (default: `"page"`) | |
1245+
| **view** | `never` | optional | [REMOVED] `view.list.navigation.view` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it named the form view to open for a record detail, and no layer resolved a view by that name: the value was passed straight into the navigation-MODE argument of the console's `onNavigate`, where anything other than `edit` or `view` matched no branch, so the key selected nothing and could silently deaden the row click. Delete the key; to choose what opens for a record, assign a `record` page to the object and let `isDefault` pick the one that opens — page assignment is the machinery that resolves a detail layout, and a list view's navigation block only decides HOW the detail is surfaced (`mode`, `size`). |
1246+
| **preventNavigation** | `boolean` | optional (default: `false`) | Disable standard navigation entirely |
1247+
| **openNewTab** | `boolean` | optional (default: `false`) | Force open in new tab (applies to page mode) |
1248+
| **size** | `Enum<'auto' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'>` | optional (default: `"auto"`) | Overlay size bucket for drawer/modal detail: 'auto' (default — renderer derives from field count + viewport; AI writes nothing) or a coarse override sm/md/lg/xl/full. Prefer this over the pixel `width`; page mode ignores it. |
1249+
| **width** | `string \| number` | optional | [DEPRECATED → size] Pixel/percent width of the drawer/modal (e.g. "600px"). A pixel width cannot be chosen at authoring time without knowing the client viewport — use the `size` bucket. |
1250+
12181251

12191252
---
12201253

0 commit comments

Comments
 (0)