# Phase 5: Design System Token Rework - Pattern Map
**Mapped:** 2026-04-20
**Files analyzed:** 14 files (1 CSS + 2 chart components + 1 shared component + 9 pages + 1 component)
**Analogs found:** 14 / 14 (all files are modifications of existing code — no new files created)
---
## File Classification
| Modified File | Role | Data Flow | Closest Analog / Self | Match Quality |
|---|---|---|---|---|
| `src/index.css` | config (CSS tokens) | transform | self — no analog needed | self |
| `src/components/dashboard/charts/SpendBarChart.tsx` | component (chart) | transform | `src/components/dashboard/charts/IncomeBarChart.tsx` | exact |
| `src/components/dashboard/charts/IncomeBarChart.tsx` | component (chart) | transform | `src/components/dashboard/charts/SpendBarChart.tsx` | exact |
| `src/components/dashboard/charts/ExpenseDonutChart.tsx` | component (chart) | transform | `SpendBarChart.tsx` / `IncomeBarChart.tsx` | role-match |
| `src/components/shared/PageShell.tsx` | component (layout) | request-response | self | self |
| `src/components/dashboard/DashboardSkeleton.tsx` | component (skeleton) | request-response | `CategoriesPage.tsx` skeleton block | role-match |
| `src/components/dashboard/CategorySection.tsx` | component (list item) | request-response | `BudgetDetailPage.tsx` group heading | role-match |
| `src/components/dashboard/charts/ChartEmptyState.tsx` | component (empty state) | request-response | self | self |
| `src/components/QuickAddPicker.tsx` | component (picker) | request-response | `BudgetDetailPage.tsx` select label dot | role-match |
| `src/pages/DashboardPage.tsx` | page | request-response | self | self |
| `src/pages/BudgetListPage.tsx` | page | request-response | `DashboardPage.tsx` / `BudgetDetailPage.tsx` | role-match |
| `src/pages/BudgetDetailPage.tsx` | page | CRUD | `CategoriesPage.tsx` / `TemplatePage.tsx` | exact |
| `src/pages/TemplatePage.tsx` | page | CRUD | `BudgetDetailPage.tsx` / `CategoriesPage.tsx` | exact |
| `src/pages/CategoriesPage.tsx` | page | CRUD | `BudgetDetailPage.tsx` / `TemplatePage.tsx` | exact |
| `src/pages/QuickAddPage.tsx` | page | CRUD | `CategoriesPage.tsx` | role-match |
| `src/pages/SettingsPage.tsx` | page | request-response | `CategoriesPage.tsx` | role-match |
| `src/pages/LoginPage.tsx` | page | request-response | `SettingsPage.tsx` | role-match |
| `src/pages/RegisterPage.tsx` | page | request-response | `SettingsPage.tsx` | role-match |
---
## Pattern Assignments
### `src/index.css` (config, CSS tokens)
**Analog:** self — authoritative token source, no external analog needed.
**Current `@theme inline` block** (lines 1-99, full file):
```css
@import "tailwindcss";
@custom-variant dark (&:is(.dark *));
@theme inline {
--color-background: oklch(0.98 0.005 260); /* → 0.01 chroma (warm) */
--color-foreground: oklch(0.25 0.02 260);
/* ... (base tokens unchanged) ... */
/* Category fill tokens — RAISE chroma to 0.22+ */
--color-income-fill: oklch(0.68 0.19 155); /* → 0.22 */
--color-bill-fill: oklch(0.65 0.19 25); /* → 0.22 */
--color-variable-expense-fill: oklch(0.70 0.18 50); /* → 0.22 */
--color-debt-fill: oklch(0.60 0.20 355); /* → 0.23 */
--color-saving-fill: oklch(0.68 0.18 220); /* → 0.22 */
--color-investment-fill: oklch(0.65 0.18 285); /* → 0.22 */
/* Chart vars — DELETE these 5 lines entirely */
--color-chart-1: oklch(0.72 0.14 155);
--color-chart-2: oklch(0.7 0.14 25);
--color-chart-3: oklch(0.72 0.14 50);
--color-chart-4: oklch(0.65 0.16 355);
--color-chart-5: oklch(0.72 0.14 220);
--radius: 0.625rem; /* → 0 */
}
@layer base {
* { @apply border-border; }
body {
@apply bg-background text-foreground;
font-feature-settings: "rlig" 1, "calt" 1;
}
}
```
**Changes to make in this file:**
1. Line 7: `--color-background: oklch(0.98 0.005 260)` → `oklch(0.98 0.01 260)`
2. Lines 65-70: raise all six `--color-*-fill` chroma values to 0.22+ per CONTEXT.md
3. Lines 52-57: delete `--color-chart-1` through `--color-chart-5` entirely
4. Line 72: `--radius: 0.625rem` → `--radius: 0`
5. After `@layer base` block: add CSS override selectors for Recharts and Sonner (see Shared Patterns section below)
---
### `src/components/dashboard/charts/SpendBarChart.tsx` (component, transform)
**Analog:** `src/components/dashboard/charts/IncomeBarChart.tsx`
**Current `Bar` radius props** (lines 64-80):
```tsx
/* → radius={0} */
{data.map((entry, index) => (
| entry.budgeted
? "var(--color-over-budget)"
: `var(--color-${entry.type}-fill)` /* already correct — no change */
}
/>
))}
|
```
**Changes to make in this file:**
- Line 67: `radius={4}` → `radius={0}`
- Line 69: `radius={4}` → `radius={0}`
- `chartConfig` (lines 31-34): no change needed — `actual` already references `--color-muted-foreground`, `budgeted` references `--color-budget-bar-bg`; neither references deleted `--color-chart-*` vars.
---
### `src/components/dashboard/charts/IncomeBarChart.tsx` (component, transform)
**Analog:** `src/components/dashboard/charts/SpendBarChart.tsx`
**Current `Bar` radius props** (lines 54-69):
```tsx
/* → radius={0} */
{data.map((entry, index) => (
| entry.budgeted
? "var(--color-over-budget)"
: "var(--color-income-fill)" /* already correct — no change */
}
/>
))}
|
```
**Current `chartConfig`** (lines 26-29):
```tsx
const chartConfig = {
budgeted: { label: "Budgeted", color: "var(--color-budget-bar-bg)" },
actual: { label: "Actual", color: "var(--color-income-fill)" }, /* already references fill token */
} satisfies ChartConfig
```
**Changes to make in this file:**
- Line 57: `radius={[4, 4, 0, 0]}` → `radius={0}`
- Line 59: `radius={[4, 4, 0, 0]}` → `radius={0}`
- `chartConfig`: no change needed — already references `--color-income-fill`, not a deleted `--color-chart-*` var.
---
### `src/components/dashboard/charts/ExpenseDonutChart.tsx` (component, transform)
**Analog:** self — already uses `var(--color-${entry.type}-fill)` pattern throughout.
**The one hardcoded `rounded-full`** (line 141):
```tsx
/* Before */
/* After — remove rounded-full entirely */
```
**Changes to make in this file:**
- Line 141: remove `rounded-full` from className string.
- No `ChartConfig` changes needed — `ExpenseDonutChart` builds its config dynamically from data using `var(--color-${entry.type}-fill)` (lines 47-51), which is already the correct post-rework pattern.
---
### `src/components/shared/PageShell.tsx` (component, layout)
**Current spacing** (line 15):
```tsx
/* → gap-8 */
```
**Changes to make in this file:**
- Line 15: `gap-6` → `gap-8`
This single change propagates the header-to-content gap increase to all 9 pages that use `PageShell`.
---
### `src/components/dashboard/DashboardSkeleton.tsx` (component, skeleton)
**Analog:** Skeleton patterns from `CategoriesPage.tsx` lines 98-114 and `BudgetDetailPage.tsx` lines 284-306.
**Hardcoded `rounded-*` locations in this file:**
```tsx
/* Line 35 — chart placeholder */
/* remove rounded-md */
/* Line 43 — chart placeholder */
/* remove rounded-md */
/* Line 51 — chart placeholder */
/* remove rounded-md */
/* Line 59 — collapsible section row */
/* remove rounded-md */
/* Lines 63-64 — inline summary badges */
/* remove rounded-full — CRITICAL: rounded-full is 9999px, not from --radius */
/* remove rounded-full */
```
**Also: spacing upgrades**:
```tsx
/* Line 20 — top-level container */
/* → gap-8 */
/* Line 22 — summary cards grid */
/* → gap-6 */
/* Line 29 — chart grid */
/* → gap-8 */
```
---
### `src/components/dashboard/CategorySection.tsx` (component, list item)
**Analog:** Group heading divs in `BudgetDetailPage.tsx` lines 352-356, `CategoriesPage.tsx` lines 134-137, `TemplatePage.tsx` lines 292-295.
**The hardcoded `rounded-md`** (line 73):
```tsx
/* Before */
/* After — remove rounded-md */
```
**Changes to make in this file:**
- Line 73: remove `rounded-md` from the button's className.
---
### `src/components/dashboard/charts/ChartEmptyState.tsx` (component, empty state)
**Current `rounded-lg`** (line 12):
```tsx
/* Before */
className={cn(
"flex min-h-[250px] w-full items-center justify-center rounded-lg border border-dashed ...",
className
)}
/* After — remove rounded-lg */
className={cn(
"flex min-h-[250px] w-full items-center justify-center border border-dashed ...",
className
)}
```
**Changes to make in this file:**
- Line 12: remove `rounded-lg` from the className string.
---
### `src/components/QuickAddPicker.tsx` (component, picker)
**Analog:** Same pattern as `BudgetDetailPage.tsx` line 496 and `TemplatePage.tsx` line 385 — `size-2 rounded-full` color swatch dots used as SelectLabel decorators.
**Hardcoded `rounded-*` locations:**
```tsx
/* Line 156 — picker item button */
className="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 ..."
/* → remove rounded-sm */
/* Line 201 — SelectLabel category dot */
/* → remove rounded-full */
```
**Changes to make in this file:**
- Line 156: remove `rounded-sm` from className.
- Line 201: remove `rounded-full` from className.
---
### `src/pages/DashboardPage.tsx` (page, request-response)
**Current spacing** (lines 186, 207):
```tsx
/* Line 186 — DashboardContent top-level wrapper */
/* → space-y-8 */
/* Line 207 — chart grid */
/* → gap-8 */
```
**Changes to make in this file:**
- Line 186: `space-y-6` → `space-y-8`
- Line 207: `gap-6` → `gap-8`
No hardcoded `rounded-*` classes in this file.
---
### `src/pages/BudgetListPage.tsx` (page, CRUD)
**Analog:** `BudgetDetailPage.tsx` lines 242-256 — same `rounded-md border p-3` container pattern.
**Hardcoded `rounded-md`** (line 243):
```tsx
/* Before */
/* After */
```
**Spacing:** Check for `gap-4`, `gap-6`, `space-y-*` patterns and apply standard upgrades:
- `gap-4` → `gap-6`
- `gap-6` → `gap-8`
- `space-y-6` → `space-y-8`
---
### `src/pages/BudgetDetailPage.tsx` (page, CRUD)
**Analog:** `CategoriesPage.tsx` and `TemplatePage.tsx` — structurally identical page pattern (group headings, Tables, PageShell).
**Hardcoded `rounded-*` locations** (confirmed from RESEARCH.md inventory):
| Line | Current | Action |
|------|---------|--------|
| 290 | `rounded-sm` on skeleton header div | Remove |
| 303 | `rounded-md` on `
` className | Remove |
| 353 | `rounded-sm` on budget item group heading div | Remove |
| 439 | `rounded-md` on summary totals box | Remove |
| 497 | `rounded-full` on SelectLabel category dot | Remove |
**Line 353 group heading** (current pattern to modify):
```tsx
/* Before */
/* After */
```
**Line 439 summary box** (current pattern to modify):
```tsx
/* Before */
/* After */
```
**Spacing upgrades:**
- `space-y-8` already present at line 338 (inner grouped content) — verify no `space-y-6` / `gap-6` remaining
- Check for `p-4` on card content wrappers → `p-6`
---
### `src/pages/TemplatePage.tsx` (page, CRUD)
**Analog:** `BudgetDetailPage.tsx` — same group heading + Table structure, same skeleton pattern.
**Hardcoded `rounded-*` locations** (confirmed from RESEARCH.md inventory):
| Line | Current | Action |
|------|---------|--------|
| 250 | `rounded-sm` on skeleton group heading div | Remove |
| 256 | `rounded-full` on `
` className | Remove |
| 258 | `rounded-md` on `` className | Remove |
| 292 | `rounded-sm` on template item group heading div | Remove |
| 385 | `rounded-full` on SelectLabel category dot | Remove |
**Line 292 group heading** (current):
```tsx
/* Before */
```
Same removal pattern as `BudgetDetailPage.tsx` line 353.
**Line 385 SelectLabel dot** (current):
```tsx
/* Before — same pattern as BudgetDetailPage line 497 */
/* After: remove rounded-full */
```
**Spacing upgrades** (current at lines 247, 268, 287):
- Line 247: `space-y-6` → `space-y-8` (skeleton wrapper)
- Line 268: `gap-6` → `gap-8` (TemplatePage own flex header, not using PageShell)
- Line 287: `space-y-6` → `space-y-8` (main content groups)
---
### `src/pages/CategoriesPage.tsx` (page, CRUD)
**Analog:** `BudgetDetailPage.tsx` / `TemplatePage.tsx` — structurally identical.
**Hardcoded `rounded-*` locations** (confirmed from RESEARCH.md inventory):
| Line | Current | Action |
|------|---------|--------|
| 101 | `rounded-sm` on skeleton group heading div | Remove |
| 107 | `rounded-full` on `
` className | Remove |
| 108 | `rounded-md` on `` className | Remove |
| 134 | `rounded-sm` on category group heading div | Remove |
**Line 134 group heading** (current):
```tsx
/* Before */
/* After: remove rounded-sm */
```
**Spacing upgrades** (current at lines 98, 130):
- Line 98: `space-y-6` → `space-y-8` (skeleton wrapper)
- Line 130: `space-y-6` → `space-y-8` (main content groups)
---
### `src/pages/QuickAddPage.tsx` (page, CRUD)
**Analog:** `CategoriesPage.tsx` — same row-list layout, same Skeleton usage.
**Hardcoded `rounded-*` locations** (confirmed from RESEARCH.md inventory):
| Line | Current | Action |
|------|---------|--------|
| 98 | `rounded-full` on `
` className | Remove |
| 100 | `rounded-md` on `` className | Remove |
**Current skeleton block** (lines 93-105):
```tsx
{[1, 2, 3, 4, 5].map((i) => (
/* line 98 — remove rounded-full */
/* line 100 — remove rounded-md */
))}
```
No major spacing changes in this file (list layout, not grid/section-based).
---
### `src/pages/SettingsPage.tsx` (page, request-response)
**Analog:** `CategoriesPage.tsx` — PageShell wrapper, Card with CardContent.
**No hardcoded `rounded-*` classes** in this file (Card uses token cascade).
**Spacing upgrade** (line 87):
```tsx
/* Before — CardContent internal spacing */
/* → space-y-6 */
```
**Changes to make in this file:**
- Line 87 (and corresponding skeleton line 69): `space-y-4` → `space-y-6` inside `CardContent`
---
### `src/pages/LoginPage.tsx` and `src/pages/RegisterPage.tsx` (pages, request-response)
**Analog:** `SettingsPage.tsx` — Card-based page, no group headings.
Per RESEARCH.md: "Card already uses `--radius` token cascade." No hardcoded `rounded-*` classes confirmed. No `--color-chart-*` references.
**Minimal changes:** Verify `p-4` → `p-6` in any card content wrappers if present. No spacing grids to upgrade. Treat as low-touch pages in implementation.
---
## Shared Patterns
### Pattern A: `@theme inline` Token Edit (CSS variable cascade)
**Source:** `src/index.css` lines 4-79
**Apply to:** `src/index.css` only — single source of truth
**Rule:** All shadcn `rounded-*` utility classes (rounded-md, rounded-sm, rounded-lg, rounded-xl) derive from `--radius`. Setting `--radius: 0` makes them all 0px automatically. Only `rounded-full` (9999px) is immune.
```css
/* The pattern: edit token value, cascade propagates everywhere */
@theme inline {
--radius: 0; /* single edit cascades to all shadcn components */
}
```
### Pattern B: Hardcoded `rounded-full` Removal (critical pitfall)
**Source:** Every page and component file listed in RESEARCH.md inventory
**Apply to:** All 17 `rounded-full` class occurrences across 8 files
**Rule:** `rounded-full` = `border-radius: 9999px` — hardcoded, NOT derived from `--radius`. Must be removed manually from every occurrence.
```tsx
/* Before — still circular after --radius: 0 */
/* After — becomes square from --radius: 0 */
```
### Pattern C: CSS Override for Third-Party Radius
**Source:** To be added to `src/index.css` after `@layer base` block
**Apply to:** Recharts bars (`SpendBarChart`, `IncomeBarChart`) and Sonner toasts
**Rule:** Recharts renders bars as `` SVG — not CSS. Must change `radius` prop directly AND add CSS override. Sonner wires `--border-radius: var(--radius)` (confirmed at `sonner.tsx` line 28) — should auto-propagate, but add CSS override as safety net.
```css
/* Add to src/index.css after the @layer base block */
/* Recharts: SVG rect elements for bar charts */
.recharts-rectangle {
rx: 0;
ry: 0;
}
/* Sonner: toast container — safety net if var(--radius) cascade insufficient */
[data-sonner-toast] {
border-radius: 0 !important;
}
```
**Note:** Sonner's `sonner.tsx` already passes `"--border-radius": "var(--radius)"` as an inline style (line 28). When `--radius: 0`, this should cascade automatically. The CSS override is a safety net only — verify in browser after token change before deciding if it is needed.
### Pattern D: Group Heading Div (shared across 5 files)
**Source:** `src/pages/CategoriesPage.tsx` line 134, `BudgetDetailPage.tsx` line 353, `TemplatePage.tsx` line 292 — all structurally identical
**Apply to:** CategoriesPage, BudgetDetailPage, TemplatePage, CategorySection, DashboardSkeleton row
```tsx
/* Pattern: border-l-4 accent bar heading — remove rounded-sm entirely */
{label}
```
### Pattern E: Spacing Upgrade Map
**Source:** `src/pages/DashboardPage.tsx` lines 186, 207; `src/components/shared/PageShell.tsx` line 15
**Apply to:** All 9 pages and PageShell
```
PageShell.tsx line 15: gap-6 → gap-8 (header-to-content gap)
DashboardPage.tsx line 186: space-y-6 → space-y-8 (section rhythm)
DashboardPage.tsx line 207: gap-6 → gap-8 (chart grid)
DashboardSkeleton.tsx line 22: gap-4 → gap-6 (summary cards grid)
DashboardSkeleton.tsx line 29: gap-6 → gap-8 (chart grid skeleton)
CategoriesPage.tsx line 98: space-y-6 → space-y-8 (skeleton wrapper)
CategoriesPage.tsx line 130: space-y-6 → space-y-8 (main content)
TemplatePage.tsx line 247: space-y-6 → space-y-8 (skeleton wrapper)
TemplatePage.tsx line 268: gap-6 → gap-8 (self-managed header flex)
TemplatePage.tsx line 287: space-y-6 → space-y-8 (main content)
BudgetDetailPage.tsx: verify no space-y-6/gap-6 remain after existing space-y-8 at line 338
SettingsPage.tsx line 87: space-y-4 → space-y-6 (CardContent internal)
```
### Pattern F: `ChartConfig` Token Reference (no `--color-chart-*`)
**Source:** `src/components/dashboard/charts/IncomeBarChart.tsx` lines 26-29
**Apply to:** Any future chart components — `SpendBarChart` and `IncomeBarChart` require no ChartConfig change
**Rule:** After deleting `--color-chart-*` from `index.css`, all chart color references must use `--color-*-fill` tokens.
```tsx
/* Correct pattern post-rework — reference fill tokens directly */
const chartConfig = {
budgeted: { label: "Budgeted", color: "var(--color-budget-bar-bg)" },
actual: { label: "Actual", color: "var(--color-income-fill)" },
} satisfies ChartConfig
```
---
## No Analog Found
None — all files are modifications of existing code. No net-new files are created in this phase.
---
## Implementation Wave Order
Per RESEARCH.md recommendation (planner reference):
**Wave 1 — Token edit only** (`src/index.css`)
- Change `--radius: 0`
- Raise `--color-*-fill` chroma values to 0.22+
- Warm `--color-background` chroma 0.005 → 0.01
- Delete `--color-chart-1` through `--color-chart-5`
- Add `.recharts-rectangle` and `[data-sonner-toast]` CSS overrides
**Wave 2 — Chart component updates** (2 files)
- `SpendBarChart.tsx`: `radius={4}` → `radius={0}` (x2 Bar props)
- `IncomeBarChart.tsx`: `radius={[4, 4, 0, 0]}` → `radius={0}` (x2 Bar props)
- `ExpenseDonutChart.tsx`: remove `rounded-full` from legend span (line 141)
**Wave 3 — Page and shared component sweep** (12 files)
- `PageShell.tsx`: `gap-6` → `gap-8`
- `DashboardSkeleton.tsx`: remove 5 `rounded-*` classes, upgrade 3 spacing values
- `CategorySection.tsx`: remove `rounded-md` from trigger button
- `ChartEmptyState.tsx`: remove `rounded-lg` from empty state div
- `QuickAddPicker.tsx`: remove 2 `rounded-*` classes
- All 9 pages: spacing upgrades + hardcoded `rounded-*` removals per inventory
---
## Metadata
**Analog search scope:** `src/pages/`, `src/components/dashboard/`, `src/components/shared/`, `src/components/ui/`, `src/index.css`
**Files read for pattern extraction:** 16 (index.css, PageShell, DashboardPage, DashboardSkeleton, SpendBarChart, IncomeBarChart, ExpenseDonutChart, ChartEmptyState, CategorySection, QuickAddPicker, SettingsPage, BudgetDetailPage excerpt x3, CategoriesPage excerpt, TemplatePage excerpt, QuickAddPage excerpt, BudgetListPage excerpt, sonner.tsx)
**Pattern extraction date:** 2026-04-20