Universal Action Dialog Specification¶
Status¶
Implementation-ready spec for the frontend universal action/event submission dialog. This builds on the completed canonical input/EventBuilder foundation:
- signed action envelopes use canonical
input - optional gate resolution uses
input.outcome.{value,reason} - observations/results live in
input.fields.* - shared
event-builder/owns envelope construction, canonicalization, signing, and hash preparation
Goal¶
Provide one reusable frontend dialog for all manual business actions that submit events through process_event.
The dialog must:
- render action context and canonical input fields
- preview the exact signed action/event details before submission
- submit only via
process_event - use the shared EventBuilder path for canonicalization/signing/hash
- avoid TypeScript workflow semantics beyond rendering server/Rust-WASM supplied action availability and declarative input metadata
Non-goals¶
- Do not evaluate workflow roles/guards/transitions in TypeScript.
- Do not hardcode business action lists in UI.
- Do not introduce legacy
dataoraction.data.*. - Do not replace workflow deployment
DeployModal; this dialog is for manual business actions. A later refactor may unify preview components. - Do not solve full rich-form/schema design in this milestone; start with deterministic canonical-section fields and a small schema subset.
Current state¶
Existing pieces:
event-builder/exposesnormalizeInput,buildActionEnvelope,prepareActionEvent,prepareActionEventAsync.frontend-react/src/crypto/actions.tsexposes:prepareAction(processId, actionName, workflow, input, prevHash?)submitEvent(actionJson, header, eventHash)signAndSubmit(...)frontend-react/src/pages/Customers.tsxcurrently submitscreatedirectly withsignAndSubmit(...)and an ad hoc form.frontend-react/src/pages/WorkflowEditor.tsxusesprepareAction(...)+submitEvent(...)for workflow deployment andDeployModalfor deploy preview.
Missing:
- no
ActionDialog/universal manual action dialog component - no generic canonical input editor
- no dialog-level preview of action JSON/signature/hash for manual actions
- no generic input inference from workflow action definitions
Component contract¶
Create frontend-react/src/components/ActionDialog.tsx.
```ts import type { ActionInput } from '../../../event-builder/index.mjs';
type ActionDialogProps = {
open: boolean;
title?: string;
workflowPath: string;
actionName: string;
processId: string;
currentState?: string;
latestHash?: string | null;
actionDefinition?: Record
type ActionInputSchema = Record
type ActionInputField = { type?: 'string' | 'number' | 'boolean' | 'date' | 'enum' | 'textarea' | 'reference' | 'json'; label?: string; required?: boolean; section?: 'fields' | 'refs' | 'outcome' | 'note' | 'evidence' | 'amounts' | 'dates' | 'flags' | 'meta'; options?: string[]; target?: string; placeholder?: string; help?: string; }; ```
The component owns local input state and computes preview via existing prepareAction(...) from frontend-react/src/crypto/actions.ts.
Dialog flow¶
- Open with workflow/action/process context.
- Build an input field model from explicit metadata if provided; otherwise infer from
actionDefinition. - Render canonical input sections.
- On input changes, normalize into an
ActionInputshape. - Generate preview by calling
prepareAction(...)with currentActionInputandlatestHash. - Show preview fields:
- workflow path
- action name
- process id
- latest hash
- registry hash
- actor public key
- canonical action JSON
- signature
- event hash
- Require explicit confirmation checkbox: "I understand this will append an immutable signed event."
- Submit by calling
submitEvent(preview.actionJson, preview.header, preview.eventHash). - On success call
onSubmitted(result).
Preview failures should be visible and fail closed; submission disabled until preview succeeds.
Canonical input rendering¶
The dialog renders sections in this order:
fieldsrefsoutcomenoteevidenceamountsdatesflagsmeta
Minimum field support for this milestone:
| Schema type | UI control | Output |
|---|---|---|
string / omitted |
text input | string |
textarea |
textarea | string |
number |
number input | number if parseable, otherwise string preserved |
boolean |
checkbox | boolean |
date |
date input | string |
enum |
select | selected string |
reference |
text input initially | string in refs |
json |
textarea parsed as JSON | JSON value |
Empty optional fields are omitted. Empty sections are removed by EventBuilder normalization.
Input schema source¶
Preferred source, if present:
json
"input": {
"fields.customer_name": {
"type": "string",
"label": "Customer name",
"required": true
},
"outcome.value": {
"type": "enum",
"options": ["approve", "reject", "investigate"],
"required": true
},
"refs.case_id": {
"type": "reference",
"target": "case/review/kyc-review"
}
}
The dialog treats this metadata as UI help only. The engine remains authoritative.
Input inference fallback¶
If inputSchema is absent, infer fields by scanning the action definition JSON for string expressions containing input.<section>.<key> and input.<section> ? '<key>'.
Inference rules:
| Expression pattern | Field path | Default type |
|---|---|---|
input.fields.foo |
fields.foo |
string |
input.refs.case_id |
refs.case_id |
reference |
input.outcome.value |
outcome.value |
string unless branch values imply enum |
input.outcome.reason |
outcome.reason |
textarea |
input.note.content |
note.content |
textarea |
input.dates.date_of_birth |
dates.date_of_birth |
date |
input.flags.override |
flags.override |
boolean |
input.fields ? 'foo' |
fields.foo |
string |
Enum inference for outcome.value:
- scan branch
whenexpressions forinput.outcome.value = 'x'orinput.outcome.value = "x" - if values exist, render as an enum/select with those options
Do not infer from state references or bare identifiers. Do not evaluate expressions.
Data/API requirements¶
The first implementation may use existing API shapes:
- workflow details from
getWorkflow(workflowPath)for action definition + optional action input metadata - processes from
getProcesses(...)or parent page data forchain_tip/prevHash prepareAction(...)for previewsubmitEvent(...)for submission
If process-specific action availability is not yet exposed by the server/Rust-WASM, the dialog must be invoked only by pages/components that already have a server/WASM-provided available action. Do not compute availability in the dialog.
First integration target¶
Integrate first with frontend-react/src/pages/Customers.tsx create flow:
- replace the ad hoc create modal/form with
ActionDialog - pass
workflowPath=activeWfPath actionName="create"processId=crypto.randomUUID()generated when opening the dialogprevHashsupplied from the active process/registrychain_tip; EventBuilder normalizes root bootstrap cases to ZERO only where valid- derive
inputSchemafrom workflow metadata$fieldsfor create action if no action-level input metadata exists - on submit success, close dialog and refresh list
This is intentionally the smallest proving path. Follow-up integrations can add process-detail action panels once server/WASM available-actions output is formalized.
UX requirements¶
- Clear immutable-event warning before submit.
- Preview must show canonical JSON, signature, event hash, and actor public key.
- Disable submit while preview is invalid or confirmation unchecked.
- Show validation/preview errors inline.
- Show submitted result or error; refresh parent on success.
- Keep styling consistent with existing modal patterns (
DeployModalcan be referenced for preview layout, but do not force coupling).
Tests¶
Add tests under frontend-react/src/__tests__/.
Required tests:
- renders inferred/schema fields grouped by canonical section
- updates preview when input changes
- submits
process_eventusing EventBuilder output and canonicalinput - request body contains no legacy
data outcome.valuebranch values render as enum options when inferred from branches- required fields disable submit until populated
Minimum gate:
bash
cd frontend-react && npm test -- --run && npm run build
Implementation phases¶
Phase 1 — Component + unit tests¶
Files likely touched:
frontend-react/src/components/ActionDialog.tsxfrontend-react/src/__tests__/action-dialog.test.tsx- optional small helper file:
frontend-react/src/components/actionInputModel.ts
Phase 2 — Customers create integration¶
Files likely touched:
frontend-react/src/pages/Customers.tsx- tests for customer create flow if existing harness supports it
Phase 3 — Process detail integration (follow-up)¶
Deferred until server/Rust-WASM action availability output is formalized. The dialog should be ready to consume available action descriptors but must not invent availability.
Descriptor contract: docs/architecture/available-action-descriptors-spec.md.
Guardrails¶
- Every submitted mutation goes through
process_event. - Use EventBuilder/
prepareActionfor preview; do not duplicate canonicalization/sign/hash logic in the component. - No legacy
datain action envelope or request body. - No TypeScript workflow guard/role/transition evaluation.
- SQL/API calls remain parameterized/server-side.
- Actor private key handling remains whatever
frontend-react/src/crypto/actions.tscurrently centralizes; do not add keys to component state beyond calling helpers.