Skip to content

fix(app-shell): a flow action whose run pauses closes with the action's own successMessage (objectui#12234) - #12246

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-12234-flow-action-success-message
Oct 12, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-12234-flow-action-success-message

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #12234
Clause-②: yes

Item 6 of the hosted-shape list card objectui#12211, split to its own card. Dispatched by the domain:ui seat 2, session https://claude.ai/code/session_01G5TSaEwbEQsKMjhzTB4rEd.

What was wrong

A flow action that declares a successMessage showed 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 on FlowRunner's terminal-success toast. That toast knew only the flow's words: the flow's own successMessage, else flowRunner.completed, which zh renders as 「流程「升级工单」已完成」. The action's message was never shown.

What was measured before editing

  • The sentence has one producer. On origin/main and on the hosted pin d758f2f20d, the only call of t('flowRunner.completed', …) outside tests is FlowRunner'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.
  • The straight-through path already works. judgeFlowLaunch returns { success: true, data, reload } for a done run, and the action runner toasts the action's successMessage. The existing control 'control: a COMPLETED run still toasts successMessage, …' in both refusedLaunch-9973 pins already reads that. flowResponse.ts drops nothing, so it is not edited.
  • There are two launch hosts, each with its own FlowRunner mount. One is useConsoleActionRuntime (list views, SDUI pages, the declared-actions bar). The other is RecordDetailView, which renders an assigned record page such as case_detail_page inside its own ActionProvider and dispatches its header actions through its own flowHandler. A case record's 升级工单 / 关闭工单 / 认领工单 run through RecordDetailView. The fix therefore needs both hosts (see Deviations).
  • Localization. Every surface that dispatches a declared action runs it through useActionTextLocalizer before execute (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. So action.successMessage in the flow handler is already the localized copy that a straight-through run toasts. No second localization path is added.
  • HotCRM's source is not readable from this session: the proxy answers 403 for that repository. The pause on those three actions is inferred from the single producer above, not read from their flow definitions.

The change

  • ScreenFlowState gains one optional member, actionSuccessMessage. It is the invoking action's successMessage as 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 own successMessage, then the flowRunner.completed sentence. A blank string, or an I18nLabel map that no localizer resolved, counts as no message. This is the same rule the action runner applies to a rung.
  • Precedence follows the card's ruling. Code reading found no recorded reason for the flow's message to win. The action's message comes first because that is what the same click shows when the run does not pause. FlowSchema.successMessage is documented as replacing a generic "Done", not as outranking the action's copy.
  • Unchanged and still pinned: the pause stays silent; an intermediate step still toasts flowRunner.nextStep; a run that ends refused or fails after the pause shows no success toast; FlowSchema.successMessage stays off the translation surface.
  • FlowRunner's header has a new section on this: "The invoking action's successMessage closes the run (objectui#12234)".

Deviations, and how the seat ruled them

Both were ruled in the seat's claim amendment 6124264136 on objectui#12234.

  1. packages/app-shell/src/views/RecordDetailView.tsx joins the file surface (option A, accepted at review). The edit adds one member to its setScreenFlow call. The test RecordDetailView.flowActionSuccessMessage-12234.test.tsx sits next to it. The claim's mechanism assumption placed the hand-off in useConsoleActionRuntime alone. 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 touch RecordDetailView.tsx. objectui#12223 edits only a comment in useConsoleActionRuntime.tsx, in a region this diff does not touch. Seat 1's claims were not read, because a dev does not read the board.
  2. The exported type ScreenFlowState gains an optional member. FlowRunnerProps is unchanged; the member rides the existing state prop as per-run data, next to runId and flowName. ScreenFlowState is exported from the package entry, so this is an additive change to a published type: the PR's line is Clause-②: yes, and the changeset (round 2, b3c549c3) declares @object-ui/app-shell minor. Existing values of the type stay valid.

Tests (code at a015abaa; round 2 at b3c549c3 changes only the changeset)

  • New: FlowRunner.actionSuccessMessage-12234.test.tsx (9 tests, real zh pack), useConsoleActionRuntime.flowActionSuccessMessage-12234.test.tsx (4 tests, action door, real runtime), and RecordDetailView.flowActionSuccessMessage-12234.test.tsx (2 tests, action door, real record page). Each asserts the full list of toast.success texts.
  • Narrowed run: every test file under packages/app-shell/src and apps/console/src whose name matches FlowRunner, useConsoleActionRuntime, flowResponse, flowLaunch, RecordDetailView, flow-envelope, FlowRunsPage, loadLanguage, FlowRefusal, DeclaredActionsBar or PageView., plus ActionParamDialog.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 the ScreenFlowState consumers in apps/console. CI runs the full suite.
  • Ablation 1: delete the action rung from FlowRunner's toast, through ablation-replace.mjs (anchor 1 to 0, blob changed, restored to the HEAD blob with an empty git 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.
  • Ablation 2: delete the hand-off line in each host separately. With RecordDetailView mutated, its pin went red (1 failed, 1 passed): expected [ 'Flow "Escalate" completed' ] to deeply equal [ 'Case escalated' ]. With useConsoleActionRuntime mutated, 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 at b3c549c3)

  • pnpm --filter @object-ui/app-shell type-check: exit 0. tsc --listFilesOnly -p tsconfig.test.json lists all three new test files.
  • pnpm --filter @object-ui/app-shell lint: exit 0, with 0 errors. The new test files add no-explicit-any warnings 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.
  • Hand-derived from the diff: 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.
  • Dependency closure built first: turbo run build --filter='@object-ui/app-shell^...' --concurrency=2, with 28 of 28 tasks successful.

Acceptance notes (boundaries; none of these is filed)

  • The terminal toast inserts the action's copy as written. It does not fill ${result.*} tokens. The spec declares that interpolation for type: 'api' and type: '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.
  • A straight-through run whose action declares no successMessage still toasts the runner's default text, not the flow's own successMessage. This predates the PR. FlowSchema.successMessage names the screen-flow UI as its toast consumer, so the console promises nothing more for that path.
  • The runtime-only ActionDef.toast.showOnSuccess (not a spec key) was never applied to FlowRunner's terminal toast, and this PR does not change that.

Generated by Claude Code

…'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>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 70 chunks) 3129.0 KB 3204.6 KB
Main entry chunk (gzip) 73.1 KB 350 KB
Entry file index-yY0jRWc9.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 20.69KB 7.62KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 588.51KB 141.71KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 244.49KB 68.27KB
fields (index.js) 279.96KB 71.37KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.26KB 11.71KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 54.31KB 15.86KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.72KB 48.07KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.77KB 50.02KB
plugin-detail (index.js) 251.02KB 66.27KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 178.37KB 46.27KB
plugin-gantt (index.js) 178.63KB 45.19KB
plugin-grid (index.js) 255.09KB 70.91KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 123.35KB 31.54KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 92.11KB 23.27KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…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>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 70 chunks) 3129.0 KB 3204.6 KB
Main entry chunk (gzip) 73.1 KB 350 KB
Entry file index-yY0jRWc9.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 20.69KB 7.62KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 588.51KB 141.71KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 244.49KB 68.27KB
fields (index.js) 279.96KB 71.37KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.26KB 11.71KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 54.31KB 15.86KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.72KB 48.07KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.77KB 50.02KB
plugin-detail (index.js) 251.02KB 66.27KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 178.37KB 46.27KB
plugin-gantt (index.js) 178.63KB 45.19KB
plugin-grid (index.js) 255.09KB 70.91KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 123.35KB 31.54KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 92.11KB 23.27KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: b3c549c3155b448f9d7cb0752bf8612b57fd5819
Local-runs: none

Read-only inputs: card objectui#12234 (body; claim 6123396297; reports 6124232882 and 6124310928; amendment 6124264136), parent objectui#12211 item 6 and triage 6121957396, PR #12246 (body, the 7-file list, both bot comments), the net diff of claude/issue-12234-flow-action-success-message against main at their merge-base (fetched into a review ref; 7 files, +636/-5), and the 33 check-runs on the head: 30 success, 3 skipped (the two coverage legs and dependabot, which skip on a PR run by design), 0 failures, none in progress. Nothing was checked out, built, run or re-run.

① Derived judgments

  1. Published type widened — right, and declared. ScreenFlowState (FlowRunner.tsx) gains the optional member actionSuccessMessage?: string. The type is re-exported by views/index.ts and by the package entry src/index.ts, which is the types target of @object-ui/app-shell's exports["."], so it is on the published surface; FlowRunnerProps.state is typed on it, so the component's accept set widens through that prop even though FlowRunnerProps is textually unchanged. Additive only: every existing value of the type stays valid. This is a Clause-②: yes widening, no arm, and takes at least minor — which the head carries (② below).
  2. Terminal-toast precedence — right. The resume handler now reads the invoking action's message, then outcome.successMessage, then flowRunner.completed. For a caller that does not set the new member (apps/console's FlowRunsPage, every external host) the branch is byte-identical to main, so nothing published narrows. Action-first matches the card's acceptance (the action's declared successMessage shows when one is set) and the straight-through path, where the ActionRunner toasts the action's copy and the flow's own successMessage is never read. The spec (FlowSchema.successMessage, flow.zod.ts) binds the runner to show the flow's message instead of a generic Done, not ahead of the action's copy; no spec text is contradicted.
  3. Rung rule is parity, not a new dialect — right. invokingActionMessage counts the copy only when it is a non-empty string; ActionRunner.composeSuccessMessage applies the same test (typeof copy === 'string' && copy.trim() !== '') to its own rungs, and both skip an unresolved I18nLabel map rather than hand an object to the toast. One rule, two readers; not a lenient consumer fallback.
  4. Both launch hosts hand over; nothing dropped — right. useConsoleActionRuntime.tsx and RecordDetailView.tsx each add one member to the existing setScreenFlow call at the screen follow-up. flowResponse.ts is untouched and needs no edit: judgeFlowLaunch's done arm returns { success: true, data, reload } and the runner toasts the action's copy, so the straight-through path already worked. The only other setScreenFlow caller on the branch is apps/console/.../FlowRunsPage.tsx, which launches flows with no invoking action, so leaving it alone is correct.
  5. Pinned behaviour holds — right. The pause still returns { success: true, silent: true } (flowResponse.ts, untouched) and both host tests assert no success toast at the pause; refused still sets the Close-only notice and returns before the toast branch, asserted in the FlowRunner and runtime tests with no toast of either colour; FlowSchema.successMessage is still consumed as the served string, with no i18n or bundle code touched; the flowRunner.nextStep toast between screens is unchanged and pinned.
  6. The localization claim — right. useActionTextLocalizer collapses successMessage to one string for the active language before dispatch on every declared-action surface the body names, verified by call site (RecordDetailView, ObjectView, DeclaredActionsBar, RelatedRecordActionsBridge, page:header, record:quick_actions, record:related_list), so the handler's action.successMessage is already the copy the straight-through toast shows. Where a dispatcher forwards the authored value without that hook (the SDUI action:button), both paths receive the same value and apply the same rung rule, so they cannot disagree — pre-existing either way.
  7. Evidence inference — right. The only non-test producer of flowRunner.completed on main is FlowRunner's resume handler (the locale files name the key in comments only), so the reported sentence can only come from a run that paused; HotCRM's flow source being unreadable from the dev's session does not weaken the fix.
  8. Tests — right. Three new files, 15 cases, each asserting the full toast.success call list so a second toast cannot hide; the FlowRunner file runs under the real zh pack with the reported sentence as its control; the two host files drive the real runtime, handler and runner. Placement follows the siblings (views/__tests__/FlowRunner.*, views/RecordDetailView.*.test.tsx). On the head: 12 test shards, Test (dist pins), Type Check and Lint are all success.
  9. Scope — right. No governed surface, no spec key, no new entry in FlowRunnerProps, 641 changed lines; the PR is draft and unarmed. Nothing here raises a landing question for this record.

② Semver level

  • .changeset/12234-flow-action-success-message.md declares '@object-ui/app-shell': minor, which matches ① item 1: a published-type widening takes at least minor, and objectui's policy (AGENTS.md §9 版本号策略) forbids major. The body names the additive public type change, the member, and that existing values stay valid. Not patch (the claim's original level, corrected by the amendment and by round 2) and not skip-changeset (six source files of one released package changed). Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read, Changeset Fixed Group Check and Changeset Overwrite Report are all success on the head.
  • The PR body's declaration: line 2 is its only line-initial declaration and reads Clause-②: yes with no arm; the one other mention sits mid-sentence in Deviations item 2 and is not read as a declaration. yes is right (① item 1) and consistent with minor; the amendment corrected the claim's no.
  • No ADR-0087 marker is needed and none is present: nothing is removed, renamed or narrowed.

③ Boundary flags

Open questions in report 6124232882, both ruled in amendment 6124264136:

  1. RecordDetailView.tsx outside the claimed surface → option A, accepted. Right: the reported case actions run through the record page's own flow handler and FlowRunner mount, so a fix limited to the listed surface would not reach the reported defect; the edit is one member on an existing call plus a co-located test. The dev declared the breach instead of deciding it alone, and the seat ruled. The neighbour reading (no other open claim names the file) is the seat's record; nothing in the inputs contradicts it.
  2. ScreenFlowState optional member versus the claim's no-new-prop rule → accepted as a published-type widening. Right as a classification: the member rides the existing state prop, the prop's accept set did widen, and the amendment resolved it the correct way — declared as Clause-②: yes with minor, not avoided. Nothing hidden.

Deviations (report and PR body): items 1 and 2 are the two questions above; item 3, flowResponse.ts not edited, is confirmed on the tree (① item 4); item 4, worktree handling, is procedural with no bearing on the diff.

Out-of-scope findings (noted in the PR, not filed):

  • ${result.*} tokens are not filled on the paused path. Accepted as a boundary: the spec declares that interpolation for type: 'api' and type: 'script' actions only, so a flow action's successMessage is plain text by contract. No card.
  • A straight-through action-door launch whose action declares no successMessage shows the runner's default, never the flow's own successMessage. Confirmed on the tree: judgeFlowLaunch's done arm drops outcome.successMessage, so the console never consumes the flow's message on that path. Pre-existing and outside this card's acceptance, so not a FAIL reason. Escalated, not filed: the spec text says the message is carried on every terminal run and calls the screen-flow toast one consumer, not the whole contract, so whether the console should show it on the straight-through path is a product reading for the dispatching seat to carry to a card or the decision box.
  • ActionDef.toast.showOnSuccess is not applied at FlowRunner's terminal toast. Pre-existing, and not reachable from authored metadata: the spec's action alias table maps an authored toast key to successMessage, so toast.showOnSuccess exists only on programmatic ActionDefs (the two in-tree users are url-type dispatches). No card.

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: claude/issue-12234-flow-action-success-message
Reviewed-by: session_01G5TSaEwbEQsKMjhzTB4rEd

VERDICT: PASS


Generated by Claude Code

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.

console: a flow-backed action's toast says 「流程「…」已完成」 instead of the action's declared successMessage (objectui#12211 item 6)

2 participants