Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .changeset/16885-navigation-view-declared-not-resolved.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
'@objectstack/spec': patch
---

`ListViewSchema.navigation.view`'s description stops promising view selection the stack never performs

`NavigationConfigSchema.view` described itself as *"Name of the form view to use
for details (e.g. `summary_view`, `edit_form`)"*. Measured against
`@objectstack/spec` source and the `objectui` checkout this repo pins in
`.objectui-sha`, no reader resolves an authored view name:

- The key's **only** read is `useNavigationOverlay`
(`packages/react/src/hooks/useNavigationOverlay.ts`). It binds
`const view = navigation?.view` and then passes that string as the **second
argument of `onNavigate`** — the slot whose other producers are navigation
**mode** tokens (`'new_window'`, and the `'view'` literal the `??` supplies).
- The hook also re-exports it on `NavigationOverlayState.view`, and **no
consumer reads that member** — while its siblings on the same returned object
(`width`, `isOverlay`, `mode`, `selectedRecord`) are read at roughly twenty
sites, which is the lit control that makes the zero a reading.
- Every `formViews` read in that tree is `formViews?.default`. None is keyed by
an authored view name, so no resolution path exists for this key to reach.
- One shipped consumer types that second argument `'view' | 'edit'` and
branches on both with **no fallback arm**, so an authored name there matches
neither branch and the row click does nothing.

The key is therefore worse than ignored: the value travels, and it lands in a
slot that means something else. The description now says that, carries the
repo's existing `[EXPERIMENTAL — not enforced]` marker, and tells authors to
leave the key unset.

⛔ **No accept set moves.** `view` is still `z.string().optional()`; every
document that parsed before parses now, with identical issues and identical
output. Nothing is retired, renamed, constrained, or newly resolved — the
enforce-or-remove decision (ADR-0049) is still open on #16885, and this change
deliberately does not take it.

**This is shipped, which is why it carries a changeset rather than
`skip-changeset`.** `@objectstack/spec`'s published `files[]` ships both `dist`
and `src/**/*.zod.ts`. Measured on the rebuilt artifact: the corrected sentence
is present in 22 built bundles and in the published source file, the old
sentence is absent from all of them, a sibling `describe()` that ships
(`Disable standard navigation entirely`) lit the same probe at 22 as the
positive control, and a test-only `it()` title lit `src` but not `dist` as the
negative control.
6 changes: 3 additions & 3 deletions content/docs/references/ui/view.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -914,7 +914,7 @@ View filter rule
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **mode** | `Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>` | optional (default: `"page"`) | |
| **view** | `string` | optional | Name of the form view to use for details (e.g. "summary_view", "edit_form") |
| **view** | `string` | optional | [EXPERIMENTAL — not enforced] Intended as the name of a form view to open for details (e.g. "summary_view", "edit_form"), but no reader resolves an authored view name: the one read forwards this string into the renderer's navigation-ACTION argument (the slot that otherwise carries the mode token), where a consumer with a closed action vocabulary matches no branch and the row click does nothing. Authoring it selects no view — leave it unset until enforce-or-remove (ADR-0049) is decided for this key. |
| **preventNavigation** | `boolean` | optional (default: `false`) | Disable standard navigation entirely |
| **openNewTab** | `boolean` | optional (default: `false`) | Force open in new tab (applies to page mode) |
| **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. |
Expand Down Expand Up @@ -1159,7 +1159,7 @@ Tab configuration for multi-tab view interface
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **mode** | `Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>` | optional (default: `"page"`) | |
| **view** | `string` | optional | Name of the form view to use for details (e.g. "summary_view", "edit_form") |
| **view** | `string` | optional | [EXPERIMENTAL — not enforced] Intended as the name of a form view to open for details (e.g. "summary_view", "edit_form"), but no reader resolves an authored view name: the one read forwards this string into the renderer's navigation-ACTION argument (the slot that otherwise carries the mode token), where a consumer with a closed action vocabulary matches no branch and the row click does nothing. Authoring it selects no view — leave it unset until enforce-or-remove (ADR-0049) is decided for this key. |
| **preventNavigation** | `boolean` | optional (default: `false`) | Disable standard navigation entirely |
| **openNewTab** | `boolean` | optional (default: `false`) | Force open in new tab (applies to page mode) |
| **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. |
Expand Down Expand Up @@ -1310,7 +1310,7 @@ View filter rule
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **mode** | `Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>` | optional (default: `"page"`) | |
| **view** | `string` | optional | Name of the form view to use for details (e.g. "summary_view", "edit_form") |
| **view** | `string` | optional | [EXPERIMENTAL — not enforced] Intended as the name of a form view to open for details (e.g. "summary_view", "edit_form"), but no reader resolves an authored view name: the one read forwards this string into the renderer's navigation-ACTION argument (the slot that otherwise carries the mode token), where a consumer with a closed action vocabulary matches no branch and the row click does nothing. Authoring it selects no view — leave it unset until enforce-or-remove (ADR-0049) is decided for this key. |
| **preventNavigation** | `boolean` | optional (default: `false`) | Disable standard navigation entirely |
| **openNewTab** | `boolean` | optional (default: `false`) | Force open in new tab (applies to page mode) |
| **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. |
Expand Down
20 changes: 18 additions & 2 deletions packages/spec/src/ui/view.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1595,8 +1595,24 @@ export const NavigationConfigSchema = lazySchema(() => strictObject({
}, {
mode: NavigationModeSchema.default('page'),

/** Target View Config */
view: z.string().optional().describe('Name of the form view to use for details (e.g. "summary_view", "edit_form")'),
/**
* [#16885] Target view config — DECLARED, NOT RESOLVED. The name promises
* view selection; nothing in the stack performs it. Measured at
* `.objectui-sha`: the single read is `useNavigationOverlay`
* (`packages/react/src/hooks/useNavigationOverlay.ts`), which passes the
* string as the SECOND argument of `onNavigate` — the slot whose other
* producers are mode tokens (`'new_window'`, `'view'`) — and re-exports it
* on `NavigationOverlayState.view`, which no consumer reads. Every
* `formViews` read in that tree is `formViews?.default`; none is keyed by
* an authored view name, so there is no path by which this could resolve.
* One shipped consumer types that argument `'view' | 'edit'` with no
* fallback branch, so an authored name there is a dead row click.
*
* Enforce-or-remove (ADR-0049) is undecided — #16885 carries the
* measurement. This description is corrected, not the accept set: the key
* still parses exactly as before.
*/
view: z.string().optional().describe('[EXPERIMENTAL — not enforced] Intended as the name of a form view to open for details (e.g. "summary_view", "edit_form"), but no reader resolves an authored view name: the one read forwards this string into the renderer\'s navigation-ACTION argument (the slot that otherwise carries the mode token), where a consumer with a closed action vocabulary matches no branch and the row click does nothing. Authoring it selects no view — leave it unset until enforce-or-remove (ADR-0049) is decided for this key.'),

/** Interaction Triggers */
preventNavigation: z.boolean().default(false).describe('Disable standard navigation entirely'),
Expand Down
Loading