@object-ui/layout 17.1.0 → 17.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,368 @@
1
1
  # @object-ui/layout
2
2
 
3
+ ## 17.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 608669e: `AppSchemaRenderer` now derives area visibility from the items inside the
8
+ area, closing the visible-but-empty regression the spec 17.0.0 area-key
9
+ retirement left behind (objectui#3311, option C of the recorded ruling).
10
+
11
+ Spec 17.0.0 retired the authorable area-level `visible` /
12
+ `requiredPermissions` (`AREA_VISIBLE_RETIRED` /
13
+ `AREA_REQUIRED_PERMISSIONS_RETIRED`) — an area is a layout grouping, not an
14
+ access boundary — and objectui followed in #3315 by deleting the area
15
+ switcher's filter. Correct on the contract, but it changed the navigation
16
+ surface: an area whose items are **all** gated away used to disappear from
17
+ the switcher and instead rendered as a selectable, empty area.
18
+
19
+ ## What changed
20
+ - **Area visibility is now derived, not authored.** An area appears in the
21
+ switcher iff at least one of its navigation items survives the exact
22
+ item-level guards `NavigationRenderer` applies: the `visible` expression,
23
+ `requiredPermissions`, the `requiresObject` / `requiresService` runtime
24
+ capability gates, and — for `action` items — the presence of an `onAction`
25
+ dispatcher (framework#4509: without one they are not rendered, so they
26
+ cannot carry an area either). Separators never count; a `group` counts only
27
+ through its children.
28
+ - **The active area is elected among visible areas only.** A fully gated
29
+ first area is no longer auto-activated, and when a gating change hides the
30
+ currently active area the shell re-elects the first visible one. A gating
31
+ change that merely _reveals_ an area never yanks the user away from where
32
+ they are.
33
+ - **An area with no items at all derives the same way**: no visible item →
34
+ hidden. (Boundary recorded in objectui#3311.)
35
+ - New export `hasVisibleNavigationItems(items, options)` from
36
+ `@object-ui/layout` — the predicate behind the derivation, usable by other
37
+ shells that render their own area switchers.
38
+
39
+ No authorable key is involved anywhere: the platform's `.strict()` area
40
+ object still rejects the retired keys, and the derivation — computed from the
41
+ same guards that decide what renders — cannot disagree with the rendered
42
+ navigation, so there is nothing for a metadata author to get wrong.
43
+
44
+ - d22ae31: Track `@objectstack/spec` 17.0.0-rc.2 (objectui#3235, #3208, #3287, #3264).
45
+
46
+ The pin moves from `^17.0.0-rc.1` to `^17.0.0-rc.2` across the workspace, and
47
+ the sibling `@objectstack/*` packages (`client` / `core` / `formula` / `lint`)
48
+ move with it — they pin `@objectstack/spec` **exactly**, so leaving them behind
49
+ kept a second copy of the spec in the tree and would have had `@objectstack/lint`
50
+ validating against rc.1 schemas that still accept keys rc.2 retires.
51
+
52
+ Breaking semantics, in FROM → TO form:
53
+
54
+ - **`app.homePageId` is retired — an app's landing page is now its first
55
+ navigation item.** An app that pinned a landing page with `homePageId` will
56
+ open on the first reachable navigation entry (by `order`) instead; the root
57
+ landing still follows `isDefault`. To restore a specific landing page, reorder
58
+ `navigation` so the intended entry comes first. Stored metadata is migrated by
59
+ `os migrate meta --from 16`. The key is a hard error now, not a stripped one:
60
+ the spec ships a tombstone that names the migration.
61
+ Upstream retired it because of its SHAPE, not its usage — it was an ID
62
+ cross-reference with no referential integrity, so a `homePageId` that pointed
63
+ at nothing silently fell back to the first navigation item anyway
64
+ (objectstack#4667, premise corrected in #4709). If the capability returns, it
65
+ returns as a flag on the navigation item itself, which cannot dangle.
66
+ - **`@object-ui/types`' `HttpMethod` now resolves to the spec's
67
+ `HttpMethodType`.** Shape is verbatim identical — the same 5-value UI subset —
68
+ and `@object-ui/types` still exports it as `HttpMethod`, so no consumer
69
+ changes. The spec renamed its `./ui` export because `HttpMethod` named two
70
+ different types depending on the import path (`./shared` / `./api` carry a
71
+ 7-value enum including `HEAD` / `OPTIONS`); objectui deliberately keeps the
72
+ 5-value one (objectstack#4691).
73
+ - **`AppContextSelector.includeAll` / `placement` are gone.** Neither ever did
74
+ anything in this renderer: context selectors are mandatory-scope, so no "All"
75
+ row was ever rendered, and `placement: 'topbar'` put nothing in the topbar.
76
+ Both carried schema defaults, which is why the liveness lint structurally
77
+ could not flag them — removal was the only channel that reaches an author
78
+ (framework#4509).
79
+ - **`NavigationArea.visible` / `order` / `requiredPermissions` are gone.** An
80
+ area is a layout grouping, not an access boundary. Gating moved down to the
81
+ navigation ITEM, where `visible` and `requiredPermissions` are unchanged and
82
+ still enforced. `AppSchemaRenderer`'s area switcher no longer hides an area, so
83
+ an area whose items are all gated away renders as visible-but-empty rather
84
+ than disappearing.
85
+ - **`@object-ui/core` no longer exports `NotificationProtocol`**
86
+ (`resolveNotificationConfig`, `specNotificationToToast`, `mapSeverityToVariant`,
87
+ `mapPosition`, `ToastNotification`). It bridged `@objectstack/spec/ui`'s
88
+ `Notification` / `NotificationConfig`, which objectstack#4610 deleted with no
89
+ successor. Use `resolveNotificationConfig` from `@object-ui/react`
90
+ (`NotificationContext`), which owns the live `NotificationSystemConfig` and is
91
+ what every notification surface already read. Note that the spec's _other_
92
+ `Notification` — `@objectstack/spec/api` — is the REST inbox row, a different
93
+ contract, and is deliberately NOT aliased in as a replacement.
94
+ - **The `email_template` client-side validator now uses
95
+ `EmailTemplateDefinitionSchema`.** It was pointing at the removed
96
+ `EmailTemplateSchema`, so authored templates were being checked against the
97
+ wrong contract: the live one is keyed `name` + `locale` (not `id`) and splits
98
+ the body into `bodyHtml` / `bodyText` (not `body` + `bodyType`)
99
+ (objectstack#4616 / #4807).
100
+
101
+ Fixes that are not breaking, but were only found because rc.2 stopped being
102
+ lenient — each had been passing vacuously:
103
+
104
+ - **`view` drafts are actually validated now.** The client validator named the
105
+ aggregated container schema while this admin authors first-class `ViewItem`s,
106
+ and the container used to strip `viewKind` / `config` in silence — so no view
107
+ draft ever had one of its own keys checked. It now validates each shape
108
+ against its own schema (objectui#3312).
109
+ - **The console's worked examples were wrong**, and being stripped rather than
110
+ refused: `view.list.object` (the container root already declares it),
111
+ `job.concurrency` / `job.timeoutMs` (no such keys; the spelling is `timeout`,
112
+ already in ms), `email_template.from` / `.to` (a template is not a send —
113
+ the sender override is `fromOverride`, an object), and
114
+ `datasource.capabilities` / `.healthCheck` (objectstack#4583 removed the
115
+ former; the latter was never a datasource key). These are the drafts an
116
+ author — or a model generating metadata — copies.
117
+ - Action key inventory re-derived: `ActionSchema` gained the package-lock
118
+ envelope (`_lock*` / `_package*` / `_provenance`), so a packaged action no
119
+ longer reports them as unknown keys.
120
+ - The schema-diff panel labels the new `default_mismatch` finding.
121
+ - Test fixtures pinning the retired `managedBy: 'system'` bucket now use
122
+ `engine-owned`. Protocol 17 split that value (objectstack#3355), so it
123
+ resolved to the default-writable fallback and a batch of "stays locked"
124
+ assertions had quietly stopped asserting anything.
125
+
126
+ ### Patch Changes
127
+
128
+ - d2363e7: The legacy `page-header` alias stops advertising `description` as an authorable
129
+ key (objectui#3226).
130
+
131
+ FROM: `registerLayout()` declared `inputs: [title, description]`. TO:
132
+ `inputs: [title, subtitle]` — the key `@objectstack/spec/ui`'s `PageHeaderProps`
133
+ declares, and the one the canonical `page:header` renderer in
134
+ `@object-ui/components` already declares.
135
+
136
+ `inputs` is a DECLARATION surface, not documentation: the designer builds its
137
+ property palette from it, and the framework's `check:react-declaration-parity`
138
+ diffs it against the spec schemas. Declaring `description` therefore did not
139
+ merely tolerate a legacy spelling — it published a second dialect for the one
140
+ concept the protocol calls `subtitle`, and told authors (an AI author most
141
+ readily, since the registry is what it reads to learn the shape) that the
142
+ non-spec key was legal. Metadata that took the offer renders a subtitle under
143
+ `page-header` and silently loses it under `page:header`: same JSON, two results,
144
+ which is the outcome a single contract exists to prevent.
145
+
146
+ No runtime behaviour changes. `PageHeader` still reads `subtitle ?? description`,
147
+ deliberately: this alias exists for out-of-repo consumer schemas, so "no in-repo
148
+ author writes `description`" (verified — zero hits) is not evidence that nobody
149
+ does, and dropping the read today would delete an external page's second line
150
+ while its title kept rendering, the least reportable failure mode there is. That
151
+ read is retired together with an ADR-0087 D2 conversion entry
152
+ (`page-header-subtitle-alias`, `description` → `subtitle` rewritten at load
153
+ time), which lives in the framework repo and is tracked separately. Narrowing the
154
+ declaration did not need to wait on it and breaks no consumer; leaving the
155
+ declaration wrong in the meantime keeps minting the metadata the conversion would
156
+ then have to absorb.
157
+
158
+ New tests pin both halves so neither can drift back: the registration may not
159
+ declare `description`, must declare `subtitle`, and — checked against the spec's
160
+ own shape rather than a hand-written allowlist — may declare nothing
161
+ `@objectstack/spec` does not; while the runtime fallback is pinned as a sequencing
162
+ guard, to be deleted in the same change that lands the conversion entry.
163
+
164
+ - Updated dependencies [18cd432]
165
+ - Updated dependencies [532cf8b]
166
+ - Updated dependencies [680080a]
167
+ - Updated dependencies [a7651e6]
168
+ - Updated dependencies [d915c47]
169
+ - Updated dependencies [b71fc92]
170
+ - Updated dependencies [34595eb]
171
+ - Updated dependencies [3889ffb]
172
+ - Updated dependencies [5781fb1]
173
+ - Updated dependencies [9e9e9a9]
174
+ - Updated dependencies [56409c2]
175
+ - Updated dependencies [042e09d]
176
+ - Updated dependencies [9cbcbf4]
177
+ - Updated dependencies [85c4c9c]
178
+ - Updated dependencies [fd54c3e]
179
+ - Updated dependencies [4eeb932]
180
+ - Updated dependencies [23018cc]
181
+ - Updated dependencies [53811d1]
182
+ - Updated dependencies [d915c47]
183
+ - Updated dependencies [f44d872]
184
+ - Updated dependencies [509104a]
185
+ - Updated dependencies [825bbe3]
186
+ - Updated dependencies [5dd0127]
187
+ - Updated dependencies [06632e9]
188
+ - Updated dependencies [a4cff5b]
189
+ - Updated dependencies [175bd79]
190
+ - Updated dependencies [f833d3a]
191
+ - Updated dependencies [2a9513d]
192
+ - Updated dependencies [71be406]
193
+ - Updated dependencies [d22ae31]
194
+ - Updated dependencies [8d8094a]
195
+ - @object-ui/core@17.3.0
196
+ - @object-ui/components@17.3.0
197
+ - @object-ui/types@17.3.0
198
+ - @object-ui/react@17.3.0
199
+
200
+ ## 17.2.0
201
+
202
+ ### Minor Changes
203
+
204
+ - 4a51e77: Stop declaring 14 symbols across ten packages under names `@objectstack/spec`
205
+ owns (objectui#3161, objectstack#4115 batch 7 — the long tail, one or two
206
+ entries per package). All ten packages leave the ledger, which drops from 17
207
+ collisions across 11 packages to 3 across 1.
208
+
209
+ **Renamed exports** — in every case the spec exports the same name for a
210
+ _different_ thing, so the old name was a mis-description rather than a dialect:
211
+
212
+ | package | was | now | what the spec's same-named export is |
213
+ | :------------------------- | :--------------------------------- | :--------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------- |
214
+ | `@object-ui/fields` | `FieldWidgetProps` | `FieldWidgetComponentProps` | the DECLARED field-widget plugin props contract (a zod object; `field.type` is the `FieldType` enum, `readonly`/`required` carry defaults) |
215
+ | `@object-ui/layout` | `PageHeaderProps` | `PageHeaderComponentProps` | the authored `page:header` node — a zod schema of `title`, `subtitle`, an icon NAME, `breadcrumb`, `actions: string[]` |
216
+ | `@object-ui/layout` | `Page` | `PageNodeRenderer` | the authored page metadata DOCUMENT (`name`, `label`, `type`, `regions`) |
217
+ | `@object-ui/plugin-detail` | `ObjectFieldLike` | `ObjectDefFieldLike` | the i18n duck type `translateObject` walks (`help`/`description`, plus `[key: string]: any`) |
218
+ | `@object-ui/plugin-grid` | `ColumnSummaryConfig` | `ColumnSummarySetting` | the OBJECT form of `ListColumn.summary` **only** — the local one was the whole union, shorthand included |
219
+ | `@object-ui/plugin-grid` | `isMultiValueField` | `hasMultiValueShape` | the spec's classifier, which requires a def with a `type`; the local one is called with `undefined` |
220
+ | `@object-ui/collaboration` | `RealtimeConfig` | `RealtimeSubscriptionConfig` | the app's realtime DECLARATION (`enabled`, `transport`, `subscriptions[]`) |
221
+ | `@object-ui/plugin-charts` | `ChartConfig` | `ChartContainerConfig` | the authored chart document (`type`, `xAxis`, `series`, `showLegend`, …) |
222
+ | `@object-ui/plugin-form` | `FormSection` / `FormSectionProps` | `FormSectionContainer` / `FormSectionContainerProps` | the authored form-section metadata (`name`, `pane`, `visibleWhen`, `fields`) |
223
+ | `@object-ui/providers` | `Theme` | `ThemePreference` | a whole theme DOCUMENT (`name`, `label`, `colors`, `typography`) |
224
+ | `@object-ui/runner` | `App` (default export) | `RunnerApp` | the authored application metadata type **and** the `App.create()` builder |
225
+ | `@object-ui/sdui-parser` | `ValidationResult` | `ManifestValidationResult` | plugin-manifest validation (`{ valid, errors?, warnings? }`), exported from both `kernel` and `contracts` |
226
+
227
+ `ManifestValidationResult` follows the `<what was validated>Validation<Error|Result>`
228
+ convention registered on objectstack#4115 (`@object-ui/core` took
229
+ `SchemaNodeValidationResult` in batch 4). `PageHeaderComponentProps` deliberately
230
+ reuses the name `@object-ui/app-shell` already chose for its own header props in
231
+ batch 3, so one concept does not acquire two dialect names one package apart.
232
+
233
+ **Now derived from the spec instead of hand-written:**
234
+
235
+ - `@object-ui/fields` — `isFileIdToken` is re-exported from
236
+ `@objectstack/spec/data`. The local copy was character-for-character identical
237
+ to the spec's function while its comment said it "mirrors" it, so every
238
+ behaviour test passed and only reference identity could tell the two apart.
239
+ The regex is a wire decision: widening it server-side while a copy here kept
240
+ the old bound would make every new id read as "not a reference", and the
241
+ widget would submit the legacy inline blob to a backend expecting a reference.
242
+ - `@object-ui/plugin-detail` — `FeedFilterMode` is re-exported from
243
+ `@objectstack/spec/data`, in a file that already imported the sibling
244
+ `FeedItemType` from the spec.
245
+ - `@object-ui/plugin-grid` — the eleven-member aggregation union is now the
246
+ spec's `ColumnSummary` enum, so the total `Record<ColumnSummaryType, string>`
247
+ label map turns a member the spec adds into a compile error instead of a
248
+ blank footer cell. `ColumnSummarySetting` is `NonNullable<ListColumn['summary']>`,
249
+ i.e. whatever forms the spec itself accepts. `hasMultiValueShape` delegates to
250
+ the spec's `isMultiValueField` rather than re-deriving it from
251
+ `MULTI_OPTION_TYPES` / `MULTI_CAPABLE_TYPES`.
252
+ - `@object-ui/providers` — `ThemePreference` is the spec's `ThemeMode` union
253
+ plus the one legacy `'system'` spelling this provider still honours for stored
254
+ preferences, read off the schema's own `_zod` carrier so the package takes no
255
+ zod dependency.
256
+
257
+ `@objectstack/spec` moves from `devDependencies` to `dependencies` in
258
+ `@object-ui/fields` (it re-exports a runtime function) and `@object-ui/providers`
259
+ (its public `.d.ts` now references the spec).
260
+
261
+ Scored `minor`, not `major`, per this repo's fixed-group rule — objectui's major
262
+ tracks `@objectstack`, so breaking changes of our own ship as minor with the
263
+ semantics spelled out above (see AGENTS.md §版本号策略). A `major` here would carry
264
+ all 39 packages of the fixed group to `18.0.0` and off objectstack's 17.x line.
265
+
266
+ - 07de7be: Navigation `action` items actually run now (framework#4509).
267
+
268
+ A `type: 'action'` nav item rendered, gated like any other item, and did
269
+ **nothing** when clicked. `NavigationRenderer` dispatches such a click to an
270
+ `onAction` prop it expects the host shell to supply — it deliberately never
271
+ reads `item.actionDef` itself — and no shipped sidebar supplied that prop. So
272
+ `actionDef.actionName` reached no dispatcher: an author could put an action in
273
+ the menu, watch it render with its icon and label, and never find out that
274
+ clicking it was a no-op. The framework's liveness ledger recorded this as the
275
+ single gap in the AppSchema navigation surface.
276
+
277
+ **New `useNavActionDispatch`** (`@object-ui/app-shell`) resolves the nav item's
278
+ `actionName` against `action` metadata at click time — the same source
279
+ `DeclaredActionsBar` reads for a record toolbar — and dispatches the resolved
280
+ definition through `useAction()`. `UnifiedSidebar` now passes it. No new
281
+ provider is involved: the sidebar already renders inside `ConsoleShell`'s
282
+ `GlobalActionRuntimeProvider`, so nav actions get the fully-wired console runner
283
+ including the confirm, param-collection, result and navigate dialogs. A declared
284
+ `params` array becomes the runner's param-dialog input, and the nav item's own
285
+ `actionDef.params` is passed as the value bag, so a menu entry can pre-fill the
286
+ action it launches.
287
+
288
+ Nav actions are inherently **global**: `ActionNavItemSchema` is strict with
289
+ exactly `{ actionName, params? }` and carries no `objectName`, so resolution is
290
+ by name alone and no record context rides along.
291
+
292
+ **Behaviour change:** a shell that passes no `onAction` no longer renders
293
+ `action` items at all, instead of rendering them dead. This mirrors the existing
294
+ capability guards — an item the host cannot serve is hidden — and it makes the
295
+ omission diagnosable: a missing prop now shows up as "my action item is gone",
296
+ which leads to the prop, rather than "clicking does nothing", which for three
297
+ releases led nowhere. Every failure at dispatch time (an unnamed item, an
298
+ unresolvable action, a throwing action) warns and toasts instead of returning
299
+ silently.
300
+
301
+ - 6d868e1: Remove `PageNodeRenderer`, the dead page-node renderer (objectui#3223, ADR-0049
302
+ enforce-or-remove).
303
+
304
+ **Removed:** the `PageNodeRenderer` export and its `./Page` module. It was
305
+ registered under no component key and imported by nothing — a whole-repo grep
306
+ found zero call sites — so it reached consumers only through
307
+ `export * from './Page'` in the package barrel. `registerLayout()` was already
308
+ saying so in a note that told the next reader _not_ to register it. Its props
309
+ were also `{ schema: PageNodeSchema; … } & any`, and an intersection with `any`
310
+ absorbs the whole type, so the signature asserted nothing beyond "there is a
311
+ schema".
312
+
313
+ **Migration:** there is nothing to re-point in a working app — an unregistered
314
+ renderer had no call site to migrate. If you imported the symbol directly:
315
+
316
+ ```diff
317
+ -import { PageNodeRenderer } from '@object-ui/layout';
318
+ +import { PageRenderer } from '@object-ui/components';
319
+ ```
320
+
321
+ `PageRenderer` in `@object-ui/components` is, and remains, the renderer for the
322
+ `page` component key. It is the one that supports page types
323
+ (record/home/app/utility), named regions and `PageVariablesProvider` — the
324
+ deleted one rendered a header plus children and nothing else. Schema-driven
325
+ consumers are unaffected: a `{ type: 'page' }` node has always resolved through
326
+ the registry to `PageRenderer`, never to this export.
327
+
328
+ Also note: this supersedes the `Page` → `PageNodeRenderer` rename shipped for
329
+ this package in the batch 7 symbol burn-down — the renamed symbol is gone rather
330
+ than renamed again. `PageHeaderProps` → `PageHeaderComponentProps` from that same
331
+ batch is unaffected.
332
+
333
+ Scored `minor`, not `major`, per this repo's fixed-group rule — objectui's major
334
+ tracks `@objectstack`, so breaking changes of our own ship as minor with the
335
+ semantics spelled out above (see AGENTS.md §版本号策略). A `major` here would carry
336
+ all 39 packages of the fixed group to `18.0.0` and off objectstack's 17.x line.
337
+
338
+ ### Patch Changes
339
+
340
+ - Updated dependencies [4ae0ac4]
341
+ - Updated dependencies [696e3c1]
342
+ - Updated dependencies [bca45cc]
343
+ - Updated dependencies [a889e31]
344
+ - Updated dependencies [09d30a4]
345
+ - Updated dependencies [4bf612c]
346
+ - Updated dependencies [335041c]
347
+ - Updated dependencies [b414983]
348
+ - Updated dependencies [256f8cc]
349
+ - Updated dependencies [d9668a7]
350
+ - Updated dependencies [cb82705]
351
+ - Updated dependencies [f572849]
352
+ - Updated dependencies [f6e8d78]
353
+ - Updated dependencies [ea96284]
354
+ - Updated dependencies [d3584c6]
355
+ - Updated dependencies [a8ad6c0]
356
+ - Updated dependencies [444457c]
357
+ - Updated dependencies [850033c]
358
+ - Updated dependencies [022e4c3]
359
+ - Updated dependencies [009e25d]
360
+ - Updated dependencies [726b89c]
361
+ - @object-ui/types@17.2.0
362
+ - @object-ui/components@17.2.0
363
+ - @object-ui/core@17.2.0
364
+ - @object-ui/react@17.2.0
365
+
3
366
  ## 17.1.0
4
367
 
5
368
  ### Patch Changes
package/README.md CHANGED
@@ -38,19 +38,23 @@ import { AppShell } from '@object-ui/layout';
38
38
  </AppShell>
39
39
  ```
40
40
 
41
- ### Page
41
+ ### PageHeader
42
42
 
43
- Standard page layout with optional header section.
43
+ Page title block with an optional description, used at the top of a page's
44
+ content area.
44
45
 
45
46
  ```typescript
46
- import { Page, PageHeader } from '@object-ui/layout';
47
+ import { PageHeader } from '@object-ui/layout';
47
48
 
48
- <Page>
49
- <PageHeader title="Dashboard" description="View your metrics" />
50
- <div>Page Content</div>
51
- </Page>
49
+ <PageHeader title="Dashboard" description="View your metrics" />
52
50
  ```
53
51
 
52
+ > **Rendering a whole `page` node?** That belongs to `PageRenderer` in
53
+ > `@object-ui/components`, which is what the `page` component key resolves to —
54
+ > it handles page types (record/home/app/utility), named regions and page
55
+ > variables. This package deliberately does not register or export a second
56
+ > renderer for that key (objectui#3223).
57
+
54
58
  ### SidebarNav
55
59
 
56
60
  Navigation sidebar component with React Router integration.
@@ -49,7 +49,9 @@ export interface AppSchemaRendererProps {
49
49
  * Responsibilities:
50
50
  * - Reads `name`, `title`, `description`, `logo`, `favicon` for branding
51
51
  * - Renders sidebar navigation from `navigation` or `areas[].navigation`
52
- * - Area switcher when multiple `areas` are defined
52
+ * - Area switcher when multiple areas are VISIBLE — area visibility is
53
+ * derived from the items inside, not authored (objectui#3311): an area
54
+ * whose items are all gated away is hidden and never auto-activated
53
55
  * - Mobile modes: `drawer` (sheet overlay, default), `bottom_nav` (fixed
54
56
  * bottom bar), `hamburger` (collapsed sidebar)
55
57
  * - Evaluates `visible` expressions and `requiredPermissions` on every item
@@ -41,7 +41,19 @@ export interface NavigationRendererProps {
41
41
  checkPermission?: PermissionChecker;
42
42
  /** Optional runtime-capability checker for `requiresObject` / `requiresService` */
43
43
  checkCapability?: CapabilityChecker;
44
- /** Called when an `action`-type item is clicked */
44
+ /**
45
+ * Called when an `action`-type item is clicked.
46
+ *
47
+ * A shell that renders `action` items MUST supply this: the renderer has no
48
+ * dispatcher of its own and never reads `item.actionDef` — unpacking
49
+ * `actionName` / `params` and invoking the action is the host's job (see
50
+ * `useNavActionDispatch` in `@objectstack/app-shell`). Omitting it does not
51
+ * degrade to an inert button; action items are **not rendered at all**,
52
+ * because a nav entry that looks clickable and silently does nothing is worse
53
+ * than an absent one (framework#4509 — every shipped sidebar omitted this
54
+ * prop, so `actionDef.actionName` reached no dispatcher and every such item
55
+ * dead-clicked).
56
+ */
45
57
  onAction?: (item: NavigationItem) => void;
46
58
  /** Search query to filter navigation items by label */
47
59
  searchQuery?: string;
@@ -137,6 +149,46 @@ export declare function resolveLabel(label: string | {
137
149
  defaultValue?: string;
138
150
  params?: Record<string, any>;
139
151
  }, t?: (key: string, options?: any) => string): string;
152
+ /** Guard callbacks for {@link hasVisibleNavigationItems}. */
153
+ export interface NavigationVisibilityOptions {
154
+ /** Evaluator for item `visible` expressions. Defaults to always-visible. */
155
+ evaluateVisibility?: VisibilityEvaluator;
156
+ /** Checker for item `requiredPermissions`. Defaults to always-permitted. */
157
+ checkPermission?: PermissionChecker;
158
+ /** Checker for `requiresObject` / `requiresService`. Defaults to pass. */
159
+ checkCapability?: CapabilityChecker;
160
+ /**
161
+ * Whether the host wires an `onAction` dispatcher. Without one, `action`
162
+ * items are not rendered at all (framework#4509 — a nav entry that looks
163
+ * clickable and silently does nothing is worse than an absent one), so
164
+ * they cannot carry an area's visibility either. Defaults to `false`,
165
+ * matching a renderer with no `onAction` prop.
166
+ */
167
+ hasActionHandler?: boolean;
168
+ }
169
+ /**
170
+ * Whether a navigation tree contains at least one item that would actually
171
+ * render under the given guards — the exact guards `NavigationItemRenderer`
172
+ * applies per item: the `visible` expression, `requiredPermissions`, the
173
+ * `requiresObject` / `requiresService` runtime-capability gates, and (for
174
+ * `action` items) the presence of an action dispatcher.
175
+ *
176
+ * Non-content nodes never count: a `separator` is a visual divider, and a
177
+ * `group` counts only through its children — a group whose children are all
178
+ * gated away contributes nothing a user can navigate to.
179
+ *
180
+ * This is the predicate behind DERIVED area visibility (objectui#3311).
181
+ * `@objectstack/spec` 17.0.0 retired the authorable area-level `visible` /
182
+ * `requiredPermissions` (`AREA_VISIBLE_RETIRED` /
183
+ * `AREA_REQUIRED_PERMISSIONS_RETIRED`): an area is a layout grouping, not an
184
+ * access boundary. What replaces those keys is not a new key but this
185
+ * derivation: an area is visible iff something inside it is. Because it is
186
+ * computed from the same guards that decide what renders, it can never
187
+ * disagree with the rendered navigation — and there is nothing for a
188
+ * metadata author to get wrong. An area with no items at all derives the
189
+ * same way (no visible item → hidden).
190
+ */
191
+ export declare function hasVisibleNavigationItems(items: NavigationItem[], options?: NavigationVisibilityOptions): boolean;
140
192
  /**
141
193
  * Lightweight template-variable context for nav items that target a
142
194
  * specific record / org. The shell injects the signed-in user id and
@@ -1,5 +1,27 @@
1
1
  import { default as React } from 'react';
2
- export interface PageHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
2
+ /**
3
+ * Props of this package's `<PageHeader>` React component.
4
+ *
5
+ * Named `PageHeaderComponentProps`, not `PageHeaderProps` (objectui#3161,
6
+ * objectstack#4115 ledger batch 7) — the same name `@object-ui/app-shell`
7
+ * settled on for its own header props in objectui#3169, deliberately reused
8
+ * rather than re-invented, so one concept does not end up with two dialect
9
+ * names one package apart.
10
+ *
11
+ * `@objectstack/spec/ui` owns `PageHeaderProps` for the AUTHORED `page:header`
12
+ * node: a zod object of `title: string`, `subtitle`, an icon NAME, `breadcrumb`
13
+ * and `actions: string[]` (action IDs). This interface is the rendered layer of
14
+ * that same element — `icon` accepts a React node as well as a name, `actions`
15
+ * is a list of action definitions the `record:quick_actions` widget renders,
16
+ * `schema` carries the raw node, and it extends `HTMLAttributes` so every DOM
17
+ * prop rides along. Authored layer vs rendered layer, which is exactly the
18
+ * one-name-two-layers failure objectstack#4115 exists to end.
19
+ *
20
+ * Pinned by `__tests__/spec-symbol-batch7.test.tsx`, which fails if the spec
21
+ * stops owning `PageHeaderProps` (then this can take the plain name back) or
22
+ * starts owning `PageHeaderComponentProps`.
23
+ */
24
+ export interface PageHeaderComponentProps extends React.HTMLAttributes<HTMLDivElement> {
3
25
  title: string;
4
26
  /**
5
27
  * Optional secondary line under the title. Spec schemas use `subtitle`,
@@ -44,4 +66,4 @@ export interface PageHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
44
66
  };
45
67
  };
46
68
  }
47
- export declare function PageHeader({ title, subtitle, description, icon, action, actions, showBack, schema, className, children, ...props }: PageHeaderProps): React.JSX.Element;
69
+ export declare function PageHeader({ title, subtitle, description, icon, action, actions, showBack, schema, className, children, ...props }: PageHeaderComponentProps): React.JSX.Element;
package/dist/index.d.ts CHANGED
@@ -4,7 +4,6 @@
4
4
  */
5
5
  export * from './PageHeader';
6
6
  export * from './AppShell';
7
- export * from './Page';
8
7
  export * from './PageCard';
9
8
  export * from './SidebarNav';
10
9
  export * from './ResponsiveGrid';