Repository navigation
fix(app-shell): a flow action whose run pauses closes with the action's own successMessage (objectui#12234) - #12246
Conversation
…'s own successMessage A flow action that declares a successMessage stays silent at a screen pause, as ruled, and its run then completed on FlowRunner's terminal toast, which knew only the flow's words. The launch hosts now hand the action's message over with the run (ScreenFlowState.actionSuccessMessage), and the terminal-success toast reads it first, then the flow's own successMessage, then the flow-completed sentence. Claude-Session: https://claude.ai/code/session_01G5TSaEwbEQsKMjhzTB4rEd Co-authored-by: Claude <noreply@anthropic.com>
The runner's toast sink passes an options bag beside the text and FlowRunner passes none, so the host pins compare the texts in order. Claude-Session: https://claude.ai/code/session_01G5TSaEwbEQsKMjhzTB4rEd 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
|
…lowState member The exported ScreenFlowState type gains an optional member, which widens a published surface (Clause-2 yes per the claim amendment), so the app-shell bump is minor rather than patch. Claude-Session: https://claude.ai/code/session_01G5TSaEwbEQsKMjhzTB4rEd 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: Read-only inputs: card objectui#12234 (body; claim ① Derived judgments
② Semver level
③ Boundary flagsOpen questions in report
Deviations (report and PR body): items 1 and 2 are the two questions above; item 3, Out-of-scope findings (noted in the PR, not filed):
Also flagged by the dev: HotCRM's flow source unreadable (judged at ① item 7) and seat 1's claims unread (answered by the amendment's reading). Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #12234
Clause-②: yes
Item 6 of the hosted-shape list card objectui#12211, split to its own card. Dispatched by the
domain:uiseat 2, sessionhttps://claude.ai/code/session_01G5TSaEwbEQsKMjhzTB4rEd.What was wrong
A flow action that declares a
successMessageshowed it only when its flow ran straight through. When the run paused on a screen, the action stayed silent at the pause (the ruled{ success: true, silent: true }), and the run then completed onFlowRunner's terminal-success toast. That toast knew only the flow's words: the flow's ownsuccessMessage, elseflowRunner.completed, which zh renders as 「流程「升级工单」已完成」. The action's message was never shown.What was measured before editing
origin/mainand on the hosted pind758f2f20d, the only call oft('flowRunner.completed', …)outside tests isFlowRunner's resume handler (resumeWith). That handler runs only after a launch paused on a screen. So the three reported case actions pause for input; they do not run straight through.judgeFlowLaunchreturns{ success: true, data, reload }for adonerun, and the action runner toasts the action'ssuccessMessage. The existing control'control: a COMPLETED run still toasts successMessage, …'in bothrefusedLaunch-9973pins already reads that.flowResponse.tsdrops nothing, so it is not edited.FlowRunnermount. One isuseConsoleActionRuntime(list views, SDUI pages, the declared-actions bar). The other isRecordDetailView, which renders an assigned record page such ascase_detail_pageinside its ownActionProviderand dispatches its header actions through its ownflowHandler. A case record's 升级工单 / 关闭工单 / 认领工单 run throughRecordDetailView. The fix therefore needs both hosts (see Deviations).useActionTextLocalizerbeforeexecute(the record header, the list view, the declared-actions bar,page:header,record:quick_actions). The runner hands that same action object to the handler and to its own toast sink. Soaction.successMessagein the flow handler is already the localized copy that a straight-through run toasts. No second localization path is added.The change
ScreenFlowStategains one optional member,actionSuccessMessage. It is the invoking action'ssuccessMessageas it reached the flow handler. Both hosts set it when a launch pauses (one line each).FlowRunner's terminal-success toast reads, in order: that message, the flow's ownsuccessMessage, then theflowRunner.completedsentence. A blank string, or anI18nLabelmap that no localizer resolved, counts as no message. This is the same rule the action runner applies to a rung.FlowSchema.successMessageis documented as replacing a generic "Done", not as outranking the action's copy.flowRunner.nextStep; a run that endsrefusedor fails after the pause shows no success toast;FlowSchema.successMessagestays off the translation surface.FlowRunner's header has a new section on this: "The invoking action'ssuccessMessagecloses the run (objectui#12234)".Deviations, and how the seat ruled them
Both were ruled in the seat's claim amendment
6124264136on objectui#12234.packages/app-shell/src/views/RecordDetailView.tsxjoins the file surface (option A, accepted at review). The edit adds one member to itssetScreenFlowcall. The testRecordDetailView.flowActionSuccessMessage-12234.test.tsxsits next to it. The claim's mechanism assumption placed the hand-off inuseConsoleActionRuntimealone. Measured, the reported reach goes through the record page's own host, so a change limited to the listed surface would leave the reported case as it is. Neighbour check, read at dispatch time: the in-flight objectui#12233 and objectui#12198 branches do not touchRecordDetailView.tsx. objectui#12223 edits only a comment inuseConsoleActionRuntime.tsx, in a region this diff does not touch. Seat 1's claims were not read, because a dev does not read the board.ScreenFlowStategains an optional member.FlowRunnerPropsis unchanged; the member rides the existingstateprop as per-run data, next torunIdandflowName.ScreenFlowStateis exported from the package entry, so this is an additive change to a published type: the PR's line isClause-②: yes, and the changeset (round 2,b3c549c3) declares@object-ui/app-shellminor. Existing values of the type stay valid.Tests (code at
a015abaa; round 2 atb3c549c3changes only the changeset)FlowRunner.actionSuccessMessage-12234.test.tsx(9 tests, real zh pack),useConsoleActionRuntime.flowActionSuccessMessage-12234.test.tsx(4 tests, action door, real runtime), andRecordDetailView.flowActionSuccessMessage-12234.test.tsx(2 tests, action door, real record page). Each asserts the full list oftoast.successtexts.packages/app-shell/srcandapps/console/srcwhose name matchesFlowRunner,useConsoleActionRuntime,flowResponse,flowLaunch,RecordDetailView,flow-envelope,FlowRunsPage,loadLanguage,FlowRefusal,DeclaredActionsBarorPageView., plusActionParamDialog.dialogReach-12080. Result:Test Files 88 passed (88),Tests 635 passed (635). The narrowing is by name. It reaches the two hosts, the runner, the launch and response modules, and theScreenFlowStateconsumers inapps/console. CI runs the full suite.FlowRunner's toast, throughablation-replace.mjs(anchor 1 to 0, blob changed, restored to the HEAD blob with an emptygit diff HEAD). The predicted direction held: 5 red, 10 green. The red pins are the 3 terminal pins in the FlowRunner file and the paused pin in each host. The red message names the reported sentence:expected [ [ '流程「升级工单」已完成' ] ] to deeply equal [ [ '工单已升级' ] ]. The straight-through, refused, failed and control pins stayed green. A first attempt was refused by the tool as a no-op (the replacement was a substring of the anchor) and was restored without running; the delete form above is the run that counts.RecordDetailViewmutated, its pin went red (1 failed, 1 passed):expected [ 'Flow "Escalate" completed' ] to deeply equal [ 'Case escalated' ]. WithuseConsoleActionRuntimemutated, its paused pin went red (1 failed, 3 passed). Both restores were verified against the HEAD blob.Gates (local, at
a015abaa; the changeset gates were re-run atb3c549c3)pnpm --filter @object-ui/app-shell type-check: exit 0.tsc --listFilesOnly -p tsconfig.test.jsonlists all three new test files.pnpm --filter @object-ui/app-shell lint: exit 0, with 0 errors. The new test files addno-explicit-anywarnings only, in the same(toast as any)form their neighbours use.pnpm check:control-bytes,pnpm check:new-line-citations(0 new citation(s)),node scripts/check-changeset-no-major.mjs,node scripts/check-changeset-presence.mjs: exit 0.check:handler-key-reads,check:action-forward-parity,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:test-path-roots,check:changeset-claims,check:pending-changeset-literals,check:i18n-keys,check:unreferenced-sources: exit 0.turbo run build --filter='@object-ui/app-shell^...' --concurrency=2, with 28 of 28 tasks successful.Acceptance notes (boundaries; none of these is filed)
${result.*}tokens. The spec declares that interpolation fortype: 'api'andtype: 'script'actions only, so a flow action's message is plain text. A straight-through run still goes through the action runner, which fills tokens for any action.successMessagestill toasts the runner's default text, not the flow's ownsuccessMessage. This predates the PR.FlowSchema.successMessagenames the screen-flow UI as its toast consumer, so the console promises nothing more for that path.ActionDef.toast.showOnSuccess(not a spec key) was never applied toFlowRunner's terminal toast, and this PR does not change that.Generated by Claude Code