diff --git a/.changeset/24332-nav-item-api-data-source.md b/.changeset/24332-nav-item-api-data-source.md new file mode 100644 index 00000000000..28f55c72630 --- /dev/null +++ b/.changeset/24332-nav-item-api-data-source.md @@ -0,0 +1,13 @@ +--- +'@objectstack/spec': minor +--- + +feat(spec): a navigation entry may declare one API data source, which its `visible` reads as `data` (planned, not yet rendered) + +Clause-②: yes (widening) + +- **What changed.** Every navigation entry that spreads the shared nav base (`object`, `dashboard`, `page`, `url`, `report`, `action`, `component`, `doc`, `group`) accepts an optional `dataSource`. It is the page element's API binding, `ElementApiDataSourceSchema`, by reference: `{ provider: 'api', read: { url, method? }, path? }` reads one same-origin endpoint with the console's signed-in fetch, and the entry's `visible` reads the value `path` selects as `data`, for example `visible: 'data.total == 1'`. +- **The refusals are the binding's own.** A `read.url` that is not a same-origin API path, a `method` other than `GET`, a credential key (`headers`, `token`, …), the object binding `{ object, … }` and an unknown key are all refused at parse, at the entry's `dataSource` path. A `separator` refuses `dataSource` with its own prescription. +- **What does not read it.** Only `visible` reads `data`. `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and a group's binding is not published to its `children`: each entry declares its own. +- **Planned, not yet rendered.** No console reads the key yet, so a `visible` that reads `data` has no value to read until the navigation renderer ships. The describe opens with `[PLANNED — not yet rendered]`, and the liveness row `app` → `navigation.dataSource` is `planned` with an author warning (`os lint` / `os validate` report it on a top-level entry). +- **Who notices.** Nobody, by construction: every app that parsed before parses unchanged. Nothing narrows. diff --git a/content/docs/references/ui/app.mdx b/content/docs/references/ui/app.mdx index 86f643d2932..017bc144f37 100644 --- a/content/docs/references/ui/app.mdx +++ b/content/docs/references/ui/app.mdx @@ -34,13 +34,22 @@ const result = ActionNavItemSchema.parse(data); | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | | **type** | `'action'` | ✅ | | | **actionDef** | `{ actionName: string; params?: Record }` | ✅ | Action definition to execute when clicked | +### Nested Shape: `ActionNavItem.dataSource` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **provider** | `'api'` | ✅ | Binds the element to one same-origin API endpoint instead of an object | +| **read** | `{ url: string; method?: 'GET' }` | ✅ | The read request — the same-origin endpoint the element binds | +| **path** | `string` | optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response | + ### Nested Shape: `ActionNavItem.actionDef` | Property | Type | Required | Description | @@ -107,7 +116,8 @@ const result = ActionNavItemSchema.parse(data); | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -130,7 +140,8 @@ const result = ActionNavItemSchema.parse(data); | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -147,7 +158,8 @@ const result = ActionNavItemSchema.parse(data); | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -165,7 +177,8 @@ const result = ActionNavItemSchema.parse(data); | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -183,7 +196,8 @@ const result = ActionNavItemSchema.parse(data); | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -200,7 +214,8 @@ const result = ActionNavItemSchema.parse(data); | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -217,7 +232,8 @@ const result = ActionNavItemSchema.parse(data); | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -237,7 +253,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -263,7 +280,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -354,7 +372,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -362,6 +381,14 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **componentRef** | `string` | ✅ | Component registry key (e.g. "metadata:directory") | | **params** | `Record` | optional | Props passed to the component | +### Nested Shape: `ComponentNavItem.dataSource` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **provider** | `'api'` | ✅ | Binds the element to one same-origin API endpoint instead of an object | +| **read** | `{ url: string; method?: 'GET' }` | ✅ | The read request — the same-origin endpoint the element binds | +| **path** | `string` | optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response | + --- @@ -377,13 +404,22 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | | **type** | `'dashboard'` | ✅ | | | **dashboardName** | `string` | ✅ | Target dashboard name | +### Nested Shape: `DashboardNavItem.dataSource` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **provider** | `'api'` | ✅ | Binds the element to one same-origin API endpoint instead of an object | +| **read** | `{ url: string; method?: 'GET' }` | ✅ | The read request — the same-origin endpoint the element binds | +| **path** | `string` | optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response | + --- @@ -401,7 +437,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -409,6 +446,14 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **book** | `string` | optional | Book to open (a declared `book` name, or the package id for the package's implicit book). Alone: opens the book at its first readable page with the book sidebar; membership is derived by rule, so docs added later appear under this entry with no navigation edit. With `doc`: that page in this book's context. At least one of `book` / `doc` is required. | | **doc** | `string` | optional | Doc to open (the doc name = its source filename stem, lowercase snake_case). Alone: its book context is the doc's own book, else the package's implicit book. At least one of `book` / `doc` is required. | +### Nested Shape: `DocNavItem.dataSource` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **provider** | `'api'` | ✅ | Binds the element to one same-origin API endpoint instead of an object | +| **read** | `{ url: string; method?: 'GET' }` | ✅ | The read request — the same-origin endpoint the element binds | +| **path** | `string` | optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response | + --- @@ -424,13 +469,22 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | | **type** | `'group'` | ✅ | | | **expanded** | `boolean` | optional (default: `false`) | Default expansion state in sidebar | +### Nested Shape: `GroupNavItem.dataSource` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **provider** | `'api'` | ✅ | Binds the element to one same-origin API endpoint instead of an object | +| **read** | `{ url: string; method?: 'GET' }` | ✅ | The read request — the same-origin endpoint the element binds | +| **path** | `string` | optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response | + --- @@ -456,7 +510,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -479,7 +534,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -496,7 +552,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -514,7 +571,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -532,7 +590,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -549,7 +608,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -566,7 +626,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -586,7 +647,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -612,7 +674,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -646,7 +709,8 @@ A navigation contribution: a package injecting nav items into an app it does not | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -669,7 +733,8 @@ A navigation contribution: a package injecting nav items into an app it does not | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -686,7 +751,8 @@ A navigation contribution: a package injecting nav items into an app it does not | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -704,7 +770,8 @@ A navigation contribution: a package injecting nav items into an app it does not | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -722,7 +789,8 @@ A navigation contribution: a package injecting nav items into an app it does not | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -739,7 +807,8 @@ A navigation contribution: a package injecting nav items into an app it does not | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -756,7 +825,8 @@ A navigation contribution: a package injecting nav items into an app it does not | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -776,7 +846,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -802,7 +873,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -833,7 +905,8 @@ This schema accepts one of the following structures: | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -846,6 +919,14 @@ This schema accepts one of the following structures: | **runAction** | `string` | optional | Auto-run this declared action once on arrival at the object's list surface (deep-link "navigate = run action"). Must name an action defined in the stack; validated by defineStack and lint. Not combinable with recordId. | | **children** | `[NavigationItem](#navigationitem)[]` | optional | Child navigation items (e.g. specific views) | +### Nested Shape: `NavigationItem[type='object'].dataSource` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **provider** | `'api'` | ✅ | Binds the element to one same-origin API endpoint instead of an object | +| **read** | `{ url: string; method?: 'GET' }` | ✅ | The read request — the same-origin endpoint the element binds | +| **path** | `string` | optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response | + --- #### Option 2 @@ -862,13 +943,22 @@ This schema accepts one of the following structures: | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | | **type** | `'dashboard'` | ✅ | | | **dashboardName** | `string` | ✅ | Target dashboard name | +### Nested Shape: `NavigationItem[type='dashboard'].dataSource` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **provider** | `'api'` | ✅ | Binds the element to one same-origin API endpoint instead of an object | +| **read** | `{ url: string; method?: 'GET' }` | ✅ | The read request — the same-origin endpoint the element binds | +| **path** | `string` | optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response | + --- #### Option 3 @@ -885,7 +975,8 @@ This schema accepts one of the following structures: | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -893,6 +984,14 @@ This schema accepts one of the following structures: | **pageName** | `string` | ✅ | Target custom page component name | | **params** | `Record` | optional | Parameters passed to the page context | +### Nested Shape: `NavigationItem[type='page'].dataSource` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **provider** | `'api'` | ✅ | Binds the element to one same-origin API endpoint instead of an object | +| **read** | `{ url: string; method?: 'GET' }` | ✅ | The read request — the same-origin endpoint the element binds | +| **path** | `string` | optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response | + --- #### Option 4 @@ -909,7 +1008,8 @@ This schema accepts one of the following structures: | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -917,6 +1017,14 @@ This schema accepts one of the following structures: | **url** | `string` | ✅ | Target external URL | | **target** | `Enum<'_self' \| '_blank'>` | optional (default: `"_self"`) | Link target window | +### Nested Shape: `NavigationItem[type='url'].dataSource` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **provider** | `'api'` | ✅ | Binds the element to one same-origin API endpoint instead of an object | +| **read** | `{ url: string; method?: 'GET' }` | ✅ | The read request — the same-origin endpoint the element binds | +| **path** | `string` | optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response | + --- #### Option 5 @@ -933,13 +1041,22 @@ This schema accepts one of the following structures: | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | | **type** | `'report'` | ✅ | | | **reportName** | `string` | ✅ | Target report name | +### Nested Shape: `NavigationItem[type='report'].dataSource` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **provider** | `'api'` | ✅ | Binds the element to one same-origin API endpoint instead of an object | +| **read** | `{ url: string; method?: 'GET' }` | ✅ | The read request — the same-origin endpoint the element binds | +| **path** | `string` | optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response | + --- #### Option 6 @@ -956,13 +1073,22 @@ This schema accepts one of the following structures: | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | | **type** | `'action'` | ✅ | | | **actionDef** | `{ actionName: string; params?: Record }` | ✅ | Action definition to execute when clicked | +### Nested Shape: `NavigationItem[type='action'].dataSource` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **provider** | `'api'` | ✅ | Binds the element to one same-origin API endpoint instead of an object | +| **read** | `{ url: string; method?: 'GET' }` | ✅ | The read request — the same-origin endpoint the element binds | +| **path** | `string` | optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response | + ### Nested Shape: `NavigationItem[type='action'].actionDef` | Property | Type | Required | Description | @@ -986,7 +1112,8 @@ This schema accepts one of the following structures: | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -994,6 +1121,14 @@ This schema accepts one of the following structures: | **componentRef** | `string` | ✅ | Component registry key (e.g. "metadata:directory") | | **params** | `Record` | optional | Props passed to the component | +### Nested Shape: `NavigationItem[type='component'].dataSource` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **provider** | `'api'` | ✅ | Binds the element to one same-origin API endpoint instead of an object | +| **read** | `{ url: string; method?: 'GET' }` | ✅ | The read request — the same-origin endpoint the element binds | +| **path** | `string` | optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response | + --- #### Option 8 @@ -1012,7 +1147,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -1020,6 +1156,14 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **book** | `string` | optional | Book to open (a declared `book` name, or the package id for the package's implicit book). Alone: opens the book at its first readable page with the book sidebar; membership is derived by rule, so docs added later appear under this entry with no navigation edit. With `doc`: that page in this book's context. At least one of `book` / `doc` is required. | | **doc** | `string` | optional | Doc to open (the doc name = its source filename stem, lowercase snake_case). Alone: its book context is the doc's own book, else the package's implicit book. At least one of `book` / `doc` is required. | +### Nested Shape: `NavigationItem[type='doc'].dataSource` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **provider** | `'api'` | ✅ | Binds the element to one same-origin API endpoint instead of an object | +| **read** | `{ url: string; method?: 'GET' }` | ✅ | The read request — the same-origin endpoint the element binds | +| **path** | `string` | optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response | + --- #### Option 9 @@ -1050,7 +1194,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -1058,6 +1203,14 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **expanded** | `boolean` | optional (default: `false`) | Default expansion state in sidebar | | **children** | `[NavigationItem](#navigationitem)[]` | ✅ | Child navigation items | +### Nested Shape: `NavigationItem[type='group'].dataSource` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **provider** | `'api'` | ✅ | Binds the element to one same-origin API endpoint instead of an object | +| **read** | `{ url: string; method?: 'GET' }` | ✅ | The read request — the same-origin endpoint the element binds | +| **path** | `string` | optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response | + --- @@ -1075,7 +1228,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -1087,6 +1241,14 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **filters** | `Record` | optional | URL filter conditions — targets the /:objectName/data bare surface via filter[``]=`` params instead of a saved view. Values support template vars `{current_user_id}`, `{current_org_id}`. Mutually exclusive with recordId/viewName. | | **runAction** | `string` | optional | Auto-run this declared action once on arrival at the object's list surface (deep-link "navigate = run action"). Must name an action defined in the stack; validated by defineStack and lint. Not combinable with recordId. | +### Nested Shape: `ObjectNavItem.dataSource` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **provider** | `'api'` | ✅ | Binds the element to one same-origin API endpoint instead of an object | +| **read** | `{ url: string; method?: 'GET' }` | ✅ | The read request — the same-origin endpoint the element binds | +| **path** | `string` | optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response | + --- @@ -1102,7 +1264,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -1110,6 +1273,14 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **pageName** | `string` | ✅ | Target custom page component name | | **params** | `Record` | optional | Parameters passed to the page context | +### Nested Shape: `PageNavItem.dataSource` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **provider** | `'api'` | ✅ | Binds the element to one same-origin API endpoint instead of an object | +| **read** | `{ url: string; method?: 'GET' }` | ✅ | The read request — the same-origin endpoint the element binds | +| **path** | `string` | optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response | + --- @@ -1125,13 +1296,22 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | | **type** | `'report'` | ✅ | | | **reportName** | `string` | ✅ | Target report name | +### Nested Shape: `ReportNavItem.dataSource` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **provider** | `'api'` | ✅ | Binds the element to one same-origin API endpoint instead of an object | +| **read** | `{ url: string; method?: 'GET' }` | ✅ | The read request — the same-origin endpoint the element binds | +| **path** | `string` | optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response | + --- @@ -1147,7 +1327,8 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions` | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | | **requiresService** | `string` | optional | Hide/disable this entry unless the named kernel service is registered | @@ -1155,6 +1336,14 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **url** | `string` | ✅ | Target external URL | | **target** | `Enum<'_self' \| '_blank'>` | optional (default: `"_self"`) | Link target window | +### Nested Shape: `UrlNavItem.dataSource` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **provider** | `'api'` | ✅ | Binds the element to one same-origin API endpoint instead of an object | +| **read** | `{ url: string; method?: 'GET' }` | ✅ | The read request — the same-origin endpoint the element binds | +| **path** | `string` | optional | Dotted path selecting the sub-tree of the response the element binds, e.g. `data.environments` in a `{ success, data }` envelope; omitted, the element binds the whole response | + --- diff --git a/packages/spec/authorable-surface/ui.json b/packages/spec/authorable-surface/ui.json index c2bf6b874dd..c31cb17ce6f 100644 --- a/packages/spec/authorable-surface/ui.json +++ b/packages/spec/authorable-surface/ui.json @@ -128,6 +128,7 @@ "ui/ActionNavItem:actionDef", "ui/ActionNavItem:badge", "ui/ActionNavItem:badgeVariant", + "ui/ActionNavItem:dataSource", "ui/ActionNavItem:icon", "ui/ActionNavItem:id", "ui/ActionNavItem:label", @@ -296,6 +297,7 @@ "ui/ComponentNavItem:badge", "ui/ComponentNavItem:badgeVariant", "ui/ComponentNavItem:componentRef", + "ui/ComponentNavItem:dataSource", "ui/ComponentNavItem:icon", "ui/ComponentNavItem:id", "ui/ComponentNavItem:label", @@ -338,6 +340,7 @@ "ui/DashboardNavItem:badge", "ui/DashboardNavItem:badgeVariant", "ui/DashboardNavItem:dashboardName", + "ui/DashboardNavItem:dataSource", "ui/DashboardNavItem:icon", "ui/DashboardNavItem:id", "ui/DashboardNavItem:label", @@ -420,6 +423,7 @@ "ui/DocNavItem:badge", "ui/DocNavItem:badgeVariant", "ui/DocNavItem:book", + "ui/DocNavItem:dataSource", "ui/DocNavItem:doc", "ui/DocNavItem:icon", "ui/DocNavItem:id", @@ -644,6 +648,7 @@ "ui/GlobalFilterOptionsFrom:valueField", "ui/GroupNavItem:badge", "ui/GroupNavItem:badgeVariant", + "ui/GroupNavItem:dataSource", "ui/GroupNavItem:expanded", "ui/GroupNavItem:icon", "ui/GroupNavItem:id", @@ -1012,6 +1017,7 @@ "ui/ObjectMetricProps:variant", "ui/ObjectNavItem:badge", "ui/ObjectNavItem:badgeVariant", + "ui/ObjectNavItem:dataSource", "ui/ObjectNavItem:filters", "ui/ObjectNavItem:icon", "ui/ObjectNavItem:id", @@ -1114,6 +1120,7 @@ "ui/PageHeaderProps:title", "ui/PageNavItem:badge", "ui/PageNavItem:badgeVariant", + "ui/PageNavItem:dataSource", "ui/PageNavItem:icon", "ui/PageNavItem:id", "ui/PageNavItem:label", @@ -1289,6 +1296,7 @@ "ui/ReportChart:yAxis", "ui/ReportNavItem:badge", "ui/ReportNavItem:badgeVariant", + "ui/ReportNavItem:dataSource", "ui/ReportNavItem:icon", "ui/ReportNavItem:id", "ui/ReportNavItem:label", @@ -1326,6 +1334,7 @@ "ui/TreeConfig:parentField", "ui/UrlNavItem:badge", "ui/UrlNavItem:badgeVariant", + "ui/UrlNavItem:dataSource", "ui/UrlNavItem:icon", "ui/UrlNavItem:id", "ui/UrlNavItem:label", diff --git a/packages/spec/liveness/app.json b/packages/spec/liveness/app.json index dd29ca97036..b2e5d970dec 100644 --- a/packages/spec/liveness/app.json +++ b/packages/spec/liveness/app.json @@ -128,6 +128,43 @@ "evidence": "objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:891 (item gate); objectui @f8a9d0fb: packages/app-shell/src/layout/UnifiedSidebar.tsx#UnifiedSidebar (`evalVis`, the CEL evaluation through the ExpressionProvider's `evaluateVisibility`, handed to `NavigationRenderer` and to `hasVisibleNavigationItems`); objectui @f8a9d0fb: packages/app-shell/src/views/nav-menu-renderer.tsx#NavMenuRenderer (the same `evalVis`, and each row returns nothing when `evalVis(item.visible)` is false)", "note": "the CEL visibility gate — enforced per item. This is the layer the retired `areas[].visible` (#4651) prescribes moving to: same CEL dialect, one level down, and actually evaluated. CLIENT-side only, at both levels, and deliberately so: #4722 made the server enforce `requiredPermissions` / `requiresService` inside `areas[]` but left CEL where it was, because evaluating it server-side needs a bound `user` context the REST read layer does not have. Anything that must never reach the browser belongs in `requiredPermissions`, not `visible` — the asymmetry is pinned in packages/rest/src/rest.test.ts. 2026-09-27 (#20146): REPOINTED off objectui's `AppSidebar.tsx` — deprecated, never mounted (the console renders `UnifiedSidebar`), and removed from objectui main by objectui PR #10617 — to the mounted reader(s) above, each re-read at the `.objectui-sha` pin f8a9d0fb and unchanged at objectui main fb91ac9b0. The `NavigationRenderer` item-gate leg is unchanged by this re-point." }, + "dataSource": { + "status": "planned", + "verifiedAt": "2026-10-19", + "evidenceScope": "cross-repo", + "authorWarn": true, + "authorHint": "Keep it, but nothing reads it yet: no console renders a navigation entry's `dataSource`, so a `visible` that reads `data` has no value to read until the navigation renderer ships.", + "note": "PLANNED RECORD (#24332, 2026-10-19, shape 1 by the domain:spec seat's claim 6257260075; the spec half of the objectstack-ai/cloud#3002 letter A sidebar row): one API data source on a navigation entry, the page element's `ElementApiDataSourceSchema` BY REFERENCE (`{ provider: 'api', read: { url, method? }, path? }`, `read.url` a same-origin API path refused at parse otherwise, no credential key), declared on the shared nav base so the nine branches that spread it carry it (a separator refuses it). Its one declared reader is the same entry's `visible`, which reads the selected value as `data`; `label` and the target keys take no `data` read, and the binding is not published to `children`. PLANNED, not live: measured at the `.objectui-sha` pin 0707b4ae37 and unchanged at objectui main 749ed76, the shell evaluates every entry's `visible` through one shared evaluator built from the ambient scope (objectui: packages/app-shell/src/hooks/useNavMemberGates.ts#useNavMemberGates, which hands `evaluateNavigationVisibility(expr, evaluator)` to the item guard; objectui: packages/layout/src/NavigationRenderer.tsx#passesNavItemGuards, which calls `evaluateVisibility(item.visible)` with no per-entry scope), and objectui's own nav mirror (objectui: packages/types/src/zod/app.zod.ts) strips the key. No objectui source reads it. Carrier: the objectui renderer half, filed by the seat when this lands (read points `useNavMemberGates`, and `resolveNavItemLabel` / `resolveHref` in `NavigationRenderer.tsx` if the label and target forks are ruled). `authorWarn` is set because an entry authored with this key and a `data.*` gate today has no value to read; the lint fans `navigation.dataSource` over the top-level entries only (it reads a row and its direct children, no deeper), so an entry nested under a group is not warned. Flip to `live` on the commit that cites the console reader at the `.objectui-sha` pin, and drop `authorWarn` / `authorHint` in that same edit.", + "children": { + "provider": { + "status": "planned", + "verifiedAt": "2026-10-19", + "note": "Planned with its container `navigation.dataSource` (see that row): the page element binding's own key, reused by reference, and read by no console on a navigation entry yet. `provider: 'api'` is the binding's discriminator; it is the only provider a navigation entry admits." + }, + "read": { + "status": "planned", + "verifiedAt": "2026-10-19", + "note": "Planned with its container `navigation.dataSource` (see that row): the page element binding's own key, reused by reference, and read by no console on a navigation entry yet. The read request: the one same-origin endpoint the entry binds.", + "children": { + "url": { + "status": "planned", + "verifiedAt": "2026-10-19", + "note": "Planned with its container `navigation.dataSource` (see that row): the page element binding's own key, reused by reference, and read by no console on a navigation entry yet. Its same-origin refusal is live at parse, on this door as on the page element's (pinned in packages/spec/src/ui/app-nav-item-api-data-source.test.ts); what is planned is the READ." + }, + "method": { + "status": "planned", + "verifiedAt": "2026-10-19", + "note": "Planned with its container `navigation.dataSource` (see that row): the page element binding's own key, reused by reference, and read by no console on a navigation entry yet. `GET` alone, refused otherwise at parse." + } + } + }, + "path": { + "status": "planned", + "verifiedAt": "2026-10-19", + "note": "Planned with its container `navigation.dataSource` (see that row): the page element binding's own key, reused by reference, and read by no console on a navigation entry yet. The dotted path selecting the value the entry's `visible` reads as `data`." + } + } + }, "requiredPermissions": { "status": "live", "verifiedAt": "2026-09-27", diff --git a/packages/spec/liveness/state-counts/app.md b/packages/spec/liveness/state-counts/app.md index 0d76583edf7..d7d2151ac5e 100644 --- a/packages/spec/liveness/state-counts/app.md +++ b/packages/spec/liveness/state-counts/app.md @@ -12,4 +12,4 @@ committed anywhere: `check:liveness` sums the shards when it reads them. | Type | live | exp | elsewhere | dead | planned | classified | |---|---|---|---|---|---|---| -| `app` | 51 | 0 | 0 | 8 | 0 | 59 | +| `app` | 51 | 0 | 0 | 8 | 4 | 63 | diff --git a/packages/spec/src/ui/app-nav-item-api-data-source.test.ts b/packages/spec/src/ui/app-nav-item-api-data-source.test.ts new file mode 100644 index 00000000000..38a0037ae57 --- /dev/null +++ b/packages/spec/src/ui/app-nav-item-api-data-source.test.ts @@ -0,0 +1,156 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * [#24332] A navigation entry may declare ONE API data source — the page + * element's `ElementApiDataSourceSchema`, BY REFERENCE — and its `visible` + * reads the bound value as `data` (shape 1 of the card, the spec half of the + * objectstack-ai/cloud#3002 letter A sidebar row). + * + * What is pinned here is the SHAPE half: + * + * 1. the binding parses on every branch that spreads the shared nav base, and + * on a `group`'s child, beside a `visible` that reads `data.*`; + * 2. it is the page element's binding and not a restatement of it: the nav + * door refuses an unknown key, an off-origin `read.url`, a credential key + * and the object binding at the nav item's own path, with the very issue + * the page element's binding raises for the same value; + * 3. a separator refuses the key with its own prescription; + * 4. the key is PLANNED: its describe carries the marker and its liveness row + * is `planned`, never `live`, until the navigation renderer reads it. + * + * The refusal sentences are the binding's own and are deliberately not pinned + * as text: each refusal is compared with the page element's answer for the + * same input, which is the claim (one binding, one set of refusals). + */ +import fs from 'node:fs'; + +import { describe, it, expect } from 'vitest'; + +import { AppSchema, NavigationItemSchema, ObjectNavItemSchema } from './app.zod'; +import { ElementApiDataSourceSchema } from './page.zod'; + +const API_BINDING = { provider: 'api', read: { url: '/api/v1/cloud/home' }, path: 'data.environments' } as const; + +/** One entry per branch that spreads the shared nav base — every `type` but `separator`. */ +const BRANCHES: ReadonlyArray<[string, Record]> = [ + ['object', { type: 'object', objectName: 'sys_environment' }], + ['dashboard', { type: 'dashboard', dashboardName: 'ops' }], + ['page', { type: 'page', pageName: 'environment_home' }], + ['url', { type: 'url', url: '/apps/cloud' }], + ['report', { type: 'report', reportName: 'usage' }], + ['action', { type: 'action', actionDef: { actionName: 'create_environment' } }], + ['component', { type: 'component', componentRef: 'metadata:directory' }], + ['doc', { type: 'doc', book: 'cloud_manual' }], + ['group', { type: 'group', children: [] }], +]; + +const withNav = (navigation: unknown[]) => ({ name: 'cloud_console', label: 'Cloud', navigation }); + +/** The page element binding's own issues for `value`, re-rooted at `prefix`. */ +const bindingIssues = (value: unknown, prefix: PropertyKey[]) => { + const r = ElementApiDataSourceSchema.safeParse(value); + expect(r.success, 'control: the page element binding must refuse this value too').toBe(false); + return r.error!.issues.map((i) => ({ code: i.code, path: [...prefix, ...i.path], message: i.message })); +}; + +const navIssues = (item: Record) => { + const r = NavigationItemSchema.safeParse(item); + expect(r.success).toBe(false); + return r.error!.issues.map((i) => ({ code: i.code, path: i.path, message: i.message })); +}; + +describe('navigation item `dataSource` — accepted on every branch that spreads the base', () => { + it.each(BRANCHES)('a `%s` entry parses with the API binding and a `visible` reading `data`', (_name, target) => { + const item = { id: 'nav_environments', label: 'Environments', visible: 'data.total == 1', dataSource: API_BINDING, ...target }; + const r = NavigationItemSchema.safeParse(item); + expect(r.success, JSON.stringify(r.error?.issues)).toBe(true); + expect((r.data as { dataSource?: unknown }).dataSource).toEqual(API_BINDING); + }); + + it('parses through the app door, on a top-level entry and on a group\'s child', () => { + const r = AppSchema.safeParse(withNav([ + { id: 'nav_environments', type: 'object', objectName: 'sys_environment', visible: 'data.total != 1', dataSource: API_BINDING }, + { + id: 'grp_tenancy', type: 'group', label: 'Tenancy', + children: [{ id: 'nav_environment_home', type: 'page', pageName: 'environment_home', visible: 'data.total == 1', dataSource: API_BINDING }], + }, + ])); + expect(r.success, JSON.stringify(r.error?.issues)).toBe(true); + }); + + it('accepts the bare binding: `path` and `read.method` stay optional, and `GET` is the one method', () => { + for (const dataSource of [ + { provider: 'api', read: { url: '/api/v1/cloud/home' } }, + { provider: 'api', read: { url: '/api/v1/cloud/home', method: 'GET' } }, + ]) { + const r = ObjectNavItemSchema.safeParse({ id: 'nav_environments', type: 'object', objectName: 'sys_environment', dataSource }); + expect(r.success, JSON.stringify(r.error?.issues)).toBe(true); + } + }); +}); + +describe('navigation item `dataSource` — the page element binding\'s refusals, at the nav item\'s path', () => { + const entry = (dataSource: unknown) => ({ id: 'nav_environments', type: 'object', objectName: 'sys_environment', dataSource }); + + it('refuses an unknown key on the binding, not drops it', () => { + const value = { ...API_BINDING, refresh: 30 }; + const issues = navIssues(entry(value)); + expect(issues).toEqual(bindingIssues(value, ['dataSource'])); + expect(issues[0]).toMatchObject({ code: 'unrecognized_keys', path: ['dataSource'] }); + }); + + it('refuses an off-origin `read.url` at `dataSource.read.url`', () => { + for (const url of ['https://api.example.com/v1/home', '//evil.example/home', 'api/v1/home']) { + const value = { provider: 'api', read: { url } }; + const issues = navIssues(entry(value)); + expect(issues, url).toEqual(bindingIssues(value, ['dataSource'])); + expect(issues[0], url).toMatchObject({ code: 'invalid_format', path: ['dataSource', 'read', 'url'] }); + } + }); + + it('refuses a credential key inside the read request', () => { + const value = { provider: 'api', read: { url: '/api/v1/cloud/home', headers: { authorization: 'Bearer x' } } }; + const issues = navIssues(entry(value)); + expect(issues).toEqual(bindingIssues(value, ['dataSource'])); + expect(issues[0]).toMatchObject({ code: 'unrecognized_keys', path: ['dataSource', 'read'] }); + }); + + it('refuses a method other than `GET`', () => { + const value = { provider: 'api', read: { url: '/api/v1/cloud/home', method: 'POST' } }; + const issues = navIssues(entry(value)); + expect(issues).toEqual(bindingIssues(value, ['dataSource'])); + expect(issues[0]).toMatchObject({ code: 'invalid_value', path: ['dataSource', 'read', 'method'] }); + }); + + it('does not admit the page element\'s OBJECT binding — a nav entry binds an API answer only', () => { + const value = { object: 'sys_environment', limit: 1 }; + const issues = navIssues(entry(value)); + expect(issues).toEqual(bindingIssues(value, ['dataSource'])); + expect(issues.every((i) => i.path[0] === 'dataSource'), JSON.stringify(issues)).toBe(true); + }); +}); + +describe('a separator refuses `dataSource` with its own prescription', () => { + it('answers with the separator guidance, not a bare unknown key', () => { + const r = NavigationItemSchema.safeParse({ type: 'separator', id: 'sep_1', dataSource: API_BINDING }); + expect(r.success).toBe(false); + const issue = r.error!.issues[0]; + expect(issue).toMatchObject({ code: 'unrecognized_keys', path: [] }); + expect(issue.message).toContain('`dataSource` is not a separator key'); + }); +}); + +describe('navigation item `dataSource` is PLANNED — declared, not yet rendered', () => { + it('its describe carries the planned marker', () => { + const shape = (ObjectNavItemSchema as unknown as { shape: Record }).shape; + expect(shape.dataSource.description).toMatch(/^\[PLANNED — not yet rendered\] /); + }); + + it('its liveness row is `planned` with an author warning, never `live`', () => { + const ledger = JSON.parse(fs.readFileSync(new URL('../../liveness/app.json', import.meta.url), 'utf8')); + const row = ledger.props.navigation.children.dataSource; + expect(row, 'liveness/app.json navigation.children.dataSource must exist').toBeDefined(); + expect(row.status).toBe('planned'); + expect(row.authorWarn).toBe(true); + }); +}); diff --git a/packages/spec/src/ui/app.zod.ts b/packages/spec/src/ui/app.zod.ts index 809076a0e70..791b294363f 100644 --- a/packages/spec/src/ui/app.zod.ts +++ b/packages/spec/src/ui/app.zod.ts @@ -4,6 +4,7 @@ import { z } from 'zod'; import { SnakeCaseIdentifierSchema } from '../shared/identifiers.zod'; import { EvaluatedExpressionInputSchema } from '../shared/expression.zod'; import { I18nLabelSchema } from './i18n.zod'; +import { ElementApiDataSourceSchema } from './page.zod'; import { retiredKey } from '../shared/retired-key'; import { strictObject, type StrictObjectOptions } from '../shared/strict-object'; import { lazySchema } from '../shared/lazy-schema'; @@ -156,7 +157,7 @@ const SEPARATOR_NAV_ITEM_ALIASES: Readonly> = { * of answering with one). */ const SEPARATOR_NAV_ITEM_GUIDANCE: Readonly> = Object.fromEntries( - ['label', 'title', 'icon', 'badge', 'badgeVariant', 'visible', 'requiredPermissions', 'requiresObject', 'requiresService'] + ['label', 'title', 'icon', 'badge', 'badgeVariant', 'visible', 'dataSource', 'requiredPermissions', 'requiresObject', 'requiresService'] .map((key) => [ key, `\`${key}\` is not a separator key — a separator is a divider and declares only ` @@ -357,8 +358,67 @@ const BaseNavItemSchema = z.object({ * Visibility condition. * Formula expression returning boolean. * e.g. "user.is_admin || user.department == 'sales'" + * + * An entry that declares `dataSource` (the next key) reads that + * binding's selected value here as `data` (`data.total == 1`) — the page + * element's own reading of its API binding in its `visibleWhen`, one dialect + * (CEL, the bare string normalised to `{ dialect: 'cel', source }`) and one + * root name. PLANNED, not yet rendered: see that key. An entry with no + * `dataSource` has no `data` to read. */ - visible: EvaluatedExpressionInputSchema.optional().describe('Visibility predicate (CEL). e.g. P`\'org_admin\' in current_user.positions`'), + visible: EvaluatedExpressionInputSchema.optional().describe('Visibility predicate (CEL). e.g. P`\'org_admin\' in current_user.positions`. An entry that declares `dataSource` reads that binding\'s selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read.'), + + /** + * One API data source for this entry — the page element's API binding, + * {@link ElementApiDataSourceSchema}, BY REFERENCE (#24332 shape 1, the spec + * half of the objectstack-ai/cloud#3002 letter A sidebar row): + * `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint + * with the console's signed-in fetch and binds the value `path` selects, which + * this entry's `visible` reads as `data`. + * + * ## One binding, not a second one + * + * Referenced, never restated: the accept set and every refusal are the page + * element's — `read.url` a same-origin API path (an absolute URL of any + * scheme, `//host`, a backslash read as a slash and a relative path are + * refused at parse), `method` `GET` alone, no credential key (`headers`, + * `token`, … are refused with the prescription to delete them), no `write` + * block, and an unknown key refused rather than dropped. The refusal text is + * therefore the binding's own, which names a page element; that wording is + * `page.zod.ts`'s to change, for both doors at once. + * + * The object binding (`ElementDataSourceSchema`, `page.zod.ts`) is NOT admitted: the + * pull is one control-plane reading (how many environments the active + * organisation has), which is an API answer, and no ruled consumer binds a + * navigation entry to an object query. + * + * ## What reads `data`, and what does not + * + * Only this entry's `visible` (see that key). `label` and the target keys + * (`recordId`, `filters`, `params`) take no `data` read: a data-bearing label + * meets the bundle rung `translateApp` applies by entry id, and a data-bearing + * target meets the `{current_user_id}` template vocabulary `recordId` already + * has, and both are open questions on #24332 rather than dialects declared + * here. The binding is not published to `children`: a `group` that declares + * one reads it in its own `visible`, and each child declares its own. + * + * Declared on the shared base, so all nine branches that spread it carry the + * key (a separator spreads nothing and refuses it with its own prescription): + * `visible` is a base key, and the console's item guard asks it of every + * branch, a `group` included. + * + * ## Planned, not yet rendered + * + * No console reads this key: objectui's navigation gates evaluate `visible` + * against the shell's ambient scope, with no `data` in it. The reader is the + * objectui renderer half, filed when this lands; the liveness row is + * `liveness/app.json` → `navigation.children.dataSource` (`planned`, with an + * author warning). When that half ships, the `[PLANNED — not yet rendered]` + * marker leaves this describe and the row flips to `live`. + */ + dataSource: ElementApiDataSourceSchema.optional().describe( + '[PLANNED — not yet rendered] One API data source for this navigation entry, the page element\'s API binding by reference: `{ provider: \'api\', read: { url }, path? }` reads one same-origin endpoint with the console\'s signed-in fetch and binds the value `path` selects, which this entry\'s `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element\'s binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read.', + ), /** Permissions required to see/access this navigation item */ requiredPermissions: z.array(z.string()).optional().describe('Permissions required to access this item'),