Skip to content

Commit 134043a

Browse files
os-zhuangclaude
andauthored
feat(automation): declarative screen-flow completion/error messages + action errorMessage (#1990)
- FlowSchema gains `successMessage` / `errorMessage`; the engine attaches them to the terminal AutomationResult (success → successMessage, failure → errorMessage) so a screen-flow runner shows a meaningful toast instead of "Done"/raw error. - AutomationResult contract gains `successMessage` / `errorMessage`. - UI Action schema gains `errorMessage` (runtime already honoured it — closes a spec↔runtime gap). - CRM convert-lead wizard declares a friendly completion + error message. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 768331d commit 134043a

6 files changed

Lines changed: 49 additions & 3 deletions

File tree

.changeset/flow-action-messages.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
"@objectstack/spec": minor
3+
"@objectstack/service-automation": minor
4+
---
5+
6+
feat(automation): declarative screen-flow completion/error messages + action `errorMessage`
7+
8+
A screen flow can now declare `successMessage` / `errorMessage` (FlowSchema). The
9+
engine surfaces them on the terminal `AutomationResult` (`successMessage` on
10+
success, `errorMessage` on failure), so the UI flow-runner shows a meaningful
11+
toast instead of a generic "Done" / the raw error — no manual "success screen"
12+
node needed. The CRM convert-lead wizard sets a friendly completion message.
13+
14+
Also exposes `errorMessage` on the UI Action schema. The runtime (ActionRunner)
15+
already honoured it; it just wasn't declarable in the spec — closing a
16+
spec↔runtime gap so authors can set a friendly failure toast.

examples/app-crm/src/flows/convert-lead.flow.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ export const ConvertLeadScreenFlow = defineFlow({
3030
status: 'active',
3131
runAs: 'user',
3232

33+
// Friendly terminal toasts — the flow-runner shows these instead of a generic
34+
// "Done" / the raw error when the wizard finishes.
35+
successMessage: '🎉 Lead converted — customer and opportunity created.',
36+
errorMessage: 'Lead conversion did not finish — review the lead and try again.',
37+
3338
variables: [
3439
// ── input (from the action trigger) ───────────────────────────────────
3540
{ name: 'recordId', type: 'text', isInput: true, isOutput: false },

packages/services/service-automation/src/engine.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,7 +1287,9 @@ export class AutomationEngine implements IAutomationService {
12871287
await this.bubbleToParent(run, output);
12881288
}
12891289

1290-
return { success: true, output, durationMs };
1290+
// Surface the flow's friendly completion message so a screen-flow
1291+
// runner shows it instead of a generic "Done".
1292+
return { success: true, output, durationMs, successMessage: flow.successMessage };
12911293
} catch (err: unknown) {
12921294
// Re-suspended at a downstream node: persist a fresh continuation.
12931295
if (isSuspendSignal(err)) {
@@ -1341,7 +1343,9 @@ export class AutomationEngine implements IAutomationService {
13411343
if (!skipBubble) {
13421344
await this.failAncestors(run.context, errorMessage);
13431345
}
1344-
return { success: false, error: errorMessage, durationMs };
1346+
// Surface the flow's friendly error message (the raw error stays
1347+
// in `error` for logs/diagnostics).
1348+
return { success: false, error: errorMessage, durationMs, errorMessage: flow.errorMessage };
13451349
}
13461350
} finally {
13471351
this.resuming.delete(runId);

packages/spec/src/automation/flow.zod.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,17 @@ export const FlowSchema = lazySchema(() => z.object({
234234
name: z.string().regex(/^[a-z_][a-z0-9_]*$/).describe('Machine name'),
235235
label: z.string().describe('Flow label'),
236236
description: z.string().optional(),
237-
237+
238+
/**
239+
* Terminal messages for `screen`-flow runs. When the run reaches a terminal
240+
* state, the UI flow-runner shows `successMessage` instead of a generic
241+
* "Done" toast, and `errorMessage` instead of the raw error. Both are
242+
* surfaced on the terminal {@link AutomationResult} (`successMessage` /
243+
* `errorMessage`). Plain strings; `{var}` is NOT interpolated here.
244+
*/
245+
successMessage: z.string().optional().describe('Toast shown when a screen flow completes (defaults to a generic "Done").'),
246+
errorMessage: z.string().optional().describe('Toast shown when a screen flow fails (defaults to the raw error).'),
247+
238248
/** Metadata & Versioning */
239249
version: z.number().int().default(1).describe('Version number'),
240250
status: z.enum(['draft', 'active', 'obsolete', 'invalid']).default('draft').describe('Deployment status'),

packages/spec/src/contracts/automation-service.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,14 @@ export interface AutomationResult {
119119
* `signal.variables`.
120120
*/
121121
screen?: ScreenSpec;
122+
/**
123+
* Friendly terminal messages copied from the flow definition
124+
* (`flow.successMessage` / `flow.errorMessage`) so a screen-flow runner can
125+
* show a meaningful toast instead of a generic "Done" / the raw error.
126+
* `successMessage` is set on terminal success, `errorMessage` on failure.
127+
*/
128+
successMessage?: string;
129+
errorMessage?: string;
122130
}
123131

124132
/** Signal payload used to resume a paused run (ADR-0019). */

packages/spec/src/ui/action.zod.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,9 @@ export const ActionSchema = lazySchema(() => z.object({
312312
/** UX Behavior */
313313
confirmText: I18nLabelSchema.optional().describe('Confirmation message before execution'),
314314
successMessage: I18nLabelSchema.optional().describe('Success message to show after execution'),
315+
// Runtime (ActionRunner) already honours this — declared here so authors can
316+
// set a friendly failure toast instead of surfacing the raw error string.
317+
errorMessage: I18nLabelSchema.optional().describe('Error message to show when the action fails (overrides the raw error).'),
315318
refreshAfter: z.boolean().default(false).describe('Refresh view after execution'),
316319

317320
/**

0 commit comments

Comments
 (0)