Files
headroom/openspec/config.yaml
Santhosh Janardhanan b9cb5170da 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
2026-02-18 13:03:08 -05:00

313 lines
13 KiB
YAML

schema: spec-driven
# Project context - shown to AI when creating artifacts
# This provides essential context about the Headroom project
techstack: |
## Backend (Laravel API)
- **Framework:** Laravel 12 (latest) with PHP 8.4
- **Database:** PostgreSQL (latest, Alpine container)
- **Caching:** Redis (latest, Alpine container) - query + response caching
- **Authentication:** JWT (tymon/jwt-auth package)
- **API Design:** REST with Laravel API Resources
- **API Documentation:** Laravel Scribe (auto-generates SwaggerUI)
- **Testing:** PHPUnit (unit) + Pest (feature)
- **Code Style:** PSR-12, Laravel conventions
- **Container:** Docker (port 3000)
## Frontend (SvelteKit)
- **Framework:** SvelteKit (latest) with Svelte 5
- **Styling:** Tailwind CSS 4 + DaisyUI 5
- **Icons:** Lucide Svelte (modern icon library)
- **Charts:** Recharts
- **Tables:** TanStack Table (React Table for Svelte)
- **Forms:** Superforms + Zod + SvelteKit Form Actions
- **State Management:** Svelte stores (minimal - UI state only)
- **HTTP Client:** Native fetch (no Axios)
- **Testing:** Vitest (unit) + Playwright (E2E)
- **Container:** Docker (port 5173)
## Infrastructure
- **Local Dev:** Docker Compose with code-mounted volumes (hot reload)
- **Reverse Proxy:** Nginx Proxy Manager (existing)
- **Database Volume:** Mounted to ./data/postgres
- **Cache Volume:** Mounted to ./data/redis
- **Secrets:** .env files (all environments)
conventions: |
## Code Style Standards
### Backend (Laravel)
- Follow PSR-12 coding standards
- Use Laravel Pint for linting
- Use PHPStan (level 5+) for static analysis
- Use Laravel conventions for naming (camelCase methods, snake_case DB columns)
- Use API Resources for consistent JSON responses
- Use Form Requests for validation
- Use Policies for authorization
- Use UUIDs for primary keys (prevents ID enumeration)
### Frontend (SvelteKit)
- Use Prettier for formatting
- Use ESLint with Svelte plugin
- Use TypeScript strict mode
- Use SvelteKit file-based routing conventions
- Use $lib alias for imports from src/lib
- Use Zod schemas for validation (shared between frontend and API contracts)
## Git Conventions
- **Branch naming:** `feature/opsx-<change-name>` for OpenSpec changes
- **Commit format:**
```
[Type] Brief description (50 chars max)
Detailed explanation (optional, 72 char wrap)
Refs: openspec/changes/<change-name>
```
- **Types:** feat, fix, refactor, test, docs, chore
- **Granular commits:** One fix = one commit
## API Conventions
- RESTful endpoints with standard HTTP verbs
- Response format: `{ "data": {}, "meta": {}, "links": {} }`
- Error format: `{ "message": "...", "errors": {} }`
- Cache keys pattern: `allocations:month:{YYYY-MM}`, `reports:forecast:{from}:{to}:{hash}`
- TTL: 1 hour (allocations), 15 min (reports), 24 hours (master data)
development_strategy:
approach: "Spec-Driven Development (SDD) + Test-Driven Development (TDD) Hybrid"
description: |
Every capability follows a 4-phase cycle:
## Phase 1: SPEC → TEST (Red Phase)
- Read scenarios from specs/<capability>/spec.md
- Write E2E tests (Playwright) - mark as test.fixme()
- Write API tests (Pest) - mark as ->todo()
- Write unit tests (Pest/Vitest) - mark as ->todo() or test.skip()
- Write component tests (Vitest) - mark as test.skip()
- Commit: "test(<capability>): Add pending tests for all scenarios"
## Phase 2: IMPLEMENT (Green Phase)
- Remove skip/todo marker from one test
- Write MINIMAL code to make it pass
- Run test suite (npm run test, php artisan test, npx playwright test)
- Commit when green: "feat(<capability>): Implement <scenario>"
- Repeat for all scenarios
## Phase 3: REFACTOR (Clean Phase)
- Review for code smells, duplication, performance
- Refactor with confidence (tests guard against regression)
- Run full test suite
- Commit: "refactor(<capability>): <improvement description>"
## Phase 4: DOCUMENT
- Generate API docs: php artisan scribe:generate
- Verify all tests pass
- Commit: "docs(<capability>): Update API documentation"
test_granularity: "Every spec scenario gets a test"
test_organization: "Mirror current structure"
test_types:
- E2E: Playwright tests for critical user journeys
- API: Pest Feature tests for all endpoints
- Unit: Pest/Vitest for internal methods and business logic
- Component: Vitest + Testing Library for Svelte components
pending_markers:
php: "->todo()"
playwright: "test.fixme()"
vitest: "test.skip()"
test_naming:
format: "Descriptive - mirror spec scenario intent"
examples:
- "authenticates user with valid credentials and issues JWT tokens"
- "returns 401 when credentials are invalid"
- "rotates refresh token on each refresh request"
coverage_target: ">70%"
regression_strategy: "Every test is a regression test - run full suite on every PR"
scripts:
backend:
test: "pest"
"test:unit": "pest --filter=Unit"
"test:feature": "pest --filter=Feature"
"test:coverage": "pest --coverage --min=70"
"test:todo": "pest --filter=todo"
lint: "pint"
"lint:fix": "pint --fix"
analyse: "phpstan analyse --level=5"
docs: "scribe:generate"
frontend:
test: "vitest run"
"test:watch": "vitest"
"test:ui": "vitest --ui"
"test:e2e": "playwright test"
"test:e2e:ui": "playwright test --ui"
"test:all": "npm run test && npm run test:e2e"
lint: "eslint ."
"lint:fix": "eslint . --fix"
format: "prettier --check ."
"format:fix": "prettier --write ."
rules:
# Project-level standing instructions (from decision-log.md)
all_changes:
- Every change must follow SDD+TDD: specs → pending tests → implementation → refactor
- Every spec scenario must have corresponding tests (E2E, API, Unit, Component)
- Pending tests must be committed before implementation (red phase)
- Changes must end with code review for style, standards, and security
- Verification (/opsx-verify) must check for uncovered code (code not tested)
- Commits must be granular (one scenario = one commit)
- Code coverage must be >70% (enforced in /opsx-verify)
- All tests must pass before merge
- Zero linting errors (Laravel Pint, ESLint, Prettier)
- API documentation must be up-to-date (Scribe generation)
# Documentation Standards
documentation:
- API documentation (Scribe annotations) is MANDATORY for all controllers
- Every endpoint must have @group, @authenticated (if protected), @response annotations
- Update docs/ when making significant decisions (decision-log.md, architecture.md)
- Document new dependencies in both config.yaml techstack AND design.md
- UI decisions go in decision-log.md → "UI Layout Decisions" section
- Architecture changes go in architecture.md → relevant section
# Workflow Loops
workflow:
- Follow capability-based workflow: Test → Implement → Refactor → Document
- Do NOT skip phases - each phase has a specific commit
- Run full test suite after EACH implementation commit
- Fix failing tests before moving to next scenario
- API documentation (Scribe) is generated in Phase 4 (Document)
- Loop through scenarios one at a time (not all at once)
# UI Standards (70% data-dense, 30% utilitarian)
ui_standards:
- Use Lucide Svelte for ALL icons (no inline SVGs, no other icon libraries)
- DaisyUI-first approach - use DaisyUI components before building custom
- Sidebar pattern: Collapsible (expanded ↔ collapsed ↔ hidden)
- Global month selector in top bar (affects all views)
- Light mode default, dark mode available via toggle
- Table density: Use table-compact for data-heavy views
- Reference apps: Obsidian (minimal chrome) + Jira (hierarchical sidebar)
# Component Patterns
component_patterns:
layout:
- AppLayout.svelte: Main wrapper with sidebar + content area
- Sidebar.svelte: Collapsible navigation with sections
- TopBar.svelte: Breadcrumbs, month selector, user menu
- Breadcrumbs.svelte: Auto-generated from route
- PageHeader.svelte: Page title + action buttons slot
state:
- layoutStore: sidebarState ('expanded'|'collapsed'|'hidden'), theme
- periodStore: selectedMonth (global YYYY-MM format)
- Persist user preferences to localStorage
navigation:
- Sections: PLANNING, REPORTS, ADMIN
- ADMIN section visible only to superuser role
- Active route highlighting required
# Accessibility Requirements
accessibility:
- Keyboard navigation: Tab through sidebar, Enter/Space to activate
- Escape to close mobile drawer
- Cmd/Ctrl + \ to toggle sidebar (desktop)
- ARIA: aria-expanded on sidebar toggle, aria-current="page" on active nav
- Focus trap in mobile drawer
- Focus restored on drawer close
- All form inputs must have associated labels
# Responsive Design
responsive:
- ≥1280px (xl): Sidebar expanded by default, manual toggle
- 1024-1279px (lg): Sidebar collapsed by default, manual toggle
- 768-1023px (md): Sidebar hidden, hamburger menu (drawer overlay)
- <768px (sm): Sidebar hidden, hamburger menu (drawer overlay)
- Mobile drawer: Full-height overlay with backdrop
- Close drawer on route change (mobile)
# State Management Patterns
state_management:
- Use Svelte stores for UI state only (not business data)
- Business data comes from API (no client-side caching beyond DaisyUI)
- Stores: auth, layout, period
- localStorage keys: headroom_access_token, headroom_refresh_token,
headroom_sidebar_state, headroom_theme
- Store files go in src/lib/stores/
proposal:
- Include clear Goals and Non-Goals sections
- Reference the 4 personas (Superuser, Manager, Developer, Top Brass)
- Align with monthly capacity planning workflow
- Include data validation rules for any new entities
specs:
- Document all validation rules explicitly
- Include RBAC permissions for each operation
- Define error scenarios and expected responses
- Reference existing data model (team_members, projects, allocations, actuals)
- Use YYYY-MM format for month references
- Each scenario must be testable (clear GIVEN/WHEN/THEN)
design:
- Include database schema changes (migrations needed)
- Define API endpoints with request/response examples
- Specify caching strategy (keys, TTL, invalidation rules)
- Include UI/UX considerations for SvelteKit + DaisyUI
- Document any new dependencies
- Document test approach for each capability
tasks:
- Organize by capability (not by layer)
- Each capability has 4 phases: Test (Red) → Implement (Green) → Refactor → Document
- Break implementation into individual scenarios
- Include explicit test tasks (write pending, enable one by one)
- Include API documentation updates as tasks
- Order capabilities by dependency and business priority
# Domain knowledge
context: |
## Project Overview
Headroom is a resource planning and capacity management tool for engineering managers.
It replaces error-prone spreadsheets with structured capacity planning, resource allocation,
and utilization tracking.
## Core Workflow (Monthly Cycle)
1. **Capacity Planning** - Define team availability (holidays, PTO, working days)
2. **Project Setup** - Track projects through lifecycle with approved estimates
3. **Resource Allocation** - Allocate hours per person per project per month
4. **Actuals Tracking** - Log actual hours worked and compare to planned
## Personas & Permissions
- **Superuser:** Full access (setup, config, all projects, all teams)
- **Manager:** Create/edit own projects, allocate own team, view all projects read-only
- **Developer:** View own allocations, log own hours, view assigned projects
- **Top Brass:** View all reports read-only (forecasts, utilization, costs)
## Key Business Rules
- Availability: 0 (unavailable), 0.5 (half day), 1.0 (full day)
- Project allocation indicators: GREEN (100% ±5%), YELLOW (<95%), RED (>105%)
- Monthly aggregate for actuals (not daily)
- Untracked resource for external team time (not billed)
- Validation: Cannot allocate to "Done" or "Cancelled" projects
## Data Model Summary
- team_members: id (UUID), name, role_id, hourly_rate, active
- projects: id (UUID), code, title, status_id, type_id, approved_estimate, forecasted_effort (JSON)
- allocations: id (UUID), project_id, team_member_id, month (YYYY-MM), allocated_hours
- actuals: id (UUID), project_id, team_member_id, month (YYYY-MM), hours_logged
- roles, project_statuses, project_types: Master data tables
- holidays, ptos: Calendar data
## Deferred to Phase 2
- Real-time notifications (WebSocket)
- PDF/CSV exports
- Background jobs (Laravel Queue)
- Audit logging
- Multi-tenancy
- Time-tracking tool integration