feat: make scene 9 a split authoring workspace

This commit is contained in:
lda
2026-07-12 06:27:29 +07:00 Verified
parent bfad5d33b1
commit a0517bc96f
5 changed files with 88 additions and 54 deletions
@@ -62,16 +62,42 @@ describe("PreparedAuthoringLifecycleScene", () => {
expect(screen.getByRole("region", { name: label })).toBeInTheDocument(); expect(screen.getByRole("region", { name: label })).toBeInTheDocument();
}); });
it("keeps the same conversation as a synchronized bottom dock", () => { it("keeps the assistant and dominant phase visual visible as stable siblings", () => {
renderBeat("draft"); renderBeat("draft");
const workspace = screen.getByRole("region", { name: "prepared workflow authoring lifecycle" });
const assistant = screen.getByRole("complementary", { name: /prepared authoring assistant/i });
const visual = screen.getByRole("region", { name: "draft graph evidence" });
expect(workspace).toContainElement(assistant);
expect(workspace).toContainElement(visual);
expect(assistant).toHaveAttribute("data-phase", "draft");
expect(assistant).toHaveAttribute("data-surface", "prepared-replay");
expect(assistant.querySelector('[data-surface="stage"]')).toBeInTheDocument();
expect(workspace.querySelector(".prepared-lifecycle-scene__dock")).not.toBeInTheDocument();
expect(workspace.querySelector("[role='dialog']")).not.toBeInTheDocument();
expect(workspace.querySelector("[aria-label='authoring phase rail']"))
.toBeInTheDocument();
expect(visual).toHaveAttribute("data-presentation-surface", "editorial");
});
it("synchronizes the assistant phase, active group, rail, and visual", () => {
renderBeat("validate");
expect(screen.getByRole("complementary", { name: /prepared authoring assistant/i }))
.toHaveAttribute("data-phase", "validate");
expect(screen.getByRole("button", { name: /validate.*2 tool calls/i }))
.toHaveAttribute("aria-expanded", "true");
expect(screen.getByRole("button", { name: /draft.*3 tool calls/i }))
.toHaveAttribute("aria-expanded", "false");
expect(screen.getByLabelText("authoring phase rail").querySelector("[data-active='true']"))
.toHaveTextContent("Validate");
const chat = screen.getByRole("log", { name: "prepared authoring conversation" }); const chat = screen.getByRole("log", { name: "prepared authoring conversation" });
expect(chat).toHaveAttribute("data-surface", "dock"); expect(chat).toHaveAttribute("data-surface", "stage");
expect(screen.getByRole("region", { name: "draft graph evidence" })).toHaveAttribute( expect(screen.getByRole("region", { name: "validation repair evidence" })).toHaveAttribute(
"data-presentation-surface", "data-presentation-surface",
"editorial", "editorial",
); );
expect(screen.getByRole("button", { name: /draft.*3 tool calls/i }))
.toHaveAttribute("aria-expanded", "true");
}); });
it("does not render the obsolete trace modal or receipt", () => { it("does not render the obsolete trace modal or receipt", () => {
@@ -1,6 +1,6 @@
import { projectPreparedAuthoringPhase } from "./authoring-projection.js"; import { projectPreparedAuthoringPhase } from "./authoring-projection.js";
import { AuthoringConversation } from "./AuthoringConversation.js";
import { AuthoringPhaseVisual } from "./AuthoringPhaseVisual.js"; import { AuthoringPhaseVisual } from "./AuthoringPhaseVisual.js";
import { PresentationAssistantPane } from "./PresentationAssistantPane.js";
import type { AuthoringPhaseId } from "./authoring-recording.js"; import type { AuthoringPhaseId } from "./authoring-recording.js";
import type { SceneBeatDefinition, SceneDefinition } from "../storyboard.js"; import type { SceneBeatDefinition, SceneDefinition } from "../storyboard.js";
import { StageCaption } from "../StageCaption.js"; import { StageCaption } from "../StageCaption.js";
@@ -21,9 +21,8 @@ const phases: readonly { readonly id: AuthoringPhaseId; readonly label: string }
/** /**
* Scene 9 — Prepared workflow authoring lifecycle. * Scene 9 — Prepared workflow authoring lifecycle.
* *
* Each beat shows a compact orientation rail and one dominant phase projection * Each beat shows a persistent prepared assistant beside one dominant phase
* sourced from the prepared authoring recording. The same Scene 8 conversation * projection sourced from the prepared authoring recording.
* remains below the canvas as a beat-synchronized assistant dock.
*/ */
export const PreparedAuthoringLifecycleScene = ({ scene, beat }: PreparedAuthoringLifecycleSceneProps) => { export const PreparedAuthoringLifecycleScene = ({ scene, beat }: PreparedAuthoringLifecycleSceneProps) => {
// Storyboard beats normally match these IDs. Discovery is a safe projection // Storyboard beats normally match these IDs. Discovery is a safe projection
@@ -44,6 +43,8 @@ export const PreparedAuthoringLifecycleScene = ({ scene, beat }: PreparedAuthori
data-support-surface="prepared-chat" data-support-surface="prepared-chat"
data-presentation-surface="editorial" data-presentation-surface="editorial"
> >
<PresentationAssistantPane phase={beatId} />
<div className="prepared-lifecycle-scene__presentation">
<ol className="prepared-lifecycle-scene__rail" aria-label="authoring phase rail"> <ol className="prepared-lifecycle-scene__rail" aria-label="authoring phase rail">
{phases.map((phase) => ( {phases.map((phase) => (
<li key={phase.id} data-active={phase.id === beatId ? "true" : "false"}> <li key={phase.id} data-active={phase.id === beatId ? "true" : "false"}>
@@ -55,12 +56,6 @@ export const PreparedAuthoringLifecycleScene = ({ scene, beat }: PreparedAuthori
<article className="prepared-lifecycle-scene__projection" key={beatId}> <article className="prepared-lifecycle-scene__projection" key={beatId}>
<AuthoringPhaseVisual projection={projection} /> <AuthoringPhaseVisual projection={projection} />
</article> </article>
<div className="prepared-lifecycle-scene__dock">
<AuthoringConversation
throughPhase={beatId}
activePhase={beatId}
surface="dock"
/>
</div> </div>
</section> </section>
</> </>
@@ -38,7 +38,7 @@ export const PresentationAssistantPane = ({ phase }: PresentationAssistantPanePr
<AuthoringConversation <AuthoringConversation
throughPhase={phase} throughPhase={phase}
activePhase={phase} activePhase={phase}
surface="dock" surface="stage"
/> />
</div> </div>
</aside> </aside>
@@ -31,6 +31,20 @@ describe("presentation.css", () => {
expect(boardBlock).toContain("flex-shrink: 0"); expect(boardBlock).toContain("flex-shrink: 0");
}); });
it("keeps Scene 9 as a bounded adaptive split without a lower dock row", () => {
const sceneBlock = css.match(
/^\.prepared-lifecycle-scene\s*\{(?<body>[\s\S]*?)\n\}/m,
)?.groups?.body;
expect(sceneBlock).toContain("grid-template-columns:");
expect(sceneBlock).toMatch(/minmax\(15rem, 0\.35fr\).*minmax\(0, 1\.65fr\)/);
expect(sceneBlock).toContain("min-height: 0");
expect(sceneBlock).toContain("overflow: hidden");
expect(sceneBlock).not.toContain("grid-template-rows:");
expect(css).not.toContain(".prepared-lifecycle-scene__dock");
expect(css).not.toMatch(/prepared-lifecycle-scene__dock[\s\S]*position:\s*(absolute|fixed)/);
});
it("keeps evidence inside a substrate stack from wide desktop through the 1080px breakpoint", () => { it("keeps evidence inside a substrate stack from wide desktop through the 1080px breakpoint", () => {
expect(css).toMatch(/\.conclusion-map__flow\s*\{\s*display: grid;\s*grid-template-columns: repeat\(3, minmax\(0, 1fr\)\);/); expect(css).toMatch(/\.conclusion-map__flow\s*\{\s*display: grid;\s*grid-template-columns: repeat\(3, minmax\(0, 1fr\)\);/);
expect(css).toMatch(/\.conclusion-map__flow-unit--substrate-stack\s*\{[\s\S]*?grid-template-rows: auto auto;/); expect(css).toMatch(/\.conclusion-map__flow-unit--substrate-stack\s*\{[\s\S]*?grid-template-rows: auto auto;/);
@@ -3186,19 +3186,24 @@
color: var(--authoring-paper); color: var(--authoring-paper);
} }
/* /* Scene 9 — persistent assistant beside the dominant phase projection. */
Scene 9 — Prepared Workflow Authoring Lifecycle.
Compact orientation rail plus dominant phase projection.
*/
.prepared-lifecycle-scene { .prepared-lifecycle-scene {
display: grid; display: grid;
grid-template-rows: auto minmax(0, 1fr) 8.5rem; grid-template-columns: minmax(15rem, 0.35fr) minmax(0, 1.65fr);
gap: 0.55rem; gap: 0.55rem;
min-height: 0; min-height: 0;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
} }
.prepared-lifecycle-scene__presentation {
display: grid;
grid-template-rows: auto minmax(0, 1fr);
min-width: 0;
min-height: 0;
overflow: hidden;
}
.prepared-lifecycle-scene__rail { .prepared-lifecycle-scene__rail {
display: flex; display: flex;
gap: 0.35rem; gap: 0.35rem;
@@ -3251,11 +3256,6 @@
margin: 0; margin: 0;
} }
.prepared-lifecycle-scene__dock {
min-height: 0;
overflow: hidden;
}
.presentation-assistant-pane { .presentation-assistant-pane {
display: flex; display: flex;
min-width: 0; min-width: 0;
@@ -3313,8 +3313,8 @@
--authoring-ink: var(--color-editorial-ink, oklch(0.19 0.015 65)); --authoring-ink: var(--color-editorial-ink, oklch(0.19 0.015 65));
--authoring-muted: var(--color-editorial-muted, oklch(0.48 0.025 65)); --authoring-muted: var(--color-editorial-muted, oklch(0.48 0.025 65));
--authoring-rule: color-mix(in oklch, var(--authoring-muted) 32%, transparent); --authoring-rule: color-mix(in oklch, var(--authoring-muted) 32%, transparent);
grid-template-rows: auto minmax(0, 1fr) minmax(9rem, 34%); grid-template-columns: minmax(15rem, 0.35fr) minmax(0, 1.65fr);
gap: 0; gap: 0.8rem;
background: var(--authoring-paper); background: var(--authoring-paper);
color: var(--authoring-ink); color: var(--authoring-ink);
} }
@@ -3349,49 +3349,48 @@
overflow: auto; overflow: auto;
} }
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .prepared-lifecycle-scene__dock { .prepared-lifecycle-scene[data-presentation-surface="editorial"] .presentation-assistant-pane {
padding-top: 0.55rem; background: color-mix(in oklch, var(--authoring-paper) 92%, var(--authoring-muted));
background: var(--authoring-paper); border-color: var(--authoring-rule);
} }
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .assistant-operator-thread[data-surface="dock"] { .prepared-lifecycle-scene[data-presentation-surface="editorial"] .presentation-assistant-pane .assistant-operator-thread {
border-top: 0;
color: var(--authoring-ink); color: var(--authoring-ink);
} }
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .assistant-operator-thread[data-surface="dock"] .assistant-thread { .prepared-lifecycle-scene[data-presentation-surface="editorial"] .presentation-assistant-pane .assistant-thread {
border: 0; border: 0;
border-radius: 0; border-radius: 0;
background: var(--authoring-paper); background: transparent;
} }
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .assistant-message__user-bubble { .prepared-lifecycle-scene[data-presentation-surface="editorial"] .presentation-assistant-pane .assistant-message__user-bubble {
border: 1px solid var(--authoring-rule); border: 1px solid var(--authoring-rule);
border-radius: 0.2rem; border-radius: 0.2rem;
background: color-mix(in oklch, var(--authoring-paper) 86%, var(--accent-cyan)); background: color-mix(in oklch, var(--authoring-paper) 86%, var(--accent-cyan));
color: var(--authoring-ink); color: var(--authoring-ink);
} }
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .assistant-message[data-role="assistant"] { .prepared-lifecycle-scene[data-presentation-surface="editorial"] .presentation-assistant-pane .assistant-message[data-role="assistant"] {
color: var(--authoring-ink); color: var(--authoring-ink);
} }
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .assistant-operator-thread [data-slot="tool-group-root"], .prepared-lifecycle-scene[data-presentation-surface="editorial"] .presentation-assistant-pane .assistant-operator-thread [data-slot="tool-group-root"],
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .assistant-operator-thread [data-slot="tool-fallback-root"] { .prepared-lifecycle-scene[data-presentation-surface="editorial"] .presentation-assistant-pane .assistant-operator-thread [data-slot="tool-fallback-root"] {
border-color: var(--authoring-rule); border-color: var(--authoring-rule);
border-radius: 0.2rem; border-radius: 0.2rem;
background: transparent; background: transparent;
color: var(--authoring-ink); color: var(--authoring-ink);
} }
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .assistant-operator-thread [data-slot="tool-group-trigger"], .prepared-lifecycle-scene[data-presentation-surface="editorial"] .presentation-assistant-pane .assistant-operator-thread [data-slot="tool-group-trigger"],
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .assistant-operator-thread [data-slot="tool-fallback-trigger"] { .prepared-lifecycle-scene[data-presentation-surface="editorial"] .presentation-assistant-pane .assistant-operator-thread [data-slot="tool-fallback-trigger"] {
color: var(--authoring-muted); color: var(--authoring-muted);
} }
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .assistant-operator-thread [data-slot="tool-fallback-trigger-icon"], .prepared-lifecycle-scene[data-presentation-surface="editorial"] .presentation-assistant-pane .assistant-operator-thread [data-slot="tool-fallback-trigger-icon"],
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .assistant-operator-thread [data-slot="tool-fallback-trigger-chevron"], .prepared-lifecycle-scene[data-presentation-surface="editorial"] .presentation-assistant-pane .assistant-operator-thread [data-slot="tool-fallback-trigger-chevron"],
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .assistant-operator-thread [data-slot="tool-group-trigger-chevron"] { .prepared-lifecycle-scene[data-presentation-surface="editorial"] .presentation-assistant-pane .assistant-operator-thread [data-slot="tool-group-trigger-chevron"] {
color: var(--authoring-accent); color: var(--authoring-accent);
} }