Skip to content

fix(i18n): render I18nLabel objects at the 13 remaining sites - #4208

Merged
yinlianghui merged 6 commits into
mainfrom
claude/issue-4163-i18nlabel-render-sites
Aug 11, 2026
Merged

fix(i18n): render I18nLabel objects at the 13 remaining sites#4208
yinlianghui merged 6 commits into
mainfrom
claude/issue-4163-i18nlabel-render-sites

Conversation

@yinlianghui

@yinlianghui yinlianghui commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Fixes #4167

Part of #4163

Part 1 only for #4163 — the thirteen render sites. Part 2 (what Studio should offer for authoring a per-locale label) stays unclaimed and pending design, so #4163 is Part of rather than Fixes. #4167 is closed in full.

MAIN HEALTH — this PR is the rest of the rc.6 train, collapsed into one landing. PR #4169 (the rc.5 to rc.6 spec bump) merged to main at 38ab5054f and left main's CI red: Type Check (check:spec-symbols, 8 symbols) and Test shards 2/3 (spec-parity + doc tests). The fixes lived in the remaining train cars, which were stacked branches. This PR is no longer stacked — it is based on main and carries all of it.

What this PR now delivers

Three layers, in the order they were built:

  1. fix(deps): triage the 14 rc.6 spec-symbol collisions and refresh the console parity exemptions (#4167) #4189 / issue Track the @objectstack family at 17.0.0-rc.6 — the adaptation surface beyond what objectstack#7100 could carry #4167 — the 14 rc.6 spec-symbol collisions triaged, console parity exemptions refreshed, record-picker inputs published. This is what repairs main's check:spec-symbols red. PR fix(deps): triage the 14 rc.6 spec-symbol collisions and refresh the console parity exemptions (#4167) #4189's auto-merge was disarmed by the PM and its content lands here instead; that PR should be closed unmerged once this one lands.
  2. fix(i18n): render I18nLabel objects at the 13 remaining sites #4208 / issue I18nLabel now admits an inline per-locale map — audit every read the compiler cannot see, and give Studio a way to author one #4163 part 1 — the thirteen I18nLabel render sites. This is what repairs the resolveActionParams typetest TS2322s.
  3. New here — the rc.6 residual fixes that neither car covered, because the tests that catch them landed on main after the train was built. Detailed below.

The merge into main, and what conflicted

git merge origin/main (merge, not rebase — shared history with an open PR). Main had moved on by 3 commits, including #4222 and #4218. Five conflicts:

file resolution
packages/app-shell/src/layout/AppSidebar.tsx branch side. The train renamed the local resolver resolveI18nLabel to resolveKeyedI18nLabel (the #4167 collision triage); utils/index.ts and every other call site already carry the new name post-merge.
packages/app-shell/src/layout/UnifiedSidebar.tsx branch side, same rename.
packages/layout/package.json branch side. The train moved @objectstack/spec from devDependencies to dependencies (layout now imports the spec resolver at runtime); main still had it in devDependencies.
pnpm-lock.yaml branch side — one hunk, the exact mirror of the layout/package.json move. pnpm install then reported "Lockfile is up to date".
packages/fields/src/widgets/AddressField.tsx semantic conflict, resolved as a hybrid — see below.

The one conflict that was not a text conflict

Both sides changed AddressValue, in ways that were individually correct and jointly incoherent:

Taking either side alone loses the other's point, and taking main's side alone reopens main's red: the declaration simply moves to address-format.ts, where check:spec-symbols still sees a local declaration of a name the spec owns. The resolution keeps both — address-format.ts (the new home of the definition) imports the type from @objectstack/spec/data, and AddressField.tsx re-exports through that module rather than bare. The from clause matters: the guard deliberately skips a re-export carrying a relative path ("whatever it points at gets judged at its own declaration site") but treats a bare export type { AddressValue } as a second local declaration.

The residual red set — measured on the combined tree, before and after

Neither train car fixed these. Both were verified red on the merged tree before being fixed, and the reverse verification below puts them back.

1. QUICK_REFERENCE.md still quoted rc.5 — 3 failures. The "Current Release" block stated ^17.0.0-rc.5 for both @objectstack/spec and @objectstack/client, while all three manifests it names as its own anchors declare ^17.0.0-rc.6. Its pin test (scripts/__tests__/quick-reference-current-release-4143.test.ts) landed on main after the train was built, so no car ever ran against it. Fix: the two rows. The third failure was the block's own "no un-derived literal" assertion, which fell out with them.

2. recordDetailsInputs.spec-parity.test.ts:123 — 1 failure, and NOT about sections. The premise check found the real cause, which is worth stating because it is the opposite of what the failure looks like. Both fixtures in the object-sections case carried layout: 'custom'. layout was removed from record:details in spec 17.0.0 (objectstack#6946, ADR-0087 D2), and rc.6 gave the key a named never rejection:

expected: "never", code: "invalid_type", path: ["layout"]
message: `record:details` property `layout` was removed in @objectstack/spec 17.0.0

So the object-form fixture failed on layout while its sections were perfectly valid. Worse, and invisibly: the id-list fixture directly above it kept PASSING — it asserts only that the parse fails with code invalid_type, and layout's own rejection satisfies both without the parse ever reaching sections. That assertion was green for a reason unrelated to what it claims to test. Dropping the key puts both verdicts back on sections (issues now resolve to path sections.0 / sections.1), and the id-list case additionally pins the issue path, since a bare code cannot tell the two rejections apart. This is a test-fixture fix only; no plugin-detail source was touched.

The file's header prose was also updated — it opened "Under layout: 'custom' sections are the ONLY source of the body", a sentence about a key that no longer exists.


Local gate ladder

Run on the final tree (5f5543316). NODE_OPTIONS=--max-old-space-size=4096, --maxWorkers=2 / --concurrency=2, every heavy step under flock /tmp/os-heavy-verify.lock. Vitest invoked from the repo root (objectui#3378).

1. Build closure

$ pnpm exec turbo run build --concurrency=2
 Tasks:    44 successful, 44 total
  Time:    4m47.376s

2. Type-check — both commands

$ pnpm exec turbo run type-check --concurrency=2
 Tasks:    78 successful, 78 total
  Time:    2m58.327s

Ten packages define a tsconfig.typetests.json, and in every one the package script is tsc --noEmit && tsc -p tsconfig.typetests.json, so the 78/78 covers both commands rather than only the first. Explicit receipts for the three the change touches most directly:

$ (packages/app-shell)    npx tsc -p tsconfig.typetests.json   -> EXIT=0
$ (packages/plugin-detail) npx tsc -p tsconfig.typetests.json  -> EXIT=0
$ (packages/fields)        npx tsc --noEmit                    -> EXIT=0

3. check:spec-symbols — the gate that is red on main

$ node scripts/check-spec-symbol-derivation.mjs
✅  spec symbol derivation: 1229 files scanned against 4834 spec export names;
    14 declared dialects, 3 untriaged collisions in 1 packages.   EXIT=0

The 3 untriaged collisions are the pre-existing DEBT ledger, unchanged by this PR and non-fatal by design. For contrast, the same gate on the tree mid-merge, before the address-format resolution:

❌  a spec-named symbol is hand-written, not derived:
    • @object-ui/fields declares 1 spec-named symbol the spec already owns:
        re-export `AddressValue`  packages/fields/src/widgets/AddressField.tsx:17

4. Tests

$ pnpm exec vitest run --maxWorkers=2 packages/app-shell/ packages/react/ packages/types/ \
    packages/plugin-list/ packages/plugin-designer/ packages/layout/ packages/components/ \
    packages/plugin-dashboard/ packages/fields/ packages/core/ packages/plugin-detail/ \
    packages/data-objectstack/ scripts/
 Test Files  871 passed (871)
      Tests  10524 passed | 1 skipped (10525)
   Duration  954.99s

$ pnpm exec vitest run --maxWorkers=2 apps/console/
 Test Files  33 passed (33)
      Tests  345 passed (345)

apps/console is run separately and deliberately: it owns the repo-wide registry-inputs-spec-parity.test.ts gate, which the merge touched.

5. Reverse verification of the residual fixes

Predicted direction, stated before running: reverting the two residual files turns the quick-reference test red on 3 cases (Spec row, Client row, un-derived-literal) and recordDetailsInputs red on 1. Taken out with git checkout HEAD -- ... and a scoped patch file, never git stash. Confirmed exactly:

⎯⎯⎯ Failed Tests 4 ⎯⎯⎯
 FAIL  quick-reference-current-release-4143.test.ts > quotes the `@objectstack/spec` range the manifests declare
   AssertionError: QUICK_REFERENCE.md must state the spec range as "^17.0.0-rc.6"
 FAIL  quick-reference-current-release-4143.test.ts > quotes the `@objectstack/client` range the manifests declare
   AssertionError: QUICK_REFERENCE.md must state the client range as "^17.0.0-rc.6"
 FAIL  quick-reference-current-release-4143.test.ts > contains only version literals this test derives from a manifest
   AssertionError: ... states ["^17.0.0-rc.5","^17.0.0-rc.5"], which no manifest in this tree produced
 FAIL  recordDetailsInputs.spec-parity.test.ts > the spec really takes OBJECT sections
   AssertionError: expected false to be true

 Test Files  2 failed (2)
      Tests  4 failed | 13 passed (17)

One honest correction to the prediction: the reverted recordDetailsInputs failure lands on the original objectForm.success assertion, not on the new path assertion — reverting the file necessarily removes the new assertion along with the fix. Restoring returns to 17 passed (17).

6. ESLint on changed files

$ pnpm exec eslint <61 changed .ts/.tsx/.mjs files>
✖ 589 problems (0 errors, 589 warnings)

0 errors. The warnings are the pre-existing no-explicit-any convention in packages/types/src/objectql.ts and the _Assert… unused-type-alias pattern in the spec-symbol-batch6 type tests.

7. i18n + byte gates

$ pnpm run check:control-bytes
✅ check-control-bytes: OK (scanned 3933 tracked text file(s); skipped 85 binary).

$ pnpm run check:i18n-keys
Every in-scope call-site key resolves against the en pack (2811 keys), every literal inline
defaultValue matches the value the pack serves, every call site passes exactly the arguments
that value has holes for, and no call site carries a literal fallback beside itself.

$ pnpm run check:i18n-drift
Compared the ten locale packs at 6314e87f2 (merge-base with origin/main) with the working
tree: 0 en value(s) changed. No en value changed in this range.

8. Changesets

The train's three changesets are already in the branch and cover every released package this PR touches:

  • .changeset/spec-symbol-collisions-rc6-4167.md — minor for types, core, react, app-shell, layout, fields, components; patch for plugin-designer. @object-ui/fields is already covered here, and still accurately: the AddressValue re-sourcing it describes survives the merge, relocated into address-format.ts.
  • .changeset/record-picker-sort-limit-empty-text-4167.md — minor for components.
  • .changeset/i18nlabel-render-sites-4163.md — patch for the five render-site packages.

No new changeset: of the three residual fixes, one is a repo doc (QUICK_REFERENCE.md), one is test-only (plugin-detail), and the third is a re-export form change in @object-ui/fields whose published type is unchanged and whose package is already covered minor.


Original #4163 part-1 detail, retained

What changed upstream

@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. (Angle brackets spaced throughout this body so GitHub's sanitizer does not eat them; tsc printed them unspaced.)

PR #4169 repaired eight such sites. Thirteen more were invisible to it for a mechanical reason worth restating: the five packages involved build through vite/rolldown, not tsc, so turbo run build never type-checks their sources. Only turbo run type-check compiles them. A bump can therefore be build-green, test-green, and still carry a double-digit type-error count in exactly the packages that render labels.

package what an author can now write and see
@object-ui/layout NavigationArea.label — area switcher button + tooltip
@object-ui/plugin-list ViewTab.label — pill row, dropdown trigger, menu items
@object-ui/plugin-dashboard DashboardWidget.title — card heading + title attribute
@object-ui/plugin-designer DashboardWidget.title — widget card + preview tile
@object-ui/app-shell ActionParam.label and each ActionParam.options[].label

The two edges that are decisions, not mechanics

1. DashboardEditor.tsx:231 is an authoring WRITE, not a read. Resolving a map into a single-line text input and writing e.target.value back would collapse every other locale on the first keystroke — an author who opened a dashboard to move a widget and happened to focus the title field would silently destroy the translations. So the write is guarded, not cast: a map-valued title renders resolved and read-only, and the stored map survives an unrelated edit-and-save round trip untouched. The type error dies from the guard. This is the same conservative branch #4169 took on DashboardWidgetInspector, and it is a placeholder that cannot lose data — not the answer. The answer is #4163 part 2.

2. @object-ui/layout carries no i18n dependency by design. Its whole i18n story is injection (NavigationRenderer takes t and the label resolvers as arguments), and AppSchemaRendererProps exposes no locale, no t, and no context carrying one. So it calls the producer's own resolver — resolveI18nLabel from @objectstack/spec/ui — rather than hand-rolling a resolution that could drift, and runs it at the spec's documented "no locale known" default (en). Stated rather than hidden: an area labelled with a map renders its en entry, not the viewer's language. That is strictly better than [object Object], and it is a floor — the day a consumer needs per-viewer area labels, the fix is to thread a locale in as a prop, and this call is the one place it lands. No new package dependency.

Anti-drift: two resolvers over one vocabulary, now pinned

layout and app-shell call the spec's resolveI18nLabel; the three plugin packages sit inside objectui's i18n tree, already hold the live UI language, and call pickLocalized (which answers '' on a miss, which is what a text node wants, where the spec's answers undefined).

Two functions over one vocabulary is exactly the drift the ruling warns about, and the harm is not a crash — it is the same authored map rendering one locale on the runtime dashboard and a different one in the designer's preview of that same dashboard, with every type and every other test green. Nothing compared them, so this PR adds packages/plugin-list/src/__tests__/i18nLabel-resolver-parity.test.ts: all six limbs of the rule (exact tag, base, regional sibling, default, en, any), asserted on both functions from one table, with the single permitted difference (the miss spelling) normalized in one named place and pinned in both directions.

Also in this PR

Four plugin-list type-test assertions re-pointed 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 — passing on nothing. Same swap #4189 avoided for ThemeInput/Theme, different package. Invisible until now because type-check is tsc --noEmit && tsc -p tsconfig.typetests.json and the && never reached that project while TabBar.tsx was red.


Generated by Claude Code

claude added 4 commits August 10, 2026 15:17
…pec 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
… 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
…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
`@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
@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Aug 11, 2026 5:09am

Request Review

claude added 2 commits August 11, 2026 04:31
…nlabel-render-sites

# Conflicts:
#	packages/app-shell/src/layout/AppSidebar.tsx
#	packages/app-shell/src/layout/UnifiedSidebar.tsx
#	packages/fields/src/widgets/AddressField.tsx
#	packages/layout/package.json
#	pnpm-lock.yaml
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.
@yinlianghui
yinlianghui changed the base branch from claude/issue-4167-rc6-adaptation-remainder to main August 11, 2026 05:11
@yinlianghui
yinlianghui marked this pull request as ready for review August 11, 2026 05:13
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit bb68488 Aug 11, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Track the @objectstack family at 17.0.0-rc.6 — the adaptation surface beyond what objectstack#7100 could carry

2 participants