Automations UI
Phase 11 — the automation editor and run history
(apps/web/src/app/(app)/w/[workspace]/settings/automations/ +
apps/web/src/components/automations/). The surface:
| Route | Purpose |
|---|---|
/w/:workspace/settings/automations |
list (table) + Runs expander per row + pause/enable switch + delete |
/w/:workspace/settings/automations/new |
create a DRAFT (name + description) → navigate to the editor |
/w/:workspace/settings/automations/:id |
editor: name/description/database-scope, triggers (add/edit/remove), the single condition, ordered actions (add/edit/remove, per-action retries/timeout) + Run now + the full run history with logs + retry |
It is reachable from the settings shell's Automations tab
(Zap icon, settings-shell.tsx). All data goes through the API
(../api/automations.md) — the UI never executes
automations itself.
1. List page
- Table rows: name (+ description), status pill (
ACTIVE/PAUSED/DRAFT/ARCHIVED), trigger types, latest run (status label or "never"), and a row of controls:- switch (
role="switch") — ACTIVE ⇄ PAUSED (callsPOST …/status; 409s surface as a toast), - Runs button — expands an inline run-history row under the automation,
- trash — DELETE (→ ARCHIVED).
- switch (
- TanStack Query owns the list (
["automations", workspaceId]) and the per-automation runs (["automations", automationId, "runs"],refetchInterval4 s while any run is PENDING/RUNNING).
2. Editor
- Triggers (
trigger-editor.tsx): 0..3, OR-semantics (any match enqueues). Per-type config forms (RECORD_CREATED/UPDATED/ PROPERTY_CHANGED + database pick, SCHEDULE + cron with the sharedcronCheckvalidation shown inline, WEBHOOK + the generated URL, MANUAL). - Condition (
condition-editor.tsx): 0..1 tree. The editor is a leaf-predicate row (field ref property|event · name · operator · value) with a raw-JSON mode for AND/OR/NOT trees — the saved shape is exactly the sharedCONDITION_EXPRESSION_SCHEMA(the API validates withvalidateConditionTree; the worker evaluates it). - Actions (
action-editor.tsx): 1..50, position-ordered (reorder by move up/down). Each row picks one of the 9 action types and renders the REALActionConfigSchemasform for it (CREATE_NOTIFICATION = recipient + body; SEND_WEBHOOK = url/headers/payload; UPDATE_RECORD = record source + property values; RUN_AI_ACTION exposestask: "classify"; CALL_INTEGRATION is exposed as-is). Per-actionretries/timeoutMsare optional overrides.- UI deviation (recorded): there is no CLASSIFY action type in the
shared contract — "classify" is modeled as RUN_AI_ACTION with
task: "classify".
- UI deviation (recorded): there is no CLASSIFY action type in the
shared contract — "classify" is modeled as RUN_AI_ACTION with
- Run now (editor) →
POST …/run(202) — manual runs work on DRAFTs. - Saving uses the sub-resource PUTs (
…/triggers,…/conditions,…/actions) +PATCH …for the header fields.
3. Run history
run-history.tsx renders the run list (newest first): status pill
(succeeded / failed / dead-letter / pending / running), trigger
type, relative start time + duration, retries: n. Expanding a run loads
the run detail and renders the full log panel — every automation_logs
line (level + ISO timestamp + message + data JSON), plus the error block
for failures. FAILED and DEAD_LETTER runs show a Retry button
(POST …/runs/:runId/retry).
4. Testids (the E2E contract)
| testid | Where |
|---|---|
automation-list |
list page (always rendered, even empty) |
automation-new |
"New automation" button |
automation-row-{id} / automation-toggle-{id} / automation-runs-{id} |
list rows |
automation-name-input, automation-save, automation-run-now |
editor |
automation-trigger-add, automation-condition-add, automation-action-add |
editor sub-editors |
automation-run-{runId} |
run row (status text lowercase) |
automation-run-log-{runId} |
the log panel |
automation-retry-run-{runId} |
retry button |
The full-stack E2E spec exercising this contract lives at
tests/e2e/specs/automation.spec.ts (requires the whole stack running —
pnpm docker:up && pnpm dev — no webServer block).