docs: complete scene 8 chat entry slice

This commit is contained in:
lda
2026-07-12 05:15:56 +07:00 Verified
parent 3158060211
commit d1128c0b49
9 changed files with 60 additions and 28 deletions
+5 -3
View File
@@ -267,9 +267,11 @@ The next presentation work is intentionally split into five implementation
slices followed by a rehearsal gate. The broader story-flow review remains a slices followed by a rehearsal gate. The broader story-flow review remains a
separate activity after these surfaces are stable. separate activity after these surfaces are stable.
1. **Scene 8 chat entry:** replace the standalone run button with a full-screen 1. **Completed: Scene 8 chat entry:** replaced the standalone run button with
assistant-style chat entry. The composer/send action starts the first a full-screen assistant-style chat entry. The local composer/send action
prepared authoring conversation; it does not start the workflow run. reveals the first prepared authoring conversation without starting a
workflow run. Implementation:
[`Scene 8 chat entry`](historical/superpowers/plans/2026-07-12-scene-8-chat-entry.md).
2. **Scene 9 assistant modal:** remove the lower chat dock, give the prepared 2. **Scene 9 assistant modal:** remove the lower chat dock, give the prepared
lifecycle canvas the available stage, and expose the synchronized authoring lifecycle canvas the available stage, and expose the synchronized authoring
conversation through an assistant-ui modal anchored on the left. conversation through an assistant-ui modal anchored on the left.
@@ -1,5 +1,8 @@
# Scene 8 Chat Entry Implementation Plan # Scene 8 Chat Entry Implementation Plan
> Historical: completed on 2026-07-12. Task commits and final verification are
> recorded in repository history.
> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to implement this plan task-by-task. Steps use checkbox syntax for tracking. > **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to implement this plan task-by-task. Steps use checkbox syntax for tracking.
**Goal:** Replace Scene 8's phase-rail transcript and standalone run button with a full-screen assistant-style chat entry whose Send action reveals the first deterministic authoring turn without starting a workflow run. **Goal:** Replace Scene 8's phase-rail transcript and standalone run button with a full-screen assistant-style chat entry whose Send action reveals the first deterministic authoring turn without starting a workflow run.
+12 -9
View File
@@ -180,8 +180,11 @@ claim boundaries and future work explicit, and end on the canonical defense
discussion index rather than a benchmark or generic conclusion. discussion index rather than a benchmark or generic conclusion.
Scenes 8 and 9 use the canonical prepared-authoring recording as their only Scenes 8 and 9 use the canonical prepared-authoring recording as their only
execution evidence. Scene 8 is a full-screen conversation that establishes the execution evidence. Scene 8 is a full-screen chat entry: its prefilled request
external agent handoff; Scene 9 breaks the prepared authoring into five phases is submitted locally, then reveals the first deterministic user, assistant, and
Discover tool group. It is deterministic replay, not a live LLM chat, and does
not start a workflow run. The handoff beat reveals the full prepared conversation;
Scene 9 breaks the prepared authoring into five phases
with the same conversation contracted into a synchronized bottom dock. Each with the same conversation contracted into a synchronized bottom dock. Each
phase opens its matching prepared tool group beneath a factual source, graph, phase opens its matching prepared tool group beneath a factual source, graph,
repair, artifact, or deployment view. Neither scene calls workflow authoring repair, artifact, or deployment view. Neither scene calls workflow authoring
@@ -330,11 +333,11 @@ Scenes 8 and 9 are the prepared authoring story. They use deterministic data
from the committed `projectPreparedAuthoring()` recording and never call from the committed `projectPreparedAuthoring()` recording and never call
workflow authoring RPC operations. workflow authoring RPC operations.
- **Scene 8 (Agent Handoff)**: a full-screen prepared conversation that - **Scene 8 (Agent Handoff)**: a full-screen deterministic chat entry that
establishes the external agent interface. The `request` beat shows the pre-fills the report-authoring request. Send is local presentation state and
operator requesting a report workflow; the `handoff` beat reveals the full reveals the first prepared Discover tool group; the `handoff` beat reveals
completed conversation with prepared `wf` tool groups across all authoring the full completed conversation with prepared `wf` tool groups across all
phases. authoring phases. This is not a live LLM chat or workflow run.
- **Scene 9 (Prepared Workflow Lifecycle)**: a five-phase lifecycle - **Scene 9 (Prepared Workflow Lifecycle)**: a five-phase lifecycle
(discover, draft, validate, artifact, deployment) with a compact phase rail (discover, draft, validate, artifact, deployment) with a compact phase rail
and one dominant factual product projection per beat. The Scene 8 thread and one dominant factual product projection per beat. The Scene 8 thread
@@ -342,8 +345,8 @@ workflow authoring RPC operations.
current beat. There is no detached trace modal or second transcript. current beat. There is no detached trace modal or second transcript.
The authoring scenes consume deterministic prepared data and never call The authoring scenes consume deterministic prepared data and never call
workflow authoring RPCs — only `workflow.health` infrastructure pings are workflow authoring RPCs. Scene 8 also skips live target probing so its request
permitted. and Send path remain fully local; later execution scenes own live-run behavior.
### Demo Climax (Scenes 1012) ### Demo Climax (Scenes 1012)
@@ -263,15 +263,17 @@ describe("PresentationRoute", () => {
expect(screen.queryByText(/checking reachability/i)).not.toBeInTheDocument(); expect(screen.queryByText(/checking reachability/i)).not.toBeInTheDocument();
}); });
it("keeps Scene 8 local after a healthy target probe", async () => { it("keeps Scene 8 local with a configured target", async () => {
window.sessionStorage.setItem("lda.workflowConsole.target", "http://127.0.0.1:8765/rpc"); window.sessionStorage.setItem("lda.workflowConsole.target", "http://127.0.0.1:8765/rpc");
window.location.hash = "#scene/agent-handoff/request"; window.location.hash = "#scene/agent-handoff/request";
mockedCallOperation.mockClear();
const { PresentationRoute } = await import("./PresentationRoute.js"); const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />); render(<PresentationRoute />);
expect(await screen.findByText(/Live target ready/i)).toBeInTheDocument(); expect(await screen.findByText(/Replay evidence is active/i)).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Send" })).toBeEnabled(); expect(screen.getByRole("button", { name: "Send" })).toBeEnabled();
expect(screen.queryByRole("button", { name: "Run prepared workflow" })).not.toBeInTheDocument(); expect(screen.queryByRole("button", { name: "Run prepared workflow" })).not.toBeInTheDocument();
expect(mockedCallOperation).not.toHaveBeenCalled();
}); });
it("opens Scene 10 approval from the canonical hash", async () => { it("opens Scene 10 approval from the canonical hash", async () => {
@@ -53,7 +53,8 @@ export const PresentationRoute = () => {
const presentationTarget = useMemo(() => resolvePresentationTarget(), []); const presentationTarget = useMemo(() => resolvePresentationTarget(), []);
const demo = useDemoTimeline(presentationTarget.target, recordEvidence, recording); const demo = useDemoTimeline(presentationTarget.target, recordEvidence, recording);
const targetStatus = usePresentationTargetStatus(presentationTarget, demo.state); const isScene8 = state.location.kind === "main" && state.location.sceneId === "agent-handoff";
const targetStatus = usePresentationTargetStatus(presentationTarget, demo.state, !isScene8);
const timelineAgent = useTimelineAgent(demo, { const timelineAgent = useTimelineAgent(demo, {
mode: presentationTarget.mode === "live" ? "live" : "replay", mode: presentationTarget.mode === "live" ? "live" : "replay",
status: targetStatus, status: targetStatus,
@@ -154,9 +154,14 @@ describe("projectPreparedAuthoringThread", () => {
}); });
it("keeps the canonical projection unchanged without a request override", () => { it("keeps the canonical projection unchanged without a request override", () => {
expect(projectPreparedAuthoringThread("discover")).toEqual( const firstUser = projectPreparedAuthoringThread("discover")
projectPreparedAuthoringThread("discover"), .find((message) => message.role === "user");
); expect(firstUser?.parts).toEqual([
{
type: "text",
text: "We need to author a report workflow for the lda_report scenario. What sources and capabilities are available?",
},
]);
}); });
it("overrides only the first user request and preserves Discover tool data", () => { it("overrides only the first user request and preserves Discover tool data", () => {
@@ -287,15 +287,15 @@ export const projectPreparedAuthoringThread = (
let requestReplaced = false; let requestReplaced = false;
return recording.slice(0, finalPhaseIndex + 1).flatMap((phase) => { return recording.slice(0, finalPhaseIndex + 1).flatMap((phase) => {
const conversation = phase.conversation.map((turn, index) => const conversation = phase.conversation.map((turn, index) => {
agentTextMessage( const shouldReplaceRequest = requestOverride !== undefined && !requestReplaced && turn.role === "user";
if (shouldReplaceRequest) requestReplaced = true;
return agentTextMessage(
`authoring-${phase.phase}-message-${index}`, `authoring-${phase.phase}-message-${index}`,
turn.role, turn.role,
requestOverride !== undefined && !requestReplaced && turn.role === "user" shouldReplaceRequest ? requestOverride : turn.text,
? (requestReplaced = true, requestOverride) );
: turn.text, });
),
);
const groupId = authoringToolGroupId(phase.phase); const groupId = authoringToolGroupId(phase.phase);
const toolParts = phase.commands.flatMap((command, index) => { const toolParts = phase.commands.flatMap((command, index) => {
const callId = `${groupId}-command-${index}`; const callId = `${groupId}-command-${index}`;
@@ -3030,7 +3030,7 @@
padding: clamp(1.5rem, 4vw, 3.75rem) clamp(1rem, 5vw, 4.5rem); padding: clamp(1.5rem, 4vw, 3.75rem) clamp(1rem, 5vw, 4.5rem);
border: 1px solid var(--authoring-rule, var(--stage-line)); border: 1px solid var(--authoring-rule, var(--stage-line));
border-radius: 0.35rem; border-radius: 0.35rem;
background: color-mix(in oklch, var(--stage-canvas) 92%, white); background: var(--authoring-paper, var(--stage-canvas));
box-shadow: 0 1.25rem 3rem color-mix(in oklch, var(--stage-ink) 10%, transparent); box-shadow: 0 1.25rem 3rem color-mix(in oklch, var(--stage-ink) 10%, transparent);
} }
@@ -3096,7 +3096,7 @@
resize: vertical; resize: vertical;
border-color: var(--authoring-rule, var(--stage-line)); border-color: var(--authoring-rule, var(--stage-line));
border-radius: 0.2rem; border-radius: 0.2rem;
background: color-mix(in oklch, var(--stage-canvas) 82%, white); background: color-mix(in oklch, var(--authoring-paper, var(--stage-canvas)) 88%, var(--authoring-muted, var(--text-secondary)));
color: var(--authoring-ink, var(--text-primary)); color: var(--authoring-ink, var(--text-primary));
font: 1.05rem/1.45 var(--font-interface); font: 1.05rem/1.45 var(--font-interface);
} }
@@ -14,6 +14,7 @@ const liveActive = (state: DemoTimelineState): boolean =>
export const usePresentationTargetStatus = ( export const usePresentationTargetStatus = (
targetState: PresentationTargetState, targetState: PresentationTargetState,
demoState: DemoTimelineState, demoState: DemoTimelineState,
probeEnabled = true,
): PresentationTargetHealth => { ): PresentationTargetHealth => {
const [probe, setProbe] = useState<TargetProbeState>( const [probe, setProbe] = useState<TargetProbeState>(
targetState.mode === "live" ? "checking" : "none", targetState.mode === "live" ? "checking" : "none",
@@ -27,6 +28,11 @@ export const usePresentationTargetStatus = (
setFailureReason(targetState.reason); setFailureReason(targetState.reason);
return; return;
} }
if (!probeEnabled) {
setProbe("none");
setFailureReason("deterministic Scene 8 replay");
return;
}
setProbe("checking"); setProbe("checking");
setFailureReason(undefined); setFailureReason(undefined);
@@ -50,7 +56,17 @@ export const usePresentationTargetStatus = (
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [targetState]); }, [probeEnabled, targetState]);
if (!probeEnabled) {
return presentationTargetHealth({
target: null,
probe: "none",
liveActive: false,
replayActive: true,
failureReason: "deterministic Scene 8 replay",
});
}
return presentationTargetHealth({ return presentationTargetHealth({
target: targetState.mode === "live" ? targetState.target : null, target: targetState.mode === "live" ? targetState.target : null,