diff --git a/.changeset/16885-navigation-view-declared-not-resolved.md b/.changeset/16885-navigation-view-declared-not-resolved.md new file mode 100644 index 0000000000..775f9b76d2 --- /dev/null +++ b/.changeset/16885-navigation-view-declared-not-resolved.md @@ -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. diff --git a/content/docs/references/ui/view.mdx b/content/docs/references/ui/view.mdx index 9394710c28..f0395788dc 100644 --- a/content/docs/references/ui/view.mdx +++ b/content/docs/references/ui/view.mdx @@ -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. | @@ -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. | @@ -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. | diff --git a/packages/spec/src/ui/view.zod.ts b/packages/spec/src/ui/view.zod.ts index 721c3930ef..f810d5ca8c 100644 --- a/packages/spec/src/ui/view.zod.ts +++ b/packages/spec/src/ui/view.zod.ts @@ -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'),