Task 3: recompose approval, resume, output, and trace beats
This commit is contained in:
@@ -43,6 +43,15 @@ const demo = {
|
|||||||
primeReplayToStage: vi.fn(),
|
primeReplayToStage: vi.fn(),
|
||||||
} as unknown as DemoTimelineController;
|
} as unknown as DemoTimelineController;
|
||||||
|
|
||||||
|
const demoWithAppliedCount = (appliedCount: number): DemoTimelineController => ({
|
||||||
|
...demo,
|
||||||
|
state: {
|
||||||
|
...demo.state,
|
||||||
|
appliedCount,
|
||||||
|
phase: "completed",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
describe("GuidedProductMoment", () => {
|
describe("GuidedProductMoment", () => {
|
||||||
it("makes approval the primary product decision with factual panels", () => {
|
it("makes approval the primary product decision with factual panels", () => {
|
||||||
render(
|
render(
|
||||||
@@ -107,7 +116,7 @@ describe("GuidedProductMoment", () => {
|
|||||||
expect(screen.getByText("Workflow input")).toBeInTheDocument();
|
expect(screen.getByText("Workflow input")).toBeInTheDocument();
|
||||||
expect(screen.getByRole("group", { name: /operator resume decision/i })).toBeInTheDocument();
|
expect(screen.getByRole("group", { name: /operator resume decision/i })).toBeInTheDocument();
|
||||||
expect(screen.queryByText("Output not created yet")).not.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", () => {
|
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("region", { name: /current product moment/i })).toHaveClass("guided-product-moment");
|
||||||
expect(screen.getByRole("group", { name: /operator resume decision/i })).toBeInTheDocument();
|
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 { demoBeatLensForBeat } from "./demo-workflow-model.js";
|
||||||
import { InterruptDecisionForm } from "./InterruptDecisionForm.js";
|
import { InterruptDecisionForm } from "./InterruptDecisionForm.js";
|
||||||
import { OperationBlock } from "./OperationBlock.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";
|
import type { SceneBeatDefinition } from "./storyboard.js";
|
||||||
|
|
||||||
export type GuidedProductMomentProps = {
|
export type GuidedProductMomentProps = {
|
||||||
@@ -60,6 +66,7 @@ export const GuidedProductMoment = ({
|
|||||||
{moment === "approval" && contract ? (
|
{moment === "approval" && contract ? (
|
||||||
<div className="guided-product-moment__approval-grid">
|
<div className="guided-product-moment__approval-grid">
|
||||||
<RunInputFacts facts={facts} />
|
<RunInputFacts facts={facts} />
|
||||||
|
<InterruptPayloadFacts facts={facts} />
|
||||||
<InterruptDecisionForm
|
<InterruptDecisionForm
|
||||||
interrupt={facts.interrupt}
|
interrupt={facts.interrupt}
|
||||||
runId={demo.state.events.find((e) => e.stage === "run_start")?.resultingIds.runId ?? "unknown"}
|
runId={demo.state.events.find((e) => e.stage === "run_start")?.resultingIds.runId ?? "unknown"}
|
||||||
@@ -77,14 +84,20 @@ export const GuidedProductMoment = ({
|
|||||||
variant="expanded"
|
variant="expanded"
|
||||||
openEvidence={openEvidence}
|
openEvidence={openEvidence}
|
||||||
/>
|
/>
|
||||||
<RunOutputFacts facts={facts} />
|
<RunResumeFacts facts={facts} />
|
||||||
|
<RunOutputFacts facts={facts} priority="report" />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{moment === "output" ? (
|
{moment === "output" ? (
|
||||||
<RunOutputFacts facts={facts} />
|
<div className="guided-product-moment__output-grid">
|
||||||
|
<RunOutputFacts facts={facts} priority="report" />
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{moment === "trace" ? (
|
{moment === "trace" ? (
|
||||||
<RunTraceFacts facts={facts} />
|
<div className="guided-product-moment__trace-grid">
|
||||||
|
<RunTraceFacts facts={facts} />
|
||||||
|
<RunOutputFacts facts={facts} priority="summary" />
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1200,3 +1200,38 @@
|
|||||||
max-width: min(62rem, 100%);
|
max-width: min(62rem, 100%);
|
||||||
justify-self: center;
|
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