Skip to content

Commit bb68488

Browse files
yinlianghuiclaude
andauthored
fix(i18n): render I18nLabel objects at the 13 remaining sites (#4208)
* retire(studio): drop the global_nav designer surfaces with the rc.6 spec bump (objectstack#7100) `global_nav` was an `ACTION_LOCATIONS` member no running-app surface ever rendered — the console's ⌘K palette reads no action metadata at all — and the Studio designer previewed a mock command-palette frame for it anyway. @objectstack/spec 17.0.0-rc.6 retired the member (objectstack#6888, maintainer ruling 2026-08-09 direction 2); this removes the designer surfaces that outlived it, plus the bump that makes them stop compiling. - ActionPreview.tsx: the mock ⌘K placement frame is gone. The metadata strip still echoes the declared token, deliberately — reporting what a draft says is honest; the frame CLAIMED the platform renders it. - ActionDefaultInspector.tsx: the `LOCATION_LABELS` entry is gone. The map is `Record<ActionLocation, string>`, so the retirement arrived as a compile error (objectui#3017's mechanism firing as designed). - block-config.ts + both locale tables: the palette option and its now-orphaned i18n key. - components' action:bar doc comment aligned (its enum is `[...ACTION_LOCATIONS]` and followed on its own). - ActionEngine.getActionsForLocation is UNCHANGED: narrowing it would be a second rejection point beside the schema's. The type and `ActionLocationSchema` are the enforcement, both pinned. The bump (all 37 `@objectstack/*` declarations, rc.5 -> rc.6) rides along because the two cannot compile apart, and its fallout is repaired here: the `…Input` alias retirement (types/theme, types/spec-report, core/ActionRunner), the five dead i18n re-exports, the `I18nLabel` widening at 8 render sites, `.extend` -> `.safeExtend` on a now-refined `GlobalFilterSchema`, and two parity lists re-pointed. NOT green: the bump's remaining surface is out of this card's scope and filed — objectui#4165 (contract conflict, blocks), #4167 (adaptation remainder), #4163 (I18nLabel consumer story), #4164 (name reclaim available). Part of objectstack-ai/objectstack#7100 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3 * fix(deps): repair the rc.6 fallout the first pass missed, and pin the one conflict Second pass over the @objectstack/spec 17.0.0-rc.6 bump. The first commit fixed every COMPILE error; a full `vitest run` (14615 tests) then surfaced the runtime half. All mechanical, each pinned; the one non-mechanical case is left as a tripwire rather than decided. - types/zod/complex.zod.ts: `GlobalFilterSchema` composes by spreading the spec's `.shape`. rc.6 closed BOTH extension doors on a now-refined schema — `.extend()` throws at module load, `.safeExtend()` types the three overridden keys as `never` (TS2322, which only `apps/site`'s build caught). The spread preserves rc.5 behaviour exactly and decides nothing: the refinement-vs-stored-`{preset}` conflict is objectui#4165, pinned by a new tripwire that reds from either side. The pin isolates the refinement by widening `defaultValue` first — the bare spec schema rejects `{preset}` on the OLD field type and the refinement never runs, so the obvious shorter assertion would have pinned the wrong thing. - core/actionKeys.ts: `description` joins SPEC_ACTION_KEYS (rc.6 added it to `ActionSchema`). Inventory only — `ActionDef` is untouched. - metadata-admin flow tests x2: `FlowNodeSchema` became pipe-wrapped, so `.shape` read `undefined`. Access-path repair (walk the wrapper); the blocks themselves are unchanged upstream, and a non-vacuity guard now says so. - clientValidation.optOuts: INVERTED — rc.6 makes `SharingRuleSchema` declare the seven ADR-0010 envelope keys, so the create door now ACCEPTS a stamped `_packageId`. Upstream convergence, pinned as accept, with a bogus-key case proving the door is still a door. - clientValidation.viewDiagnostics: fixture supplies a `label`; rc.6's widened `I18nLabel` made every label a LEAF union, which #3606's root-union expansion does not reach. The resulting bare `Invalid input` is recorded as its own pin (objectui#4163), not papered over. - components/record-picker parity: fixture drops `displayField` — an ADR-0087 D2 tombstone refused by name, which was failing all three `filter` assertions. - console registry parity: `SPEC_SHAPE_EMPTY_ON_THE_PIN` deleted exactly as its own self-clearing test instructed (rc.6 lands `PageContainerProps`), with a tombstone assertion so the deletion cannot be silently undone. - i18n residue ratchet: matches `const { t, … } = useObjectTranslation(` by pattern. Both sidebars legitimately also take `language` now, to resolve the spec's inline per-locale labels. Still red, filed, NOT fixed here: 2 console-parity exemption-list entries and `check:spec-symbols`' 13 name collisions (objectui#4167) — each is per-symbol triage, not a mechanical edit — and objectui#4165, which blocks ready. Part of objectstack-ai/objectstack#7100 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3 * fix(deps): triage the 14 rc.6 spec-symbol collisions and refresh the console parity exemptions (#4167) objectui#4167, objectstack#4115. `check:spec-symbols` reported 13 collisions on the @objectstack/spec 17.0.0-rc.6 bump and a 14th (`GlobalFilterSchema`) appeared during the bump itself; each is triaged on its own rather than blanket-renamed. Renamed (3) — the spec's same-named export means something else: MetadataState -> MetadataCacheState (react, app-shell) resolveI18nLabel -> resolveKeyedI18nLabel (react, app-shell, + apps/console) DateRangePreset -> FilterBuilderDateRangePreset (types) Bound or derived (10): DATE_RANGE_PRESETS (+ a `satisfies` completeness pin on the local bounds table), NavigationMode (+ a both-directions type pin), AddressValue, BreakpointColumnMap, BreakpointOrderMap, KanbanConfig, CalendarConfig, GanttConfig. Four copies were losing information: GanttConfig declared 6 of the spec's 17 keys while plugin-gantt reads all of them, AddressValue 5 of 7 parts, and DATE_RANGE_PRESETS was the third copy objectstack#4614 extracted the vocabulary to collapse. ALLOW (1): GlobalFilterSchema, the same `.shape`-spread dialect as SelectOptionSchema beside it — it collided only because rc.6's refinement forced `.extend()` to be respelled, moving a derivation the guard could see into an object literal it deliberately does not descend into. Nothing about #4165 is encoded. Console registry-inputs-spec-parity: all 12 off-spec exemptions deleted (rc.6 landed objectstack#6776 and objectstack#5775, verified per key against the resolved shape), and `element:record_picker` publishes `sort` / `limit` / `emptyText` — read by the renderer all along, and the red demanding them was predicted in writing by the exemption it replaces. 49 tests/2 failing -> 51/0. Also repairs 3 pre-existing type-check errors of this card's own rc.6 family, found only by running `type-check` (which the bump PR did not): the retired `ThemeInput` alias re-pointed BY SIDE, and `DroppedFieldsEvent.reason`'s new `primary_key` arm. The consumer gap that arm exposes is objectui#3935. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3 * fix(i18n): render I18nLabel objects at the 13 remaining sites `@objectstack/spec` 17.0.0-rc.6 widened `I18nLabel` from `string` to `string | Record<string, string>`, so an author may inline a per-locale map (`label: { en: 'Owner', 'zh-CN': '负责人' }`) anywhere the spec accepts a display label. PR #4169 repaired eight such sites; thirteen more 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. Read sites now resolve the map to the viewer's locale instead of reaching a text node as `[object Object]`: - `@object-ui/layout` — `NavigationArea.label` (area switcher button + tooltip) - `@object-ui/plugin-list` — `ViewTab.label` (pill row, dropdown trigger, menu) - `@object-ui/plugin-dashboard` — `DashboardWidget.title` (heading + attribute) - `@object-ui/plugin-designer` — `DashboardWidget.title` (card + preview tile) - `@object-ui/app-shell` — `ActionParam.label` and each option's `label`, fixed on the `RawActionParam` DECLARATION rather than on the test that surfaced it Two edges are deliberate rather than mechanical: `DashboardEditor`'s title `<input>` is an authoring WRITE, not a display. Resolving a map into it and writing `e.target.value` back would collapse every other locale on the first keystroke, so the write is GUARDED — a map-valued title renders resolved and read-only, and survives an unrelated edit-and-save round trip untouched. The type error dies from the guard, not from a cast. What Studio should actually offer for authoring a per-locale label is #4163 part 2, which stays unclaimed and pending design. `@object-ui/layout` carries no i18n dependency by design (its whole i18n story is injection), so it uses the PRODUCER's resolver — `resolveI18nLabel` from `@objectstack/spec/ui` — at the spec's documented `en` default rather than hand-rolling a resolution that could drift. The three plugin packages sit inside objectui's i18n tree and already hold the live language, so they use `pickLocalized`; `plugin-list/src/__tests__/i18nLabel-resolver-parity.test.ts` pins the two resolvers limb for limb so the runtime dashboard and the designer's preview of it cannot start disagreeing about which entry wins. Also re-points four `plugin-list` type-test assertions at `ViewTabParsed`: rc.6's `…Input`-alias retirement moved the bare `ViewTab` onto the input side, so following the NAME left them comparing the authoring side against itself. Invisible until now because `type-check` is `tsc --noEmit && tsc -p tsconfig.typetests.json` and the `&&` never reached that project. Part of #4163 * fix(rc.6): finish the rc.6 adaptation the train never saw Three residual reds on main after the rc.5→rc.6 spec bump (#4169), none of which the train cars themselves covered. QUICK_REFERENCE.md's "Current Release" block still quoted `^17.0.0-rc.5` for both `@objectstack/spec` and `@objectstack/client` while every manifest it names as its anchor declares `rc.6`. The pin test for that block (quick-reference-current-release-4143) landed on main AFTER the train was built, so no car ever ran against it. recordDetailsInputs.spec-parity carried `layout: 'custom'` in both fixtures of its object-sections case. `layout` was removed in @objectstack/spec 17.0.0 (objectstack#6946, ADR-0087 D2) and rc.6 gave it a named `never` rejection, so the object-form fixture failed on `layout` while its `sections` were valid — and the id-list fixture above it kept PASSING on `layout`'s own `invalid_type` without ever reaching `sections`. Dropping the key puts both verdicts back on `sections`; the id-list case now also pins the issue PATH, since a bare code could not tell the two rejections apart. AddressField re-exports `AddressValue` through its declaring module rather than bare. main's #4222 moved the declaration into the pure `address-format` module while this train re-sourced the type from `@objectstack/spec/data`; the merge keeps both by importing the spec type in `address-format` (where the definition now lives) and letting the widget re-export from there, which is the form check:spec-symbols resolves to a declaration site instead of reading as a second local declaration of a name the spec owns. --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6314e87 commit bb68488

67 files changed

Lines changed: 1926 additions & 340 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
'@object-ui/layout': patch
3+
'@object-ui/plugin-list': patch
4+
'@object-ui/plugin-dashboard': patch
5+
'@object-ui/plugin-designer': patch
6+
'@object-ui/app-shell': patch
7+
---
8+
9+
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
10+
11+
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.
12+
13+
| package | what an author can now write and see |
14+
| --- | --- |
15+
| `@object-ui/layout` | `NavigationArea.label` — the sidebar area switcher's button and its tooltip |
16+
| `@object-ui/plugin-list` | `ViewTab.label` — the inline pill row, and the mobile dropdown's trigger and menu items |
17+
| `@object-ui/plugin-dashboard` | `DashboardWidget.title` — the widget card heading and its `title` attribute |
18+
| `@object-ui/plugin-designer` | `DashboardWidget.title` — the widget card and the preview tile |
19+
| `@object-ui/app-shell` | `ActionParam.label` **and** each `ActionParam.options[].label` |
20+
21+
**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.
22+
23+
Two consequences worth knowing:
24+
25+
- **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.
26+
- **`@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.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
"@object-ui/components": minor
3+
---
4+
5+
`element:record_picker` publishes `sort`, `limit` and `emptyText` as authoring
6+
inputs (objectui#4167).
7+
8+
All three were already READ by the renderer and declared by the contract — the
9+
renderer has passed `sort` into `$orderby` and `limit` into `$top` since the
10+
block existed, and `emptyText` decides the no-rows message — but none of them
11+
appeared in `inputs`, so every layer that reads a manifest said they did not
12+
exist. `packages/components/src/renderers/layout/page.tsx` builds the JSX-page
13+
compiler's prop whitelist from `getKnownTypes()` plus these `inputs`, so writing
14+
any of the three on a JSX page drew an `unknown-prop` warning from
15+
`sdui-parser/src/validate.ts` on a key the renderer then went on to honour.
16+
17+
That is objectui#3407's shape — honoured, undiscoverable — and this is the same
18+
repair objectui#3808 made for `record:details.hideFields` and objectui#3830 made
19+
for `element:record_picker.filter`. `@objectstack/spec` 17.0.0-rc.6 is what made
20+
it actionable: objectstack#5775 declared the three upstream, and the reverse
21+
direction of the console's registry parity gate went red demanding them the
22+
moment the pin moved — a red the previous exemption had predicted in writing and
23+
called "correct and wanted".
24+
25+
Each description documents the renderer's real behaviour rather than restating
26+
the schema, because that is the half an author cannot read off the contract:
27+
28+
- **`sort`** and **`limit`** are both overridden OUTRIGHT by a node-level
29+
`dataSource` binding (`dataSource.sort ?? sort`), not merged with it — so a
30+
node that carries a `dataSource` silently ignores them.
31+
- **`limit`** defaults to 50 in the renderer, not in the schema, and a record
32+
outside the limit cannot be picked at all with nothing in the control to say
33+
more exist.
34+
- **`emptyText`** is published as `string` against a contract of
35+
`string | Record< string, string >`: rc.6 widened it to `I18nLabel`, and this
36+
renderer passes the value straight into a text node with no locale resolution,
37+
so only the plain-string form renders today. The description says so rather
38+
than advertising a shape the renderer drops — the narrowed-type treatment
39+
objectui#3832 describes, with the render-site gap tracked in objectui#4163.
40+
41+
The console's `registry-inputs-spec-parity` suite also drops all twelve of its
42+
off-spec exemptions, which rc.6 obsoleted at once (objectstack#6776 declared
43+
`page:header.recordChrome` / `showStar` / `showCopyId`, `page:accordion.variant`
44+
and `page:tabs.tabStyle`; objectstack#5775 declared the `element:record_picker`
45+
trio and `children` on the four page containers). The forward direction of that
46+
gate now runs with no cover of any kind.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
---
2+
"@object-ui/types": minor
3+
"@object-ui/core": minor
4+
"@object-ui/react": minor
5+
"@object-ui/app-shell": minor
6+
"@object-ui/layout": minor
7+
"@object-ui/fields": minor
8+
"@object-ui/components": minor
9+
"@object-ui/plugin-designer": patch
10+
---
11+
12+
Stop declaring 14 symbols under names `@objectstack/spec` owns at `17.0.0-rc.6`
13+
(objectui#4167, objectstack#4115).
14+
15+
The rc.6 bump published nine names this repo already declared locally, on top of
16+
four that predate it — `check:spec-symbols` reported all thirteen at once, and a
17+
fourteenth (`GlobalFilterSchema`) appeared during the bump itself. Each was
18+
triaged on its own rather than blanket-renamed, because the right answer differs
19+
per symbol: five bind to the spec, three are renamed because the spec's
20+
same-named export means something else, five arrive by derivation, and one is a
21+
declared dialect with a written reason.
22+
23+
**Breaking for importers of `@object-ui/react`, `@object-ui/app-shell` and
24+
`@object-ui/types`** — three exported names changed, because the spec exports the
25+
same name for a *different* thing:
26+
27+
| package | was | now | what the spec's same-named export actually is |
28+
|:--|:--|:--|:--|
29+
| `react` / `app-shell` | `MetadataState` | `MetadataCacheState` | a metadata item's LIFECYCLE state — `'draft' \| 'active' \| 'deprecated' \| 'archived'` (`MetadataStateSchema`, `@objectstack/spec/system`) |
30+
| `react` / `app-shell` | `resolveI18nLabel` | `resolveKeyedI18nLabel` | a resolver for the INLINE per-locale map (`{ en: 'Owner', 'zh-CN': '负责人' }`) against a BCP-47 locale |
31+
| `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 |
32+
33+
`resolveI18nLabel` is the one where the collision had already started costing
34+
something. rc.6 widened `I18nLabel` from `string` to
35+
`string | Record< string, string >`, so the same authored value now reaches
36+
either resolver — and each answers wrongly, silently, for the other's input: the
37+
keyed one returns `undefined` for `{ en: 'Owner' }` (no `key`, no
38+
`defaultValue`), and the spec's reads `key` / `defaultValue` / `params` as locale
39+
tags. The rc.6 bump PR met this and aliased the spec's import as
40+
`resolveInlineI18nLabel` in five files, with hand-written comments at two of
41+
them. That is a review convention, which is what objectstack#4115 exists to
42+
replace with a rule — so `Keyed` is now the counterpart of that `Inline`, and the
43+
name says which vocabulary it resolves at every call site.
44+
45+
**Eleven keep their names and are now imported or derived from the spec** instead
46+
of re-declared: `DATE_RANGE_PRESETS`, `NavigationMode`, `AddressValue`,
47+
`BreakpointColumnMap`, `BreakpointOrderMap`, `KanbanConfig`, `CalendarConfig`,
48+
`GanttConfig`, plus the three renamed above at their new names.
49+
50+
**Four of the copies were losing information, not just duplicating it.**
51+
52+
- **`GanttConfig` declared six keys and called itself canonical; rc.6's
53+
`GanttConfigSchema` declares seventeen.** The eleven it never mentioned —
54+
`parentField`, `typeField`, `baselineStartField`, `baselineEndField`,
55+
`groupByField`, `resourceView`, `assigneeField`, `effortField`, `capacity`,
56+
`quickFilters`, `autoZoomToFilter` — are all read by
57+
`plugin-gantt/src/ObjectGantt.tsx`, through a local `GanttConfigEx`
58+
intersection that existed only because this type did not carry them. It now
59+
derives from the spec, with `timeSegments` (shift segmentation) as the one
60+
genuinely local extension; the schema is `$loose` upstream, so that key is
61+
legal metadata rather than a second dialect.
62+
- **`GanttConfig.tooltipFields` carried the comment "not part of the upstream
63+
GanttConfigSchema".** It is, as of rc.6, so the key now arrives from the spec.
64+
- **`AddressValue` declared five of the spec's seven parts** — `countryCode` and
65+
`formatted` were missing, under a comment already claiming to be "the part
66+
names of `AddressSchema`". The widget still renders five inputs; binding the
67+
type stops it from asserting the platform cannot store the other two, and makes
68+
the `{ ...address }` write-through say so.
69+
- **`DATE_RANGE_PRESETS` was `Object.keys(PRESET_RANGES)`,** a third copy of a
70+
vocabulary the spec extracted in objectstack#4614 precisely to collapse — its
71+
own doc comment names this module as one of the three. It is now the spec's
72+
array by reference, and the local date-macro bounds table is pinned complete
73+
against it with `satisfies`, so a preset the schema gains without bounds here
74+
is a compile error rather than a filter that validates clean and then selects
75+
nothing.
76+
77+
`NavigationMode` was one hop from the spec already (`NavigationConfig['mode']`);
78+
it is bound directly, with a both-directions type pin that it stays the same type
79+
as the config's own `mode`. `KanbanConfig` / `CalendarConfig` /
80+
`BreakpointColumnMap` / `BreakpointOrderMap` were exact hand copies of `$strict`
81+
schemas and are now re-exports — "still exact" is the argument for binding them,
82+
since a copy with nothing to protect can only drift.
83+
84+
`GlobalFilterSchema` is the one ALLOW entry. It is the same spread-composition
85+
dialect as `SelectOptionSchema` next to it, and it collided only because rc.6's
86+
new refinement forced `.extend()` to be respelled as a `.shape` spread — which
87+
moved a derivation the guard could see into an object literal it deliberately
88+
does not descend into. The dialect is unchanged and its three divergences are
89+
pinned; which side moves on the refinement itself is objectui#4165.
90+
91+
`@objectstack/spec` moves from `devDependencies` to `dependencies` in
92+
`@object-ui/layout`: its public type surface now references the spec.

‎QUICK_REFERENCE.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ that test tells you to edit this block. The one exception is called out on its r
113113

114114
- **Version:** 17.4.0 (the version every `@object-ui/*` manifest carries — they are one
115115
`fixed` group in `.changeset/config.json`, so a release moves all of them together)
116-
- **Spec:** `@objectstack/spec` ^17.0.0-rc.5 (declared by the root `package.json` and by
116+
- **Spec:** `@objectstack/spec` ^17.0.0-rc.6 (declared by the root `package.json` and by
117117
`apps/console/package.json`)
118-
- **Client:** `@objectstack/client` ^17.0.0-rc.5 (declared by `apps/console/package.json`
118+
- **Client:** `@objectstack/client` ^17.0.0-rc.6 (declared by `apps/console/package.json`
119119
and `packages/data-objectstack/package.json`)
120120
- **Node.js:** ≥ 22 (see root `engines.node`)
121121
- **pnpm:** ≥ 9 (the workspace pins `pnpm@10.31.0` via `packageManager`)

0 commit comments

Comments
 (0)