Skip to content

Commit b89f48b

Browse files
committed
spec(ui): regenerate reference docs + declarations, pin both label directions
- `content/docs/references/**` and `api-surface-declarations/ui.txt` regenerated from the relaxed schema (40 nav-item label rows flip to optional; the App / Area / Dropdown label rows stay required). - `app-nav-label-optional-inherited.pin.test.ts` pins both directions: absent parses on the union and on all eight spread branches and materialises no key, present is returned byte-for-byte, unknown keys are still refused, and the three sibling label sites stay required. - minor changeset for `@objectstack/spec`. Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude <noreply@anthropic.com>
1 parent fa7f087 commit b89f48b

7 files changed

Lines changed: 278 additions & 69 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
spec(ui): a navigation entry may omit `label` — it then inherits its target's CURRENT label at render time (#19049)
6+
7+
Clause-②: yes (widening)
8+
9+
`BaseNavItemSchema.label` is `.optional()`. An `app.navigation` entry written without a `label` now parses, and the semantic it parses into is declared on the key itself: **absent means the entry inherits, at render time, the current label of whatever it opens** — the view's label when it names a view and that view is labelled, else the object's / dashboard's label. A label the author *did* write renders verbatim and is never overwritten.
10+
11+
This executes the maintainer's cloud#2021 ruling (「2021 可以接受有些修改刷新才生效」) as letter **A** on objectui#9868: sync by render-time inheritance, no stored state. The spec moves first because the console reads its navigation contract from here — until now an unnamed entry was not *representable*, so the promise "an unnamed entry shows its target's name" had nowhere to be declared.
12+
13+
- **Accept-set widening only, on eight branches at once.** `BaseNavItemSchema` is spread (`...BaseNavItemSchema.shape`) into the `object`, `dashboard`, `page`, `url`, `report`, `action`, `component` and `group` nav-item declarations, so the one-line relaxation reaches all eight. The ninth branch, `separator`, spreads nothing and has never carried a `label`. Nothing that parsed before stops parsing: a present `label` is accepted exactly as before, and every other key on the item is untouched.
14+
- **Nothing is stored for the absent case.** There is no new member and no `inherited` flag — the parse adds no key the author did not write. That is the whole point of resolving at render: a target renamed after the entry was authored shows its new name on the next render, where a label materialised at authoring time would be a stale snapshot. Consumers must resolve an absent `label` at render, not at ingest.
15+
- **The rule this relaxes still holds.** *Every real destination must have identity and text* — identity is the target, text is inherited at render. That sentence is recorded in the key's `describe`, so it ships to the reference page and to any tool reading the JSON Schema.
16+
- **The three sibling `label` declarations in this file are unchanged and still required**: `NavigationArea.label`, `AppContextSelector.label` and `App.label`. Each names a container the author is creating rather than a target it could inherit from, so there is nothing for an absent label to resolve against. The ruling covers navigation entries only.
17+
18+
Downstream, in order: objectui#9868 relaxes its own `packages/types` validator to match, resolves the absent label in the nav renderer, and stops writing `label || pageName` for an unnamed entry; then cloud#2021 stops materialising an inherited label in `apply_blueprint`.

‎content/docs/references/api/metadata.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ const result = AppDefinitionResponseSchema.parse(data);
9292
| **isDefault** | `boolean` | optional (default: `false`) | Is default app |
9393
| **hidden** | `boolean` | optional | Hide from the App Switcher; the shell surfaces hidden apps via the avatar menu instead (navigation only — never an access gate) |
9494
| **_unpublished** | `boolean` | optional | Machine-managed publish gate (ADR-0045 §3) — true = unpublished, externally unobservable. Written by AI materialization, cleared by publish-drafts. Never authored. |
95-
| **navigation** | `({ id: string; label: string \| Record<string, string>; icon?: string; order?: number; … } \| { type: 'separator'; id?: string; order?: number } \| … +7 more)[]` | optional | Full navigation tree for the app sidebar |
95+
| **navigation** | `({ id: string; label?: string \| Record<string, string>; icon?: string; order?: number; … } \| { type: 'separator'; id?: string; order?: number } \| … +7 more)[]` | optional | Full navigation tree for the app sidebar |
9696
| **areas** | `{ id: string; label: string \| Record<string, string>; icon?: string; description?: string \| Record<string, string>; … }[]` | optional | Navigation areas for partitioning navigation by business domain |
9797
| **contextSelectors** | `{ id: string; label: string \| Record<string, string>; icon?: string; optionsSource: object; … }[]` | optional | App-level scope dropdowns whose value is injected into nav items as `{<id>}` template vars |
9898
| **homePageId** | `never` | optional | [REMOVED] `app.homePageId` was removed in @objectstack/spec 17.0.0 (ADR-0049). objectui's console did read it before v17 (`resolveLandingRoute`), so this key had a consumer — it was retired because the capability is better expressed on the navigation item itself than as an ID cross-reference that silently falls back when it dangles. An app's landing page IS its first navigation item (by `order`), and the root landing follows `isDefault` routing. Delete the key; to change where an app opens, reorder `navigation` so the intended entry is first, and set `isDefault` on the app that should own the root landing. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |

‎content/docs/references/api/package-api.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,7 @@ A navigation contribution: a package injecting nav items into an app it does not
624624
| **app** | `string` | ✅ | Target app name to contribute navigation into (e.g. "setup") |
625625
| **group** | `string` | optional | Target group nav-item id to append into (e.g. "group_integrations"); omit to append at the app top level. Naming a group the target app does not declare is not refused: the items are appended at the app top level anyway and a `nav_contribution_group_missing` diagnostic is emitted — by the runtime at `warn`, and by `os build` and `os validate` at compile time. |
626626
| **priority** | `integer` | optional (default: `200`) | Merge priority within the target group — lower applied first (matches object extender priority) |
627-
| **items** | `({ id: string; label: string \| Record<string, string>; icon?: string; order?: number; … } \| { type: 'separator'; id?: string; order?: number } \| … +7 more)[]` | ✅ | Navigation items contributed into the target app/group |
627+
| **items** | `({ id: string; label?: string \| Record<string, string>; icon?: string; order?: number; … } \| { type: 'separator'; id?: string; order?: number } \| … +7 more)[]` | ✅ | Navigation items contributed into the target app/group |
628628

629629
### Nested Shape: `PackageInstallBody[option 2].engine`
630630

‎content/docs/references/kernel/manifest.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ A navigation contribution: a package injecting nav items into an app it does not
107107
| **app** | `string` | ✅ | Target app name to contribute navigation into (e.g. "setup") |
108108
| **group** | `string` | optional | Target group nav-item id to append into (e.g. "group_integrations"); omit to append at the app top level. Naming a group the target app does not declare is not refused: the items are appended at the app top level anyway and a `nav_contribution_group_missing` diagnostic is emitted — by the runtime at `warn`, and by `os build` and `os validate` at compile time. |
109109
| **priority** | `integer` | optional (default: `200`) | Merge priority within the target group — lower applied first (matches object extender priority) |
110-
| **items** | `({ id: string; label: string \| Record<string, string>; icon?: string; order?: number; … } \| { type: 'separator'; id?: string; order?: number } \| … +7 more)[]` | ✅ | Navigation items contributed into the target app/group |
110+
| **items** | `({ id: string; label?: string \| Record<string, string>; icon?: string; order?: number; … } \| { type: 'separator'; id?: string; order?: number } \| … +7 more)[]` | ✅ | Navigation items contributed into the target app/group |
111111

112112
### Nested Shape: `Manifest.engine`
113113

0 commit comments

Comments
 (0)