diff --git a/.planning/phases/02-layout-and-brand-identity/02-RESEARCH.md b/.planning/phases/02-layout-and-brand-identity/02-RESEARCH.md new file mode 100644 index 0000000..3ddab9f --- /dev/null +++ b/.planning/phases/02-layout-and-brand-identity/02-RESEARCH.md @@ -0,0 +1,440 @@ +# Phase 2: Layout and Brand Identity - Research + +**Researched:** 2026-03-11 +**Domain:** React UI polish — auth screen branding, shadcn sidebar customization, Tailwind CSS v4 token-based styling +**Confidence:** HIGH + +--- + + +## Phase Requirements + +| ID | Description | Research Support | +|----|-------------|-----------------| +| AUTH-01 | Login screen has a branded pastel gradient background (not plain white) | Replace `bg-background` wrapper with gradient using palette tokens; full-bleed gradient div wrapping the Card | +| AUTH-02 | Login screen displays a styled app wordmark/logo treatment | Replace plain `CardTitle` text with a styled typographic mark using font weight + letter-spacing or gradient text fill | +| AUTH-03 | Register screen matches login screen's branded look | Same structural changes as AUTH-01/02 applied to `RegisterPage.tsx` | +| AUTH-04 | Auth form errors display with styled alert blocks and error icons | Install shadcn `alert` component; replace `

` with `` | +| NAV-01 | Sidebar has a pastel background color distinct from the main content area | `--sidebar` token already set to `oklch(0.97 0.012 280)` in index.css; sidebar.tsx uses `bg-sidebar` natively — need to verify contrast reads in browser | +| NAV-02 | Sidebar app name has a branded typographic treatment (not plain h2) | Replace `

` with richer markup: gradient text, tracking-wide, or a custom wordmark span | +| NAV-03 | Active navigation item has a clearly visible color indicator using sidebar-primary token | `SidebarMenuButton` receives `isActive` prop — need to verify the `isActive` styling is visible; may need to override via className | +| NAV-04 | Sidebar is collapsible via a toggle button for smaller screens | `SidebarTrigger` component already exists and exported from `ui/sidebar.tsx`; needs to be placed in the layout (currently missing from `AppLayout.tsx`) | + + +--- + +## Summary + +Phase 2 focuses on two surface areas: the auth screens (LoginPage, RegisterPage) and the app shell (AppLayout with the shadcn Sidebar). Both are self-contained files with minimal external dependencies, so the risk profile is low. + +The auth screens currently render a plain white Card centered on `bg-background`. Both pages use the same pattern and can be updated in parallel. The wordmark treatment (AUTH-02) is pure CSS — no new libraries needed, just a styled span with gradient text or tracked lettering using existing font/color tokens. The error display (AUTH-04) requires installing the shadcn `alert` component, which does not exist in `frontend/src/components/ui/` yet. + +The sidebar is already fully wired with the shadcn Sidebar primitives including the `--sidebar` CSS token (set to a distinct pastel `oklch(0.97 0.012 280)`). The main gap is NAV-04: `SidebarTrigger` is exported from `ui/sidebar.tsx` but is not rendered anywhere in `AppLayout.tsx`. The active-state indicator (NAV-03) is passed via `isActive` to `SidebarMenuButton` — the shadcn component has built-in active styling, but visual verification is needed since the token values may produce low contrast. + +**Primary recommendation:** Add shadcn Alert, apply gradient backgrounds to auth pages, add `SidebarTrigger` to AppLayout, and style the sidebar wordmark using CSS gradient text — all within existing files with no new routing or data fetching. + +--- + +## Standard Stack + +### Core +| Library | Version | Purpose | Why Standard | +|---------|---------|---------|--------------| +| shadcn/ui | 4.0.0 (installed) | UI component primitives via CSS variables | Project constraint: all UI via shadcn | +| Tailwind CSS v4 | 4.2.1 (installed) | Utility classes; `@theme inline` exposes CSS vars as utilities | Project constraint | +| lucide-react | 0.577.0 (installed) | Icon set including `AlertCircle`, `PanelLeft` | Already used throughout | +| tw-animate-css | 1.4.0 (installed) | Animation utilities (for any transitions) | Out-of-scope alternative to Framer Motion | + +### Supporting +| Library | Version | Purpose | When to Use | +|---------|---------|---------|-------------| +| class-variance-authority | 0.7.1 (installed) | Variant-based className logic | If extracting a reusable wordmark component | +| clsx + tailwind-merge | installed | Conditional class merging | Standard in this project via `cn()` utility | + +### Not Needed for This Phase +- No new npm installs required except `npx shadcn add alert` (fetches from registry, not a new npm dep) +- No animation library installs — tw-animate-css handles any needed transitions +- No routing changes + +**Installation:** +```bash +cd frontend && bunx --bun shadcn add alert +``` + +--- + +## Architecture Patterns + +### Recommended File Scope + +All work is confined to these existing files: +``` +frontend/src/ +├── pages/ +│ ├── LoginPage.tsx # AUTH-01, AUTH-02, AUTH-04 +│ └── RegisterPage.tsx # AUTH-03, AUTH-04 +├── components/ +│ ├── AppLayout.tsx # NAV-01, NAV-02, NAV-03, NAV-04 +│ └── ui/ +│ └── alert.tsx # NEW — installed via shadcn CLI +└── index.css # No changes needed (tokens already set) +``` + +### Pattern 1: Gradient Background Wrapper (AUTH-01, AUTH-03) + +**What:** Replace the `bg-background` wrapper div on auth pages with a full-bleed gradient using existing palette tokens via inline style or Tailwind arbitrary values. + +**When to use:** Full-screen auth layouts where background IS the brand statement. + +**Example:** +```tsx +// Replace: +
+ +// With (using CSS custom properties from index.css): +
+``` + +The gradient values should pull from the `palette.ts` saving/bill/investment light shades to stay within the established pastel family. Alternatively, add a dedicated `--auth-gradient` CSS custom property to `index.css` and reference it with a Tailwind arbitrary value `bg-[var(--auth-gradient)]`. + +### Pattern 2: Styled Wordmark (AUTH-02, NAV-02) + +**What:** A CSS gradient text treatment using `background-clip: text` — a standard technique with full browser support. + +**When to use:** When the app name needs brand identity treatment without SVG or image assets. + +**Example:** +```tsx +// Auth page wordmark (inside CardHeader, replacing or supplementing CardDescription) + + Budget Dashboard + + +// Sidebar app name (replacing

) + + {t('app.title')} + +``` + +The gradient values come from `--primary` (`oklch(0.50 0.12 260)`) shifted slightly in hue — no new tokens needed. + +### Pattern 3: shadcn Alert for Error Display (AUTH-04) + +**What:** Replace `

` with a structured Alert component. + +**When to use:** Form-level errors that need icon + message treatment. + +**Example:** +```tsx +// After: bunx --bun shadcn add alert +import { Alert, AlertDescription } from '@/components/ui/alert' +import { AlertCircle } from 'lucide-react' + +// In LoginPage and RegisterPage CardContent: +{error && ( + + + {error} + +)} +``` + +The shadcn `alert` component uses `--destructive` and `--destructive-foreground` tokens which are already defined in `index.css`. + +### Pattern 4: SidebarTrigger Placement (NAV-04) + +**What:** Add a visible toggle button that calls `toggleSidebar()` from the sidebar context. `SidebarTrigger` is already implemented and exported from `ui/sidebar.tsx`. + +**When to use:** The current `AppLayout.tsx` renders no trigger — collapsing is only possible via the keyboard shortcut `Ctrl+B`. + +**Example:** +```tsx +import { SidebarTrigger, /* existing imports */ } from '@/components/ui/sidebar' + +// In AppLayout, inside SidebarInset, add a header bar: + +

+ +
+
{children}
+ +``` + +The `SidebarTrigger` renders a `PanelLeftIcon` button and calls `toggleSidebar()` internally. The current `collapsible` prop on `` defaults to `"offcanvas"`, which means on mobile it slides in as a Sheet and on desktop it shifts the content. This behavior is already fully implemented in `sidebar.tsx`. + +### Pattern 5: Active Nav Indicator (NAV-03) + +**What:** Verify and if needed, reinforce the `isActive` visual state on `SidebarMenuButton`. + +**Current state:** `AppLayout.tsx` already passes `isActive={location.pathname === item.path}` to `SidebarMenuButton`. The shadcn sidebar component applies `data-active` attribute and styles active items with `bg-sidebar-accent` and `text-sidebar-accent-foreground` by default. + +**Potential gap:** The current `--sidebar-accent` is `oklch(0.93 0.020 280)` against `--sidebar` of `oklch(0.97 0.012 280)` — that is a 4-point lightness difference. This may render as visually insufficient contrast. If so, the fix is to update `SidebarMenuButton` via a `className` override or adjust `--sidebar-accent` in index.css to use `--sidebar-primary` for the active state. + +**Verification test:** Render in browser, click nav items, confirm visible selection change. If not visible, apply: +```tsx + +``` + +### Anti-Patterns to Avoid + +- **Editing `ui/sidebar.tsx` directly:** Project constraint — customize via `className` props or CSS variable overrides only, never edit `src/components/ui/` source files. +- **Hardcoded hex or oklch values in component files:** All colors must come from design tokens (`--primary`, `palette.ts`) or explicit inline style with values from the established token system. +- **Adding `text-green-*` or `text-blue-*` raw classes for wordmark:** Use gradient text via inline style from token values, consistent with Phase 1 decisions. +- **Adding `BrowserRouter` to auth pages:** Auth pages render outside the router (`App.tsx` renders them before `BrowserRouter`). Do not add routing-dependent hooks to `LoginPage` or `RegisterPage`. + +--- + +## Don't Hand-Roll + +| Problem | Don't Build | Use Instead | Why | +|---------|-------------|-------------|-----| +| Styled error alerts | Custom div with icon + border CSS | `shadcn add alert` | Handles destructive variant, screen-reader role, proper token use | +| Sidebar toggle button | Custom button + `useState` for open/closed | `SidebarTrigger` from `ui/sidebar` | Already wired to sidebar context; persists state in cookie | +| Sidebar collapse state | Manual `useState` + CSS width transitions | `SidebarProvider` + `collapsible="icon"` | Full collapse behavior with keyboard shortcut already built | +| Gradient text wordmark | SVG logo | CSS `background-clip: text` with inline style | No asset needed; uses existing tokens; responsive | + +**Key insight:** The shadcn Sidebar component is unusually complete — toggle, collapse, cookie persistence, mobile Sheet, and keyboard shortcut are all pre-built. The only missing piece is exposing `SidebarTrigger` in the rendered layout. + +--- + +## Common Pitfalls + +### Pitfall 1: Alert Component Not Installed +**What goes wrong:** Importing `@/components/ui/alert` fails at build time — the file does not exist yet. +**Why it happens:** shadcn components are opt-in; `alert.tsx` is not in `frontend/src/components/ui/`. +**How to avoid:** Run `bunx --bun shadcn add alert` before writing the import. Verify the file appears at `frontend/src/components/ui/alert.tsx`. +**Warning signs:** TypeScript error `Cannot find module '@/components/ui/alert'`. + +### Pitfall 2: Sidebar Active State Low Contrast +**What goes wrong:** The `isActive` indicator renders but is nearly invisible due to minimal lightness difference between `--sidebar` and `--sidebar-accent`. +**Why it happens:** `--sidebar: oklch(0.97 0.012 280)` vs `--sidebar-accent: oklch(0.93 0.020 280)` — 4 lightness points difference in a low-chroma space. +**How to avoid:** After implementing, visually verify active state. If insufficient, override with `data-[active=true]:bg-sidebar-primary data-[active=true]:text-sidebar-primary-foreground` in the `SidebarMenuButton` className. The `--sidebar-primary` token `oklch(0.50 0.12 260)` provides strong contrast. +**Warning signs:** Clicking nav items produces no perceptible visual change. + +### Pitfall 3: Auth Gradient Feels Jarring +**What goes wrong:** A high-saturation gradient makes the login screen feel loud rather than refined. +**Why it happens:** Pastels require low chroma (0.02–0.06) at high lightness (0.92+). Using chart or header gradient values (medium shades at 0.88 lightness) will appear oversaturated as full-screen backgrounds. +**How to avoid:** Use the `light` shades from `palette.ts` (lightness 0.95–0.97, chroma 0.03–0.04), not `medium` or `base` shades. Keep the gradient subtle — it should feel like tinted paper, not a colorful splash screen. +**Warning signs:** The gradient overwhelms the card form element visually. + +### Pitfall 4: Wordmark Gradient Text Fallback +**What goes wrong:** `WebkitTextFillColor: 'transparent'` with `backgroundClip: 'text'` renders as invisible text in some edge environments. +**Why it happens:** The `-webkit-` prefix technique requires both properties to be set correctly. +**How to avoid:** Always pair `WebkitBackgroundClip: 'text'`, `WebkitTextFillColor: 'transparent'`, AND `backgroundClip: 'text'`. The non-prefixed version is needed for Firefox compatibility. +**Warning signs:** Wordmark text disappears or renders as black. + +### Pitfall 5: SidebarTrigger Outside SidebarProvider +**What goes wrong:** `useSidebar()` throws "useSidebar must be used within a SidebarProvider." +**Why it happens:** `SidebarTrigger` calls `useSidebar()` internally and will throw if placed outside ``. +**How to avoid:** `SidebarTrigger` must be placed inside the `` tree — either inside ``, ``, or any other child. In `AppLayout.tsx` the current structure has both `` and `` inside ``, so placing it in `` is safe. + +### Pitfall 6: RegisterPage Missing from Auth Page Update +**What goes wrong:** AUTH-03 is missed — RegisterPage still renders the plain white card after LoginPage is polished. +**Why it happens:** The two pages are separate files with identical structure; easy to forget to update both. +**How to avoid:** Treat AUTH-01/02/04 and AUTH-03 as one logical task that touches both files simultaneously. Register page should be a near-exact structural mirror of Login page. + +--- + +## Code Examples + +### Full-Screen Gradient Auth Wrapper +```tsx +// Source: palette.ts light shades — keeping within established token system +
+ + {/* ... */} + +
+``` + +### Alert Destructive Error Block +```tsx +// Source: shadcn alert component (installed via bunx shadcn add alert) +import { Alert, AlertDescription } from '@/components/ui/alert' +import { AlertCircle } from 'lucide-react' + +{error && ( + + + {error} + +)} +``` + +### Sidebar Header with Branded Wordmark +```tsx +// Source: index.css --primary token; no new tokens needed + + + {t('app.title')} + + {auth.user && ( +

{auth.user.display_name}

+ )} +
+``` + +### SidebarTrigger in Layout Header +```tsx +// Source: ui/sidebar.tsx — SidebarTrigger already exported +import { SidebarTrigger, /* ... */ } from '@/components/ui/sidebar' + + +
+ +
+
{children}
+
+``` + +### Active Nav Item Override (if default contrast insufficient) +```tsx +// Source: AppLayout.tsx — className addition only, no sidebar.tsx edit + + + + {item.label} + + +``` + +--- + +## State of the Art + +| Old Approach | Current Approach | When Changed | Impact | +|--------------|------------------|--------------|--------| +| Manual sidebar toggle with useState | shadcn Sidebar with SidebarProvider context | shadcn 2.x | Built-in toggle, cookie persistence, keyboard shortcut | +| Tailwind v3 `theme.extend.colors` for custom tokens | CSS custom properties in `:root` + `@theme inline` in Tailwind v4 | Tailwind v4 | Tokens are pure CSS, not Tailwind-config-dependent | +| `lucide-react` named imports | Same — no change | - | Lucide is still the icon standard for shadcn | + +**Deprecated/outdated:** +- `bg-primary` Tailwind class approach for backgrounds: Now use CSS variable direct references (`var(--color-primary)`) or token-based inline styles for gradient backgrounds — Tailwind v4 exposes all custom properties as utilities. + +--- + +## Open Questions + +1. **Active sidebar item contrast adequacy** + - What we know: `--sidebar-accent` is 4 lightness points from `--sidebar`; `SidebarMenuButton` uses `data-[active=true]:bg-sidebar-accent` by default + - What's unclear: Whether this is visually sufficient without browser testing + - Recommendation: Plan task includes a visual verification step; if insufficient, apply the `data-[active=true]:bg-sidebar-primary` className override + +2. **Wordmark: gradient text vs. font-weight/tracking only** + - What we know: Gradient text works cross-browser with correct CSS properties + - What's unclear: Whether the design intent is a text-gradient wordmark or just tracked/bold typography + - Recommendation: Gradient text is the stronger brand treatment; the planner should implement gradient text as the default and treat simpler alternatives as a fallback + +3. **SidebarInset main content padding** + - What we know: Current `AppLayout.tsx` has `
{children}
` with no padding + - What's unclear: Whether adding a header bar with `SidebarTrigger` requires padding adjustments to existing page components + - Recommendation: Add `p-4` to main only if pages do not already manage their own padding; inspect `DashboardPage` to confirm + +--- + +## Validation Architecture + +### Test Framework +| Property | Value | +|----------|-------| +| Framework | Vitest 4.0.18 + @testing-library/react 16.3.2 | +| Config file | `frontend/vite.config.ts` (test section present) | +| Quick run command | `cd frontend && bun vitest run --reporter=verbose` | +| Full suite command | `cd frontend && bun vitest run` | + +### Phase Requirements → Test Map + +| Req ID | Behavior | Test Type | Automated Command | File Exists? | +|--------|----------|-----------|-------------------|--------------| +| AUTH-01 | Login page renders gradient background wrapper | unit | `bun vitest run src/pages/LoginPage.test.tsx` | No — Wave 0 | +| AUTH-02 | Login page renders wordmark with gradient text style | unit | `bun vitest run src/pages/LoginPage.test.tsx` | No — Wave 0 | +| AUTH-03 | Register page renders same gradient/wordmark as login | unit | `bun vitest run src/pages/RegisterPage.test.tsx` | No — Wave 0 | +| AUTH-04 | Error string renders Alert with destructive variant | unit | `bun vitest run src/pages/LoginPage.test.tsx` | No — Wave 0 | +| NAV-01 | Sidebar renders with bg-sidebar class | unit | `bun vitest run src/components/AppLayout.test.tsx` | No — Wave 0 | +| NAV-02 | Sidebar header contains branded wordmark element | unit | `bun vitest run src/components/AppLayout.test.tsx` | No — Wave 0 | +| NAV-03 | Active nav item receives data-active attribute | unit | `bun vitest run src/components/AppLayout.test.tsx` | No — Wave 0 | +| NAV-04 | SidebarTrigger button is rendered in layout | unit | `bun vitest run src/components/AppLayout.test.tsx` | No — Wave 0 | + +**Note on visual requirements:** AUTH-01 (gradient background), AUTH-02 (wordmark appearance), NAV-01 (sidebar color distinction), and NAV-03 (visible color indicator) have a visual correctness dimension that unit tests cannot fully capture. Unit tests verify structural presence (element rendered, class present, inline style set); visual correctness requires browser verification. Plan tasks should include explicit browser check steps alongside automated tests. + +### Sampling Rate +- **Per task commit:** `cd frontend && bun vitest run` +- **Per wave merge:** `cd frontend && bun vitest run && cd frontend && bun run build` +- **Phase gate:** Full suite green + production build green before `/gsd:verify-work` + +### Wave 0 Gaps +- [ ] `frontend/src/pages/LoginPage.test.tsx` — covers AUTH-01, AUTH-02, AUTH-04 +- [ ] `frontend/src/pages/RegisterPage.test.tsx` — covers AUTH-03 +- [ ] `frontend/src/components/AppLayout.test.tsx` — covers NAV-01, NAV-02, NAV-03, NAV-04 +- [ ] `frontend/src/components/ui/alert.tsx` — must exist before any test imports it (install via shadcn CLI) + +--- + +## Sources + +### Primary (HIGH confidence) +- Direct file inspection: `frontend/src/components/AppLayout.tsx` — confirms SidebarTrigger not yet rendered +- Direct file inspection: `frontend/src/pages/LoginPage.tsx` and `RegisterPage.tsx` — confirms plain `bg-background` wrapper and `

` error display +- Direct file inspection: `frontend/src/components/ui/sidebar.tsx` — confirms SidebarTrigger exported, collapsible behavior fully implemented +- Direct file inspection: `frontend/src/index.css` — confirms `--sidebar`, `--sidebar-primary`, `--sidebar-accent` token values +- Direct file inspection: `frontend/src/lib/palette.ts` — confirms light/medium/base shades for gradient construction + +### Secondary (MEDIUM confidence) +- shadcn/ui Alert component documentation pattern — standard destructive variant with AlertCircle icon is the established pattern for form error alerts in shadcn projects + +### Tertiary (LOW confidence) +- None + +--- + +## Metadata + +**Confidence breakdown:** +- Standard stack: HIGH — all libraries verified as installed via package.json; no new npm deps needed beyond `bunx shadcn add alert` +- Architecture: HIGH — all files inspected directly; changes are confined to known files with no new routing +- Pitfalls: HIGH — active state contrast gap identified by reading actual token values; alert install gap confirmed by directory listing + +**Research date:** 2026-03-11 +**Valid until:** 2026-04-10 (stable shadcn/Tailwind ecosystem; 30-day window)