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";
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 }));
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 } });
}, [state.location]);
const handleToggleMotion = useCallback(() => {
dispatch({ type: "toggle_motion" });
}, []);
const composition = compositionForState(state);
const showChatDock = composition.chatMode === "dock" && state.location.kind !== "discussion";
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
state={state}
demo={demo}
@@ -205,6 +209,7 @@ export const PresentationRoute = () => {
forceReplay={handleForceReplay}
resetOverrides={handleResetOverrides}
resetScene={handleResetScene}
toggleMotion={handleToggleMotion}
/>
)}
<button
@@ -9,7 +9,7 @@ import { OperatorChat } from "./OperatorChat.js";
import type { PresentationState } from "./presentation-state.js";
import { compositionForState } from "./presentation-state.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 = {
readonly state: PresentationState;
@@ -45,10 +45,6 @@ export const PresentationStage = ({
const composition = compositionForState(state);
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 (
<div
@@ -56,6 +52,7 @@ export const PresentationStage = ({
data-stage-theme={composition.stageTheme}
data-chat-theme={composition.chatTheme}
data-chat-mode={composition.chatMode}
data-evidence-mode={composition.evidenceMode}
>
<aside className="presentation-stage__chat" aria-label="agent chat region">
<OperatorChat state={state} messages={messages} onApprove={onApprove} onDeny={onDeny} />
@@ -71,15 +68,13 @@ export const PresentationStage = ({
selectedNodeId={state.selectedNodeId}
selectNode={selectNode}
/>
{hasBranches && (
<button
type="button"
className="presentation-stage__discussion-toggle"
onClick={toggleDiscussionIndex}
>
Open discussion topics
</button>
)}
<button
type="button"
className="presentation-stage__discussion-toggle"
onClick={toggleDiscussionIndex}
>
Discussion topics
</button>
</>
)}
{state.discussionIndexOpen && (
@@ -19,6 +19,7 @@ describe("PresenterControls", () => {
forceReplay: vi.fn(),
resetOverrides: vi.fn(),
resetScene: vi.fn(),
toggleMotion: vi.fn(),
};
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 { compositionForState } from "./presentation-state.js";
import type { ChatMode, ChatTheme, MainLocation, PresentationLocation, StageTheme } from "./storyboard.js";
@@ -15,6 +15,7 @@ type PresenterControlsProps = {
readonly forceReplay: () => void;
readonly resetOverrides: () => void;
readonly resetScene: () => void;
readonly toggleMotion: () => void;
};
export const PresenterControls = ({
@@ -28,6 +29,7 @@ export const PresenterControls = ({
forceReplay,
resetOverrides,
resetScene,
toggleMotion,
}: PresenterControlsProps) => {
const composition = compositionForState(state);
const isMain = state.location.kind === "main";
@@ -40,6 +42,22 @@ export const PresenterControls = ({
const sceneNumber = currentScene?.number ?? 0;
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 (
<div className="presenter-controls" role="dialog" aria-label="presenter controls">
<div className="presenter-controls__nav">
@@ -50,6 +68,20 @@ export const PresenterControls = ({
<span>Scene {sceneNumber}/{totalScenes}: {currentScene?.title ?? "Discussion"}</span>
{currentBeat && <span> · {beatIndex + 1}/{totalBeats} {currentBeat.title}</span>}
</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">
<label>
Stage theme
@@ -91,6 +123,9 @@ export const PresenterControls = ({
<span>{state.playbackMode === "replay" ? "Replay" : "Live"}</span>
<button type="button" onClick={forceReplay}>Force replay fallback</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>
</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 }) => (
<>
<StageCaption eyebrow={`Act I · ${scene.claimClass}`} title={scene.title}>
<p>{beat.caption}</p>
</StageCaption>
<div className="scene-body__positioning-grid">
<div className="scene-body__positioning-card">
<strong>Tool loops</strong>
<span>Direct action, no lifecycle</span>
const PositioningScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => {
const highlightLda = beat.id === "lda-position";
return (
<>
<StageCaption eyebrow={`Act I · ${scene.claimClass}`} title={scene.title}>
<p>{beat.caption}</p>
</StageCaption>
<div className="scene-body__positioning-grid">
<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 className="scene-body__positioning-card">
<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>
</>
);
<p className="scene-body__evidence">{scene.evidencePointer}</p>
</>
);
};
const BoundaryScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => (
<>
<StageCaption eyebrow="Act II · implemented" title={scene.title}>
<p>{beat.caption}</p>
</StageCaption>
<div className="scene-body__boundary">
<div className="scene-body__boundary-side">
<h3>Planner</h3>
<p>External LLM proposes and revises workflow structure.</p>
const BoundaryScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => {
const showPlanner = beat.id === "planner" || beat.id === "boundary";
const showRuntime = beat.id === "runtime" || beat.id === "boundary";
return (
<>
<StageCaption eyebrow="Act II · implemented" title={scene.title}>
<p>{beat.caption}</p>
</StageCaption>
<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 className="scene-body__boundary-divider" />
<div className="scene-body__boundary-side">
<h3>Runtime</h3>
<p>Validates, executes, records, and resumes deterministically.</p>
</div>
</div>
<p className="scene-body__evidence">{scene.evidencePointer}</p>
</>
);
<p className="scene-body__evidence">{scene.evidencePointer}</p>
</>
);
};
const lifecycleStages = [
{ id: "draft", label: "Draft" },
{ id: "artifact", label: "Artifact" },
{ id: "deployment", label: "Deployment" },
{ id: "run", label: "Run" },
];
const LifecycleScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBeatDefinition }) => (
<>
@@ -86,11 +100,14 @@ const LifecycleScene = ({ scene, beat }: { scene: SceneDefinition; beat: SceneBe
<p>{beat.caption}</p>
</StageCaption>
<div className="scene-body__lifecycle">
{["Draft", "Artifact", "Deployment", "Run"].map((stage, i) => (
<div key={stage} className="scene-body__lifecycle-stage">
{lifecycleStages.map((stage, i) => (
<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>
<strong>{stage}</strong>
{i < 3 && <span className="scene-body__lifecycle-arrow"></span>}
<strong>{stage.label}</strong>
{i < lifecycleStages.length - 1 && <span className="scene-body__lifecycle-arrow"></span>}
</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 }) => (
<>
<StageCaption eyebrow="Act II · implemented" title={scene.title}>
<p>{beat.caption}</p>
</StageCaption>
<div className="scene-body__architecture">
<div className="scene-body__architecture-layer">Client operations</div>
<div className="scene-body__architecture-arrow"></div>
<div className="scene-body__architecture-layer">Transport / JSON-RPC</div>
<div className="scene-body__architecture-arrow"></div>
<div className="scene-body__architecture-layer">Runtime & providers</div>
<div className="scene-body__architecture-arrow"></div>
<div className="scene-body__architecture-layer scene-body__architecture-layer--node">NodeUse</div>
</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>}
{architectureLayers.map((layer, i) => (
<div key={layer.id}>
<div
className={`scene-body__architecture-layer${layer.isNode ? " scene-body__architecture-layer--node" : ""}${beat.id === layer.id ? " scene-body__architecture-layer--active" : ""}`}
>
{layer.label}
</div>
{i < architectureLayers.length - 1 && <div className="scene-body__architecture-arrow"></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 }) => (
<>
<StageCaption eyebrow={`Act I · ${scene.claimClass}`} title={scene.title}>
<p>{beat.caption}</p>
</StageCaption>
<div className="scene-body__evaluation">
<div className="scene-body__evaluation-stat">
<strong>36</strong>
<span>trials</span>
</div>
<div className="scene-body__evaluation-stat">
<strong>2</strong>
<span>challenges</span>
</div>
<div className="scene-body__evaluation-stat">
<strong>3</strong>
<span>waves</span>
</div>
{evaluationStats.map((stat) => (
<div
key={stat.id}
className={`scene-body__evaluation-stat${beat.id === stat.id ? " scene-body__evaluation-stat--active" : ""}`}
>
<strong>{stat.value}</strong>
<span>{stat.label}</span>
</div>
))}
</div>
<p className="scene-body__evidence">{scene.evidencePointer}</p>
</>
@@ -28,6 +28,8 @@ export type PresentationState = {
readonly chatModeOverride: ChatMode | null;
readonly controlsOpen: boolean;
readonly discussionIndexOpen: boolean;
readonly motionDisabled: boolean;
readonly startedAt: number;
};
export type PresentationAction =
@@ -46,7 +48,8 @@ export type PresentationAction =
| { readonly type: "set_chat_theme"; readonly theme: ChatTheme | null }
| { readonly type: "set_chat_mode"; readonly mode: ChatMode | null }
| { readonly type: "toggle_controls" }
| { readonly type: "toggle_discussion_index" };
| { readonly type: "toggle_discussion_index" }
| { readonly type: "toggle_motion" };
export const initialPresentationState: PresentationState = {
location: defaultMainLocation,
@@ -59,6 +62,8 @@ export const initialPresentationState: PresentationState = {
chatModeOverride: null,
controlsOpen: false,
discussionIndexOpen: false,
motionDisabled: false,
startedAt: Date.now(),
};
const compositionForLocation = (
@@ -199,5 +204,7 @@ export const presentationReducer = (
return { ...state, controlsOpen: !state.controlsOpen };
case "toggle_discussion_index":
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);
}
/* 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 */
.presentation-stage[data-chat-theme="light"] .presentation-stage__chat {
background: oklch(0.96 0.005 80);
@@ -112,6 +205,15 @@ body {
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__primary,
.presentation-stage__evidence {
@@ -444,6 +546,32 @@ body {
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 {
display: flex;
flex-direction: column;
@@ -697,6 +825,47 @@ body {
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) {
.scene-rail {
padding: 0.25rem 0.5rem;
@@ -725,3 +894,11 @@ body {
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",
title: "MCP and agent-facing scale",
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.",
},
{
@@ -290,7 +290,7 @@ export const discussionBranches = defineDiscussionBranches([
title: "Lifecycle state machine",
claimClass: "implemented",
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",
@@ -330,7 +330,7 @@ export const discussionBranches = defineDiscussionBranches([
title: "Provider-neutral security",
claimClass: "implemented",
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",