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

9.5 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
02-layout-and-brand-identity 02 execute 1
frontend/src/components/AppLayout.tsx
frontend/src/components/AppLayout.test.tsx
true
NAV-01
NAV-02
NAV-03
NAV-04
truths artifacts key_links
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
path provides contains
frontend/src/components/AppLayout.tsx Branded sidebar with wordmark, active indicator, collapse trigger SidebarTrigger
path provides
frontend/src/components/AppLayout.test.tsx Unit tests for NAV-01 through NAV-04
from to via pattern
frontend/src/components/AppLayout.tsx frontend/src/components/ui/sidebar.tsx SidebarTrigger import for collapse toggle SidebarTrigger
from to via pattern
frontend/src/components/AppLayout.tsx SidebarMenuButton isActive data-active styling override for visible indicator data-[active=true]
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.

<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>

@.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

From frontend/src/components/AppLayout.tsx:

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):

--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):

export { SidebarTrigger } // renders PanelLeftIcon button, calls toggleSidebar()
Task 1: Create AppLayout test scaffold frontend/src/components/AppLayout.test.tsx 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:

// 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. cd /home/jean-luc-makiola/Development/projects/SimpleFinanceDash/frontend && bun vitest run src/components/AppLayout.test.tsx --reporter=verbose 2>&1 | tail -20 AppLayout.test.tsx exists with 4 test cases. Tests run (expected: most FAIL since features not yet implemented).

Task 2: Brand sidebar with wordmark, active indicator override, and collapse trigger frontend/src/components/AppLayout.tsx 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):

    <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):

    <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):

    <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. 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 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.

- `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)

<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>
After completion, create `.planning/phases/02-layout-and-brand-identity/02-layout-and-brand-identity-02-02-SUMMARY.md`