fix: address presentation review findings

This commit is contained in:
lda
2026-07-13 07:12:45 +07:00 Verified
parent a16e0933e2
commit ab943deed5
25 changed files with 339 additions and 92 deletions
@@ -128,6 +128,34 @@ describe("useTimelineAgent", () => {
expect(start).toHaveBeenCalledWith("live");
});
it("does not advertise live launch when the timeline cannot start", () => {
const demo = demoController({ canStart: false });
const { result } = renderHook(() => useTimelineAgent(demo, {
mode: "live",
status: readyStatus,
}));
expect(result.current.canRunLive).toBe(false);
});
it("uses target health for an explicit live launch regardless of selected mode", async () => {
const start = vi.fn();
const demo = demoController({
state: { ...initialDemoTimelineState, mode: "replay" },
start,
});
const { result } = renderHook(() => useTimelineAgent(demo, {
mode: "replay",
status: readyStatus,
}));
expect(result.current.canRunLive).toBe(true);
await act(async () => result.current.runPreparedWorkflow("live"));
expect(start).toHaveBeenCalledWith("live");
});
it("does not offer an explicit live launch when health has failed", () => {
const start = vi.fn();
const demo = demoController({ start });
@@ -91,9 +91,15 @@ export const useTimelineAgent = (
const runLabel = modeLabel === "live" ? "Run prepared workflow" : "Run replay walkthrough";
const canRun = demo.canStart && !demo.inFlight && demo.state.phase !== "running";
const canRunLive = (options.liveTargetReady ?? (options.mode === "live" && (
// Live capability is about the target and timeline, not the selected display
// mode; callers can request a live run from a replay-backed slide explicitly.
const liveTargetReady = options.liveTargetReady ?? (
options.status.kind === "ready" || options.status.kind === "active"
))) && !demo.inFlight && demo.state.phase !== "running";
);
const canRunLive = liveTargetReady
&& demo.canStart
&& !demo.inFlight
&& demo.state.phase !== "running";
const runPreparedWorkflow = useCallback(async (requestedMode?: TimelineAgentMode) => {
const mode = requestedMode ?? modeLabel;
@@ -111,6 +111,15 @@ describe("PresentationRoute", () => {
15000,
);
it("exposes an application-owned readiness signal for rehearsal capture", async () => {
window.location.hash = "#scene/thesis/title";
const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />);
const route = await screen.findByRole("main", { name: /lda\.chat presentation/i });
await waitFor(() => expect(route).toHaveAttribute("data-presentation-ready", "true"));
});
const visualRouteContracts = [
{ hash: "#scene/thesis/title", heading: "Design and Implementation of lda.chat", primary: "thesis opening" },
{ hash: "#scene/lifecycle/draft", heading: "Workflow Lifecycle", primary: "workflow lifecycle rail" },
@@ -43,6 +43,29 @@ export const PresentationRoute = () => {
{ type: "jump_hash", hash: initialHash },
),
);
const [presentationReady, setPresentationReady] = useState(false);
useEffect(() => {
let cancelled = false;
let firstFrame = 0;
let secondFrame = 0;
setPresentationReady(false);
// Font metrics and two paint opportunities let measured diagrams settle
// before the rehearsal runner accepts the route as screenshot-ready.
const markReady = async () => {
await (document.fonts?.ready ?? Promise.resolve());
if (cancelled) return;
firstFrame = window.requestAnimationFrame(() => {
secondFrame = window.requestAnimationFrame(() => setPresentationReady(true));
});
};
void markReady();
return () => {
cancelled = true;
window.cancelAnimationFrame(firstFrame);
window.cancelAnimationFrame(secondFrame);
};
}, [state.location]);
const recording = useMemo(() => loadCanonicalDemoRecording(), []);
const replayEvidence = useMemo(() => projectRecordingToEvidence(recording), [recording]);
@@ -260,7 +283,12 @@ export const PresentationRoute = () => {
);
return (
<main className="presentation-route" aria-label="lda.chat presentation" data-motion={state.motionDisabled ? "disabled" : "enabled"}>
<main
className="presentation-route"
aria-label="lda.chat presentation"
data-motion={state.motionDisabled ? "disabled" : "enabled"}
data-presentation-ready={presentationReady ? "true" : "false"}
>
<PresentationCanvas>
<PresentationStage
state={state}
@@ -1,11 +1,17 @@
import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";
import { SceneProgress } from "./SceneProgress.js";
import { SceneProgress, shouldShowBeatCounter } from "./SceneProgress.js";
import type { MainLocation } from "./storyboard.js";
afterEach(() => cleanup());
describe("SceneProgress", () => {
it("uses scene metadata for a single-beat counter", () => {
expect(shouldShowBeatCounter({ alwaysShowBeatCounter: true }, 1)).toBe(true);
expect(shouldShowBeatCounter({ alwaysShowBeatCounter: false }, 1)).toBe(false);
expect(shouldShowBeatCounter(undefined, 2)).toBe(true);
});
it("shows scene and beat position for architecture/runtime", () => {
const location: MainLocation = {
kind: "main",
@@ -1,9 +1,15 @@
import { findScene, mainScenes, type MainLocation } from "./storyboard.js";
import type { SceneDefinition } from "./storyboard.js";
type SceneProgressProps = {
readonly location: MainLocation;
};
export const shouldShowBeatCounter = (
scene: Pick<SceneDefinition, "alwaysShowBeatCounter"> | undefined,
totalBeats: number,
): boolean => totalBeats > 1 || scene?.alwaysShowBeatCounter === true;
export const SceneProgress = ({ location }: SceneProgressProps) => {
const scene = findScene(location.sceneId);
const sceneIndex = mainScenes.findIndex((s) => s.id === location.sceneId);
@@ -18,7 +24,7 @@ export const SceneProgress = ({ location }: SceneProgressProps) => {
{sceneIndex + 1} / {totalScenes}
</span>
)}
{(totalBeats > 1 || location.sceneId === "agent-handoff") && (
{shouldShowBeatCounter(scene, totalBeats) && (
<span className="scene-progress__beat">
{beatIndex >= 0 ? beatIndex + 1 : 1} / {totalBeats}
</span>
@@ -1,15 +1,16 @@
import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";
import { cleanup, render, screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, describe, expect, it, vi } from "vitest";
import { findBeat, findScene } from "../storyboard.js";
import { PreparedAuthoringLifecycleScene } from "./PreparedAuthoringLifecycleScene.js";
afterEach(() => cleanup());
const renderBeat = (beatId: string) => {
const renderBeat = (beatId: string, onAdvance?: () => void) => {
const scene = findScene("prepared-lifecycle");
const beat = findBeat("prepared-lifecycle", beatId);
if (!scene || !beat) throw new Error(`missing prepared-lifecycle/${beatId}`);
return render(<PreparedAuthoringLifecycleScene scene={scene} beat={beat} />);
return render(<PreparedAuthoringLifecycleScene scene={scene} beat={beat} onAdvance={onAdvance} />);
};
describe("PreparedAuthoringLifecycleScene", () => {
@@ -85,6 +86,35 @@ describe("PreparedAuthoringLifecycleScene", () => {
expect(workspace.querySelectorAll('[data-visual-role="lifecycle-primary"]')).toHaveLength(1);
});
it("projects a custom discover submission into the prepared conversation", async () => {
const user = userEvent.setup();
renderBeat("discover");
const input = screen.getByRole("textbox", { name: /message to authoring assistant/i });
await user.type(input, "Inspect the report source first.");
await user.click(screen.getByRole("button", { name: /send message/i }));
const conversation = screen.getByRole("log", { name: "prepared authoring conversation" });
expect(within(conversation).getByText("Inspect the report source first.")).toBeInTheDocument();
});
it.each([
["draft", true],
["artifact", true],
["validate", false],
["deployment", false],
] as const)("%s submission advances only when its beat owns the transition", async (beatId, advances) => {
const user = userEvent.setup();
const onAdvance = vi.fn();
renderBeat(beatId, onAdvance);
const input = screen.getByRole("textbox", { name: /message to authoring assistant/i });
if (beatId === "validate") await user.type(input, "Review the validation result.");
await user.click(screen.getByRole("button", { name: /send message/i }));
expect(onAdvance).toHaveBeenCalledTimes(advances ? 1 : 0);
});
it.each([
["discover", "Discover"],
["draft", "Draft"],
@@ -63,6 +63,9 @@ export const PreparedAuthoringLifecycleScene = ({ scene, beat, onAdvance }: Prep
onDraftChange={(draft) => dispatch({ type: "draft_edited", draft })}
onSubmit={(submittedText) => {
dispatch({ type: "draft_edited", draft: submittedText });
if (beatId === "discover") {
dispatch({ type: "discover_submitted" });
}
if (beatId === "draft") {
dispatch({ type: "draft_submitted" });
onAdvance?.();
@@ -1,4 +1,4 @@
import { useEffect, useState, type FormEvent, type KeyboardEvent } from "react";
import { useEffect, useState } from "react";
import { Button } from "../../components/ui/button.js";
import { Textarea } from "../../components/ui/textarea.js";
import { AuthoringConversation } from "./AuthoringConversation.js";
@@ -7,6 +7,7 @@ import type {
Scene9MessageProjection,
Scene9SubmittedOverrides,
} from "./scene9-message-state.js";
import { PREPARED_COMPOSER_HELP, usePreparedComposerSubmit } from "./usePreparedComposerSubmit.js";
export type PresentationAssistantPaneProps = {
readonly phase: AuthoringPhaseId;
@@ -52,16 +53,10 @@ export const PresentationAssistantPane = ({
setDraft(nextDraft);
onDraftChange(nextDraft);
};
const submit = (event?: FormEvent<HTMLFormElement>) => {
event?.preventDefault();
if (canSubmit) onSubmit(draft);
};
const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
submit();
}
};
const { submit, handleKeyDown } = usePreparedComposerSubmit(
canSubmit,
() => onSubmit(draft),
);
return (
<aside
@@ -104,7 +99,7 @@ export const PresentationAssistantPane = ({
</Button>
</div>
<p id="scene9-authoring-message-help" className="presentation-assistant-pane__composer-help">
Shift+Enter adds a new line. This is a deterministic prepared replay, not a live model request.
{PREPARED_COMPOSER_HELP}
</p>
{runRequested !== null ? (
<p role="status" className="presentation-assistant-pane__run-status">
@@ -1,4 +1,3 @@
import type { FormEvent } from "react";
import { Button } from "../../components/ui/button.js";
import { Textarea } from "../../components/ui/textarea.js";
import { AuthoringConversation } from "./AuthoringConversation.js";
@@ -7,6 +6,7 @@ import {
type Scene8EntryAction,
type Scene8EntryState,
} from "./scene8-entry-state.js";
import { PREPARED_COMPOSER_HELP, usePreparedComposerSubmit } from "./usePreparedComposerSubmit.js";
type Scene8ChatEntryProps = {
readonly state: Scene8EntryState;
@@ -17,11 +17,10 @@ type Scene8ChatEntryProps = {
export const Scene8ChatEntry = ({ state, dispatch }: Scene8ChatEntryProps) => {
const submitted = state.phase === "submitted";
const canSubmit = canSubmitScene8Entry(state);
const submit = (event?: FormEvent<HTMLFormElement>) => {
event?.preventDefault();
if (canSubmit) dispatch({ type: "submit" });
};
const { submit, handleKeyDown } = usePreparedComposerSubmit(
canSubmit,
() => dispatch({ type: "submit" }),
);
return (
<section
@@ -43,12 +42,7 @@ export const Scene8ChatEntry = ({ state, dispatch }: Scene8ChatEntryProps) => {
value={state.draft}
disabled={submitted}
onChange={(event) => dispatch({ type: "draft_changed", draft: event.target.value })}
onKeyDown={(event) => {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
submit();
}
}}
onKeyDown={handleKeyDown}
aria-describedby="scene8-authoring-request-help"
/>
<div className="agent-handoff-scene__composer-actions">
@@ -57,7 +51,7 @@ export const Scene8ChatEntry = ({ state, dispatch }: Scene8ChatEntryProps) => {
</Button>
</div>
<p id="scene8-authoring-request-help" className="agent-handoff-scene__composer-help">
Shift+Enter adds a new line. This is a deterministic prepared replay, not a live model request.
{PREPARED_COMPOSER_HELP}
</p>
</form>
{submitted ? (
@@ -205,4 +205,20 @@ describe("projectPreparedAuthoringThread", () => {
canonical.filter((message) => message.id.endsWith("-tools")),
);
});
it("keeps each staged override scoped to its phase", () => {
const messages = projectPreparedAuthoringThread("deployment", undefined, {
validate: "Edited validation request",
deployment: "Edited deployment request",
});
const userText = messages
.filter((message) => message.role === "user")
.map((message) => message.parts[0]?.type === "text" ? message.parts[0].text : "");
expect(userText).toEqual([
"We need to author a report workflow for the lda_report scenario. What sources and capabilities are available?",
"Edited validation request",
"Edited deployment request",
]);
});
});
@@ -281,28 +281,40 @@ export const authoringToolGroupId = (phase: AuthoringPhaseId): string =>
export const projectPreparedAuthoringThread = (
throughPhase: AuthoringPhaseId = "deployment",
requestOverride?: string,
requestOverrides?: Readonly<Partial<Record<"validate" | "deployment", string>>>,
requestOverrides?: Readonly<Partial<Record<"discover" | "validate" | "deployment", string>>>,
): readonly AgentMessage[] => {
const finalPhaseIndex = recording.findIndex(({ phase }) => phase === throughPhase);
if (finalPhaseIndex < 0) throw new Error(`unknown phase: ${throughPhase}`);
let requestReplaced = false;
const phaseOverrideApplied = new Set<AuthoringPhaseId>();
return recording.slice(0, finalPhaseIndex + 1).flatMap((phase) => {
const phaseOverride =
phase.phase === "validate"
phase.phase === "discover"
? requestOverrides?.discover
: phase.phase === "validate"
? requestOverrides?.validate
: phase.phase === "deployment"
? requestOverrides?.deployment
: undefined;
const conversation = phase.conversation.map((turn, index) => {
const shouldReplaceRequest =
turn.role === "user" &&
(phaseOverride !== undefined || (requestOverride !== undefined && !requestReplaced));
if (shouldReplaceRequest && phaseOverride === undefined) requestReplaced = true;
let replacement: string | undefined;
if (turn.role === "user" && phaseOverride !== undefined && !phaseOverrideApplied.has(phase.phase)) {
replacement = phaseOverride;
phaseOverrideApplied.add(phase.phase);
} else if (
turn.role === "user"
&& phaseOverride === undefined
&& requestOverride !== undefined
&& !requestReplaced
) {
replacement = requestOverride;
requestReplaced = true;
}
return agentTextMessage(
`authoring-${phase.phase}-message-${index}`,
turn.role,
shouldReplaceRequest ? (phaseOverride ?? requestOverride ?? turn.text) : turn.text,
replacement ?? turn.text,
);
});
const groupId = authoringToolGroupId(phase.phase);
@@ -69,6 +69,19 @@ describe("scene 9 staged message state", () => {
});
});
it("stores discover submissions under the discover destination", () => {
const state = scene9MessageReducer(initialScene9MessageState, {
type: "draft_edited",
draft: "Inspect the report source first.",
});
expect(scene9MessageReducer(state, { type: "discover_submitted" })).toEqual({
draft: state.draft,
submittedOverrides: { discover: state.draft },
runRequested: null,
});
});
it("ignores blank submits and keeps duplicate submits idempotent", () => {
const blank = { ...initialScene9MessageState, draft: " \n\t" };
expect(scene9MessageReducer(blank, { type: "draft_submitted" })).toBe(blank);
@@ -1,7 +1,7 @@
import type { AuthoringPhaseId } from "./authoring-recording.js";
export type Scene9MessagePhase = AuthoringPhaseId;
export type Scene9DestinationPhase = "validate" | "deployment";
export type Scene9DestinationPhase = "discover" | "validate" | "deployment";
export const SCENE9_PHASE_PROMPTS: Readonly<Record<Scene9MessagePhase, string>> = {
discover: "",
@@ -38,6 +38,7 @@ export const initialScene9MessageState: Scene9MessageState = {
export type Scene9MessageAction =
| { readonly type: "draft_edited"; readonly draft: string }
| { readonly type: "discover_submitted" }
| { readonly type: "draft_submitted" }
| { readonly type: "artifact_submitted" }
| { readonly type: "run_requested" };
@@ -66,6 +67,8 @@ export const scene9MessageReducer = (
switch (action.type) {
case "draft_edited":
return state.runRequested === null ? { ...state, draft: action.draft } : state;
case "discover_submitted":
return submitOverride(state, "discover");
case "draft_submitted":
return submitOverride(state, "validate");
case "artifact_submitted":
@@ -92,7 +95,7 @@ export const projectScene9Message = (
placeholder: SCENE9_PHASE_PLACEHOLDERS[phase],
});
/** Keeps the transcript-facing map limited to the two staged handoff destinations. */
/** Returns the transcript-facing request overrides by destination phase. */
export const projectScene9SubmittedOverrides = (
state: Scene9MessageState,
): Scene9SubmittedOverrides => state.submittedOverrides;
@@ -0,0 +1,28 @@
import type { FormEvent, KeyboardEvent } from "react";
type PreparedComposerSubmitHandlers = {
readonly submit: (event?: FormEvent<HTMLFormElement>) => void;
readonly handleKeyDown: (event: KeyboardEvent<HTMLTextAreaElement>) => void;
};
export const PREPARED_COMPOSER_HELP =
"Shift+Enter adds a new line. This is a deterministic prepared replay, not a live model request.";
/** Shares the prepared-replay composer keyboard and form submission contract. */
export const usePreparedComposerSubmit = (
canSubmit: boolean,
onSubmit: () => void,
): PreparedComposerSubmitHandlers => {
const submit = (event?: FormEvent<HTMLFormElement>) => {
event?.preventDefault();
if (canSubmit) onSubmit();
};
const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
submit();
}
};
return { submit, handleKeyDown };
};
@@ -172,7 +172,13 @@ describe("AssistantOperatorThread", () => {
it("can keep a first-stage group anchored at the start of the transcript", async () => {
const setScrollTop = vi.fn();
const originalDescriptor = Object.getOwnPropertyDescriptor(HTMLDivElement.prototype, "scrollTop");
const descriptors = {
scrollTop: Object.getOwnPropertyDescriptor(HTMLDivElement.prototype, "scrollTop"),
scrollHeight: Object.getOwnPropertyDescriptor(HTMLDivElement.prototype, "scrollHeight"),
clientHeight: Object.getOwnPropertyDescriptor(HTMLDivElement.prototype, "clientHeight"),
offsetTop: Object.getOwnPropertyDescriptor(HTMLElement.prototype, "offsetTop"),
offsetHeight: Object.getOwnPropertyDescriptor(HTMLElement.prototype, "offsetHeight"),
};
const messages: ReadonlyArray<AgentMessage> = [
{
id: "assistant-start-scroll-text",
@@ -192,10 +198,14 @@ describe("AssistantOperatorThread", () => {
];
try {
Object.defineProperty(HTMLDivElement.prototype, "scrollTop", {
configurable: true,
get: () => 0,
set: setScrollTop,
Object.defineProperties(HTMLDivElement.prototype, {
scrollTop: { configurable: true, get: () => 0, set: setScrollTop },
scrollHeight: { configurable: true, get: () => 400 },
clientHeight: { configurable: true, get: () => 80 },
});
Object.defineProperties(HTMLElement.prototype, {
offsetTop: { configurable: true, get: () => 120 },
offsetHeight: { configurable: true, get: () => 40 },
});
render(
<AssistantOperatorThread
@@ -209,10 +219,15 @@ describe("AssistantOperatorThread", () => {
await waitFor(() => expect(setScrollTop).toHaveBeenCalledWith(0));
} finally {
if (originalDescriptor) {
Object.defineProperty(HTMLDivElement.prototype, "scrollTop", originalDescriptor);
} else {
delete (HTMLDivElement.prototype as { scrollTop?: number }).scrollTop;
for (const [name, descriptor] of Object.entries(descriptors)) {
const prototype = name === "offsetTop" || name === "offsetHeight"
? HTMLElement.prototype
: HTMLDivElement.prototype;
if (descriptor) {
Object.defineProperty(prototype, name, descriptor);
} else {
delete (prototype as unknown as Record<string, unknown>)[name];
}
}
}
});
@@ -5,6 +5,34 @@ import { describe, expect, it } from "vitest";
const css = readFileSync(join(import.meta.dirname, "presentation.css"), "utf8").replace(/\r\n/g, "\n");
const demoWorkflowCss = readFileSync(join(import.meta.dirname, "styles", "demo-workflow.css"), "utf8").replace(/\r\n/g, "\n");
const cssBlocks = (source: string, selector: string): readonly string[] => {
const blocks: string[] = [];
let searchFrom = 0;
while (searchFrom < source.length) {
const selectorStart = source.indexOf(selector, searchFrom);
if (selectorStart < 0) break;
const openingBrace = source.indexOf("{", selectorStart);
if (openingBrace < 0) break;
let depth = 0;
for (let index = openingBrace; index < source.length; index += 1) {
if (source[index] === "{") depth += 1;
if (source[index] !== "}") continue;
depth -= 1;
if (depth === 0) {
blocks.push(source.slice(openingBrace + 1, index));
searchFrom = index + 1;
break;
}
}
if (searchFrom <= selectorStart) break;
}
return blocks;
};
const cssBlock = (source: string, selector: string): string | undefined =>
cssBlocks(source, selector).at(-1);
describe("presentation.css", () => {
it("keeps the demo footer rail compact and removes the old launch control", () => {
const railBlock = css.match(/\.presentation-demo-rail\s*\{(?<body>[\s\S]*?)\n\}/)?.groups?.body;
@@ -80,16 +108,30 @@ describe("presentation.css", () => {
it("bounds Scene 9 conversation scrolling and recenters Scene 8 at compact stage widths", () => {
expect(css.match(/\.presentation-stage\[data-scene-view="agent"\] \.presentation-stage__primary\s*\{/g)).toHaveLength(2);
expect(css).toMatch(
/\.presentation-stage__primary > \.agent-handoff-scene\s*\{[\s\S]*?margin-inline:\s*auto;/,
expect(cssBlocks(css, ".presentation-stage__primary > .agent-handoff-scene")
.some((body) => body.includes("margin-inline: auto;"))).toBe(true);
expect(cssBlocks(css, ".agent-handoff-scene__intro")
.some((body) => body.includes("width: min(calc(100% - 3rem), 72rem);"))).toBe(true);
expect(cssBlocks(css, ".agent-handoff-scene__composer")
.some((body) => body.includes("width: min(calc(100% - 3rem), 72rem);"))).toBe(true);
const conversation = cssBlock(
css,
'.prepared-lifecycle-scene[data-presentation-surface="editorial"] .presentation-assistant-pane__conversation',
);
expect(css).toMatch(/\.agent-handoff-scene__intro\s*\{[\s\S]*?width:\s*min\(calc\(100% - 3rem\), 72rem\);/);
expect(css).toMatch(/\.agent-handoff-scene__composer\s*\{[\s\S]*?width:\s*min\(calc\(100% - 3rem\), 72rem\);/);
expect(css).toMatch(
/\.prepared-lifecycle-scene\[data-presentation-surface="editorial"\] \.presentation-assistant-pane__conversation\s*\{[\s\S]*?flex:\s*1 1 auto;[\s\S]*?min-height:\s*0;[\s\S]*?overflow:\s*auto;/,
expect(conversation).toContain("flex: 1 1 auto;");
expect(conversation).toContain("min-height: 0;");
expect(conversation).toContain("overflow: auto;");
const compactAgentPrimary = cssBlock(
cssBlock(css, "@media (max-width: 1100px)") ?? "",
'.presentation-stage[data-scene-view="agent"] .presentation-stage__primary',
);
expect(css).toMatch(
/@media \(max-width: 1100px\)[\s\S]*?\.presentation-stage\[data-scene-view="agent"\] \.presentation-stage__primary\s*\{[\s\S]*?align-items:\s*center;[\s\S]*?justify-content:\s*center;/,
expect(compactAgentPrimary).toContain("align-items: center;");
expect(compactAgentPrimary).toContain("justify-content: center;");
});
it("does not keep an empty findings campaign-strip ruleset", () => {
expect(css).not.toContain(
'.evaluation-board[data-evaluation-focus="findings"] .evaluation-board__campaign-strip {\n /*',
);
});
@@ -188,11 +188,6 @@
grid-template-columns: 1fr;
}
.evaluation-board[data-evaluation-focus="findings"] .evaluation-board__campaign-strip {
/* grid-template-columns: minmax(12rem, 0.7fr) minmax(20rem, 1.3fr); */
/* difference between two beats -> shifting */
}
.evaluation-board[data-evaluation-focus="cohort"] .evaluation-board__cohort {
border-right: 0;
padding-block: 1.35rem 0.8rem;
@@ -40,6 +40,7 @@ export type SceneDefinition = {
readonly evidencePointer: string;
readonly view: SceneView;
readonly beats: readonly SceneBeatDefinition[];
readonly alwaysShowBeatCounter?: boolean;
};
const defineScenes = <const Scenes extends readonly SceneDefinition[]>(scenes: Scenes): Scenes => scenes;
@@ -158,6 +159,7 @@ export const mainScenes = defineScenes([
claimClass: "implemented",
evidencePointer: "Constrained demo agent and prepared replay recipe",
view: "agent",
alwaysShowBeatCounter: true,
beats: [
sceneBeat("request", "Operator request", "A thin agent interface receives the report request.", { chatMode: "hidden", chatTheme: "light" }),
],