Files
SimpleFinanceDash/.planning/research/STACK.md

293 lines
14 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.
# Technology Stack
**Project:** SimpleFinanceDash — UI Polish Milestone
**Researched:** 2026-03-11
**Confidence:** HIGH (stack already locked; all versions confirmed from package.json and source files)
---
## Current Stack (Already Installed — Do Not Change)
| Technology | Version | Role |
|------------|---------|------|
| React | 19.2.0 | UI runtime |
| TypeScript | 5.9.3 | Type safety |
| Vite | 7.3.1 | Build tool |
| Tailwind CSS | 4.2.1 | Utility styling |
| shadcn/ui | 4.0.0 | Component library |
| radix-ui | 1.4.3 | Headless primitives (single package, not individual `@radix-ui/*`) |
| Recharts | 2.15.4 | Charts |
| tw-animate-css | 1.4.0 | CSS animation utilities |
| Geist Variable | 5.2.8 | Primary font (already loaded, set as `--font-sans`) |
| lucide-react | 0.577.0 | Icon set |
**Critical context:** The project is on the new shadcn v4 architecture (single `radix-ui` package, `@theme inline` in CSS, `oklch` color space). All recommendations below are scoped to this exact setup.
---
## Recommended Stack — UI Polish Layer
### 1. CSS Variable Strategy for Pastel Theme
**Technique: Replace oklch values in `:root` inside `index.css`**
The current `index.css` uses default shadcn black/white tokens:
- `--background: oklch(1 0 0)` → pure white
- `--primary: oklch(0.205 0 0)` → near-black
- `--secondary: oklch(0.97 0 0)` → near-white gray
**Replace with pastel-tinted equivalents.** oklch is ideal for pastel work because the `C` (chroma) channel controls saturation independently of lightness. A pastel is high-L, low-C:
```
Soft lavender background: oklch(0.97 0.015 280)
Soft blue primary: oklch(0.55 0.12 250)
Rose secondary: oklch(0.94 0.025 350)
Amber accent: oklch(0.94 0.04 85)
Soft green: oklch(0.94 0.04 145)
```
**Finance category color tokens** (add as custom vars alongside shadcn defaults):
```css
:root {
/* Category semantic tokens — used in chart fills and row backgrounds */
--color-income: oklch(0.88 0.08 145); /* soft green */
--color-bill: oklch(0.88 0.07 250); /* soft blue */
--color-expense: oklch(0.90 0.08 85); /* soft amber */
--color-debt: oklch(0.90 0.08 15); /* soft red/rose */
--color-saving: oklch(0.88 0.07 280); /* soft violet */
--color-investment: oklch(0.90 0.07 320); /* soft pink */
}
```
Expose them in the `@theme inline` block so Tailwind generates utility classes (`bg-income`, `bg-bill`, etc.).
**Confidence: HIGH** — This is how shadcn v4 + Tailwind v4 theming works. The `@theme inline` bridging pattern is confirmed by the existing codebase structure.
---
### 2. shadcn/ui Component Customization Strategy
**Pattern: Variant extension via `class-variance-authority` (CVA) — already available**
Do not fork shadcn component source files unless necessary. Instead:
1. **CSS variable changes** cover 80% of polish (background, border, primary, radius).
2. **className overrides at the call site** via `cn()` cover the remaining 20%.
3. For components that need a recurring custom style (e.g., Card with always-pastel header), wrap in a thin local component: `PastelCard`, `StatCard`. This avoids touching `ui/card.tsx`.
**Radius token:** The current `--radius: 0.625rem` is fine. For a softer look, increase to `0.75rem` or `1rem`.
**Border softness:** Replace `--border: oklch(0.922 0 0)` with a tinted, slightly lower-opacity token like `oklch(0.88 0.02 250 / 60%)` for a barely-there border that reads as "pastel spreadsheet cell divider."
**Confidence: HIGH**
---
### 3. Chart Customization with Recharts + shadcn ChartContainer
The codebase already has shadcn's `ChartContainer` / `ChartTooltipContent` wrapper in `ui/chart.tsx`. This is the correct foundation.
**Current gap:** `ExpenseBreakdown.tsx` uses raw `<PieChart>` without `ChartContainer`. It should be migrated to use `ChartContainer` + `ChartConfig` so chart colors come from CSS variables rather than hardcoded hex strings.
**Pattern to follow for all charts:**
```typescript
const chartConfig = {
bills: { label: 'Bills', color: 'var(--color-bill)' },
expenses: { label: 'Expenses', color: 'var(--color-expense)' },
savings: { label: 'Savings', color: 'var(--color-saving)' },
investments: { label: 'Investments', color: 'var(--color-investment)' },
} satisfies ChartConfig
```
Then use `fill="var(--color-bills)"` on Recharts `<Cell>` elements. The `ChartStyle` component injects these as scoped CSS vars so dark mode works automatically.
**Tooltip customization:** Use `<ChartTooltipContent>` with a `formatter` prop to render currency-formatted values. The existing `ChartTooltipContent` already handles the indicator dot pattern — just pass `formatter={(value) => formatCurrency(value, currency)}`.
**Recharts animation:** Recharts has built-in entrance animations on charts (`isAnimationActive={true}` is the default). The animation duration can be tuned via `animationDuration` (prop on `<Bar>`, `<Pie>`, `<Line>`). Set to `600``800ms` for a polished feel. Do NOT disable animations.
**Chart types in use / recommended:**
- `PieChart` — expense breakdown (already exists, needs ChartContainer migration)
- `BarChart` — budget vs actual comparison (recommended addition for FinancialOverview)
- `RadialBarChart` — AvailableBalance progress indicator (recommended to replace plain number display)
**Confidence: HIGH** — Recharts 2.x API is stable and well-understood. The ChartContainer pattern is confirmed from the existing `ui/chart.tsx` source.
---
### 4. Animation and Transition Strategy
**Primary tool: `tw-animate-css` (already installed)**
`tw-animate-css` provides Tailwind v4-compatible utility classes wrapping Animate.css keyframes. Use it for:
- Page entrance animations: `animate-fade-in` on dashboard sections
- Dialog/sheet enters: `animate-in slide-in-from-bottom-4` (shadcn already applies these via `data-[state=open]` variants)
- Loading skeletons: `animate-pulse` (Tailwind built-in, no extra library needed)
**Do NOT add Motion/Framer Motion for this milestone.** Reason: the interactions needed (inline edit toggle, form submission feedback, card entrance) are all achievable with CSS transitions and `tw-animate-css`. Framer Motion adds ~45KB to the bundle and its React 19 compatibility had rough edges in early 2025. Revisit for a future milestone if complex drag-and-drop or physics animations are needed.
**Inline edit transitions** (e.g., BillsTracker `InlineEditRow`): Use CSS `transition-all duration-150` on the toggle between display and input state. This is zero-dependency and already idiomatic in the codebase.
**Recommended micro-interaction patterns:**
- Input focus: `focus-visible:ring-2 focus-visible:ring-primary/40` for a soft glow
- Row hover: `hover:bg-muted/60 transition-colors duration-150`
- Button press: shadcn Button already has `active:scale-[0.98]` equivalent via its CVA variants — verify in `ui/button.tsx`
- Number updates (balance changing): CSS `@keyframes` count-up is overkill; a simple `transition-opacity` flash on value change via a `key` prop reset is sufficient
**Confidence: MEDIUM** — tw-animate-css is confirmed installed. Framer Motion React 19 compatibility claim is based on training data; the recommendation to skip it is defensive but well-justified on bundle size grounds alone.
---
### 5. Typography
**Current state:** Geist Variable is already installed (`@fontsource-variable/geist`) and set as `--font-sans`. This is the correct font for this project.
**Geist Variable characteristics:** Clean, geometric, excellent for data/numbers, neutral without being cold. Used by Vercel's dashboard products. Well-suited for finance UIs.
**Do NOT add a second typeface.** The spreadsheet aesthetic is served by a single well-chosen sans. A display font pairing would clash with the data-dense layout.
**Typography scale recommendations:**
```css
/* Add to @theme inline */
--font-mono: 'Geist Mono Variable', ui-monospace, monospace;
```
Install `@fontsource-variable/geist-mono` alongside the existing font. Use `font-mono tabular-nums` on all currency amounts. The existing `ChartTooltipContent` already does this (`font-mono tabular-nums` class on values) — extend this pattern to table cells showing currency.
**Text hierarchy for finance:**
- Card titles: `text-sm font-semibold tracking-wide text-muted-foreground uppercase` — reads as a spreadsheet column header
- Primary numbers (balance): `text-3xl font-bold tabular-nums`
- Secondary numbers (line items): `text-sm tabular-nums`
- Labels: `text-xs text-muted-foreground`
**Confidence: HIGH** — Font is already installed. Geist Mono is the natural companion and from the same @fontsource-variable namespace.
---
### 6. Icon Usage (lucide-react)
**Already installed at 0.577.0.** lucide-react is the correct choice — it's the shadcn default and provides consistent stroke-width icons that match the component style.
**Pattern for finance icons:**
| Concept | Lucide Icon |
|---------|-------------|
| Income | `TrendingUp` |
| Bills | `Receipt` |
| Variable expenses | `ShoppingCart` |
| Debt | `CreditCard` |
| Savings | `PiggyBank` |
| Investments | `BarChart2` |
| Settings | `Settings2` |
| Categories | `Tag` |
| Budget period | `CalendarDays` |
Use `size={16}` consistently for inline icons, `size={20}` for standalone icon buttons.
**Confidence: HIGH**
---
### 7. Layout Pattern for Dashboard
The current `DashboardPage.tsx` uses a flat `flex flex-col gap-6 p-6` layout. For polish, the recommended evolution is:
**Header area:** Sticky top bar with budget selector + month navigation + key stat (available balance). Removes the selector from inline flow.
**Grid approach (keep existing):** The `lg:grid-cols-3` + `lg:col-span-2` pattern for FinancialOverview + AvailableBalance is correct. Continue using CSS Grid for two-column chart sections.
**Card anatomy standard:**
- `CardHeader`: Always has a soft background gradient using two adjacent pastel tokens
- `CardTitle`: Uses the uppercase label style (see Typography section)
- `CardContent`: `p-0` for tables, `pt-4` for charts/other content
This pattern already exists in the codebase (BillsTracker, FinancialOverview headers use `bg-gradient-to-r from-blue-50 to-indigo-50`). The task is to make it consistent and driven by category-semantic tokens rather than hardcoded Tailwind color names.
**Do NOT use Tailwind's `bg-blue-50` directly.** Reason: when the CSS variable values are updated for theming, hardcoded `bg-blue-50` won't change. Use `bg-(--color-bill)/20` syntax to reference semantic tokens.
**Confidence: HIGH**
---
## Alternatives Considered
| Category | Recommended | Alternative | Why Not |
|----------|-------------|-------------|---------|
| Animation | tw-animate-css (installed) | Framer Motion | Bundle cost (~45KB), React 19 edge cases, overkill for this scope |
| Animation | tw-animate-css (installed) | react-spring | Same — overkill, no benefit over CSS transitions for this UI |
| Charts | Recharts (installed) | Victory / Nivo | Recharts is already embedded, ChartContainer wrapper is done, switching has zero upside |
| Charts | Recharts (installed) | D3 directly | Too low-level; maintenance cost far exceeds polish benefit |
| Fonts | Geist Variable (installed) | Inter | Geist is already there; Inter would be a downgrade in distinctiveness |
| Fonts | Single font | Font pairing | Finance dashboards read better with single-family discipline |
| Color format | oklch (current) | HSL / HEX | oklch is perceptually uniform, better for generating harmonious pastel palettes |
---
## What NOT to Use
**Do NOT install:**
- `framer-motion` — React 19 had compatibility issues in early 2025; bundle cost not justified
- `@radix-ui/*` individual packages — The project is on the new `radix-ui` unified package; mixing them causes version conflicts
- `react-select` / custom dropdown libraries — shadcn's `Select` covers the need
- A CSS-in-JS solution — Tailwind v4 + CSS variables is the correct pattern; CSS-in-JS would fight the build setup
- A color picker library — custom themes are explicitly out of scope
- `date-fns` or `dayjs` — only needed if date formatting becomes complex; standard `Intl.DateTimeFormat` is sufficient
- `react-hook-form` — the forms in scope (login, budget creation) are simple enough for controlled inputs; adding a form library is premature
---
## Key Tailwind v4 Syntax Notes
**Important: Tailwind v4 changed syntax.** The codebase correctly uses the new forms but contributors must be aware:
| v3 syntax | v4 syntax |
|-----------|-----------|
| `bg-opacity-50` | `bg-black/50` or `oklch(.../50%)` |
| `text-opacity-75` | `text-foreground/75` |
| `bg-[--color-bill]` | `bg-(--color-bill)` (parentheses, not brackets) |
| `@apply border-border` | Same (works in v4) |
| Plugin-based config | `@theme inline` in CSS |
Use `bg-(--color-bill)/20` to reference a CSS variable with opacity. This is the correct v4 syntax.
**Confidence: HIGH** — Confirmed by reading the existing `index.css` which uses the `@theme inline` pattern correctly.
---
## Installation
No new packages are required for the core polish milestone. All tools are already installed.
**Optional — only if currency formatting needs locale support beyond what exists:**
```bash
# Nothing to add — Intl.NumberFormat is native
```
**If Geist Mono is desired (recommended for tabular numbers):**
```bash
cd frontend && bun add @fontsource-variable/geist-mono
```
Then in `index.css`:
```css
@import "@fontsource-variable/geist-mono";
```
And in `@theme inline`:
```css
--font-mono: 'Geist Mono Variable', ui-monospace, monospace;
```
---
## Sources
- Confirmed from codebase: `/frontend/package.json`, `/frontend/src/index.css`, `/frontend/src/components/ui/chart.tsx`
- shadcn v4 `@theme inline` + `oklch` color pattern: confirmed from existing `index.css` structure (HIGH confidence)
- Tailwind v4 CSS variable reference syntax (`bg-(--var)`): confirmed from Tailwind v4 docs in training data, consistent with codebase patterns (HIGH confidence)
- Recharts ChartContainer pattern: confirmed from existing `ui/chart.tsx` implementation (HIGH confidence)
- tw-animate-css v4 compatibility: confirmed from import in `index.css` (`@import "tw-animate-css"`) (HIGH confidence)
- Framer Motion React 19 compatibility concerns: training data (MEDIUM confidence — may have improved in later 2025 patch releases, but avoidance recommendation stands on bundle-size grounds alone)