docs: complete adaptive presentation canvas slice
This commit is contained in:
@@ -102,12 +102,12 @@ Implementation order:
|
|||||||
[`defense presentation storyboard`](superpowers/specs/2026-07-04-defense-presentation-storyboard-design.md).
|
[`defense presentation storyboard`](superpowers/specs/2026-07-04-defense-presentation-storyboard-design.md).
|
||||||
Implementation:
|
Implementation:
|
||||||
[`editorial canvas and Interactive Figure plan`](historical/superpowers/plans/2026-07-05-editorial-canvas-interactive-figure.md).
|
[`editorial canvas and Interactive Figure plan`](historical/superpowers/plans/2026-07-05-editorial-canvas-interactive-figure.md).
|
||||||
12. Then: adapt the logical presentation canvas continuously from `4:3` to
|
12. Completed: adapt the logical presentation canvas continuously from `4:3` to
|
||||||
`16:9` and replace the resizing evidence drawer with a progress-row receipt
|
`16:9` and replace the resizing evidence drawer with a progress-row receipt
|
||||||
and centered inspector. Design:
|
and centered inspector. Design:
|
||||||
[`adaptive presentation canvas and evidence inspector`](superpowers/specs/2026-07-05-adaptive-presentation-canvas-design.md).
|
[`adaptive presentation canvas and evidence inspector`](superpowers/specs/2026-07-05-adaptive-presentation-canvas-design.md).
|
||||||
Implementation:
|
Implementation:
|
||||||
[`adaptive presentation canvas plan`](superpowers/plans/2026-07-06-adaptive-presentation-canvas.md).
|
[`adaptive presentation canvas plan`](historical/superpowers/plans/2026-07-06-adaptive-presentation-canvas.md).
|
||||||
13. Then: adopt source-owned AI Elements chat primitives against existing
|
13. Then: adopt source-owned AI Elements chat primitives against existing
|
||||||
`AgentMessagePart` / `AgentDriver` contracts.
|
`AgentMessagePart` / `AgentDriver` contracts.
|
||||||
14. Future: implement Schema Form Surface and synchronized Approval Session.
|
14. Future: implement Schema Form Surface and synchronized Approval Session.
|
||||||
|
|||||||
+28
-7
@@ -111,7 +111,7 @@ With the Python server running, verify in the browser:
|
|||||||
1. The initial page makes no upstream request
|
1. The initial page makes no upstream request
|
||||||
2. Connect succeeds against `http://127.0.0.1:8765/rpc`
|
2. Connect succeeds against `http://127.0.0.1:8765/rpc`
|
||||||
3. Source rows appear after connection
|
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
|
5. Equivalent CLI text is visible
|
||||||
6. `http://example.com:8765/rpc` is rejected without upstream fetch
|
6. `http://example.com:8765/rpc` is rejected without upstream fetch
|
||||||
7. Stopping the Python server produces the unreachable state while preserving
|
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
|
The console exposes `/present`, a 720p no-scroll defense compositor for the
|
||||||
prepared `lda_report_workflow` story. It renders a 12-scene, multi-beat
|
prepared `lda_report_workflow` story. It renders a 12-scene, multi-beat
|
||||||
storyboard with stable stage regions, discussion branches, act themes, a chat
|
storyboard with an adaptive aspect-ratio canvas, stable stage regions, discussion
|
||||||
dock, and keyboard navigation.
|
branches, act themes, a chat dock, and keyboard navigation.
|
||||||
|
|
||||||
Scenes 8 through 10 use the canonical replay as their only execution evidence.
|
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
|
The handoff expands an interpreted run operation into the center stage, then
|
||||||
keeps one workflow graph mounted while execution reaches the typed interrupt
|
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:
|
The key defense states are directly addressable:
|
||||||
|
|
||||||
@@ -230,9 +231,16 @@ committed `lda-report-success-v1` recording. No RPC server is required.
|
|||||||
|
|
||||||
#### Editorial Canvas
|
#### Editorial Canvas
|
||||||
|
|
||||||
The presentation renders on a fixed 1280x720 editorial canvas that scales and
|
The presentation renders on an adaptive editorial canvas that derives its aspect
|
||||||
letterboxes to fit any viewport without reflowing scene content. Scene 6 uses a
|
ratio from the browser viewport. The canvas preserves a fixed logical height of
|
||||||
recursive Interactive Figure with expand/collapse and breadcrumb navigation.
|
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:
|
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
|
- **Tab / arrows**: move focus between figure nodes without advancing the presentation
|
||||||
- **Breadcrumbs**: jump to any ancestor focus level
|
- **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
|
#### Constraints
|
||||||
|
|
||||||
This plan deliberately defers:
|
This plan deliberately defers:
|
||||||
|
|||||||
@@ -28,6 +28,63 @@ beforeAll(() => {
|
|||||||
afterEach(() => cleanup());
|
afterEach(() => cleanup());
|
||||||
|
|
||||||
describe("PresentationRoute", () => {
|
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 () => {
|
it("renders stable chat, primary, progress, and transient evidence surfaces", async () => {
|
||||||
const { PresentationRoute } = await import("./PresentationRoute.js");
|
const { PresentationRoute } = await import("./PresentationRoute.js");
|
||||||
render(<PresentationRoute />);
|
render(<PresentationRoute />);
|
||||||
|
|||||||
@@ -409,6 +409,8 @@
|
|||||||
place-items: center;
|
place-items: center;
|
||||||
padding: 2.5rem 5%;
|
padding: 2.5rem 5%;
|
||||||
background: oklch(0.08 0.02 250 / 0.68);
|
background: oklch(0.08 0.02 250 / 0.68);
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 150ms ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.evidence-inspector {
|
.evidence-inspector {
|
||||||
|
|||||||
Reference in New Issue
Block a user