feat: connect scene 9 staged progression
This commit is contained in:
@@ -89,6 +89,68 @@ describe("PresentationRoute", () => {
|
||||
expect(await screen.findByLabelText("authoring phase rail")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("advances Draft to Validate once and projects the edited request", async () => {
|
||||
window.location.hash = "#scene/prepared-lifecycle/draft";
|
||||
const { PresentationRoute } = await import("./PresentationRoute.js");
|
||||
render(<PresentationRoute />);
|
||||
|
||||
const message = screen.getByRole("textbox", { name: /message to authoring assistant/i });
|
||||
await userEvent.clear(message);
|
||||
await userEvent.type(message, "Check this edited draft request");
|
||||
await userEvent.click(screen.getByRole("button", { name: /send message/i }));
|
||||
|
||||
expect(window.location.hash).toBe("#scene/prepared-lifecycle/validate");
|
||||
expect(await screen.findByText("Check this edited draft request")).toBeInTheDocument();
|
||||
expect(screen.getByRole("complementary", { name: /prepared authoring assistant/i }))
|
||||
.toHaveAttribute("data-phase", "validate");
|
||||
});
|
||||
|
||||
it("advances Artifact to Deployment once and projects the edited request", async () => {
|
||||
window.location.hash = "#scene/prepared-lifecycle/artifact";
|
||||
const { PresentationRoute } = await import("./PresentationRoute.js");
|
||||
render(<PresentationRoute />);
|
||||
|
||||
const message = screen.getByRole("textbox", { name: /message to authoring assistant/i });
|
||||
await userEvent.clear(message);
|
||||
await userEvent.type(message, "Save this edited deployment request");
|
||||
await userEvent.click(screen.getByRole("button", { name: /send message/i }));
|
||||
|
||||
expect(window.location.hash).toBe("#scene/prepared-lifecycle/deployment");
|
||||
expect(await screen.findByText("Save this edited deployment request")).toBeInTheDocument();
|
||||
expect(screen.getByRole("complementary", { name: /prepared authoring assistant/i }))
|
||||
.toHaveAttribute("data-phase", "deployment");
|
||||
});
|
||||
|
||||
it("records a Deployment run request locally without an RPC", async () => {
|
||||
window.location.hash = "#scene/prepared-lifecycle/deployment";
|
||||
const { PresentationRoute } = await import("./PresentationRoute.js");
|
||||
render(<PresentationRoute />);
|
||||
await screen.findByRole("textbox", { name: /message to authoring assistant/i });
|
||||
mockedCallOperation.mockClear();
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: /send message/i }));
|
||||
|
||||
expect(await screen.findByRole("status")).toHaveTextContent(
|
||||
"Run request prepared for the next execution slice.",
|
||||
);
|
||||
expect(mockedCallOperation.mock.calls.some(([operation]) => operation === "workflow.runs.start"))
|
||||
.toBe(false);
|
||||
});
|
||||
|
||||
it("keeps Validate submission on the current beat", async () => {
|
||||
window.location.hash = "#scene/prepared-lifecycle/validate";
|
||||
const { PresentationRoute } = await import("./PresentationRoute.js");
|
||||
render(<PresentationRoute />);
|
||||
|
||||
const message = screen.getByRole("textbox", { name: /message to authoring assistant/i });
|
||||
await userEvent.type(message, "Keep validating this draft");
|
||||
await userEvent.click(screen.getByRole("button", { name: /send message/i }));
|
||||
|
||||
expect(window.location.hash).toBe("#scene/prepared-lifecycle/validate");
|
||||
expect(screen.getByRole("complementary", { name: /prepared authoring assistant/i }))
|
||||
.toHaveAttribute("data-phase", "validate");
|
||||
});
|
||||
|
||||
it("renders audience progress chrome without rail or mode label", async () => {
|
||||
setReplayMode();
|
||||
const { PresentationRoute } = await import("./PresentationRoute.js");
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
createInitialPresentationState,
|
||||
presentationReducer,
|
||||
} from "./presentation-state.js";
|
||||
import { hashForLocation } from "./storyboard-navigation.js";
|
||||
import { hashForLocation, nextMainLocation } from "./storyboard-navigation.js";
|
||||
import type { MainLocation } from "./storyboard.js";
|
||||
import type { DemoApprovalActions, DemoApprovalUiState } from "./demo-approval-actions.js";
|
||||
import "./presentation.css";
|
||||
@@ -232,6 +232,15 @@ export const PresentationRoute = () => {
|
||||
[],
|
||||
);
|
||||
|
||||
const handleScene9Advance = useCallback(() => {
|
||||
if (
|
||||
state.location.kind !== "main"
|
||||
|| state.location.sceneId !== "prepared-lifecycle"
|
||||
|| (state.location.beatId !== "draft" && state.location.beatId !== "artifact")
|
||||
) return;
|
||||
dispatch({ type: "jump", location: nextMainLocation(state.location) });
|
||||
}, [state.location]);
|
||||
|
||||
const handleOpenDiscussion = useCallback(
|
||||
(branchId: string) => dispatch({ type: "open_discussion", branchId }),
|
||||
[],
|
||||
@@ -253,6 +262,7 @@ export const PresentationRoute = () => {
|
||||
approvalActions={approvalActions}
|
||||
targetStatus={targetStatus}
|
||||
jump={handleJump}
|
||||
onScene9Advance={handleScene9Advance}
|
||||
selectNode={(nodeId) => dispatch({ type: "select_node", nodeId })}
|
||||
openEvidence={() => dispatch({ type: "set_evidence_presentation", presentation: "inspector" })}
|
||||
closeOverlay={() => dispatch({ type: "close_overlay" })}
|
||||
|
||||
@@ -25,6 +25,7 @@ type PresentationStageProps = {
|
||||
readonly onRequestRevision?: (() => void) | undefined;
|
||||
readonly targetStatus: PresentationTargetHealth;
|
||||
readonly jump: (location: MainLocation) => void;
|
||||
readonly onScene9Advance?: (() => void) | undefined;
|
||||
readonly selectNode: (nodeId: string | null) => void;
|
||||
readonly openEvidence: () => void;
|
||||
readonly closeOverlay: () => void;
|
||||
@@ -43,6 +44,7 @@ export const PresentationStage = ({
|
||||
onRequestRevision,
|
||||
targetStatus,
|
||||
jump,
|
||||
onScene9Advance,
|
||||
selectNode,
|
||||
openEvidence,
|
||||
closeOverlay,
|
||||
@@ -87,6 +89,7 @@ export const PresentationStage = ({
|
||||
}}
|
||||
motionDisabled={state.motionDisabled}
|
||||
approvalActions={approvalActions}
|
||||
onScene9Advance={onScene9Advance}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
|
||||
@@ -34,6 +34,7 @@ type SceneBodyProps = {
|
||||
readonly onFocusPathChange: (path: readonly string[]) => void;
|
||||
readonly motionDisabled: boolean;
|
||||
readonly approvalActions?: DemoApprovalActions | undefined;
|
||||
readonly onScene9Advance?: (() => void) | undefined;
|
||||
};
|
||||
|
||||
const DiscussionLinks = ({
|
||||
@@ -304,7 +305,7 @@ const assertNever = (value: never): never => {
|
||||
throw new Error(`Unexpected view: ${value}`);
|
||||
};
|
||||
|
||||
export const SceneBody = ({ location, demo, timelineAgent, selectedNodeId, selectNode, openEvidence, openDiscussion, onFocusPathChange, motionDisabled, approvalActions }: SceneBodyProps) => {
|
||||
export const SceneBody = ({ location, demo, timelineAgent, selectedNodeId, selectNode, openEvidence, openDiscussion, onFocusPathChange, motionDisabled, approvalActions, onScene9Advance }: SceneBodyProps) => {
|
||||
const sceneId = location.kind === "main" ? location.sceneId : "positioning";
|
||||
const beatId = location.kind === "main" ? location.beatId : "landscape";
|
||||
const scene = findScene(sceneId) ?? findScene("thesis")!;
|
||||
@@ -342,7 +343,7 @@ export const SceneBody = ({ location, demo, timelineAgent, selectedNodeId, selec
|
||||
case "agent":
|
||||
return <AgentHandoffScene scene={scene} beat={beat} />;
|
||||
case "demo-lifecycle":
|
||||
return <PreparedAuthoringLifecycleScene scene={scene} beat={beat} />;
|
||||
return <PreparedAuthoringLifecycleScene scene={scene} beat={beat} onAdvance={onScene9Advance} />;
|
||||
case "demo":
|
||||
return (
|
||||
<DemoWorkflowScene
|
||||
|
||||
@@ -15,6 +15,7 @@ import { StageCaption } from "../StageCaption.js";
|
||||
type PreparedAuthoringLifecycleSceneProps = {
|
||||
readonly scene: SceneDefinition;
|
||||
readonly beat: SceneBeatDefinition;
|
||||
readonly onAdvance?: (() => void) | undefined;
|
||||
};
|
||||
|
||||
const phases: readonly { readonly id: AuthoringPhaseId; readonly label: string }[] = [
|
||||
@@ -31,7 +32,7 @@ const phases: readonly { readonly id: AuthoringPhaseId; readonly label: string }
|
||||
* Each beat shows a persistent prepared assistant beside one dominant phase
|
||||
* projection sourced from the prepared authoring recording.
|
||||
*/
|
||||
export const PreparedAuthoringLifecycleScene = ({ scene, beat }: PreparedAuthoringLifecycleSceneProps) => {
|
||||
export const PreparedAuthoringLifecycleScene = ({ scene, beat, onAdvance }: PreparedAuthoringLifecycleSceneProps) => {
|
||||
const [messageState, dispatch] = useReducer(
|
||||
scene9MessageReducer,
|
||||
initialScene9MessageState,
|
||||
@@ -60,9 +61,16 @@ export const PreparedAuthoringLifecycleScene = ({ scene, beat }: PreparedAuthori
|
||||
submittedOverrides={projectScene9SubmittedOverrides(messageState)}
|
||||
runRequested={messageState.runRequested}
|
||||
onDraftChange={(draft) => dispatch({ type: "draft_edited", draft })}
|
||||
onSubmit={() => {
|
||||
if (beatId === "draft") dispatch({ type: "draft_submitted" });
|
||||
if (beatId === "artifact") dispatch({ type: "artifact_submitted" });
|
||||
onSubmit={(submittedText) => {
|
||||
dispatch({ type: "draft_edited", draft: submittedText });
|
||||
if (beatId === "draft") {
|
||||
dispatch({ type: "draft_submitted" });
|
||||
onAdvance?.();
|
||||
}
|
||||
if (beatId === "artifact") {
|
||||
dispatch({ type: "artifact_submitted" });
|
||||
onAdvance?.();
|
||||
}
|
||||
if (beatId === "deployment") dispatch({ type: "run_requested" });
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user