Conversation
Converts 73 UI message catalogs from `{...} satisfies UiCatalog<T>`
(which requires every UI_LOCALES member, including a future `ko`) to
`resolveUiMessageCatalog(defineUiMessageCatalog<T>()({...}))`, where
only `en` is required and other locales are optional DeepPartial
overlays merged over it. This is the compat PR me2seeks asked for in
the apache#5100 review thread, ahead of that PR's `ko` core commit, so the
resolver migration is validated against the existing en/zh-CN/zh-TW
locales before any new locale lands.
No translated strings change. Every converted catalog was already a
complete Record<UiLocale, T>, so resolveUiMessageCatalog's merge
(translation wins when present, otherwise falls back to en) reproduces
the exact same values for every existing locale.
Two real issues surfaced during conversion, not just mechanical risk:
- astryx-i18n.tsx's OVERRIDES_BY_LOCALE was excluded from this PR. Its
`Overrides` type is self-locale-keyed (each locale's own value is
keyed by that same locale name, to match Astryx's override API
shape), which breaks mergeUiMessages: it walks the English
fallback's top-level keys ('en'), never finds that key in the zh-CN/
zh-TW override objects (whose top-level key is their own locale
name), and silently discards all of their content. Wrapping this
catalog would have been a real behavior regression, not a type-only
change, so it stays on `satisfies UiCatalog<Overrides>`.
- DeepPartial<T> and ExactMessageShape<T> (packages/core/src/ui-locale.ts)
decompose object types via `keyof`, which is `never` for a bare
function type, collapsing any function-valued catalog leaf (e.g. a
`(name: string) => string` copy formatter) to `{}` during inference
and losing its parameter types. This surfaced as TS7006 implicit-any
errors on catalogs with parameterized copy. Fixed per-catalog by
hoisting each locale's block into its own `const x: T = {...}`
(using the full, non-partial T, since these catalogs are already
complete for every locale) before passing it into
defineUiMessageCatalog, which sidesteps the generic inference path
entirely. Applied to host-handoff-copy.ts (manual) and 23 other
catalogs with function-valued leaves (scripted).
Verified: full workspace build, typecheck, and
scripts/check-locale-hygiene.mjs are clean; lint and format pass; the
full test suite passes (confirmed via a pristine-vs-modified
comparison — concurrent heavy workspace suites had been causing
unrelated timing flakiness in runtime-host's integration tests, not
this change).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Astro-Han
left a comment
There was a problem hiding this comment.
Reviewed f9428cb336205b703140f03823b594252f91188c (73 files, +927/−942, a single commit).
P2 — the test job fails on this head at the renderer architecture check, and the migration itself is what trips it.
Run 36976556208, step check:architecture: copy catalog validation failed: missing UiCatalog marker from @maka/core/ui-locale, reported across the migrated catalog files (shell-copy.ts, shell-remaining-copy.ts, settings-*-copy.ts, task-readiness-copy.ts, work-board-error-copy.ts, storage-usage-copy.ts, and more), together with dependencyPaths changed; expected {}, received {"@maka/core/ui-locale":1} for the same files. The migration replaces satisfies UiCatalog<T> with the new form, so the marker that the copy-catalog validation looks for is gone, and the committed architecture ledger no longer matches what the files import. The new form therefore needs to be either recognised by that check or accompanied by a regenerated ledger — a convention question worth settling on the PR rather than by hand-editing the ledger.
Some entries in the same failure are not attributable to this commit: src/renderer/platform/desktop/onboarding-snapshot-bridge.ts ("Desktop adapter imports unbudgeted renderer legacy code") and use-app-shell-session-list.ts (import counts) do not appear in this commit's diff, so they come from base-branch drift relative to the check's --base. Those should clear once the branch merges the current base.
On the three risks this review was asked about, the migration holds up.
- Key loss: mitigated by construction. Each migrated catalog keeps the same type argument:
satisfies UiCatalog<Record<GeneralizedErrorClass, string>>becomesdefineUiMessageCatalog<Record<GeneralizedErrorClass, string>>()({ … })(andresolveUiMessageCatalog(...)around it where the call site needs a resolved catalog). The interface that forced every locale to carry every key is still applied per locale, so a missing key remains a compile error rather than a runtime hole. - Placeholder and interpolation semantics: I checked the diff for changed message values rather than wrapper lines, and the message strings appear only as re-indentations of identical content, in all three locales. The type arguments are unchanged too, so a renamed or re-ordered placeholder that changed arity would have to show up in those types.
- The resolver itself is not modified here —
packages/core/src/ui-localeis untouched, so the resolution mechanism the catalogs migrate to is the existing one.
One change worth naming explicitly, because it is security-adjacent in a copy refactor: packages/core/src/redaction.ts is touched, but only its exported GENERALIZED_ERROR_COPY constant is re-wrapped into the new form with the identical strings and the identical type argument; the redaction logic itself is unchanged.
Gate on this head: test is red (the architecture step above); label is green.
What I could not judge
- I did not run the architecture checker or the workspace suites myself; the above is read from the failing job's per-file output plus the commit's diff.
- No renderer or Electron run, so the resolved output of the new form is taken from the type arguments and unchanged message data rather than observed.
I did not approve, request changes, or merge.
Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
The renderer architecture check admits a locales/*-copy.ts file as a validated copy catalog only when it carries a `satisfies UiCatalog<T>` marker. The resolver migration dropped that marker, so every catalog fell out of the admitted class and the ratchet reported their imports as unbudgeted legacy debt. Wrap each resolveUiMessageCatalog(...) result in `satisfies UiCatalog<T>` with the same type argument. This keeps the per-locale key contract on the resolved value and needs no change to the checker, which the base cross-check forbids a PR from loosening. Regenerate the ledger for the new @maka/core/ui-locale dependency edge. settings/provider-display-copy.ts stays on the old form: it lives outside locales/, so a runtime ui-locale import would be new debt. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
It lives outside locales/, so a runtime @maka/core/ui-locale import would be new debt under the architecture ratchet. Its exported types are unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Astro-Han
left a comment
There was a problem hiding this comment.
Reviewed 476cbe246acae79da589f48f1f53d62c1ac0c179. The delta since my previous review is a merge of main plus a large re-baseline of apps/desktop/renderer-architecture.json (164 lines).
The specific failure I reported is resolved; the test job is nevertheless red on this head for something else — 1×P2.
What I can verify directly: the previous run failed at check:architecture with copy catalog validation failed: missing UiCatalog marker from @maka/core/ui-locale plus dependencyPaths changed; expected {}, received {"@maka/core/ui-locale":1} across the migrated catalogs. Neither string appears in this head's failed log, the architecture ledger has been re-baselined, and the check now reports its rules passing (for example ✔ rejects unbudgeted legacy imports from every strict ownership zone). So the marker/ledger problem is closed.
What I cannot close: this head's test job still ends in failure. The retrieved log's failing step is the one that also attempts to upload apps/desktop/e2e/test-results/, which that step reports as not found ("No files were found with the provided path"), so the failure sits in the Desktop e2e area — but the log I have does not surface the failing spec or assertion, and I am not going to guess at it. The PR cannot land until that job is green on this revision, so this is a P2 on the gate rather than a claim about the migration.
Everything I verified about the migration itself still holds, since nothing in this delta touches it: each migrated catalog keeps the same type argument as the satisfies UiCatalog<…> it replaced, so per-locale key completeness is still enforced at compile time; the message values appear only as re-indentations of identical strings; and the resolver in packages/core/src/ui-locale is not modified here.
Gate on this head: test is red (as described); mergeable is true and the state is blocked.
What I could not judge
- The identity of the failing Desktop e2e case, since the log I retrieved does not contain it; whoever holds the job's report can settle it in one look.
- I did not run the workspace or e2e suites myself.
I did not approve, request changes, or merge.
Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
Astro-Han
left a comment
There was a problem hiding this comment.
Follow-up to the review above at the same head, identifying the test failure it could not pin down.
P2: Desktop typecheck fails on the migrated catalogs (run 36980276109, tsc -p tsconfig.renderer.json --noEmit). Every function-valued message in the migrated renderer catalogs now reports TS7006: Parameter '…' implicitly has an 'any' type, for example:
apps/desktop/src/renderer/locales/settings-data-copy.ts:52(created,overwritten,skipped),:56(label),:58(items),:67(error)settings-subagents-copy.ts:106(total),:113(name),:151(max)settings-daily-review-copy.ts:48,settings-web-search-copy.ts:43,:52
Under satisfies UiCatalog<T> these parameters were contextually typed from T. Through the new defineUiMessageCatalog<T>()({…}) / resolveUiMessageCatalog path they no longer are, so the compiler can no longer check that a message function's parameters match the catalog type. That weakens the guarantee the earlier review relied on: missing keys may still be caught, but a placeholder whose parameter was renamed or reordered would now type-check as any.
Fix: make the helper's parameter type flow into the object literal (for example, type the inner call's argument as T or UiMessageSource<T> rather than inferring it from the literal), then rerun npm run typecheck -w apps/desktop. Adding explicit parameter annotations at each call site would silence the errors but keep the weaker contract, so we would prefer the helper fix.
This is an automated review by Claude (Anthropic), run on behalf of the maintainer. It is not an independent human review.
|
Correction to my review above, and the actual cause of the red I said I would not guess at the failure, then went on to say the migration's type story still held. Two things need fixing, and the second is mine:
This remains a P2: the head cannot land until
|
…logs
DeepPartial and ExactMessageShape treated functions as plain objects and
mapped them to `{}`, so function-valued messages in non-en locales lost
their contextual signature and fell back to implicit any (TS7006).
Pass functions through both helpers unchanged, which restores parameter
type and arity checking for interpolated messages.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
Thanks for pinning this down — the cause was in the helper, as you suggested. |
Astro-Han
left a comment
There was a problem hiding this comment.
Reviewed ecb29e5199acbd1b363faa59e53bf9a436329597.
The P2 I raised — and publicly corrected myself about — is fixed, and the gate proves it. On this head the test job is completed/success, and that job is the one that previously failed at Desktop typecheck with thirty TS7006 implicit-any errors on the function-valued messages. Those errors are gone.
How it was fixed, read from the type machinery. ui-locale.ts now has an explicit MessageFunction notion and uses it in the shaping types: DeepPartial excludes message functions (so a function is no longer deep-partialled, which is what had destroyed its parameter types), and ExactMessageShape gained a MessageFunction branch that compares functions as functions rather than recursing into them. That is the right level to fix it: the loss was never in the catalogs, it was in the helper's shaping of function-valued entries, so the catalogs keep their unannotated arrow parameters and get their contextual types back.
So the sequence is: I reported the missing UiCatalog marker and the stale ledger, corrected my own claim when the real cause turned out to be the dropped contextual typing, and the fix has now landed at the level that cause pointed to. Nothing else about the migration changes — key presence is still constrained by the type argument, message values are unchanged re-indentations, and the resolver in packages/core is untouched.
Gate on this head: test completed successfully; no Grok involvement; mergeable is true.
What I could not judge
- I did not run the typecheck myself; the evidence is the job result plus the shaping types as written.
- I did not re-read the whole migration; this pass was scoped to the increment that addresses my finding.
I did not approve, request changes, or merge.
Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
…atalog-resolver Resolve executor-submission.ts: keep the resolver-based SUBMISSION_COPY and add the new `invalid` key from main to its expected shape. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
Incremental re-review of exact head f12dbb18. The previous reviewed head was ecb29e51, which had become CONFLICTING with main. The increment is a single merge commit, f12dbb18f (merge of upstream/main). It moves the merge-base from c7fa6bb6 to 7c90bac2, pulling in 14 main commits. The PR is unchanged in size: +711/-669 across 74 files.
Isolating the real change. I compared diff(c7fa6bb6, ecb29e51) with diff(7c90bac2, f12dbb18). They touch the same 74 files. Ignoring index and hunk-offset lines, the patches differ only in the two files where main's new content met the PR's transform. Main also changed three more of the PR's files; their resolutions are checked below.
apps/desktop/src/renderer/features/conversation/model/executor-submission.ts:50-66: main added aninvalidmessage for stale model/mode selections. The resolution addsinvalidto thedefineUiMessageCatalog<...>key type and keeps all three locale strings, now insideresolveUiMessageCatalog(...).executorSubmissionErrorstill returnsSUBMISSION_COPY[locale].invalid(:80). Correct.packages/ui/src/executor-model-picker.tsx: main renamedselectionFailedtomodeFailedand addedmode. The PR's wrap now applies to main'sExecutorCopyunchanged (defineUiMessageCatalog<ExecutorCopy>()). Correct.apps/desktop/src/renderer/locales/shell-copy.tsandsettings-external-agents-copy.ts: every line main added sincec7fa6bb6(13 and 3 lines) is present at the new head, in the hoisted resolver form.apps/desktop/renderer-architecture.json: the PR's@maka/core/ui-localedependency-path additions re-apply cleanly on top of main's changes.
No main-side change was dropped, and no PR-side transform was lost in the resolution.
Findings
- P3, carry-over (not introduced by this merge): one catalog is still on the old form.
apps/desktop/src/renderer/features/diagnostics/locales/diagnostics-copy.ts:52is still{...} satisfies UiCatalog<DiagnosticsCopy>. It arrived on main via #5892 (2026-10-02), likely after the PR's initial sweep. It was already unmigrated atecb29e51. It compiles and behaves correctly as is, but the description says everysatisfies UiCatalog<T>catalog is migrated. Its shape is a plain nested string object, with no self-locale keys likeastryx-i18n.tsxand noUiCatalog<string>leaf likeprovider-display-copy.ts, so the same mechanical wrap applies. The only remaining old-form catalogs are this file and the two documented exclusions (packages/ui/src/astryx-i18n.tsx:102,apps/desktop/src/renderer/settings/provider-display-copy.ts:54). Either migrate it or note it in the description.
CI and merge state: test passes on this head. The PR is MERGEABLE again (the earlier conflict is resolved); it is BLOCKED only on required review.
Verdict: the conflict resolution is correct. Only the P3 completeness nit remains; nothing blocks.
…e format Wrap the catalog in resolveUiMessageCatalog(defineUiMessageCatalog<DiagnosticsCopy>()(...)) like the other migrated catalogs. The file arrived on main via apache#5892 after the initial sweep and was the last plain-object catalog still on satisfies UiCatalog<T>. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
Thanks @Astro-Han — the P3 is addressed in 7962619: |
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
Incremental re-review of exact head 7962619c. The previous reviewed head was f12dbb18. The increment is one commit, 7962619c9 ("migrate diagnostics-copy to the resolver-based message format"). The merge-base is unchanged (7c90bac2), so the PR diff grows by exactly that commit: +714/-672 across 75 files.
Prior P3: resolved. apps/desktop/src/renderer/features/diagnostics/locales/diagnostics-copy.ts:20,30,52 now uses resolveUiMessageCatalog(defineUiMessageCatalog<DiagnosticsCopy>()({...})) satisfies UiCatalog<DiagnosticsCopy>. That matches the wrap in capability-reason-copy.ts, onboarding-copy.ts and the other migrated catalogs. The three locale objects are byte-identical, and getDiagnosticsCopy is unchanged. At this head, the only satisfies UiCatalog<...> catalogs not wrapped in the resolver are the two documented exclusions (packages/ui/src/astryx-i18n.tsx:102, apps/desktop/src/renderer/settings/provider-display-copy.ts:54).
Current main. Main has moved four commits past the merge-base (to 3597abe8). Two of them touch files this PR changes: apps/desktop/renderer-architecture.json, and settings-provider-copy.ts from #5866, which adds 11 default-model keys per locale. A trial git merge-tree of this head against 3597abe8 is clean. The merged settings-provider-copy.ts keeps all of #5866's new strings in zh-CN, zh-TW and en, inside the PR's resolveUiMessageCatalog(...) wrap. No new old-form catalogs arrive from main.
Findings: none.
CI and merge state: test is still running on this head (pending at review time). The PR is MERGEABLE and BLOCKED only on required review.
Verdict: the last P3 is addressed and the delta is a mechanical, correct wrap. Nothing blocks once CI is green.
Background
In the #5100 review thread (#5100 (comment)), me2seeks adopted the incremental Korean rollout approach I proposed and asked for this as a separate, preceding "catalog compat PR" against
main:{...} satisfies UiCatalog<T>(which hard-requires everyUI_LOCALESmember, including a futureko) to the existingdefineUiMessageCatalog/resolveUiMessageCatalogresolver pattern (packages/core/src/ui-locale.ts), whose only precedent today ispackages/cli/src/pi-tui-mcp-status.ts. Under this pattern onlyenis required; other locales are optionalDeepPartialoverlays merged over it.kotoUI_LOCALEShere —#5100stays the independent, revertible PR that does that.en/zh-CN/zh-TWbehavior byte-for-byte identical, and keepscripts/check-locale-hygiene.mjsgreen.#4515used and that is now off the table).This PR is that preceding compat PR. Intended order: this PR →
#5100→ the individual translation PRs.Scope
73 files — every catalog on
upstream/maintyped either{...} satisfies UiCatalog<T>orconst x: UiCatalog<T> = {...}(a second syntax the initialsatisfies-only grep missed, found by actually running the build). Every file gets the same mechanical transform:No translated string content changed — only the declaration wrapping.
One file is deliberately excluded:
packages/ui/src/astryx-i18n.tsx'sOVERRIDES_BY_LOCALE. ItsOverridestype is self-locale-keyed — each locale's own value is itself keyed by that same locale name (to match Astryx's override API shape), e.g.'zh-CN': { 'zh-CN': {...60 keys...} }.mergeUiMessageswalks the English fallback's top-level keys ('en'), never finds that key inside the zh-CN/zh-TW override objects, and silently discards all of their content, falling back to the 2-key English stub. I caught this through the test suite (gives collapsible plaintext code a localized accessible namestarted failing), root-caused it to this merge-semantics mismatch, and left the file on its originalsatisfies UiCatalog<Overrides>form — wrapping it here would have been a real behavior regression, not a type-only change.A real type-inference bug in the resolver helpers
DeepPartial<T>andExactMessageShape<Actual, Expected>(both inpackages/core/src/ui-locale.ts) decompose object types viakeyof.keyofon a bare function type isnever, so any catalog property whose value is a parameterized copy function (e.g.descriptions: (targetName: string) => ({...})) gets collapsed to{}during generic inference, and TypeScript can no longer contextually type that inline arrow function's parameters — surfacing asTS7006: implicitly has an 'any' type.Fixed per-file by hoisting each locale's block into its own fully, non-partially typed
const x: T = {...}before passing it intodefineUiMessageCatalog, which sidesteps the broken generic-inference path (the value is now a concrete, already-typed expression, not a literal needing contextual typing through the generic). This is safe because every catalog converted here was already a completeRecord<UiLocale, T>, so typing every locale block with the fullT(notDeepPartial<T>) changes nothing semantically. Applied by hand topackages/runtime-host/src/client/host-handoff-copy.tsand via a small one-off ts-morph script to 23 other catalogs with function-valued leaves (conversation-copy.ts,tool-activity/copy.ts,settings-health-copy.ts, and others — full list in the diff).I did not touch
DeepPartial/ExactMessageShapethemselves; that's shared core infrastructure outside this PR's "mechanical wrap only" scope, and worth its own discussion if the project wants a cleaner upstream fix inui-locale.tsfor this edge case.Verification
npm run build(full workspace) — cleannpm run typecheck(full workspace) — clean. The one remaining error (apps/desktop/src/preload/runtime-host-session-catalog.ts,TS2339: Property 'id' does not exist on type 'DesktopSessionSummary') is pre-existing and untouched by this PR — confirmed viagit diffshowing zero changes to that file.node scripts/check-locale-hygiene.mjs --base <branch point>— passednpm run lint/npm run format:check(biome) — clean@maka/runtime-host's integration tests (UDS client/session/continuation durability, host election timing); a pristine-vs-modified comparison under matching narrow scope showed the same tests pass cleanly on both pristineupstream/mainand this branch, confirming those were CPU-contention artifacts from running multiple heavy workspace suites concurrently, not a regression from this change.Record<UiLocale, T>before this change, soresolveUiMessageCatalog's merge (translationvalue wins whenever present, otherwise falls back toen) reproduces the exact same value for every existing key in every existing locale.Non-goals
UI_LOCALESis unchanged;kois not added here (#5100's job).localeOptions(adding thekoselector entry) is scs0209's separate PR, untouched here.#5011's tree is untouched.🤖 Generated with Claude Code