17 KiB
Project Research Summary
Project: SimpleFinanceDash — UI Polish Milestone Domain: Personal finance dashboard (pastel spreadsheet aesthetic) Researched: 2026-03-11 Confidence: HIGH
Executive Summary
SimpleFinanceDash is a functional, fully-deployed personal finance dashboard built on Go + React + shadcn/ui. The backend is complete. This milestone is a pure frontend polish pass: making an app that already works look and feel intentionally designed. The core problem is that the existing UI has all the structural scaffolding for a pastel theme — correct gradient classes on card headers, a Tailwind 4 @theme inline token bridge, and shadcn components that read from CSS variables — but the CSS variables themselves have zero chroma (neutral grey/white values). A single targeted change to index.css unlocks the entire visual identity across every shadcn component simultaneously.
The recommended approach is strictly layered: define the pastel token system first (Phase 1), then polish structural layout surfaces (Phase 2), then address interaction quality and empty states (Phase 3), then finalize charts (Phase 4). This order is non-negotiable because later phases depend on earlier ones — chart colors must reference the semantic token system, and component polish cannot happen consistently until shared components (InlineEditCell, StatCard, EmptyState) are extracted first. Skipping the token foundation and going component-by-component is the most common failure mode for this type of polish project.
The key risk is color fragmentation: the codebase already has three conflicting color systems (hardcoded hex in Recharts fills, raw Tailwind palette classes like bg-emerald-50, and the CSS variable token system). If the pastel token system is added as a fourth system without migrating the existing approaches, the app will look inconsistent regardless of effort spent. The mitigation is a lib/palette.ts file as the single source of truth, and a strict rule that no color value other than a CSS variable reference belongs in a component file.
Key Findings
Recommended Stack
The stack is already locked — no new dependencies are required for the core milestone. All tools are present: React 19 + TypeScript 5, Tailwind CSS 4.2.1, shadcn/ui 4.0.0 (on the new single radix-ui package architecture), Recharts 2.15.4, tw-animate-css, lucide-react, and Geist Variable font. The one optional addition is @fontsource-variable/geist-mono for tabular currency numbers (one bun add command).
The project is on the new shadcn v4 architecture which uses @theme inline in CSS and oklch color space. This is the correct foundation for a pastel palette — oklch's independent chroma channel (C) makes pastel generation straightforward: high L, low C values produce clean pastels without the perceptual distortion of HSL.
Core technologies:
Tailwind CSS 4.2.1: Utility styling via@theme inline— all tokens live inindex.css, zerotailwind.config.jsneededshadcn/ui 4.0.0: Component library that reads 100% from CSS variables — theme changes flow through tokens automaticallyradix-ui 1.4.3: Single unified package (not individual@radix-ui/*); mixing them causes version conflictsRecharts 2.15.4: Charts via shadcn'sChartContainerwrapper;fillprops require string values, not CSS variables — requiresgetComputedStyleor alib/palette.tsconstant maptw-animate-css 1.4.0: Already imported inindex.css; sufficient for all loading states and micro-interactions without Framer Motionlucide-react 0.577.0: Icon library matching shadcn's stroke-width style; consistentsize={16}/size={20}discipline needed- Geist Variable (already installed): Single typeface; do not add a second. Geist Mono companion is the only optional addition.
Expected Features
The FEATURES.md analysis is based on direct codebase inspection and identifies a concrete, ordered list of gaps.
Must have (table stakes):
- Pastel CSS variable system — all other polish depends on it; currently
--primaryand--accenthave zero chroma - Branded login/register background — first impression is white card on white screen
- App wordmark/logo treatment on login and sidebar — no visual identity currently
- Active nav item color —
isActiveis wired but unstyled due to missing--sidebar-primaryvalue - Card hover states —
hover:bg-mutedis invisible because--mutedhas no chroma - Loading spinner on form submit buttons —
disabledstate only, no visual motion - Page-level loading skeletons styled to match section colors
- Empty state for first-time dashboard (no budgets) and CategoriesPage
- Consistent section header palette — currently each card picks ad-hoc gradient colors
- Positive/negative amount coloring applied uniformly across all tables
Should have (differentiators):
- Pencil icon affordance on inline-editable rows (currently only a background hover hint)
- Save confirmation flash after inline edit (brief green background, 100ms)
- Chart tooltips formatted with
formatCurrency(currently raw numbers) - Budget health badge (green/amber/red) near the budget selector
- Donut chart center label showing month + year context
- Month navigator (prev/next arrows) alongside budget selector
- Progress bars in BillsTracker for actual vs. budgeted amount
- Collapsible sidebar toggle for smaller screens
Defer to v2+:
- Dark mode pastel palette — define light mode fully first; dark pastel is a distinct design problem
- Custom color picker / theme selector — out of scope per PROJECT.md
- Route-level animated page transitions — adds Framer Motion weight for marginal benefit
- Toast notifications for saves — noisy in a dashboard with rapid sequential edits; use contextual row flash instead
- Drag-to-reorder categories on dashboard
- New chart types (multi-month trend, sparklines)
- Onboarding wizard / guided tour
- Category creation inline from dashboard — high complexity, separate milestone
Architecture Approach
The design system follows a strict three-tier layered architecture: Token layer (all CSS custom properties in index.css) → Variant layer (CVA-based component variants in components/ui/) → Composition layer (domain-aware wrapper components in components/). Styling flows strictly downward; data flows upward from hooks through pages into components via props. The component boundary rule is critical: components/ui/ must not import domain types from lib/api.ts.
Major components:
index.css— single source of all CSS tokens,@theme inlineTailwind bridge, and base styles; no other CSS fileslib/palette.ts(new) — single source of truth for category-to-color mapping, used by both Tailwind classes and Rechartsfillstringslib/colors.ts(new) —getCategoryColor()utility for runtime CSS variable resolution in chart componentscomponents/category-badge.tsx(new) — domain badge using CVAcategoryVariantsas single source of category-to-color mappingcomponents/stat-card.tsx(new) — financial metric display card wrapping shadcn Cardcomponents/progress-bar.tsx(new) — budget vs. actual visual with color-coded threshold statescomponents/empty-state.tsx(new) — consistent empty state for all conditional sectionscomponents/InlineEditCell.tsx(extract) — extract from three duplicated local definitions in BillsTracker, VariableExpenses, DebtTracker
Critical Pitfalls
-
Hardcoded color values bypassing the token system — The codebase has hex strings in Recharts fills, raw Tailwind palette classes (
bg-emerald-50), and per-componentPASTEL_COLORSarrays alongside the CSS variable system. Adding a fourth system (the pastel tokens) without migrating the existing approaches produces a visually inconsistent result regardless of effort. Prevention:lib/palette.tsas the single source of truth; ban inline hex values in component files. -
Editing shadcn source files directly —
src/components/ui/is effectively vendor code. Editing it risks overwrite by futureshadcn addcommands and makes the diff from upstream invisible. Prevention: customize exclusively via CSS variables inindex.css; create wrapper components (stat-card.tsx,category-badge.tsx) for domain logic rather than modifyingcard.tsx,badge.tsx. -
InlineEditRow duplicated in three components —
BillsTracker.tsx,VariableExpenses.tsx, andDebtTracker.tsxeach contain a localInlineEditRowfunction. The polish pass will make them diverge further if not extracted first. Prevention: extract tocomponents/InlineEditCell.tsxbefore any visual work touches these tables. -
Chart colors not connected to the semantic color system —
AvailableBalancedonut slice colors andFinancialOverviewtable row colors use different approaches for the same category types. A user sees two different blues for "Bills" on the same screen. Prevention: all chartfillprops must derive fromCATEGORY_COLORSinlib/palette.ts, keyed by category type — never index-based arrays. -
Spacing inconsistency overlooked in favor of color work —
CardContentpadding varies (p-0vspt-4vspt-6) with no governing rule. Even correct colors will feel amateur if layout rhythm is inconsistent. Prevention: define spacing standards early (dashboard section gaps, card anatomy) and treat auth pages as first-class design surfaces rather than an afterthought.
Implications for Roadmap
Based on combined research, a four-phase structure is strongly indicated by the dependency graph in FEATURES.md and the build order in ARCHITECTURE.md.
Phase 1: Design Token Foundation
Rationale: Everything else depends on this. The CSS variable system must have real pastel values before any component work produces consistent results. Establishing lib/palette.ts here prevents the color fragmentation pitfall from propagating through all subsequent phases.
Delivers: Pastel color system live across all shadcn components, category semantic tokens available as Tailwind utilities, lib/palette.ts and lib/colors.ts in place, InlineEditCell.tsx extracted (dependency clearance before Phase 2).
Addresses: Table stakes #1 (pastel CSS variable system); Pitfalls 1, 3, 4.
Avoids: Starting component polish before the token layer is stable — the most common failure mode for this type of project.
Phase 2: Layout and Brand Identity
Rationale: Once tokens exist, the surfaces users see on every page load (login, sidebar, card headers, page headers) can be polished. These are high-visibility, high-leverage changes that establish the overall visual tone before touching individual feature components.
Delivers: Branded login/register page, polished sidebar with visual hierarchy and colored active nav, consistent card header palette, typography hierarchy on dashboard (FinancialOverview + AvailableBalance as hero row), budget selector with proper page header framing.
Uses: Pastel tokens from Phase 1, Geist Mono for tabular numbers.
Implements: page-header.tsx, stat-card.tsx composition components.
Addresses: Table stakes #2–5 (login background, logo, sidebar, active nav, typography).
Avoids: Pitfall 5 (spacing inconsistency) — establish spacing standards here.
Phase 3: Interaction Quality and Completeness
Rationale: With color and layout established, the focus shifts to states and interactions: loading, empty, error, and edit feedback. These guard against the "unfinished" feeling that persists even in visually polished apps.
Delivers: Loading spinners on form submit buttons, empty states for dashboard first-run and CategoriesPage, inline edit pencil icon affordance, save confirmation flash, positive/negative amount coloring uniformly applied, delete confirmation dialog on CategoriesPage, i18n keys for all new text.
Implements: empty-state.tsx, interaction patterns on InlineEditCell.tsx.
Addresses: Table stakes #6–14; Pitfalls 6 (i18n), 8 (empty states), 9 (delete confirmation).
Avoids: New feature scope creep — defer month navigator, progress bars, budget health badge to optional stretch if foundation is solid.
Phase 4: Chart Polish
Rationale: Charts come last because they depend on both the token system (Phase 1) and the domain components (Phase 2-3) being stable. Polishing charts before tokens are final means redoing the work.
Delivers: ExpenseBreakdown migrated to ChartContainer pattern, all chart fills referencing CATEGORY_COLORS from lib/palette.ts, custom ChartTooltipContent with formatCurrency formatting, donut center label with month/year context, formatCurrency locale bug fixed.
Uses: ChartContainer, ChartTooltipContent from existing ui/chart.tsx.
Implements: Recharts color consistency via getCategoryColor().
Addresses: Differentiators (chart tooltips, donut context label); Pitfalls 7 (Recharts tooltip styling), 10 (formatCurrency locale).
Phase Ordering Rationale
- Phase 1 must precede all others because CSS token values are consumed by every subsequent change. Polish done without live tokens requires rework.
- Phase 2 before Phase 3 because layout surfaces (sidebar, login, card headers) are always visible and set the perceptual quality bar; interaction states are noticed only when triggered.
- Phase 3 before Phase 4 because empty states (Phase 3) affect what charts render against; fixing chart styling before the empty-state context is designed produces mismatched polish.
- Extraction of
InlineEditCell.tsxis placed at the start of Phase 1 (before any visual work) to prevent the three-component divergence pitfall.
Research Flags
Phases with standard patterns (research not needed):
- Phase 1: Tailwind 4
@theme inlinetoken system is well-documented; patterns confirmed directly from codebase inspection. No research needed. - Phase 2: shadcn layout patterns are established and fully confirmed. No research needed.
- Phase 3: All interaction patterns (loading spinner, row flash, empty state) are straightforward CSS transitions and existing shadcn Dialog usage. No research needed.
- Phase 4: Recharts +
ChartContainerintegration pattern is confirmed from existingui/chart.tsx. No research needed.
No phases require /gsd:research-phase — this is a well-scoped polish pass on an existing functional codebase with a fully locked, known stack.
Confidence Assessment
| Area | Confidence | Notes |
|---|---|---|
| Stack | HIGH | All versions confirmed from package.json; no new dependencies needed |
| Features | HIGH (table stakes) / MEDIUM (differentiators) | Table stakes from direct code inspection; differentiators from design domain experience without external benchmark sources |
| Architecture | HIGH | Confirmed from direct inspection of index.css, components.json, button.tsx, FinancialOverview.tsx, ui/chart.tsx |
| Pitfalls | HIGH | All pitfalls grounded in observed code — specific files and line-level patterns cited |
Overall confidence: HIGH
Gaps to Address
- formatCurrency locale bug (Pitfall 10): The
de-DEhardcode inlib/format.tsis a correctness issue, not just cosmetic. Confirm thepreferred_localefield is available on the settings API response before implementing the fix in Phase 4. - Delete cascade behavior on categories (Pitfall 9): Before adding a confirmation dialog in Phase 3, verify what the Go backend does when a category with associated budget items is deleted. The confirmation copy should reflect the actual impact.
- Differentiator priority (budget health badge, month navigator, progress bars): These are MEDIUM-confidence additions based on design judgment. Treat them as stretch goals within Phase 3 rather than committed scope. Re-evaluate after Phase 2 is complete.
- Geist Mono availability: Confirm
@fontsource-variable/geist-monois available on the deployment host or can be bundled before adding thebun addstep.
Sources
Primary (HIGH confidence)
- Direct codebase inspection:
/frontend/src/index.css— confirms Tailwind 4@theme inline, oklch color space, shadcn CSS variable architecture - Direct codebase inspection:
/frontend/package.json— all version numbers confirmed - Direct codebase inspection:
/frontend/components.json— confirms shadcn v4,cssVariables: true, notailwind.config.ts - Direct codebase inspection:
/frontend/src/components/ui/button.tsx,chart.tsx— confirms CVA pattern,ChartContainerwrapper - Direct codebase inspection:
/frontend/src/components/FinancialOverview.tsx,AvailableBalance.tsx,ExpenseBreakdown.tsx— identifies hardcoded color anti-patterns - Direct codebase inspection:
/frontend/src/all pages and components — identifiesInlineEditRowduplication, missing empty states, inconsistent amount coloring
Secondary (MEDIUM confidence)
- Framer Motion React 19 compatibility: training data — recommendation to avoid is defensible on bundle-size grounds alone (~45KB) regardless of compatibility status
- Differentiator features (budget health badge, progress bars, animated counters): domain experience with personal finance UI patterns; no external benchmark sources verified in this session
Tertiary (LOW confidence)
- None — all findings are grounded in codebase or framework documentation patterns
Research completed: 2026-03-11 Ready for roadmap: yes