Skip to content

Commit 3be2ca3

Browse files
committed
docs: add superpowers plans and specs for header system and typography
1 parent e7b0b69 commit 3be2ca3

4 files changed

Lines changed: 2959 additions & 0 deletions

File tree

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
# Header System Implementation Plan
2+
3+
> **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.
4+
5+
**Goal:** Unify all web headers into three fixed public components: `PageHeader`, `PanelHeader`, and `DialogHeader`.
6+
7+
**Architecture:** Keep `PageHeader` as the shared navigation header, add a new `PanelHeader` for dense surface chrome, and make modal headers the canonical `DialogHeader`. Existing page-specific headers should be migrated onto these primitives without introducing any new ad hoc header shapes.
8+
9+
**Tech Stack:** React, TypeScript, Vitest, Testing Library, CSS modules / global component CSS
10+
11+
---
12+
13+
### Task 1: Extend the shared page header primitive
14+
15+
**Files:**
16+
- Modify: `packages/web/src/features/shared/components/page-header.tsx`
17+
- Modify: `packages/web/src/features/shared/components/page-header.test.tsx`
18+
- Modify: `packages/web/src/features/shared/components/mobile-page-header.tsx`
19+
- Modify: `packages/web/src/features/shared/components/mobile-page-header.test.tsx`
20+
- Modify: `packages/web/src/styles/components.css`
21+
22+
- [ ] **Step 1: Write the failing test**
23+
24+
```tsx
25+
import { render, screen } from "@testing-library/react";
26+
import { describe, expect, it, vi } from "vitest";
27+
import { PageHeader } from "./page-header";
28+
29+
describe("PageHeader", () => {
30+
it("supports primary and secondary levels with distinct title sizing hooks", () => {
31+
render(
32+
<>
33+
<PageHeader title="Primary" level="primary" onBack={vi.fn()} />
34+
<PageHeader title="Secondary" level="secondary" onBack={vi.fn()} />
35+
</>
36+
);
37+
38+
expect(document.querySelectorAll(".page-header--primary").length).toBe(1);
39+
expect(document.querySelectorAll(".page-header--secondary").length).toBe(1);
40+
});
41+
});
42+
```
43+
44+
- [ ] **Step 2: Run the test to verify it fails**
45+
46+
Run: `pnpm vitest packages/web/src/features/shared/components/page-header.test.tsx -t "supports primary and secondary levels with distinct title sizing hooks" -v`
47+
Expected: FAIL because `level` and the modifier classes do not exist yet.
48+
49+
- [ ] **Step 3: Write the minimal implementation**
50+
51+
```tsx
52+
export interface PageHeaderProps {
53+
title: string;
54+
level?: "primary" | "secondary";
55+
onBack?: () => void;
56+
backLabel?: string;
57+
backAriaLabel?: string;
58+
kicker?: ReactNode;
59+
rightSlot?: ReactNode;
60+
titleAs?: PageHeaderTitleElement;
61+
className?: string;
62+
}
63+
64+
export function PageHeader({
65+
title,
66+
level = "secondary",
67+
onBack,
68+
backLabel = "Back",
69+
backAriaLabel,
70+
kicker,
71+
rightSlot,
72+
titleAs = "h2",
73+
className,
74+
}: PageHeaderProps) {
75+
const TitleTag = titleAs;
76+
77+
return (
78+
<div className={clsx("page-header", `page-header--${level}`, className)}>
79+
<div className="page-header__leading">
80+
{onBack ? (
81+
<button
82+
type="button"
83+
className="page-header__back"
84+
onClick={onBack}
85+
aria-label={backAriaLabel ?? backLabel}
86+
>
87+
<ArrowLeft size={16} />
88+
<span>{backLabel}</span>
89+
</button>
90+
) : null}
91+
<div className="page-header__copy">
92+
{kicker ? <div className="page-header__kicker">{kicker}</div> : null}
93+
<TitleTag className="page-header__title">{title}</TitleTag>
94+
</div>
95+
</div>
96+
{rightSlot ? <div className="page-header__actions">{rightSlot}</div> : null}
97+
</div>
98+
);
99+
}
100+
```
101+
102+
- [ ] **Step 4: Run the test to verify it passes**
103+
104+
Run: `pnpm vitest packages/web/src/features/shared/components/page-header.test.tsx -v`
105+
Expected: PASS.
106+
107+
- [ ] **Step 5: Commit**
108+
109+
```bash
110+
git add packages/web/src/features/shared/components/page-header.tsx packages/web/src/features/shared/components/page-header.test.tsx packages/web/src/features/shared/components/mobile-page-header.tsx packages/web/src/features/shared/components/mobile-page-header.test.tsx packages/web/src/styles/components.css
111+
git commit -m "feat(web): add header levels to shared page header"
112+
```
113+
114+
### Task 2: Add a dedicated PanelHeader primitive and migrate dense panel chrome
115+
116+
**Files:**
117+
- Create: `packages/web/src/features/shared/components/panel-header.tsx`
118+
- Create: `packages/web/src/features/shared/components/panel-header.test.tsx`
119+
- Modify: `packages/web/src/features/agent-panes/views/shared/session-card.tsx`
120+
- Modify: `packages/web/src/features/workspace/views/shared/git-diff-viewer.tsx`
121+
- Modify: `packages/web/src/features/workspace/views/desktop/workspace-desktop-view.tsx`
122+
- Modify: `packages/web/src/styles/components.css`
123+
124+
- [ ] **Step 1: Write the failing test**
125+
126+
```tsx
127+
import { render, screen, within } from "@testing-library/react";
128+
import { describe, expect, it, vi } from "vitest";
129+
import { PanelHeader } from "./panel-header";
130+
131+
describe("PanelHeader", () => {
132+
it("renders title, meta, and right-side actions in a dense panel layout", () => {
133+
render(
134+
<PanelHeader
135+
title="Files"
136+
meta="3 items"
137+
actions={<button type="button">New File</button>}
138+
/>
139+
);
140+
141+
expect(screen.getByText("Files")).toBeInTheDocument();
142+
expect(screen.getByText("3 items")).toBeInTheDocument();
143+
expect(within(document.querySelector(".panel-header") as HTMLElement).getByRole("button")).toBeInTheDocument();
144+
});
145+
});
146+
```
147+
148+
- [ ] **Step 2: Run the test to verify it fails**
149+
150+
Run: `pnpm vitest packages/web/src/features/shared/components/panel-header.test.tsx -v`
151+
Expected: FAIL because the component does not exist yet.
152+
153+
- [ ] **Step 3: Write the minimal implementation**
154+
155+
```tsx
156+
export interface PanelHeaderProps {
157+
title: string;
158+
meta?: ReactNode;
159+
status?: ReactNode;
160+
actions?: ReactNode;
161+
className?: string;
162+
}
163+
164+
export function PanelHeader({ title, meta, status, actions, className }: PanelHeaderProps) {
165+
return (
166+
<div className={clsx("panel-header", className)}>
167+
<div className="panel-header__copy">
168+
<div className="panel-header__title">{title}</div>
169+
{meta || status ? <div className="panel-header__meta">{meta ?? status}</div> : null}
170+
</div>
171+
{actions ? <div className="panel-header__actions">{actions}</div> : null}
172+
</div>
173+
);
174+
}
175+
```
176+
177+
- [ ] **Step 4: Run the test to verify it passes**
178+
179+
Run: `pnpm vitest packages/web/src/features/shared/components/panel-header.test.tsx -v`
180+
Expected: PASS.
181+
182+
- [ ] **Step 5: Migrate panel chrome to the new primitive**
183+
184+
Replace the hand-written headers in:
185+
186+
- `packages/web/src/features/agent-panes/views/shared/session-card.tsx`
187+
- `packages/web/src/features/workspace/views/shared/git-diff-viewer.tsx`
188+
- `packages/web/src/features/workspace/views/desktop/workspace-desktop-view.tsx`
189+
190+
Keep the existing surface behavior, but move header layout and sizing into `PanelHeader` and shared CSS classes.
191+
192+
- [ ] **Step 6: Commit**
193+
194+
```bash
195+
git add packages/web/src/features/shared/components/panel-header.tsx packages/web/src/features/shared/components/panel-header.test.tsx packages/web/src/features/agent-panes/views/shared/session-card.tsx packages/web/src/features/workspace/views/shared/git-diff-viewer.tsx packages/web/src/features/workspace/views/desktop/workspace-desktop-view.tsx packages/web/src/styles/components.css
196+
git commit -m "feat(web): introduce shared panel header"
197+
```
198+
199+
### Task 3: Make modal headers the canonical DialogHeader
200+
201+
**Files:**
202+
- Modify: `packages/web/src/components/ui/modal/index.tsx`
203+
- Modify: `packages/web/src/components/ui/modal/index.module.css`
204+
- Modify: `packages/web/src/components/ui/modal/index.test.tsx`
205+
- Modify: `packages/web/src/components/ui/index.ts`
206+
- Modify: `packages/web/src/components/ui/modal/README.md`
207+
- Modify: `packages/web/src/features/supervisor/views/shared/objective-dialog.tsx`
208+
209+
- [ ] **Step 1: Write the failing test**
210+
211+
```tsx
212+
import { render, screen } from "@testing-library/react";
213+
import { describe, expect, it } from "vitest";
214+
import { DialogHeader, ModalTitle } from "."; // new public alias
215+
216+
describe("Modal dialog header", () => {
217+
it("exports a dialog header alias with the same modal chrome", () => {
218+
render(
219+
<DialogHeader>
220+
<ModalTitle>Workspace details</ModalTitle>
221+
</DialogHeader>
222+
);
223+
224+
expect(screen.getByText("Workspace details")).toBeInTheDocument();
225+
expect(document.querySelector(".modal-header")).toBeTruthy();
226+
});
227+
});
228+
```
229+
230+
- [ ] **Step 2: Run the test to verify it fails**
231+
232+
Run: `pnpm vitest packages/web/src/components/ui/modal/index.test.tsx -v`
233+
Expected: FAIL because `DialogHeader` is not exported yet.
234+
235+
- [ ] **Step 3: Write the minimal implementation**
236+
237+
```tsx
238+
export function DialogHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
239+
return <div {...props} className={clsx(styles.header, "modal-header", className)} />;
240+
}
241+
242+
export { DialogHeader as ModalHeader };
243+
```
244+
245+
- [ ] **Step 4: Run the test to verify it passes**
246+
247+
Run: `pnpm vitest packages/web/src/components/ui/modal/index.test.tsx -v`
248+
Expected: PASS.
249+
250+
- [ ] **Step 5: Migrate supervisor dialog to the dialog header contract**
251+
252+
Update `packages/web/src/features/supervisor/views/shared/objective-dialog.tsx` so it uses the canonical dialog header structure for icon, title, description, and close action instead of an ad hoc header block.
253+
254+
- [ ] **Step 6: Update exports and docs**
255+
256+
Expose `DialogHeader` from `packages/web/src/components/ui/index.ts` and describe the preserved compatibility class names in `packages/web/src/components/ui/modal/README.md`.
257+
258+
- [ ] **Step 7: Commit**
259+
260+
```bash
261+
git add packages/web/src/components/ui/modal/index.tsx packages/web/src/components/ui/modal/index.module.css packages/web/src/components/ui/modal/index.test.tsx packages/web/src/components/ui/index.ts packages/web/src/components/ui/modal/README.md packages/web/src/features/supervisor/views/shared/objective-dialog.tsx
262+
git commit -m "feat(web): add dialog header alias"
263+
```
264+
265+
### Task 4: Migrate settings and lock the header contract with coverage
266+
267+
**Files:**
268+
- Modify: `packages/web/src/features/settings/components/settings-page.tsx`
269+
- Modify: `packages/web/src/features/shared/components/page-header.test.tsx`
270+
- Modify: `packages/web/src/features/shared/components/mobile-page-header.test.tsx`
271+
- Modify: `packages/web/src/features/agent-panes/components/session-card.test.tsx`
272+
- Modify: `packages/web/src/components/ui/modal/index.test.tsx`
273+
- Modify: `packages/web/src/styles/components.theme.test.ts`
274+
275+
- [ ] **Step 1: Write the failing test**
276+
277+
```tsx
278+
import { render, screen } from "@testing-library/react";
279+
import { describe, expect, it } from "vitest";
280+
import { SettingsPage } from "./settings-page";
281+
282+
describe("SettingsPage", () => {
283+
it("renders the page header through the shared PageHeader contract on desktop", () => {
284+
render(
285+
<SettingsPage />
286+
);
287+
288+
expect(document.querySelector(".settings-header")).toBeTruthy();
289+
expect(document.querySelector(".page-header")).toBeTruthy();
290+
});
291+
});
292+
```
293+
294+
- [ ] **Step 2: Run the test to verify it fails**
295+
296+
Run: `pnpm vitest packages/web/src/features/settings/components/settings-page.tsx -v`
297+
Expected: FAIL until the desktop settings header is migrated to `PageHeader`.
298+
299+
- [ ] **Step 3: Write the minimal implementation**
300+
301+
```tsx
302+
<header className="settings-header">
303+
<PageHeader
304+
level={isMobile ? "secondary" : "secondary"}
305+
title={headerTitle}
306+
titleAs="h1"
307+
onBack={handleBack}
308+
backLabel={t("action.back")}
309+
/>
310+
</header>
311+
```
312+
313+
- [ ] **Step 4: Update coverage for the contract**
314+
315+
Add or adjust tests so the project now verifies:
316+
317+
- `PageHeader` primary / secondary rendering
318+
- `PanelHeader` dense layout expectations
319+
- `DialogHeader` export / modal compatibility
320+
- mobile page header still suppresses kicker on narrow layouts
321+
322+
- [ ] **Step 5: Run the targeted test set**
323+
324+
Run:
325+
326+
```bash
327+
pnpm vitest packages/web/src/features/shared/components/page-header.test.tsx packages/web/src/features/shared/components/mobile-page-header.test.tsx packages/web/src/features/shared/components/panel-header.test.tsx packages/web/src/components/ui/modal/index.test.tsx packages/web/src/features/settings/components/settings-page.tsx packages/web/src/features/agent-panes/components/session-card.test.tsx -v
328+
```
329+
330+
Expected: PASS.
331+
332+
- [ ] **Step 6: Commit**
333+
334+
```bash
335+
git add packages/web/src/features/settings/components/settings-page.tsx packages/web/src/features/shared/components/page-header.test.tsx packages/web/src/features/shared/components/mobile-page-header.test.tsx packages/web/src/features/agent-panes/components/session-card.test.tsx packages/web/src/components/ui/modal/index.test.tsx packages/web/src/styles/components.theme.test.ts
336+
git commit -m "feat(web): migrate settings header to shared contract"
337+
```
338+
339+
---
340+
341+
**Execution notes**
342+
343+
- Keep all new public header usage inside the shared primitives.
344+
- Do not introduce any new `*-header` business-only component during implementation.
345+
- Prefer compatibility aliases during migration, but remove ad hoc layout logic as each surface is moved.

0 commit comments

Comments
 (0)