Task 3: recompose approval, resume, output, and trace beats
This commit is contained in:
@@ -43,6 +43,15 @@ const demo = {
|
||||
primeReplayToStage: vi.fn(),
|
||||
} as unknown as DemoTimelineController;
|
||||
|
||||
const demoWithAppliedCount = (appliedCount: number): DemoTimelineController => ({
|
||||
...demo,
|
||||
state: {
|
||||
...demo.state,
|
||||
appliedCount,
|
||||
phase: "completed",
|
||||
},
|
||||
});
|
||||
|
||||
describe("GuidedProductMoment", () => {
|
||||
it("makes approval the primary product decision with factual panels", () => {
|
||||
render(
|
||||
@@ -107,7 +116,7 @@ describe("GuidedProductMoment", () => {
|
||||
expect(screen.getByText("Workflow input")).toBeInTheDocument();
|
||||
expect(screen.getByRole("group", { name: /operator resume decision/i })).toBeInTheDocument();
|
||||
expect(screen.queryByText("Output not created yet")).not.toBeInTheDocument();
|
||||
expect(screen.getByText(/lda.chat Thesis And Project Readiness Report/i)).toBeInTheDocument();
|
||||
expect(screen.getAllByText(/lda.chat Thesis And Project Readiness Report/i).length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("marks the primary surface for visual hierarchy", () => {
|
||||
@@ -131,4 +140,82 @@ describe("GuidedProductMoment", () => {
|
||||
expect(screen.getByRole("region", { name: /current product moment/i })).toHaveClass("guided-product-moment");
|
||||
expect(screen.getByRole("group", { name: /operator resume decision/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("approval shows input, interrupt payload, and decision but no output or trace", () => {
|
||||
render(
|
||||
<GuidedProductMoment
|
||||
beat={findBeat("typed-human-boundary", "approval")!}
|
||||
demo={demo}
|
||||
contract={contract}
|
||||
operation={null}
|
||||
approvalActions={{
|
||||
state: "ready",
|
||||
canSubmit: true,
|
||||
canCancel: true,
|
||||
submit: vi.fn(async () => {}),
|
||||
cancel: vi.fn(async () => {}),
|
||||
}}
|
||||
openEvidence={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Workflow input")).toBeInTheDocument();
|
||||
expect(screen.getByText("Interrupt payload")).toBeInTheDocument();
|
||||
expect(screen.getByRole("region", { name: /interrupt report markdown/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole("group", { name: /operator resume decision/i })).toBeInTheDocument();
|
||||
expect(screen.queryByText("Output")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Trace frames")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("resume shows operation, resume payload, and large output report", () => {
|
||||
const resumedDemo = demoWithAppliedCount(6);
|
||||
|
||||
render(
|
||||
<GuidedProductMoment
|
||||
beat={findBeat("resume-output-evidence", "resume")!}
|
||||
demo={resumedDemo}
|
||||
contract={contract}
|
||||
operation={resumeOperation}
|
||||
openEvidence={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByLabelText("workflow.runs.resume operation")).toBeInTheDocument();
|
||||
expect(screen.getByText("Resume decision")).toBeInTheDocument();
|
||||
expect(screen.getByRole("region", { name: /workflow markdown output/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("output beat makes the report and created issues primary", () => {
|
||||
const resumedDemo = demoWithAppliedCount(6);
|
||||
|
||||
render(
|
||||
<GuidedProductMoment
|
||||
beat={findBeat("resume-output-evidence", "output")!}
|
||||
demo={resumedDemo}
|
||||
contract={contract}
|
||||
operation={null}
|
||||
openEvidence={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole("region", { name: /workflow markdown output/i })).toHaveClass("run-facts-scroll-region");
|
||||
expect(screen.getByText("ISSUE-001")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("trace beat shows trace frames instead of the empty fallback after trace is primed", () => {
|
||||
const tracedDemo = demoWithAppliedCount(5);
|
||||
|
||||
render(
|
||||
<GuidedProductMoment
|
||||
beat={findBeat("resume-output-evidence", "trace")!}
|
||||
demo={tracedDemo}
|
||||
contract={contract}
|
||||
operation={null}
|
||||
openEvidence={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByText("No trace frames captured.")).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("region", { name: /workflow trace frames/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,7 +8,13 @@ import type {
|
||||
import { demoBeatLensForBeat } from "./demo-workflow-model.js";
|
||||
import { InterruptDecisionForm } from "./InterruptDecisionForm.js";
|
||||
import { OperationBlock } from "./OperationBlock.js";
|
||||
import { RunInputFacts, RunOutputFacts, RunTraceFacts } from "./RunFactsPanel.js";
|
||||
import {
|
||||
InterruptPayloadFacts,
|
||||
RunInputFacts,
|
||||
RunOutputFacts,
|
||||
RunResumeFacts,
|
||||
RunTraceFacts,
|
||||
} from "./RunFactsPanel.js";
|
||||
import type { SceneBeatDefinition } from "./storyboard.js";
|
||||
|
||||
export type GuidedProductMomentProps = {
|
||||
@@ -60,6 +66,7 @@ export const GuidedProductMoment = ({
|
||||
{moment === "approval" && contract ? (
|
||||
<div className="guided-product-moment__approval-grid">
|
||||
<RunInputFacts facts={facts} />
|
||||
<InterruptPayloadFacts facts={facts} />
|
||||
<InterruptDecisionForm
|
||||
interrupt={facts.interrupt}
|
||||
runId={demo.state.events.find((e) => e.stage === "run_start")?.resultingIds.runId ?? "unknown"}
|
||||
@@ -77,14 +84,20 @@ export const GuidedProductMoment = ({
|
||||
variant="expanded"
|
||||
openEvidence={openEvidence}
|
||||
/>
|
||||
<RunOutputFacts facts={facts} />
|
||||
<RunResumeFacts facts={facts} />
|
||||
<RunOutputFacts facts={facts} priority="report" />
|
||||
</div>
|
||||
) : null}
|
||||
{moment === "output" ? (
|
||||
<RunOutputFacts facts={facts} />
|
||||
<div className="guided-product-moment__output-grid">
|
||||
<RunOutputFacts facts={facts} priority="report" />
|
||||
</div>
|
||||
) : null}
|
||||
{moment === "trace" ? (
|
||||
<RunTraceFacts facts={facts} />
|
||||
<div className="guided-product-moment__trace-grid">
|
||||
<RunTraceFacts facts={facts} />
|
||||
<RunOutputFacts facts={facts} priority="summary" />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1200,3 +1200,38 @@
|
||||
max-width: min(62rem, 100%);
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.guided-product-moment__approval-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(13rem, 0.55fr) minmax(0, 1.2fr) minmax(18rem, 0.75fr);
|
||||
gap: 0.85rem;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.guided-product-moment__resume-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(18rem, 0.85fr) minmax(14rem, 0.45fr) minmax(0, 1fr);
|
||||
gap: 0.85rem;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.guided-product-moment__output-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.guided-product-moment__trace-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(17rem, 0.45fr);
|
||||
gap: 0.85rem;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
@container presentation-canvas (max-width: 1050px) {
|
||||
.guided-product-moment__approval-grid,
|
||||
.guided-product-moment__resume-grid,
|
||||
.guided-product-moment__trace-grid {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user