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:
@@ -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>
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user