Skip to content

Commit a7df552

Browse files
docs(spec): ObjectNavItemSchema.viewName states the view the console opens when it is omitted (#21981)
Fixes #21973 Clause-②: no ## What changed `ObjectNavItemSchema.viewName`'s describe said `Defaults to "all"`. The console does not do that. The describe now states the console's real rule. One sentence changed, and "Ignored when `recordId` is set." stays: > Default list view to open. When omitted, the console opens the object's default list view, else its first declared list view; `all` names the console's fallback tab, which exists only for an object that declares no list view. Ignored when `recordId` is set. - `packages/spec/src/ui/app.zod.ts`: the describe, plus a short docblock above it naming the objectui code the sentence mirrors. - `content/docs/references/ui/app.mdx`: regenerated with `pnpm --filter @objectstack/spec gen:docs` (the five `viewName` rows). Not edited by hand. - `packages/lint/src/lint-view-refs.ts`: only the header comment's quote of the old sentence (about `:46`) moves. This file was declared on #6023. No lint logic, rule or test changes. - `.changeset/21973-nav-view-name-default.md`: `@objectstack/spec` `patch`. - ⛔ No schema, type, optionality, default, export or accept-set change. No runtime change, and no objectui file. ## The runtime rule, read at objectui `.objectui-sha` = `0abd4f9f8769fc4c19ad2f96707684876f74c09f` (read-only) - `packages/layout/src/NavigationRenderer.tsx` `resolveHref`: precedence is `recordId`, then `filters`, then `viewName`. With no `viewName`, the entry links to the bare object route, which has no view segment. - `packages/app-shell/src/views/ObjectView.tsx:2151`: `activeViewId = resolvedViewId || defaultViewId || views[0]?.id`. `defaultViewId` (`:2129`) is the first tab whose `isDefault` is set. - `buildViewTabs` (`:936`): it sets `isDefault: true` on the primary `list` and moves that tab to the front. It builds the `fallbackTab`, `{ id: 'all', label: allRecords }`, only when `viewList.length === 0`, which means no defined view and no primary `list`. Saved overlay rows are merged after that, and they can carry their own `isDefault` (a user's set-default). "The object's default list view" covers both cases. - `resolveViewId` (`@object-ui/core`, `utils/resolve-view-id.ts`) matches three ways: exact id, short name retried with the object prefix added, and qualified name retried with the prefix stripped. On a miss it returns `undefined`. `ObjectView` then logs a `console.warn` and falls back to `defaultViewId || views[0]`. The describe leaves this out to stay short. `view-ref-nav-view-missing` in `packages/lint` already refuses an unresolvable name at build time. ## Census: other live text that restates the old default Searched docs (excluding `content/docs/releases/`), skills, examples, `packages/spec` and the rest of the tree for `Defaults to "all"`, `Defaults to 'all'`, and default-`all`-view phrasings. - `content/docs/references/ui/app.mdx`: lines 116, 465, 655, 842 and 1084. Regenerated in this PR. - `packages/lint/src/lint-view-refs.ts:46`: the quote. Moved in this PR. - `packages/lint/src/lint-view-refs.test.ts:240`: a test comment, "The schema documents viewName as 'Defaults to "all"'". It is in the lint lane and asserts nothing about the text, so it is listed here and not edited. - No test asserts the describe text. Searched `*.test.ts`, `*.snap` and `*.json`. - No hit in `skills/**`, `examples/**`, `content/docs/ui/apps.mdx` or `docs/**`. ## Tests and gates (all at head `b53d7b5a34`) - `pnpm --filter @objectstack/spec build`: VERDICT command-exit 0. - `pnpm --filter @objectstack/spec check:generated`: - Before the regeneration: `✗ 1 of 15 artifact(s) stale: content/docs/references/**`. - After: `✓ All 15 generated artifacts are up to date`. - `pnpm --filter @objectstack/spec typecheck`: exit 0. - `pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2`: `Test Files 619 passed (619)`, `Tests 18485 passed | 1 todo`. - `pnpm --filter @objectstack/lint typecheck` and `vitest run --maxWorkers=2`: `Test Files 119 passed (119)`, `Tests 5624 passed | 5 skipped`. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 104 commands. All 104 ran and exited 0. - `--ran` reconciliation: `✓ dispatch-gates --ran: 104 derived famil(ies) accounted for — 104 run, 0 NOT-MEASURED`. - The first attempt hit four `PREREQUISITE NOT MET` (exit 3): `check:doc-formula-expressions`, `check:doc-security-posture`, `check:skill-examples` and `check:dual-build-cjs-loads`. Each was re-run green after building what it reads. - NOT MEASURED, because CI owns them: - The six path-scheduled CI jobs that `dispatch-gates` lists (Test Core, Temporal Conformance, Dogfood, Dogfood Verify CLI, Build Core, Build Docs). - The workflow-valued families. - The repo-wide `pnpm lint`. ## Changeset, measured - `@objectstack/spec` ships `dist` and `src/**/*.zod.ts` (`files[]`). After the build, the new describe text is in `packages/spec/dist/ui/index.{js,mjs}`. Positive control: the `recordId` describe is in the same files. The old text has zero hits. - `@objectstack/lint` ships only `dist`, `README.md` and `CHANGELOG.md`. The header comment has zero hits in `packages/lint/dist`. Positive control: `view-ref-nav-view-missing` hits `index.js`, `index.cjs` and `runtime.js`. So lint publishes nothing new and gets no changeset. ## Acceptance notes - **Wording vs. the dispatch.** The dispatch said `all` exists "only for an object that declares no `listViews`". The code's condition is wider than `listViews`: no defined view AND no primary `list`. An object that declares only a default `list` gets no `all` tab, and in the spec `listViews` means a container's *additional* named views. So the sentence says "declares no list view". - **The first sentence is kept.** "Default list view to open." is quoted verbatim in four places: `packages/spec/src/ai/solution-blueprint.zod.ts:174`, `packages/platform-objects/src/apps/setup-users-nav-view.test.ts:13`, `packages/lint/CHANGELOG.md:4743` and `packages/spec/CHANGELOG.md:17669`. Rewriting it would make those quotes stale and step outside the declared file surface. - **`views[0]` and the user's tab order.** `views[0]` follows a per-user tab order (localStorage) and saved `sortOrder` when they exist; otherwise it is declared order. The describe states the metadata rule. - **`lint-view-refs.ts:64`–`:66` is untouched.** It says the schema's `all` "resolves only when the object actually declares it". That is outside the declared quote. It stays true for the lint, because the rule skips objects whose list-view namespace is empty, and those are exactly the objects that get the runtime fallback tab. Carrier: none. - **Serial note.** #21976 (#21968) also edits `app.zod.ts`, about 1,200 lines away (near `:1655`). `origin/main` was re-fetched before this PR opened (`dcf3eb494a`). Nothing that landed touches these four files, so no merge was needed. --- _Generated by [Claude Code](https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent db87a02 commit a7df552

4 files changed

Lines changed: 32 additions & 8 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
`ObjectNavItemSchema.viewName`'s describe no longer says the default is "all". It now states what the console does when an object nav entry names no view: it opens the object's default list view, else its first declared list view. `all` is only the console's fallback tab, and it exists only for an object that declares no list view.
6+
7+
Clause-②: no
8+
9+
- The rule is read from objectui at the `.objectui-sha` pin. `ObjectView` opens `defaultViewId || views[0]`, where `defaultViewId` is the view `buildViewTabs` marks `isDefault` (the default `list`). `buildViewTabs` adds the `all` tab only when the object has no list view at all.
10+
- An author who omitted `viewName` expecting all records got that default or first declared view instead. When the object declares more than one list view, name the one the entry should open in `viewName`.
11+
- The generated app reference page follows (#21973). The lint header that quoted the old sentence follows too, as a comment only.
12+
- ⛔ No schema, type, optionality, default, export or accept-set change. The console's behaviour does not change.

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ const result = ActionNavItemSchema.parse(data);
113113
| **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered |
114114
| **type** | `'object'` | ✅ | |
115115
| **objectName** | `string` | ✅ | Target object name |
116-
| **viewName** | `string` | optional | Default list view to open. Defaults to "all". Ignored when `recordId` is set. |
116+
| **viewName** | `string` | optional | Default list view to open. When omitted, the console opens the object's default list view, else its first declared list view; `all` names the console's fallback tab, which exists only for an object that declares no list view. Ignored when `recordId` is set. |
117117
| **recordId** | `string` | optional | Navigate directly to this record id instead of the list view. Supports template vars: `{current_user_id}`, `{current_org_id}`. |
118118
| **recordMode** | `Enum<'view' \| 'edit'>` | optional | Open the record in view (default) or edit mode. Only meaningful when `recordId` is set. |
119119
| **filters** | `Record<string, string>` | optional | URL filter conditions — targets the /:objectName/data bare surface via filter[`<field>`]=`<value>` params instead of a saved view. Values support template vars `{current_user_id}`, `{current_org_id}`. Mutually exclusive with recordId/viewName. |
@@ -462,7 +462,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc`
462462
| **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered |
463463
| **type** | `'object'` | ✅ | |
464464
| **objectName** | `string` | ✅ | Target object name |
465-
| **viewName** | `string` | optional | Default list view to open. Defaults to "all". Ignored when `recordId` is set. |
465+
| **viewName** | `string` | optional | Default list view to open. When omitted, the console opens the object's default list view, else its first declared list view; `all` names the console's fallback tab, which exists only for an object that declares no list view. Ignored when `recordId` is set. |
466466
| **recordId** | `string` | optional | Navigate directly to this record id instead of the list view. Supports template vars: `{current_user_id}`, `{current_org_id}`. |
467467
| **recordMode** | `Enum<'view' \| 'edit'>` | optional | Open the record in view (default) or edit mode. Only meaningful when `recordId` is set. |
468468
| **filters** | `Record<string, string>` | optional | URL filter conditions — targets the /:objectName/data bare surface via filter[`<field>`]=`<value>` params instead of a saved view. Values support template vars `{current_user_id}`, `{current_org_id}`. Mutually exclusive with recordId/viewName. |
@@ -652,7 +652,7 @@ A navigation contribution: a package injecting nav items into an app it does not
652652
| **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered |
653653
| **type** | `'object'` | ✅ | |
654654
| **objectName** | `string` | ✅ | Target object name |
655-
| **viewName** | `string` | optional | Default list view to open. Defaults to "all". Ignored when `recordId` is set. |
655+
| **viewName** | `string` | optional | Default list view to open. When omitted, the console opens the object's default list view, else its first declared list view; `all` names the console's fallback tab, which exists only for an object that declares no list view. Ignored when `recordId` is set. |
656656
| **recordId** | `string` | optional | Navigate directly to this record id instead of the list view. Supports template vars: `{current_user_id}`, `{current_org_id}`. |
657657
| **recordMode** | `Enum<'view' \| 'edit'>` | optional | Open the record in view (default) or edit mode. Only meaningful when `recordId` is set. |
658658
| **filters** | `Record<string, string>` | optional | URL filter conditions — targets the /:objectName/data bare surface via filter[`<field>`]=`<value>` params instead of a saved view. Values support template vars `{current_user_id}`, `{current_org_id}`. Mutually exclusive with recordId/viewName. |
@@ -839,7 +839,7 @@ This schema accepts one of the following structures:
839839
| **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered |
840840
| **type** | `'object'` | ✅ | |
841841
| **objectName** | `string` | ✅ | Target object name |
842-
| **viewName** | `string` | optional | Default list view to open. Defaults to "all". Ignored when `recordId` is set. |
842+
| **viewName** | `string` | optional | Default list view to open. When omitted, the console opens the object's default list view, else its first declared list view; `all` names the console's fallback tab, which exists only for an object that declares no list view. Ignored when `recordId` is set. |
843843
| **recordId** | `string` | optional | Navigate directly to this record id instead of the list view. Supports template vars: `{current_user_id}`, `{current_org_id}`. |
844844
| **recordMode** | `Enum<'view' \| 'edit'>` | optional | Open the record in view (default) or edit mode. Only meaningful when `recordId` is set. |
845845
| **filters** | `Record<string, string>` | optional | URL filter conditions — targets the /:objectName/data bare surface via filter[`<field>`]=`<value>` params instead of a saved view. Values support template vars `{current_user_id}`, `{current_org_id}`. Mutually exclusive with recordId/viewName. |
@@ -1081,7 +1081,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc`
10811081
| **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered |
10821082
| **type** | `'object'` | ✅ | |
10831083
| **objectName** | `string` | ✅ | Target object name |
1084-
| **viewName** | `string` | optional | Default list view to open. Defaults to "all". Ignored when `recordId` is set. |
1084+
| **viewName** | `string` | optional | Default list view to open. When omitted, the console opens the object's default list view, else its first declared list view; `all` names the console's fallback tab, which exists only for an object that declares no list view. Ignored when `recordId` is set. |
10851085
| **recordId** | `string` | optional | Navigate directly to this record id instead of the list view. Supports template vars: `{current_user_id}`, `{current_org_id}`. |
10861086
| **recordMode** | `Enum<'view' \| 'edit'>` | optional | Open the record in view (default) or edit mode. Only meaningful when `recordId` is set. |
10871087
| **filters** | `Record<string, string>` | optional | URL filter conditions — targets the /:objectName/data bare surface via filter[`<field>`]=`<value>` params instead of a saved view. Values support template vars `{current_user_id}`, `{current_org_id}`. Mutually exclusive with recordId/viewName. |

‎packages/lint/src/lint-view-refs.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@
4343
*
4444
* A form action target is one way to name a view; an app's navigation is the
4545
* other, and it is the one an end user travels every day. `ObjectNavItemSchema`
46-
* documents `viewName` as *"Default list view to open. Defaults to 'all'"* — so
47-
* an unresolvable name does not fail, it **falls back**. Measured end to end:
46+
* documents `viewName` as *"Default list view to open. When omitted, the console
47+
* opens the object's default list view, else its first declared list view"* —
48+
* and an unresolvable name does not fail, it **falls back** to that same view.
49+
* Measured end to end:
4850
* mutating a real app's `viewName` to a name nothing declares leaves
4951
* `os validate --json` reporting `valid: true`, and `os build` green.
5052
*

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,17 @@ export const ObjectNavItemSchema = lazySchema(() => strictObject(navItemSurface(
429429
...BaseNavItemSchema.shape,
430430
type: z.literal('object'),
431431
objectName: z.string().describe('Target object name'),
432-
viewName: z.string().optional().describe('Default list view to open. Defaults to "all". Ignored when `recordId` is set.'),
432+
/**
433+
* The rule this sentence states is the console's, read at the objectui commit
434+
* `.objectui-sha` pins: an entry with no `viewName` links to the bare object
435+
* route, where `ObjectView` opens `defaultViewId || views[0]` — the view
436+
* `buildViewTabs` marks `isDefault` (the default `list`), else the first
437+
* declared list view. Its `all` tab is the fallback `buildViewTabs` adds only
438+
* when the object has no list view at all, so `all` is no default (#21973).
439+
*/
440+
viewName: z.string().optional().describe(
441+
'Default list view to open. When omitted, the console opens the object\'s default list view, else its first declared list view; `all` names the console\'s fallback tab, which exists only for an object that declares no list view. Ignored when `recordId` is set.',
442+
),
433443
/**
434444
* When set, navigate straight to the detail page of this specific
435445
* record instead of the object's list view. Supports template

0 commit comments

Comments
 (0)