30 KiB
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: 0token change insrc/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-*-filltokens 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:
/* 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:
/* 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:
/* 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:
/* 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/*.tsxdo 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.tsxpadding:CardContentalready usespx-6. Thep-4 → p-6upgrade applies to page-level wrappers that add inner padding on top of card defaults, not to the Card component itself. - Adding new
rounded-noneclasses broadly: Prefer removing the hardcodedrounded-*class entirely over addingrounded-none, since--radius: 0already means allrounded-md/rounded-smTailwind classes resolve to 0px. Only addrounded-noneif 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-*-filltokens 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)
/* 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
// 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
// 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
// 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
// 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
-
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: 0change; if toasts remain rounded, inspect the DOM element for the correct attribute selector
- What we know: Sonner 2.0.7 is installed;
-
space-y-6vsgap-6on TemplatePage and BudgetDetailPage- What we know: These pages use
space-y-6for section stacking (notgap-*which requires flex/grid parent) - What's unclear: Whether changing
space-y-6→space-y-8alone is sufficient or whether the wrapping flex container also needsgap-8 - Recommendation: Apply
space-y-8where currentlyspace-y-6; applygap-8where currentlygap-6in flex/grid containers — both changes are needed depending on context
- What we know: These pages use
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 lintas 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--radiusvalue, all OKLCH color values,--color-chart-*variablessrc/components/ui/(codebase) — shadcn component source confirmingrounded-*class usage,sonner.tsxToaster inline style wiringsrc/components/dashboard/charts/(codebase) — RechartsBarradius prop values, ChartConfig objects, Cell fill patternssrc/pages/(codebase) — All 9 pages audited forgap-*,space-y-*,rounded-*classes05-UI-SPEC.md(project planning) — Confirmed post-rework color values, spacing targets, component inventory
Secondary (MEDIUM confidence)
- Tailwind CSS 4
@theme inlinedocumentation —rounded-mdetc. derive from--radiustoken;rounded-fulldoes 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/ryoverride 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)