fix: close remaining compositor correctness gaps

- F1: Preload replayEvidence on mount so evidence drawer shows records in replay mode
- F2: close_overlay forces evidenceModeOverride to hidden when beat-default is open
- F3: Add CSS transitions (opacity, scale, border, background) for beat-driven elements
- F4: Fix planned time from 20 to 15 minutes for defense presentation
- F5: Fix stale speaker notes by keying textarea on beat.id
- F6: Lazy-initialize startedAt via useReducer initializer instead of module-level Date.now()
- F7: Fix MCP evidence pointer to Anthropic + Cloudflare
This commit is contained in:
lda
2026-07-04 18:58:20 +07:00 Verified
parent 49ae5ebd35
commit c06809b080
5 changed files with 50 additions and 4 deletions
@@ -36,13 +36,16 @@ export const PresentationRoute = () => {
const [state, dispatch] = useReducer( const [state, dispatch] = useReducer(
presentationReducer, presentationReducer,
initialPresentationState, initialPresentationState,
(initial) => presentationReducer(initial, { type: "jump_hash", hash: window.location.hash }), (initial) => presentationReducer(
{ ...initial, startedAt: Date.now() },
{ type: "jump_hash", hash: window.location.hash },
),
); );
const recording = useMemo(() => loadCanonicalDemoRecording(), []); const recording = useMemo(() => loadCanonicalDemoRecording(), []);
const replayEvidence = useMemo(() => projectRecordingToEvidence(recording), [recording]); const replayEvidence = useMemo(() => projectRecordingToEvidence(recording), [recording]);
const [evidence, setEvidence] = useState<readonly EvidenceRecord[]>([]); const [evidence, setEvidence] = useState<readonly EvidenceRecord[]>(replayEvidence);
const recordEvidence = useCallback((record: EvidenceRecord) => { const recordEvidence = useCallback((record: EvidenceRecord) => {
setEvidence((records) => [...records, record]); setEvidence((records) => [...records, record]);
}, []); }, []);
@@ -56,7 +56,7 @@ export const PresenterControls = ({
return `${m}:${s.toString().padStart(2, "0")}`; return `${m}:${s.toString().padStart(2, "0")}`;
}; };
const plannedMinutes = 20; const plannedMinutes = 15;
return ( return (
<div className="presenter-controls" role="dialog" aria-label="presenter controls"> <div className="presenter-controls" role="dialog" aria-label="presenter controls">
@@ -76,6 +76,7 @@ export const PresenterControls = ({
<label> <label>
Speaker notes Speaker notes
<textarea <textarea
key={currentBeat?.id ?? "none"}
defaultValue={currentBeat?.caption ?? ""} defaultValue={currentBeat?.caption ?? ""}
rows={3} rows={3}
placeholder="Add notes for this beat..." placeholder="Add notes for this beat..."
@@ -181,6 +181,13 @@ export const presentationReducer = (
case "close_overlay": { case "close_overlay": {
if (state.selectedNodeId !== null) return { ...state, selectedNodeId: null }; if (state.selectedNodeId !== null) return { ...state, selectedNodeId: null };
if (state.evidenceModeOverride !== null) return { ...state, evidenceModeOverride: null }; if (state.evidenceModeOverride !== null) return { ...state, evidenceModeOverride: null };
const derivedEvidenceMode = (() => {
if (state.location.kind === "discussion") return "hidden" as const;
const scene = findScene(state.location.sceneId);
const beat = scene?.beats.find((b) => b.id === (state.location as MainLocation).beatId);
return beat?.evidenceMode ?? "hidden";
})();
if (derivedEvidenceMode !== "hidden") return { ...state, evidenceModeOverride: "hidden" };
if (state.discussionIndexOpen) return { ...state, discussionIndexOpen: false }; if (state.discussionIndexOpen) return { ...state, discussionIndexOpen: false };
if (state.controlsOpen) return { ...state, controlsOpen: false }; if (state.controlsOpen) return { ...state, controlsOpen: false };
if (state.location.kind === "discussion") { if (state.location.kind === "discussion") {
@@ -828,42 +828,77 @@ body {
/* Beat-driven active states */ /* Beat-driven active states */
.scene-body__boundary-side--dim { .scene-body__boundary-side--dim {
opacity: 0.4; opacity: 0.4;
transform: scale(0.97);
transition: opacity 0.3s ease, transform 0.3s ease;
}
.scene-body__boundary-side {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.scene-body__lifecycle-stage,
.scene-body__authoring-step {
transition: opacity 0.3s ease, transform 0.25s ease, border-color 0.2s ease, background 0.2s ease;
} }
.scene-body__lifecycle-stage:not(.scene-body__lifecycle-stage--active) { .scene-body__lifecycle-stage:not(.scene-body__lifecycle-stage--active) {
opacity: 0.4; opacity: 0.4;
transform: scale(0.96);
} }
.scene-body__lifecycle-stage--active { .scene-body__lifecycle-stage--active {
border-color: oklch(0.7 0.16 195); border-color: oklch(0.7 0.16 195);
background: oklch(0.2 0.06 195); background: oklch(0.2 0.06 195);
transform: scale(1.03);
}
.scene-body__architecture-layer {
transition: opacity 0.3s ease, transform 0.25s ease, border-color 0.2s ease, background 0.2s ease;
} }
.scene-body__architecture-layer:not(.scene-body__architecture-layer--active):not(.scene-body__architecture-layer--node) { .scene-body__architecture-layer:not(.scene-body__architecture-layer--active):not(.scene-body__architecture-layer--node) {
opacity: 0.4; opacity: 0.4;
transform: scale(0.97);
} }
.scene-body__architecture-layer--active { .scene-body__architecture-layer--active {
border-color: oklch(0.7 0.16 195); border-color: oklch(0.7 0.16 195);
background: oklch(0.2 0.06 195); background: oklch(0.2 0.06 195);
transform: scale(1.03);
} }
.scene-body__authoring-step:not(.scene-body__authoring-step--active) { .scene-body__authoring-step:not(.scene-body__authoring-step--active) {
opacity: 0.4; opacity: 0.4;
transform: scale(0.96);
} }
.scene-body__authoring-step--active { .scene-body__authoring-step--active {
border-color: oklch(0.7 0.16 195); border-color: oklch(0.7 0.16 195);
background: oklch(0.2 0.06 195); background: oklch(0.2 0.06 195);
transform: scale(1.03);
}
.scene-body__evaluation-stat {
transition: opacity 0.3s ease, transform 0.25s ease, border-color 0.2s ease, background 0.2s ease;
} }
.scene-body__evaluation-stat:not(.scene-body__evaluation-stat--active) { .scene-body__evaluation-stat:not(.scene-body__evaluation-stat--active) {
opacity: 0.4; opacity: 0.4;
transform: scale(0.96);
} }
.scene-body__evaluation-stat--active { .scene-body__evaluation-stat--active {
border-color: oklch(0.7 0.16 195); border-color: oklch(0.7 0.16 195);
background: oklch(0.2 0.06 195); background: oklch(0.2 0.06 195);
transform: scale(1.05);
}
.scene-body__positioning-card {
transition: opacity 0.3s ease, transform 0.25s ease, border-color 0.2s ease, background 0.2s ease;
}
.scene-body__positioning-card--active {
transform: scale(1.03);
} }
@media (max-height: 760px) { @media (max-height: 760px) {
@@ -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; Anthropic MCP documentation; Cloudflare Code Mode", evidencePointer: "Thesis: Model Context Protocol; Anthropic MCP; 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.",
}, },
{ {