Repository navigation
feat(app-shell): the flow runner names the flow by its served label, translated (objectui#11092) - #11636
Conversation
…translated FlowRunner resolves the flow's display name as the active language's `flows.FLOW.label`, then the served `AutomationResult.flowLabel`, then the API name, and draws it above the screen heading and in the completion toast. `interpretFlowResponse` lifts `flowLabel` once (paused and done arms, and the screen follow-up), `ScreenFlowState` carries it, and the three hosts that open the runner (the list-action runtime, the record page and the Flow Runs test panel) pass it through. Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL Co-authored-by: Claude <noreply@anthropic.com>
✅ 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: Inputs read, and nothing else: card #11092 (body and all five comments: the triage grade 5890788541, the hold release 5985889871, the claim 5986377350, the os-dev-report 5987179410 and the ACCEPT 5987195646; the last two were treated as the dev's and the dispatching seat's own statements and re-derived from the diff, not adopted), PR #11636 (body, its 11-file list, and the net diff against its merge-base with ① Derived judgments
② Semver level
③ Boundary flags
Dev flags (os-dev-report
Check-runs on the head: 43 in total, 40 Implemented-by: VERDICT: PASS |
|
Generated by Claude Code |
Fixes #11092
Clause-②: yes
The objectui half (stage 2) of objectstack-ai/objectstack#20318: the screen-flow runner reads the app bundle's
flows.FLOW.labeland the servedflowLabel, and names the flow by them.What changes
FlowRunnerresolves the flow's display name in the card's order: the active language'sflows.FLOW.label, then the servedflowLabel, thenflowName. The first two steps are the spec's owntranslateFlow(@objectstack/spec/system), handed the served label as the authored literal and fed the sameactiveFlowsBundleread thatlocalizeScreenalready uses. There is no second loader and no hand-typed bundle address.flowparameter of the completion toast (flowRunner.completed), in place of the API name. No i18n key is added or changed.interpretFlowResponseliftsflowLabelonce (servedFlowLabel, atypeofread of the one member the contract declares) onto itspausedanddonearms, andjudgeFlowLaunchcarries it on thescreenfollow-up. Both app-shell launch hosts pass it intoScreenFlowState. On resume, a paused answer replaces the runner's held label, and the toast names the flow from thedoneanswer's label.FlowRunsPage(the developer Test Run panel) passes the trigger answer'sflowLabelon both of its open paths: Run, and Continue run.Landing site
As the dispatch expected:
packages/app-shell/src/views/FlowRunner.tsx,packages/app-shell/src/utils/flowResponse.ts, the two app-shell hosts (useConsoleActionRuntime.tsx,RecordDetailView.tsx), andapps/console/src/pages/developer/FlowRunsPage.tsx, each with tests beside it.Dispatch assumptions, measured
@objectstack/specis 17.6.0 (lockfile andnode_modules). Itsdist/contracts/index.d.tsdeclaresflowLabel?: stringonAutomationResult, and the trigger response schema indist/automation-api.zod-*.d.tscarriesflowLabelas an optionalz.ZodString. Every new read is typed by the contract (PickofAutomationResulton the keyflowLabel, or its indexed type). Nothing re-declares the field.FlowRunsPagebuilds aScreenFlowStateat two sites fromclient.automation.execute. In the installed@objectstack/client17.6.0 that call resolves to the unwrappedAutomationResultfrom the same trigger door, so it carriesflowLabel. Threaded at both sites.interpretFlowResponseis where both launch hosts and the resume classify an answer. It is internal (not exported from the package entry), so lifting there widens no published type. Therefusedarm is deliberately not lifted, because nothing it opens names the flow: the launch refusal notice is titled with the action's own label.flow, and no new key is needed. The bundle access is the existingactiveFlowsBundle, read once per render and shared bylocalizeScreenand the label.text-xs font-medium text-muted-foreground) above theDialogTitle. The title stays the step's own heading (or theflowRunner.titlefallback), so it is still the dialog's accessible name and every existing heading pin holds. I considered showing the label in place of theInputfallback on untitled screens and did not take it, because that changes the accessible name and leavesflowRunner.titleeffectively unreachable.Widened public surface (Clause-②)
ScreenFlowState, exported from@object-ui/app-shell, gains one optional member:flowLabel. The interface now extendsPickofAutomationResulton the keyflowLabel, so the member is an optional string and carries the contract's own TSDoc. (Generic arguments are written out in words here because GitHub strips angle-bracket spans from PR bodies.)FlowRunnerProps.stateaccepts the new member through that type.Against an older backend
A backend older than objectstack-ai/objectstack#20633 serves no
flowLabel. The header line and the toast then show the API name, the chain's last step, unless the bundle translatesflows.FLOW.labelfor the active language. The existing toast pins (Flow "convert_lead" completed,Flow "quote_generation" completed) are this case, and they are unchanged.Tests
Pins:
FlowRunner.flowLabel-11092.test.tsx, besideFlowRunner.flowsTranslation-5920.test.tsx. Each case checks the header line and the toast. A zh-CN bundle gives the translation. Inen, the served label. For an untranslated flow (with the bundle live for the screen heading), the authored label. With no served label, the API name. A paused resume answer carries its label forward.flowResponse.test.ts: the lift onpausedanddone,undefinedfor an absent or non-string value, and thescreenfollow-up.useConsoleActionRuntime.refusedLaunch-9973,RecordDetailView.refusedLaunch-9973), and one inFlowRunsPage.test.tsxcovering both open paths.Runs (local, through the shared verify lock; CI runs the full farm):
5c65546, before the merge: the touched files, 5 files / 51 tests passed. The wholepackages/app-shell/package: 1001 files passed and 1 skipped; 9941 tests passed and 9 skipped.pnpm --filter @object-ui/app-shell type-checkexit 0, and the test program includes all four touched app-shell test files (--listFilesOnly).pnpm --filter @object-ui/app-shell lint: 0 errors, and no warning on any of the 385 lines this diff adds. Console:type-checkexit 0,lint0 errors and no warning on the 34 added lines, andFlowRunsPage.test.tsxplusloadLanguage.test.tsx2 files / 8 tests passed.origin/main(1c2e2c4, objectui#11624) as merge commit6950ab0: the dependency closure was rebuilt for the merged packages. The touched files plusloadLanguage.test.tsx: 6 files / 56 tests passed. Bothtype-checks exit 0, and consolelint0 errors. The wholepackages/app-shell/rerun: 1000 files passed and 1 failed, with 9940 tests passed. The one failure isAppContent.deniedVsUnpublished.test.tsx, which this diff does not touch. Its network-escape guard tripped on a reallocalhost:3000/api/v1/metaread, the file installs no fetch double, and run alone at6950ab0it passes 15/15. It also passed in the first whole-package run. See Acceptance notes.6950ab0, each run with its own verdict line:check:new-line-citations(0 new),check:control-bytes,check:i18n-keys,check:changeset-claims,check:pending-changeset-literals,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:test-path-roots,check:phantom-deps,check:installed-pin-claims,check:spec-symbols,check:esm-specifiers,scripts/check-changeset-presence.mjsandscripts/check-changeset-no-major.mjs: all exit 0.scripts/check-governed-queue-guard.mjs --testover the 11 changed paths answers NOT GOVERNED.Ablation (one-shot, at
5c65546; nothing left in the tree)Each leg was run with
ablation-replace.mjs: a literal anchor that must hit once, the mutation and its marker counted on disk while the run held it, and a restore fromHEADproven by the blob hash and an emptygit diff HEAD. The expected red/green split was written down before each run.flowName(it ignores the bundle and the served label). 7 red: zh, en, untranslated and resume in the runner file, plus the three host pins. The no-label pin and the four lift pins stay green.undefined. 8 red: three lift pins, both app-shell host pins, and en, untranslated and resume in the runner file. The zh pin stays green because the bundle still wins,FlowRunsPagestays green because it does not route throughinterpretFlowResponse, and so do the no-label and absent/non-string pins.Acceptance notes
origin/mainwas merged once, as merge commit6950ab0. The source of this PR's own files is identical before and after the merge.translation.flows.<flow>.label(1 key) objectstack#20318 flips its ledger row. That flip is not part of this PR.AppContent.deniedVsUnpublished.test.tsxpasses alone but can trip the network-escape guard under whole-package load, because it never stubs the meta probe's fetch. This is outside this card's scope. carrier: none.FlowRunsPage,clientis typedany, so the new read there is as untyped as the page's existingstatus/runId/screenreads.Session:
https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL(dispatched by thedomain:uiseat 1 PM loop; claim comment 5986377350).Generated by Claude Code