chore: archive v1.0 phase directories

This commit is contained in:
2026-03-24 09:46:00 +01:00
parent 3a771ba7cd
commit 439d0e950d
35 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,410 @@
---
phase: 01-design-foundation-and-primitives
plan: 02
type: execute
wave: 2
depends_on:
- 01-01
files_modified:
- src/components/shared/PageShell.tsx
- src/components/dashboard/StatCard.tsx
- src/components/dashboard/SummaryStrip.tsx
- src/components/dashboard/DashboardSkeleton.tsx
- src/pages/DashboardPage.tsx
autonomous: true
requirements:
- UI-DASH-01
- UI-DESIGN-01
- UI-RESPONSIVE-01
must_haves:
truths:
- "PageShell renders a consistent page header with title, optional description, and optional CTA slot"
- "StatCard renders a KPI card with title, large formatted value, optional semantic color, and optional variance badge with directional icon"
- "SummaryStrip renders 3 StatCards in a responsive grid (1 col mobile, 2 cols tablet, 3 cols desktop)"
- "DashboardSkeleton mirrors the real summary card grid and chart card layout with pulse animations"
- "DashboardPage uses PageShell instead of inline h1 header"
- "DashboardPage uses SummaryStrip instead of inline SummaryCard components"
- "DashboardPage shows DashboardSkeleton during loading instead of returning null"
- "Balance card uses semantic text-on-budget/text-over-budget classes instead of hardcoded text-green-600/text-red-600"
artifacts:
- path: "src/components/shared/PageShell.tsx"
provides: "Consistent page header wrapper"
exports: ["PageShell"]
min_lines: 15
- path: "src/components/dashboard/StatCard.tsx"
provides: "KPI display card with variance badge"
exports: ["StatCard"]
min_lines: 30
- path: "src/components/dashboard/SummaryStrip.tsx"
provides: "Responsive row of 3 StatCards"
exports: ["SummaryStrip"]
min_lines: 20
- path: "src/components/dashboard/DashboardSkeleton.tsx"
provides: "Skeleton loading placeholder for dashboard"
exports: ["DashboardSkeleton"]
min_lines: 20
- path: "src/pages/DashboardPage.tsx"
provides: "Refactored dashboard page using new components"
contains: "PageShell"
key_links:
- from: "src/components/dashboard/SummaryStrip.tsx"
to: "src/components/dashboard/StatCard.tsx"
via: "import and composition"
pattern: "import.*StatCard"
- from: "src/pages/DashboardPage.tsx"
to: "src/components/shared/PageShell.tsx"
via: "import and wrapping"
pattern: "import.*PageShell"
- from: "src/pages/DashboardPage.tsx"
to: "src/components/dashboard/SummaryStrip.tsx"
via: "import replacing inline SummaryCard"
pattern: "import.*SummaryStrip"
- from: "src/pages/DashboardPage.tsx"
to: "src/components/dashboard/DashboardSkeleton.tsx"
via: "import replacing null loading state"
pattern: "import.*DashboardSkeleton"
- from: "src/pages/DashboardPage.tsx"
to: "src/index.css"
via: "semantic token classes"
pattern: "text-(on-budget|over-budget)"
---
<objective>
Build the shared components (PageShell, StatCard, SummaryStrip, DashboardSkeleton) and integrate them into DashboardPage, replacing the inline SummaryCard, null loading state, and hardcoded color classes.
Purpose: Deliver the visual foundation components that all subsequent phases consume. After this plan, the dashboard has semantic KPI cards with variance badges, skeleton loading, and a consistent page header pattern ready for reuse across all 9 pages.
Output: 4 new component files, refactored DashboardPage.tsx.
</objective>
<execution_context>
@/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md
@/home/jlmak/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/01-design-foundation-and-primitives/01-RESEARCH.md
@.planning/phases/01-design-foundation-and-primitives/01-01-SUMMARY.md
@src/pages/DashboardPage.tsx
@src/components/ui/card.tsx
@src/components/ui/badge.tsx
@src/components/ui/skeleton.tsx
@src/lib/format.ts
@src/lib/palette.ts
@src/lib/types.ts
@src/i18n/en.json
<interfaces>
<!-- Key types and contracts the executor needs. Extracted from codebase. -->
From src/lib/types.ts:
```typescript
export type CategoryType = "income" | "bill" | "variable_expense" | "debt" | "saving" | "investment"
```
From src/lib/format.ts:
```typescript
export function formatCurrency(amount: number, currency?: string): string
```
From src/lib/palette.ts:
```typescript
export const categoryColors: Record<CategoryType, string>
```
From src/components/ui/card.tsx:
```typescript
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
```
From src/components/ui/badge.tsx:
```typescript
export { Badge, badgeVariants }
```
From src/components/ui/skeleton.tsx:
```typescript
export { Skeleton }
```
From src/hooks/useBudgets.ts:
```typescript
export function useBudgets(): { budgets: Budget[], loading: boolean, ... }
export function useBudgetDetail(id: string): { budget: Budget | null, items: BudgetItem[], loading: boolean }
```
From existing DashboardPage.tsx (lines 45-66) - the SummaryCard being REPLACED:
```typescript
interface SummaryCardProps {
title: string
value: string
valueClassName?: string
}
function SummaryCard({ title, value, valueClassName }: SummaryCardProps) { ... }
```
CSS tokens available from Plan 01 (src/index.css):
- `text-on-budget` (maps to --color-on-budget)
- `text-over-budget` (maps to --color-over-budget)
- `text-income` (maps to --color-income)
- `text-destructive` (maps to --color-destructive)
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Create PageShell, StatCard, SummaryStrip, and DashboardSkeleton components</name>
<files>src/components/shared/PageShell.tsx, src/components/dashboard/StatCard.tsx, src/components/dashboard/SummaryStrip.tsx, src/components/dashboard/DashboardSkeleton.tsx</files>
<action>
Create 4 new component files. Create directories `src/components/shared/` and `src/components/dashboard/` if they do not exist.
**File 1: src/components/shared/PageShell.tsx**
```tsx
interface PageShellProps {
title: string
description?: string
action?: React.ReactNode
children: React.ReactNode
}
export function PageShell({ title, description, action, children }: PageShellProps) {
return (
<div className="flex flex-col gap-6">
<div className="flex items-start justify-between gap-4">
<div>
<h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
{description && (
<p className="mt-1 text-sm text-muted-foreground">{description}</p>
)}
</div>
{action && <div className="shrink-0">{action}</div>}
</div>
{children}
</div>
)
}
```
Key decisions:
- Named export (not default) per convention for shared components
- `text-2xl font-semibold tracking-tight` matches existing DashboardPage heading
- `action` is a ReactNode slot, not a button-specific prop
- No padding baked in -- AppLayout.tsx already provides `p-6`
- No i18n dependency -- title comes from the caller via `t()` at the page level
**File 2: src/components/dashboard/StatCard.tsx**
Follow the pattern from research (Pattern 2) exactly. Named export `StatCard`.
Props interface:
```typescript
interface StatCardProps {
title: string
value: string
valueClassName?: string
variance?: {
amount: string
direction: "up" | "down" | "neutral"
label: string
}
}
```
Implementation:
- Import `Card`, `CardContent`, `CardHeader`, `CardTitle` from `@/components/ui/card`
- Import `TrendingUp`, `TrendingDown`, `Minus` from `lucide-react`
- Import `cn` from `@/lib/utils`
- Use `text-2xl font-bold tabular-nums tracking-tight` for the value (upgraded from existing `font-semibold` for more visual weight)
- Variance section renders a directional icon (size-3) + amount text + label in `text-xs text-muted-foreground`
- Do NOT import Badge -- the variance display uses inline layout, not a badge component
**File 3: src/components/dashboard/SummaryStrip.tsx**
Follow the pattern from research (Pattern 3). Named export `SummaryStrip`.
Props interface:
```typescript
interface SummaryStripProps {
income: { value: string; budgeted: string }
expenses: { value: string; budgeted: string }
balance: { value: string; isPositive: boolean }
t: (key: string) => string
}
```
Implementation:
- Import `StatCard` from `./StatCard`
- Renders a `<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">` with 3 StatCards
- Income card: `title={t("dashboard.totalIncome")}`, `valueClassName="text-income"`, variance with direction "neutral" and label `t("budgets.budgeted")`
- Expenses card: `title={t("dashboard.totalExpenses")}`, `valueClassName="text-destructive"`, variance with direction "neutral" and label `t("budgets.budgeted")`
- Balance card: `title={t("dashboard.availableBalance")}`, `valueClassName={balance.isPositive ? "text-on-budget" : "text-over-budget"}`, no variance prop
Note: The `t` function is passed as a prop to keep SummaryStrip as a presentational component that does not call `useTranslation()` internally. The parent (DashboardContent) already has `t` from `useTranslation()`.
**File 4: src/components/dashboard/DashboardSkeleton.tsx**
Follow the pattern from research (Pattern 4). Named export `DashboardSkeleton`.
Implementation:
- Import `Skeleton` from `@/components/ui/skeleton`
- Import `Card`, `CardContent`, `CardHeader` from `@/components/ui/card`
- Renders a `<div className="flex flex-col gap-6">` with:
1. Summary cards skeleton: `<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">` with 3 skeleton cards matching StatCard layout (Skeleton h-4 w-24 for title, Skeleton h-8 w-32 for value, Skeleton h-3 w-20 for variance)
2. Chart area skeleton: `<div className="grid gap-6 lg:grid-cols-2">` with 2 skeleton cards (Skeleton h-5 w-40 for chart title, Skeleton h-[240px] w-full rounded-md for chart area)
This mirrors the real dashboard grid exactly so there is no layout shift when data loads.
All 4 files use named exports. Follow import order convention: React first, third-party, internal types, internal utilities, components.
</action>
<verify>
<automated>npm run build</automated>
</verify>
<done>All 4 component files exist, export the correct named exports, follow project conventions, and build passes. PageShell accepts title/description/action/children. StatCard accepts title/value/valueClassName/variance. SummaryStrip renders 3 StatCards in responsive grid with semantic color classes. DashboardSkeleton mirrors the real layout structure.</done>
</task>
<task type="auto">
<name>Task 2: Integrate new components into DashboardPage</name>
<files>src/pages/DashboardPage.tsx</files>
<action>
Refactor `src/pages/DashboardPage.tsx` to use the new shared components. This is a MODIFY operation -- preserve all existing logic (derived totals, pie chart, progress groups) while replacing the presentation layer.
**Changes to make:**
1. **Remove the inline SummaryCard component** (lines 45-66). Delete the entire `SummaryCardProps` interface and `SummaryCard` function. These are replaced by `StatCard`/`SummaryStrip`.
2. **Add new imports** at the appropriate positions in the import order:
```typescript
import { PageShell } from "@/components/shared/PageShell"
import { SummaryStrip } from "@/components/dashboard/SummaryStrip"
import { DashboardSkeleton } from "@/components/dashboard/DashboardSkeleton"
```
3. **Replace loading states with DashboardSkeleton:**
- In `DashboardContent`: Replace `if (loading) return null` (line 76) with `if (loading) return <DashboardSkeleton />`
- In `DashboardPage`: Replace `if (loading) return null` (line 291) with:
```tsx
if (loading) return (
<PageShell title={t("dashboard.title")}>
<DashboardSkeleton />
</PageShell>
)
```
4. **Replace hardcoded balance color** (lines 95-98):
- BEFORE: `const balanceColor = availableBalance >= 0 ? "text-green-600 dark:text-green-400" : "text-red-600 dark:text-red-400"`
- AFTER: `const balanceColor = availableBalance >= 0 ? "text-on-budget" : "text-over-budget"`
5. **Replace hardcoded progress bar colors** (lines 219-221):
- BEFORE: `const barColor = group.overBudget ? "bg-red-500 dark:bg-red-400" : "bg-green-500 dark:bg-green-400"`
- AFTER: `const barColor = group.overBudget ? "bg-over-budget" : "bg-on-budget"`
6. **Replace hardcoded progress text color** (lines 235-239):
- BEFORE: `group.overBudget ? "text-red-600 dark:text-red-400" : "text-muted-foreground"`
- AFTER: `group.overBudget ? "text-over-budget" : "text-muted-foreground"`
7. **Replace inline summary cards with SummaryStrip** in DashboardContent's return JSX. Replace the `<div className="grid gap-4 sm:grid-cols-3">` block (lines 135-149) with:
```tsx
<SummaryStrip
income={{
value: formatCurrency(totalIncome, currency),
budgeted: formatCurrency(
items.filter((i) => i.category?.type === "income").reduce((sum, i) => sum + i.budgeted_amount, 0),
currency
),
}}
expenses={{
value: formatCurrency(totalExpenses, currency),
budgeted: formatCurrency(
items.filter((i) => i.category?.type !== "income").reduce((sum, i) => sum + i.budgeted_amount, 0),
currency
),
}}
balance={{
value: formatCurrency(availableBalance, currency),
isPositive: availableBalance >= 0,
}}
t={t}
/>
```
To avoid recomputing budgeted totals inline, derive them alongside the existing totalIncome/totalExpenses calculations:
```typescript
const budgetedIncome = items
.filter((i) => i.category?.type === "income")
.reduce((sum, i) => sum + i.budgeted_amount, 0)
const budgetedExpenses = items
.filter((i) => i.category?.type !== "income")
.reduce((sum, i) => sum + i.budgeted_amount, 0)
```
8. **Replace the page header with PageShell** in the `DashboardPage` component's return. Replace:
```tsx
<div>
<div className="mb-6 flex items-center justify-between">
<h1 className="text-2xl font-semibold">{t("dashboard.title")}</h1>
</div>
{/* content */}
</div>
```
With:
```tsx
<PageShell title={t("dashboard.title")}>
{/* content */}
</PageShell>
```
**What to preserve:**
- All imports for Recharts (PieChart, Pie, Cell, ResponsiveContainer, Tooltip)
- The `EXPENSE_TYPES` constant
- The `currentMonthStart` helper
- The `DashboardContent` component structure (budgetId prop, hooks, derived totals, pie chart, progress groups)
- The `QuickAddPicker` usage
- The entire pie chart + legend section
- The entire category progress section (but with updated color classes)
- The no-budget empty state with Link to /budgets
**What to remove:**
- The `SummaryCardProps` interface and `SummaryCard` function component
- The hardcoded `text-green-600`, `text-red-600`, `bg-red-500`, `bg-green-500` color classes
- The `if (loading) return null` patterns (both in DashboardContent and DashboardPage)
- The inline `<div className="mb-6 flex items-center justify-between">` header
</action>
<verify>
<automated>npm run build && npm run lint</automated>
</verify>
<done>DashboardPage imports and uses PageShell, SummaryStrip, and DashboardSkeleton. No more inline SummaryCard component. Loading states show skeleton instead of null. All hardcoded green/red color classes replaced with semantic token classes (text-on-budget, text-over-budget, bg-on-budget, bg-over-budget). Build and lint pass.</done>
</task>
</tasks>
<verification>
1. `npm run build && npm run lint` passes
2. `src/components/shared/PageShell.tsx` exports `PageShell`
3. `src/components/dashboard/StatCard.tsx` exports `StatCard`
4. `src/components/dashboard/SummaryStrip.tsx` exports `SummaryStrip` and imports `StatCard`
5. `src/components/dashboard/DashboardSkeleton.tsx` exports `DashboardSkeleton`
6. `src/pages/DashboardPage.tsx` imports PageShell, SummaryStrip, DashboardSkeleton
7. No occurrences of `text-green-600`, `text-red-600`, `bg-red-500`, `bg-green-500` remain in DashboardPage.tsx
8. No occurrences of `SummaryCard` remain in DashboardPage.tsx
9. No `return null` for loading states in DashboardPage.tsx
</verification>
<success_criteria>
- All 4 new component files exist and are well-typed
- DashboardPage uses PageShell for header, SummaryStrip for KPI cards, DashboardSkeleton for loading
- Zero hardcoded green/red color values in DashboardPage
- Build and lint pass cleanly
- Summary cards display in responsive grid (1/2/3 columns by breakpoint)
</success_criteria>
<output>
After completion, create `.planning/phases/01-design-foundation-and-primitives/01-02-SUMMARY.md`
</output>