Repository navigation
fix(app-shell): the screen-flow runner applies the app's flows translation overlay - #10234
Conversation
…slation overlay FlowRunner drew a paused screen's heading and field labels/placeholders verbatim off the wire, so an app's `TranslationData.flows` copy reached no screen in any locale. The runner now overlays it: the heading through the spec's `resolveFlowScreenTitle`, the per-field copy through the spec's `FLOW_SCREEN_FIELD_COPY_KEYS` (imported, never retyped), each key falling back to the authored string on its own. The bundle is read out of the i18next resource tree the console already loads for the active language. Screen `description`, the runner chrome and `successMessage` stay untranslated, per the spec's flows face and the recorded rulings. The package's `@objectstack/spec` floor moves to ^17.3.0, the first release exporting the resolver family. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Rendered by an isolated review subagent spawned by this seat. It was fed only the card, the PR, the two trees and the spec resolver source. Its served tier was checked by this seat against the stamp in its transcript. Adopted by this seat. ① Derived judgments
② Semver level
Prose, sentence by sentence: TRUE.
Nothing to quote. ③ Boundary flags
Implemented-by: VERDICT: PASS Landing: draft until |
…w-runner-translation-overlay
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Fixes #5920
Clause-②: no
The screen-flow runner now draws a paused screen's heading and each field's
label/placeholderfrom the app'sTranslationData.flowsbundle. This is the runner half of objectstack#11287, applied client-side per the side pick recorded on the card.What changed
packages/app-shell/src/views/FlowRunner.tsxis the one producer of every runtime screen-flow dialog. All three hosts mount this component:useConsoleActionRuntime,RecordDetailViewand the developer Flow Runs page.ScreenViewis unchanged and receives the localized screen.resolveFlowScreenTitle(bundle, flowName, screen).FLOW_SCREEN_FIELD_COPY_KEYS, which is imported and never retyped. The field-copy address is typed by the spec'sTranslationData, so if the spec renames the path,type-checkfails here.I18nProvider'sloadLanguagepasses the payload throughtransformSpecTranslations, which forwardsflowsunchanged under the app namespace of the i18next resource tree. The runner reads that group back for the active language withgetResourceBundle. There is no second loader and no new fetch.ScreenSpec(state.screen/ the resume payload) and never the flow document, sotranslateFlowupstream is not an option here. The spec also exports no per-field resolver, so the runner mirrors the spec's non-empty-string rule for the field keys.packages/app-shell/package.json: the@objectstack/specfloor moves from^17.0.0to^17.3.0.pnpm-lock.yamlchanges by that onespecifier:line; resolution stays at 17.4.0. This is outside the claimed file surface, and required: the new runtime imports first ship in 17.3.0. Measured with the registry loader inscripts/check-spec-range-floors.mjsitself:./systemin 17.0.0, 17.1.0 and 17.2.0 exports neitherresolveFlowScreenTitlenorFLOW_SCREEN_FIELD_COPY_KEYS. The positive control,TranslationBundleandTranslationData, is present in all five 17.x releases, andlowestSatisfyingVersionreturns 17.3.0. Leaving the floor at^17.0.0would publish a range that admits three releases this package cannot load against (the objectui#5793 ruling: floors track reality).patch).Deliberately not changed:
description: it is outside the spec's flows face, and an off-specdescriptionin a bundle is ignored (pinned);FlowSchema.successMessage.No export is added or changed, and
FlowRunner's props are unchanged.Pins:
packages/app-shell/src/views/__tests__/FlowRunner.flowsTranslation-5920.test.tsxEach pin loads the bundle the way the console does (spec payload, then
transformSpecTranslations, thenaddResourceBundle) under the realI18nProvider.flowsentries gives a translated heading, field label and placeholder, and the authored English is gone.en, with the zh-CN bundle loaded, gives the authored copy unchanged.descriptionin the bundle is NOT drawn, and the dialog's accessible description stays authored. The translated heading on the same screen shows that the bundle is being read.Ablation
The fix was committed first (
577fa817). The ablation went throughablation-replace.mjsin WRAP mode with an automatic restore.const shown = localizeScreen(...)→const shown = screen;. Anchor count went 1 → 0, replacement count 0 → 1, and the blob changed9c48baa8a47e→b37d27312868.请填写以下必填字段:Opportunity Name, i.e. zh chrome around the English field name), pin 6 (at its translated-label assertion) and pin 7 (at its translated-heading control).9c48baa8a47e) andgit diff HEADis empty. The restore-leg run gives 7/7 green.dist/sits in the resolution path. The test imports../FlowRunnerfrom source, and the mutated file is not a build input of anything the test reads, so neither leg needed a rebuild.Verification at HEAD
577fa817Heavy runs went through the shared verify lock, from the repo root.
Tests and type-check:
pnpm exec vitest runon the new pin file: 7/7.FlowRunner/ScreenView/ScreenPreview/flowResponse, pluspackages/app-shell/src/views/__tests__/,useConsoleActionRuntime*,flow-envelope.ratchet,spec-symbol-parityand the consoleFlowRunsPagetest: 31 files / 351 tests passed.RecordDetailView.*host tests plus two flow-launch tests: 29 files / 224 tests passed.pnpm --filter @object-ui/app-shell type-checkexits 0, after building the@object-ui/app-shell^...closure.--listFilesOnlyshows bothFlowRunner.tsxand the new test are inside it.Gates, all exit 0:
check:control-bytes,check:new-line-citations(0 new),check-changeset-presence,check-changeset-no-major,check-changeset-fixedandcheck-changeset-overwrite;check:i18n-keysandcheck:i18n-drift;check:spec-symbolsandcheck:installed-pin-claims;check:lockfile-integrityandcheck:lockfile-dedupe;check:phantom-deps,check:unused-deps,check:self-import,check:esm-specifiersandcheck:unreferenced-sources;check:vi-mock-specifiers,check:vi-mock-inheritandcheck:vi-mock-override-shape;check:test-path-roots,check-type-check-coverage,check-lint-coverageandcheck-pre-install-import-graph;check:handler-key-readsandcheck:metadata-write-doors;check:changeset-claims(report-only). It names four pending changesets that mentionpnpm-lock.yamlorpackages/app-shell/package.json. I read each paragraph, and this diff falsifies none of them.ESLint: run with
--no-inline-configon the two changed source files (2 files in the--format jsonoutput). It found 0 errors and 4 warnings, and the same 4 rules fire on the base file (no-explicit-any×3,set-state-in-effect). The config enables no type-aware linting, so this diff cannot change a verdict on any untouched file.NOT MEASURED locally:
check:spec-floors: it needs the whole workspace built (release and nightly lane). Its registry loader was run directly, as described above.check:eager-closureandcheck:node-esm-load: they need the console build.@objectstack/spec/systemis already reached today throughmetadata-form-i18n, and the console'svendor-objectstackgroup folds every spec module into one eager chunk, so the expected delta is two small functions.descriptionmeasurement (feeds the widen-or-not ruling)FlowRunnerrendersScreenSpec.descriptionas RadixDialogDescriptioninsideDialogHeader, directly under the heading and above any alert and the fields. It usestext-sm text-muted-foreground, against the heading'stext-lg font-semibold, with no truncation. It is also the dialog's accessible description, announced on open (pin 7 asserts this). The StudioScreenPreviewdraws it too.c164186. 4 of the 6 author adescription: all three app-crmcrm_convert_lead_wizardscreens, and the app-todosuccess_screen.screen_already_converted,success_screen), where the description is the whole body under the heading.Task "{subject}" created successfully!. The executor interpolatesdescriptionon the server, and the wire carries neither the template nor the variables. A client-side overlay therefore cannot translate it without losing the per-run value. Widening the face to coverdescriptionwould need the overlay applied server-side, before interpolation.Ledger evidence (for the objectstack spec seat; the PM relays it, so nothing is posted on objectstack#11287 from here)
flows›children.screens: backed by this PR once it lands and ships. The reader isFlowRunner(localizeScreenviaresolveFlowScreenTitleplusFLOW_SCREEN_FIELD_COPY_KEYS), pinned by the test file above. The row'sauthorHintnamesconfig.title,fields[].labelandfields[].placeholder, and all three are now read. A title that falls back to the node label is covered by the same key.flows›children.label: NOT backed. Nothing in objectui drawsflows.FLOW.label. The dialog heading is the screen heading, and the completion toast names the flow's machine name (chrome, out of scope). This row staysplanned.Acceptance notes
{var}token inside it renders literally, because the wire title is already interpolated. 0 of 6 example headings carry a token today. This is recorded in theFlowRunnerheader.loadLanguagetransforms a spec payload only whenisSpecTranslationDatasees an object entry withfields. Otherwise the payload loads un-namespaced and no client resolver reads it. A named probe showed this: with the zh-CN payloadobjects.crm_lead.labelonly,useObjectLabel().objectLabeldrawsLead, while the same payload with one field label draws线索. A bundle that carries onlyflowswould be dropped the same way.Generated by Claude Code