feat: integrate evaluation closing and questions
This commit is contained in:
@@ -114,6 +114,30 @@ describe("PresentationRoute", () => {
|
||||
expect(window.location.hash).toBe("#scene/positioning/landscape");
|
||||
});
|
||||
|
||||
it("returns to the questions beat after closing a discussion opened from the index", async () => {
|
||||
const user = userEvent.setup();
|
||||
window.location.hash = "#scene/conclusion/questions";
|
||||
const { PresentationRoute } = await import("./PresentationRoute.js");
|
||||
render(<PresentationRoute />);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /where is the ai agent/i }));
|
||||
expect(window.location.hash).toBe("#discuss/where-is-ai-agent");
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /return to thesis/i }));
|
||||
expect(window.location.hash).toBe("#scene/conclusion/questions");
|
||||
});
|
||||
|
||||
it("advances from the conclusion beat to the questions beat", async () => {
|
||||
window.location.hash = "#scene/conclusion/conclusion";
|
||||
const { PresentationRoute } = await import("./PresentationRoute.js");
|
||||
render(<PresentationRoute />);
|
||||
|
||||
await userEvent.keyboard("{ArrowRight}");
|
||||
|
||||
expect(window.location.hash).toBe("#scene/conclusion/questions");
|
||||
expect(await screen.findByRole("navigation", { name: /defense discussion index/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders stable chat, primary, progress, and transient evidence surfaces", async () => {
|
||||
const { PresentationRoute } = await import("./PresentationRoute.js");
|
||||
render(<PresentationRoute />);
|
||||
|
||||
@@ -39,6 +39,19 @@ const demo: DemoTimelineController = {
|
||||
|
||||
afterEach(() => cleanup());
|
||||
|
||||
const renderSceneBodyAtMainLocation = (sceneId: PresentationLocation["sceneId"], beatId: string, openDiscussion = noop) => render(
|
||||
<SceneBody
|
||||
location={{ kind: "main", sceneId, beatId, focusPath: [] }}
|
||||
demo={demo}
|
||||
selectedNodeId={null}
|
||||
selectNode={noop}
|
||||
openEvidence={noop}
|
||||
openDiscussion={openDiscussion}
|
||||
onFocusPathChange={noop}
|
||||
motionDisabled={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
describe("SceneBody", () => {
|
||||
it("renders Scene 1 as an opening decomposition visual", () => {
|
||||
const location: PresentationLocation = { kind: "main", sceneId: "thesis", beatId: "substrate", focusPath: [] };
|
||||
@@ -382,6 +395,31 @@ describe("SceneBody", () => {
|
||||
expect(openDiscussion).toHaveBeenCalledWith("hosted-automation");
|
||||
});
|
||||
|
||||
it("routes the evaluation scene through the dedicated evidence board", () => {
|
||||
renderSceneBodyAtMainLocation("evaluation", "cohort");
|
||||
|
||||
expect(screen.getByRole("group", { name: /evaluation evidence board/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("routes conclusion beats through the dedicated conclusion scene", () => {
|
||||
renderSceneBodyAtMainLocation("conclusion", "conclusion");
|
||||
|
||||
expect(screen.getByRole("region", { name: /thesis contribution boundary/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the defense discussion index on the questions beat and opens its branches", async () => {
|
||||
const user = userEvent.setup();
|
||||
const openDiscussion = vi.fn();
|
||||
renderSceneBodyAtMainLocation("conclusion", "questions", openDiscussion);
|
||||
|
||||
expect(screen.getByRole("navigation", { name: /defense discussion index/i })).toBeInTheDocument();
|
||||
expect(screen.queryByLabelText("defense discussion topics")).not.toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /where is the ai agent/i }));
|
||||
|
||||
expect(openDiscussion).toHaveBeenCalledWith("where-is-ai-agent");
|
||||
});
|
||||
|
||||
it("renders evidence before discussion links so the chip lane cannot cover evidence text", () => {
|
||||
const location: PresentationLocation = { kind: "main", sceneId: "positioning", beatId: "landscape", focusPath: [] };
|
||||
const { container } = render(
|
||||
|
||||
@@ -11,6 +11,9 @@ import {
|
||||
import { DemoLifecycleScene } from "./DemoLifecycleScene.js";
|
||||
import { DemoWorkflowScene } from "./DemoWorkflowScene.js";
|
||||
import { StageCaption } from "./StageCaption.js";
|
||||
import { ConclusionScene } from "./conclusion/ConclusionScene.js";
|
||||
import { DefenseDiscussionIndex } from "./discussion/DefenseDiscussionIndex.js";
|
||||
import { EvaluationEvidenceScene } from "./evaluation/EvaluationEvidenceScene.js";
|
||||
import { ArchitectureScene } from "./scenes/ArchitectureScene.js";
|
||||
import { OpeningThesisScene } from "./opening/OpeningThesisScene.js";
|
||||
import { ProblemLoopScene } from "./opening/ProblemLoopScene.js";
|
||||
@@ -257,32 +260,6 @@ const AuthoringScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBe
|
||||
</>
|
||||
);
|
||||
|
||||
const evaluationStats = [
|
||||
{ id: "cohort", value: "36", label: "trials" },
|
||||
{ id: "validity", value: "2", label: "challenges" },
|
||||
{ id: "findings", value: "3", label: "waves" },
|
||||
];
|
||||
|
||||
const EvaluationScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => (
|
||||
<>
|
||||
<StageCaption eyebrow={`Act I · ${scene.claimClass}`} title={scene.title}>
|
||||
<p>{beat.caption}</p>
|
||||
</StageCaption>
|
||||
<div className="scene-body__evaluation">
|
||||
{evaluationStats.map((stat) => (
|
||||
<div
|
||||
key={stat.id}
|
||||
className={`scene-body__evaluation-stat${beat.id === stat.id ? " scene-body__evaluation-stat--active" : ""}`}
|
||||
>
|
||||
<strong>{stat.value}</strong>
|
||||
<span>{stat.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="scene-body__evidence">{scene.evidencePointer}</p>
|
||||
</>
|
||||
);
|
||||
|
||||
const AgentHandoffScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => (
|
||||
<>
|
||||
<StageCaption eyebrow="Agent handoff" title={scene.title}>
|
||||
@@ -302,7 +279,10 @@ export const SceneBody = ({ location, demo, selectedNodeId, selectNode, openEvid
|
||||
const scene = findScene(sceneId) ?? findScene("thesis")!;
|
||||
const beat = findBeat(sceneId, beatId) ?? scene.beats[0]!;
|
||||
|
||||
const discussionLinks = <DiscussionLinks sceneId={scene.id} openDiscussion={openDiscussion} />;
|
||||
// The questions beat renders its own grouped discussion index. Suppress the
|
||||
// generic per-scene rail there so the same actions do not appear twice.
|
||||
const showDiscussionRail = !(scene.id === "conclusion" && beat.id === "questions");
|
||||
const discussionLinks = showDiscussionRail ? <DiscussionLinks sceneId={scene.id} openDiscussion={openDiscussion} /> : null;
|
||||
const content = (() => {
|
||||
switch (scene.view) {
|
||||
case "narrative":
|
||||
@@ -345,9 +325,11 @@ export const SceneBody = ({ location, demo, selectedNodeId, selectNode, openEvid
|
||||
/>
|
||||
);
|
||||
case "evaluation":
|
||||
return <EvaluationScene scene={scene} beat={beat} />;
|
||||
return <EvaluationEvidenceScene scene={scene} beat={beat} />;
|
||||
case "conclusion":
|
||||
return <NarrativeScene scene={scene} beat={beat} />;
|
||||
return beat.id === "questions"
|
||||
? <DefenseDiscussionIndex openDiscussion={openDiscussion} />
|
||||
: <ConclusionScene scene={scene} beat={beat} />;
|
||||
default:
|
||||
return assertNever(scene.view);
|
||||
}
|
||||
|
||||
@@ -220,6 +220,20 @@ describe("presentationReducer", () => {
|
||||
.toEqual(deepRuntimeState.location);
|
||||
});
|
||||
|
||||
it("preserves the questions beat as the discussion return location", () => {
|
||||
const atQuestions = presentationReducer(initialPresentationState, {
|
||||
type: "jump",
|
||||
location: { kind: "main", sceneId: "conclusion", beatId: "questions", focusPath: [] },
|
||||
});
|
||||
const opened = presentationReducer(atQuestions, {
|
||||
type: "open_discussion",
|
||||
branchId: "where-is-ai-agent",
|
||||
});
|
||||
|
||||
expect(opened.discussionReturn).toEqual(atQuestions.location);
|
||||
expect(presentationReducer(opened, { type: "close_discussion" }).location).toEqual(atQuestions.location);
|
||||
});
|
||||
|
||||
it("derives a receipt from beat metadata without opening an inspector", () => {
|
||||
const state = presentationReducer(initialPresentationState, {
|
||||
type: "jump",
|
||||
|
||||
Reference in New Issue
Block a user