Files
lda-wf/docs/historical/superpowers/plans/2026-07-08-demo-climax-craft-pass.md
T

35 KiB

Demo Climax Craft Pass 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: Make Scenes 9 and 10 read as one clear product demonstration: an agent-facing request becomes a persisted workflow run, reaches a typed human interrupt, resumes, and leaves inspectable evidence.

Architecture: Keep the prepared replay, JSON-RPC evidence, and existing graph components unchanged. Add a thin presentation model that describes each demo beat, then render that model through small visual components inside DemoWorkflowScene. This is a presentation craft pass, not a new console feature, transport, agent driver, or runtime workflow.

Tech Stack: React, TypeScript, Vitest, Testing Library, Motion, existing @lda/console presentation CSS, prepared replay data.

Global Constraints

  • Do not add live backend dependencies; /present must remain replay-first.
  • Do not replace React Flow or the existing WorkflowGraphStage in this slice.
  • Do not change workflow runtime, RPC schemas, demo recording format, or examples/lda_report_workflow.
  • Keep Scene 9 and Scene 10 routes and beat ids stable:
    • workflow-demo: operation, graph, interrupt
    • interrupt-evidence: approval, resume, output, trace
  • Prefer small presentation components over growing DemoWorkflowScene.tsx into a large conditional file.
  • Add comments around any non-obvious layout or evidence-projection logic.
  • Keep browser zoom overflow compatible with the current hidden-scroll stage behavior.

File Structure

  • Modify web/apps/console/src/presentation/demo-workflow-model.ts
    • Add the presentation-only beat lens that gives each demo beat a headline, proof label, active phase, and presenter-facing summary.
  • Modify web/apps/console/src/presentation/demo-workflow-model.test.ts
    • Cover every Scene 9/10 beat id and the graph execution continuity.
  • Create web/apps/console/src/presentation/DemoContinuityRail.tsx
    • Render the product story spine: agent request -> workflow run -> human boundary -> persisted evidence.
  • Create web/apps/console/src/presentation/DemoContinuityRail.test.tsx
    • Verify labels, active phase, and compact proof text.
  • Create web/apps/console/src/presentation/DemoOutcomePanel.tsx
    • Render beat-specific proof for approval, resume, output, and trace without inventing data.
  • Create web/apps/console/src/presentation/DemoOutcomePanel.test.tsx
    • Verify typed interrupt language, submitted/cancelled outcomes, persisted run continuity, output/evidence copy.
  • Modify web/apps/console/src/presentation/DemoWorkflowScene.tsx
    • Compose continuity rail, graph, operation block, contract preview, and outcome panel.
  • Modify web/apps/console/src/presentation/DemoWorkflowScene.test.tsx
    • Pin Scene 9/10 choreography and guard against losing the graph/contract/evidence.
  • Modify web/apps/console/src/presentation/styles/demo-workflow.css
    • Add layout and responsive styling for the continuity rail and outcome panel.
  • Modify web/README.md
    • Update Presentation Mode docs with the new demo-climax behavior.
  • Modify docs/current_roadmap.md
    • Mark the presentation craft pass as completed after implementation and link this plan under historical/.
  • Move this plan to docs/historical/superpowers/plans/2026-07-08-demo-climax-craft-pass.md after implementation.

Task 1: Add A Demo Beat Lens

Files:

  • Modify: web/apps/console/src/presentation/demo-workflow-model.ts
  • Modify: web/apps/console/src/presentation/demo-workflow-model.test.ts

Interfaces:

  • Consumes: demo beat ids from storyboard.ts.

  • Produces:

    • type DemoClimaxPhase = "agent" | "run" | "interrupt" | "resume" | "evidence"
    • type DemoBeatLens
    • demoBeatLensForBeat(beatId: string): DemoBeatLens
  • Step 1: Write failing tests for the beat lens

In web/apps/console/src/presentation/demo-workflow-model.test.ts, add these imports:

import {
  demoBeatLensForBeat,
  graphExecutionForBeat,
  projectInterruptContract,
  projectOperationPresentation,
} from "./demo-workflow-model.js";

If the file already imports some of these symbols, merge the imports instead of duplicating them.

Add this test block:

describe("demo beat lens", () => {
  it("defines a product-story phase for every Scene 9 and Scene 10 beat", () => {
    const beatIds = ["operation", "graph", "interrupt", "approval", "resume", "output", "trace"];

    expect(beatIds.map((beatId) => demoBeatLensForBeat(beatId).phase)).toEqual([
      "agent",
      "run",
      "interrupt",
      "interrupt",
      "resume",
      "evidence",
      "evidence",
    ]);
  });

  it("uses stable audience-facing labels for the demo climax", () => {
    expect(demoBeatLensForBeat("operation")).toMatchObject({
      eyebrow: "Agent handoff",
      headline: "Request becomes a workflow run",
      proofLabel: "workflow.runs.start",
    });
    expect(demoBeatLensForBeat("approval")).toMatchObject({
      eyebrow: "Typed human boundary",
      headline: "The run waits for an explicit resume decision",
      proofLabel: "issue_review",
    });
    expect(demoBeatLensForBeat("trace")).toMatchObject({
      eyebrow: "Evidence remains",
      headline: "The result can still be inspected after the demo",
      proofLabel: "trace frames",
    });
  });

  it("keeps graph execution continuity across the interrupt and resume beats", () => {
    expect(graphExecutionForBeat("approval")).toEqual(graphExecutionForBeat("interrupt"));
    expect(graphExecutionForBeat("resume")).toMatchObject({
      completedNodeIds: ["read_docs", "build_report", "review_issues"],
      currentNodeId: "create_issues",
    });
    expect(graphExecutionForBeat("trace")).toMatchObject({
      currentNodeId: "end_completed",
    });
  });
});
  • Step 2: Run the tests and verify they fail

Run:

pnpm --dir web --filter @lda/console test -- src/presentation/demo-workflow-model.test.ts

Expected: fail with demoBeatLensForBeat not exported.

  • Step 3: Implement the beat lens

In web/apps/console/src/presentation/demo-workflow-model.ts, add these exports after GraphExecutionPresentation:

export type DemoClimaxPhase = "agent" | "run" | "interrupt" | "resume" | "evidence";

export type DemoBeatLens = {
  readonly phase: DemoClimaxPhase;
  readonly eyebrow: string;
  readonly headline: string;
  readonly proofLabel: string;
  readonly speakerLine: string;
};

const demoBeatLensByBeat: Readonly<Record<string, DemoBeatLens>> = {
  operation: {
    phase: "agent",
    eyebrow: "Agent handoff",
    headline: "Request becomes a workflow run",
    proofLabel: "workflow.runs.start",
    speakerLine: "The AI-facing layer does not own execution; it asks lda.chat to start a durable workflow run.",
  },
  graph: {
    phase: "run",
    eyebrow: "Durable graph",
    headline: "The product owns the reusable workflow shape",
    proofLabel: "typed graph",
    speakerLine: "The graph is the reusable automation boundary, not a one-off transcript of tool calls.",
  },
  interrupt: {
    phase: "interrupt",
    eyebrow: "Typed pause",
    headline: "Execution stops at a declared human boundary",
    proofLabel: "issue_review",
    speakerLine: "The runtime exposes what decision is needed before any mutation continues.",
  },
  approval: {
    phase: "interrupt",
    eyebrow: "Typed human boundary",
    headline: "The run waits for an explicit resume decision",
    proofLabel: "issue_review",
    speakerLine: "The operator answers a schema-backed request; the run remains persisted while waiting.",
  },
  resume: {
    phase: "resume",
    eyebrow: "Same run resumes",
    headline: "The approved payload continues the persisted run",
    proofLabel: "workflow.runs.resume",
    speakerLine: "Resume is not a restart; it continues the stopped run with a validated payload.",
  },
  output: {
    phase: "evidence",
    eyebrow: "Workflow output",
    headline: "The workflow produces artifacts outside the chat",
    proofLabel: "report + issues",
    speakerLine: "The result is inspectable product state: a report and issue-board changes.",
  },
  trace: {
    phase: "evidence",
    eyebrow: "Evidence remains",
    headline: "The result can still be inspected after the demo",
    proofLabel: "trace frames",
    speakerLine: "Trace and protocol evidence keep the demo auditable after the live moment is over.",
  },
};

/**
 * Presentation-only copy for the demo climax. This intentionally stays separate
 * from replay evidence parsing so changing speaker framing cannot mutate the
 * canonical run recording.
 */
export const demoBeatLensForBeat = (beatId: string): DemoBeatLens =>
  demoBeatLensByBeat[beatId] ?? {
    phase: "run",
    eyebrow: "Workflow",
    headline: "Workflow state",
    proofLabel: beatId,
    speakerLine: "This beat is not part of the prepared demo climax.",
  };
  • Step 4: Run the tests and verify they pass

Run:

pnpm --dir web --filter @lda/console test -- src/presentation/demo-workflow-model.test.ts

Expected: all tests in the file pass.

  • Step 5: Commit Task 1
git add web/apps/console/src/presentation/demo-workflow-model.ts web/apps/console/src/presentation/demo-workflow-model.test.ts
git commit -m "feat: model demo climax beats"

Task 2: Add The Demo Continuity Rail

Files:

  • Create: web/apps/console/src/presentation/DemoContinuityRail.tsx
  • Create: web/apps/console/src/presentation/DemoContinuityRail.test.tsx
  • Modify: web/apps/console/src/presentation/styles/demo-workflow.css

Interfaces:

  • Consumes: DemoBeatLens from Task 1.

  • Produces:

    • DemoContinuityRail({ lens }: { readonly lens: DemoBeatLens })
  • Step 1: Write the failing component tests

Create web/apps/console/src/presentation/DemoContinuityRail.test.tsx:

import { cleanup, render, screen, within } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";
import { demoBeatLensForBeat } from "./demo-workflow-model.js";
import { DemoContinuityRail } from "./DemoContinuityRail.js";

afterEach(() => cleanup());

describe("DemoContinuityRail", () => {
  it("renders the four-step product story spine", () => {
    render(<DemoContinuityRail lens={demoBeatLensForBeat("approval")} />);

    const rail = screen.getByLabelText("demo continuity");
    expect(within(rail).getByText("Agent request")).toBeInTheDocument();
    expect(within(rail).getByText("Workflow run")).toBeInTheDocument();
    expect(within(rail).getByText("Human boundary")).toBeInTheDocument();
    expect(within(rail).getByText("Evidence")).toBeInTheDocument();
  });

  it("marks the current phase and shows the current proof label", () => {
    render(<DemoContinuityRail lens={demoBeatLensForBeat("resume")} />);

    expect(screen.getByText("workflow.runs.resume")).toBeInTheDocument();
    expect(screen.getByText("Human boundary").closest("[data-active]")).toHaveAttribute(
      "data-active",
      "false",
    );
    expect(screen.getByText("Evidence").closest("[data-active]")).toHaveAttribute(
      "data-active",
      "false",
    );
    expect(screen.getByText("Workflow run").closest("[data-active]")).toHaveAttribute(
      "data-completed",
      "true",
    );
  });
});
  • Step 2: Run the tests and verify they fail

Run:

pnpm --dir web --filter @lda/console test -- src/presentation/DemoContinuityRail.test.tsx

Expected: fail because DemoContinuityRail.tsx does not exist.

  • Step 3: Implement DemoContinuityRail

Create web/apps/console/src/presentation/DemoContinuityRail.tsx:

import type { DemoBeatLens, DemoClimaxPhase } from "./demo-workflow-model.js";

type DemoContinuityRailProps = {
  readonly lens: DemoBeatLens;
};

type RailStep = {
  readonly phase: DemoClimaxPhase;
  readonly label: string;
  readonly detail: string;
};

const railSteps: ReadonlyArray<RailStep> = [
  { phase: "agent", label: "Agent request", detail: "thin interface" },
  { phase: "run", label: "Workflow run", detail: "typed runtime" },
  { phase: "interrupt", label: "Human boundary", detail: "schema-backed" },
  { phase: "evidence", label: "Evidence", detail: "traceable output" },
];

const phaseOrder: ReadonlyArray<DemoClimaxPhase> = [
  "agent",
  "run",
  "interrupt",
  "resume",
  "evidence",
];

const normalizedPhase = (phase: DemoClimaxPhase): DemoClimaxPhase =>
  phase === "resume" ? "interrupt" : phase;

export const DemoContinuityRail = ({ lens }: DemoContinuityRailProps) => {
  const currentPhase = normalizedPhase(lens.phase);
  const currentIndex = phaseOrder.indexOf(lens.phase);

  return (
    <section className="demo-continuity-rail" aria-label="demo continuity">
      <div className="demo-continuity-rail__proof">
        <span>{lens.eyebrow}</span>
        <strong>{lens.headline}</strong>
        <code>{lens.proofLabel}</code>
      </div>
      <ol className="demo-continuity-rail__steps">
        {railSteps.map((step) => {
          const stepIndex = phaseOrder.indexOf(step.phase);
          const active = step.phase === currentPhase;
          const completed = stepIndex < currentIndex;
          return (
            <li
              key={step.phase}
              data-active={active}
              data-completed={completed}
            >
              <span>{step.label}</span>
              <small>{step.detail}</small>
            </li>
          );
        })}
      </ol>
    </section>
  );
};
  • Step 4: Add rail CSS

Append these selectors near the other demo workflow stage selectors in web/apps/console/src/presentation/styles/demo-workflow.css:

.demo-continuity-rail {
  display: grid;
  grid-template-columns: minmax(16rem, 0.72fr) minmax(0, 1fr);
  gap: 0.9rem;
  align-items: stretch;
  flex: 0 0 auto;
  margin-bottom: 0.65rem;
}

.demo-continuity-rail__proof {
  display: grid;
  gap: 0.2rem;
  border: 1px solid oklch(0.72 0.17 195 / 0.34);
  border-radius: 0.75rem;
  padding: 0.65rem 0.8rem;
  background: oklch(0.12 0.022 250 / 0.82);
}

.demo-continuity-rail__proof span {
  color: var(--accent-cyan);
  font: 700 0.62rem/1 var(--font-mono, monospace);
  letter-spacing: 0.11em;
  text-transform: uppercase;
}

.demo-continuity-rail__proof strong {
  color: var(--text-primary);
  font-size: 0.92rem;
  line-height: 1.15;
}

.demo-continuity-rail__proof code {
  color: var(--text-secondary);
  font: 600 0.68rem/1.2 var(--font-mono, monospace);
}

.demo-continuity-rail__steps {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.45rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.demo-continuity-rail__steps li {
  display: grid;
  align-content: center;
  gap: 0.18rem;
  min-width: 0;
  border: 1px solid var(--stage-line);
  border-radius: 0.65rem;
  padding: 0.55rem 0.65rem;
  background: oklch(0.135 0.022 250 / 0.72);
  color: var(--text-muted);
}

.demo-continuity-rail__steps li[data-completed="true"] {
  border-color: oklch(0.72 0.17 195 / 0.36);
  color: var(--text-secondary);
}

.demo-continuity-rail__steps li[data-active="true"] {
  border-color: var(--accent-cyan);
  background: oklch(0.18 0.052 210 / 0.92);
  color: var(--text-primary);
}

.demo-continuity-rail__steps span {
  overflow: hidden;
  font: 700 0.74rem/1.1 var(--font-interface, sans-serif);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.demo-continuity-rail__steps small {
  color: color-mix(in oklch, currentColor 70%, transparent);
  font: 600 0.6rem/1.1 var(--font-mono, monospace);
}

@container presentation-canvas (max-width: 1050px) {
  .demo-continuity-rail {
    grid-template-columns: minmax(0, 1fr);
  }
}
  • Step 5: Run the tests and verify they pass

Run:

pnpm --dir web --filter @lda/console test -- src/presentation/DemoContinuityRail.test.tsx

Expected: all tests pass.

  • Step 6: Commit Task 2
git add web/apps/console/src/presentation/DemoContinuityRail.tsx web/apps/console/src/presentation/DemoContinuityRail.test.tsx web/apps/console/src/presentation/styles/demo-workflow.css
git commit -m "feat: add demo continuity rail"

Task 3: Add Scene 10 Outcome Proof

Files:

  • Create: web/apps/console/src/presentation/DemoOutcomePanel.tsx
  • Create: web/apps/console/src/presentation/DemoOutcomePanel.test.tsx
  • Modify: web/apps/console/src/presentation/styles/demo-workflow.css

Interfaces:

  • Consumes:

    • DemoBeatLens
    • OperationPresentation | null
    • InterruptContractPresentation | null
  • Produces:

    • DemoOutcomePanel({ beatId, lens, operation, contract })
  • Step 1: Write the failing component tests

Create web/apps/console/src/presentation/DemoOutcomePanel.test.tsx:

import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";
import {
  demoBeatLensForBeat,
  type InterruptContractPresentation,
  type OperationPresentation,
} from "./demo-workflow-model.js";
import { DemoOutcomePanel } from "./DemoOutcomePanel.js";

afterEach(() => cleanup());

const operation: OperationPresentation = {
  operation: "workflow.runs.resume",
  status: "completed",
  durationMs: 42,
  command: "uv run wf run resume run_recorded_lda_report",
  deploymentId: "lda_report_workflow.default",
  runId: "run_recorded_lda_report",
  interruptKind: null,
};

const contract: InterruptContractPresentation = {
  kind: "issue_review",
  outcomes: ["submitted", "cancelled"],
  resumeSchema: { type: "object" },
  runId: "run_recorded_lda_report",
};

describe("DemoOutcomePanel", () => {
  it("explains the approval beat as a schema-backed decision", () => {
    render(
      <DemoOutcomePanel
        beatId="approval"
        lens={demoBeatLensForBeat("approval")}
        operation={null}
        contract={contract}
      />,
    );

    expect(screen.getByLabelText("demo outcome proof")).toHaveTextContent("schema-backed");
    expect(screen.getByText("submitted / cancelled")).toBeInTheDocument();
    expect(screen.getByText("run_recorded_lda_report")).toBeInTheDocument();
  });

  it("explains resume as same-run continuation", () => {
    render(
      <DemoOutcomePanel
        beatId="resume"
        lens={demoBeatLensForBeat("resume")}
        operation={operation}
        contract={contract}
      />,
    );

    expect(screen.getByText("Same persisted run")).toBeInTheDocument();
    expect(screen.getByText("workflow.runs.resume")).toBeInTheDocument();
    expect(screen.getByText("completed")).toBeInTheDocument();
  });

  it("explains output and trace without pretending to run live services", () => {
    const { rerender } = render(
      <DemoOutcomePanel
        beatId="output"
        lens={demoBeatLensForBeat("output")}
        operation={null}
        contract={contract}
      />,
    );
    expect(screen.getByText("Report markdown")).toBeInTheDocument();
    expect(screen.getByText("Issue board changes")).toBeInTheDocument();

    rerender(
      <DemoOutcomePanel
        beatId="trace"
        lens={demoBeatLensForBeat("trace")}
        operation={operation}
        contract={contract}
      />,
    );
    expect(screen.getByText("Trace frames")).toBeInTheDocument();
    expect(screen.getByText("Protocol evidence")).toBeInTheDocument();
  });
});
  • Step 2: Run the tests and verify they fail

Run:

pnpm --dir web --filter @lda/console test -- src/presentation/DemoOutcomePanel.test.tsx

Expected: fail because DemoOutcomePanel.tsx does not exist.

  • Step 3: Implement DemoOutcomePanel

Create web/apps/console/src/presentation/DemoOutcomePanel.tsx:

import type {
  DemoBeatLens,
  InterruptContractPresentation,
  OperationPresentation,
} from "./demo-workflow-model.js";

type DemoOutcomePanelProps = {
  readonly beatId: string;
  readonly lens: DemoBeatLens;
  readonly operation: OperationPresentation | null;
  readonly contract: InterruptContractPresentation | null;
};

const outcomesText = (contract: InterruptContractPresentation | null): string =>
  contract?.outcomes.join(" / ") ?? "submitted / cancelled";

export const DemoOutcomePanel = ({
  beatId,
  lens,
  operation,
  contract,
}: DemoOutcomePanelProps) => {
  const runId = operation?.runId ?? contract?.runId ?? "run unavailable";

  if (beatId === "approval") {
    return (
      <aside className="demo-outcome-panel" aria-label="demo outcome proof">
        <span>{lens.eyebrow}</span>
        <strong>Operator sees a schema-backed request</strong>
        <dl>
          <div><dt>Run</dt><dd><code>{runId}</code></dd></div>
          <div><dt>Allowed outcomes</dt><dd>{outcomesText(contract)}</dd></div>
        </dl>
      </aside>
    );
  }

  if (beatId === "resume") {
    return (
      <aside className="demo-outcome-panel" aria-label="demo outcome proof">
        <span>{lens.eyebrow}</span>
        <strong>Same persisted run</strong>
        <dl>
          <div><dt>Operation</dt><dd><code>{operation?.operation ?? lens.proofLabel}</code></dd></div>
          <div><dt>Status</dt><dd>{operation?.status ?? "completed"}</dd></div>
        </dl>
      </aside>
    );
  }

  if (beatId === "output") {
    return (
      <aside className="demo-outcome-panel demo-outcome-panel--split" aria-label="demo outcome proof">
        <span>{lens.eyebrow}</span>
        <strong>Product state, not chat-only text</strong>
        <ul>
          <li><b>Report markdown</b><small>Generated from selected documents.</small></li>
          <li><b>Issue board changes</b><small>Created only after approval.</small></li>
        </ul>
      </aside>
    );
  }

  return (
    <aside className="demo-outcome-panel demo-outcome-panel--split" aria-label="demo outcome proof">
      <span>{lens.eyebrow}</span>
      <strong>Auditable after the moment passes</strong>
      <ul>
        <li><b>Trace frames</b><small>Node-level execution history.</small></li>
        <li><b>Protocol evidence</b><small>Raw and interpreted JSON-RPC records.</small></li>
      </ul>
    </aside>
  );
};
  • Step 4: Add outcome panel CSS

Append near the interrupt contract selectors in web/apps/console/src/presentation/styles/demo-workflow.css:

.demo-outcome-panel {
  display: grid;
  gap: 0.65rem;
  align-content: start;
  border: 1px solid oklch(0.76 0.18 70 / 0.38);
  border-radius: 0.85rem;
  padding: 0.85rem;
  background:
    linear-gradient(145deg, oklch(0.76 0.18 70 / 0.08), transparent 55%),
    oklch(0.13 0.025 250 / 0.9);
  color: var(--text-primary);
}

.demo-outcome-panel > span {
  color: var(--accent-amber);
  font: 700 0.62rem/1 var(--font-mono, monospace);
  letter-spacing: 0.11em;
  text-transform: uppercase;
}

.demo-outcome-panel > strong {
  font-size: 1rem;
  line-height: 1.18;
}

.demo-outcome-panel dl {
  display: grid;
  gap: 0.45rem;
  margin: 0;
}

.demo-outcome-panel dt {
  color: var(--text-muted);
  font: 700 0.58rem/1 var(--font-mono, monospace);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.demo-outcome-panel dd {
  margin: 0.2rem 0 0;
  color: var(--text-secondary);
  font-size: 0.78rem;
}

.demo-outcome-panel ul {
  display: grid;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.demo-outcome-panel li {
  border: 1px solid var(--stage-line);
  border-radius: 0.65rem;
  padding: 0.6rem;
  background: oklch(0.11 0.022 250 / 0.72);
}

.demo-outcome-panel b {
  display: block;
  margin-bottom: 0.25rem;
}

.demo-outcome-panel small {
  color: var(--text-secondary);
}
  • Step 5: Run tests and verify they pass

Run:

pnpm --dir web --filter @lda/console test -- src/presentation/DemoOutcomePanel.test.tsx

Expected: all tests pass.

  • Step 6: Commit Task 3
git add web/apps/console/src/presentation/DemoOutcomePanel.tsx web/apps/console/src/presentation/DemoOutcomePanel.test.tsx web/apps/console/src/presentation/styles/demo-workflow.css
git commit -m "feat: add demo outcome proof panel"

Task 4: Compose The Rail And Outcome Panel Into Scenes 9 And 10

Files:

  • Modify: web/apps/console/src/presentation/DemoWorkflowScene.tsx
  • Modify: web/apps/console/src/presentation/DemoWorkflowScene.test.tsx
  • Modify: web/apps/console/src/presentation/styles/demo-workflow.css

Interfaces:

  • Consumes:

    • demoBeatLensForBeat(beatId)
    • DemoContinuityRail
    • DemoOutcomePanel
    • existing OperationBlock, WorkflowGraphStage, InterruptContractPreview
  • Produces: Scene 9/10 route compositions with a stable continuity rail and beat-specific outcome proof.

  • Step 1: Write failing integration tests

In web/apps/console/src/presentation/DemoWorkflowScene.test.tsx, add these tests inside the existing describe("DemoWorkflowScene", ...):

it("shows the continuity rail across Scene 9 operation, graph, and interrupt beats", () => {
  const { unmount } = renderBeat("operation");
  expect(screen.getByLabelText("demo continuity")).toHaveTextContent("Request becomes a workflow run");
  expect(screen.getByLabelText("demo continuity")).toHaveTextContent("workflow.runs.start");
  unmount();

  const graph = renderBeat("graph");
  expect(screen.getByLabelText("demo continuity")).toHaveTextContent("The product owns the reusable workflow shape");
  graph.unmount();

  renderBeat("interrupt");
  expect(screen.getByLabelText("demo continuity")).toHaveTextContent("Execution stops at a declared human boundary");
});

it("adds outcome proof to approval, resume, output, and trace beats", () => {
  const approval = renderBeat("approval", "interrupt-evidence");
  expect(screen.getByLabelText("demo outcome proof")).toHaveTextContent("schema-backed");
  approval.unmount();

  const resume = renderBeat("resume", "interrupt-evidence");
  expect(screen.getByLabelText("demo outcome proof")).toHaveTextContent("Same persisted run");
  resume.unmount();

  const output = renderBeat("output", "interrupt-evidence");
  expect(screen.getByLabelText("demo outcome proof")).toHaveTextContent("Report markdown");
  output.unmount();

  renderBeat("trace", "interrupt-evidence");
  expect(screen.getByLabelText("demo outcome proof")).toHaveTextContent("Trace frames");
});
  • Step 2: Run tests and verify they fail

Run:

pnpm --dir web --filter @lda/console test -- src/presentation/DemoWorkflowScene.test.tsx

Expected: fail because demo continuity and demo outcome proof are not rendered.

  • Step 3: Compose the new components

In web/apps/console/src/presentation/DemoWorkflowScene.tsx, update imports:

import { DemoContinuityRail } from "./DemoContinuityRail.js";
import { DemoOutcomePanel } from "./DemoOutcomePanel.js";
import {
  demoBeatLensForBeat,
  graphExecutionForBeat,
  projectInterruptContract,
  projectOperationPresentation,
} from "./demo-workflow-model.js";

Replace the current model import block with the above merged import. Inside DemoWorkflowScene, after const layout = layoutForBeat(beat.id);, add:

  const lens = demoBeatLensForBeat(beat.id);
  const currentOperation = currentEvent ? projectOperationPresentation(currentEvent) : null;
  const showOutcomePanel = beat.id === "approval" || beat.id === "resume" || beat.id === "output" || beat.id === "trace";

After </StageCaption> and before <div className="demo-workflow-stage"...>, render:

      <DemoContinuityRail lens={lens} />

Inside .demo-workflow-stage, after the graph block and before the pending block, render:

          {showOutcomePanel && (
            <DemoOutcomePanel
              beatId={beat.id}
              lens={lens}
              operation={currentOperation}
              contract={contract}
            />
          )}
  • Step 4: Tune layout CSS for the combined composition

In web/apps/console/src/presentation/styles/demo-workflow.css, update .demo-workflow-stage:

.demo-workflow-stage {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

Add these layout refinements after .demo-workflow-stage__graph:has(.interrupt-contract-preview):

.demo-workflow-stage:has(.demo-outcome-panel) {
  grid-template-columns: minmax(0, 1fr) minmax(15rem, 23%);
  gap: 0.85rem;
}

.demo-workflow-stage:has(.demo-outcome-panel) .demo-workflow-stage__graph {
  min-width: 0;
}

.demo-workflow-stage[data-demo-layout="approval"] {
  grid-template-columns: minmax(0, 1.2fr) minmax(17rem, 0.55fr);
}

.demo-workflow-stage[data-demo-layout="approval"] .demo-workflow-stage__graph {
  grid-template-columns: minmax(0, 1fr) minmax(17rem, 32%);
}

.demo-workflow-stage[data-demo-layout="evidence"] {
  grid-template-columns: minmax(0, 1fr) minmax(16rem, 24%);
}

@container presentation-canvas (max-width: 1050px) {
  .demo-workflow-stage:has(.demo-outcome-panel),
  .demo-workflow-stage[data-demo-layout="approval"],
  .demo-workflow-stage[data-demo-layout="evidence"] {
    grid-template-columns: minmax(0, 1fr);
  }
}

If this causes visual crowding during screenshot smoke, prefer reducing stage-caption height and panel padding before removing the outcome panel.

  • Step 5: Run tests and verify they pass

Run:

pnpm --dir web --filter @lda/console test -- src/presentation/DemoWorkflowScene.test.tsx src/presentation/DemoContinuityRail.test.tsx src/presentation/DemoOutcomePanel.test.tsx

Expected: all tests pass.

  • Step 6: Commit Task 4
git add web/apps/console/src/presentation/DemoWorkflowScene.tsx web/apps/console/src/presentation/DemoWorkflowScene.test.tsx web/apps/console/src/presentation/styles/demo-workflow.css
git commit -m "feat: compose demo climax scenes"

Task 5: Verify In Browser And Update Docs

Files:

  • Modify: web/README.md
  • Modify: docs/current_roadmap.md
  • Move: docs/superpowers/plans/2026-07-08-demo-climax-craft-pass.md to docs/historical/superpowers/plans/2026-07-08-demo-climax-craft-pass.md

Interfaces:

  • Consumes: completed Scene 9/10 demo climax UI.

  • Produces: updated live roadmap and README documentation.

  • Step 1: Run the focused and broad verification commands

Run:

pnpm --dir web --filter @lda/console test -- src/presentation
pnpm --dir web --filter @lda/console typecheck
pnpm --dir web --filter @lda/console build
git diff --check

Expected:

  • Presentation tests pass.

  • Typecheck exits 0.

  • Build exits 0; the existing Vite chunk-size warning is acceptable.

  • git diff --check reports no whitespace errors. Windows LF/CRLF warnings are acceptable.

  • Step 2: Capture browser smoke screenshots

With pnpm dev already running, use the existing Playwright CLI flow. If no browser session exists, create one:

pnpx --package @playwright/cli playwright-cli -s=demo-climax open "http://127.0.0.1:5173/present#scene/workflow-demo/operation"

Capture these routes at 1280x720:

pnpx --package @playwright/cli playwright-cli -s=demo-climax resize 1280 720
pnpx --package @playwright/cli playwright-cli -s=demo-climax open "http://127.0.0.1:5173/present#scene/workflow-demo/operation"
pnpx --package @playwright/cli playwright-cli -s=demo-climax screenshot --filename web/apps/console/.visual-smoke/demo-climax-09-operation.png
pnpx --package @playwright/cli playwright-cli -s=demo-climax open "http://127.0.0.1:5173/present#scene/workflow-demo/interrupt"
pnpx --package @playwright/cli playwright-cli -s=demo-climax screenshot --filename web/apps/console/.visual-smoke/demo-climax-09-interrupt.png
pnpx --package @playwright/cli playwright-cli -s=demo-climax open "http://127.0.0.1:5173/present#scene/interrupt-evidence/approval"
pnpx --package @playwright/cli playwright-cli -s=demo-climax screenshot --filename web/apps/console/.visual-smoke/demo-climax-10-approval.png
pnpx --package @playwright/cli playwright-cli -s=demo-climax open "http://127.0.0.1:5173/present#scene/interrupt-evidence/output"
pnpx --package @playwright/cli playwright-cli -s=demo-climax screenshot --filename web/apps/console/.visual-smoke/demo-climax-10-output.png

Check screenshots manually. Acceptance criteria:

  • Scene 9 operation beat says the request becomes a workflow run.

  • Scene 9 interrupt beat still shows graph and typed contract.

  • Scene 10 approval beat makes the human decision obvious.

  • Scene 10 output beat makes report/issues/trace evidence visible without relying on chat text.

  • No visible scrollbars at normal 1280x720.

  • Step 3: Update web/README.md

In the Presentation Mode section, replace the current Scene 9/10 bullet list if present, or add this paragraph after the scene deep links:

Scenes 9 and 10 are the demo climax. They keep a continuity rail visible while
the prepared replay moves from agent handoff, to persisted workflow run, to
typed human interrupt, to resume/output/evidence. The rail and outcome panel are
presentation-only projections over the committed replay; they do not add live
backend dependencies.
  • Step 4: Update docs/current_roadmap.md

Under Presentation wishlist / defense readiness, change the open craft-pass item:

   5. Presentation craft pass: tune motion, discussion-chip placement, Q&A modal
      hierarchy, evidence receipt placement, and remaining generic-slide styling
      after the compositions are stable.

to:

   5. Completed: demo climax craft pass made Scenes 9 and 10 read as one
      continuous product demonstration from agent handoff, to persisted run, to
      typed interrupt, to resume/output/evidence. Implementation:
      [`demo climax craft pass`](historical/superpowers/plans/2026-07-08-demo-climax-craft-pass.md).
   6. Presentation craft pass: tune motion, discussion-chip placement, Q&A modal
      hierarchy, evidence receipt placement, and remaining generic-slide styling
      after the demo climax is stable.
  • Step 5: Archive the plan

Run:

Move-Item docs/superpowers/plans/2026-07-08-demo-climax-craft-pass.md docs/historical/superpowers/plans/2026-07-08-demo-climax-craft-pass.md

Then verify:

rg -n "demo-climax-craft-pass|Demo Climax" docs/current_roadmap.md docs/superpowers/plans docs/historical/superpowers/plans web/README.md

Expected:

  • Roadmap points to historical/superpowers/plans/2026-07-08-demo-climax-craft-pass.md.

  • Active docs/superpowers/plans/ does not contain this completed plan after archival.

  • Step 6: Commit Task 5

git add web/README.md docs/current_roadmap.md docs/historical/superpowers/plans/2026-07-08-demo-climax-craft-pass.md
git add -u docs/superpowers/plans/2026-07-08-demo-climax-craft-pass.md
git commit -m "docs: record demo climax craft pass"

Final Verification

Run:

pnpm --dir web --filter @lda/console test -- src/presentation
pnpm --dir web --filter @lda/console typecheck
pnpm --dir web --filter @lda/console build
git diff --check
git status --short

Expected:

  • Presentation tests pass.
  • Typecheck passes.
  • Build passes, with only the existing Vite chunk-size warning if it appears.
  • git diff --check has no whitespace errors.
  • git status --short is clean after all commits.

Self-Review

  • Spec coverage: Tasks 1-4 cover the Scene 9/10 demo climax, product-story continuity, typed interrupt proof, resume/output/evidence proof, and stable replay-first boundaries. Task 5 covers docs, roadmap, screenshots, and archival.
  • Placeholder scan: no placeholder markers or unspecified tests are present.
  • Type consistency: DemoBeatLens, DemoClimaxPhase, demoBeatLensForBeat, DemoContinuityRail, and DemoOutcomePanel are defined before use and imported from exact paths.
  • Scope check: this plan does not change runtime code, RPC transport, demo recording, React Flow architecture, chat framework, or live backend behavior.