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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user