@object-ui/layout 17.2.0 → 17.5.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,626 @@
1
1
  # @object-ui/layout
2
2
 
3
+ ## 17.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - bb68488: Stop declaring 14 symbols under names `@objectstack/spec` owns at `17.0.0-rc.6`
8
+ (objectui#4167, objectstack#4115).
9
+
10
+ The rc.6 bump published nine names this repo already declared locally, on top of
11
+ four that predate it — `check:spec-symbols` reported all thirteen at once, and a
12
+ fourteenth (`GlobalFilterSchema`) appeared during the bump itself. Each was
13
+ triaged on its own rather than blanket-renamed, because the right answer differs
14
+ per symbol: five bind to the spec, three are renamed because the spec's
15
+ same-named export means something else, five arrive by derivation, and one is a
16
+ declared dialect with a written reason.
17
+
18
+ **Breaking for importers of `@object-ui/react`, `@object-ui/app-shell` and
19
+ `@object-ui/types`** — three exported names changed, because the spec exports the
20
+ same name for a _different_ thing:
21
+
22
+ | package | was | now | what the spec's same-named export actually is |
23
+ | :-------------------- | :----------------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
24
+ | `react` / `app-shell` | `MetadataState` | `MetadataCacheState` | a metadata item's LIFECYCLE state — `'draft' \| 'active' \| 'deprecated' \| 'archived'` (`MetadataStateSchema`, `@objectstack/spec/system`) |
25
+ | `react` / `app-shell` | `resolveI18nLabel` | `resolveKeyedI18nLabel` | a resolver for the INLINE per-locale map (`{ en: 'Owner', 'zh-CN': '负责人' }`) against a BCP-47 locale |
26
+ | `types` | `DateRangePreset` | `FilterBuilderDateRangePreset` | the thirteen HISTORICAL dashboard filter-bar presets; this one is the filter-builder set, which adds eight FUTURE windows the dashboard schema rejects |
27
+
28
+ `resolveI18nLabel` is the one where the collision had already started costing
29
+ something. rc.6 widened `I18nLabel` from `string` to
30
+ `string | Record< string, string >`, so the same authored value now reaches
31
+ either resolver — and each answers wrongly, silently, for the other's input: the
32
+ keyed one returns `undefined` for `{ en: 'Owner' }` (no `key`, no
33
+ `defaultValue`), and the spec's reads `key` / `defaultValue` / `params` as locale
34
+ tags. The rc.6 bump PR met this and aliased the spec's import as
35
+ `resolveInlineI18nLabel` in five files, with hand-written comments at two of
36
+ them. That is a review convention, which is what objectstack#4115 exists to
37
+ replace with a rule — so `Keyed` is now the counterpart of that `Inline`, and the
38
+ name says which vocabulary it resolves at every call site.
39
+
40
+ **Eleven keep their names and are now imported or derived from the spec** instead
41
+ of re-declared: `DATE_RANGE_PRESETS`, `NavigationMode`, `AddressValue`,
42
+ `BreakpointColumnMap`, `BreakpointOrderMap`, `KanbanConfig`, `CalendarConfig`,
43
+ `GanttConfig`, plus the three renamed above at their new names.
44
+
45
+ **Four of the copies were losing information, not just duplicating it.**
46
+
47
+ - **`GanttConfig` declared six keys and called itself canonical; rc.6's
48
+ `GanttConfigSchema` declares seventeen.** The eleven it never mentioned —
49
+ `parentField`, `typeField`, `baselineStartField`, `baselineEndField`,
50
+ `groupByField`, `resourceView`, `assigneeField`, `effortField`, `capacity`,
51
+ `quickFilters`, `autoZoomToFilter` — are all read by
52
+ `plugin-gantt/src/ObjectGantt.tsx`, through a local `GanttConfigEx`
53
+ intersection that existed only because this type did not carry them. It now
54
+ derives from the spec, with `timeSegments` (shift segmentation) as the one
55
+ genuinely local extension; the schema is `$loose` upstream, so that key is
56
+ legal metadata rather than a second dialect.
57
+ - **`GanttConfig.tooltipFields` carried the comment "not part of the upstream
58
+ GanttConfigSchema".** It is, as of rc.6, so the key now arrives from the spec.
59
+ - **`AddressValue` declared five of the spec's seven parts** — `countryCode` and
60
+ `formatted` were missing, under a comment already claiming to be "the part
61
+ names of `AddressSchema`". The widget still renders five inputs; binding the
62
+ type stops it from asserting the platform cannot store the other two, and makes
63
+ the `{ ...address }` write-through say so.
64
+ - **`DATE_RANGE_PRESETS` was `Object.keys(PRESET_RANGES)`,** a third copy of a
65
+ vocabulary the spec extracted in objectstack#4614 precisely to collapse — its
66
+ own doc comment names this module as one of the three. It is now the spec's
67
+ array by reference, and the local date-macro bounds table is pinned complete
68
+ against it with `satisfies`, so a preset the schema gains without bounds here
69
+ is a compile error rather than a filter that validates clean and then selects
70
+ nothing.
71
+
72
+ `NavigationMode` was one hop from the spec already (`NavigationConfig['mode']`);
73
+ it is bound directly, with a both-directions type pin that it stays the same type
74
+ as the config's own `mode`. `KanbanConfig` / `CalendarConfig` /
75
+ `BreakpointColumnMap` / `BreakpointOrderMap` were exact hand copies of `$strict`
76
+ schemas and are now re-exports — "still exact" is the argument for binding them,
77
+ since a copy with nothing to protect can only drift.
78
+
79
+ `GlobalFilterSchema` is the one ALLOW entry. It is the same spread-composition
80
+ dialect as `SelectOptionSchema` next to it, and it collided only because rc.6's
81
+ new refinement forced `.extend()` to be respelled as a `.shape` spread — which
82
+ moved a derivation the guard could see into an object literal it deliberately
83
+ does not descend into. The dialect is unchanged and its three divergences are
84
+ pinned; which side moves on the refinement itself is objectui#4165.
85
+
86
+ `@objectstack/spec` moves from `devDependencies` to `dependencies` in
87
+ `@object-ui/layout`: its public type surface now references the spec.
88
+
89
+ ### Patch Changes
90
+
91
+ - f650253: `BaseSchema.ariaLabel` declares the keyed i18n vocabulary the renderer actually
92
+ resolves, `.disabled` accepts the predicate string it actually evaluates, and the
93
+ keyed shape finally has a name (objectui#4581)
94
+
95
+ Three slots on one base type had drifted from what the renderer does with them.
96
+ PR #4593 fixed `visible` and measured the rest; these are the rest.
97
+
98
+ `ariaLabel` was `string`, but `SchemaRenderer.tsx:111` resolves it with
99
+ `resolveKeyedI18nLabel`, whose input is the KEYED form
100
+ `{ key, defaultValue?, params? }` — a reference into a translation bundle. It is
101
+ now `string | KeyedI18nLabel`, and `KeyedI18nLabel` is a new exported type in
102
+ `@object-ui/types` rather than a fourth inline copy of one object literal: the
103
+ three that existed (`@object-ui/react`'s resolver, `@object-ui/layout`'s
104
+ `resolveLabel`, `@object-ui/app-shell`'s `t`-taking twin) were verified identical
105
+ in their object half first, and two of them now import the name.
106
+
107
+ The vocabulary matters more than the widening. `#4581` originally asked for
108
+ `string | I18nLabel`, and that spelling was withdrawn as measured-wrong: the
109
+ spec's `I18nLabel` is the INLINE LOCALE MAP (`string | Record<string, string>`),
110
+ a different vocabulary resolved against a BCP-47 locale by a different function
111
+ of a confusingly similar name. Under it the shipped keyed fixture type-checked
112
+ only vacuously — as a locale map whose "locales" are named `key` and
113
+ `defaultValue` — the same label carrying `params` was rejected outright, and a
114
+ genuine `{ en: 'Owner' }` compiled while rendering an EMPTY `aria-label`. Naming
115
+ the keyed shape is the declaration half of the fix objectui#4167 started on the
116
+ naming side; `@object-ui/app-shell`'s copy keeps its inline spelling for now
117
+ because an open PR has a pending change to that file, and the comment there says
118
+ so.
119
+
120
+ `disabled` was `boolean` on a key the renderer never reads as one:
121
+ `SchemaRenderer.tsx:466` evaluates it through the same `evaluateCondition` as
122
+ `visible`, and a `disabledOn?: string` sibling exists for the same reason. It is
123
+ now `boolean | string`. The asymmetry with `visible` was accidental rather than
124
+ deliberate.
125
+
126
+ Both are widenings on authored-input-dominant properties: authors gain a
127
+ spelling, nothing that type-checked before stops doing so, and readers already
128
+ coped with `any` through `BaseSchema`'s index signature. Three test fixtures that
129
+ had been casting past these declarations with `as unknown as BaseSchema` state
130
+ their values directly now, and the declared unions are pinned invariantly so
131
+ neither a missing widening nor an overshoot to `any` can pass unnoticed.
132
+
133
+ Declaring the vocabulary honestly also surfaced a real one: the `toggle`
134
+ renderer writes `aria-label` itself instead of going through SchemaRenderer's
135
+ resolver, and it forwarded the raw value. Invoked directly it emitted
136
+ `aria-label="[object Object]"` for a keyed label — announced verbatim by a
137
+ screen reader. It resolves now. Through `SchemaRenderer` the bug was invisible,
138
+ because SchemaRenderer injects its own resolved `aria-label` afterwards; a
139
+ downstream type-check sweep found it, not a test.
140
+
141
+ `BaseSchema.label` and `.description` are deliberately unchanged and pinned that
142
+ way. They receive the spec's inline `I18nLabel` from the view bridges, which is a
143
+ real defect, but resolving it belongs at the spec-to-schema boundary rather than
144
+ in this declaration — and that work is still blocked on a design question about
145
+ where the display locale enters, so it is not in this release.
146
+
147
+ - bb68488: An inline per-locale label now renders its locale's string at the thirteen read sites the `@objectstack/spec` 17.0.0-rc.6 bump exposed
148
+
149
+ rc.6 widened `I18nLabel` from `string` to `string | Record<string, string>`, so an author may write `label: { en: 'Owner', 'zh-CN': '负责人' }` anywhere the spec accepts a display label. PR #4169 repaired eight such sites; these thirteen were invisible to it because the five packages involved build through vite/rolldown, so `turbo run build` never type-checks their sources — only `turbo run type-check` does. All thirteen are now resolved through a shared resolver against a real locale, and `turbo run type-check` is 78/78 with zero errors.
150
+
151
+ | package | what an author can now write and see |
152
+ | ----------------------------- | --------------------------------------------------------------------------------------- |
153
+ | `@object-ui/layout` | `NavigationArea.label` — the sidebar area switcher's button and its tooltip |
154
+ | `@object-ui/plugin-list` | `ViewTab.label` — the inline pill row, and the mobile dropdown's trigger and menu items |
155
+ | `@object-ui/plugin-dashboard` | `DashboardWidget.title` — the widget card heading and its `title` attribute |
156
+ | `@object-ui/plugin-designer` | `DashboardWidget.title` — the widget card and the preview tile |
157
+ | `@object-ui/app-shell` | `ActionParam.label` **and** each `ActionParam.options[].label` |
158
+
159
+ **Patch, not minor, in every case: no public surface changes meaning.** Every entry above is a read site that previously could only be reached with a value the type system rejected, so no caller's working code changes behaviour. `@object-ui/app-shell` is the only package with an exported-type change and it is purely additive on the authoring side — `RawActionParam.label` and `RawActionParam.options[].label` widen to `I18nLabel` (they accept strictly more), `ResolveActionParamsContext` gains an optional `locale`, and the new `RawActionParamOption` names the authoring shape that was previously spelled with the resolved one. What `resolveActionParams` **emits** is unchanged: `ActionParamDef.label` and its options' labels are still plain `string`s.
160
+
161
+ Two consequences worth knowing:
162
+
163
+ - **The dashboard designer's title input is deliberately read-only for a map-valued title.** Resolving a per-locale map into a single-line input and writing `e.target.value` back would collapse every other locale on the first keystroke, so the write is guarded and an inline map survives an unrelated edit-and-save round trip untouched — the same conservative branch #4169 took for `DashboardWidgetInspector`. What Studio should actually offer for authoring a per-locale label is objectui#4163 part 2, which is unclaimed and pending design.
164
+ - **`@object-ui/layout` resolves at the spec's `en` default, not the viewer's language.** That package carries no i18n dependency by design (its whole i18n story is injection), and `AppSchemaRendererProps` exposes no locale to thread. The choice and what would change it are documented at the call site.
165
+
166
+ - Updated dependencies [ceccdcf]
167
+ - Updated dependencies [d6e5124]
168
+ - Updated dependencies [debad27]
169
+ - Updated dependencies [dc2aa3e]
170
+ - Updated dependencies [ee66e2e]
171
+ - Updated dependencies [ee26e65]
172
+ - Updated dependencies [5900ac5]
173
+ - Updated dependencies [f650253]
174
+ - Updated dependencies [3d9769a]
175
+ - Updated dependencies [8f85f8b]
176
+ - Updated dependencies [d0c3b26]
177
+ - Updated dependencies [3fc2971]
178
+ - Updated dependencies [aca27fa]
179
+ - Updated dependencies [dde7283]
180
+ - Updated dependencies [4dadf0d]
181
+ - Updated dependencies [ae10a01]
182
+ - Updated dependencies [92876f0]
183
+ - Updated dependencies [f279deb]
184
+ - Updated dependencies [4b70d28]
185
+ - Updated dependencies [eb7f586]
186
+ - Updated dependencies [e901131]
187
+ - Updated dependencies [d9d3463]
188
+ - Updated dependencies [2a40f69]
189
+ - Updated dependencies [bec3e14]
190
+ - Updated dependencies [613b167]
191
+ - Updated dependencies [b4d3c22]
192
+ - Updated dependencies [1f9b905]
193
+ - Updated dependencies [cb13400]
194
+ - Updated dependencies [bc64bfe]
195
+ - Updated dependencies [abb0f81]
196
+ - Updated dependencies [38ab505]
197
+ - Updated dependencies [3e19fe7]
198
+ - Updated dependencies [b953a97]
199
+ - Updated dependencies [d7f3e30]
200
+ - Updated dependencies [7e4f0e5]
201
+ - Updated dependencies [a84385b]
202
+ - Updated dependencies [45e1949]
203
+ - Updated dependencies [92250d6]
204
+ - Updated dependencies [c1d939f]
205
+ - Updated dependencies [49ae9f4]
206
+ - Updated dependencies [a3ae404]
207
+ - Updated dependencies [bfdf3d4]
208
+ - Updated dependencies [bb68488]
209
+ - Updated dependencies [b1e42d0]
210
+ - Updated dependencies [2459a3e]
211
+ - Updated dependencies [d6aa172]
212
+ - Updated dependencies [fe52a04]
213
+ - Updated dependencies [3f5f87c]
214
+ - Updated dependencies [f5e1143]
215
+ - Updated dependencies [f148a64]
216
+ - Updated dependencies [bb68488]
217
+ - Updated dependencies [9461dd3]
218
+ - Updated dependencies [47f551b]
219
+ - Updated dependencies [ab04728]
220
+ - Updated dependencies [5bf09fd]
221
+ - @object-ui/react@17.5.0
222
+ - @object-ui/components@17.5.0
223
+ - @object-ui/core@17.5.0
224
+ - @object-ui/types@17.5.0
225
+
226
+ ## 17.4.0
227
+
228
+ ### Patch Changes
229
+
230
+ - 6bd6a4d: `registerLayout()` 的 `inputs` 声明面与渲染器实现对齐 —— 校验器不再对正确写法报假诊断
231
+
232
+ `inputs` 是**作者面**:`sdui-parser/src/validate.ts` 拿一个节点的顶层属性逐个比对
233
+ `comp.inputs`,不在其中的报 `unknown-prop`,类型不符的报 `type-mismatch`;设计器面板、
234
+ `sdui.manifest.json`、生成的 JSX 类型也都由它派生。所以声明面写错的代价不是"文档不全",
235
+ 而是**校验器对着能正常渲染的 schema 说谎**(objectui#3972,与 objectui#3900 同族、换到属性面)。
236
+
237
+ 两处修正,都是按渲染器**实际读点**审计出来的:
238
+
239
+ - **`page-header` 补声明 `icon` 与 `actions`。** 两个键三面齐全:渲染器真读
240
+ (`PageHeader.tsx:117` 取参 `:224-226` 渲染 icon;`:119` 取参 `:192-196` 把 actions
241
+ 委派给 `record:quick_actions`)、`@objectstack/spec/ui` 的 `PageHeaderProps` 声明、
242
+ `ManifestInputType` 表达得了(`string` / `array`)。`content/docs/layout/page-header.mdx`
243
+ 的 Component Props 段落也把两者写成公开契约,而该文档页**唯一**的 live demo
244
+ (`layout-page-header/pageheader-with-actions`)就写着 `"icon": "users"` —— 于是仓内
245
+ 自己的文档示例每次过 manifest 门都收到一条 `unknown-prop: page-header has no prop "icon"`。
246
+ `actions` 的类型与 canonical 的 `page:header` 逐字一致(`type: 'array'`),一个概念一个键
247
+ 一种类型。
248
+ - **`navigation-renderer` 的 `items` 由 `type: 'object'` 改为 `'array'`。**
249
+ `NavigationRendererProps.items` 是 `NavigationItem[]`(`NavigationRenderer.tsx:108`),
250
+ 而 `checkType` 对 `'object'` 判 `typeof value === 'object' && !Array.isArray(value)`、
251
+ 对 `'array'` 判 `Array.isArray`(`validate.ts:124-129`)—— 两者互斥。所以旧声明对这个
252
+ 渲染器**唯一能渲染的形状**报 `type-mismatch ... expected an object`,而对真会让它崩的
253
+ 对象形状一言不发。这不是 objectui#3832 的表达力问题:`ManifestInputType` 本来就有
254
+ `'array'`,只是声明写错了一个表达得了的类型。
255
+
256
+ **刻意不声明**的键同样被钉住,因为"照抄 spec 的 shape"是这条修复最容易滑进去的反向缺陷:
257
+ `breadcrumb`(spec 有、这个渲染器零读点 —— 声明它就是 objectui#3829 的缺陷方向)、
258
+ `showBack` / `action` / `description`(渲染器读、spec 无此键 —— 声明任一个就是在开第二套
259
+ 方言,正是 objectui#3226 收窄要防的事)、`aria`(每个 block 都因同一理由省略的可访问性逃逸口)。
260
+
261
+ 渲染输出逐字节不变:渲染路径从不读 `inputs`。变化只在校验/设计器/清单这一侧,且两个方向
262
+ 都钉了 —— 正确写法放行的同时,`description` 这类刻意不声明的键仍报 `unknown-prop`、
263
+ `items` 写成对象仍报 `type-mismatch`(诊断没有被弄哑)。
264
+
265
+ - 876e3f7: `@object-ui/layout` no longer tells bundlers it has no side effects while registering components at load time (objectui#3899)
266
+
267
+ The published manifest declared `"sideEffects": false` — a promise that no module
268
+ in the package does anything on evaluation, so any module whose exports go unused
269
+ may be dropped whole. But `src/index.ts` ends with a bare
270
+ `try { registerLayout(); } catch {}`, and that call is the only thing that puts
271
+ `page-header`, `page:card`, `app-shell`, `responsive-grid`,
272
+ `navigation-renderer` and `app-schema-renderer` into the `ComponentRegistry`.
273
+
274
+ Both statements cannot be true, and a bundler that believes the manifest is
275
+ right to delete the registration. Measured with the repo's own bundler by
276
+ building `import '@object-ui/layout';` — the side-effect-only import, i.e. the
277
+ documented "import it to register" pattern:
278
+
279
+ - `sideEffects: false` — the bundle is **0 bytes**. Zero registrations, exit
280
+ code 0, no warning.
281
+ - after this change — the bundle keeps all six `ComponentRegistry.register`
282
+ calls.
283
+
284
+ A dropped registration does not fail where it happened. It surfaces later as a
285
+ red `Unknown component type` panel (OBJUI-001) on a fully green build, with
286
+ nothing in the build log to connect the two. Nobody had been bitten yet only
287
+ because every consumer today ALSO imports a named export, which forces the module
288
+ to be evaluated regardless — coincidence, not design. objectui#3787 met the
289
+ hazard and routed around it by calling `registerLayout()` explicitly.
290
+
291
+ `sideEffects` is now the narrowest honest answer: an array naming the modules
292
+ that actually register, rather than `true`, which would be honest but would hand
293
+ the whole package to every bundler as unshakeable.
294
+
295
+ ```json
296
+ "sideEffects": ["./dist/index.js", "./dist/index.umd.cjs", "./src/index.ts"]
297
+ ```
298
+
299
+ All three are load-bearing, and the set is derived from the manifest rather than
300
+ guessed:
301
+
302
+ - `./dist/index.js` and `./dist/index.umd.cjs` are every JS file the manifest's
303
+ own entry fields point at (`module` / `main` / `exports` import+require). The
304
+ library build inlines everything into those two files, so there is no third
305
+ chunk to name.
306
+ - `./src/index.ts` is not published (`files` ships `dist` only) but is bundled
307
+ for real: `apps/console` and `examples/console-starter` both alias the
308
+ specifier straight at `packages/layout/src`, and a bundler reads this same
309
+ manifest for those files. With only the published paths declared, the console's
310
+ alias shape still produced a 0-byte bundle.
311
+
312
+ What deliberately did NOT change: the load-time `registerLayout()` itself.
313
+ Replacing it with an explicit registration API is the opposite direction — it
314
+ eliminates the side effect instead of declaring it, and it is breaking for any
315
+ consumer relying on automatic registration. objectui#3899 leaves that call to the
316
+ maintainer, and the two steps do not conflict: once the manifest tells the truth,
317
+ the migration to explicit registration can happen whenever it is wanted.
318
+
319
+ A new pin (`packages/layout/src/__tests__/side-effects-manifest.test.ts`) runs a
320
+ real bundler build per entry form and asserts the registrations survive a
321
+ side-effect-only import, with a `sideEffects: false` control per form asserting
322
+ they are dropped — so the pin cannot pass because the bundler stopped shaking
323
+ anything. The required set is derived from the manifest's own entry fields, so a
324
+ renamed build output or a new `exports` subpath fails as a missing declaration
325
+ instead of drifting silently.
326
+
327
+ - f3b2874: `navigation-renderer` 的 `items` 声明为 `required: true` —— 校验器不再放过必崩的节点
328
+
329
+ `items` 在组件侧是**非可选**的 `NavigationItem[]`(`NavigationRendererProps.items`,无 `?`),
330
+ 渲染器也不给默认值,而注册声明一直没写 `required`。`sdui-parser` 只在 `input.required` 为真时
331
+ 报 `missing-required-prop`(`validate.ts:55-64`),于是 `{ "type": "navigation-renderer" }`
332
+ 这个节点**校验零诊断、渲染直接抛**:第一处无守卫的读点是 `pinnedItems` memo 里的
333
+ `collectPinnedItems(filteredItems)`(`NavigationRenderer.tsx:1242` → `:1410` 的
334
+ `for (const item of items)`),实测 `TypeError: items is not iterable`。
335
+ (`resolveActiveNavItem` memo 挡得住 —— 它的 `visit` 首行是 `if (!nodes) return`;
336
+ `:1247` 的 `filteredItems.slice()` 同样会抛,但根本走不到。)
337
+
338
+ 这是 objectui#3972(键的**存在**与**类型**三面对齐)的第四面:**可选性**。#3972 与
339
+ objectui#3900 都是删除假诊断,这一条相反——它是**收紧**。
340
+
341
+ **blast radius:** 今天省略 `items` 写 `navigation-renderer` 的 schema,会新增一条
342
+ **error** 级 `missing-required-prop`。受影响面是仓外按 `inputs` / `packages/layout/README.md`
343
+ 做 schema 驱动的消费者(仓内没有任何 JSON 元数据把它当 schema 节点写,React 调用侧的必填由
344
+ TS 兜住;`examples/schema-catalog` 的 `not-a-container` 对照节点补了 `items: []`,使它只剩
345
+ 那一处故意植入的缺陷)。而这条诊断新拦下的形状,**恰好等于渲染必然崩溃的形状** —— 让作者
346
+ (尤其是 AI 作者)在发布期就听到运行期注定要发生的失败,正是 `missing-required-prop` 存在
347
+ 的理由。若某个消费者确实想要"缺 items 就渲染空导航",那是给组件加 `= []` 默认值的另一条路
348
+ (objectui#3987 里记了,与本改动不互斥),而不是让校验器继续沉默。
349
+
350
+ `basePath` **保持可选**并被钉成对照:渲染器真的给了它默认值(`basePath = ''`)。`required`
351
+ 是逐个属性从组件读出的事实,不是一刀切——否则这道门会开始拒绝完全能渲染的 schema,作者就会
352
+ 学着无视 `missing-required-prop`,正如 #3972 里他们被教着无视 `type-mismatch`。
353
+
354
+ - 82f8dff: `page-header` 注册补 `isContainer: true` —— 校验器不再对文档承诺的 children 写法报 `not-a-container`
355
+
356
+ `PageHeader` 一直**有意**把 `schema.children` 渲染进右侧动作槽(`PageHeader.tsx:182`,
357
+ `record:quick_actions` 嵌在 `page:header.children` 下就是靠它),
358
+ `content/docs/layout/page-header.mdx` 把该槽的优先级(`action` → React `children` →
359
+ `actions` → schema children)写成公开契约,该文档页唯一的 live demo
360
+ (`layout-page-header/pageheader-with-actions`)正是这个形状且实测正常渲染。但
361
+ `packages/layout/src/index.ts` 的注册漏了 `isContainer: true`。
362
+
363
+ 漏这个 flag 从来没有挡住任何渲染 —— 渲染路径根本不读它(`SchemaRenderer` 把
364
+ `children` 从 React props 里剥掉当元数据,而始终把整个节点作为 `schema` 传下去,
365
+ `PageHeader` 自己再把 `schema.children` 放回槽里)。它的消费者在别处:`sdui-parser`
366
+ 的 `not-a-container` 诊断、Studio 调色板元数据、react-page 标签表。所以真正的后果是
367
+ **校验器在说谎**:作者照文档写出能正常渲染的 schema,却拿到一条
368
+ "`page-header` does not accept children" 的 warning;信了这条 warning 去掉 children,
369
+ 右槽就空掉。而会说谎的 warning 比缺一条 warning 更贵 —— 它训练作者(尤其 AI 作者)
370
+ 连真实的 `not-a-container`(那些确实不收子节点的组件)一起无视。
371
+
372
+ 这不是在 spec 之外新开作者面:`children` 是 objectui JSON 协议里**每个节点**的基础属性
373
+ (`sdui-parser/src/validate.ts` 的 `BASE_PROPS` 把它和 `type`/`id`/`className` 并列),
374
+ 不是 `PageHeaderProps` 的键。所以这个 flag 回答的是协议层面的"该节点是否接受子节点列表",
375
+ 而对这个组件,答案一直是"是"。维护者 2026-08-09 就 objectui#3900 的 A/B 分叉裁定 A 案,
376
+ 理由同上。
377
+
378
+ 行为面变化极窄:注册元数据一个布尔位。渲染输出逐字节不变(渲染路径不读该 flag);
379
+ `sdui-parser` 对带 children 的 `page-header` 少报一条 warning;设计器把它当容器对待
380
+ (即它本来的样子)。canonical 的 `page:header`(`@object-ui/components`)不在此列且刻意不动
381
+ —— 那个渲染器完全不读 `schema.children`,所以它没有 `isContainer` 是正确的。
382
+
383
+ 两个方向都已钉住:文档 demo 走应用真实构建的 manifest 后不再产生 `not-a-container`,
384
+ 而一个真正不收子节点的组件(`navigation-renderer`)带 children 时诊断照旧触发 ——
385
+ 后者是前者的对照,保证这条修复不是把诊断弄哑了。
386
+
387
+ - Updated dependencies [794c497]
388
+ - Updated dependencies [993336f]
389
+ - Updated dependencies [f0a625a]
390
+ - Updated dependencies [b5980f4]
391
+ - Updated dependencies [8aad9fd]
392
+ - Updated dependencies [6719877]
393
+ - Updated dependencies [56ff091]
394
+ - Updated dependencies [0cbdca8]
395
+ - Updated dependencies [d229dfa]
396
+ - Updated dependencies [ecae400]
397
+ - Updated dependencies [4bc6c23]
398
+ - Updated dependencies [d3e738a]
399
+ - Updated dependencies [c3b01a7]
400
+ - Updated dependencies [7ed3360]
401
+ - Updated dependencies [0fa5e4d]
402
+ - Updated dependencies [5bfaabd]
403
+ - Updated dependencies [e06810e]
404
+ - Updated dependencies [ab3ad4f]
405
+ - Updated dependencies [c2fd122]
406
+ - Updated dependencies [e24d767]
407
+ - Updated dependencies [aca561a]
408
+ - Updated dependencies [48132f7]
409
+ - Updated dependencies [0ef9dfd]
410
+ - Updated dependencies [1d723e3]
411
+ - Updated dependencies [0109f54]
412
+ - Updated dependencies [7e5bb5d]
413
+ - Updated dependencies [fbc23e0]
414
+ - Updated dependencies [e6fdbdc]
415
+ - Updated dependencies [54233b1]
416
+ - Updated dependencies [97b63d7]
417
+ - Updated dependencies [6bb454a]
418
+ - Updated dependencies [523be48]
419
+ - Updated dependencies [7e2b7e9]
420
+ - Updated dependencies [c1e1e6b]
421
+ - @object-ui/components@17.4.0
422
+ - @object-ui/react@17.4.0
423
+ - @object-ui/core@17.4.0
424
+ - @object-ui/types@17.4.0
425
+
426
+ ## 17.3.0
427
+
428
+ ### Minor Changes
429
+
430
+ - 608669e: `AppSchemaRenderer` now derives area visibility from the items inside the
431
+ area, closing the visible-but-empty regression the spec 17.0.0 area-key
432
+ retirement left behind (objectui#3311, option C of the recorded ruling).
433
+
434
+ Spec 17.0.0 retired the authorable area-level `visible` /
435
+ `requiredPermissions` (`AREA_VISIBLE_RETIRED` /
436
+ `AREA_REQUIRED_PERMISSIONS_RETIRED`) — an area is a layout grouping, not an
437
+ access boundary — and objectui followed in #3315 by deleting the area
438
+ switcher's filter. Correct on the contract, but it changed the navigation
439
+ surface: an area whose items are **all** gated away used to disappear from
440
+ the switcher and instead rendered as a selectable, empty area.
441
+
442
+ ## What changed
443
+
444
+ - **Area visibility is now derived, not authored.** An area appears in the
445
+ switcher iff at least one of its navigation items survives the exact
446
+ item-level guards `NavigationRenderer` applies: the `visible` expression,
447
+ `requiredPermissions`, the `requiresObject` / `requiresService` runtime
448
+ capability gates, and — for `action` items — the presence of an `onAction`
449
+ dispatcher (framework#4509: without one they are not rendered, so they
450
+ cannot carry an area either). Separators never count; a `group` counts only
451
+ through its children.
452
+ - **The active area is elected among visible areas only.** A fully gated
453
+ first area is no longer auto-activated, and when a gating change hides the
454
+ currently active area the shell re-elects the first visible one. A gating
455
+ change that merely _reveals_ an area never yanks the user away from where
456
+ they are.
457
+ - **An area with no items at all derives the same way**: no visible item →
458
+ hidden. (Boundary recorded in objectui#3311.)
459
+ - New export `hasVisibleNavigationItems(items, options)` from
460
+ `@object-ui/layout` — the predicate behind the derivation, usable by other
461
+ shells that render their own area switchers.
462
+
463
+ No authorable key is involved anywhere: the platform's `.strict()` area
464
+ object still rejects the retired keys, and the derivation — computed from the
465
+ same guards that decide what renders — cannot disagree with the rendered
466
+ navigation, so there is nothing for a metadata author to get wrong.
467
+
468
+ - d22ae31: Track `@objectstack/spec` 17.0.0-rc.2 (objectui#3235, #3208, #3287, #3264).
469
+
470
+ The pin moves from `^17.0.0-rc.1` to `^17.0.0-rc.2` across the workspace, and
471
+ the sibling `@objectstack/*` packages (`client` / `core` / `formula` / `lint`)
472
+ move with it — they pin `@objectstack/spec` **exactly**, so leaving them behind
473
+ kept a second copy of the spec in the tree and would have had `@objectstack/lint`
474
+ validating against rc.1 schemas that still accept keys rc.2 retires.
475
+
476
+ Breaking semantics, in FROM → TO form:
477
+
478
+ - **`app.homePageId` is retired — an app's landing page is now its first
479
+ navigation item.** An app that pinned a landing page with `homePageId` will
480
+ open on the first reachable navigation entry (by `order`) instead; the root
481
+ landing still follows `isDefault`. To restore a specific landing page, reorder
482
+ `navigation` so the intended entry comes first. Stored metadata is migrated by
483
+ `os migrate meta --from 16`. The key is a hard error now, not a stripped one:
484
+ the spec ships a tombstone that names the migration.
485
+ Upstream retired it because of its SHAPE, not its usage — it was an ID
486
+ cross-reference with no referential integrity, so a `homePageId` that pointed
487
+ at nothing silently fell back to the first navigation item anyway
488
+ (objectstack#4667, premise corrected in #4709). If the capability returns, it
489
+ returns as a flag on the navigation item itself, which cannot dangle.
490
+ - **`@object-ui/types`' `HttpMethod` now resolves to the spec's
491
+ `HttpMethodType`.** Shape is verbatim identical — the same 5-value UI subset —
492
+ and `@object-ui/types` still exports it as `HttpMethod`, so no consumer
493
+ changes. The spec renamed its `./ui` export because `HttpMethod` named two
494
+ different types depending on the import path (`./shared` / `./api` carry a
495
+ 7-value enum including `HEAD` / `OPTIONS`); objectui deliberately keeps the
496
+ 5-value one (objectstack#4691).
497
+ - **`AppContextSelector.includeAll` / `placement` are gone.** Neither ever did
498
+ anything in this renderer: context selectors are mandatory-scope, so no "All"
499
+ row was ever rendered, and `placement: 'topbar'` put nothing in the topbar.
500
+ Both carried schema defaults, which is why the liveness lint structurally
501
+ could not flag them — removal was the only channel that reaches an author
502
+ (framework#4509).
503
+ - **`NavigationArea.visible` / `order` / `requiredPermissions` are gone.** An
504
+ area is a layout grouping, not an access boundary. Gating moved down to the
505
+ navigation ITEM, where `visible` and `requiredPermissions` are unchanged and
506
+ still enforced. `AppSchemaRenderer`'s area switcher no longer hides an area, so
507
+ an area whose items are all gated away renders as visible-but-empty rather
508
+ than disappearing.
509
+ - **`@object-ui/core` no longer exports `NotificationProtocol`**
510
+ (`resolveNotificationConfig`, `specNotificationToToast`, `mapSeverityToVariant`,
511
+ `mapPosition`, `ToastNotification`). It bridged `@objectstack/spec/ui`'s
512
+ `Notification` / `NotificationConfig`, which objectstack#4610 deleted with no
513
+ successor. Use `resolveNotificationConfig` from `@object-ui/react`
514
+ (`NotificationContext`), which owns the live `NotificationSystemConfig` and is
515
+ what every notification surface already read. Note that the spec's _other_
516
+ `Notification` — `@objectstack/spec/api` — is the REST inbox row, a different
517
+ contract, and is deliberately NOT aliased in as a replacement.
518
+ - **The `email_template` client-side validator now uses
519
+ `EmailTemplateDefinitionSchema`.** It was pointing at the removed
520
+ `EmailTemplateSchema`, so authored templates were being checked against the
521
+ wrong contract: the live one is keyed `name` + `locale` (not `id`) and splits
522
+ the body into `bodyHtml` / `bodyText` (not `body` + `bodyType`)
523
+ (objectstack#4616 / #4807).
524
+
525
+ Fixes that are not breaking, but were only found because rc.2 stopped being
526
+ lenient — each had been passing vacuously:
527
+
528
+ - **`view` drafts are actually validated now.** The client validator named the
529
+ aggregated container schema while this admin authors first-class `ViewItem`s,
530
+ and the container used to strip `viewKind` / `config` in silence — so no view
531
+ draft ever had one of its own keys checked. It now validates each shape
532
+ against its own schema (objectui#3312).
533
+ - **The console's worked examples were wrong**, and being stripped rather than
534
+ refused: `view.list.object` (the container root already declares it),
535
+ `job.concurrency` / `job.timeoutMs` (no such keys; the spelling is `timeout`,
536
+ already in ms), `email_template.from` / `.to` (a template is not a send —
537
+ the sender override is `fromOverride`, an object), and
538
+ `datasource.capabilities` / `.healthCheck` (objectstack#4583 removed the
539
+ former; the latter was never a datasource key). These are the drafts an
540
+ author — or a model generating metadata — copies.
541
+ - Action key inventory re-derived: `ActionSchema` gained the package-lock
542
+ envelope (`_lock*` / `_package*` / `_provenance`), so a packaged action no
543
+ longer reports them as unknown keys.
544
+ - The schema-diff panel labels the new `default_mismatch` finding.
545
+ - Test fixtures pinning the retired `managedBy: 'system'` bucket now use
546
+ `engine-owned`. Protocol 17 split that value (objectstack#3355), so it
547
+ resolved to the default-writable fallback and a batch of "stays locked"
548
+ assertions had quietly stopped asserting anything.
549
+
550
+ ### Patch Changes
551
+
552
+ - d2363e7: The legacy `page-header` alias stops advertising `description` as an authorable
553
+ key (objectui#3226).
554
+
555
+ FROM: `registerLayout()` declared `inputs: [title, description]`. TO:
556
+ `inputs: [title, subtitle]` — the key `@objectstack/spec/ui`'s `PageHeaderProps`
557
+ declares, and the one the canonical `page:header` renderer in
558
+ `@object-ui/components` already declares.
559
+
560
+ `inputs` is a DECLARATION surface, not documentation: the designer builds its
561
+ property palette from it, and the framework's `check:react-declaration-parity`
562
+ diffs it against the spec schemas. Declaring `description` therefore did not
563
+ merely tolerate a legacy spelling — it published a second dialect for the one
564
+ concept the protocol calls `subtitle`, and told authors (an AI author most
565
+ readily, since the registry is what it reads to learn the shape) that the
566
+ non-spec key was legal. Metadata that took the offer renders a subtitle under
567
+ `page-header` and silently loses it under `page:header`: same JSON, two results,
568
+ which is the outcome a single contract exists to prevent.
569
+
570
+ No runtime behaviour changes. `PageHeader` still reads `subtitle ?? description`,
571
+ deliberately: this alias exists for out-of-repo consumer schemas, so "no in-repo
572
+ author writes `description`" (verified — zero hits) is not evidence that nobody
573
+ does, and dropping the read today would delete an external page's second line
574
+ while its title kept rendering, the least reportable failure mode there is. That
575
+ read is retired together with an ADR-0087 D2 conversion entry
576
+ (`page-header-subtitle-alias`, `description` → `subtitle` rewritten at load
577
+ time), which lives in the framework repo and is tracked separately. Narrowing the
578
+ declaration did not need to wait on it and breaks no consumer; leaving the
579
+ declaration wrong in the meantime keeps minting the metadata the conversion would
580
+ then have to absorb.
581
+
582
+ New tests pin both halves so neither can drift back: the registration may not
583
+ declare `description`, must declare `subtitle`, and — checked against the spec's
584
+ own shape rather than a hand-written allowlist — may declare nothing
585
+ `@objectstack/spec` does not; while the runtime fallback is pinned as a sequencing
586
+ guard, to be deleted in the same change that lands the conversion entry.
587
+
588
+ - Updated dependencies [18cd432]
589
+ - Updated dependencies [532cf8b]
590
+ - Updated dependencies [680080a]
591
+ - Updated dependencies [a7651e6]
592
+ - Updated dependencies [d915c47]
593
+ - Updated dependencies [b71fc92]
594
+ - Updated dependencies [34595eb]
595
+ - Updated dependencies [3889ffb]
596
+ - Updated dependencies [5781fb1]
597
+ - Updated dependencies [9e9e9a9]
598
+ - Updated dependencies [56409c2]
599
+ - Updated dependencies [042e09d]
600
+ - Updated dependencies [9cbcbf4]
601
+ - Updated dependencies [85c4c9c]
602
+ - Updated dependencies [fd54c3e]
603
+ - Updated dependencies [4eeb932]
604
+ - Updated dependencies [23018cc]
605
+ - Updated dependencies [53811d1]
606
+ - Updated dependencies [d915c47]
607
+ - Updated dependencies [f44d872]
608
+ - Updated dependencies [509104a]
609
+ - Updated dependencies [825bbe3]
610
+ - Updated dependencies [5dd0127]
611
+ - Updated dependencies [06632e9]
612
+ - Updated dependencies [a4cff5b]
613
+ - Updated dependencies [175bd79]
614
+ - Updated dependencies [f833d3a]
615
+ - Updated dependencies [2a9513d]
616
+ - Updated dependencies [71be406]
617
+ - Updated dependencies [d22ae31]
618
+ - Updated dependencies [8d8094a]
619
+ - @object-ui/core@17.3.0
620
+ - @object-ui/components@17.3.0
621
+ - @object-ui/types@17.3.0
622
+ - @object-ui/react@17.3.0
623
+
3
624
  ## 17.2.0
4
625
 
5
626
  ### Minor Changes
@@ -815,6 +1436,7 @@
815
1436
  - b703480: feat(layout): smoother sidebar transitions
816
1437
 
817
1438
  `SidebarNav` now animates the previously-instant state changes:
1439
+
818
1440
  - Active-state colour swap on `SidebarMenuButton` /
819
1441
  `SidebarMenuSubButton` is wrapped in `transition-colors duration-150`
820
1442
  so navigating between rows glides rather than snaps.
@@ -894,6 +1516,7 @@
894
1516
  record detail page (lead, opportunity, future account/contact/case).
895
1517
  Before this change it had two problems that bled into every custom
896
1518
  page across the product:
1519
+
897
1520
  1. **Quadruple registration**: `@object-ui/layout` registered both
898
1521
  `page-header` and `page:header`, and `@object-ui/components`
899
1522
  independently registered `page:header` (and `page:section`).
@@ -904,6 +1527,7 @@
904
1527
  than the default detail view it was meant to supersede.
905
1528
 
906
1529
  This commit:
1530
+
907
1531
  - Removes the `@object-ui/layout` `page:header` registration. The
908
1532
  layout package keeps the legacy kebab-cased `page-header` alias only.
909
1533
  The canonical renderer now lives in `@object-ui/components` and is
@@ -982,6 +1606,7 @@
982
1606
  - d714e85: Lookup display-name resolution now falls back through a Salesforce-style chain
983
1607
  when an `$expand`'d reference object lacks a top-level `name`/`label`/
984
1608
  `display_name`/`title` field:
1609
+
985
1610
  1. Standard display fields (existing behaviour)
986
1611
  2. `salutation first_name last_name` composite — handles person records that
987
1612
  only carry first/last name parts
@@ -1040,6 +1665,7 @@ Lin` and no `name` field now renders as `Bob Lin` everywhere — instead of
1040
1665
  pages without a record context are unaffected.
1041
1666
 
1042
1667
  - 2bd45af: feat(shell): main becomes the scroll container; record tabs are sticky
1668
+
1043
1669
  - `AppShell`'s SidebarProvider wrapper is now constrained to viewport
1044
1670
  height (`h-svh overflow-hidden`) instead of expanding with content via
1045
1671
  the default `min-h-svh`. This makes the inner `<main>` (which is
@@ -1062,6 +1688,7 @@ Lin` and no `name` field now renders as `Bob Lin` everywhere — instead of
1062
1688
  ### Patch Changes
1063
1689
 
1064
1690
  - 6b683c8: fix(detail): clean up record page rendering
1691
+
1065
1692
  - Drop `ai:chat_window` from the protocol-component placeholder list. The
1066
1693
  floating chat overlay (plugin-chatbot) is the canonical AI entry point;
1067
1694
  inline page schemas that still reference `ai:chat_window` now surface
@@ -1075,6 +1702,7 @@ Lin` and no `name` field now renders as `Bob Lin` everywhere — instead of
1075
1702
  as a fallback so both inlined and raw-bag schema shapes are supported.
1076
1703
 
1077
1704
  - 0d8eb98: feat(detail): Salesforce-style record header + section field grid
1705
+
1078
1706
  - `page:header` now renders an icon chip (resolves Lucide names via
1079
1707
  `LazyIcon`) plus subtitle, so detail pages can show
1080
1708
  "Name / Company" without an extra component.
@@ -1233,6 +1861,7 @@ Lin` and no `name` field now renders as `Bob Lin` everywhere — instead of
1233
1861
  Library builds (vite lib mode) now externalize every non-relative import instead of bundling third-party CJS dependencies into the published dist. This avoids inlined `require("react")` / `require("react-dom")` calls that cause `Calling \`require\` for "react" in an environment that doesn't expose the \`require\` function` runtime errors when consumer apps re-bundle the published dist.
1234
1862
 
1235
1863
  Specifically fixes:
1864
+
1236
1865
  - `@object-ui/plugin-dashboard` no longer inlines `react-grid-layout` (and its transitive `react-draggable` / `react-resizable` CJS bundles). `react-grid-layout` is now declared as a peer dependency so consumers install a single ESM-friendly copy.
1237
1866
  - `@object-ui/components`, `@object-ui/plugin-calendar`, `@object-ui/plugin-charts`, `@object-ui/plugin-designer` no longer inline `react-i18next` / `i18next` / `use-sync-external-store` CJS shims.
1238
1867
  - All plugin packages now use a unified `external: (id) => !/^[./]/.test(id) && !id.startsWith(__dirname)` rule, ensuring future additions of CJS deps are automatically externalized.