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

@@ -177,6 +177,8 @@ git tag -a v1.0-docs -m "Initial documentation complete"
| What's the data model? | Architecture | Data Model |
| What are the success metrics? | Executive Summary | Success Metrics |
| What's the testing strategy? | Architecture | Quality Standards |
| **UI Layout approach?** | **Decision Log** | **UI Layout Decisions** |
| **Sidebar + TopBar pattern?** | **Architecture** | **Frontend Layout Architecture** |
## Questions or Updates?
@@ -184,5 +186,5 @@ Contact: Santhosh J (Project Owner)
---
**Last Updated:** February 17, 2026
**Documentation Version:** 1.0
**Last Updated:** February 18, 2026
**Documentation Version:** 1.1

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

View File

@@ -796,6 +796,120 @@ For month M:
---
## UI Layout Decisions
**Date:** February 18, 2026
**Context:** After initial authentication implementation, reviewed login page UI and decided to establish a comprehensive layout system.
### Problem Statement
The initial UI implementation used a simple top-navbar pattern without a standardized layout system. For a data-dense resource planning application, this approach would not scale well.
### Design Direction
**Aesthetic Spectrum Decision:**
- Target: **70% Data-Dense | 30% Utilitarian**
- Reference Apps: **Obsidian** (minimal chrome, content-first) + **Jira** (hierarchical sidebar, dense tables)
### Core Layout Decisions
| Decision | Choice | Rationale |
|----------|--------|-----------|
| **Sidebar** | Collapsible (full ↔ icons-only ↔ hidden) | Preserves screen real estate for low-resolution displays; sidebar should not eat productive space |
| **Month Selector** | Global (in top bar) | Allocation and actuals views are month-centric; affects all views |
| **Default Theme** | Light mode | Starting simple; dark mode available as toggle |
| **Icon Library** | **Lucide Svelte** | Modern, more icon variety, consistent with Svelte ecosystem |
| **UI Framework** | **DaisyUI-first** (no shadcn-svelte) | Already have it, excellent for business apps, use ~80% of its potential |
| **Navigation Pattern** | Persistent sidebar + top bar | Obsidian/Jira style; sectioned navigation (Planning, Reports, Admin) |
### Sidebar Specifications
```
EXPANDED (240px) COLLAPSED (64px) HIDDEN (0px)
┌────────────────┐ ┌────────┐ ┌──────────────────┐
│ ◀ ▶ │ │ ▶ │ │ │
│ ────────────── │ │ ────── │ │ Full width │
│ PLANNING │ │ │ │ content │
│ 📊 Dashboard │ │ 📊 │ │ │
│ 👥 Team Mem │ │ 👥 │ │ (toggle via │
│ 📁 Projects │ │ 📁 │ │ Cmd/Ctrl+\) │
│ 📅 Allocations │ │ 📅 │ │ │
│ ✅ Actuals │ │ ✅ │ │ │
│ ────────────── │ │ ────── │ │ │
│ REPORTS │ │ │ │ │
│ 📈 Forecast │ │ 📈 │ │ │
│ 📉 Utilization │ │ 📉 │ │ │
│ 💰 Costs │ │ 💰 │ │ │
│ 📋 Variance │ │ 📋 │ │ │
│ ────────────── │ │ ────── │ │ │
│ ADMIN* │ │ │ │ │
│ ⚙️ Settings │ │ ⚙️ │ │ │
│ ────────────── │ │ ────── │ │ │
│ 🌙 Dark [tgl] │ │ 🌙 │ │ │
└────────────────┘ └────────┘ └──────────────────┘
* Admin section visible only to superuser role
```
### Responsive Behavior
| Breakpoint | Sidebar Behavior | Toggle |
|------------|------------------|--------|
| ≥1280px (xl) | Expanded by default | Manual toggle only |
| 1024-1279px (lg) | Collapsed by default | Manual toggle only |
| 768-1023px (md) | Hidden (drawer overlay) | Hamburger menu |
| <768px (sm) | Hidden (drawer overlay) | Hamburger menu |
### Implementation Approach
**Phased Changes:**
1. `p00-api-documentation` — Add Scribe annotations to all controllers
2. `p01-ui-foundation` — Types, stores, Lucide setup, theme system
3. `p02-app-layout` — AppLayout, Sidebar, TopBar, Breadcrumbs
4. `p03-dashboard-enhancement` — Dashboard with stat cards
5. `p04-content-patterns` — DataTable, StatCard, FilterBar, EmptyState, LoadingState
6. `p05-page-migrations` — Migrate remaining pages incrementally
### Files to Create
```
frontend/src/lib/
├── types/layout.ts # SidebarState, NavItem, NavSection
├── stores/
│ ├── layout.ts # sidebarState, theme
│ └── period.ts # selectedMonth (global)
├── config/navigation.ts # Navigation sections config
└── components/layout/
├── AppLayout.svelte # Main layout wrapper
├── Sidebar.svelte # Collapsible navigation
├── TopBar.svelte # Search, month, user menu
├── Breadcrumbs.svelte # Navigation context
└── PageHeader.svelte # Page title + actions
```
### DaisyUI Table Density
| View | Classes |
|------|---------|
| Allocation Matrix | `table-compact table-pin-rows table-pin-cols` |
| Projects List | `table-zebra table-pin-rows` |
| Team Members | `table-zebra` |
| Reports | `table-compact table-pin-rows` |
### Key Quotes from Discussion
> "I like a collapsible sidebar, because some people use very low screen resolution which will limit the real estate to play around with. Sidebar should not eat up the productive space."
> "Make [month selector] global."
> "Light mode for now."
> "I lean more towards data-dense. How about keeping it 30-70 (utilitarian-data-dense)?"
> "Use Lucide."
---
## Next Steps (Post-Documentation)
### Immediate Actions