fix: resolve remaining review findings for compositor

- F1: Add data-evidence-mode attr + CSS selectors for peek/open evidence widths
- F2: Always show discussion button, not just on scenes with branches
- F3: Beat-driven visuals: lifecycle/arch/positioning/authoring/evaluation highlight per beat
- F4: Add elapsed timer, speaker notes textarea, motion toggle to presenter controls
- F5: Paper-theme contrast: override card colors for readability on light backgrounds
- F6: Fix inaccurate summaries: lifecycle bypass, provider-neutral boundary strength
- F7: Fix MCP evidence pointer order
This commit is contained in:
lda
2026-07-04 18:09:09 +07:00 Verified
parent 43c7105575
commit 49ae5ebd35
9 changed files with 367 additions and 107 deletions
@@ -67,7 +67,7 @@ describe("PresentationRoute", () => {
window.location.hash = "#scene/positioning/lda-position"; window.location.hash = "#scene/positioning/lda-position";
render(<PresentationRoute />); render(<PresentationRoute />);
await userEvent.click(screen.getByRole("button", { name: /open discussion topics/i })); await userEvent.click(screen.getByRole("button", { name: /discussion topics/i }));
await userEvent.click(screen.getByRole("button", { name: /hosted automation/i })); await userEvent.click(screen.getByRole("button", { name: /hosted automation/i }));
expect(window.location.hash).toBe("#discuss/hosted-automation"); expect(window.location.hash).toBe("#discuss/hosted-automation");
@@ -172,11 +172,15 @@ export const PresentationRoute = () => {
dispatch({ type: "jump", location: { kind: "main", sceneId: state.location.sceneId, beatId: scene.beats[0]!.id } }); dispatch({ type: "jump", location: { kind: "main", sceneId: state.location.sceneId, beatId: scene.beats[0]!.id } });
}, [state.location]); }, [state.location]);
const handleToggleMotion = useCallback(() => {
dispatch({ type: "toggle_motion" });
}, []);
const composition = compositionForState(state); const composition = compositionForState(state);
const showChatDock = composition.chatMode === "dock" && state.location.kind !== "discussion"; const showChatDock = composition.chatMode === "dock" && state.location.kind !== "discussion";
return ( return (
<main className="presentation-route" aria-label="lda.chat presentation"> <main className="presentation-route" aria-label="lda.chat presentation" data-motion={state.motionDisabled ? "disabled" : "enabled"}>
<PresentationStage <PresentationStage
state={state} state={state}
demo={demo} demo={demo}
@@ -205,6 +209,7 @@ export const PresentationRoute = () => {
forceReplay={handleForceReplay} forceReplay={handleForceReplay}
resetOverrides={handleResetOverrides} resetOverrides={handleResetOverrides}
resetScene={handleResetScene} resetScene={handleResetScene}
toggleMotion={handleToggleMotion}
/> />
)} )}
<button <button
@@ -9,7 +9,7 @@ import { OperatorChat } from "./OperatorChat.js";
import type { PresentationState } from "./presentation-state.js"; import type { PresentationState } from "./presentation-state.js";
import { compositionForState } from "./presentation-state.js"; import { compositionForState } from "./presentation-state.js";
import type { DemoTimelineController } from "../demo/useDemoTimeline.js"; import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
import { discussionBranches, findScene, type PresentationLocation } from "./storyboard.js"; import { findScene, type PresentationLocation } from "./storyboard.js";
type PresentationStageProps = { type PresentationStageProps = {
readonly state: PresentationState; readonly state: PresentationState;
@@ -45,10 +45,6 @@ export const PresentationStage = ({
const composition = compositionForState(state); const composition = compositionForState(state);
const isMainScene = state.location.kind === "main"; const isMainScene = state.location.kind === "main";
const currentScene = isMainScene ? findScene(state.location.sceneId) : null;
const hasBranches = currentScene
? discussionBranches.some((b) => b.parentSceneId === currentScene.id)
: false;
return ( return (
<div <div
@@ -56,6 +52,7 @@ export const PresentationStage = ({
data-stage-theme={composition.stageTheme} data-stage-theme={composition.stageTheme}
data-chat-theme={composition.chatTheme} data-chat-theme={composition.chatTheme}
data-chat-mode={composition.chatMode} data-chat-mode={composition.chatMode}
data-evidence-mode={composition.evidenceMode}
> >
<aside className="presentation-stage__chat" aria-label="agent chat region"> <aside className="presentation-stage__chat" aria-label="agent chat region">
<OperatorChat state={state} messages={messages} onApprove={onApprove} onDeny={onDeny} /> <OperatorChat state={state} messages={messages} onApprove={onApprove} onDeny={onDeny} />
@@ -71,15 +68,13 @@ export const PresentationStage = ({
selectedNodeId={state.selectedNodeId} selectedNodeId={state.selectedNodeId}
selectNode={selectNode} selectNode={selectNode}
/> />
{hasBranches && ( <button
<button type="button"
type="button" className="presentation-stage__discussion-toggle"
className="presentation-stage__discussion-toggle" onClick={toggleDiscussionIndex}
onClick={toggleDiscussionIndex} >
> Discussion topics
Open discussion topics </button>
</button>
)}
</> </>
)} )}
{state.discussionIndexOpen && ( {state.discussionIndexOpen && (
@@ -19,6 +19,7 @@ describe("PresenterControls", () => {
forceReplay: vi.fn(), forceReplay: vi.fn(),
resetOverrides: vi.fn(), resetOverrides: vi.fn(),
resetScene: vi.fn(), resetScene: vi.fn(),
toggleMotion: vi.fn(),
}; };
it("changes stage and chat themes independently", async () => { it("changes stage and chat themes independently", async () => {
@@ -1,4 +1,4 @@
import { useEffect, useRef } from "react"; import { useEffect, useRef, useState } from "react";
import type { PresentationState } from "./presentation-state.js"; import type { PresentationState } from "./presentation-state.js";
import { compositionForState } from "./presentation-state.js"; import { compositionForState } from "./presentation-state.js";
import type { ChatMode, ChatTheme, MainLocation, PresentationLocation, StageTheme } from "./storyboard.js"; import type { ChatMode, ChatTheme, MainLocation, PresentationLocation, StageTheme } from "./storyboard.js";
@@ -15,6 +15,7 @@ type PresenterControlsProps = {
readonly forceReplay: () => void; readonly forceReplay: () => void;
readonly resetOverrides: () => void; readonly resetOverrides: () => void;
readonly resetScene: () => void; readonly resetScene: () => void;
readonly toggleMotion: () => void;
}; };
export const PresenterControls = ({ export const PresenterControls = ({
@@ -28,6 +29,7 @@ export const PresenterControls = ({
forceReplay, forceReplay,
resetOverrides, resetOverrides,
resetScene, resetScene,
toggleMotion,
}: PresenterControlsProps) => { }: PresenterControlsProps) => {
const composition = compositionForState(state); const composition = compositionForState(state);
const isMain = state.location.kind === "main"; const isMain = state.location.kind === "main";
@@ -40,6 +42,22 @@ export const PresenterControls = ({
const sceneNumber = currentScene?.number ?? 0; const sceneNumber = currentScene?.number ?? 0;
const totalScenes = mainScenes.length; const totalScenes = mainScenes.length;
const [elapsed, setElapsed] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setElapsed(Math.floor((Date.now() - state.startedAt) / 1000));
}, 1000);
return () => clearInterval(interval);
}, [state.startedAt]);
const formatTime = (seconds: number) => {
const m = Math.floor(seconds / 60);
const s = seconds % 60;
return `${m}:${s.toString().padStart(2, "0")}`;
};
const plannedMinutes = 20;
return ( return (
<div className="presenter-controls" role="dialog" aria-label="presenter controls"> <div className="presenter-controls" role="dialog" aria-label="presenter controls">
<div className="presenter-controls__nav"> <div className="presenter-controls__nav">
@@ -50,6 +68,20 @@ export const PresenterControls = ({
<span>Scene {sceneNumber}/{totalScenes}: {currentScene?.title ?? "Discussion"}</span> <span>Scene {sceneNumber}/{totalScenes}: {currentScene?.title ?? "Discussion"}</span>
{currentBeat && <span> · {beatIndex + 1}/{totalBeats} {currentBeat.title}</span>} {currentBeat && <span> · {beatIndex + 1}/{totalBeats} {currentBeat.title}</span>}
</div> </div>
<div className="presenter-controls__timer">
<span>Elapsed: {formatTime(elapsed)}</span>
<span> / {plannedMinutes}:00 planned</span>
</div>
<div className="presenter-controls__notes">
<label>
Speaker notes
<textarea
defaultValue={currentBeat?.caption ?? ""}
rows={3}
placeholder="Add notes for this beat..."
/>
</label>
</div>
<div className="presenter-controls__over"> <div className="presenter-controls__over">
<label> <label>
Stage theme Stage theme
@@ -91,6 +123,9 @@ export const PresenterControls = ({
<span>{state.playbackMode === "replay" ? "Replay" : "Live"}</span> <span>{state.playbackMode === "replay" ? "Replay" : "Live"}</span>
<button type="button" onClick={forceReplay}>Force replay fallback</button> <button type="button" onClick={forceReplay}>Force replay fallback</button>
<button type="button" onClick={resetScene}>Reset scene</button> <button type="button" onClick={resetScene}>Reset scene</button>
<button type="button" onClick={toggleMotion}>
Motion: {state.motionDisabled ? "off" : "on"}
</button>
<button type="button" onClick={resetOverrides}>Reset overrides</button> <button type="button" onClick={resetOverrides}>Reset overrides</button>
</div> </div>
</div> </div>
+126 -86
View File
@@ -29,56 +29,70 @@ const NarrativeScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBe
</> </>
); );
const PositioningScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => ( const PositioningScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => {
<> const highlightLda = beat.id === "lda-position";
<StageCaption eyebrow={`Act I · ${scene.claimClass}`} title={scene.title}> return (
<p>{beat.caption}</p> <>
</StageCaption> <StageCaption eyebrow={`Act I · ${scene.claimClass}`} title={scene.title}>
<div className="scene-body__positioning-grid"> <p>{beat.caption}</p>
<div className="scene-body__positioning-card"> </StageCaption>
<strong>Tool loops</strong> <div className="scene-body__positioning-grid">
<span>Direct action, no lifecycle</span> <div className="scene-body__positioning-card">
<strong>Tool loops</strong>
<span>Direct action, no lifecycle</span>
</div>
<div className="scene-body__positioning-card">
<strong>Scripts</strong>
<span>Simple, debuggable</span>
</div>
<div className={`scene-body__positioning-card${highlightLda ? " scene-body__positioning-card--active" : ""}`}>
<strong>lda.chat</strong>
<span>Typed lifecycle contracts</span>
</div>
<div className="scene-body__positioning-card">
<strong>Agent graphs</strong>
<span>Shared durability</span>
</div>
<div className="scene-body__positioning-card">
<strong>MCP</strong>
<span>Capability protocol</span>
</div>
</div> </div>
<div className="scene-body__positioning-card"> <p className="scene-body__evidence">{scene.evidencePointer}</p>
<strong>Scripts</strong> </>
<span>Simple, debuggable</span> );
</div> };
<div className="scene-body__positioning-card scene-body__positioning-card--active">
<strong>lda.chat</strong>
<span>Typed lifecycle contracts</span>
</div>
<div className="scene-body__positioning-card">
<strong>Agent graphs</strong>
<span>Shared durability</span>
</div>
<div className="scene-body__positioning-card">
<strong>MCP</strong>
<span>Capability protocol</span>
</div>
</div>
<p className="scene-body__evidence">{scene.evidencePointer}</p>
</>
);
const BoundaryScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => ( const BoundaryScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => {
<> const showPlanner = beat.id === "planner" || beat.id === "boundary";
<StageCaption eyebrow="Act II · implemented" title={scene.title}> const showRuntime = beat.id === "runtime" || beat.id === "boundary";
<p>{beat.caption}</p> return (
</StageCaption> <>
<div className="scene-body__boundary"> <StageCaption eyebrow="Act II · implemented" title={scene.title}>
<div className="scene-body__boundary-side"> <p>{beat.caption}</p>
<h3>Planner</h3> </StageCaption>
<p>External LLM proposes and revises workflow structure.</p> <div className="scene-body__boundary">
<div className={`scene-body__boundary-side${showPlanner ? "" : " scene-body__boundary-side--dim"}`}>
<h3>Planner</h3>
<p>External LLM proposes and revises workflow structure.</p>
</div>
<div className="scene-body__boundary-divider" />
<div className={`scene-body__boundary-side${showRuntime ? "" : " scene-body__boundary-side--dim"}`}>
<h3>Runtime</h3>
<p>Validates, executes, records, and resumes deterministically.</p>
</div>
</div> </div>
<div className="scene-body__boundary-divider" /> <p className="scene-body__evidence">{scene.evidencePointer}</p>
<div className="scene-body__boundary-side"> </>
<h3>Runtime</h3> );
<p>Validates, executes, records, and resumes deterministically.</p> };
</div>
</div> const lifecycleStages = [
<p className="scene-body__evidence">{scene.evidencePointer}</p> { id: "draft", label: "Draft" },
</> { id: "artifact", label: "Artifact" },
); { id: "deployment", label: "Deployment" },
{ id: "run", label: "Run" },
];
const LifecycleScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => ( const LifecycleScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => (
<> <>
@@ -86,11 +100,14 @@ const LifecycleScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBe
<p>{beat.caption}</p> <p>{beat.caption}</p>
</StageCaption> </StageCaption>
<div className="scene-body__lifecycle"> <div className="scene-body__lifecycle">
{["Draft", "Artifact", "Deployment", "Run"].map((stage, i) => ( {lifecycleStages.map((stage, i) => (
<div key={stage} className="scene-body__lifecycle-stage"> <div
key={stage.id}
className={`scene-body__lifecycle-stage${beat.id === stage.id ? " scene-body__lifecycle-stage--active" : ""}`}
>
<span className="scene-body__lifecycle-number">{i + 1}</span> <span className="scene-body__lifecycle-number">{i + 1}</span>
<strong>{stage}</strong> <strong>{stage.label}</strong>
{i < 3 && <span className="scene-body__lifecycle-arrow"></span>} {i < lifecycleStages.length - 1 && <span className="scene-body__lifecycle-arrow"></span>}
</div> </div>
))} ))}
</div> </div>
@@ -98,35 +115,27 @@ const LifecycleScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBe
</> </>
); );
const architectureLayers = [
{ id: "client", label: "Client operations" },
{ id: "api", label: "Transport / JSON-RPC" },
{ id: "runtime", label: "Runtime & providers" },
{ id: "node-use", label: "NodeUse", isNode: true },
];
const ArchitectureScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => ( const ArchitectureScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => (
<> <>
<StageCaption eyebrow="Act II · implemented" title={scene.title}> <StageCaption eyebrow="Act II · implemented" title={scene.title}>
<p>{beat.caption}</p> <p>{beat.caption}</p>
</StageCaption> </StageCaption>
<div className="scene-body__architecture"> <div className="scene-body__architecture">
<div className="scene-body__architecture-layer">Client operations</div> {architectureLayers.map((layer, i) => (
<div className="scene-body__architecture-arrow"></div> <div key={layer.id}>
<div className="scene-body__architecture-layer">Transport / JSON-RPC</div> <div
<div className="scene-body__architecture-arrow"></div> className={`scene-body__architecture-layer${layer.isNode ? " scene-body__architecture-layer--node" : ""}${beat.id === layer.id ? " scene-body__architecture-layer--active" : ""}`}
<div className="scene-body__architecture-layer">Runtime & providers</div> >
<div className="scene-body__architecture-arrow"></div> {layer.label}
<div className="scene-body__architecture-layer scene-body__architecture-layer--node">NodeUse</div> </div>
</div> {i < architectureLayers.length - 1 && <div className="scene-body__architecture-arrow"></div>}
<p className="scene-body__evidence">{scene.evidencePointer}</p>
</>
);
const AuthoringScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => (
<>
<StageCaption eyebrow="Act II · implemented" title={scene.title}>
<p>{beat.caption}</p>
</StageCaption>
<div className="scene-body__authoring">
{["Discover", "Author", "Diagnose", "Repair"].map((step, i) => (
<div key={step} className="scene-body__authoring-step">
<span className="scene-body__authoring-number">{i + 1}</span>
<strong>{step}</strong>
{i < 3 && <span className="scene-body__authoring-arrow"></span>}
</div> </div>
))} ))}
</div> </div>
@@ -134,24 +143,55 @@ const AuthoringScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBe
</> </>
); );
const authoringSteps = [
{ id: "discover", label: "Discover" },
{ id: "author", label: "Author" },
{ id: "diagnose", label: "Diagnose" },
{ id: "repair", label: "Repair" },
];
const AuthoringScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => (
<>
<StageCaption eyebrow="Act II · implemented" title={scene.title}>
<p>{beat.caption}</p>
</StageCaption>
<div className="scene-body__authoring">
{authoringSteps.map((step, i) => (
<div
key={step.id}
className={`scene-body__authoring-step${beat.id === step.id ? " scene-body__authoring-step--active" : ""}`}
>
<span className="scene-body__authoring-number">{i + 1}</span>
<strong>{step.label}</strong>
{i < authoringSteps.length - 1 && <span className="scene-body__authoring-arrow"></span>}
</div>
))}
</div>
<p className="scene-body__evidence">{scene.evidencePointer}</p>
</>
);
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 }) => ( const EvaluationScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => (
<> <>
<StageCaption eyebrow={`Act I · ${scene.claimClass}`} title={scene.title}> <StageCaption eyebrow={`Act I · ${scene.claimClass}`} title={scene.title}>
<p>{beat.caption}</p> <p>{beat.caption}</p>
</StageCaption> </StageCaption>
<div className="scene-body__evaluation"> <div className="scene-body__evaluation">
<div className="scene-body__evaluation-stat"> {evaluationStats.map((stat) => (
<strong>36</strong> <div
<span>trials</span> key={stat.id}
</div> className={`scene-body__evaluation-stat${beat.id === stat.id ? " scene-body__evaluation-stat--active" : ""}`}
<div className="scene-body__evaluation-stat"> >
<strong>2</strong> <strong>{stat.value}</strong>
<span>challenges</span> <span>{stat.label}</span>
</div> </div>
<div className="scene-body__evaluation-stat"> ))}
<strong>3</strong>
<span>waves</span>
</div>
</div> </div>
<p className="scene-body__evidence">{scene.evidencePointer}</p> <p className="scene-body__evidence">{scene.evidencePointer}</p>
</> </>
@@ -28,6 +28,8 @@ export type PresentationState = {
readonly chatModeOverride: ChatMode | null; readonly chatModeOverride: ChatMode | null;
readonly controlsOpen: boolean; readonly controlsOpen: boolean;
readonly discussionIndexOpen: boolean; readonly discussionIndexOpen: boolean;
readonly motionDisabled: boolean;
readonly startedAt: number;
}; };
export type PresentationAction = export type PresentationAction =
@@ -46,7 +48,8 @@ export type PresentationAction =
| { readonly type: "set_chat_theme"; readonly theme: ChatTheme | null } | { readonly type: "set_chat_theme"; readonly theme: ChatTheme | null }
| { readonly type: "set_chat_mode"; readonly mode: ChatMode | null } | { readonly type: "set_chat_mode"; readonly mode: ChatMode | null }
| { readonly type: "toggle_controls" } | { readonly type: "toggle_controls" }
| { readonly type: "toggle_discussion_index" }; | { readonly type: "toggle_discussion_index" }
| { readonly type: "toggle_motion" };
export const initialPresentationState: PresentationState = { export const initialPresentationState: PresentationState = {
location: defaultMainLocation, location: defaultMainLocation,
@@ -59,6 +62,8 @@ export const initialPresentationState: PresentationState = {
chatModeOverride: null, chatModeOverride: null,
controlsOpen: false, controlsOpen: false,
discussionIndexOpen: false, discussionIndexOpen: false,
motionDisabled: false,
startedAt: Date.now(),
}; };
const compositionForLocation = ( const compositionForLocation = (
@@ -199,5 +204,7 @@ export const presentationReducer = (
return { ...state, controlsOpen: !state.controlsOpen }; return { ...state, controlsOpen: !state.controlsOpen };
case "toggle_discussion_index": case "toggle_discussion_index":
return { ...state, discussionIndexOpen: !state.discussionIndexOpen }; return { ...state, discussionIndexOpen: !state.discussionIndexOpen };
case "toggle_motion":
return { ...state, motionDisabled: !state.motionDisabled };
} }
}; };
@@ -74,6 +74,99 @@ body {
color: oklch(0.2 0.02 80); color: oklch(0.2 0.02 80);
} }
/* Paper theme scene-body element overrides */
.presentation-stage[data-stage-theme="paper"] .scene-body__positioning-card {
border-color: oklch(0.75 0.04 80);
background: oklch(0.92 0.01 80);
color: oklch(0.15 0.01 80);
}
.presentation-stage[data-stage-theme="paper"] .scene-body__positioning-card span {
color: oklch(0.4 0.03 80);
}
.presentation-stage[data-stage-theme="paper"] .scene-body__positioning-card--active {
border-color: oklch(0.45 0.14 80);
background: oklch(0.85 0.04 80);
color: oklch(0.12 0.02 80);
}
.presentation-stage[data-stage-theme="paper"] .scene-body__boundary-side {
border-color: oklch(0.75 0.04 80);
background: oklch(0.92 0.01 80);
color: oklch(0.15 0.01 80);
}
.presentation-stage[data-stage-theme="paper"] .scene-body__boundary-side--dim {
opacity: 0.4;
}
.presentation-stage[data-stage-theme="paper"] .scene-body__boundary-divider {
background: oklch(0.5 0.08 80);
}
.presentation-stage[data-stage-theme="paper"] .scene-body__lifecycle-stage,
.presentation-stage[data-stage-theme="paper"] .scene-body__authoring-step {
border-color: oklch(0.75 0.04 80);
background: oklch(0.92 0.01 80);
color: oklch(0.15 0.01 80);
}
.presentation-stage[data-stage-theme="paper"] .scene-body__lifecycle-number,
.presentation-stage[data-stage-theme="paper"] .scene-body__authoring-number {
background: oklch(0.45 0.14 80);
color: oklch(0.97 0.005 80);
}
.presentation-stage[data-stage-theme="paper"] .scene-body__lifecycle-arrow,
.presentation-stage[data-stage-theme="paper"] .scene-body__authoring-arrow {
color: oklch(0.5 0.06 80);
}
.presentation-stage[data-stage-theme="paper"] .scene-body__lifecycle-stage--active,
.presentation-stage[data-stage-theme="paper"] .scene-body__authoring-step--active {
border-color: oklch(0.45 0.14 80);
background: oklch(0.85 0.04 80);
}
.presentation-stage[data-stage-theme="paper"] .scene-body__architecture-layer {
border-color: oklch(0.75 0.04 80);
background: oklch(0.92 0.01 80);
color: oklch(0.15 0.01 80);
}
.presentation-stage[data-stage-theme="paper"] .scene-body__architecture-layer--node {
border-color: oklch(0.45 0.14 80);
background: oklch(0.85 0.04 80);
}
.presentation-stage[data-stage-theme="paper"] .scene-body__architecture-layer--active {
border-color: oklch(0.45 0.14 80);
background: oklch(0.85 0.04 80);
}
.presentation-stage[data-stage-theme="paper"] .scene-body__architecture-arrow {
color: oklch(0.5 0.06 80);
}
.presentation-stage[data-stage-theme="paper"] .scene-body__evaluation-stat {
border-color: oklch(0.75 0.04 80);
background: oklch(0.92 0.01 80);
}
.presentation-stage[data-stage-theme="paper"] .scene-body__evaluation-stat strong {
color: oklch(0.45 0.14 80);
}
.presentation-stage[data-stage-theme="paper"] .scene-body__evaluation-stat span {
color: oklch(0.4 0.03 80);
}
.presentation-stage[data-stage-theme="paper"] .scene-body__evaluation-stat--active {
border-color: oklch(0.45 0.14 80);
background: oklch(0.85 0.04 80);
}
/* Chat themes */ /* Chat themes */
.presentation-stage[data-chat-theme="light"] .presentation-stage__chat { .presentation-stage[data-chat-theme="light"] .presentation-stage__chat {
background: oklch(0.96 0.005 80); background: oklch(0.96 0.005 80);
@@ -112,6 +205,15 @@ body {
display: none; display: none;
} }
/* Evidence modes */
.presentation-stage[data-evidence-mode="peek"] {
--evidence-width: 20rem;
}
.presentation-stage[data-evidence-mode="open"] {
--evidence-width: 24rem;
}
.presentation-stage__chat, .presentation-stage__chat,
.presentation-stage__primary, .presentation-stage__primary,
.presentation-stage__evidence { .presentation-stage__evidence {
@@ -444,6 +546,32 @@ body {
color: oklch(0.78 0.035 250); color: oklch(0.78 0.035 250);
} }
.presenter-controls__timer {
display: flex;
gap: 0.25rem;
font-family: var(--font-mono, monospace);
font-size: 0.8rem;
color: oklch(0.78 0.035 250);
}
.presenter-controls__notes label {
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.presenter-controls__notes textarea {
width: 100%;
padding: 0.35rem;
border: 1px solid oklch(0.36 0.04 250);
background: oklch(0.18 0.025 250);
color: inherit;
border-radius: 0.3rem;
font-size: 0.75rem;
font-family: inherit;
resize: vertical;
}
.presenter-controls__over label { .presenter-controls__over label {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -697,6 +825,47 @@ body {
letter-spacing: 0.05em; letter-spacing: 0.05em;
} }
/* Beat-driven active states */
.scene-body__boundary-side--dim {
opacity: 0.4;
}
.scene-body__lifecycle-stage:not(.scene-body__lifecycle-stage--active) {
opacity: 0.4;
}
.scene-body__lifecycle-stage--active {
border-color: oklch(0.7 0.16 195);
background: oklch(0.2 0.06 195);
}
.scene-body__architecture-layer:not(.scene-body__architecture-layer--active):not(.scene-body__architecture-layer--node) {
opacity: 0.4;
}
.scene-body__architecture-layer--active {
border-color: oklch(0.7 0.16 195);
background: oklch(0.2 0.06 195);
}
.scene-body__authoring-step:not(.scene-body__authoring-step--active) {
opacity: 0.4;
}
.scene-body__authoring-step--active {
border-color: oklch(0.7 0.16 195);
background: oklch(0.2 0.06 195);
}
.scene-body__evaluation-stat:not(.scene-body__evaluation-stat--active) {
opacity: 0.4;
}
.scene-body__evaluation-stat--active {
border-color: oklch(0.7 0.16 195);
background: oklch(0.2 0.06 195);
}
@media (max-height: 760px) { @media (max-height: 760px) {
.scene-rail { .scene-rail {
padding: 0.25rem 0.5rem; padding: 0.25rem 0.5rem;
@@ -725,3 +894,11 @@ body {
scroll-behavior: auto !important; scroll-behavior: auto !important;
} }
} }
.presentation-route[data-motion="disabled"] *,
.presentation-route[data-motion="disabled"] *::before,
.presentation-route[data-motion="disabled"] *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
@@ -281,7 +281,7 @@ export const discussionBranches = defineDiscussionBranches([
parentSceneId: "positioning", parentSceneId: "positioning",
title: "MCP and agent-facing scale", title: "MCP and agent-facing scale",
claimClass: "external-context", claimClass: "external-context",
evidencePointer: "Thesis: Model Context Protocol; Cloudflare Code Mode; Anthropic code execution with MCP", evidencePointer: "Thesis: Model Context Protocol; Anthropic MCP documentation; Cloudflare Code Mode",
summary: "MCP is a capability protocol; progressive discovery addresses large agent-facing surfaces.", summary: "MCP is a capability protocol; progressive discovery addresses large agent-facing surfaces.",
}, },
{ {
@@ -290,7 +290,7 @@ export const discussionBranches = defineDiscussionBranches([
title: "Lifecycle state machine", title: "Lifecycle state machine",
claimClass: "implemented", claimClass: "implemented",
evidencePointer: "Thesis Workflow Lifecycle; lifecycle state transitions", evidencePointer: "Thesis Workflow Lifecycle; lifecycle state transitions",
summary: "Draft, artifact, deployment, and run form a linear lifecycle with typed transitions.", summary: "Draft, artifact, deployment, and run form a typed lifecycle; raw plans can bypass the draft stage.",
}, },
{ {
id: "raw-plan-import", id: "raw-plan-import",
@@ -330,7 +330,7 @@ export const discussionBranches = defineDiscussionBranches([
title: "Provider-neutral security", title: "Provider-neutral security",
claimClass: "implemented", claimClass: "implemented",
evidencePointer: "Thesis Architecture; provider-neutral capability resolution", evidencePointer: "Thesis Architecture; provider-neutral capability resolution",
summary: "Provider-neutral capabilities avoid vendor lock-in while maintaining security boundaries.", summary: "Provider-neutral capabilities reduce vendor lock-in; runtime enforces boundary contracts.",
}, },
{ {
id: "evaluation-validity", id: "evaluation-validity",