36 KiB
Workflow Console Selected-Step Dataflow 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: Let console operators inspect and atomically replace capability-step input bindings, output-to-state bindings, and optional execution metadata without editing raw JSON.
Architecture: Extend the existing authored Effect/browser operation seam with the two focused binding methods already present in the checked contract manifest. Parse canonical draft data into pure selected-step projections, submit complete ordered binding lists through the shared draft-authoring client/controller, and render three inspector tabs that always rehydrate from the returned canonical draft.
Tech Stack: React 19, TypeScript, Effect RPC, generated workflow contract schemas, Valibot domain decoders, Vite/Vitest, Testing Library, Hono browser proxy, Playwright/Chrome smoke testing.
Global Constraints
- The returned canonical draft is the only editable workflow document; do not add browser-owned graph persistence.
- Use
runtimeSchemasFor(...)and the checked generated workflow contract; do not hand-write duplicate transport schemas. - Input and output binding replacement preserves list order, nested paths, literals, explicit null, whole-payload bindings, and repeated-source fan-out.
- New output targets are projected into workflow state by the existing backend operation; the browser does not synthesize state schema patches.
- Blank retry and timeout values are optional. Retry accepts integer
0or greater; timeout accepts values greater than0or remains absent. - Keep the browser operation allowlist explicit and fail closed for unrelated mutations.
- Direct drag-to-bind, workflow-level contracts, non-capability steps, state deletion, and dataflow graph edges are out of scope.
- Preserve revision checks, target provenance, operation evidence, conflict reload/reapply, mobile sheets, and accessible keyboard alternatives.
- Use TDD for every task. Do not modify Serena configuration.
File Structure
Transport Boundary
src/wf_core/models/steps.py: canonical recursive JSON literal type for input value bindings.tests/core/test_canonical_node_bindings.py: JSON literal model/schema coverage.tests/wf_contract_manifest/test_generate.py: generated manifest coverage for literal JSON values.contracts/workflow-api.manifest.json: regenerated neutral workflow contract.web/packages/rpc/scripts/workflow-contract-generator.ts: authored runtime operation cohort.web/packages/rpc/scripts/workflow-contract-generator.test.ts: runtime cohort generation coverage.web/packages/rpc/src/generated/workflow-contract.ts: regenerated TypeScript contract.web/packages/rpc/src/rpcs.ts: authored Effect RPC definitions generated from checked schemas.web/packages/rpc/src/service.ts: operation dispatch to the JSON-RPC client.web/packages/rpc/src/method-registry.ts: labels, interpretations, and equivalent CLI evidence.web/packages/rpc/src/index.ts: public exports.web/apps/console/src/connection/contracts.ts: browser response operation decoder.web/apps/server/src/browser-operation-policy.ts: explicit browser allowlist.
Workspace Domain
web/apps/console/src/workspace/domain/draft-workspace-models.ts: canonical binding and focused mutation input types.web/apps/console/src/workspace/domain/draft-authoring-client.ts: transport-neutral selected-step mutation interface.web/apps/console/src/workspace/authoring/selected-step-dataflow.ts: pure draft/schema projection and form serialization.web/apps/console/src/workspace/authoring/useDraftAuthoring.ts: mutation lifecycle, conflict preservation, and reapply.
React Surfaces
web/apps/console/src/workspace/authoring/CapabilitySetupForm.tsx: presence-aware metadata patch form.web/apps/console/src/workspace/authoring/StepInputBindingsForm.tsx: ordered path/literal input editor.web/apps/console/src/workspace/authoring/StepOutputBindingsForm.tsx: ordered output-to-state editor and inferred-schema preview.web/apps/console/src/workspace/authoring/SelectedCapabilityInspector.tsx: Setup/Inputs/Outputs tab composition.web/apps/console/src/workspace/authoring/ContextInspector.tsx: selection routing only.web/apps/console/src/workspace/authoring/authoring-graph.ts: compact binding-count summary on capability nodes.web/apps/console/src/graph/graph-model.ts: optional node summary carried through the shared graph model.web/apps/console/src/graph/WorkflowGraph.tsx: render the optional node summary.web/apps/console/src/styles/global.css: inspector tabs, ordered rows, responsive controls, and node summary styling.
Task 1: Author The Two Binding RPC Operations
Files:
- Modify:
web/packages/rpc/src/rpcs.ts - Modify:
web/packages/rpc/src/service.ts - Modify:
web/packages/rpc/src/method-registry.ts - Modify:
web/packages/rpc/src/index.ts - Modify:
web/packages/rpc/src/service.test.ts - Modify:
web/packages/rpc/src/method-registry.test.ts - Modify:
web/packages/rpc/src/json-schema/runtime-schema.test.ts - Modify:
web/packages/rpc/src/json-schema/rpc-parity.test.ts - Modify:
web/packages/rpc/src/json-schema/authored-rpc-fixtures.ts - Modify:
web/packages/rpc/scripts/workflow-contract-generator.ts - Modify:
web/packages/rpc/scripts/workflow-contract-generator.test.ts - Modify:
src/wf_core/models/steps.py - Modify:
tests/core/test_canonical_node_bindings.py - Modify:
tests/wf_contract_manifest/test_generate.py - Regenerate:
contracts/workflow-api.manifest.json - Regenerate:
web/packages/rpc/src/generated/workflow-contract.ts - Modify:
web/apps/console/src/connection/contracts.ts - Modify:
web/apps/server/src/browser-operation-policy.ts - Modify:
web/apps/server/src/browser-operation-policy.test.ts - Modify:
web/apps/server/src/app.test.ts
Interfaces:
-
Produces: callable operations
workflow.draft_workspaces.set_step_input_bindingsandworkflow.draft_workspaces.set_step_output_bindings. -
Produces: payload/result schema exports named
WorkflowDraftWorkspacesSetStepInputBindingsPayloadSchema,WorkflowDraftWorkspacesSetStepInputBindingsResultSchema,WorkflowDraftWorkspacesSetStepOutputBindingsPayloadSchema, andWorkflowDraftWorkspacesSetStepOutputBindingsResultSchema. -
Preserves: the explicit browser policy independent from generated operation inventory.
-
Step 1: Write failing canonical JSON-literal and generator tests
Add model and manifest tests proving InputValueBinding.value accepts and
describes every JSON value category: string, number, boolean, null, array, and
object. The generated TypeScript InputValueBinding["value"] must be a
recursive JSON value, not { [key: string]: unknown }.
Replace the object-only InputValueBindingSchema.value in
authored-rpc-fixtures.ts with the same recursive JSON-value semantics. Add
fixture decode assertions for scalar, null, array, and object literals so
parity cannot pass with a narrower authored reference.
Extend workflow-contract-generator.test.ts so removing either focused binding
operation from the complete fixture fails with missing runtime operation, and
so both operation names and their reachable binding schemas appear in
workflowRuntimeContract.
- Step 2: Run the contract tests and verify RED
Run:
uv run pytest tests/core/test_canonical_node_bindings.py tests/wf_contract_manifest/test_generate.py -q
pnpm --dir web --filter @lda/workflow-rpc test -- scripts/workflow-contract-generator.test.ts
Expected: failures because unconstrained Python object generates an
object-only TypeScript value and the two methods are absent from the runtime
operation cohort.
- Step 3: Repair the canonical contract and regenerate checked outputs
Define one recursive JSON-value alias at the canonical binding model seam and
use it for InputValueBinding.value. Keep booleans distinct from integers in
tests and reject non-JSON Python objects. Add both focused methods to
runtimeOperationNameList.
Regenerate in dependency order:
uv run python -m wf_contract_manifest write
pnpm --dir web --filter @lda/workflow-rpc contract:write
pnpm --dir web --filter @lda/workflow-rpc contract:check
Do not edit either generated file manually.
- Step 4: Write failing RPC service and registry tests
Add representative cases to service.test.ts:
{
operation: "workflow.draft_workspaces.set_step_input_bindings" as const,
params: {
workspace_id: "console.demo",
revision: 3,
step_id: "render",
bindings: [
{ path: "input.title", target: "report.title" },
{ target: "format", value: "markdown" },
],
},
result: draftWorkspaceResult,
},
{
operation: "workflow.draft_workspaces.set_step_output_bindings" as const,
params: {
workspace_id: "console.demo",
revision: 4,
step_id: "render",
bindings: [{ source: "report", target: "state.report" }],
},
result: draftWorkspaceResult,
},
Add registry assertions that input bindings render wf draft set-input and
output bindings render wf draft set-output. Add dedicated evidence renderers
for these replacement commands: the existing add-capability renderer's
--input syntax must remain unchanged. Use --clear for an empty list; use
repeated --map/--value only when exactly representable, otherwise mark the
CLI evidence non-equivalent and point to --bindings-file.
- Step 5: Write failing policy and contract tests
Extend browser-operation-policy.test.ts to require both focused methods and
continue rejecting workflow.draft_workspaces.replace_document,
workflow.draft_workspaces.remove_step, and workflow.admin.auth.list.
Extend app.test.ts with one accepted request per operation. Extend runtime
schema and RPC parity tests so both payload and success sides translate from
the checked manifest and enforce exact canonical binding unions. Add matching
entries to authored-rpc-fixtures.ts; parity coverage must compare both
generated schemas against those authored reference schemas.
- Step 6: Run the focused tests and verify RED
Run:
pnpm --dir web --filter @lda/workflow-rpc test -- src/service.test.ts src/method-registry.test.ts src/json-schema/runtime-schema.test.ts src/json-schema/rpc-parity.test.ts
pnpm --dir web --filter @lda/server test -- src/browser-operation-policy.test.ts src/app.test.ts
Expected: failures because the two operations are not in WorkflowRpcs,
service dispatch, registry metadata, browser contracts, or browser policy.
- Step 7: Implement the authored RPC definitions and dispatch
Follow the existing update-capability pattern in rpcs.ts:
const setStepInputBindingsSchemas = runtimeSchemasFor(
"workflow.draft_workspaces.set_step_input_bindings",
);
export const WorkflowDraftWorkspacesSetStepInputBindingsPayloadSchema =
setStepInputBindingsSchemas.payload;
export const WorkflowDraftWorkspacesSetStepInputBindingsResultSchema =
setStepInputBindingsSchemas.success;
export const WorkflowDraftWorkspacesSetStepInputBindings = Rpc.make(
"workflow.draft_workspaces.set_step_input_bindings",
{
payload: WorkflowDraftWorkspacesSetStepInputBindingsPayloadSchema,
success: WorkflowDraftWorkspacesSetStepInputBindingsResultSchema,
error: Schema.Never,
},
);
Create the matching output definition. Add both to WorkflowRpcs, service
imports/switch cases, registry metadata, and public exports. Decode parameters
before dispatch exactly like existing draft operations.
- Step 8: Open only the narrow browser seam
Add both names to OperationNameSchema and browserAllowedOperationNames.
Update tests in the same authored order. Do not derive the browser list from
generated WorkflowOperationName.
- Step 9: Run focused verification and commit
Run the focused commands from Step 6 plus:
pnpm --dir web --filter @lda/workflow-rpc typecheck
pnpm --dir web --filter @lda/server typecheck
Expected: all pass.
Commit:
git add web/packages/rpc web/apps/console/src/connection/contracts.ts web/apps/server/src
git add src/wf_core/models/steps.py tests/core/test_canonical_node_bindings.py tests/wf_contract_manifest/test_generate.py contracts/workflow-api.manifest.json
git commit -m "feat: expose canonical step binding RPCs"
Task 2: Project Canonical Selected-Step Dataflow
Files:
- Modify:
web/apps/console/src/workspace/domain/draft-workspace-models.ts - Create:
web/apps/console/src/workspace/authoring/selected-step-dataflow.ts - Create:
web/apps/console/src/workspace/authoring/selected-step-dataflow.test.ts - Modify:
web/apps/console/src/workspace/authoring/canonical-capability-form.ts - Create:
web/apps/console/src/workspace/authoring/canonical-capability-form.test.ts
Interfaces:
-
Produces:
StatePath,OutputBinding,SetStepInputBindingsInput, andSetStepOutputBindingsInputdomain types. -
Produces:
SelectedStepDataflow,projectSelectedStepDataflow(draft, stepId),inputBindingRows(...),outputBindingRows(...), and canonical row serializers. -
Produces:
bindingDiagnosticsForStep(...)for row-owned and unmatched diagnostics. -
Produces:
CapabilitySetupPatch, shared by the controller and Setup form. -
Produces: metadata presence that distinguishes absent, explicit null, and numeric values.
-
Step 1: Write failing pure projection tests
Use both a keyed draft and the equivalent compiled nodes[] draft containing:
steps: {
render: {
use: "wf.std.concat",
input: [
{ path: "input.items", target: "items" },
{ target: "separator", value: null },
{ path: "state.fallback", target: "fallback" },
],
output: [
{ source: "text", target: "state.report" },
{ source: "text", target: "state.audit.latest" },
],
retry: 0,
},
},
Assert exact list order, explicit null, repeated output source, absent timeout,
and retry: 0. Add cases for structural path objects, whole payload ., empty
lists, malformed records with explicit unsupported reasons, and a missing step.
The keyed and compiled shapes must project to the same SelectedStepDataflow.
- Step 2: Run the pure test and verify RED
Run:
pnpm --dir web --filter @lda/console test -- src/workspace/authoring/selected-step-dataflow.test.ts
Expected: failure because the module and output binding types do not exist.
- Step 3: Add canonical domain types
Add:
export type StatePath =
| string
| { readonly root: "state"; readonly parts: string[] };
export type OutputBinding = {
readonly source: LocalInputPath;
readonly target: StatePath;
};
export type SetStepInputBindingsInput = {
readonly workspaceId: string;
readonly revision: number;
readonly stepId: string;
readonly bindings: ReadonlyArray<InputBinding>;
};
export type SetStepOutputBindingsInput = {
readonly workspaceId: string;
readonly revision: number;
readonly stepId: string;
readonly bindings: ReadonlyArray<OutputBinding>;
};
- Step 4: Implement parser-first projection
Define a compound return type with a docstring:
export type SelectedStepDataflow = {
readonly stepId: string;
readonly capabilityName: string;
readonly description: string | null | undefined;
readonly retry: number | null | undefined;
readonly timeoutSeconds: number | null | undefined;
readonly inputs: ReadonlyArray<InputBinding>;
readonly outputs: ReadonlyArray<OutputBinding>;
readonly unsupported: ReadonlyArray<UnsupportedBindingRow>;
};
export type UnsupportedBindingRow = {
readonly field: "input" | "output";
readonly index: number;
readonly raw: unknown;
readonly reason: string;
};
export type CapabilitySetupPatch = {
readonly description?: string | null;
readonly retry?: number | null;
readonly timeoutSeconds?: number | null;
};
Use type guards for records and structural path variants. Preserve valid rows in order. Do not coerce malformed bindings into maps or discard them silently; append an indexed unsupported reason so the inspector can block destructive replacement until the operator acknowledges or repairs the raw shape.
Unsupported rows remain in their original positions as read-only raw previews
with an explicit Remove unsupported row action. A save stays blocked while
any unsupported row remains. Removing one is an explicit repair decision and
serializes the remaining canonical rows in their original relative order.
Although StatePath remains wire-compatible with generated types, output-row
serialization must reject bare state, state., empty structural parts, and
empty path segments. Test canonical nested targets and each rejected form.
Update canonical-capability-form.ts so absent metadata remains undefined
instead of being collapsed to null by ?? null.
- Step 5: Add schema-informed suggestions and preview helpers
Add pure helpers that return canonical strings:
workflowSourceSuggestions(inputSchema, stateSchema): ReadonlyArray<string>
capabilityLocalPathSuggestions(capabilitySchema): ReadonlyArray<string>
stateTargetSuggestions(stateSchema): ReadonlyArray<string>
inferredStateSchemaPreview(outputSchema, source, target): unknown | null
bindingDiagnosticsForStep(diagnostics, stepId, field): BindingDiagnostics
Reuse normalizeSchema, parseTOMLPath, and formatTOMLPath. Do not implement
JSON Schema compatibility in the browser; the preview only shows the selected
source schema the backend will project.
BindingDiagnostics contains row issues keyed by binding index plus unmatched
issues. Accept only these structural path forms:
nodes[N].input[M].../nodes[N].output[M]..., whereNis the selected step's index in the compilednodes[]array;/steps/<escaped-id>/input/M/...//steps/<escaped-id>/output/M/..., using JSON Pointer escaping; andbindings[M]...only whendiagnostic.step_idequals the selected step and the caller supplies the field for the focused operation.
Field-level paths without M, mismatched step_id, unknown formats, and prose
messages are unmatched. Forms receive row issues explicitly. Unmatched issues
remain in the shared ContextInspector diagnostics panel. Add positive and
negative tests for every accepted form.
- Step 6: Verify and commit
Run:
pnpm --dir web --filter @lda/console test -- src/workspace/authoring/selected-step-dataflow.test.ts src/workspace/authoring/canonical-capability-form.test.ts
pnpm --dir web --filter @lda/console typecheck
Commit:
git add web/apps/console/src/workspace/domain/draft-workspace-models.ts web/apps/console/src/workspace/authoring/selected-step-dataflow.* web/apps/console/src/workspace/authoring/canonical-capability-form*
git commit -m "feat: project selected-step dataflow"
Task 3: Add Focused Binding Methods To The Console Client
Files:
- Modify:
web/apps/console/src/workspace/domain/draft-authoring-client.ts - Modify:
web/apps/console/src/workspace/domain/draft-authoring-client.test.ts
Interfaces:
-
Consumes:
SetStepInputBindingsInputandSetStepOutputBindingsInputfrom Task 2. -
Produces:
DraftAuthoringClient.setStepInputBindings(input)andDraftAuthoringClient.setStepOutputBindings(input). -
Step 1: Write exact payload tests
Add tests that call both client methods and assert:
expect(run).toHaveBeenCalledWith(
"workflow.draft_workspaces.set_step_output_bindings",
{
workspace_id: "report",
revision: 7,
step_id: "render",
bindings: [
{ source: "text", target: "state.report" },
{ source: "text", target: "state.audit.latest" },
],
},
decodeDraftWorkspace,
);
Cover explicit input null, structural paths, empty arrays, blank identifiers, and caller-array immutability.
- Step 2: Run the client test and verify RED
Run:
pnpm --dir web --filter @lda/console test -- src/workspace/domain/draft-authoring-client.test.ts
Expected: failures because the methods are absent.
- Step 3: Implement minimal focused methods
Use requireIdentifier, copy each binding record into a fresh array, pass the
current revision unchanged, and decode with decodeDraftWorkspace. Do not add
merge flags because the UI replaces complete canonical lists.
- Step 4: Verify and commit
Run the focused test and console typecheck.
Commit:
git add web/apps/console/src/workspace/domain/draft-authoring-client.ts web/apps/console/src/workspace/domain/draft-authoring-client.test.ts
git commit -m "feat: add selected-step binding client"
Task 4: Extend The Authoring Controller Without Losing Conflict State
Files:
- Modify:
web/apps/console/src/workspace/authoring/useDraftAuthoring.ts - Modify:
web/apps/console/src/workspace/authoring/useDraftAuthoring.test.tsx
Interfaces:
-
Consumes: focused client methods from Task 3.
-
Produces:
setStepInputs(bindings),setStepOutputs(bindings),updateSetup(patch), and tab-scoped preserved submissions. -
Preserves: one pending mutation, revision conflict reload/reapply, target provenance, and selected node identity.
-
Step 1: Write failing controller tests for all three submissions
Test a selected node named render and assert:
-
setStepInputsuses the selected node id and current revision; -
setStepOutputsaccepts repeated sources and commits the returned draft; -
updateSetup({ retry: 0 })sends only retry; -
updateSetup({ timeoutSeconds: null })clears an existing timeout; -
an untouched optional field is absent from the update object;
-
duplicate clicks share the same pending promise;
-
a second different mutation is rejected while one is pending; and
-
stale-target responses do not replace the current draft.
-
Step 2: Write failing conflict/reapply tests
Return a conflict draft, change selection, reload revision 8, and call reapply. Assert the mutation still targets the original immutable step id and preserves the exact ordered rows. Add one test each for input and output submissions. Assert the preserved submission records its tab and exact serializable form payload. Controller state owns the last submitted mutation for conflict reapply; it does not own every unsaved keystroke.
- Step 3: Run controller tests and verify RED
Run:
pnpm --dir web --filter @lda/console test -- src/workspace/authoring/useDraftAuthoring.test.tsx
- Step 4: Generalize last submissions at the controller seam
Extend LastSubmission with:
| { readonly kind: "setup"; readonly targetStepId: string; readonly patch: CapabilitySetupPatch }
| { readonly kind: "inputs"; readonly targetStepId: string; readonly bindings: ReadonlyArray<InputBinding> }
| { readonly kind: "outputs"; readonly targetStepId: string; readonly bindings: ReadonlyArray<OutputBinding> }
Keep runMutation as the one lifecycle implementation. Add a small helper that
captures the selected node id before submission. Reapply dispatches through
the same focused controller methods against the refreshed revision.
- Step 5: Preserve submitted conflict state without stealing form ownership
Keep the discriminated LastSubmission as the exact submitted Setup, Inputs,
or Outputs payload. SelectedCapabilityInspector, keyed by selected stepId,
owns active tab and unsaved controlled rows. The existing mobile sheet remains
mounted while closed, so closing/reopening preserves these values. Reset local
forms only when selection changes, the operator explicitly reloads, or a
confirmed canonical response is adopted. On conflict, keep local values and
use LastSubmission for the explicit reapply action. Document this split near
the controller state seam.
- Step 6: Verify and commit
Run the focused tests and console typecheck.
Commit:
git add web/apps/console/src/workspace/authoring/useDraftAuthoring.ts web/apps/console/src/workspace/authoring/useDraftAuthoring.test.tsx
git commit -m "feat: control selected-step dataflow mutations"
Task 5: Build Presence-Aware Setup And Ordered Input Forms
Files:
- Create:
web/apps/console/src/workspace/authoring/CapabilitySetupForm.tsx - Create:
web/apps/console/src/workspace/authoring/CapabilitySetupForm.test.tsx - Create:
web/apps/console/src/workspace/authoring/StepInputBindingsForm.tsx - Create:
web/apps/console/src/workspace/authoring/StepInputBindingsForm.test.tsx - Modify:
web/apps/console/src/workspace/authoring/CapabilityNodeForm.tsx - Modify:
web/apps/console/src/workspace/authoring/CapabilityNodeForm.test.tsx - Modify:
web/apps/console/src/workspace/schema-form/schema-field.ts - Modify:
web/apps/console/src/workspace/schema-form/schema-field.test.ts
Interfaces:
-
Consumes:
CapabilitySetupPatchfrom Task 2 with omitted/null/value metadata semantics. -
Produces:
StepInputBindingsFormthat submits a completeReadonlyArray<InputBinding>. -
Consumes: input-row issues from
bindingDiagnosticsForStep(...); unmatched diagnostics remain outside the form. -
Reuses: normalized schema fields and
SchemaFieldControlfor literal editing. -
Step 1: Pin the optional metadata regression
Add a CapabilityNodeForm test that leaves retry and timeout blank, submits a
valid capability input, and expects both keys to be absent from the add value.
Add Setup tests for untouched blank, clearing an existing value, retry 0,
rejected retry -1, rejected fractional retry, rejected timeout 0, and valid
positive timeout.
- Step 2: Write failing ordered input editor tests
Render initial path, literal-null, and nested-target rows. Assert accessible
controls for target, source mode, source path/value, move up, move down, remove,
and add. Reorder rows and assert exact submission order. Select one source for
two targets and assert fan-out is preserved. Submit [] through an explicit
clear action.
- Step 3: Run tests and verify RED
Run:
pnpm --dir web --filter @lda/console test -- src/workspace/authoring/CapabilityNodeForm.test.tsx src/workspace/authoring/CapabilitySetupForm.test.tsx src/workspace/authoring/StepInputBindingsForm.test.tsx
- Step 4: Implement presence-aware setup values
Use touched-field state rather than deriving patch presence from blank text:
For add-node CapabilityNodeForm, omit blank optional metadata. Preserve the
ability to add a step with explicit retry: 0. Use min={0}/step={1} for
retry and min greater than zero for timeout, plus explicit parser messages so
native validity is not the only error surface.
- Step 5: Expose schema field lookup for literal rows
Add a pure schemaFieldAtPath(root, path) helper to schema-field.ts. Test
nested object, array, ., and missing paths. StepInputBindingsForm uses the
matched field with SchemaFieldControl; an unmatched target falls back to a
labelled JSON value editor with an explicit explanation.
- Step 6: Implement controlled ordered rows
Keep stable row ids separate from canonical data. Convert rows to canonical bindings only on change/submit. Validate:
- local target is nonblank;
- path source starts with
input.,state., orcontext.; - a row contains exactly one of path or value; and
- canonical duplicates/overlaps remain server-authoritative unless locally unambiguous.
Buttons provide reorder behavior; do not add drag sorting.
- Step 7: Verify and commit
Run focused tests, console typecheck, and React Doctor changed scope.
Commit:
git add web/apps/console/src/workspace/authoring/CapabilityNodeForm* web/apps/console/src/workspace/authoring/CapabilitySetupForm* web/apps/console/src/workspace/authoring/StepInputBindingsForm* web/apps/console/src/workspace/schema-form/schema-field*
git commit -m "feat: edit capability setup and inputs"
Task 6: Build The Output-To-State Binding Form
Files:
- Create:
web/apps/console/src/workspace/authoring/StepOutputBindingsForm.tsx - Create:
web/apps/console/src/workspace/authoring/StepOutputBindingsForm.test.tsx - Modify:
web/apps/console/src/styles/global.css
Interfaces:
-
Consumes:
OutputBinding, output/state suggestions, and inferred preview helpers from Task 2. -
Produces: complete ordered
ReadonlyArray<OutputBinding>submissions. -
Consumes: output-row issues from
bindingDiagnosticsForStep(...); unmatched diagnostics remain outside the form. -
Step 1: Write failing output form tests
Cover:
-
source choices from capability output schema;
-
target choices from existing state schema;
-
a new nested
state.report.markdowntarget; -
inferred source schema preview;
-
repeated source fan-out;
-
move up/down and remove;
-
explicit empty-list clearing confirmation;
-
malformed stored rows that block replacement instead of disappearing; and
-
row-scoped backend diagnostics.
-
Step 2: Run the focused test and verify RED
Run:
pnpm --dir web --filter @lda/console test -- src/workspace/authoring/StepOutputBindingsForm.test.tsx
- Step 3: Implement the controlled output editor
Source uses a select plus an escape-hatch text value for valid nested/whole
local paths not represented by top-level schema suggestions. Target accepts a
canonical state.* path and suggests existing fields. Render the inferred
schema as a bounded summary, with raw JSON collapsed.
Use explicit copy:
Saving a new target asks the workflow API to project this output schema into
state. Clearing bindings does not delete existing state fields.
- Step 4: Add responsive and accessible styling
Rows use a readable grid on desktop and stack inside the mobile inspector sheet. Keep add/save/clear actions reachable without horizontal scrolling. Hide no scrollbar that is needed to discover overflow.
- Step 5: Verify and commit
Run focused tests, console typecheck, and React Doctor changed scope.
Commit:
git add web/apps/console/src/workspace/authoring/StepOutputBindingsForm* web/apps/console/src/styles/global.css
git commit -m "feat: edit capability output bindings"
Task 7: Compose The Selected-Step Inspector And Acceptance Path
Files:
- Create:
web/apps/console/src/workspace/authoring/SelectedCapabilityInspector.tsx - Create:
web/apps/console/src/workspace/authoring/SelectedCapabilityInspector.test.tsx - Modify:
web/apps/console/src/workspace/authoring/ContextInspector.tsx - Modify:
web/apps/console/src/workspace/authoring/ContextInspector.test.tsx - Modify:
web/apps/console/src/workspace/authoring/authoring-graph.ts - Modify:
web/apps/console/src/workspace/authoring/authoring-graph.test.ts - Modify:
web/apps/console/src/graph/graph-model.ts - Modify:
web/apps/console/src/graph/graph-model.test.ts - Modify:
web/apps/console/src/graph/WorkflowGraph.tsx - Modify:
web/apps/console/src/graph/WorkflowGraph.test.tsx - Modify:
web/apps/console/src/workspace/authoring/DraftWorkbench.test.tsx - Modify:
web/apps/console/src/workspace/routes/DraftDetailRoute.test.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-08-09-workflow-console-selected-step-dataflow.mdtodocs/historical/superpowers/plans/2026-08-09-workflow-console-selected-step-dataflow.md
Interfaces:
-
Consumes: Tasks 2–6.
-
Produces: complete Setup/Inputs/Outputs selected-step workflow.
-
Preserves: add-capability form, route inspector, canvas inspector, operation evidence, navigation protection, and mobile sheets.
-
Step 1: Write failing composition tests
Select a capability node and assert three tabs with Setup active initially. Switch to Inputs and Outputs without clearing local rows. Submit each form and assert the correct controller method, selected step id, and returned revision. Add tests for loading/error capability detail, unsupported stored binding shape, diagnostics, saving state, revision conflict, reload, and reapply. Close and reopen the mounted mobile sheet and assert the active tab and unsaved rows survive. Change the selected step and assert the inspector rehydrates from that step's canonical data instead of leaking the previous form.
- Step 2: Write failing graph summary tests
Project a node with two inputs and one output. Assert its graph detail is
2 inputs · 1 state write. Use singular forms for one and omit the summary for
zero/zero. Assert buildWorkflowGraph carries a distinct summary field and
the generic WorkflowGraph renders it without replacing the existing
description/detail, route-edge selection, or node handles.
- Step 3: Run integration tests and verify RED
Run:
pnpm --dir web --filter @lda/console test -- src/workspace/authoring/SelectedCapabilityInspector.test.tsx src/workspace/authoring/ContextInspector.test.tsx src/workspace/authoring/authoring-graph.test.ts src/graph/graph-model.test.ts src/graph/WorkflowGraph.test.tsx src/workspace/authoring/DraftWorkbench.test.tsx src/workspace/routes/DraftDetailRoute.test.tsx
- Step 4: Extract selected capability composition
Move the selected-node capability branch out of ContextInspector into
SelectedCapabilityInspector. Keep selection routing and generic diagnostics
in ContextInspector. Use stable tab ids and role="tablist", role="tab",
and role="tabpanel". Restore the active tab and preserved form after mobile
sheet close/reopen and conflict reapply.
- Step 5: Add truthful node summaries
Set an authoring-only raw-node summary from canonical binding counts and carry
it into a new optional WorkflowGraphNodeData.summary. Render data.summary
below the node reference while preserving data.detail for descriptions. Do
not draw binding edges or State nodes in this slice.
- Step 6: Run full automated verification
Run:
pnpm --dir web test
pnpm --dir web typecheck
pnpm --dir web build
Push-Location web/apps/console
npx react-doctor@latest --verbose --scope changed
Pop-Location
git diff --check
Expected: all tests and typechecks pass; build succeeds with only previously accepted warnings; React Doctor does not regress.
- Step 7: Run real-server browser smoke
With the console, Hono server, and wf-rpc-server running:
- Connect the console to
http://127.0.0.1:8765/rpc. - Open a disposable draft containing a
wf.std.constantorwf.std.concatcapability step. - Confirm blank retry and timeout submit without entering zero.
- Replace ordered input bindings, including one literal.
- Bind one capability output to two
state.*targets. - Confirm the returned revision increments and the state schema includes new projected targets.
- Reload the route and confirm canonical rows persist in the same order.
- Clear output bindings and confirm the state declarations remain.
- Resize to the mobile breakpoint and confirm all tabs and reorder buttons remain usable.
Capture a screenshot and operation evidence for the input and output success
states under the gitignored .visual-smoke/ directory.
- Step 8: Update live documentation and archive the plan
Document selected-step Setup/Inputs/Outputs and canonical replacement behavior
in web/README.md. Mark Roadmap Slice 3 completed and retain Slices 4–7 as
planned. Move this completed plan to the historical path and update its roadmap
link.
- Step 9: Review and commit
Run the requesting-code-review skill on the complete slice. Fix all valid Critical and Important findings, rerun affected verification, then commit:
git add web/apps/console web/README.md docs/current_roadmap.md docs/superpowers/specs/2026-08-09-workflow-console-selected-step-dataflow-design.md docs/historical/superpowers/plans/2026-08-09-workflow-console-selected-step-dataflow.md
git commit -m "feat: edit selected-step dataflow"