@object-ui/layout 17.2.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,202 @@
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
+
3
200
  ## 17.2.0
4
201
 
5
202
  ### Minor Changes
@@ -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
@@ -149,6 +149,46 @@ export declare function resolveLabel(label: string | {
149
149
  defaultValue?: string;
150
150
  params?: Record<string, any>;
151
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;
152
192
  /**
153
193
  * Lightweight template-variable context for nav items that target a
154
194
  * specific record / org. The shell injects the signed-in user id and