23 KiB
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):
@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:
- Line 7:
--color-background: oklch(0.98 0.005 260)→oklch(0.98 0.01 260) - Lines 65-70: raise all six
--color-*-fillchroma values to 0.22+ per CONTEXT.md - Lines 52-57: delete
--color-chart-1through--color-chart-5entirely - Line 72:
--radius: 0.625rem→--radius: 0 - After
@layer baseblock: 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):
<Bar
dataKey="budgeted"
fill="var(--color-budgeted)"
radius={4} /* → radius={0} */
/>
<Bar dataKey="actual" radius={4}> /* → radius={0} */
{data.map((entry, index) => (
<Cell
key={index}
fill={
entry.actual > entry.budgeted
? "var(--color-over-budget)"
: `var(--color-${entry.type}-fill)` /* already correct — no change */
}
/>
))}
</Bar>
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 —actualalready references--color-muted-foreground,budgetedreferences--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):
<Bar
dataKey="budgeted"
fill="var(--color-budgeted)"
radius={[4, 4, 0, 0]} /* → radius={0} */
/>
<Bar dataKey="actual" radius={[4, 4, 0, 0]}> /* → radius={0} */
{data.map((entry, index) => (
<Cell
key={index}
fill={
entry.actual > entry.budgeted
? "var(--color-over-budget)"
: "var(--color-income-fill)" /* already correct — no change */
}
/>
))}
</Bar>
Current chartConfig (lines 26-29):
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):
/* Before */
<span
className="inline-block size-3 shrink-0 rounded-full"
style={{ backgroundColor: `var(--color-${entry.type}-fill)` }}
/>
/* After — remove rounded-full entirely */
<span
className="inline-block size-3 shrink-0"
style={{ backgroundColor: `var(--color-${entry.type}-fill)` }}
/>
Changes to make in this file:
- Line 141: remove
rounded-fullfrom className string. - No
ChartConfigchanges needed —ExpenseDonutChartbuilds its config dynamically from data usingvar(--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):
<div className="flex flex-col gap-6"> /* → 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:
/* Line 35 — chart placeholder */
<Skeleton className="h-[250px] w-full rounded-md" /> /* remove rounded-md */
/* Line 43 — chart placeholder */
<Skeleton className="h-[250px] w-full rounded-md" /> /* remove rounded-md */
/* Line 51 — chart placeholder */
<Skeleton className="h-[250px] w-full rounded-md" /> /* remove rounded-md */
/* Line 59 — collapsible section row */
<div className="flex items-center gap-3 rounded-md border-l-4 ..."> /* remove rounded-md */
/* Lines 63-64 — inline summary badges */
<Skeleton className="h-5 w-24 rounded-full" /> /* remove rounded-full — CRITICAL: rounded-full is 9999px, not from --radius */
<Skeleton className="h-5 w-24 rounded-full" /> /* remove rounded-full */
Also: spacing upgrades:
/* Line 20 — top-level container */
<div className="flex flex-col gap-6"> /* → gap-8 */
/* Line 22 — summary cards grid */
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> /* → gap-6 */
/* Line 29 — chart grid */
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> /* → 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):
/* Before */
<button
className="group flex w-full items-center gap-3 rounded-md border-l-4 bg-card px-4 py-3 text-left hover:bg-muted/40 transition-colors"
style={{ borderLeftColor: categoryColors[type] }}
>
/* After — remove rounded-md */
<button
className="group flex w-full items-center gap-3 border-l-4 bg-card px-4 py-3 text-left hover:bg-muted/40 transition-colors"
style={{ borderLeftColor: categoryColors[type] }}
>
Changes to make in this file:
- Line 73: remove
rounded-mdfrom the button's className.
src/components/dashboard/charts/ChartEmptyState.tsx (component, empty state)
Current rounded-lg (line 12):
/* 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-lgfrom 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:
/* 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 */
<div
className="size-2 rounded-full"
style={{ backgroundColor: categoryColors[type] }}
/>
/* → remove rounded-full */
Changes to make in this file:
- Line 156: remove
rounded-smfrom className. - Line 201: remove
rounded-fullfrom className.
src/pages/DashboardPage.tsx (page, request-response)
Current spacing (lines 186, 207):
/* Line 186 — DashboardContent top-level wrapper */
<div className="space-y-6"> /* → space-y-8 */
/* Line 207 — chart grid */
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> /* → 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):
/* Before */
<div className="flex items-center gap-3 rounded-md border p-3">
/* After */
<div className="flex items-center gap-3 border p-3">
Spacing: Check for gap-4, gap-6, space-y-* patterns and apply standard upgrades:
gap-4→gap-6gap-6→gap-8space-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 <Skeleton> 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):
/* Before */
<div
className="mb-2 flex items-center gap-3 rounded-sm border-l-4 bg-muted/30 px-3 py-2"
style={{ borderLeftColor: categoryColors[type] }}
>
/* After */
<div
className="mb-2 flex items-center gap-3 border-l-4 bg-muted/30 px-3 py-2"
style={{ borderLeftColor: categoryColors[type] }}
>
Line 439 summary box (current pattern to modify):
/* Before */
<div className="rounded-md border p-4">
/* After */
<div className="border p-4">
Spacing upgrades:
space-y-8already present at line 338 (inner grouped content) — verify nospace-y-6/gap-6remaining- Check for
p-4on 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 <Skeleton> className |
Remove |
| 258 | rounded-md on <Skeleton> 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):
/* Before */
<div
className="mb-2 flex items-center gap-3 rounded-sm border-l-4 bg-muted/30 px-3 py-2"
style={{ borderLeftColor: categoryColors[type] }}
>
Same removal pattern as BudgetDetailPage.tsx line 353.
Line 385 SelectLabel dot (current):
/* Before — same pattern as BudgetDetailPage line 497 */
<div
className="size-2 rounded-full"
style={{ backgroundColor: categoryColors[type] }}
/>
/* 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 <Skeleton> className |
Remove |
| 108 | rounded-md on <Skeleton> className |
Remove |
| 134 | rounded-sm on category group heading div |
Remove |
Line 134 group heading (current):
/* Before */
<div
className="mb-2 flex items-center gap-3 rounded-sm border-l-4 bg-muted/30 px-3 py-2"
style={{ borderLeftColor: categoryColors[type] }}
>
/* 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 <Skeleton> className |
Remove |
| 100 | rounded-md on <Skeleton> className |
Remove |
Current skeleton block (lines 93-105):
<div className="space-y-1">
{[1, 2, 3, 4, 5].map((i) => (
<div key={i} className="flex items-center gap-4 px-4 py-2.5 border-b border-border">
<Skeleton className="h-5 w-10 rounded-full" /> /* line 98 — remove rounded-full */
<Skeleton className="h-4 w-36" />
<Skeleton className="ml-auto h-7 w-7 rounded-md" /> /* line 100 — remove rounded-md */
</div>
))}
</div>
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):
/* Before — CardContent internal spacing */
<CardContent className="space-y-4 pt-6"> /* → space-y-6 */
Changes to make in this file:
- Line 87 (and corresponding skeleton line 69):
space-y-4→space-y-6insideCardContent
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.
/* 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.
/* Before — still circular after --radius: 0 */
<Skeleton className="h-5 w-16 rounded-full" />
<span className="inline-block size-3 shrink-0 rounded-full" ... />
<div className="size-2 rounded-full" ... />
/* After — becomes square from --radius: 0 */
<Skeleton className="h-5 w-16" />
<span className="inline-block size-3 shrink-0" ... />
<div className="size-2" ... />
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 <rect rx="4" ry="4"> 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.
/* 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
/* Pattern: border-l-4 accent bar heading — remove rounded-sm entirely */
<div
className="mb-2 flex items-center gap-3 border-l-4 bg-muted/30 px-3 py-2"
style={{ borderLeftColor: categoryColors[type] }}
>
<span className="text-sm font-semibold">{label}</span>
</div>
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.
/* 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-*-fillchroma values to 0.22+ - Warm
--color-backgroundchroma 0.005 → 0.01 - Delete
--color-chart-1through--color-chart-5 - Add
.recharts-rectangleand[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: removerounded-fullfrom legend span (line 141)
Wave 3 — Page and shared component sweep (12 files)
PageShell.tsx:gap-6→gap-8DashboardSkeleton.tsx: remove 5rounded-*classes, upgrade 3 spacing valuesCategorySection.tsx: removerounded-mdfrom trigger buttonChartEmptyState.tsx: removerounded-lgfrom empty state divQuickAddPicker.tsx: remove 2rounded-*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