docs(ui): Add UI layout refactor plan and OpenSpec changes

- Update decision-log with UI layout decisions (Feb 18, 2026)
- Update architecture with frontend layout patterns
- Update config.yaml with TDD, documentation, UI standards rules
- Create p00-api-documentation change (Scribe annotations)
- Create p01-ui-foundation change (types, stores, Lucide)
- Create p02-app-layout change (AppLayout, Sidebar, TopBar)
- Create p03-dashboard-enhancement change (PageHeader, StatCard)
- Create p04-content-patterns change (DataTable, FilterBar)
- Create p05-page-migrations change (page migrations)
- Fix E2E auth tests (11/11 passing)
- Add JWT expiry validation to dashboard guard
This commit is contained in:
2026-02-18 13:03:08 -05:00
parent f935754df4
commit 3e36ea8888
29 changed files with 3341 additions and 59 deletions

View File

@@ -1149,12 +1149,324 @@ sequenceDiagram
---
## Frontend Layout Architecture
**Added:** February 18, 2026
**Status:** Approved for Implementation
### Layout System Overview
The Headroom frontend uses a **sidebar + content** layout pattern optimized for data-dense resource planning workflows.
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ HEADROOM LAYOUT ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┬──────────────────────────────────────────────────────────┐ │
│ │ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ │ 🔍 Search... [Feb 2026 ▼] [+ Add] 👤 User ▼ │ │ │
│ │ │ ├────────────────────────────────────────────────────┤ │ │
│ │ SIDEBAR │ │ Breadcrumbs: Dashboard > Overview │ │ │
│ │ 240px │ ├────────────────────────────────────────────────────┤ │ │
│ │ │ │ │ │ │
│ │ ◀ ▶ │ │ │ │ │
│ │ │ │ │ │ │
│ │ ─────── │ │ MAIN CONTENT AREA │ │ │
│ │ PLANNING│ │ │ │ │
│ │ ─────── │ │ (Tables, Grids, Charts, Forms) │ │ │
│ │ 📊 Dash │ │ │ │ │
│ │ 👥 Team │ │ Full width, minimal padding │ │ │
│ │ 📁 Projs│ │ │ │ │
│ │ 📅 Alloc│ │ │ │ │
│ │ ✅ Actu │ │ │ │ │
│ │ │ │ │ │ │
│ │ ─────── │ │ │ │ │
│ │ REPORTS │ │ │ │ │
│ │ ─────── │ │ │ │ │
│ │ 📈 Forecast │ │ │ │
│ │ 📉 Util │ │ │ │
│ │ 💰 Costs │ │ │ │
│ │ 📋 Variance │ │ │ │
│ │ │ │ │ │ │
│ │ ─────── │ │ │ │ │
│ │ ADMIN* │ │ │ │ │
│ │ ─────── │ │ │ │ │
│ │ ⚙️ Set │ │ │ │ │
│ │ │ │ │ │ │
│ │ ─────── │ │ │ │ │
│ │ 🌙/☀️ │ │ │ │ │
│ └──────────┴──────────────────────────────────────────────────────────┘ │
│ │
│ * Admin section visible only to superuser │
└─────────────────────────────────────────────────────────────────────────────┘
```
### Component Hierarchy
```mermaid
graph TB
subgraph "Route Layer"
Layout["+layout.svelte"]
end
subgraph "Layout Layer"
AppLayout["AppLayout.svelte"]
Sidebar["Sidebar.svelte"]
TopBar["TopBar.svelte"]
Breadcrumbs["Breadcrumbs.svelte"]
end
subgraph "State Layer"
LayoutStore["layout.ts store"]
PeriodStore["period.ts store"]
AuthStore["auth.ts store"]
end
subgraph "Page Layer"
PageContent["<slot /> Page Content"]
PageHeader["PageHeader.svelte"]
end
Layout --> AppLayout
AppLayout --> Sidebar
AppLayout --> TopBar
AppLayout --> Breadcrumbs
AppLayout --> PageContent
Sidebar --> LayoutStore
Sidebar --> AuthStore
TopBar --> PeriodStore
TopBar --> AuthStore
PageContent --> PageHeader
style AppLayout fill:#ff3e00
style LayoutStore fill:#336791
style PeriodStore fill:#336791
```
### Sidebar Component
**File:** `src/lib/components/layout/Sidebar.svelte`
**States:**
- `expanded` (240px) — Full navigation with labels
- `collapsed` (64px) — Icons only
- `hidden` (0px) — Completely hidden (mobile drawer)
**Features:**
- Toggle button in header
- Section headers: PLANNING, REPORTS, ADMIN
- Role-based visibility (ADMIN section for superuser only)
- Active route highlighting
- Dark mode toggle at bottom
- Keyboard shortcut: `Cmd/Ctrl + \` to toggle
**Responsive Behavior:**
| Breakpoint | Default State | Toggle Method |
|------------|---------------|---------------|
| ≥1280px | expanded | Manual |
| 1024-1279px | collapsed | Manual |
| <1024px | hidden | Hamburger menu (drawer overlay) |
### TopBar Component
**File:** `src/lib/components/layout/TopBar.svelte`
**Elements:**
- Left: Hamburger menu (mobile only)
- Center: Breadcrumbs
- Right: Month selector, User menu
**Global Month Selector:**
```typescript
// src/lib/stores/period.ts
import { writable } from 'svelte/store';
export const selectedMonth = writable<string>(getCurrentMonth());
function getCurrentMonth(): string {
const now = new Date();
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
}
export function setMonth(month: string): void {
selectedMonth.set(month);
}
```
### Layout Store
**File:** `src/lib/stores/layout.ts`
```typescript
// src/lib/stores/layout.ts
import { writable } from 'svelte/store';
import { browser } from '$app/environment';
export type SidebarState = 'expanded' | 'collapsed' | 'hidden';
export type Theme = 'light' | 'dark';
function createLayoutStore() {
const defaultSidebar: SidebarState = browser && window.innerWidth >= 1280
? 'expanded'
: browser && window.innerWidth >= 1024
? 'collapsed'
: 'hidden';
const storedSidebar = browser
? (localStorage.getItem('headroom_sidebar_state') as SidebarState) || defaultSidebar
: defaultSidebar;
const storedTheme = browser
? (localStorage.getItem('headroom_theme') as Theme) || 'light'
: 'light';
const sidebarState = writable<SidebarState>(storedSidebar);
const theme = writable<Theme>(storedTheme);
return {
sidebarState: { subscribe: sidebarState.subscribe },
theme: { subscribe: theme.subscribe },
toggleSidebar: () => {
sidebarState.update(current => {
const next = current === 'expanded' ? 'collapsed' :
current === 'collapsed' ? 'hidden' : 'expanded';
if (browser) localStorage.setItem('headroom_sidebar_state', next);
return next;
});
},
setTheme: (newTheme: Theme) => {
theme.set(newTheme);
if (browser) localStorage.setItem('headroom_theme', newTheme);
}
};
}
export const layoutStore = createLayoutStore();
```
### Navigation Configuration
**File:** `src/lib/config/navigation.ts`
```typescript
// src/lib/config/navigation.ts
import type { NavSection } from '$lib/types/layout';
export const navigationSections: NavSection[] = [
{
title: 'PLANNING',
items: [
{ label: 'Dashboard', href: '/dashboard', icon: 'layout-dashboard' },
{ label: 'Team Members', href: '/team-members', icon: 'users' },
{ label: 'Projects', href: '/projects', icon: 'folder' },
{ label: 'Allocations', href: '/allocations', icon: 'calendar' },
{ label: 'Actuals', href: '/actuals', icon: 'check-circle' },
]
},
{
title: 'REPORTS',
items: [
{ label: 'Forecast', href: '/reports/forecast', icon: 'trending-up' },
{ label: 'Utilization', href: '/reports/utilization', icon: 'bar-chart-2' },
{ label: 'Costs', href: '/reports/costs', icon: 'dollar-sign' },
{ label: 'Variance', href: '/reports/variance', icon: 'alert-circle' },
{ label: 'Allocation Matrix', href: '/reports/allocation', icon: 'grid-3x3' },
]
},
{
title: 'ADMIN',
roles: ['superuser'],
items: [
{ label: 'Settings', href: '/settings', icon: 'settings' },
{ label: 'Master Data', href: '/master-data', icon: 'database' },
]
},
];
```
### Theme System
**Implementation:**
- DaisyUI theme switching via `data-theme` attribute on `<html>` element
- Light theme: `light` (DaisyUI default)
- Dark theme: `dark` or `business`
- Persisted to localStorage
- Respects system preference on first visit
```typescript
// Theme toggle effect
$effect(() => {
if (browser) {
document.documentElement.setAttribute('data-theme', $theme);
}
});
```
### Route Layout Integration
**File:** `src/routes/+layout.svelte`
```svelte
<script lang="ts">
import AppLayout from '$lib/components/layout/AppLayout.svelte';
import { page } from '$app/stores';
// Pages without AppLayout
const publicPages = ['/login', '/auth'];
$: isPublicPage = publicPages.some(p => $page.url.pathname.startsWith(p));
</script>
{#if isPublicPage}
<slot />
{:else}
<AppLayout>
<slot />
</AppLayout>
{/if}
```
### Data Density Patterns
**Table Configurations (DaisyUI):**
| View | Classes | Purpose |
|------|---------|---------|
| Allocation Matrix | `table table-compact table-pin-rows table-pin-cols` | Max density, pinned header/first column |
| Projects List | `table table-zebra table-pin-rows` | Readability, pinned header |
| Team Members | `table table-zebra` | Standard readability |
| Reports | `table table-compact table-pin-rows` | Dense data, pinned header |
### Accessibility Requirements
1. **Keyboard Navigation:**
- `Tab` through sidebar items
- `Enter/Space` to activate
- `Escape` to close mobile drawer
- `Cmd/Ctrl + \` to toggle sidebar
2. **ARIA Attributes:**
- `aria-expanded` on sidebar toggle
- `aria-current="page"` on active nav item
- `role="navigation"` on sidebar
- `role="main"` on content area
3. **Focus Management:**
- Focus trap in mobile drawer
- Focus restored on drawer close
---
**Document Control:**
- **Owner:** Santhosh J
- **Approver:** Santhosh J
- **Next Review:** Post-MVP implementation
- **Change History:**
- v1.0 (2026-02-17): Initial architecture approved
- v1.1 (2026-02-18): Added Frontend Layout Architecture section
---