docs: complete adaptive presentation canvas slice

This commit is contained in:
lda
2026-07-06 04:15:23 +07:00 Verified
parent 3751ada899
commit 0c2b23ffd3
5 changed files with 89 additions and 9 deletions
+28 -7
View File
@@ -111,7 +111,7 @@ With the Python server running, verify in the browser:
1. The initial page makes no upstream request
2. Connect succeeds against `http://127.0.0.1:8765/rpc`
3. Source rows appear after connection
4. Raw health and source-list exchanges are selectable in the evidence drawer
4. Raw health and source-list exchanges are selectable in the evidence inspector
5. Equivalent CLI text is visible
6. `http://example.com:8765/rpc` is rejected without upstream fetch
7. Stopping the Python server produces the unreachable state while preserving
@@ -170,13 +170,14 @@ both modes. Replay is visibly labeled and does not create real issues.
The console exposes `/present`, a 720p no-scroll defense compositor for the
prepared `lda_report_workflow` story. It renders a 12-scene, multi-beat
storyboard with stable stage regions, discussion branches, act themes, a chat
dock, and keyboard navigation.
storyboard with an adaptive aspect-ratio canvas, stable stage regions, discussion
branches, act themes, a chat dock, and keyboard navigation.
Scenes 8 through 10 use the canonical replay as their only execution evidence.
The handoff expands an interpreted run operation into the center stage, then
keeps one workflow graph mounted while execution reaches the typed interrupt
and approval boundary. Raw protocol payloads remain in the evidence drawer.
and approval boundary. Raw protocol payloads are available through the evidence
receipt and inspector.
The key defense states are directly addressable:
@@ -230,9 +231,16 @@ committed `lda-report-success-v1` recording. No RPC server is required.
#### Editorial Canvas
The presentation renders on a fixed 1280x720 editorial canvas that scales and
letterboxes to fit any viewport without reflowing scene content. Scene 6 uses a
recursive Interactive Figure with expand/collapse and breadcrumb navigation.
The presentation renders on an adaptive editorial canvas that derives its aspect
ratio from the browser viewport. The canvas preserves a fixed logical height of
720px and clamps the logical width between 960px (4:3) and 1280px (16:9),
scaling the result to fit the viewport with letterboxing. Viewports wider than
~1.78:1 fill the maximum 1280x720 logical region; narrower viewports receive a
proportionally narrower canvas without reflowing scene content. No URL query
parameters or local-storage settings control the ratio.
Scene 6 uses a recursive Interactive Figure with expand/collapse and breadcrumb
navigation.
Key Scene 6 deep links:
@@ -247,6 +255,19 @@ Key Scene 6 deep links:
- **Tab / arrows**: move focus between figure nodes without advancing the presentation
- **Breadcrumbs**: jump to any ancestor focus level
#### Evidence Presentation
Evidence availability never resizes the primary presentation region. Each beat
may request an evidence presentation state (`hidden`, `receipt`, or `inspector`),
and a global override can be set through the presentation tool. The state is
cleared on scene navigation (next, previous, jump) but preserved through
discussion transitions.
- **Receipt**: a compact bottom-row button showing the count and label of
available evidence records. Clicking opens the inspector.
- **Inspector**: a centered modal dialog with `Interpreted` and `Raw` tabs, a
record selector, and focus trapping. `Escape` closes the inspector.
#### Constraints
This plan deliberately defers:
@@ -28,6 +28,63 @@ beforeAll(() => {
afterEach(() => cleanup());
describe("PresentationRoute", () => {
it("renders the presentation stage entry point", async () => {
const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />);
expect(screen.getByRole("main", { name: /lda.chat presentation/i })).toBeInTheDocument();
expect(screen.getByRole("heading", { name: /Thesis/ })).toBeInTheDocument();
});
it("starts from a scene hash and advances with keyboard", async () => {
window.location.hash = "#scene/agent-handoff/request";
const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />);
expect(screen.getByRole("heading", { name: /Agent Handoff/i })).toBeInTheDocument();
await userEvent.keyboard("{ArrowRight}");
expect(await screen.findByText(/The interface delegates durable work to lda\.chat/i)).toBeInTheDocument();
});
it("renders audience progress chrome without rail or mode label", async () => {
const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />);
expect(screen.getByText("Prepare the thesis readiness report.")).toBeInTheDocument();
expect(screen.queryByLabelText(/presentation scene rail/i)).not.toBeInTheDocument();
expect(screen.getByLabelText("scene position")).toBeInTheDocument();
});
it("shows node spotlight when a graph node is selected", async () => {
window.location.hash = "#scene/workflow-demo/graph";
const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />);
await userEvent.click(screen.getByRole("button", { name: /issue review/i }));
expect(screen.getByRole("dialog", { name: /issue review/i })).toBeInTheDocument();
expect(screen.getByText("Workflow node")).toBeInTheDocument();
});
it("can advance replay far enough to show a product operation block", async () => {
window.location.hash = "#scene/workflow-demo/operation";
const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />);
expect(await screen.findByText(/workflow.runs.start/i)).toBeInTheDocument();
});
it("opens a positioning branch via hash and returns to the parent scene first beat", async () => {
window.location.hash = "#discuss/hosted-automation";
const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />);
expect(await screen.findByRole("button", { name: /return to positioning/i })).toBeInTheDocument();
await userEvent.click(screen.getByRole("button", { name: /return to positioning/i }));
expect(window.location.hash).toBe("#scene/positioning/landscape");
});
it("uses the parent scene as return location for a directly linked branch", async () => {
window.location.hash = "#discuss/mcp-agent-scale";
const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />);
await userEvent.click(screen.getByRole("button", { name: /return to positioning/i }));
expect(window.location.hash).toBe("#scene/positioning/landscape");
});
it("renders stable chat, primary, progress, and transient evidence surfaces", async () => {
const { PresentationRoute } = await import("./PresentationRoute.js");
render(<PresentationRoute />);
@@ -409,6 +409,8 @@
place-items: center;
padding: 2.5rem 5%;
background: oklch(0.08 0.02 250 / 0.68);
opacity: 1;
transition: opacity 150ms ease-in-out;
}
.evidence-inspector {