26 KiB
Workflow Console Lifecycle Explorer Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Build a generic read-only artifact -> deployment -> run -> trace explorer with an interactive workflow graph, exercised through examples/lda_report_workflow/.
Architecture: Extend the existing Effect RPC registry with public lifecycle read operations, then adapt decoded results into plain console view models. A focused React lifecycle shell owns selection and request generations; @xyflow/react renders workflow plans laid out by @dagrejs/dagre, while a separate execution adapter correlates bounded trace frames to graph nodes.
Tech Stack: TypeScript 6, React 19, Vite 8, Effect 3 / @effect/rpc, Valibot, Vitest, Testing Library, @xyflow/react, @dagrejs/dagre, Hono.
Global Constraints
- Use only public JSON-RPC methods; never read workflow stores directly.
- Add no Python RPC methods in this slice.
- Keep the slice read-only: no draft mutation, run start/resume, autoplay, replay, agent, or presentation code.
- Decode external results before React components receive them; React consumes plain view models and does not run Effect programs.
- Preserve raw request, raw response, duration, and equivalent CLI evidence for every operation.
- Request at most 50 artifacts/runs initially and one bounded trace page of 50 frames.
- Ignore stale responses after target or selection changes.
- Use
@xyflow/reactand@dagrejs/dagre; do not hand-roll graph layout. - Use
examples/lda_report_workflow/as the reference fixture without hard-coding its ids in production components. - Do not use
any, unchecked TypeScript assertions, or direct mutation of decoded transport objects.
File Structure
Create focused modules instead of expanding App.tsx:
web/apps/console/src/
lifecycle/
models.ts # Valibot decoding and plain lifecycle view models
models.test.ts
state.ts # Pure selection/loading reducer
state.test.ts
useLifecycleExplorer.ts # RPC orchestration and stale-response guards
LifecycleExplorer.tsx # Focus navigation and master-detail composition
LifecycleExplorer.test.tsx
RecordColumns.tsx # Artifact/deployment/run list columns
RecordDetails.tsx # Read-only selected-record details
graph/
graph-model.ts # Raw plan -> laid-out React Flow model
graph-model.test.ts
WorkflowGraph.tsx # Canvas and node selection
WorkflowGraph.test.tsx
NodeInspector.tsx # Semantic node detail drawer
execution/
trace-model.ts # Trace decoding/correlation view model
trace-model.test.ts
ExecutionView.tsx # Run summary, interrupt, and trace timeline
ExecutionView.test.tsx
Modify the existing RPC package, browser contracts, App shell, styles, docs, and package manifests only where their responsibility requires it.
Task 1: Map Lifecycle Read Operations Through Effect RPC
Files:
- Modify:
web/packages/rpc/src/rpcs.ts - Modify:
web/packages/rpc/src/method-registry.ts - Modify:
web/packages/rpc/src/service.ts - Modify:
web/packages/rpc/src/index.ts - Modify:
web/packages/rpc/src/service.test.ts - Modify:
web/apps/server/src/app.test.ts
Interfaces:
-
Produces operation names for artifact list/inspect, deployment list/inspect/validate, and run list/inspect/trace.
-
Produces interpreted transport objects whose field names are camelCase and whose full raw responses remain in
OperationExchange.exchange. -
Consumers in later tasks call the existing
WorkflowRpc.execute(operation, target, params)API. -
Step 1: Write failing RPC service tests for all new methods
Add table-driven cases to service.test.ts using the existing fake-fetch pattern:
const lifecycleCases = [
{
operation: "workflow.artifacts.list",
params: { limit: 50 },
result: {
nodes: [{
name: "workflow.report@1",
artifact_id: "report",
version: 1,
kind: "workflow",
display_name: "Report",
description: null,
outcomes: ["ok"],
input_schema: { type: "object" },
output_schema: { type: "object" },
required_sources: ["local.report"],
diagnostics: [],
}],
total: 1,
cursor: null,
next_cursor: null,
limit: 50,
},
},
{
operation: "workflow.deployments.list",
params: {},
result: {
deployments: [{
id: "report.default",
artifact_id: "report",
artifact_version: 1,
binding_count: 1,
drift_policy: "block",
}],
},
},
{
operation: "workflow.runs.list",
params: { limit: 50 },
result: {
runs: [{
run_id: "run_1",
deployment_id: "report.default",
artifact_id: "report",
artifact_version: 1,
status: "interrupted",
resume_readiness: "ready",
diagnostic_count: 0,
created_at: "2026-07-02T00:00:00Z",
updated_at: "2026-07-02T00:00:01Z",
}],
total: 1,
cursor: null,
next_cursor: null,
limit: 50,
},
},
] as const;
For inspect/validate/trace, assert at least the identity, status, plan, bindings, diagnostics, interrupt, and bounded trace fields used by later view models.
- Step 2: Run the focused tests and confirm the red state
Run:
pnpm --dir web --filter @lda/workflow-rpc test -- service.test.ts
Expected: FAIL because the operation names are unknown or absent from
WorkflowRpcs.
- Step 3: Add Effect schemas and RPC declarations
In rpcs.ts, introduce reusable strict primitives and public result schemas:
const PositiveIntegerSchema = Schema.Number.pipe(
Schema.int(),
Schema.between(1, Number.MAX_SAFE_INTEGER),
);
const JsonObjectSchema = Schema.Record({
key: Schema.String,
value: Schema.Unknown,
});
export const ArtifactRefSchema = Schema.Struct({
artifact_id: Schema.String,
version: PositiveIntegerSchema,
});
export const TraceRangeSchema = Schema.Struct({
start: NonNegativeIntegerSchema,
limit: PositiveIntegerSchema,
});
Define one Rpc.make per exact public method and add each to WorkflowRpcs.
Payloads must mirror src/wf_transport_rpc_http/models.py; success schemas
must decode the selected UI fields and preserve plan/output/interrupt/trace
objects as schema-checked records or explicit nullable fields.
- Step 4: Extend the operation registry and execution switch
Add one OperationMeta entry per method. Equivalent CLI strings must be:
uv run wf artifact list --limit 50
uv run wf artifact inspect ARTIFACT_ID --version VERSION
uv run wf deploy list
uv run wf deploy inspect DEPLOYMENT_ID
uv run wf deploy validate DEPLOYMENT_ID
uv run wf run list --limit 50
uv run wf run inspect RUN_ID
uv run wf run trace RUN_ID --from START --limit LIMIT
Extend OperationName and the executeImpl switch. Follow the existing
generated-client access style, for example:
case "workflow.artifacts.list": {
const payload = yield* decodeParams(WorkflowArtifactsListPayloadSchema, params);
return yield* client.workflow["artifacts.list"](payload);
}
Keep metadata.interpret() inside decodeOperationMetadata so schema failures
remain RpcDecodeError values with evidence.
- Step 5: Run RPC and server tests
Run:
pnpm --dir web --filter @lda/workflow-rpc test
pnpm --dir web --filter @lda/web-server test
Expected: all tests pass, including an app test proving a newly registered
operation reaches RunOperation rather than returning unknown_operation.
- Step 6: Commit the RPC read surface
git add web/packages/rpc web/apps/server/src/app.test.ts
git commit -m "feat: expose lifecycle reads to web console"
Task 2: Add Typed Browser Contracts And Lifecycle View Models
Files:
- Modify:
web/apps/console/src/connection/contracts.ts - Modify:
web/apps/console/src/connection/api.test.ts - Create:
web/apps/console/src/lifecycle/models.ts - Create:
web/apps/console/src/lifecycle/models.test.ts
Interfaces:
-
Produces
ArtifactSummary,ArtifactDetail,DeploymentSummary,DeploymentDetail,DeploymentValidation,RunSummary,RunDetail, andTracePage. -
Produces
decodeArtifactList,decodeArtifactDetail,decodeDeploymentList,decodeDeploymentDetail,decodeDeploymentValidation,decodeRunList,decodeRunDetail, anddecodeTracePage. -
Later React tasks consume only these view models.
-
Step 1: Write failing adapter tests
Cover valid payloads, missing required identities, negative counts, malformed plans, nullable interrupt/output fields, and bounded trace metadata. Example:
it("decodes an artifact list into immutable summaries", () => {
const result = decodeArtifactList({
nodes: [{
artifactId: "report",
version: 1,
kind: "workflow",
displayName: "Report",
description: null,
outcomes: ["ok"],
requiredSources: ["local.report"],
diagnosticCount: 0,
}],
nextCursor: null,
total: 1,
});
expect(result.items[0]?.key).toBe("report@1");
});
- Step 2: Run tests and confirm missing adapters
pnpm --dir web --filter @lda/console test -- models.test.ts
Expected: FAIL because lifecycle/models.ts does not exist.
- Step 3: Extend the operation-name browser contract
Add all lifecycle method literals to OperationNameSchema. Keep the envelope
forward-compatible for error codes, but reject unknown success operation names.
- Step 4: Implement Valibot-backed lifecycle adapters
Each decoder validates unknown and returns a plain immutable object. Use a
shared error wrapper:
const decode = <T>(
label: string,
schema: v.GenericSchema<unknown, T>,
value: unknown,
): T => {
const result = v.safeParse(schema, value);
if (result.success) return result.output;
throw new Error(`${label} is malformed: ${result.issues[0]?.message ?? "unknown issue"}`);
};
Do not pass transport snake_case values into React. Normalize stable keys, display labels, status enums, counts, pagination cursors, and inspect payloads inside these adapters.
- Step 5: Run console adapter/API tests
pnpm --dir web --filter @lda/console test -- models.test.ts api.test.ts
Expected: all focused tests pass.
- Step 6: Commit browser lifecycle models
git add web/apps/console/src/connection web/apps/console/src/lifecycle
git commit -m "feat: add lifecycle console view models"
Task 3: Build Lifecycle Selection State And RPC Orchestration
Files:
- Create:
web/apps/console/src/lifecycle/state.ts - Create:
web/apps/console/src/lifecycle/state.test.ts - Create:
web/apps/console/src/lifecycle/useLifecycleExplorer.ts - Create:
web/apps/console/src/lifecycle/useLifecycleExplorer.test.tsx
Interfaces:
-
Produces
LifecycleState,LifecycleAction,lifecycleReducer, andinitialLifecycleState. -
Produces
useLifecycleExplorer(target)with selection commands, refresh commands, load-more commands, and plain state. -
Step 1: Write reducer tests for selection invariants
Pin these transitions:
selectArtifact("report@1")
// clears selectedDeploymentId, selectedRunId, deployment detail, run detail, trace
selectDeployment("report.default")
// clears selectedRunId, run detail, trace
targetChanged()
// returns the complete lifecycle state to its initial value
Also cover loading, empty, partial error, append-page, inspect-success, and trace-page states.
- Step 2: Run reducer tests and confirm red
pnpm --dir web --filter @lda/console test -- lifecycle/state.test.ts
Expected: FAIL because the reducer is missing.
- Step 3: Implement the pure reducer
Use separate operation states rather than one global loading flag:
type LoadState<T> =
| { readonly phase: "idle" }
| { readonly phase: "loading"; readonly previous: T | null }
| { readonly phase: "loaded"; readonly value: T }
| { readonly phase: "error"; readonly message: string; readonly previous: T | null };
Keep ids and decoded records separate so selection changes do not mutate list objects.
- Step 4: Write hook tests with deferred promises
Mock callOperation and prove:
-
initial target load requests artifact/deployment/run lists with limit 50;
-
selecting an artifact requests inspect once;
-
a late artifact response is ignored after a newer selection;
-
reconnect invalidates all outstanding generations;
-
deployment validation failure leaves artifact detail visible;
-
trace next-page appends only the matching run's frames.
-
Step 5: Implement
useLifecycleExplorer
Use one generation counter for target-wide list loads and one counter per selected inspect/trace chain. Dispatch evidence through a supplied callback:
export const useLifecycleExplorer = (
target: string | null,
recordEvidence: (record: EvidenceRecord) => void,
): LifecycleExplorerController => { /* orchestrate callOperation */ };
Never duplicate evidence DTO construction; extract the existing App logic into a small helper if both source inventory and lifecycle operations need it.
- Step 6: Run reducer and hook tests
pnpm --dir web --filter @lda/console test -- lifecycle
Expected: all lifecycle state/orchestration tests pass.
- Step 7: Commit lifecycle state management
git add web/apps/console/src/lifecycle
git commit -m "feat: orchestrate lifecycle explorer reads"
Task 4: Render The Lifecycle Master-Detail Explorer
Files:
- Create:
web/apps/console/src/lifecycle/RecordColumns.tsx - Create:
web/apps/console/src/lifecycle/RecordDetails.tsx - Create:
web/apps/console/src/lifecycle/LifecycleExplorer.tsx - Create:
web/apps/console/src/lifecycle/LifecycleExplorer.test.tsx - Modify:
web/apps/console/src/styles/global.css
Interfaces:
-
Consumes
LifecycleExplorerControllerfrom Task 3. -
Produces a generic artifact -> deployment -> run navigation shell and focus-mode buttons.
-
Emits selected artifact plan and selected run/trace data to graph/execution children introduced later.
-
Step 1: Write component tests for the complete read spine
Render a loaded controller fixture and assert:
expect(screen.getByRole("button", { name: /Report.*version 1/i })).toBeVisible();
expect(screen.getByRole("button", { name: /report.default/i })).toBeVisible();
expect(screen.getByRole("button", { name: /run_1.*interrupted/i })).toBeVisible();
Click each level and assert descendant selection callbacks. Add explicit tests for empty artifacts, unrelated deployments, unavailable trace, and partial validation errors.
- Step 2: Run the component test and confirm red
pnpm --dir web --filter @lda/console test -- LifecycleExplorer.test.tsx
Expected: FAIL because the components do not exist.
- Step 3: Implement accessible list columns and record details
Use semantic buttons/lists and visible selection state. Details must prioritize interpreted values:
- artifact: title, id/version, kind, outcomes, required sources;
- deployment: id, artifact ref, drift policy, bindings, validation status and diagnostics;
- run: id, deployment, artifact ref, status, readiness, outcome, updated time.
Do not render whole-object JSON in these panels.
- Step 4: Implement focus navigation
LifecycleExplorer owns four buttons: Lifecycle, Graph, Execution, Raw. Disable
Graph without an inspected artifact and Execution without an inspected run.
Raw remains available when evidence exists.
- Step 5: Add responsive styles
Desktop uses three linked columns plus a details panel. Narrow screens use horizontal focus navigation and one active column at a time; do not shrink all columns until unreadable.
- Step 6: Run explorer tests and typecheck
pnpm --dir web --filter @lda/console test -- LifecycleExplorer.test.tsx
pnpm --dir web --filter @lda/console typecheck
Expected: both commands pass.
- Step 7: Commit the lifecycle UI
git add web/apps/console/src/lifecycle web/apps/console/src/styles/global.css
git commit -m "feat: render lifecycle master detail explorer"
Task 5: Add Library-Backed Workflow Graph Adaptation
Files:
- Modify:
web/apps/console/package.json - Modify:
web/pnpm-lock.yaml - Create:
web/apps/console/src/graph/graph-model.ts - Create:
web/apps/console/src/graph/graph-model.test.ts
Interfaces:
-
Produces
WorkflowGraphModel,WorkflowGraphNodeData, andbuildWorkflowGraph(plan). -
Uses
@dagrejs/dagreonly for layout; it never writes coordinates back to workflow data. -
Step 1: Install graph dependencies
pnpm --dir web --filter @lda/console add @xyflow/react@12.11.1 @dagrejs/dagre@3.0.0
Both pinned packages ship TypeScript declarations; do not add a legacy
@types/dagre package.
- Step 2: Write failing graph-model tests
Use fixtures containing capability use, condition, interrupt, foreach, join, and end nodes. Assert stable ids, semantic labels, route edges, node kinds, deterministic coordinates, and immutability of the input plan.
const first = buildWorkflowGraph(plan);
const second = buildWorkflowGraph(structuredClone(plan));
expect(second).toEqual(first);
expect(plan).toEqual(originalPlan);
- Step 3: Run graph-model tests and confirm red
pnpm --dir web --filter @lda/console test -- graph-model.test.ts
Expected: FAIL because the adapter is missing.
- Step 4: Implement plan normalization and Dagre layout
Decode only the plan structures required to render nodes/edges. Convert all supported node variants into a common view model:
export type WorkflowGraphNodeData = {
readonly nodeId: string;
readonly kind: "use" | "condition" | "interrupt" | "foreach" | "join" | "end" | "control";
readonly label: string;
readonly capability: string | null;
readonly outcomes: ReadonlyArray<string>;
readonly raw: Readonly<Record<string, unknown>>;
};
Use Dagre's directed layout with fixed presentation dimensions. Sort nodes and edges by stable ids before layout so identical plans produce identical models.
- Step 5: Run graph tests and typecheck
pnpm --dir web --filter @lda/console test -- graph-model.test.ts
pnpm --dir web --filter @lda/console typecheck
Expected: all checks pass.
- Step 6: Commit graph dependencies and model
git add web/apps/console/package.json web/pnpm-lock.yaml web/apps/console/src/graph
git commit -m "feat: adapt workflow plans for graph rendering"
Task 6: Render The Workflow Graph And Node Inspector
Files:
- Create:
web/apps/console/src/graph/WorkflowGraph.tsx - Create:
web/apps/console/src/graph/WorkflowGraph.test.tsx - Create:
web/apps/console/src/graph/NodeInspector.tsx - Modify:
web/apps/console/src/styles/global.css
Interfaces:
-
Consumes
WorkflowGraphModeland an optional active trace node id. -
Produces node selection events and a semantic inspector drawer.
-
Step 1: Write failing graph interaction tests
Mock @xyflow/react only where jsdom lacks layout APIs. Assert that nodes and
edges render, selecting review opens the inspector, and activeNodeId marks
the matching node without changing graph data.
- Step 2: Run the component test and confirm red
pnpm --dir web --filter @lda/console test -- WorkflowGraph.test.tsx
Expected: FAIL because the graph components are missing.
- Step 3: Implement the graph canvas
Import @xyflow/react/dist/style.css, render controls/background/fit-view, and
use custom node presentation for semantic kinds. Keep the graph read-only:
disable connect, delete, drag persistence, and mutation callbacks.
- Step 4: Implement the node inspector
Show node kind, capability/source reference, input/output bindings, outcomes, and routes. Put raw node JSON behind a collapsed disclosure labeled Raw node definition.
- Step 5: Run graph component tests and accessibility checks
pnpm --dir web --filter @lda/console test -- WorkflowGraph.test.tsx
pnpm --dir web --filter @lda/console typecheck
Expected: all checks pass with no missing accessible names.
- Step 6: Commit graph presentation
git add web/apps/console/src/graph web/apps/console/src/styles/global.css
git commit -m "feat: render interactive workflow graph"
Task 7: Add Run Execution, Trace, And Interrupt Views
Files:
- Create:
web/apps/console/src/execution/trace-model.ts - Create:
web/apps/console/src/execution/trace-model.test.ts - Create:
web/apps/console/src/execution/ExecutionView.tsx - Create:
web/apps/console/src/execution/ExecutionView.test.tsx - Modify:
web/apps/console/src/styles/global.css
Interfaces:
-
Produces
TraceFrameView,buildTraceFrames(tracePage), andExecutionView. -
Emits selected frame node ids to
WorkflowGraphfor correlation. -
Step 1: Write failing trace adapter tests
Use the public trace shape:
{
frame_id: "root",
node_id: "review",
step_type: "interrupt",
resolved_input: { report: "..." },
outcome: "submitted",
next_node_id: "create_issues",
output: {},
state_changes: {},
}
Assert stable frame keys, node correlation, concise summaries, ordering, and
next-page state from trace_start, trace_limit, and trace_truncated.
- Step 2: Run trace tests and confirm red
pnpm --dir web --filter @lda/console test -- trace-model.test.ts
Expected: FAIL because trace adapters are missing.
- Step 3: Implement trace adaptation
Never stringify unbounded input/output into list rows. Produce counts and short field summaries; retain full decoded values only for the selected frame drawer.
- Step 4: Write and implement execution component tests
Cover completed, failed, and interrupted runs. The interrupted fixture must
render kind, payload, outcomes, request schema, resume schema, and typed flag,
but no submit/resume button.
Selecting a trace frame must call:
onSelectNode(frame.nodeId);
- Step 5: Run execution tests and typecheck
pnpm --dir web --filter @lda/console test -- execution
pnpm --dir web --filter @lda/console typecheck
Expected: all checks pass.
- Step 6: Commit execution and trace views
git add web/apps/console/src/execution web/apps/console/src/styles/global.css
git commit -m "feat: render run execution and trace"
Task 8: Integrate The Explorer, Document Smoke, And Verify
Files:
- Modify:
web/apps/console/src/app/App.tsx - Modify:
web/apps/console/src/app/App.test.tsx - Modify:
web/apps/console/src/app/state.ts - Modify:
web/apps/console/src/components/ProtocolEvidence.tsx - Modify:
web/apps/console/src/styles/global.css - Modify:
web/README.md - Modify:
docs/current_roadmap.md - Move after completion:
docs/superpowers/plans/2026-07-02-workflow-console-lifecycle-explorer.md->docs/historical/superpowers/plans/2026-07-02-workflow-console-lifecycle-explorer.md
Interfaces:
-
Connects the existing connection/source foundation to Lifecycle, Graph, Execution, and Raw focus modes.
-
Preserves the current source inventory and protocol evidence behavior.
-
Step 1: Write the failing App integration test
Mock lifecycle RPC responses and prove this sequence:
Connect -> artifacts load -> select artifact -> Graph enabled
-> select deployment -> select run -> Execution enabled
-> select trace frame -> matching graph node active
-> Raw shows evidence for each call
Also prove reconnect clears lifecycle selection and ignores late responses from the previous target.
- Step 2: Run the App test and confirm red
pnpm --dir web --filter @lda/console test -- App.test.tsx
Expected: FAIL because App does not mount the explorer.
- Step 3: Integrate focus modes without growing App orchestration
App.tsx should instantiate useLifecycleExplorer and pass the controller to
LifecycleExplorer; it must not absorb lifecycle reducer cases or transport
decoding. Reuse ProtocolEvidence for Raw focus rather than adding a second raw
viewer.
- Step 4: Document the optional live smoke
Add these exact prerequisites and checks to web/README.md:
uv run wf-rpc-server --config examples/lda_report_workflow/wf.config.json --host 127.0.0.1 --port 8765
pnpm --dir web dev
Document how to seed or generate the example's artifact/deployment/run using
its existing README or build script; do not introduce a helper that drives
WorkflowApi directly. The smoke passes when artifact, deployment, run, graph,
trace, and raw evidence are visible.
- Step 5: Run complete verification
pnpm --dir web test
pnpm --dir web typecheck
pnpm --dir web build
uv run pytest tests/docs -q -n0
git diff --check
Expected: all commands exit 0. CRLF conversion warnings are acceptable; whitespace errors are not.
- Step 6: Perform the optional live smoke
With the Python server and web dev process running, verify:
Invoke-RestMethod http://127.0.0.1:8787/api/health
Then inspect the lifecycle through http://127.0.0.1:5173. Record any skipped
fixture setup explicitly; do not claim live smoke success from unit tests.
- Step 7: Update roadmap and archive the plan
Mark roadmap item 4 completed only after the full verification and live smoke requirements have been met. Move this plan to the historical mirror path and update any live links.
- Step 8: Commit integration and documentation
git add web docs/current_roadmap.md docs/historical/superpowers/plans/2026-07-02-workflow-console-lifecycle-explorer.md
git commit -m "feat: add workflow lifecycle explorer"
Plan Self-Review
- Spec coverage: public RPC reads, pagination, stale guards, linking, graph, trace, interrupt contract, evidence, error states, testing, and live smoke each map to explicit tasks.
- Scope: draft workspace inspection remains a named follow-up and is not mixed into the artifact-to-run vertical slice.
- Type consistency: lifecycle adapters feed the controller; the controller
feeds React; graph and trace models share stable
nodeIdstrings. - Placeholder scan: no TBD/TODO or unspecified implementation steps remain.