19 KiB
Presentation Surface Theme Normalization Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Normalize /present chat and discussion/Q&A surfaces so light chat, direct discussion branches, and presenter notes share one editorial presentation vocabulary.
Architecture: Add a small semantic surface contract with data-presentation-surface="editorial|night" on presentation side-surfaces. Style OperatorChat and DiscussionPanel through that contract, keeping existing storyboard, layout, chat messages, and discussion routing intact. Do not introduce a new theme engine or component library in this slice.
Tech Stack: React, TypeScript, CSS, Vitest, Testing Library, Playwright CLI screenshot smoke.
Global Constraints
- Follow design spec:
docs/superpowers/specs/2026-07-08-presentation-surface-theme-normalization-design.md. - Do not change
/console. - Do not replace chat UI.
- Do not alter storyboard content or scene composition.
- Do not add dependencies.
- Keep CSS scoped to presentation surfaces.
- Prefer semantic surface attributes over ad hoc color selectors.
- Generated
.visual-smoke/screenshots must remain ignored.
File Structure
- Modify
web/apps/console/src/presentation/OperatorChat.tsx- Add
data-presentation-surface.
- Add
- Modify
web/apps/console/src/presentation/OperatorChat.test.tsx- Pin editorial/night surface mapping.
- Modify
web/apps/console/src/presentation/DiscussionPanel.tsx- Add
data-presentation-surface="editorial".
- Add
- Modify
web/apps/console/src/presentation/DiscussionPanel.test.tsx- Pin discussion panel surface contract for Q&A branches and legacy branches.
- Modify
web/apps/console/src/presentation/presentation.css- Normalize chat/discussion/presenter-note surfaces.
- Modify
docs/current_roadmap.md- Mark surface normalization complete after implementation.
- Move this plan to
docs/historical/superpowers/plans/2026-07-08-presentation-surface-theme-normalization.mdafter implementation.
Task 1: Add Semantic Surface Attributes
Files:
- Modify:
web/apps/console/src/presentation/OperatorChat.tsx - Modify:
web/apps/console/src/presentation/OperatorChat.test.tsx - Modify:
web/apps/console/src/presentation/DiscussionPanel.tsx - Modify:
web/apps/console/src/presentation/DiscussionPanel.test.tsx
Interfaces:
-
Consumes:
compositionForState(state).chatThemeDiscussionPanelbranch ID.
-
Produces:
OperatorChatroot hasdata-presentation-surface="editorial"whenchatTheme === "light", otherwise"night".DiscussionPanelroot hasdata-presentation-surface="editorial".
-
Step 1: Add failing OperatorChat surface test
In web/apps/console/src/presentation/OperatorChat.test.tsx, add this test:
it("maps light chat theme to the editorial presentation surface", () => {
const state = {
...initialPresentationState,
location: { kind: "main" as const, sceneId: "workflow-demo" as const, beatId: "graph", focusPath: [] },
};
render(<OperatorChat state={state} />);
const chat = screen.getByLabelText("scripted operator chat");
expect(chat).toHaveAttribute("data-chat-theme", "light");
expect(chat).toHaveAttribute("data-presentation-surface", "editorial");
});
Add a second test:
it("maps dark chat theme to the night presentation surface", () => {
render(<OperatorChat state={initialPresentationState} />);
const chat = screen.getByLabelText("scripted operator chat");
expect(chat).toHaveAttribute("data-presentation-surface", "night");
});
- Step 2: Add failing DiscussionPanel surface tests
In web/apps/console/src/presentation/DiscussionPanel.test.tsx, add:
it("renders Q&A branches on the editorial presentation surface", () => {
render(<DiscussionPanel branchId="where-is-ai-agent" onClose={onClose} />);
expect(screen.getByRole("dialog")).toHaveAttribute("data-presentation-surface", "editorial");
});
it("renders legacy discussion branches on the same editorial surface", () => {
render(<DiscussionPanel branchId="hosted-automation" onClose={onClose} />);
expect(screen.getByRole("dialog")).toHaveAttribute("data-presentation-surface", "editorial");
});
- Step 3: Run failing tests
Run:
pnpm --dir web --filter @lda/console test -- src/presentation/OperatorChat.test.tsx src/presentation/DiscussionPanel.test.tsx
Expected: FAIL because data-presentation-surface is not emitted yet.
- Step 4: Add
OperatorChatsurface attribute
In web/apps/console/src/presentation/OperatorChat.tsx, find:
const composition = compositionForState(state);
return (
<aside
className="operator-chat"
data-mode={composition.chatMode}
data-chat-theme={composition.chatTheme}
data-readable-surface={composition.chatTheme === "light" ? "light" : "dark"}
aria-label="scripted operator chat"
>
Replace with:
const composition = compositionForState(state);
const presentationSurface = composition.chatTheme === "light" ? "editorial" : "night";
return (
<aside
className="operator-chat"
data-mode={composition.chatMode}
data-chat-theme={composition.chatTheme}
data-readable-surface={composition.chatTheme === "light" ? "light" : "dark"}
data-presentation-surface={presentationSurface}
aria-label="scripted operator chat"
>
- Step 5: Add
DiscussionPanelsurface attribute
In web/apps/console/src/presentation/DiscussionPanel.tsx, find the root <div>:
<div
ref={dialogRef}
className="discussion-panel"
role="dialog"
aria-modal="true"
aria-label={branch.title}
onKeyDown={trapKeyboardWithinDialog}
>
Add:
data-presentation-surface="editorial"
Expected result:
<div
ref={dialogRef}
className="discussion-panel"
data-presentation-surface="editorial"
role="dialog"
aria-modal="true"
aria-label={branch.title}
onKeyDown={trapKeyboardWithinDialog}
>
- Step 6: Run focused tests
Run:
pnpm --dir web --filter @lda/console test -- src/presentation/OperatorChat.test.tsx src/presentation/DiscussionPanel.test.tsx
Expected: PASS.
- Step 7: Commit Task 1
git add web/apps/console/src/presentation/OperatorChat.tsx web/apps/console/src/presentation/OperatorChat.test.tsx web/apps/console/src/presentation/DiscussionPanel.tsx web/apps/console/src/presentation/DiscussionPanel.test.tsx
git commit -m "feat: mark presentation side surfaces"
Task 2: Normalize Chat Surface Styling
Files:
- Modify:
web/apps/console/src/presentation/presentation.css - Test:
web/apps/console/src/presentation/OperatorChat.test.tsx
Interfaces:
-
Consumes:
.operator-chat[data-presentation-surface="editorial"].operator-chat[data-presentation-surface="night"].chat-message.chat-tool-part
-
Produces:
- Editorial chat cards are off-paper/tinted, not pure white.
- Night chat cards stay dark and readable.
-
Step 1: Add test for message surface class stability
In OperatorChat.test.tsx, add this to the light-theme test from Task 1 after the data-presentation-surface assertion:
expect(screen.getAllByText(/Found prepared workflow recipe/)[0]?.closest(".chat-message"))
.toHaveClass("chat-message");
This test does not assert computed color. It pins the DOM hook that CSS relies on.
- Step 2: Update chat CSS
In web/apps/console/src/presentation/presentation.css, keep the base .chat-message dark readable style, then replace the current light override:
.operator-chat[data-readable-surface="light"] .chat-message {
border-color: color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 35%, transparent);
background: color-mix(in oklch, var(--color-editorial-paper, oklch(0.975 0.012 82)) 92%, white);
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
}
with:
.operator-chat[data-presentation-surface="editorial"] .chat-message {
border-color: color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 34%, transparent);
background: color-mix(in oklch, var(--color-editorial-paper, oklch(0.975 0.012 82)) 82%, var(--color-editorial-muted, oklch(0.48 0.025 65)) 18%);
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
}
Replace:
.operator-chat[data-readable-surface="light"] .chat-message strong {
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
}
with:
.operator-chat[data-presentation-surface="editorial"] .chat-message strong {
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
}
Replace:
.operator-chat[data-readable-surface="light"] .chat-tool-part {
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
}
with:
.operator-chat[data-presentation-surface="editorial"] .chat-tool-part {
border-color: color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 40%, transparent);
background: color-mix(in oklch, var(--color-editorial-paper, oklch(0.975 0.012 82)) 70%, transparent);
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
}
Keep data-readable-surface selectors only if another rule still uses them. Prefer the new semantic surface selectors for new rules.
- Step 3: Run focused test
Run:
pnpm --dir web --filter @lda/console test -- src/presentation/OperatorChat.test.tsx
Expected: PASS.
- Step 4: Commit Task 2
git add web/apps/console/src/presentation/presentation.css web/apps/console/src/presentation/OperatorChat.test.tsx
git commit -m "fix: normalize presentation chat surface"
Task 3: Normalize Discussion/Q&A Surface Styling
Files:
- Modify:
web/apps/console/src/presentation/presentation.css - Test:
web/apps/console/src/presentation/DiscussionPanel.test.tsx
Interfaces:
-
Consumes:
.discussion-panel[data-presentation-surface="editorial"].discussion-panel__qna.discussion-panel__presenter-note
-
Produces:
- Discussion panels use editorial surface colors.
- Q&A answers are readable.
- Presenter notes are demoted but legible.
-
Step 1: Add tests for Q&A/presenter-note hooks
In DiscussionPanel.test.tsx, extend "renders defense Q&A fields when present" with:
expect(screen.getByLabelText("defense question")).toBeInTheDocument();
Extend "renders speaker hints as presenter notes instead of answer content" with:
expect(note).toHaveClass("discussion-panel__presenter-note");
- Step 2: Update discussion panel CSS
In web/apps/console/src/presentation/presentation.css, replace the base .discussion-panel rule:
.discussion-panel {
padding: 1rem;
border: 1px solid oklch(0.36 0.04 250);
background: oklch(0.16 0.025 250);
border-radius: 0.65rem;
}
with:
.discussion-panel {
padding: 1rem;
border: 1px solid color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 35%, transparent);
border-radius: 0.75rem;
background: color-mix(in oklch, var(--color-editorial-paper, oklch(0.975 0.012 82)) 88%, white);
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
}
Update the following selectors to use editorial colors:
.discussion-panel h2 {
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
}
.discussion-panel__badge {
background: color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 18%, transparent);
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
}
.discussion-panel__evidence {
color: var(--color-editorial-muted, oklch(0.48 0.025 65));
}
.discussion-panel__summary {
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
}
.discussion-panel__qna {
border-color: color-mix(in oklch, var(--color-intent, oklch(0.53 0.17 250)) 28%, transparent);
background: color-mix(in oklch, var(--color-editorial-paper, oklch(0.975 0.012 82)) 74%, white);
}
.discussion-panel__question,
.discussion-panel__short-answer {
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
}
.discussion-panel__expanded-answer {
color: color-mix(in oklch, var(--color-editorial-ink, oklch(0.19 0.015 65)) 82%, var(--color-editorial-muted, oklch(0.48 0.025 65)));
}
.discussion-panel__presenter-note {
border-color: color-mix(in oklch, var(--color-human, oklch(0.68 0.17 55)) 38%, transparent);
background: color-mix(in oklch, var(--color-human, oklch(0.68 0.17 55)) 10%, var(--color-editorial-paper, oklch(0.975 0.012 82)));
color: color-mix(in oklch, var(--color-editorial-ink, oklch(0.19 0.015 65)) 76%, var(--color-editorial-muted, oklch(0.48 0.025 65)));
}
.discussion-panel__presenter-note span {
color: color-mix(in oklch, var(--color-human, oklch(0.68 0.17 55)) 70%, var(--color-editorial-ink, oklch(0.19 0.015 65)));
}
.discussion-panel__detail {
color: color-mix(in oklch, var(--color-editorial-ink, oklch(0.19 0.015 65)) 82%, var(--color-editorial-muted, oklch(0.48 0.025 65)));
border-left-color: color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 42%, transparent);
}
Keep .discussion-panel__return as a clear action button, but ensure it does not look like a saturated unrelated pill. If needed, update it to:
.discussion-panel__return {
margin-top: 0.75rem;
border: 1px solid var(--color-editorial-ink, oklch(0.19 0.015 65));
border-radius: 0.4rem;
padding: 0.4rem 0.75rem;
background: var(--color-editorial-ink, oklch(0.19 0.015 65));
color: var(--color-editorial-paper, oklch(0.975 0.012 82));
font-weight: 600;
}
- Step 3: Run focused test
Run:
pnpm --dir web --filter @lda/console test -- src/presentation/DiscussionPanel.test.tsx
Expected: PASS.
- Step 4: Commit Task 3
git add web/apps/console/src/presentation/presentation.css web/apps/console/src/presentation/DiscussionPanel.test.tsx
git commit -m "fix: normalize discussion surface theme"
Task 4: Screenshot Smoke And Roadmap
Files:
- Modify:
docs/current_roadmap.md - Move:
docs/superpowers/plans/2026-07-08-presentation-surface-theme-normalization.mdtodocs/historical/superpowers/plans/2026-07-08-presentation-surface-theme-normalization.md
Interfaces:
-
Consumes: Tasks 1-3.
-
Produces: verified visual smoke and updated roadmap.
-
Step 1: Run focused tests
Run:
pnpm --dir web --filter @lda/console test -- src/presentation/OperatorChat.test.tsx src/presentation/DiscussionPanel.test.tsx
Expected: PASS.
- Step 2: Run build checks
Run:
pnpm --dir web --filter @lda/console typecheck
pnpm --dir web --filter @lda/console build
git diff --check
Expected:
-
Typecheck passes.
-
Build succeeds. Existing chunk-size warning is acceptable if unchanged.
-
No whitespace errors.
-
Step 3: Capture screenshot smoke
Start dev server if needed:
pnpm --dir web dev
Capture:
New-Item -ItemType Directory -Force web/apps/console/.visual-smoke | Out-Null
pnpx playwright screenshot --viewport-size=1280,720 "http://127.0.0.1:5173/present#scene/positioning/landscape" web/apps/console/.visual-smoke/surface-positioning-1280x720.png
pnpx playwright screenshot --viewport-size=1280,720 "http://127.0.0.1:5173/present#scene/planner-runtime/planner" web/apps/console/.visual-smoke/surface-planner-runtime-1280x720.png
pnpx playwright screenshot --viewport-size=1280,720 "http://127.0.0.1:5173/present#scene/lifecycle/draft" web/apps/console/.visual-smoke/surface-lifecycle-1280x720.png
pnpx playwright screenshot --viewport-size=1280,720 "http://127.0.0.1:5173/present#discuss/where-is-ai-agent" web/apps/console/.visual-smoke/surface-qna-agent-1280x720.png
pnpx playwright screenshot --viewport-size=1280,720 "http://127.0.0.1:5173/present#discuss/evaluation-validity" web/apps/console/.visual-smoke/surface-qna-evaluation-1280x720.png
pnpx playwright screenshot --viewport-size=1280,720 "http://127.0.0.1:5173/present#scene/interrupt-evidence/approval" web/apps/console/.visual-smoke/surface-approval-1280x720.png
Manual acceptance:
-
Chat is not a bright white card stack.
-
Discussion/Q&A panels look editorial and readable.
-
Presenter note is demoted but legible.
-
Scene 3/4/5 direct surfaces do not look like mismatched dark modals.
-
.visual-smoke/remains ignored by git. -
Step 4: Update roadmap
In docs/current_roadmap.md, under Next presentation visual slices, replace:
2. Surface theme normalization pass: make light chat, discussion modals, and
Q&A branches use the same editorial presentation surface instead of
jumping between overly white chat cards and dark modal interiors.
with:
2. Completed: surface theme normalization pass made light chat, discussion
modals, and Q&A branches use the same editorial presentation surface.
Implementation:
[`presentation surface theme normalization`](historical/superpowers/plans/2026-07-08-presentation-surface-theme-normalization.md).
Renumber the remaining entries so the scene composition pass is 3 and the
presentation craft pass is 4.
- Step 5: Archive plan
Run:
git mv docs/superpowers/plans/2026-07-08-presentation-surface-theme-normalization.md docs/historical/superpowers/plans/2026-07-08-presentation-surface-theme-normalization.md
If the plan is not tracked yet, use PowerShell:
Move-Item docs/superpowers/plans/2026-07-08-presentation-surface-theme-normalization.md docs/historical/superpowers/plans/2026-07-08-presentation-surface-theme-normalization.md
- Step 6: Commit Task 4
git add docs/current_roadmap.md docs/historical/superpowers/plans/2026-07-08-presentation-surface-theme-normalization.md
git commit -m "docs: record presentation surface normalization"
Final Verification
- Run focused tests:
pnpm --dir web --filter @lda/console test -- src/presentation/OperatorChat.test.tsx src/presentation/DiscussionPanel.test.tsx
Expected: PASS.
- Run full web tests:
pnpm --dir web test
Expected: PASS.
- Run full web typecheck:
pnpm --dir web typecheck
Expected: PASS.
- Run status checks:
git diff --check
git status --short
Expected:
- No whitespace errors.
- No untracked
.visual-smoke/files. - Only intentional files remain if not yet committed.
Review Checklist
- Chat no longer appears as a bright white card stack.
- Direct discussion routes are readable and editorial.
- Q&A branches and legacy branches use the same discussion surface.
- Presenter notes are demoted and legible.
/consoleunchanged.- No new dependency added.
- Scene composition remains deferred to the next slice.