docs(05): research phase 5 design system token rework
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
550
.planning/phases/05-design-system-token-rework/05-RESEARCH.md
Normal file
550
.planning/phases/05-design-system-token-rework/05-RESEARCH.md
Normal file
@@ -0,0 +1,550 @@
|
|||||||
|
# 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)
|
||||||
Reference in New Issue
Block a user