docs: plan presentation discussion modal composition

This commit is contained in:
lda
2026-07-09 05:05:42 +07:00 Verified
parent 7d39df59b3
commit e93a91a0d5
@@ -0,0 +1,604 @@
# 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.
- 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.md` after 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 `DiscussionPanelProps` and `DiscussionBranchDefinition` fields.
- Produces: stable DOM sections with labels:
- `discussion shell`
- `discussion body`
- `discussion support`
- `discussion actions`
- [ ] **Step 1: Add failing structure tests**
In `web/apps/console/src/presentation/DiscussionPanel.test.tsx`, add:
```tsx
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:
```powershell
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:
```tsx
<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.detail` block after the Q&A section.
- Keep `returnButtonRef` attached to the return button.
- Keep `role="dialog"`, `aria-modal`, `aria-label`, and `onKeyDown` on the root `.discussion-panel` element.
- [ ] **Step 4: Run tests and verify they pass**
Run:
```powershell
pnpm --dir web --filter @lda/console test -- src/presentation/DiscussionPanel.test.tsx
```
Expected: pass.
- [ ] **Step 5: Commit Task 1**
```powershell
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:
```tsx
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:
```powershell
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:
```css
.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:
```css
.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:
```css
.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:
```powershell
pnpm --dir web --filter @lda/console test -- src/presentation/DiscussionPanel.test.tsx
```
Expected: pass.
- [ ] **Step 7: Commit Task 2**
```powershell
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.md` to `docs/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:
```powershell
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:
```powershell
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:
```powershell
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-shadow` findings.
- No `side-stripe` style finding.
- No new `all-caps-body` findings from modal content.
- `line-length` or `flat-type-hierarchy` findings may still appear; report them if present and do not expand scope.
- [ ] **Step 4: Capture screenshots**
Run:
```powershell
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:
```md
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:
```powershell
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:
```powershell
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**
```powershell
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:
```powershell
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 --check` is clean.
- `git status --short` is 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.