Files
SimpleFinanceDash/.planning/phases/02-layout-and-brand-identity/02-02-PLAN.md

206 lines
9.5 KiB
Markdown

---
phase: 02-layout-and-brand-identity
plan: 02
type: execute
wave: 1
depends_on: []
files_modified:
- frontend/src/components/AppLayout.tsx
- frontend/src/components/AppLayout.test.tsx
autonomous: true
requirements: [NAV-01, NAV-02, NAV-03, NAV-04]
must_haves:
truths:
- "Sidebar has a visually distinct pastel background from the main content area"
- "Sidebar app name displays as a branded gradient text wordmark"
- "Clicking a nav item produces a clearly visible active state indicator"
- "A toggle button exists to collapse/expand the sidebar"
artifacts:
- path: "frontend/src/components/AppLayout.tsx"
provides: "Branded sidebar with wordmark, active indicator, collapse trigger"
contains: "SidebarTrigger"
- path: "frontend/src/components/AppLayout.test.tsx"
provides: "Unit tests for NAV-01 through NAV-04"
key_links:
- from: "frontend/src/components/AppLayout.tsx"
to: "frontend/src/components/ui/sidebar.tsx"
via: "SidebarTrigger import for collapse toggle"
pattern: "SidebarTrigger"
- from: "frontend/src/components/AppLayout.tsx"
to: "SidebarMenuButton isActive"
via: "data-active styling override for visible indicator"
pattern: "data-\\[active=true\\]"
---
<objective>
Polish the sidebar shell -- branded wordmark, visible active nav indicator, and collapse toggle -- so every authenticated page load feels intentionally designed.
Purpose: The sidebar is visible on every page after login. Its polish directly determines the perceived quality of the entire app.
Output: Updated AppLayout.tsx with branded sidebar and passing unit tests.
</objective>
<execution_context>
@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md
@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/02-layout-and-brand-identity/02-RESEARCH.md
@.planning/phases/02-layout-and-brand-identity/02-VALIDATION.md
@frontend/src/components/AppLayout.tsx
<interfaces>
<!-- Current AppLayout structure the executor will modify -->
From frontend/src/components/AppLayout.tsx:
```typescript
interface Props {
auth: AuthContext
children: React.ReactNode
}
// Uses: useTranslation, useLocation from react-router-dom
// Imports from ui/sidebar: Sidebar, SidebarContent, SidebarFooter, SidebarGroup,
// SidebarGroupContent, SidebarHeader, SidebarMenu, SidebarMenuButton,
// SidebarMenuItem, SidebarProvider, SidebarInset
// NOTE: SidebarTrigger is NOT currently imported
// Current app name: <h2 className="text-lg font-semibold">{t('app.title')}</h2>
// Current active state: isActive={location.pathname === item.path} (no className override)
// Current SidebarInset: <main className="flex-1">{children}</main> (no header bar)
```
From frontend/src/index.css (sidebar token values):
```css
--sidebar: oklch(0.97 0.012 280); /* light lavender background */
--sidebar-primary: oklch(0.50 0.12 260); /* strong accent for active state */
--sidebar-primary-foreground: oklch(0.99 0.005 290);
--sidebar-accent: oklch(0.93 0.020 280); /* 4-point lightness diff from sidebar - may be too subtle */
--primary: oklch(0.50 0.12 260); /* for wordmark gradient start */
```
From frontend/src/components/ui/sidebar.tsx (already exported):
```typescript
export { SidebarTrigger } // renders PanelLeftIcon button, calls toggleSidebar()
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Create AppLayout test scaffold</name>
<files>frontend/src/components/AppLayout.test.tsx</files>
<action>
Create `frontend/src/components/AppLayout.test.tsx` with test cases for all four NAV requirements.
Test setup:
- Mock `react-i18next`: `vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => key }) }))`
- Mock `react-router-dom`: `vi.mock('react-router-dom', () => ({ Link: ({ children, to, ...props }: any) => <a href={to} {...props}>{children}</a>, useLocation: () => ({ pathname: '/' }) }))`
- Create mock auth: `{ user: { display_name: 'Test' }, loading: false, login: vi.fn(), register: vi.fn(), logout: vi.fn(), token: 'test' } as unknown as AuthContext`
- Render with: `render(<AppLayout auth={mockAuth}><div>content</div></AppLayout>)`
Note: SidebarProvider uses ResizeObserver internally. Add to test file top:
```typescript
// Mock ResizeObserver for sidebar tests
globalThis.ResizeObserver = class { observe() {} unobserve() {} disconnect() {} } as any
```
Test cases:
- NAV-01: The sidebar element renders (verify `<aside>` or element with `data-sidebar="sidebar"` exists). The `--sidebar` token is already applied by the shadcn sidebar component via `bg-sidebar` -- this test confirms the sidebar renders, and the visual distinction comes from the token value in index.css.
- NAV-02: An element with `data-testid="sidebar-wordmark"` exists inside the sidebar header (gradient wordmark).
- NAV-03: The first nav item (dashboard, matching pathname `/`) has `data-active="true"` attribute on its SidebarMenuButton. Verify with `screen.getByRole('link', { name: /nav.dashboard/i })` and check closest `[data-active]` parent.
- NAV-04: A button with accessible name matching the SidebarTrigger (look for a button containing the PanelLeft icon, or query `screen.getByRole('button', { name: /toggle sidebar/i })` -- the SidebarTrigger renders with `sr-only` text "Toggle Sidebar").
These tests verify structural presence. Visual correctness (contrast, color distinction) will be verified in the checkpoint.
</action>
<verify>
<automated>cd /home/jean-luc-makiola/Development/projects/SimpleFinanceDash/frontend && bun vitest run src/components/AppLayout.test.tsx --reporter=verbose 2>&1 | tail -20</automated>
</verify>
<done>AppLayout.test.tsx exists with 4 test cases. Tests run (expected: most FAIL since features not yet implemented).</done>
</task>
<task type="auto">
<name>Task 2: Brand sidebar with wordmark, active indicator override, and collapse trigger</name>
<files>frontend/src/components/AppLayout.tsx</files>
<action>
Apply all four NAV requirements to AppLayout.tsx.
1. Add `SidebarTrigger` to imports from `@/components/ui/sidebar` (NAV-04).
2. Replace the plain `<h2>` app name in SidebarHeader with a gradient text wordmark (NAV-02):
```tsx
<SidebarHeader className="border-b px-4 py-3">
<span
data-testid="sidebar-wordmark"
className="text-base font-semibold tracking-wide"
style={{
background: `linear-gradient(to right, oklch(0.50 0.12 260), oklch(0.50 0.12 300))`,
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
backgroundClip: 'text',
}}
>
{t('app.title')}
</span>
{auth.user && (
<p className="text-sm text-muted-foreground">{auth.user.display_name}</p>
)}
</SidebarHeader>
```
The gradient starts at --primary hue (260) and shifts to hue 300 for a subtle purple-to-pink sweep.
3. Add a className override to SidebarMenuButton for a stronger active state indicator (NAV-03):
```tsx
<SidebarMenuButton
asChild
isActive={location.pathname === item.path}
className="data-[active=true]:bg-sidebar-primary data-[active=true]:text-sidebar-primary-foreground"
>
```
This overrides the default `bg-sidebar-accent` (only 4 lightness points difference, nearly invisible) with `bg-sidebar-primary` (oklch 0.50 vs 0.97 -- very visible).
4. Add a header bar with SidebarTrigger inside SidebarInset (NAV-04):
```tsx
<SidebarInset>
<header className="flex h-12 shrink-0 items-center gap-2 border-b px-4">
<SidebarTrigger className="-ml-1" />
</header>
<main className="flex-1 p-4">{children}</main>
</SidebarInset>
```
The SidebarTrigger is placed inside SidebarInset (which is inside SidebarProvider), so the `useSidebar()` hook call is safe. Added `p-4` to main for consistent content padding.
5. NAV-01 is already satisfied by the existing `--sidebar: oklch(0.97 0.012 280)` token in index.css -- the sidebar renders with `bg-sidebar` natively. No code change needed; just verify it works via tests and visual check.
Do NOT edit `frontend/src/components/ui/sidebar.tsx` -- project constraint prohibits editing shadcn ui source files. All customization is via className props and CSS variable overrides.
</action>
<verify>
<automated>cd /home/jean-luc-makiola/Development/projects/SimpleFinanceDash/frontend && bun vitest run src/components/AppLayout.test.tsx --reporter=verbose && bun run build 2>&1 | tail -5</automated>
</verify>
<done>All 4 AppLayout test cases pass. Production build succeeds. Sidebar renders gradient wordmark, strong active indicator using sidebar-primary, and a visible SidebarTrigger collapse button in the content header.</done>
</task>
</tasks>
<verification>
- `cd frontend && bun vitest run src/components/AppLayout.test.tsx --reporter=verbose` -- all tests green
- `cd frontend && bun run build` -- zero errors
- `cd frontend && bun vitest run` -- full suite green (no regressions)
</verification>
<success_criteria>
- Sidebar renders with bg-sidebar pastel background distinct from main content
- App name in sidebar header is a gradient text wordmark, not a plain h2
- Active nav item shows bg-sidebar-primary with high contrast (not the subtle default accent)
- SidebarTrigger button is rendered in a header bar within SidebarInset
- All new and existing tests pass, production build succeeds
</success_criteria>
<output>
After completion, create `.planning/phases/02-layout-and-brand-identity/02-layout-and-brand-identity-02-02-SUMMARY.md`
</output>