Files
SimpleFinanceDash/.planning/phases/05-design-system-token-rework/05-RESEARCH.md
2026-04-20 16:44:11 +02:00

551 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 5: Design System Token Rework - Research
**Researched:** 2026-04-20
**Domain:** CSS design tokens, Tailwind CSS 4 `@theme inline`, OKLCH color, shadcn/ui, Recharts, Sonner
**Confidence:** HIGH
---
<user_constraints>
## User Constraints (from CONTEXT.md)
### Locked Decisions
- All elements get 0px border radius — truly sharp corners everywhere
- Implementation via single `--radius: 0` token change in `src/index.css` (cascades to all shadcn components)
- No exceptions for avatars, badges, or any element
- Third-party components (Recharts bars, Sonner toasts) overridden via CSS selectors to force 0 radius
- Increase chroma on category fill colors to 0.22+ for visible colorful pop against white backgrounds
- Keep the two-tier color system: text colors at ~0.55L for WCAG 4.5:1 contrast, fill colors lighter/more saturated
- Slightly warm the base UI colors: background chroma from 0.005→0.01 for subtle warmth
- Align chart colors with category fill tokens directly — remove separate `--color-chart-*` variables and use `--color-*-fill` tokens in charts
- Increase section gaps: gap-4→gap-6, gap-6→gap-8 across all pages
- Increase card internal padding: p-4→p-6 for breathing room
- Keep existing max-w-7xl container constraint
- Standardize all page headers to mb-6 + section gaps to gap-8 for consistent rhythm
### Claude's Discretion
- Exact OKLCH chroma/lightness values for category fills (as long as they're 0.22+ chroma and pass WCAG)
- Order of page updates during implementation
- Whether to create a spacing utility class or apply changes inline
### Deferred Ideas (OUT OF SCOPE)
None — discussion stayed within phase scope
</user_constraints>
---
<phase_requirements>
## Phase Requirements
| ID | Description | Research Support |
|----|-------------|------------------|
| DS-01 | User sees sharp-edged UI across all pages (no rounded corners) | Single `--radius: 0` token in index.css cascades to all shadcn/ui components; hardcoded `rounded-*` Tailwind classes in pages/components require explicit removal; Recharts bars and Sonner toasts require CSS override selectors |
| DS-02 | User sees clear pastel colors that are visibly colorful, not washed out | Raise `--color-*-fill` chroma from 0.18-0.20 to 0.22+; remove `--color-chart-*` variables; point chart components to `--color-*-fill` tokens directly |
| DS-03 | User sees a clean, minimal layout with generous whitespace | Upgrade `space-y-6``space-y-8`, `gap-6``gap-8` across 9 pages; `p-4``p-6` for card internals; standardize PageShell gap to `gap-8` |
</phase_requirements>
---
## Summary
Phase 5 is a pure design token and spacing rework with zero feature additions. The codebase uses Tailwind CSS 4's `@theme inline` block in `src/index.css` as the single source of truth for all CSS custom properties. Because shadcn/ui components reference `--radius` via Tailwind's `rounded-*` utility classes that are generated from this token, setting `--radius: 0` in `src/index.css` propagates automatically to every shadcn component. No changes to `src/components/ui/` files are needed for the radius.
However, hardcoded `rounded-*` Tailwind classes exist in 6 page files and 4 component files — these are not driven by `--radius` and must be removed or changed to `rounded-none` explicitly. Additionally, Recharts bar components pass a `radius` prop (not CSS) and require a CSS selector override in `index.css`. Sonner toasts pass `--border-radius` as an inline style through the Toaster component and additionally need a CSS override.
The color work has two parts: (1) editing token values in `src/index.css` only, and (2) removing `--color-chart-*` variables from `index.css` and updating the two chart components that currently reference them via `ChartConfig` objects to instead reference `--color-*-fill` tokens.
**Primary recommendation:** Execute as three focused waves — (1) token edit in `index.css` (radius + colors + remove chart vars), (2) chart component updates to reference fill tokens, (3) per-page spacing and hardcoded `rounded-*` sweep across all 9 pages.
---
## Architectural Responsibility Map
| Capability | Primary Tier | Secondary Tier | Rationale |
|------------|-------------|----------------|-----------|
| Design token definition | CSS (index.css) | — | Single `@theme inline` block is the authoritative token source |
| shadcn component rounding | CSS token cascade | — | `rounded-*` utilities derive from `--radius`; no component code change needed |
| Hardcoded Tailwind class removal | Page/component TSX | — | `rounded-full`, `rounded-md` etc. are inline JSX; must be changed in source files |
| Recharts bar radius override | CSS global selector | Recharts prop | CSS `rect` selector overrides SVG radius attribute; prop change is alternative |
| Sonner toast radius override | CSS global selector | Sonner component | Inline `--border-radius` style in sonner.tsx; CSS override wins |
| Color token values | CSS (index.css) | — | All category and fill colors live in `@theme inline` |
| Chart color wiring | Chart TSX components | CSS tokens | `ChartConfig` objects in SpendBarChart and IncomeBarChart must reference fill tokens |
| Page spacing | Page TSX files | Shared components | `gap-*`, `space-y-*`, `p-*` are inline classes in page JSX |
---
## Standard Stack
### Core
| Library | Version | Purpose | Why Standard |
|---------|---------|---------|--------------|
| Tailwind CSS | 4.2.1 | Utility CSS + token system (`@theme inline`) | Already installed; `@theme inline` is the v4 way to define CSS variables as Tailwind utilities |
| shadcn/ui | new-york preset | Component layer above Radix UI | Already installed; all components in `src/components/ui/` |
| Recharts | 2.15.4 | Chart rendering (BarChart, PieChart) | Already installed; SpendBarChart, IncomeBarChart, ExpenseDonutChart use it |
| Sonner | 2.0.7 | Toast notifications | Already installed; `src/components/ui/sonner.tsx` wraps it |
### Supporting
| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| OKLCH (native CSS) | CSS Color Level 4 | Perceptually uniform color space | Already in use; all token edits stay in OKLCH |
No new libraries are needed for this phase.
**Installation:** None required.
---
## Architecture Patterns
### System Architecture Diagram
```
src/index.css (@theme inline)
├── --radius: 0 ──cascade──► all shadcn rounded-* classes
├── --color-background: ... ──cascade──► bg-background utility
├── --color-*-fill: ... ──cascade──► var(--color-*-fill) in chart components
└── [--color-chart-* REMOVED]
src/components/ui/*.tsx (shadcn)
│ Button, Card, Input, Badge, Select, Dialog, Sheet, Sidebar, ...
│ All use rounded-* utilities → auto-sharp from --radius: 0
src/components/dashboard/charts/*.tsx
│ SpendBarChart, IncomeBarChart: update ChartConfig + Bar radius prop → 0
│ ExpenseDonutChart: already uses var(--color-*-fill); no ChartConfig change needed
src/pages/*.tsx (9 pages)
│ Remove hardcoded rounded-* classes
│ Upgrade spacing: space-y-6→space-y-8, gap-6→gap-8, p-4→p-6
src/index.css (@layer base or global)
└── CSS overrides for third-party radius
├── .recharts-rectangle { rx: 0; ry: 0; border-radius: 0 }
└── [data-sonner-toast] { border-radius: 0 }
```
### Recommended Project Structure
No structural changes. Files modified:
```
src/
├── index.css # Token edits: --radius, --color-*-fill, remove --color-chart-*; CSS overrides
├── components/
│ └── dashboard/
│ └── charts/
│ ├── SpendBarChart.tsx # Update ChartConfig + Bar radius prop
│ └── IncomeBarChart.tsx # Update ChartConfig + Bar radius prop
└── pages/
├── DashboardPage.tsx # Spacing: space-y-6→space-y-8, gap-6→gap-8
├── BudgetListPage.tsx # Spacing + remove rounded-md from row div
├── BudgetDetailPage.tsx # Spacing + remove rounded-* classes
├── TemplatePage.tsx # Spacing + remove rounded-sm, rounded-full
├── CategoriesPage.tsx # Spacing + remove rounded-sm, rounded-full
├── QuickAddPage.tsx # Remove rounded-full, rounded-md
├── SettingsPage.tsx # Spacing: space-y-4→space-y-6 in card content
├── LoginPage.tsx # Card already uses --radius token cascade
└── RegisterPage.tsx # Card already uses --radius token cascade
```
### Pattern 1: Tailwind 4 `@theme inline` Token Edit
**What:** All CSS variables in the `@theme inline` block are automatically available as Tailwind utility classes. Changing a value in this block propagates everywhere the utility is used.
**When to use:** Changing `--radius`, color tokens, any design-system-level property.
**Example:**
```css
/* src/index.css */
@theme inline {
--radius: 0; /* was 0.625rem — single change, cascades everywhere */
/* Fill colors: raise chroma to 0.22+ */
--color-income-fill: oklch(0.72 0.22 155);
--color-bill-fill: oklch(0.70 0.22 25);
--color-variable-expense-fill: oklch(0.74 0.22 50);
--color-debt-fill: oklch(0.66 0.23 355);
--color-saving-fill: oklch(0.72 0.22 220);
--color-investment-fill: oklch(0.68 0.22 285);
/* Background: warm slightly */
--color-background: oklch(0.98 0.01 260);
/* Remove --color-chart-1 through --color-chart-5 entirely */
}
```
[VERIFIED: codebase — src/index.css lines 1-99]
### Pattern 2: CSS Selector Override for Third-Party Radius
**What:** When a third-party library applies border-radius via its own SVG attributes or inline styles, a targeted CSS selector override in `@layer base` in `index.css` forces the desired value.
**When to use:** Recharts bar SVG rectangles, Sonner toast container.
**Example:**
```css
/* src/index.css — in @layer base block or after @theme */
/* Recharts: bars are SVG <rect> elements; CSS border-radius overrides SVG rx/ry */
.recharts-rectangle {
rx: 0;
ry: 0;
}
/* Sonner: targets the toast wrapper element */
[data-sonner-toast] {
border-radius: 0 !important;
}
```
[ASSUMED — selector names from Recharts/Sonner DOM inspection convention; verify in browser devtools during implementation]
### Pattern 3: Hardcoded Tailwind Class Removal
**What:** `rounded-full`, `rounded-md`, `rounded-sm`, `rounded-lg` as inline className strings in TSX are not driven by `--radius`. They must be removed or replaced with `rounded-none`.
**When to use:** Category color swatches, skeleton shapes, chart legend dots, picker items.
**Example:**
```tsx
/* Before — in ExpenseDonutChart.tsx line 141 */
<span className="inline-block size-3 shrink-0 rounded-full" ... />
/* After */
<span className="inline-block size-3 shrink-0" ... />
/* or explicitly: rounded-none if you need to reset a parent's rounded */
```
[VERIFIED: codebase scan]
### Pattern 4: Chart Component Token Wiring
**What:** `SpendBarChart` and `IncomeBarChart` use `ChartConfig` objects to map data keys to colors. Currently `IncomeBarChart.actual` is `color: "var(--color-income-fill)"` — already correct. `SpendBarChart.budgeted` uses `--color-budget-bar-bg` and `actual` uses `--color-muted-foreground`. `Bar radius` props must go to 0.
**When to use:** Any chart using Recharts `Bar` component.
**Example:**
```tsx
/* IncomeBarChart.tsx — Bar radius change */
<Bar dataKey="budgeted" fill="var(--color-budgeted)" radius={0} />
<Bar dataKey="actual" radius={0}>
/* SpendBarChart.tsx — Bar radius change */
<Bar dataKey="budgeted" fill="var(--color-budgeted)" radius={0} />
<Bar dataKey="actual" radius={0}>
```
Note: `SpendBarChart` uses `Cell` with dynamic `var(--color-${entry.type}-fill)` — this already references fill tokens, which is correct post-rework. No ChartConfig color change needed there.
[VERIFIED: codebase — SpendBarChart.tsx, IncomeBarChart.tsx]
### Pattern 5: Spacing Upgrade Pattern
**What:** Page-level spacing classes that control section separation and card internal rhythm are upgraded in-place in each page's JSX.
**When to use:** Each of the 9 pages.
**Mapping:**
```
space-y-6 → space-y-8 (section-level vertical rhythm in pages)
gap-6 → gap-8 (grid gaps between card sections)
gap-4 → gap-6 (inner grid element gaps where currently gap-4)
```
Card internal padding (`p-4``p-6`) applies to card content divs in pages, not to `src/components/ui/card.tsx` directly (Card already uses `py-6` and `CardContent` uses `px-6`).
[VERIFIED: codebase — PageShell.tsx, DashboardPage.tsx, BudgetDetailPage.tsx]
### Anti-Patterns to Avoid
- **Editing shadcn component files for radius:** `src/components/ui/*.tsx` do NOT need edits for the radius change — the token cascade handles it. Editing them directly risks breaking shadcn's upgrade path.
- **Changing `src/components/ui/card.tsx` padding:** `CardContent` already uses `px-6`. The `p-4 → p-6` upgrade applies to page-level wrappers that add inner padding on top of card defaults, not to the Card component itself.
- **Adding new `rounded-none` classes broadly:** Prefer removing the hardcoded `rounded-*` class entirely over adding `rounded-none`, since `--radius: 0` already means all `rounded-md`/`rounded-sm` Tailwind classes resolve to 0px. Only add `rounded-none` if you need to explicitly reset a parent that might not be covered.
- **Relying on `--color-chart-*` variables in new code:** These are being deleted this phase. All chart coloring must reference `--color-*-fill` tokens going forward.
---
## Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| WCAG contrast calculation | Manual contrast ratio math | Use UI-SPEC.md confirmed values; verify with browser devtools color picker | OKLCH values already computed and confirmed in CONTEXT.md and UI-SPEC.md |
| CSS-in-JS color token system | Runtime token object | Tailwind 4 `@theme inline` already does this | Single file, zero runtime cost |
| Per-component radius reset | Adding `style={{ borderRadius: 0 }}` to every element | `--radius: 0` token | Token cascade handles all shadcn components in one edit |
| Custom chart color mapping | Separate color registry | Existing `var(--color-*-fill)` CSS variables | Already wired in ExpenseDonutChart; standardize SpendBarChart/IncomeBarChart to match |
---
## Hardcoded `rounded-*` Inventory
Complete list of non-token-driven rounding that requires manual removal, organized by file:
### Pages
| File | Line | Class | Action |
|------|------|-------|--------|
| CategoriesPage.tsx | 101 | `rounded-sm` (group header div) | Remove |
| CategoriesPage.tsx | 107 | `rounded-full` (Skeleton) | Remove |
| CategoriesPage.tsx | 108 | `rounded-md` (Skeleton) | Remove |
| CategoriesPage.tsx | 134 | `rounded-sm` (category header) | Remove |
| TemplatePage.tsx | 250 | `rounded-sm` (group header div) | Remove |
| TemplatePage.tsx | 256 | `rounded-full` (Skeleton) | Remove |
| TemplatePage.tsx | 258 | `rounded-md` (Skeleton) | Remove |
| TemplatePage.tsx | 292 | `rounded-sm` (template item header) | Remove |
| TemplatePage.tsx | 385 | `rounded-full` (color swatch dot) | Remove |
| QuickAddPage.tsx | 98 | `rounded-full` (Skeleton) | Remove |
| QuickAddPage.tsx | 100 | `rounded-md` (Skeleton) | Remove |
| BudgetListPage.tsx | 243 | `rounded-md` (row container div) | Remove |
| BudgetDetailPage.tsx | 290 | `rounded-sm` (skeleton header) | Remove |
| BudgetDetailPage.tsx | 303 | `rounded-md` (Skeleton) | Remove |
| BudgetDetailPage.tsx | 353 | `rounded-sm` (budget item header) | Remove |
| BudgetDetailPage.tsx | 439 | `rounded-md` (summary box) | Remove |
| BudgetDetailPage.tsx | 497 | `rounded-full` (color swatch dot) | Remove |
### Components
| File | Line | Class | Action |
|------|------|-------|--------|
| CategorySection.tsx | 73 | `rounded-md` (collapsible trigger button) | Remove |
| DashboardSkeleton.tsx | 35,43,51 | `rounded-md` (Skeleton placeholders) | Remove |
| DashboardSkeleton.tsx | 59 | `rounded-md` (row placeholder) | Remove |
| DashboardSkeleton.tsx | 63,64 | `rounded-full` (Skeleton) | Remove |
| ChartEmptyState.tsx | 12 | `rounded-lg` (empty state border) | Remove |
| ExpenseDonutChart.tsx | 141 | `rounded-full` (legend color dot) | Remove |
| QuickAddPicker.tsx | 156 | `rounded-sm` (picker item) | Remove |
| QuickAddPicker.tsx | 201 | `rounded-full` (category dot) | Remove |
**Note on Skeleton components:** `Skeleton` in `src/components/ui/skeleton.tsx` has `rounded-md` in its base class (`animate-pulse rounded-md bg-accent`). When `--radius: 0`, `rounded-md` resolves to 0px automatically — skeleton rounding is already handled by the token. The hardcoded `rounded-full` overrides on individual `<Skeleton>` usages (which pass `className` that overrides the base) must still be removed to avoid pill-shaped skeletons.
---
## Common Pitfalls
### Pitfall 1: Skeleton `rounded-full` Overrides Persist
**What goes wrong:** After `--radius: 0`, the base `Skeleton` component becomes square. But in multiple pages, Skeleton elements are given `className="h-5 w-16 rounded-full"`. Since `rounded-full` uses a fixed `border-radius: 9999px` that does not derive from `--radius`, those skeleton placeholders remain pill-shaped.
**Why it happens:** `rounded-full` in Tailwind is always `9999px` regardless of the `--radius` token. Only `rounded-md`, `rounded-sm`, `rounded-lg`, `rounded-xl` etc. derive from `--radius`.
**How to avoid:** Remove `rounded-full` from every `className` passed to `<Skeleton>` as listed in the inventory above.
**Warning signs:** Pill-shaped loading placeholders visible on BudgetDetail, Template, Categories, QuickAdd pages after the token change.
### Pitfall 2: Recharts `radius` Prop vs. CSS
**What goes wrong:** `<Bar radius={4}>` and `<Bar radius={[4, 4, 0, 0]}>` are SVG attribute props, not CSS. Setting `--radius: 0` does not affect them. CSS overrides on `.recharts-rectangle` may also need `rx: 0; ry: 0` as SVG presentation attributes.
**Why it happens:** Recharts renders bars as `<rect rx="4" ry="4">` SVG elements. CSS `border-radius` on SVG `rect` has browser-level quirks — some browsers respect it, others require the `rx`/`ry` attributes.
**How to avoid:** Change the `radius` prop to `0` directly on `<Bar>` in both `SpendBarChart.tsx` and `IncomeBarChart.tsx`. This is the most reliable fix.
**Warning signs:** Chart bars still have rounded caps after CSS override but not prop change.
### Pitfall 3: Sonner Toast Radius Override Specificity
**What goes wrong:** Sonner's Toaster component passes `"--border-radius": "var(--radius)"` as an inline `style` prop. After setting `--radius: 0`, this should automatically propagate. However, Sonner also has its own internal styles.
**Why it happens:** The sonner.tsx wrapper already wires `--border-radius` to `var(--radius)`. When `--radius` becomes `0`, this should cascade automatically. The risk is Sonner's own stylesheet having higher specificity.
**How to avoid:** Verify in browser after `--radius: 0` change. If toasts are still rounded, add `[data-sonner-toast] { border-radius: 0 !important; }` to `index.css`.
**Warning signs:** Toast notifications still display with rounded corners after `--radius: 0` edit.
### Pitfall 4: `--color-chart-*` Still Referenced After Deletion
**What goes wrong:** After removing `--color-chart-1` through `--color-chart-5` from `index.css`, if any component still references them the color falls back to transparent/browser default (usually black or undefined).
**Why it happens:** The variables are only defined in one place but could theoretically be referenced in multiple chart config objects.
**How to avoid:** Before deleting, grep the entire `src/` directory for `color-chart` to confirm only `index.css` references them (already confirmed — zero TSX references found). Safe to delete.
**Warning signs:** Charts render with black or missing colors.
### Pitfall 5: `PageShell` `gap-6` Governs All Page Layouts
**What goes wrong:** `PageShell` uses `flex flex-col gap-6`. Because it wraps most pages, the gap between the page header and first content section is controlled here. Updating spacing in individual pages but not in `PageShell` creates inconsistency.
**Why it happens:** PageShell is a shared wrapper — spacing changes to individual pages don't affect header-to-content gap unless PageShell is also updated.
**How to avoid:** Update `PageShell` from `gap-6` to `gap-8` as part of the spacing wave. All pages using `PageShell` benefit automatically.
**Warning signs:** Pages with `PageShell` wrapper still show tight header-to-content gap despite page-level spacing upgrades.
### Pitfall 6: Donut Chart Legend Dots Remain Circular
**What goes wrong:** The custom legend in `ExpenseDonutChart.tsx` uses `<span className="inline-block size-3 shrink-0 rounded-full">` for category color dots. These remain pill/circle shaped because `rounded-full` is hardcoded.
**Why it happens:** The legend is custom HTML, not a Recharts component — token cascade doesn't reach it.
**How to avoid:** Remove `rounded-full` from the legend span. A square dot of 3×3px is intentionally square in the sharp design aesthetic.
**Warning signs:** Circular color swatches in the donut chart legend after the rework.
---
## Code Examples
### index.css Token Block (post-rework shape)
```css
/* Source: src/index.css — annotated for phase 5 changes */
@theme inline {
/* UI warmth — chroma 0.005 → 0.01 */
--color-background: oklch(0.98 0.01 260);
/* ... other base tokens unchanged ... */
/* Category text colors — UNCHANGED (already WCAG 4.5:1) */
--color-income: oklch(0.55 0.17 155);
/* ... */
/* Category fill colors — chroma raised to 0.22+ */
--color-income-fill: oklch(0.72 0.22 155);
--color-bill-fill: oklch(0.70 0.22 25);
--color-variable-expense-fill: oklch(0.74 0.22 50);
--color-debt-fill: oklch(0.66 0.23 355);
--color-saving-fill: oklch(0.72 0.22 220);
--color-investment-fill: oklch(0.68 0.22 285);
/* --color-chart-1 through --color-chart-5: DELETED */
--radius: 0; /* was 0.625rem */
}
```
[VERIFIED: src/index.css]
### SpendBarChart.tsx Post-Rework
```tsx
// Source: src/components/dashboard/charts/SpendBarChart.tsx — key changes
const chartConfig = {
budgeted: { label: "Budgeted", color: "var(--color-budget-bar-bg)" },
actual: { label: "Actual", color: "var(--color-muted-foreground)" },
} satisfies ChartConfig
// Bar radius props: 4 → 0
<Bar dataKey="budgeted" fill="var(--color-budgeted)" radius={0} />
<Bar dataKey="actual" radius={0}>
{/* Cell fill already uses var(--color-${entry.type}-fill) — correct */}
</Bar>
```
[VERIFIED: src/components/dashboard/charts/SpendBarChart.tsx]
### IncomeBarChart.tsx Post-Rework
```tsx
// Source: src/components/dashboard/charts/IncomeBarChart.tsx — key changes
const chartConfig = {
budgeted: { label: "Budgeted", color: "var(--color-budget-bar-bg)" },
actual: { label: "Actual", color: "var(--color-income-fill)" }, // unchanged — already correct
} satisfies ChartConfig
// Bar radius props: [4, 4, 0, 0] → 0
<Bar dataKey="budgeted" fill="var(--color-budgeted)" radius={0} />
<Bar dataKey="actual" radius={0}>
```
[VERIFIED: src/components/dashboard/charts/IncomeBarChart.tsx]
### PageShell Spacing Upgrade
```tsx
// Source: src/components/shared/PageShell.tsx — spacing change
// Before:
<div className="flex flex-col gap-6">
// After:
<div className="flex flex-col gap-8">
```
[VERIFIED: src/components/shared/PageShell.tsx]
### DashboardPage Section Spacing Upgrade
```tsx
// Source: src/pages/DashboardPage.tsx — key spacing changes
// Before: <div className="space-y-6">
// After: <div className="space-y-8">
// Before: <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
// After: <div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
```
[VERIFIED: src/pages/DashboardPage.tsx lines 186, 207]
---
## State of the Art
| Old Approach | Current Approach | When Changed | Impact |
|--------------|------------------|--------------|--------|
| Separate `--color-chart-*` variables | Reference `--color-*-fill` directly | This phase | Eliminates color duplication; single source of truth per category |
| `--radius: 0.625rem` | `--radius: 0` | This phase | All shadcn components become sharp-cornered |
| Fill chroma 0.18-0.20 | Fill chroma 0.22+ | This phase | Visually saturated pastels that read as colorful, not grey |
| `gap-6` / `space-y-6` sections | `gap-8` / `space-y-8` | This phase | More generous breathing room between content sections |
---
## Assumptions Log
| # | Claim | Section | Risk if Wrong |
|---|-------|---------|---------------|
| A1 | Sonner toast `--border-radius: var(--radius)` inline style in sonner.tsx will propagate `--radius: 0` correctly without additional CSS override | Pitfall 3 / Pattern 2 | If wrong, toasts remain rounded; mitigation: add `[data-sonner-toast] { border-radius: 0 !important }` as CSS override |
| A2 | CSS `rx: 0; ry: 0` on `.recharts-rectangle` overrides SVG presentation attributes cross-browser | Pitfall 2 | If wrong, bars may still show rounding in some browsers; mitigation: change `radius` prop to 0 on `<Bar>` (reliable) |
| A3 | No `--color-chart-*` references exist in any TSX file (confirmed by grep returning zero results) | Don't Hand-Roll | LOW risk — grep confirmed no TSX references |
---
## Open Questions
1. **Sonner version 2.x selector name for border-radius override**
- What we know: Sonner 2.0.7 is installed; `[data-sonner-toast]` is the conventional selector from v1/v2
- What's unclear: Whether v2.x changed the data attribute name on the toast wrapper
- Recommendation: Verify in browser devtools after `--radius: 0` change; if toasts remain rounded, inspect the DOM element for the correct attribute selector
2. **`space-y-6` vs `gap-6` on TemplatePage and BudgetDetailPage**
- What we know: These pages use `space-y-6` for section stacking (not `gap-*` which requires flex/grid parent)
- What's unclear: Whether changing `space-y-6``space-y-8` alone is sufficient or whether the wrapping flex container also needs `gap-8`
- Recommendation: Apply `space-y-8` where currently `space-y-6`; apply `gap-8` where currently `gap-6` in flex/grid containers — both changes are needed depending on context
---
## Environment Availability
Step 2.6: SKIPPED — Phase 5 is purely CSS/TSX file edits with no external dependencies, database changes, or CLI tools beyond the existing Vite dev server.
---
## Validation Architecture
### Test Framework
| Property | Value |
|----------|-------|
| Framework | None detected — no jest.config.*, vitest.config.*, pytest.ini found |
| Config file | None — Wave 0 gap |
| Quick run command | `npm run build` (TypeScript compile check) |
| Full suite command | Manual visual pass across 9 pages in browser |
### Phase Requirements → Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|--------|----------|-----------|-------------------|-------------|
| DS-01 | No rounded corners visible anywhere | Manual (visual) | `npm run build` (compile check only) | N/A — visual |
| DS-02 | Category fills visibly colorful, not washed out | Manual (visual) | `npm run build` | N/A — visual |
| DS-03 | Generous whitespace, no visual crowding | Manual (visual) | `npm run build` | N/A — visual |
### Sampling Rate
- **Per task commit:** `npm run build` — confirms no TypeScript errors
- **Per wave merge:** `npm run build` + manual browser check of affected pages
- **Phase gate:** Full 9-page visual pass per the UI-SPEC checklist before `/gsd-verify-work`
### Wave 0 Gaps
- [ ] No unit/integration test infrastructure exists — this phase is purely visual; manual browser verification is the acceptance gate
- [ ] Consider running `npm run lint` as a secondary automated check
---
## Security Domain
Security enforcement: not applicable. Phase 5 introduces zero new features, endpoints, authentication flows, user input handling, or data access. No ASVS categories apply. This is a pure CSS token and spacing change.
---
## Sources
### Primary (HIGH confidence)
- `src/index.css` (codebase) — Full token inventory, current `--radius` value, all OKLCH color values, `--color-chart-*` variables
- `src/components/ui/` (codebase) — shadcn component source confirming `rounded-*` class usage, `sonner.tsx` Toaster inline style wiring
- `src/components/dashboard/charts/` (codebase) — Recharts `Bar` radius prop values, ChartConfig objects, Cell fill patterns
- `src/pages/` (codebase) — All 9 pages audited for `gap-*`, `space-y-*`, `rounded-*` classes
- `05-UI-SPEC.md` (project planning) — Confirmed post-rework color values, spacing targets, component inventory
### Secondary (MEDIUM confidence)
- Tailwind CSS 4 `@theme inline` documentation — `rounded-md` etc. derive from `--radius` token; `rounded-full` does not
### Tertiary (LOW confidence — A1, A2 in assumptions log)
- Sonner 2.x data attribute selector name (`[data-sonner-toast]`) — based on conventional usage; verify in browser
- Recharts CSS `rx`/`ry` override cross-browser behavior — recommend prop change as primary approach
---
## Metadata
**Confidence breakdown:**
- Token edit scope (index.css): HIGH — full file read, all variables inventoried
- Hardcoded rounded-* inventory: HIGH — exhaustive grep of src/pages + src/components
- Spacing upgrade targets: HIGH — grep confirmed current class values in all 9 pages
- Third-party overrides (Recharts, Sonner): MEDIUM — prop-based fix is reliable; CSS override selector needs browser verification
- Color OKLCH values: HIGH — values provided in CONTEXT.md and UI-SPEC.md
**Research date:** 2026-04-20
**Valid until:** 2026-05-20 (stable stack — Tailwind 4, Recharts 2.15, Sonner 2.x)