23 KiB
Presentation Discussion Modal Composition 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: Make discussion routes look like intentional defense overlays instead of plain document cards by giving Q&A and context-only branches a stage-aware composition.
Architecture: Keep discussion navigation, branch data, and focus-trap behavior unchanged. Add a small presentation shell around DiscussionPanel content: the panel remains the only dialog component, but it gets an inner layout with header, content, and action zones. CSS chooses a Q&A layout or context layout from data-discussion-layout; no new state, routes, or component library.
Tech Stack: React, TypeScript, Vitest, Testing Library, existing presentation CSS, Playwright CLI screenshots, npx -y impeccable detect rendered-route scan.
Global Constraints
- Do not change branch ids, route hashes, or Q&A answer copy.
- Do not change focus trap semantics: return button focuses on open, Escape closes, Tab stays inside dialog.
- Do not introduce a modal library or component library.
- Do not touch demo workflow scenes, graph layout, evidence inspector, or replay state.
- Avoid slop patterns: centered generic card, huge empty context page, side-stripe callout, wide diffuse shadow, all-caps body text.
- At
1280x720, both Q&A and context discussion routes must fit without page scroll.
Current Findings
The previous discussion craft pass improved semantics and hierarchy, but screenshots still show:
- Q&A route reads as a document card spanning the top of the slide.
- Context-only route leaves the bottom two-thirds empty, which looks unfinished.
- Return action is functionally correct but visually disconnected from the content hierarchy.
This slice should make discussion feel like a stage overlay: left side for the question/claim, right side for answer/provenance/actions, and a compact context layout for non-Q&A branches.
File Structure
- Modify
web/apps/console/src/presentation/DiscussionPanel.tsx- Add structural wrappers:
.discussion-panel__shell,.discussion-panel__header,.discussion-panel__body,.discussion-panel__aside,.discussion-panel__actions. - Keep current branch fields and focus trap.
- Add structural wrappers:
- Modify
web/apps/console/src/presentation/DiscussionPanel.test.tsx- Pin shell/body/aside/actions semantics for Q&A and context layouts.
- Modify
web/apps/console/src/presentation/presentation.css- Add stage-sized discussion layout.
- Make context layout intentionally compact instead of top-heavy.
- Preserve existing editorial tokens and avoid shadows/side stripes.
- Modify
docs/current_roadmap.md- Mark this modal composition pass complete after implementation.
- Move this plan to
docs/historical/superpowers/plans/2026-07-09-presentation-discussion-modal-composition.mdafter implementation.
Task 1: Add Discussion Panel Composition Structure
Files:
- Modify:
web/apps/console/src/presentation/DiscussionPanel.tsx - Modify:
web/apps/console/src/presentation/DiscussionPanel.test.tsx
Interfaces:
-
Consumes: existing
DiscussionPanelPropsandDiscussionBranchDefinitionfields. -
Produces: stable DOM sections with labels:
discussion shelldiscussion bodydiscussion supportdiscussion actions
-
Step 1: Add failing structure tests
In web/apps/console/src/presentation/DiscussionPanel.test.tsx, add:
it("renders Q&A discussion as shell, body, support, and actions regions", () => {
render(<DiscussionPanel branchId="where-is-ai-agent" onClose={onClose} />);
expect(screen.getByRole("dialog")).toHaveAttribute("data-discussion-layout", "qna");
expect(screen.getByLabelText("discussion shell")).toBeInTheDocument();
expect(screen.getByLabelText("discussion body")).toHaveTextContent("Where is the AI agent in this thesis?");
expect(screen.getByLabelText("discussion support")).toHaveTextContent("Evidence");
expect(screen.getByLabelText("discussion actions")).toContainElement(
screen.getByRole("button", { name: /return to thesis/i }),
);
});
it("renders context-only discussion with a compact support column", () => {
render(<DiscussionPanel branchId="hosted-automation" onClose={onClose} />);
expect(screen.getByRole("dialog")).toHaveAttribute("data-discussion-layout", "context");
expect(screen.getByLabelText("discussion body")).toHaveTextContent(/Hosted triggers/i);
expect(screen.getByLabelText("discussion support")).toHaveTextContent(/Workflow Automation Platforms/i);
expect(screen.queryByLabelText("defense question")).not.toBeInTheDocument();
});
- Step 2: Run tests and verify they fail
Run:
pnpm --dir web --filter @lda/console test -- src/presentation/DiscussionPanel.test.tsx
Expected: fail because the labelled shell/body/support/actions regions do not exist.
- Step 3: Refactor JSX into shell regions
In web/apps/console/src/presentation/DiscussionPanel.tsx, keep imports, props, focus effect, trapKeyboardWithinDialog, and hasQuestion logic. Replace the dialog contents inside the root <div> with this structure:
<div className="discussion-panel__shell" aria-label="discussion shell">
<header className="discussion-panel__header">
<div>
<span className="discussion-panel__badge">{branch.claimClass}</span>
<h2>{branch.title}</h2>
</div>
<p>{branch.summary}</p>
</header>
<main className="discussion-panel__body" aria-label="discussion body">
{hasQuestion ? (
<section className="discussion-panel__qna" aria-label="defense question">
<p className="discussion-panel__question">{branch.question}</p>
{branch.shortAnswer && (
<article className="discussion-panel__answer-card" aria-label="short defense answer">
<span>Short answer</span>
<p>{branch.shortAnswer}</p>
</article>
)}
{branch.expandedAnswer && (
<article className="discussion-panel__answer-card discussion-panel__answer-card--expanded" aria-label="answer expansion">
<span>Expanded answer</span>
<p>{branch.expandedAnswer}</p>
</article>
)}
</section>
) : (
<section className="discussion-panel__context" aria-label="discussion context">
<p>{branch.summary}</p>
{branch.detail && (
<div className="discussion-panel__detail" aria-label="additional context">
<p>
{branch.detail.links?.map((link, index) => (
<span key={link.href}>
{index > 0 && " · "}
<a href={link.href} target="_blank" rel="noopener noreferrer">{link.label}</a>
</span>
))}
{branch.detail.links && branch.detail.links.length > 0 ? " — " : ""}
{branch.detail.text}
</p>
</div>
)}
</section>
)}
</main>
<aside className="discussion-panel__aside" aria-label="discussion support">
<section className="discussion-panel__provenance" aria-label="answer provenance">
<span>Evidence</span>
<p>{branch.evidencePointer}</p>
</section>
{branch.speakerHint && (
<aside className="discussion-panel__presenter-note" aria-label="presenter note">
<span>Presenter note</span>
<p>{branch.speakerHint}</p>
</aside>
)}
</aside>
<footer className="discussion-panel__actions" aria-label="discussion actions">
<button ref={returnButtonRef} type="button" onClick={onClose} className="discussion-panel__return">
Return to {parentScene?.title ?? "scene"}
</button>
</footer>
</div>
Important:
-
Remove the old top-level
<p className="discussion-panel__summary">outside the shell. -
Remove the old duplicate
branch.detailblock after the Q&A section. -
Keep
returnButtonRefattached to the return button. -
Keep
role="dialog",aria-modal,aria-label, andonKeyDownon the root.discussion-panelelement. -
Step 4: Run tests and verify they pass
Run:
pnpm --dir web --filter @lda/console test -- src/presentation/DiscussionPanel.test.tsx
Expected: pass.
- Step 5: Commit Task 1
git add web/apps/console/src/presentation/DiscussionPanel.tsx web/apps/console/src/presentation/DiscussionPanel.test.tsx
git commit -m "fix: structure discussion modal regions"
Task 2: Compose Q&A And Context Layouts Visually
Files:
- Modify:
web/apps/console/src/presentation/presentation.css - Modify:
web/apps/console/src/presentation/DiscussionPanel.test.tsx
Interfaces:
-
Consumes:
.discussion-panel[data-discussion-layout="qna" | "context"]and shell regions from Task 1. -
Produces: stage-aware modal composition with no document-like top-heavy layout.
-
Step 1: Add class-contract tests
In web/apps/console/src/presentation/DiscussionPanel.test.tsx, add:
it("marks the modal shell with the active layout for styling", () => {
const { rerender } = render(<DiscussionPanel branchId="where-is-ai-agent" onClose={onClose} />);
expect(screen.getByLabelText("discussion shell")).toHaveClass("discussion-panel__shell");
expect(screen.getByRole("dialog")).toHaveAttribute("data-discussion-layout", "qna");
rerender(<DiscussionPanel branchId="hosted-automation" onClose={onClose} />);
expect(screen.getByRole("dialog")).toHaveAttribute("data-discussion-layout", "context");
});
- Step 2: Run tests
Run:
pnpm --dir web --filter @lda/console test -- src/presentation/DiscussionPanel.test.tsx
Expected: pass after Task 1. This test pins styling hooks before CSS work.
- Step 3: Replace discussion panel base CSS
In web/apps/console/src/presentation/presentation.css, replace the current .discussion-panel[data-presentation-surface="editorial"] block and its direct h2/badge rules with:
.discussion-panel[data-presentation-surface="editorial"] {
min-height: min(34rem, calc(100cqh - 2rem));
padding: 0;
border: 1px solid color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 34%, transparent);
border-radius: 0.9rem;
overflow: hidden;
background: color-mix(in oklch, var(--color-editorial-paper, oklch(0.975 0.012 82)) 94%, white);
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
}
.discussion-panel__shell {
display: grid;
grid-template-columns: minmax(0, 1.35fr) minmax(17rem, 0.65fr);
grid-template-rows: auto minmax(0, 1fr) auto;
gap: 0;
min-height: inherit;
}
.discussion-panel__header {
grid-column: 1 / -1;
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(18rem, 0.55fr);
gap: 1rem;
align-items: end;
border-bottom: 1px solid color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 28%, transparent);
padding: 1rem 1.1rem 0.85rem;
}
.discussion-panel__header h2 {
margin: 0.35rem 0 0;
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
font-size: clamp(1.25rem, 2.6vw, 2rem);
line-height: 1;
}
.discussion-panel__header p {
margin: 0;
color: color-mix(in oklch, var(--color-editorial-ink, oklch(0.19 0.015 65)) 74%, var(--color-editorial-muted, oklch(0.48 0.025 65)));
font-size: 0.9rem;
line-height: 1.35;
}
.discussion-panel[data-presentation-surface="editorial"] .discussion-panel__badge {
display: inline-block;
border-radius: 0.35rem;
background: color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 16%, transparent);
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
padding: 0.18rem 0.45rem;
font: 700 0.7rem/1 var(--font-interface);
}
- Step 4: Add body/support/action CSS
Still in presentation.css, replace the current .discussion-panel__qna, .discussion-panel__question, .discussion-panel__answer-card, .discussion-panel__presenter-note, .discussion-panel__detail, and .discussion-panel__return rules with:
.discussion-panel__body {
min-width: 0;
padding: 1rem 1.1rem;
}
.discussion-panel__aside {
display: grid;
align-content: start;
gap: 0.75rem;
border-left: 1px solid color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 22%, transparent);
padding: 1rem;
background: color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 7%, transparent);
}
.discussion-panel__actions {
grid-column: 1 / -1;
display: flex;
justify-content: flex-end;
border-top: 1px solid color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 22%, transparent);
padding: 0.75rem 1rem;
}
.discussion-panel[data-presentation-surface="editorial"] .discussion-panel__qna {
display: grid;
gap: 0.75rem;
margin: 0;
}
.discussion-panel[data-presentation-surface="editorial"] .discussion-panel__question {
margin: 0;
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
font-size: clamp(1.35rem, 2.4vw, 2rem);
font-weight: 760;
line-height: 1.08;
text-wrap: balance;
}
.discussion-panel[data-presentation-surface="editorial"] .discussion-panel__answer-card {
display: grid;
gap: 0.4rem;
border: 1px solid color-mix(in oklch, var(--color-intent, oklch(0.53 0.17 250)) 28%, transparent);
border-radius: 0.75rem;
padding: 0.8rem 0.85rem;
background: color-mix(in oklch, var(--color-intent, oklch(0.53 0.17 250)) 5%, var(--color-editorial-paper, oklch(0.975 0.012 82)));
}
.discussion-panel[data-presentation-surface="editorial"] .discussion-panel__answer-card--expanded {
border-color: color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 28%, transparent);
background: color-mix(in oklch, var(--color-editorial-paper, oklch(0.975 0.012 82)) 90%, white);
}
.discussion-panel[data-presentation-surface="editorial"] .discussion-panel__answer-card span,
.discussion-panel[data-presentation-surface="editorial"] .discussion-panel__provenance span,
.discussion-panel[data-presentation-surface="editorial"] .discussion-panel__presenter-note span {
color: color-mix(in oklch, var(--color-editorial-ink, oklch(0.19 0.015 65)) 68%, var(--color-editorial-muted, oklch(0.48 0.025 65)));
font: 700 0.68rem/1 var(--font-interface);
}
.discussion-panel[data-presentation-surface="editorial"] .discussion-panel__answer-card p {
margin: 0;
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
line-height: 1.42;
}
.discussion-panel[data-presentation-surface="editorial"] .discussion-panel__provenance,
.discussion-panel[data-presentation-surface="editorial"] .discussion-panel__presenter-note,
.discussion-panel[data-presentation-surface="editorial"] .discussion-panel__detail {
display: grid;
gap: 0.35rem;
border: 1px solid color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 24%, transparent);
border-radius: 0.7rem;
padding: 0.65rem;
background: color-mix(in oklch, var(--color-editorial-paper, oklch(0.975 0.012 82)) 74%, white);
}
.discussion-panel[data-presentation-surface="editorial"] .discussion-panel__provenance p,
.discussion-panel[data-presentation-surface="editorial"] .discussion-panel__presenter-note p,
.discussion-panel[data-presentation-surface="editorial"] .discussion-panel__detail p {
margin: 0;
color: color-mix(in oklch, var(--color-editorial-ink, oklch(0.19 0.015 65)) 84%, var(--color-editorial-muted, oklch(0.48 0.025 65)));
font-size: 0.85rem;
line-height: 1.38;
}
.discussion-panel[data-presentation-surface="editorial"] .discussion-panel__presenter-note {
border-style: dashed;
background: color-mix(in oklch, var(--color-human, oklch(0.68 0.17 55)) 8%, var(--color-editorial-paper, oklch(0.975 0.012 82)));
}
.discussion-panel[data-presentation-surface="editorial"] .discussion-panel__return {
border: 1px solid var(--color-editorial-ink, oklch(0.19 0.015 65));
border-radius: 0.45rem;
padding: 0.48rem 0.8rem;
background: var(--color-editorial-ink, oklch(0.19 0.015 65));
color: var(--color-editorial-paper, oklch(0.975 0.012 82));
font-weight: 650;
}
- Step 5: Add context layout CSS
Add these rules after the block above:
.discussion-panel[data-discussion-layout="context"] .discussion-panel__shell {
grid-template-columns: minmax(0, 1fr) minmax(18rem, 0.42fr);
min-height: min(24rem, calc(100cqh - 2rem));
}
.discussion-panel[data-discussion-layout="context"] .discussion-panel__body {
display: grid;
align-content: start;
gap: 0.75rem;
}
.discussion-panel__context {
display: grid;
gap: 0.75rem;
}
.discussion-panel__context > p {
margin: 0;
max-width: 68ch;
color: var(--color-editorial-ink, oklch(0.19 0.015 65));
font-size: 1.12rem;
line-height: 1.35;
}
@media (max-height: 760px) {
.discussion-panel__shell,
.discussion-panel[data-discussion-layout="context"] .discussion-panel__shell {
grid-template-columns: minmax(0, 1fr);
min-height: auto;
}
.discussion-panel__header {
grid-template-columns: minmax(0, 1fr);
}
.discussion-panel__aside {
border-left: 0;
border-top: 1px solid color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 22%, transparent);
}
}
- Step 6: Run tests and verify they pass
Run:
pnpm --dir web --filter @lda/console test -- src/presentation/DiscussionPanel.test.tsx
Expected: pass.
- Step 7: Commit Task 2
git add web/apps/console/src/presentation/DiscussionPanel.test.tsx web/apps/console/src/presentation/presentation.css
git commit -m "fix: compose discussion modal stage layout"
Task 3: Browser Smoke, Detector, And Roadmap
Files:
- Modify:
docs/current_roadmap.md - Move:
docs/superpowers/plans/2026-07-09-presentation-discussion-modal-composition.mdtodocs/historical/superpowers/plans/2026-07-09-presentation-discussion-modal-composition.md
Interfaces:
-
Consumes: rendered
/present#discuss/...routes. -
Produces: screenshots, detector output, roadmap completion.
-
Step 1: Run focused tests
Run:
pnpm --dir web --filter @lda/console test -- src/presentation/DiscussionPanel.test.tsx src/presentation/PresentationRoute.test.tsx
Expected: pass.
- Step 2: Run broad presentation checks
Run:
pnpm --dir web --filter @lda/console test -- src/presentation
pnpm --dir web --filter @lda/console typecheck
pnpm --dir web --filter @lda/console build
git diff --check
Expected: presentation tests pass, typecheck clean, build passes with only known chunk-size warning, diff check clean.
- Step 3: Run detector smoke
Run:
npx -y impeccable detect --json --gpt --gemini http://127.0.0.1:5173/present#discuss/where-is-ai-agent
npx -y impeccable detect --json --gpt --gemini http://127.0.0.1:5173/present#discuss/hosted-automation
Expected:
-
No
gpt-thin-border-wide-shadowfindings. -
No
side-stripestyle finding. -
No new
all-caps-bodyfindings from modal content. -
line-lengthorflat-type-hierarchyfindings may still appear; report them if present and do not expand scope. -
Step 4: Capture screenshots
Run:
pnpx --package @playwright/cli playwright-cli -s=discussion-modal open "http://127.0.0.1:5173/present#discuss/where-is-ai-agent"
pnpx --package @playwright/cli playwright-cli -s=discussion-modal resize 1280 720
pnpx --package @playwright/cli playwright-cli -s=discussion-modal screenshot --filename web/apps/console/.visual-smoke/discussion-modal-qna.png
pnpx --package @playwright/cli playwright-cli -s=discussion-modal open "http://127.0.0.1:5173/present#discuss/hosted-automation"
pnpx --package @playwright/cli playwright-cli -s=discussion-modal screenshot --filename web/apps/console/.visual-smoke/discussion-modal-context.png
Acceptance criteria:
-
Q&A modal uses the slide area intentionally with header/body/support/actions regions.
-
Context-only modal no longer leaves a huge empty lower stage.
-
Return button remains visible at
1280x720. -
No visible page scrollbar.
-
Step 5: Update roadmap
In docs/current_roadmap.md, under Next presentation visual slices, insert this completed item after the discussion craft item:
9. Completed: discussion modal composition pass made Q&A and context-only
routes stage-aware, with body/support/action regions instead of plain
document cards. Implementation:
[`presentation discussion modal composition`](historical/superpowers/plans/2026-07-09-presentation-discussion-modal-composition.md).
10. Presentation craft pass: tune remaining motion, evidence receipt
placement, route-level caption contrast, and graph visual language after
the discussion modal is stable.
Renumber the existing open craft-pass item if needed.
- Step 6: Archive the plan
Run:
Move-Item docs/superpowers/plans/2026-07-09-presentation-discussion-modal-composition.md docs/historical/superpowers/plans/2026-07-09-presentation-discussion-modal-composition.md
Verify:
rg -n "presentation discussion modal composition|2026-07-09-presentation-discussion-modal-composition" docs/current_roadmap.md docs/superpowers/plans docs/historical/superpowers/plans
Expected:
-
Roadmap points to the historical plan path.
-
No active copy remains under
docs/superpowers/plans/. -
Step 7: Commit Task 3
git add docs/current_roadmap.md docs/historical/superpowers/plans/2026-07-09-presentation-discussion-modal-composition.md
git add -u docs/superpowers/plans/2026-07-09-presentation-discussion-modal-composition.md
git commit -m "docs: record presentation discussion modal composition"
Final Verification
Run:
pnpm --dir web --filter @lda/console test -- src/presentation
pnpm --dir web --filter @lda/console typecheck
pnpm --dir web --filter @lda/console build
npx -y impeccable detect --json --gpt --gemini http://127.0.0.1:5173/present#discuss/where-is-ai-agent
npx -y impeccable detect --json --gpt --gemini http://127.0.0.1:5173/present#discuss/hosted-automation
git diff --check
git status --short
Expected:
- Presentation tests pass.
- Typecheck passes.
- Build passes with only known chunk warning if present.
- Detector shows no ghost-card, side-stripe, or all-caps-body findings from the discussion modal.
git diff --checkis clean.git status --shortis clean.
Self-Review
- Spec coverage: plan covers structure, visual composition, context-only route, detector smoke, screenshots, roadmap, and archival.
- Placeholder scan: no placeholder markers or vague implementation instructions are present.
- Type consistency: uses existing
DiscussionPanelProps, existing branch fields, and current test utilities. - Scope check: no demo workflow, graph, evidence inspector, route hash, or Q&A copy changes.