diff --git a/.changeset/nav-item-label-resolution-order.md b/.changeset/nav-item-label-resolution-order.md new file mode 100644 index 0000000000..89a1de30ac --- /dev/null +++ b/.changeset/nav-item-label-resolution-order.md @@ -0,0 +1,18 @@ +--- +'@objectstack/spec': patch +--- + +docs(spec): a navigation entry's `label` describe states the one order the label resolves in + +Clause-②: no + +The `label` of a navigation entry (`BaseNavItemSchema`) said a present label "renders +verbatim and is never overwritten", which, read literally, forbids the id-keyed localization +`translateApp` already performs at the `/meta` boundary. Its describe and JSDoc now state one +order: the bundle entry `apps..navigation..label` for the active locale chain, keyed +by the entry's `id` and applied by `translateApp` over the app's `navigation` tree (not +`areas`); else a present label as authored — its inline locale map's value for that locale, +else its text; else, when absent, the current label of what the entry opens, at render time, +localized by the target's own translation. A present label is never replaced by its target's +label and never translated by matching its text. No accepted shape changes: `label` stays an +optional `I18nLabel`. diff --git a/content/docs/references/ui/app.mdx b/content/docs/references/ui/app.mdx index 7dfbfe1958..f58707c3a4 100644 --- a/content/docs/references/ui/app.mdx +++ b/content/docs/references/ui/app.mdx @@ -29,7 +29,7 @@ const result = ActionNavItemSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -102,7 +102,7 @@ const result = ActionNavItemSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -125,7 +125,7 @@ const result = ActionNavItemSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -142,7 +142,7 @@ const result = ActionNavItemSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -160,7 +160,7 @@ const result = ActionNavItemSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -178,7 +178,7 @@ const result = ActionNavItemSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -195,7 +195,7 @@ const result = ActionNavItemSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -212,7 +212,7 @@ const result = ActionNavItemSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -232,7 +232,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -258,7 +258,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -349,7 +349,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -372,7 +372,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -396,7 +396,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -419,7 +419,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -451,7 +451,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -474,7 +474,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -491,7 +491,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -509,7 +509,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -527,7 +527,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -544,7 +544,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -561,7 +561,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -581,7 +581,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -607,7 +607,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -641,7 +641,7 @@ A navigation contribution: a package injecting nav items into an app it does not | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -664,7 +664,7 @@ A navigation contribution: a package injecting nav items into an app it does not | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -681,7 +681,7 @@ A navigation contribution: a package injecting nav items into an app it does not | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -699,7 +699,7 @@ A navigation contribution: a package injecting nav items into an app it does not | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -717,7 +717,7 @@ A navigation contribution: a package injecting nav items into an app it does not | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -734,7 +734,7 @@ A navigation contribution: a package injecting nav items into an app it does not | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -751,7 +751,7 @@ A navigation contribution: a package injecting nav items into an app it does not | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -771,7 +771,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -797,7 +797,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -828,7 +828,7 @@ This schema accepts one of the following structures: | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -857,7 +857,7 @@ This schema accepts one of the following structures: | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -880,7 +880,7 @@ This schema accepts one of the following structures: | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -904,7 +904,7 @@ This schema accepts one of the following structures: | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -928,7 +928,7 @@ This schema accepts one of the following structures: | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -951,7 +951,7 @@ This schema accepts one of the following structures: | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -981,7 +981,7 @@ This schema accepts one of the following structures: | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -1007,7 +1007,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -1045,7 +1045,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -1070,7 +1070,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -1097,7 +1097,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -1120,7 +1120,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | @@ -1142,7 +1142,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique identifier for this navigation item (lowercase snake_case) | -| **label** | `string \| Record` | optional | Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time — the view's label when it names a view and that view is labelled, else the object's / dashboard's label; a present label renders verbatim and is never overwritten. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | +| **label** | `string \| Record` | optional | Display proper label. Optional. Resolved in one order: (1) the bundle entry `apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT label of what the entry opens, at render time — the view's label when it names a labelled view, else the object's / dashboard's label — localized by the target's own translation. A present label is never replaced by its target's label and never translated by matching its text. Every real destination must have identity and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is materialised for the absent case. | | **icon** | `string` | optional | Icon name | | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | diff --git a/packages/spec/src/system/i18n-resolver.nav-label-identity.test.ts b/packages/spec/src/system/i18n-resolver.nav-label-identity.test.ts new file mode 100644 index 0000000000..1d99918f5e --- /dev/null +++ b/packages/spec/src/system/i18n-resolver.nav-label-identity.test.ts @@ -0,0 +1,89 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Pin: `translateApp` localizes a navigation entry's label by the entry's + * `id`, never by the label's text — the "never translated by matching its + * text" half of the order `BaseNavItemSchema.label` states in `ui/app.zod.ts`. + * + * Every entry below carries a label EQUAL to its target's machine name, the + * one shape a text-matching rule would treat as "not customised". The bundle + * holds every text-keyed route such a rule could take: the target's own + * translation (`objects..label`, `objects.._views..label`, + * `dashboards..label`) and a navigation key spelled like the text. + * With no key for the entry's `id`, the label must come back unchanged; with + * one, the answer must be that key's value and nothing else. + */ + +import { describe, it, expect } from 'vitest'; +import { translateApp } from './i18n-resolver'; +import { TranslationBundleSchema, type TranslationBundle } from './translation.zod'; +import { AppSchema } from '../ui/app.zod'; + +const app = { + name: 'crm', + label: 'CRM', + navigation: [ + { id: 'nav_account', type: 'object', objectName: 'account', label: 'account' }, + { id: 'nav_pipeline', type: 'object', objectName: 'opportunity', viewName: 'pipeline', label: 'pipeline' }, + { id: 'nav_sales_overview', type: 'dashboard', dashboardName: 'sales_overview', label: 'sales_overview' }, + ], +}; + +/** Every route a text match could take, and no key for any entry's `id`. */ +const textKeyedOnly: TranslationBundle = { + 'zh-CN': { + objects: { + account: { label: '客户' }, + opportunity: { label: '商机', _views: { pipeline: { label: '销售管道' } } }, + }, + dashboards: { sales_overview: { label: '销售总览' } }, + apps: { + crm: { + label: '客户关系', + navigation: { + account: { label: '按文本-客户' }, + pipeline: { label: '按文本-管道' }, + sales_overview: { label: '按文本-总览' }, + }, + }, + }, + }, +}; + +/** The same bundle plus one key per entry `id`. */ +const withIdKeys: TranslationBundle = { + 'zh-CN': { + ...textKeyedOnly['zh-CN'], + apps: { + crm: { + label: '客户关系', + navigation: { + ...textKeyedOnly['zh-CN'].apps!.crm.navigation, + nav_account: { label: '我的客户' }, + nav_pipeline: { label: '我的管道' }, + nav_sales_overview: { label: '我的总览' }, + }, + }, + }, + }, +}; + +const labels = (out: typeof app) => out.navigation.map((n) => n.label); + +describe('translateApp — a nav label equal to its target name localizes only through the entry id', () => { + it('both fixtures are authorable: the app and the bundles parse', () => { + expect(AppSchema.safeParse(app).success).toBe(true); + expect(TranslationBundleSchema.safeParse(textKeyedOnly).success).toBe(true); + expect(TranslationBundleSchema.safeParse(withIdKeys).success).toBe(true); + }); + + it('with no key for the entry id, the label comes back unchanged', () => { + const out = translateApp(app, textKeyedOnly, { locale: 'zh-CN' }); + expect(labels(out)).toEqual(['account', 'pipeline', 'sales_overview']); + }); + + it('with a key for the entry id, the label is that key and nothing else', () => { + const out = translateApp(app, withIdKeys, { locale: 'zh-CN' }); + expect(labels(out)).toEqual(['我的客户', '我的管道', '我的总览']); + }); +}); diff --git a/packages/spec/src/ui/app.zod.ts b/packages/spec/src/ui/app.zod.ts index 1efcd844c7..669509f070 100644 --- a/packages/spec/src/ui/app.zod.ts +++ b/packages/spec/src/ui/app.zod.ts @@ -307,20 +307,36 @@ const BaseNavItemSchema = z.object({ * Display label — OPTIONAL since the cloud#2021 / objectui#9868 letter-A * ruling, and optional in the ACCEPT sense only: an entry still shows text. * - * Absent ⇒ the entry inherits, **at render time**, the CURRENT label of what - * it opens — the view's label when it names a view and that view is labelled, - * else the object's / dashboard's label. Present ⇒ rendered verbatim, never - * overwritten. Nothing is stored for the absent case: there is no `inherited` - * flag and no materialised copy, which is what makes a renamed target show - * its new name on the next render rather than a stale snapshot. + * Resolved in ONE order, keyed by the entry's identity: + * 1. the bundle entry `apps..navigation..label` for the active + * locale chain — applied at the `/meta` boundary by `translateApp` + * (`system/i18n-resolver.ts`), the one place this step runs, over the + * app's `navigation` tree (it does not walk `areas`); + * 2. else a present label, the author's source text: its inline locale + * map's value for that locale, else the text as authored; + * 3. else (absent) the CURRENT label of what the entry opens, **at render + * time** — the view's label when it names a view and that view is + * labelled, else the object's / dashboard's label — localized by the + * target's own translation. + * + * A present label is never replaced by its target's label and never + * translated by matching its text (pinned in + * `system/i18n-resolver.nav-label-identity.test.ts`). Nothing is stored for + * the absent case: there is no `inherited` flag and no materialised copy, + * which is what makes a renamed target show its new name on the next render + * rather than a stale snapshot. * * The rule this relaxes — *every real destination must have identity and * text* — still holds: identity is the target, text is inherited at render. */ label: I18nLabelSchema.optional().describe( - 'Display proper label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render ' - + "time — the view's label when it names a view and that view is labelled, else the object's / dashboard's " - + 'label; a present label renders verbatim and is never overwritten. Every real destination must have identity ' + 'Display proper label. Optional. Resolved in one order: (1) the bundle entry ' + + "`apps..navigation..label` for the active locale chain, keyed by this entry's `id` — `translateApp` " + + "applies it at the `/meta` boundary, over the app's `navigation` tree (not `areas`); (2) else a present label " + + "as authored — its inline locale map's value for that locale, else its text; (3) else (absent) the CURRENT " + + "label of what the entry opens, at render time — the view's label when it names a labelled view, else the " + + "object's / dashboard's label — localized by the target's own translation. A present label is never replaced " + + "by its target's label and never translated by matching its text. Every real destination must have identity " + 'and text: identity is the target, text is inherited at render. No stored inherited flag; nothing is ' + 'materialised for the absent case.', ),