docs(06): phase verification /home/jlmak/Projects/jlmak/SimpleFinanceDash/.planning/phases/06-preset-data-first-run-detection-and-db-safety/06-VERIFICATION.md
This commit is contained in:
@@ -3,15 +3,15 @@ gsd_state_version: 1.0
|
||||
milestone: v2.0
|
||||
milestone_name: milestone
|
||||
status: executing
|
||||
stopped_at: Roadmap created — ready to plan Phase 5
|
||||
last_updated: "2026-04-20T15:46:32.529Z"
|
||||
stopped_at: Completed 06-03-PLAN.md — Phase 06 fully executed
|
||||
last_updated: "2026-04-20T18:09:03.463Z"
|
||||
last_activity: 2026-04-20 -- Phase 06 execution started
|
||||
progress:
|
||||
total_phases: 5
|
||||
completed_phases: 1
|
||||
completed_phases: 2
|
||||
total_plans: 6
|
||||
completed_plans: 4
|
||||
percent: 67
|
||||
completed_plans: 6
|
||||
percent: 100
|
||||
---
|
||||
|
||||
# Project State
|
||||
@@ -26,9 +26,9 @@ See: .planning/PROJECT.md (updated 2026-04-02)
|
||||
## Current Position
|
||||
|
||||
Phase: 06 (preset-data-first-run-detection-and-db-safety) — EXECUTING
|
||||
Plan: 3 of 3 (COMPLETE)
|
||||
Status: Phase 06 complete
|
||||
Last activity: 2026-04-20 -- Completed plan 06-03 (useFirstRunState + DB push)
|
||||
Plan: 1 of 3
|
||||
Status: Executing Phase 06
|
||||
Last activity: 2026-04-20 -- Phase 06 execution started
|
||||
|
||||
Progress: [████████████████████] 3/3 plans (100%)
|
||||
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
"plan_check": true,
|
||||
"verifier": true,
|
||||
"nyquist_validation": true,
|
||||
"_auto_chain_active": true
|
||||
"_auto_chain_active": false
|
||||
}
|
||||
}
|
||||
677
.planning/phases/05-design-system-token-rework/05-PATTERNS.md
Normal file
677
.planning/phases/05-design-system-token-rework/05-PATTERNS.md
Normal file
@@ -0,0 +1,677 @@
|
||||
# Phase 5: Design System Token Rework - Pattern Map
|
||||
|
||||
**Mapped:** 2026-04-20
|
||||
**Files analyzed:** 14 files (1 CSS + 2 chart components + 1 shared component + 9 pages + 1 component)
|
||||
**Analogs found:** 14 / 14 (all files are modifications of existing code — no new files created)
|
||||
|
||||
---
|
||||
|
||||
## File Classification
|
||||
|
||||
| Modified File | Role | Data Flow | Closest Analog / Self | Match Quality |
|
||||
|---|---|---|---|---|
|
||||
| `src/index.css` | config (CSS tokens) | transform | self — no analog needed | self |
|
||||
| `src/components/dashboard/charts/SpendBarChart.tsx` | component (chart) | transform | `src/components/dashboard/charts/IncomeBarChart.tsx` | exact |
|
||||
| `src/components/dashboard/charts/IncomeBarChart.tsx` | component (chart) | transform | `src/components/dashboard/charts/SpendBarChart.tsx` | exact |
|
||||
| `src/components/dashboard/charts/ExpenseDonutChart.tsx` | component (chart) | transform | `SpendBarChart.tsx` / `IncomeBarChart.tsx` | role-match |
|
||||
| `src/components/shared/PageShell.tsx` | component (layout) | request-response | self | self |
|
||||
| `src/components/dashboard/DashboardSkeleton.tsx` | component (skeleton) | request-response | `CategoriesPage.tsx` skeleton block | role-match |
|
||||
| `src/components/dashboard/CategorySection.tsx` | component (list item) | request-response | `BudgetDetailPage.tsx` group heading | role-match |
|
||||
| `src/components/dashboard/charts/ChartEmptyState.tsx` | component (empty state) | request-response | self | self |
|
||||
| `src/components/QuickAddPicker.tsx` | component (picker) | request-response | `BudgetDetailPage.tsx` select label dot | role-match |
|
||||
| `src/pages/DashboardPage.tsx` | page | request-response | self | self |
|
||||
| `src/pages/BudgetListPage.tsx` | page | request-response | `DashboardPage.tsx` / `BudgetDetailPage.tsx` | role-match |
|
||||
| `src/pages/BudgetDetailPage.tsx` | page | CRUD | `CategoriesPage.tsx` / `TemplatePage.tsx` | exact |
|
||||
| `src/pages/TemplatePage.tsx` | page | CRUD | `BudgetDetailPage.tsx` / `CategoriesPage.tsx` | exact |
|
||||
| `src/pages/CategoriesPage.tsx` | page | CRUD | `BudgetDetailPage.tsx` / `TemplatePage.tsx` | exact |
|
||||
| `src/pages/QuickAddPage.tsx` | page | CRUD | `CategoriesPage.tsx` | role-match |
|
||||
| `src/pages/SettingsPage.tsx` | page | request-response | `CategoriesPage.tsx` | role-match |
|
||||
| `src/pages/LoginPage.tsx` | page | request-response | `SettingsPage.tsx` | role-match |
|
||||
| `src/pages/RegisterPage.tsx` | page | request-response | `SettingsPage.tsx` | role-match |
|
||||
|
||||
---
|
||||
|
||||
## Pattern Assignments
|
||||
|
||||
### `src/index.css` (config, CSS tokens)
|
||||
|
||||
**Analog:** self — authoritative token source, no external analog needed.
|
||||
|
||||
**Current `@theme inline` block** (lines 1-99, full file):
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--color-background: oklch(0.98 0.005 260); /* → 0.01 chroma (warm) */
|
||||
--color-foreground: oklch(0.25 0.02 260);
|
||||
|
||||
/* ... (base tokens unchanged) ... */
|
||||
|
||||
/* Category fill tokens — RAISE chroma to 0.22+ */
|
||||
--color-income-fill: oklch(0.68 0.19 155); /* → 0.22 */
|
||||
--color-bill-fill: oklch(0.65 0.19 25); /* → 0.22 */
|
||||
--color-variable-expense-fill: oklch(0.70 0.18 50); /* → 0.22 */
|
||||
--color-debt-fill: oklch(0.60 0.20 355); /* → 0.23 */
|
||||
--color-saving-fill: oklch(0.68 0.18 220); /* → 0.22 */
|
||||
--color-investment-fill: oklch(0.65 0.18 285); /* → 0.22 */
|
||||
|
||||
/* Chart vars — DELETE these 5 lines entirely */
|
||||
--color-chart-1: oklch(0.72 0.14 155);
|
||||
--color-chart-2: oklch(0.7 0.14 25);
|
||||
--color-chart-3: oklch(0.72 0.14 50);
|
||||
--color-chart-4: oklch(0.65 0.16 355);
|
||||
--color-chart-5: oklch(0.72 0.14 220);
|
||||
|
||||
--radius: 0.625rem; /* → 0 */
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* { @apply border-border; }
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-feature-settings: "rlig" 1, "calt" 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Changes to make in this file:**
|
||||
1. Line 7: `--color-background: oklch(0.98 0.005 260)` → `oklch(0.98 0.01 260)`
|
||||
2. Lines 65-70: raise all six `--color-*-fill` chroma values to 0.22+ per CONTEXT.md
|
||||
3. Lines 52-57: delete `--color-chart-1` through `--color-chart-5` entirely
|
||||
4. Line 72: `--radius: 0.625rem` → `--radius: 0`
|
||||
5. After `@layer base` block: add CSS override selectors for Recharts and Sonner (see Shared Patterns section below)
|
||||
|
||||
---
|
||||
|
||||
### `src/components/dashboard/charts/SpendBarChart.tsx` (component, transform)
|
||||
|
||||
**Analog:** `src/components/dashboard/charts/IncomeBarChart.tsx`
|
||||
|
||||
**Current `Bar` radius props** (lines 64-80):
|
||||
|
||||
```tsx
|
||||
<Bar
|
||||
dataKey="budgeted"
|
||||
fill="var(--color-budgeted)"
|
||||
radius={4} /* → radius={0} */
|
||||
/>
|
||||
<Bar dataKey="actual" radius={4}> /* → radius={0} */
|
||||
{data.map((entry, index) => (
|
||||
<Cell
|
||||
key={index}
|
||||
fill={
|
||||
entry.actual > entry.budgeted
|
||||
? "var(--color-over-budget)"
|
||||
: `var(--color-${entry.type}-fill)` /* already correct — no change */
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Bar>
|
||||
```
|
||||
|
||||
**Changes to make in this file:**
|
||||
- Line 67: `radius={4}` → `radius={0}`
|
||||
- Line 69: `radius={4}` → `radius={0}`
|
||||
- `chartConfig` (lines 31-34): no change needed — `actual` already references `--color-muted-foreground`, `budgeted` references `--color-budget-bar-bg`; neither references deleted `--color-chart-*` vars.
|
||||
|
||||
---
|
||||
|
||||
### `src/components/dashboard/charts/IncomeBarChart.tsx` (component, transform)
|
||||
|
||||
**Analog:** `src/components/dashboard/charts/SpendBarChart.tsx`
|
||||
|
||||
**Current `Bar` radius props** (lines 54-69):
|
||||
|
||||
```tsx
|
||||
<Bar
|
||||
dataKey="budgeted"
|
||||
fill="var(--color-budgeted)"
|
||||
radius={[4, 4, 0, 0]} /* → radius={0} */
|
||||
/>
|
||||
<Bar dataKey="actual" radius={[4, 4, 0, 0]}> /* → radius={0} */
|
||||
{data.map((entry, index) => (
|
||||
<Cell
|
||||
key={index}
|
||||
fill={
|
||||
entry.actual > entry.budgeted
|
||||
? "var(--color-over-budget)"
|
||||
: "var(--color-income-fill)" /* already correct — no change */
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Bar>
|
||||
```
|
||||
|
||||
**Current `chartConfig`** (lines 26-29):
|
||||
|
||||
```tsx
|
||||
const chartConfig = {
|
||||
budgeted: { label: "Budgeted", color: "var(--color-budget-bar-bg)" },
|
||||
actual: { label: "Actual", color: "var(--color-income-fill)" }, /* already references fill token */
|
||||
} satisfies ChartConfig
|
||||
```
|
||||
|
||||
**Changes to make in this file:**
|
||||
- Line 57: `radius={[4, 4, 0, 0]}` → `radius={0}`
|
||||
- Line 59: `radius={[4, 4, 0, 0]}` → `radius={0}`
|
||||
- `chartConfig`: no change needed — already references `--color-income-fill`, not a deleted `--color-chart-*` var.
|
||||
|
||||
---
|
||||
|
||||
### `src/components/dashboard/charts/ExpenseDonutChart.tsx` (component, transform)
|
||||
|
||||
**Analog:** self — already uses `var(--color-${entry.type}-fill)` pattern throughout.
|
||||
|
||||
**The one hardcoded `rounded-full`** (line 141):
|
||||
|
||||
```tsx
|
||||
/* Before */
|
||||
<span
|
||||
className="inline-block size-3 shrink-0 rounded-full"
|
||||
style={{ backgroundColor: `var(--color-${entry.type}-fill)` }}
|
||||
/>
|
||||
|
||||
/* After — remove rounded-full entirely */
|
||||
<span
|
||||
className="inline-block size-3 shrink-0"
|
||||
style={{ backgroundColor: `var(--color-${entry.type}-fill)` }}
|
||||
/>
|
||||
```
|
||||
|
||||
**Changes to make in this file:**
|
||||
- Line 141: remove `rounded-full` from className string.
|
||||
- No `ChartConfig` changes needed — `ExpenseDonutChart` builds its config dynamically from data using `var(--color-${entry.type}-fill)` (lines 47-51), which is already the correct post-rework pattern.
|
||||
|
||||
---
|
||||
|
||||
### `src/components/shared/PageShell.tsx` (component, layout)
|
||||
|
||||
**Current spacing** (line 15):
|
||||
|
||||
```tsx
|
||||
<div className="flex flex-col gap-6"> /* → gap-8 */
|
||||
```
|
||||
|
||||
**Changes to make in this file:**
|
||||
- Line 15: `gap-6` → `gap-8`
|
||||
|
||||
This single change propagates the header-to-content gap increase to all 9 pages that use `PageShell`.
|
||||
|
||||
---
|
||||
|
||||
### `src/components/dashboard/DashboardSkeleton.tsx` (component, skeleton)
|
||||
|
||||
**Analog:** Skeleton patterns from `CategoriesPage.tsx` lines 98-114 and `BudgetDetailPage.tsx` lines 284-306.
|
||||
|
||||
**Hardcoded `rounded-*` locations in this file:**
|
||||
|
||||
```tsx
|
||||
/* Line 35 — chart placeholder */
|
||||
<Skeleton className="h-[250px] w-full rounded-md" /> /* remove rounded-md */
|
||||
|
||||
/* Line 43 — chart placeholder */
|
||||
<Skeleton className="h-[250px] w-full rounded-md" /> /* remove rounded-md */
|
||||
|
||||
/* Line 51 — chart placeholder */
|
||||
<Skeleton className="h-[250px] w-full rounded-md" /> /* remove rounded-md */
|
||||
|
||||
/* Line 59 — collapsible section row */
|
||||
<div className="flex items-center gap-3 rounded-md border-l-4 ..."> /* remove rounded-md */
|
||||
|
||||
/* Lines 63-64 — inline summary badges */
|
||||
<Skeleton className="h-5 w-24 rounded-full" /> /* remove rounded-full — CRITICAL: rounded-full is 9999px, not from --radius */
|
||||
<Skeleton className="h-5 w-24 rounded-full" /> /* remove rounded-full */
|
||||
```
|
||||
|
||||
**Also: spacing upgrades**:
|
||||
|
||||
```tsx
|
||||
/* Line 20 — top-level container */
|
||||
<div className="flex flex-col gap-6"> /* → gap-8 */
|
||||
|
||||
/* Line 22 — summary cards grid */
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> /* → gap-6 */
|
||||
|
||||
/* Line 29 — chart grid */
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> /* → gap-8 */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `src/components/dashboard/CategorySection.tsx` (component, list item)
|
||||
|
||||
**Analog:** Group heading divs in `BudgetDetailPage.tsx` lines 352-356, `CategoriesPage.tsx` lines 134-137, `TemplatePage.tsx` lines 292-295.
|
||||
|
||||
**The hardcoded `rounded-md`** (line 73):
|
||||
|
||||
```tsx
|
||||
/* Before */
|
||||
<button
|
||||
className="group flex w-full items-center gap-3 rounded-md border-l-4 bg-card px-4 py-3 text-left hover:bg-muted/40 transition-colors"
|
||||
style={{ borderLeftColor: categoryColors[type] }}
|
||||
>
|
||||
|
||||
/* After — remove rounded-md */
|
||||
<button
|
||||
className="group flex w-full items-center gap-3 border-l-4 bg-card px-4 py-3 text-left hover:bg-muted/40 transition-colors"
|
||||
style={{ borderLeftColor: categoryColors[type] }}
|
||||
>
|
||||
```
|
||||
|
||||
**Changes to make in this file:**
|
||||
- Line 73: remove `rounded-md` from the button's className.
|
||||
|
||||
---
|
||||
|
||||
### `src/components/dashboard/charts/ChartEmptyState.tsx` (component, empty state)
|
||||
|
||||
**Current `rounded-lg`** (line 12):
|
||||
|
||||
```tsx
|
||||
/* Before */
|
||||
className={cn(
|
||||
"flex min-h-[250px] w-full items-center justify-center rounded-lg border border-dashed ...",
|
||||
className
|
||||
)}
|
||||
|
||||
/* After — remove rounded-lg */
|
||||
className={cn(
|
||||
"flex min-h-[250px] w-full items-center justify-center border border-dashed ...",
|
||||
className
|
||||
)}
|
||||
```
|
||||
|
||||
**Changes to make in this file:**
|
||||
- Line 12: remove `rounded-lg` from the className string.
|
||||
|
||||
---
|
||||
|
||||
### `src/components/QuickAddPicker.tsx` (component, picker)
|
||||
|
||||
**Analog:** Same pattern as `BudgetDetailPage.tsx` line 496 and `TemplatePage.tsx` line 385 — `size-2 rounded-full` color swatch dots used as SelectLabel decorators.
|
||||
|
||||
**Hardcoded `rounded-*` locations:**
|
||||
|
||||
```tsx
|
||||
/* Line 156 — picker item button */
|
||||
className="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 ..."
|
||||
/* → remove rounded-sm */
|
||||
|
||||
/* Line 201 — SelectLabel category dot */
|
||||
<div
|
||||
className="size-2 rounded-full"
|
||||
style={{ backgroundColor: categoryColors[type] }}
|
||||
/>
|
||||
/* → remove rounded-full */
|
||||
```
|
||||
|
||||
**Changes to make in this file:**
|
||||
- Line 156: remove `rounded-sm` from className.
|
||||
- Line 201: remove `rounded-full` from className.
|
||||
|
||||
---
|
||||
|
||||
### `src/pages/DashboardPage.tsx` (page, request-response)
|
||||
|
||||
**Current spacing** (lines 186, 207):
|
||||
|
||||
```tsx
|
||||
/* Line 186 — DashboardContent top-level wrapper */
|
||||
<div className="space-y-6"> /* → space-y-8 */
|
||||
|
||||
/* Line 207 — chart grid */
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> /* → gap-8 */
|
||||
```
|
||||
|
||||
**Changes to make in this file:**
|
||||
- Line 186: `space-y-6` → `space-y-8`
|
||||
- Line 207: `gap-6` → `gap-8`
|
||||
|
||||
No hardcoded `rounded-*` classes in this file.
|
||||
|
||||
---
|
||||
|
||||
### `src/pages/BudgetListPage.tsx` (page, CRUD)
|
||||
|
||||
**Analog:** `BudgetDetailPage.tsx` lines 242-256 — same `rounded-md border p-3` container pattern.
|
||||
|
||||
**Hardcoded `rounded-md`** (line 243):
|
||||
|
||||
```tsx
|
||||
/* Before */
|
||||
<div className="flex items-center gap-3 rounded-md border p-3">
|
||||
|
||||
/* After */
|
||||
<div className="flex items-center gap-3 border p-3">
|
||||
```
|
||||
|
||||
**Spacing:** Check for `gap-4`, `gap-6`, `space-y-*` patterns and apply standard upgrades:
|
||||
- `gap-4` → `gap-6`
|
||||
- `gap-6` → `gap-8`
|
||||
- `space-y-6` → `space-y-8`
|
||||
|
||||
---
|
||||
|
||||
### `src/pages/BudgetDetailPage.tsx` (page, CRUD)
|
||||
|
||||
**Analog:** `CategoriesPage.tsx` and `TemplatePage.tsx` — structurally identical page pattern (group headings, Tables, PageShell).
|
||||
|
||||
**Hardcoded `rounded-*` locations** (confirmed from RESEARCH.md inventory):
|
||||
|
||||
| Line | Current | Action |
|
||||
|------|---------|--------|
|
||||
| 290 | `rounded-sm` on skeleton header div | Remove |
|
||||
| 303 | `rounded-md` on `<Skeleton>` className | Remove |
|
||||
| 353 | `rounded-sm` on budget item group heading div | Remove |
|
||||
| 439 | `rounded-md` on summary totals box | Remove |
|
||||
| 497 | `rounded-full` on SelectLabel category dot | Remove |
|
||||
|
||||
**Line 353 group heading** (current pattern to modify):
|
||||
|
||||
```tsx
|
||||
/* Before */
|
||||
<div
|
||||
className="mb-2 flex items-center gap-3 rounded-sm border-l-4 bg-muted/30 px-3 py-2"
|
||||
style={{ borderLeftColor: categoryColors[type] }}
|
||||
>
|
||||
|
||||
/* After */
|
||||
<div
|
||||
className="mb-2 flex items-center gap-3 border-l-4 bg-muted/30 px-3 py-2"
|
||||
style={{ borderLeftColor: categoryColors[type] }}
|
||||
>
|
||||
```
|
||||
|
||||
**Line 439 summary box** (current pattern to modify):
|
||||
|
||||
```tsx
|
||||
/* Before */
|
||||
<div className="rounded-md border p-4">
|
||||
|
||||
/* After */
|
||||
<div className="border p-4">
|
||||
```
|
||||
|
||||
**Spacing upgrades:**
|
||||
- `space-y-8` already present at line 338 (inner grouped content) — verify no `space-y-6` / `gap-6` remaining
|
||||
- Check for `p-4` on card content wrappers → `p-6`
|
||||
|
||||
---
|
||||
|
||||
### `src/pages/TemplatePage.tsx` (page, CRUD)
|
||||
|
||||
**Analog:** `BudgetDetailPage.tsx` — same group heading + Table structure, same skeleton pattern.
|
||||
|
||||
**Hardcoded `rounded-*` locations** (confirmed from RESEARCH.md inventory):
|
||||
|
||||
| Line | Current | Action |
|
||||
|------|---------|--------|
|
||||
| 250 | `rounded-sm` on skeleton group heading div | Remove |
|
||||
| 256 | `rounded-full` on `<Skeleton>` className | Remove |
|
||||
| 258 | `rounded-md` on `<Skeleton>` className | Remove |
|
||||
| 292 | `rounded-sm` on template item group heading div | Remove |
|
||||
| 385 | `rounded-full` on SelectLabel category dot | Remove |
|
||||
|
||||
**Line 292 group heading** (current):
|
||||
|
||||
```tsx
|
||||
/* Before */
|
||||
<div
|
||||
className="mb-2 flex items-center gap-3 rounded-sm border-l-4 bg-muted/30 px-3 py-2"
|
||||
style={{ borderLeftColor: categoryColors[type] }}
|
||||
>
|
||||
```
|
||||
|
||||
Same removal pattern as `BudgetDetailPage.tsx` line 353.
|
||||
|
||||
**Line 385 SelectLabel dot** (current):
|
||||
|
||||
```tsx
|
||||
/* Before — same pattern as BudgetDetailPage line 497 */
|
||||
<div
|
||||
className="size-2 rounded-full"
|
||||
style={{ backgroundColor: categoryColors[type] }}
|
||||
/>
|
||||
/* After: remove rounded-full */
|
||||
```
|
||||
|
||||
**Spacing upgrades** (current at lines 247, 268, 287):
|
||||
- Line 247: `space-y-6` → `space-y-8` (skeleton wrapper)
|
||||
- Line 268: `gap-6` → `gap-8` (TemplatePage own flex header, not using PageShell)
|
||||
- Line 287: `space-y-6` → `space-y-8` (main content groups)
|
||||
|
||||
---
|
||||
|
||||
### `src/pages/CategoriesPage.tsx` (page, CRUD)
|
||||
|
||||
**Analog:** `BudgetDetailPage.tsx` / `TemplatePage.tsx` — structurally identical.
|
||||
|
||||
**Hardcoded `rounded-*` locations** (confirmed from RESEARCH.md inventory):
|
||||
|
||||
| Line | Current | Action |
|
||||
|------|---------|--------|
|
||||
| 101 | `rounded-sm` on skeleton group heading div | Remove |
|
||||
| 107 | `rounded-full` on `<Skeleton>` className | Remove |
|
||||
| 108 | `rounded-md` on `<Skeleton>` className | Remove |
|
||||
| 134 | `rounded-sm` on category group heading div | Remove |
|
||||
|
||||
**Line 134 group heading** (current):
|
||||
|
||||
```tsx
|
||||
/* Before */
|
||||
<div
|
||||
className="mb-2 flex items-center gap-3 rounded-sm border-l-4 bg-muted/30 px-3 py-2"
|
||||
style={{ borderLeftColor: categoryColors[type] }}
|
||||
>
|
||||
/* After: remove rounded-sm */
|
||||
```
|
||||
|
||||
**Spacing upgrades** (current at lines 98, 130):
|
||||
- Line 98: `space-y-6` → `space-y-8` (skeleton wrapper)
|
||||
- Line 130: `space-y-6` → `space-y-8` (main content groups)
|
||||
|
||||
---
|
||||
|
||||
### `src/pages/QuickAddPage.tsx` (page, CRUD)
|
||||
|
||||
**Analog:** `CategoriesPage.tsx` — same row-list layout, same Skeleton usage.
|
||||
|
||||
**Hardcoded `rounded-*` locations** (confirmed from RESEARCH.md inventory):
|
||||
|
||||
| Line | Current | Action |
|
||||
|------|---------|--------|
|
||||
| 98 | `rounded-full` on `<Skeleton>` className | Remove |
|
||||
| 100 | `rounded-md` on `<Skeleton>` className | Remove |
|
||||
|
||||
**Current skeleton block** (lines 93-105):
|
||||
|
||||
```tsx
|
||||
<div className="space-y-1">
|
||||
{[1, 2, 3, 4, 5].map((i) => (
|
||||
<div key={i} className="flex items-center gap-4 px-4 py-2.5 border-b border-border">
|
||||
<Skeleton className="h-5 w-10 rounded-full" /> /* line 98 — remove rounded-full */
|
||||
<Skeleton className="h-4 w-36" />
|
||||
<Skeleton className="ml-auto h-7 w-7 rounded-md" /> /* line 100 — remove rounded-md */
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
```
|
||||
|
||||
No major spacing changes in this file (list layout, not grid/section-based).
|
||||
|
||||
---
|
||||
|
||||
### `src/pages/SettingsPage.tsx` (page, request-response)
|
||||
|
||||
**Analog:** `CategoriesPage.tsx` — PageShell wrapper, Card with CardContent.
|
||||
|
||||
**No hardcoded `rounded-*` classes** in this file (Card uses token cascade).
|
||||
|
||||
**Spacing upgrade** (line 87):
|
||||
|
||||
```tsx
|
||||
/* Before — CardContent internal spacing */
|
||||
<CardContent className="space-y-4 pt-6"> /* → space-y-6 */
|
||||
```
|
||||
|
||||
**Changes to make in this file:**
|
||||
- Line 87 (and corresponding skeleton line 69): `space-y-4` → `space-y-6` inside `CardContent`
|
||||
|
||||
---
|
||||
|
||||
### `src/pages/LoginPage.tsx` and `src/pages/RegisterPage.tsx` (pages, request-response)
|
||||
|
||||
**Analog:** `SettingsPage.tsx` — Card-based page, no group headings.
|
||||
|
||||
Per RESEARCH.md: "Card already uses `--radius` token cascade." No hardcoded `rounded-*` classes confirmed. No `--color-chart-*` references.
|
||||
|
||||
**Minimal changes:** Verify `p-4` → `p-6` in any card content wrappers if present. No spacing grids to upgrade. Treat as low-touch pages in implementation.
|
||||
|
||||
---
|
||||
|
||||
## Shared Patterns
|
||||
|
||||
### Pattern A: `@theme inline` Token Edit (CSS variable cascade)
|
||||
|
||||
**Source:** `src/index.css` lines 4-79
|
||||
**Apply to:** `src/index.css` only — single source of truth
|
||||
**Rule:** All shadcn `rounded-*` utility classes (rounded-md, rounded-sm, rounded-lg, rounded-xl) derive from `--radius`. Setting `--radius: 0` makes them all 0px automatically. Only `rounded-full` (9999px) is immune.
|
||||
|
||||
```css
|
||||
/* The pattern: edit token value, cascade propagates everywhere */
|
||||
@theme inline {
|
||||
--radius: 0; /* single edit cascades to all shadcn components */
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern B: Hardcoded `rounded-full` Removal (critical pitfall)
|
||||
|
||||
**Source:** Every page and component file listed in RESEARCH.md inventory
|
||||
**Apply to:** All 17 `rounded-full` class occurrences across 8 files
|
||||
**Rule:** `rounded-full` = `border-radius: 9999px` — hardcoded, NOT derived from `--radius`. Must be removed manually from every occurrence.
|
||||
|
||||
```tsx
|
||||
/* Before — still circular after --radius: 0 */
|
||||
<Skeleton className="h-5 w-16 rounded-full" />
|
||||
<span className="inline-block size-3 shrink-0 rounded-full" ... />
|
||||
<div className="size-2 rounded-full" ... />
|
||||
|
||||
/* After — becomes square from --radius: 0 */
|
||||
<Skeleton className="h-5 w-16" />
|
||||
<span className="inline-block size-3 shrink-0" ... />
|
||||
<div className="size-2" ... />
|
||||
```
|
||||
|
||||
### Pattern C: CSS Override for Third-Party Radius
|
||||
|
||||
**Source:** To be added to `src/index.css` after `@layer base` block
|
||||
**Apply to:** Recharts bars (`SpendBarChart`, `IncomeBarChart`) and Sonner toasts
|
||||
**Rule:** Recharts renders bars as `<rect rx="4" ry="4">` SVG — not CSS. Must change `radius` prop directly AND add CSS override. Sonner wires `--border-radius: var(--radius)` (confirmed at `sonner.tsx` line 28) — should auto-propagate, but add CSS override as safety net.
|
||||
|
||||
```css
|
||||
/* Add to src/index.css after the @layer base block */
|
||||
|
||||
/* Recharts: SVG rect elements for bar charts */
|
||||
.recharts-rectangle {
|
||||
rx: 0;
|
||||
ry: 0;
|
||||
}
|
||||
|
||||
/* Sonner: toast container — safety net if var(--radius) cascade insufficient */
|
||||
[data-sonner-toast] {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** Sonner's `sonner.tsx` already passes `"--border-radius": "var(--radius)"` as an inline style (line 28). When `--radius: 0`, this should cascade automatically. The CSS override is a safety net only — verify in browser after token change before deciding if it is needed.
|
||||
|
||||
### Pattern D: Group Heading Div (shared across 5 files)
|
||||
|
||||
**Source:** `src/pages/CategoriesPage.tsx` line 134, `BudgetDetailPage.tsx` line 353, `TemplatePage.tsx` line 292 — all structurally identical
|
||||
**Apply to:** CategoriesPage, BudgetDetailPage, TemplatePage, CategorySection, DashboardSkeleton row
|
||||
|
||||
```tsx
|
||||
/* Pattern: border-l-4 accent bar heading — remove rounded-sm entirely */
|
||||
<div
|
||||
className="mb-2 flex items-center gap-3 border-l-4 bg-muted/30 px-3 py-2"
|
||||
style={{ borderLeftColor: categoryColors[type] }}
|
||||
>
|
||||
<span className="text-sm font-semibold">{label}</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Pattern E: Spacing Upgrade Map
|
||||
|
||||
**Source:** `src/pages/DashboardPage.tsx` lines 186, 207; `src/components/shared/PageShell.tsx` line 15
|
||||
**Apply to:** All 9 pages and PageShell
|
||||
|
||||
```
|
||||
PageShell.tsx line 15: gap-6 → gap-8 (header-to-content gap)
|
||||
DashboardPage.tsx line 186: space-y-6 → space-y-8 (section rhythm)
|
||||
DashboardPage.tsx line 207: gap-6 → gap-8 (chart grid)
|
||||
DashboardSkeleton.tsx line 22: gap-4 → gap-6 (summary cards grid)
|
||||
DashboardSkeleton.tsx line 29: gap-6 → gap-8 (chart grid skeleton)
|
||||
CategoriesPage.tsx line 98: space-y-6 → space-y-8 (skeleton wrapper)
|
||||
CategoriesPage.tsx line 130: space-y-6 → space-y-8 (main content)
|
||||
TemplatePage.tsx line 247: space-y-6 → space-y-8 (skeleton wrapper)
|
||||
TemplatePage.tsx line 268: gap-6 → gap-8 (self-managed header flex)
|
||||
TemplatePage.tsx line 287: space-y-6 → space-y-8 (main content)
|
||||
BudgetDetailPage.tsx: verify no space-y-6/gap-6 remain after existing space-y-8 at line 338
|
||||
SettingsPage.tsx line 87: space-y-4 → space-y-6 (CardContent internal)
|
||||
```
|
||||
|
||||
### Pattern F: `ChartConfig` Token Reference (no `--color-chart-*`)
|
||||
|
||||
**Source:** `src/components/dashboard/charts/IncomeBarChart.tsx` lines 26-29
|
||||
**Apply to:** Any future chart components — `SpendBarChart` and `IncomeBarChart` require no ChartConfig change
|
||||
**Rule:** After deleting `--color-chart-*` from `index.css`, all chart color references must use `--color-*-fill` tokens.
|
||||
|
||||
```tsx
|
||||
/* Correct pattern post-rework — reference fill tokens directly */
|
||||
const chartConfig = {
|
||||
budgeted: { label: "Budgeted", color: "var(--color-budget-bar-bg)" },
|
||||
actual: { label: "Actual", color: "var(--color-income-fill)" },
|
||||
} satisfies ChartConfig
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## No Analog Found
|
||||
|
||||
None — all files are modifications of existing code. No net-new files are created in this phase.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Wave Order
|
||||
|
||||
Per RESEARCH.md recommendation (planner reference):
|
||||
|
||||
**Wave 1 — Token edit only** (`src/index.css`)
|
||||
- Change `--radius: 0`
|
||||
- Raise `--color-*-fill` chroma values to 0.22+
|
||||
- Warm `--color-background` chroma 0.005 → 0.01
|
||||
- Delete `--color-chart-1` through `--color-chart-5`
|
||||
- Add `.recharts-rectangle` and `[data-sonner-toast]` CSS overrides
|
||||
|
||||
**Wave 2 — Chart component updates** (2 files)
|
||||
- `SpendBarChart.tsx`: `radius={4}` → `radius={0}` (x2 Bar props)
|
||||
- `IncomeBarChart.tsx`: `radius={[4, 4, 0, 0]}` → `radius={0}` (x2 Bar props)
|
||||
- `ExpenseDonutChart.tsx`: remove `rounded-full` from legend span (line 141)
|
||||
|
||||
**Wave 3 — Page and shared component sweep** (12 files)
|
||||
- `PageShell.tsx`: `gap-6` → `gap-8`
|
||||
- `DashboardSkeleton.tsx`: remove 5 `rounded-*` classes, upgrade 3 spacing values
|
||||
- `CategorySection.tsx`: remove `rounded-md` from trigger button
|
||||
- `ChartEmptyState.tsx`: remove `rounded-lg` from empty state div
|
||||
- `QuickAddPicker.tsx`: remove 2 `rounded-*` classes
|
||||
- All 9 pages: spacing upgrades + hardcoded `rounded-*` removals per inventory
|
||||
|
||||
---
|
||||
|
||||
## Metadata
|
||||
|
||||
**Analog search scope:** `src/pages/`, `src/components/dashboard/`, `src/components/shared/`, `src/components/ui/`, `src/index.css`
|
||||
**Files read for pattern extraction:** 16 (index.css, PageShell, DashboardPage, DashboardSkeleton, SpendBarChart, IncomeBarChart, ExpenseDonutChart, ChartEmptyState, CategorySection, QuickAddPicker, SettingsPage, BudgetDetailPage excerpt x3, CategoriesPage excerpt, TemplatePage excerpt, QuickAddPage excerpt, BudgetListPage excerpt, sonner.tsx)
|
||||
**Pattern extraction date:** 2026-04-20
|
||||
@@ -0,0 +1,140 @@
|
||||
---
|
||||
phase: 06-preset-data-first-run-detection-and-db-safety
|
||||
verified: 2026-04-20T19:15:00Z
|
||||
status: human_needed
|
||||
score: 5/5
|
||||
overrides_applied: 0
|
||||
human_verification:
|
||||
- test: "Attempt duplicate budget INSERT for same (user_id, start_date) via Supabase SQL editor"
|
||||
expected: "INSERT fails with unique constraint violation error 23505"
|
||||
why_human: "Requires a running Supabase instance with live DB to test constraint enforcement"
|
||||
- test: "Attempt duplicate category INSERT for same (user_id, name) via Supabase SQL editor"
|
||||
expected: "INSERT fails with unique constraint violation error 23505"
|
||||
why_human: "Requires a running Supabase instance with live DB to test constraint enforcement"
|
||||
- test: "Check profiles table for existing v1.0 user rows — confirm setup_completed = true"
|
||||
expected: "All users who had categories or template items before migration show setup_completed = true"
|
||||
why_human: "Requires inspecting live database state after backfill"
|
||||
---
|
||||
|
||||
# Phase 6: Preset Data, First-Run Detection, and DB Safety Verification Report
|
||||
|
||||
**Phase Goal:** The data layer is safe and ready -- duplicate budget/category writes are impossible at the DB level, and the app correctly identifies first-run users
|
||||
**Verified:** 2026-04-20T19:15:00Z
|
||||
**Status:** human_needed
|
||||
**Re-verification:** No -- initial verification
|
||||
|
||||
## Goal Achievement
|
||||
|
||||
### Observable Truths
|
||||
|
||||
| # | Truth | Status | Evidence |
|
||||
|---|-------|--------|----------|
|
||||
| 1 | Attempting to create two budgets for the same user and month is rejected at the DB level | VERIFIED | `006_uniqueness_constraints.sql` contains `ADD CONSTRAINT budgets_user_month_unique UNIQUE (user_id, start_date)` wrapped in BEGIN/COMMIT. User confirmed DB push completed successfully. |
|
||||
| 2 | Attempting to create two categories with the same name for the same user is rejected at the DB level | VERIFIED | `006_uniqueness_constraints.sql` contains `ADD CONSTRAINT categories_user_name_unique UNIQUE (user_id, name)` with safe deduplication DELETE. User confirmed DB push completed. |
|
||||
| 3 | All existing v1.0 users have `profiles.setup_completed = true` after backfill | VERIFIED | `007_setup_completed.sql` adds column `boolean NOT NULL DEFAULT false` then runs UPDATE with UNION covering both `categories` and `template_items` tables. User confirmed DB push applied. |
|
||||
| 4 | `useFirstRunState` hook returns `true` only for users with zero categories or zero template items | VERIFIED | `src/hooks/useFirstRunState.ts` exports function returning `{ isFirstRun: categories.length === 0 \|\| items.length === 0, loading: catLoading \|\| tmplLoading }`. Derives from `useCategories()` and `useTemplate()` caches. `tsc --noEmit` passes. |
|
||||
| 5 | `src/data/presets.ts` contains ~15-20 curated budget items with i18n translation keys | VERIFIED | 19 items (4 income, 4 bill, 5 variable_expense, 2 debt, 2 saving, 2 investment). `en.json` and `de.json` both have `presets` key with 19 slugs across 6 type categories. Both JSON files parse cleanly. |
|
||||
|
||||
**Score:** 5/5 truths verified
|
||||
|
||||
### Deferred Items
|
||||
|
||||
Items not yet met but explicitly addressed in later milestone phases.
|
||||
|
||||
| # | Item | Addressed In | Evidence |
|
||||
|---|------|-------------|----------|
|
||||
| 1 | useFirstRunState not yet consumed by any component | Phase 7 | Phase 7 SC 1: "A new user is automatically redirected to /setup on their first login" -- requires useFirstRunState |
|
||||
| 2 | PRESETS/PresetItem not yet imported by any component | Phase 7 | Phase 7 SC 2: "recurring items step shows ~15-20 pre-filled common items" -- consumes PRESETS array |
|
||||
|
||||
### Required Artifacts
|
||||
|
||||
| Artifact | Expected | Status | Details |
|
||||
|----------|----------|--------|---------|
|
||||
| `supabase/migrations/006_uniqueness_constraints.sql` | Atomic deduplication + unique constraint DDL | VERIFIED | 29 lines. BEGIN/COMMIT transaction. 2 DISTINCT ON dedup DELETEs. 2 ADD CONSTRAINT statements. |
|
||||
| `supabase/migrations/007_setup_completed.sql` | ALTER TABLE + backfill UPDATE | VERIFIED | 18 lines. ADD COLUMN setup_completed boolean NOT NULL DEFAULT false. UPDATE with UNION backfill. |
|
||||
| `src/lib/types.ts` | Profile interface with setup_completed | VERIFIED | Line 16: `setup_completed: boolean` present in Profile interface. |
|
||||
| `src/data/presets.ts` | PresetItem interface + PRESETS array | VERIFIED | Exports PresetItem interface and PRESETS array with 19 items. No one_off tier values. Pure static module. |
|
||||
| `src/i18n/en.json` | English preset translations | VERIFIED | Top-level `presets` key with 6 type groups, 19 total slugs. Valid JSON. |
|
||||
| `src/i18n/de.json` | German preset translations | VERIFIED | Top-level `presets` key with 6 type groups, 19 total slugs. Valid JSON. |
|
||||
| `src/hooks/useFirstRunState.ts` | Derived first-run state hook | VERIFIED | 28 lines. Exports `useFirstRunState()` returning `{ isFirstRun, loading }`. No direct Supabase calls. |
|
||||
|
||||
### Key Link Verification
|
||||
|
||||
| From | To | Via | Status | Details |
|
||||
|------|----|-----|--------|---------|
|
||||
| `007_setup_completed.sql` | profiles table | ALTER TABLE ADD COLUMN | WIRED | `setup_completed boolean NOT NULL DEFAULT false` present |
|
||||
| `src/lib/types.ts` | Profile interface | TypeScript field | WIRED | `setup_completed: boolean` on line 16 |
|
||||
| `src/data/presets.ts` | `src/lib/types.ts` | `import type { CategoryType }` | WIRED | Line 1 imports CategoryType from types |
|
||||
| `src/i18n/en.json` | presets.{type}.{slug} | react-i18next dot-path | WIRED | `"presets"` key present with nested type/slug structure |
|
||||
| `src/hooks/useFirstRunState.ts` | `src/hooks/useCategories.ts` | useCategories() call | WIRED | Line 1 import + line 21 invocation |
|
||||
| `src/hooks/useFirstRunState.ts` | `src/hooks/useTemplate.ts` | useTemplate() call | WIRED | Line 2 import + line 22 invocation |
|
||||
|
||||
### Data-Flow Trace (Level 4)
|
||||
|
||||
| Artifact | Data Variable | Source | Produces Real Data | Status |
|
||||
|----------|--------------|--------|-------------------|--------|
|
||||
| `src/hooks/useFirstRunState.ts` | categories, items | useCategories() cache, useTemplate() cache | Yes -- upstream hooks query Supabase | FLOWING |
|
||||
| `src/data/presets.ts` | PRESETS | Static array literal | Yes -- 19 hardcoded items (intentionally static) | FLOWING |
|
||||
|
||||
### Behavioral Spot-Checks
|
||||
|
||||
| Behavior | Command | Result | Status |
|
||||
|----------|---------|--------|--------|
|
||||
| TypeScript compiles cleanly | `npx tsc --noEmit` | Zero errors (no output) | PASS |
|
||||
| Migration 006 has 2 constraints | `grep -c "ADD CONSTRAINT" 006_uniqueness_constraints.sql` | 2 | PASS |
|
||||
| Migration 006 is transactional | `grep -c "BEGIN" 006_uniqueness_constraints.sql` | 1 | PASS |
|
||||
| Migration 007 has column add | `grep "ADD COLUMN setup_completed" 007_setup_completed.sql` | 1 match | PASS |
|
||||
| Migration 007 has backfill | `grep "UPDATE profiles" 007_setup_completed.sql` | 1 match | PASS |
|
||||
| Presets has 19 items | `grep -c '{ slug:' src/data/presets.ts` | 19 | PASS |
|
||||
| No one_off in presets | `grep "one_off" src/data/presets.ts` | 0 matches | PASS |
|
||||
| en.json has 19 preset slugs | node JSON parse + count | 19 | PASS |
|
||||
| de.json has 19 preset slugs | node JSON parse + count | 19 | PASS |
|
||||
| useFirstRunState exports function | `grep "export function useFirstRunState"` | 1 match | PASS |
|
||||
|
||||
### Requirements Coverage
|
||||
|
||||
| Requirement | Source Plan | Description | Status | Evidence |
|
||||
|-------------|-----------|-------------|--------|----------|
|
||||
| AUTO-01 | 06-01, 06-03 | Auto-budget uses template on first month visit | SATISFIED (Phase 6 portion) | Budgets unique constraint prevents duplicates; useFirstRunState detects first-run. Full auto-creation in Phase 8. |
|
||||
| AUTO-03 | 06-01, 06-02 | Auto-creation uses user's configured currency | SATISFIED (Phase 6 portion) | Preset amounts are plain EUR numbers with no currency symbol. Profile has currency field. Full currency usage in Phase 8. |
|
||||
| SETUP-01 | 06-01, 06-03 | New user guided through wizard | SATISFIED (Phase 6 portion) | setup_completed column + backfill identifies existing users. useFirstRunState hook detects new users. Wizard UI in Phase 7. |
|
||||
| SETUP-02 | 06-02 | User sees pre-filled common budget items | SATISFIED (Phase 6 portion) | 19 preset items in PRESETS array with en/de translations. Wizard display in Phase 7. |
|
||||
|
||||
No orphaned requirements found -- all 4 IDs (AUTO-01, AUTO-03, SETUP-01, SETUP-02) appear in plan frontmatter and are traced to REQUIREMENTS.md.
|
||||
|
||||
### Anti-Patterns Found
|
||||
|
||||
| File | Line | Pattern | Severity | Impact |
|
||||
|------|------|---------|----------|--------|
|
||||
| (none) | - | - | - | No anti-patterns detected in any phase 6 artifact |
|
||||
|
||||
### Human Verification Required
|
||||
|
||||
### 1. Budget Unique Constraint Enforcement
|
||||
|
||||
**Test:** In Supabase SQL editor, INSERT two budget rows with the same `user_id` and `start_date`.
|
||||
**Expected:** Second INSERT fails with error code 23505 (unique_violation).
|
||||
**Why human:** Requires running Supabase instance with live DB. Note: User already confirmed DB push succeeded, but runtime constraint rejection is a behavioral check.
|
||||
|
||||
### 2. Category Unique Constraint Enforcement
|
||||
|
||||
**Test:** In Supabase SQL editor, INSERT two category rows with the same `user_id` and `name`.
|
||||
**Expected:** Second INSERT fails with error code 23505 (unique_violation).
|
||||
**Why human:** Requires running Supabase instance with live DB.
|
||||
|
||||
### 3. Backfill Correctness for Existing Users
|
||||
|
||||
**Test:** In Supabase Table Editor, check `profiles` rows for users who existed before migration 007.
|
||||
**Expected:** Any user with existing categories or template items has `setup_completed = true`. New test users have `setup_completed = false`.
|
||||
**Why human:** Requires inspecting live database state.
|
||||
|
||||
### Gaps Summary
|
||||
|
||||
No gaps found. All 5 roadmap success criteria are verified at the code/artifact level. Two artifacts (useFirstRunState hook and PRESETS array) are intentionally orphaned -- they are consumed by Phase 7 (Setup Wizard), which is the next phase.
|
||||
|
||||
Three items require human verification against the live database: budget constraint enforcement, category constraint enforcement, and backfill correctness. These are behavioral checks that cannot be verified by static code analysis alone.
|
||||
|
||||
---
|
||||
|
||||
_Verified: 2026-04-20T19:15:00Z_
|
||||
_Verifier: Claude (gsd-verifier)_
|
||||
Reference in New Issue
Block a user