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();
|
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 () => {
|
it("renders audience progress chrome without rail or mode label", async () => {
|
||||||
setReplayMode();
|
setReplayMode();
|
||||||
const { PresentationRoute } = await import("./PresentationRoute.js");
|
const { PresentationRoute } = await import("./PresentationRoute.js");
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
createInitialPresentationState,
|
createInitialPresentationState,
|
||||||
presentationReducer,
|
presentationReducer,
|
||||||
} from "./presentation-state.js";
|
} 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 { MainLocation } from "./storyboard.js";
|
||||||
import type { DemoApprovalActions, DemoApprovalUiState } from "./demo-approval-actions.js";
|
import type { DemoApprovalActions, DemoApprovalUiState } from "./demo-approval-actions.js";
|
||||||
import "./presentation.css";
|
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(
|
const handleOpenDiscussion = useCallback(
|
||||||
(branchId: string) => dispatch({ type: "open_discussion", branchId }),
|
(branchId: string) => dispatch({ type: "open_discussion", branchId }),
|
||||||
[],
|
[],
|
||||||
@@ -253,6 +262,7 @@ export const PresentationRoute = () => {
|
|||||||
approvalActions={approvalActions}
|
approvalActions={approvalActions}
|
||||||
targetStatus={targetStatus}
|
targetStatus={targetStatus}
|
||||||
jump={handleJump}
|
jump={handleJump}
|
||||||
|
onScene9Advance={handleScene9Advance}
|
||||||
selectNode={(nodeId) => dispatch({ type: "select_node", nodeId })}
|
selectNode={(nodeId) => dispatch({ type: "select_node", nodeId })}
|
||||||
openEvidence={() => dispatch({ type: "set_evidence_presentation", presentation: "inspector" })}
|
openEvidence={() => dispatch({ type: "set_evidence_presentation", presentation: "inspector" })}
|
||||||
closeOverlay={() => dispatch({ type: "close_overlay" })}
|
closeOverlay={() => dispatch({ type: "close_overlay" })}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ type PresentationStageProps = {
|
|||||||
readonly onRequestRevision?: (() => void) | undefined;
|
readonly onRequestRevision?: (() => void) | undefined;
|
||||||
readonly targetStatus: PresentationTargetHealth;
|
readonly targetStatus: PresentationTargetHealth;
|
||||||
readonly jump: (location: MainLocation) => void;
|
readonly jump: (location: MainLocation) => void;
|
||||||
|
readonly onScene9Advance?: (() => void) | undefined;
|
||||||
readonly selectNode: (nodeId: string | null) => void;
|
readonly selectNode: (nodeId: string | null) => void;
|
||||||
readonly openEvidence: () => void;
|
readonly openEvidence: () => void;
|
||||||
readonly closeOverlay: () => void;
|
readonly closeOverlay: () => void;
|
||||||
@@ -43,6 +44,7 @@ export const PresentationStage = ({
|
|||||||
onRequestRevision,
|
onRequestRevision,
|
||||||
targetStatus,
|
targetStatus,
|
||||||
jump,
|
jump,
|
||||||
|
onScene9Advance,
|
||||||
selectNode,
|
selectNode,
|
||||||
openEvidence,
|
openEvidence,
|
||||||
closeOverlay,
|
closeOverlay,
|
||||||
@@ -87,6 +89,7 @@ export const PresentationStage = ({
|
|||||||
}}
|
}}
|
||||||
motionDisabled={state.motionDisabled}
|
motionDisabled={state.motionDisabled}
|
||||||
approvalActions={approvalActions}
|
approvalActions={approvalActions}
|
||||||
|
onScene9Advance={onScene9Advance}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ type SceneBodyProps = {
|
|||||||
readonly onFocusPathChange: (path: readonly string[]) => void;
|
readonly onFocusPathChange: (path: readonly string[]) => void;
|
||||||
readonly motionDisabled: boolean;
|
readonly motionDisabled: boolean;
|
||||||
readonly approvalActions?: DemoApprovalActions | undefined;
|
readonly approvalActions?: DemoApprovalActions | undefined;
|
||||||
|
readonly onScene9Advance?: (() => void) | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DiscussionLinks = ({
|
const DiscussionLinks = ({
|
||||||
@@ -304,7 +305,7 @@ const assertNever = (value: never): never => {
|
|||||||
throw new Error(`Unexpected view: ${value}`);
|
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 sceneId = location.kind === "main" ? location.sceneId : "positioning";
|
||||||
const beatId = location.kind === "main" ? location.beatId : "landscape";
|
const beatId = location.kind === "main" ? location.beatId : "landscape";
|
||||||
const scene = findScene(sceneId) ?? findScene("thesis")!;
|
const scene = findScene(sceneId) ?? findScene("thesis")!;
|
||||||
@@ -342,7 +343,7 @@ export const SceneBody = ({ location, demo, timelineAgent, selectedNodeId, selec
|
|||||||
case "agent":
|
case "agent":
|
||||||
return <AgentHandoffScene scene={scene} beat={beat} />;
|
return <AgentHandoffScene scene={scene} beat={beat} />;
|
||||||
case "demo-lifecycle":
|
case "demo-lifecycle":
|
||||||
return <PreparedAuthoringLifecycleScene scene={scene} beat={beat} />;
|
return <PreparedAuthoringLifecycleScene scene={scene} beat={beat} onAdvance={onScene9Advance} />;
|
||||||
case "demo":
|
case "demo":
|
||||||
return (
|
return (
|
||||||
<DemoWorkflowScene
|
<DemoWorkflowScene
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { StageCaption } from "../StageCaption.js";
|
|||||||
type PreparedAuthoringLifecycleSceneProps = {
|
type PreparedAuthoringLifecycleSceneProps = {
|
||||||
readonly scene: SceneDefinition;
|
readonly scene: SceneDefinition;
|
||||||
readonly beat: SceneBeatDefinition;
|
readonly beat: SceneBeatDefinition;
|
||||||
|
readonly onAdvance?: (() => void) | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const phases: readonly { readonly id: AuthoringPhaseId; readonly label: string }[] = [
|
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
|
* Each beat shows a persistent prepared assistant beside one dominant phase
|
||||||
* projection sourced from the prepared authoring recording.
|
* projection sourced from the prepared authoring recording.
|
||||||
*/
|
*/
|
||||||
export const PreparedAuthoringLifecycleScene = ({ scene, beat }: PreparedAuthoringLifecycleSceneProps) => {
|
export const PreparedAuthoringLifecycleScene = ({ scene, beat, onAdvance }: PreparedAuthoringLifecycleSceneProps) => {
|
||||||
const [messageState, dispatch] = useReducer(
|
const [messageState, dispatch] = useReducer(
|
||||||
scene9MessageReducer,
|
scene9MessageReducer,
|
||||||
initialScene9MessageState,
|
initialScene9MessageState,
|
||||||
@@ -60,9 +61,16 @@ export const PreparedAuthoringLifecycleScene = ({ scene, beat }: PreparedAuthori
|
|||||||
submittedOverrides={projectScene9SubmittedOverrides(messageState)}
|
submittedOverrides={projectScene9SubmittedOverrides(messageState)}
|
||||||
runRequested={messageState.runRequested}
|
runRequested={messageState.runRequested}
|
||||||
onDraftChange={(draft) => dispatch({ type: "draft_edited", draft })}
|
onDraftChange={(draft) => dispatch({ type: "draft_edited", draft })}
|
||||||
onSubmit={() => {
|
onSubmit={(submittedText) => {
|
||||||
if (beatId === "draft") dispatch({ type: "draft_submitted" });
|
dispatch({ type: "draft_edited", draft: submittedText });
|
||||||
if (beatId === "artifact") dispatch({ type: "artifact_submitted" });
|
if (beatId === "draft") {
|
||||||
|
dispatch({ type: "draft_submitted" });
|
||||||
|
onAdvance?.();
|
||||||
|
}
|
||||||
|
if (beatId === "artifact") {
|
||||||
|
dispatch({ type: "artifact_submitted" });
|
||||||
|
onAdvance?.();
|
||||||
|
}
|
||||||
if (beatId === "deployment") dispatch({ type: "run_requested" });
|
if (beatId === "deployment") dispatch({ type: "run_requested" });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user