TrajectoryOS Frontend Transformation Plan
**Version**: 1.4 **Created**: 2025-12-12 **Last Updated**: 2025-12-12 **Status**: π Core Features Complete β Ready for Production Polish **Overall Progress**: 18/23 tasks complete (78%)
Full Public Reader
TrajectoryOS Frontend Transformation Plan
Version: 1.4
Created: 2025-12-12
Last Updated: 2025-12-12
Status: π Core Features Complete β Ready for Production Polish
Overall Progress: 18/23 tasks complete (78
---
Executive Summary
### Vision
Transform the TrajectoryOS frontend from a skeleton application (20
### Current State
- β
Backend: 95
- β οΈ Frontend: 20
- β Integration: Broken (dashboard points to gesture trainer API instead of trajectory-core)
### Target State
- β
Production-ready web dashboard with real-time physics computation
- β
Interactive trajectory timeline (calendar replacement)
- β
Scenario planning studio with visual comparison
- β
Skill graph visualizer with Bayesian uncertainty
- β
Full integration with backend Python models
- β
Type-safe, performant, responsive, and polished
### Success Metrics
- [ ] 100
- [ ] Real-time data updates (<500ms latency)
- [ ] Mobile-responsive design (320px - 2560px)
- [ ] 90+ Lighthouse performance score
- [ ] Zero TypeScript errors
- [ ] Comprehensive error handling and loading states
- [ ] User can manage entire trajectory without traditional calendar
---
Architecture Overview
Tech Stack
#### Core Framework
- Next.js 16 (App Router) - Server/client components
- React 19 - UI library
- TypeScript 5 - Type safety
#### State Management
- @tanstack/react-query - Server state (API calls, caching)
- zustand - Client state (UI state, user preferences)
#### Data Visualization
- recharts - Charts and graphs β
Already installed
- react-flow or D3.js - Skill graph network visualization
#### UI & Styling
- framer-motion - Animations β
Already installed
- Tailwind CSS - Utility-first styling
- shadcn/ui - Component library (optional)
- sonner - Toast notifications
#### Forms & Validation
- react-hook-form - Form management
- zod - Schema validation (matches backend schemas)
#### Utilities
- date-fns - Date manipulation for timeline
- axios - HTTP client
- cmdk - Command palette (power user feature)
Folder Structure
apps/web-dashboard/
βββ src/
β βββ app/ # Next.js App Router
β β βββ (dashboard)/ # Dashboard layout group
β β β βββ layout.tsx # Shared dashboard layout
β β β βββ page.tsx # Physics Dashboard (home)
β β β βββ timeline/ # Calendar replacement
β β β β βββ page.tsx
β β β βββ scenarios/ # Scenario planner
β β β β βββ page.tsx # List/generate
β β β β βββ [id]/page.tsx # Scenario detail
β β β βββ skills/ # Skill graph
β β β β βββ page.tsx
β β β βββ constraints/ # Gravity/constraint manager
β β β β βββ page.tsx
β β β βββ history/ # Analysis timeline
β β β β βββ page.tsx
β β β βββ settings/ # User settings
β β β βββ page.tsx
β β βββ api/ # API routes (if needed)
β β βββ layout.tsx # Root layout
β β βββ globals.css # Global styles
β β
β βββ lib/ # Shared utilities
β β βββ api/ # API client layer
β β β βββ client.ts # Axios instance + config
β β β βββ queries/ # React Query hooks
β β β β βββ useSkills.ts
β β β β βββ useLifeState.ts
β β β β βββ useScenarios.ts
β β β β βββ usePlanner.ts
β β β βββ mutations/ # React Query mutations
β β β β βββ useSubmitEvidence.ts
β β β β βββ useGenerateScenarios.ts
β β β β βββ useCreatePlan.ts
β β β βββ types.ts # API types (from backend)
β β β
β β βββ store/ # Zustand stores
β β β βββ uiStore.ts # UI state (sidebar, modals)
β β β βββ userStore.ts # User preferences
β β β
β β βββ utils/ # Utility functions
β β β βββ physics.ts # Physics calculations
β β β βββ dates.ts # Date utilities
β β β βββ formatting.ts # Number/text formatting
β β β
β β βββ hooks/ # Custom React hooks
β β βββ usePhysicsMetrics.ts
β β βββ useTrajectoryForecast.ts
β β βββ useRealtime.ts
β β
β βββ components/ # React components
β βββ ui/ # Base UI components
β β βββ Button.tsx
β β βββ Card.tsx
β β βββ Chart.tsx
β β βββ Modal.tsx
β β βββ ...
β β
β βββ physics/ # Physics-specific components
β β βββ EscapeIndexDisplay.tsx
β β βββ PhysicsCard.tsx
β β βββ MetricSparkline.tsx
β β βββ TrajectoryChart.tsx
β β
β βββ timeline/ # Timeline components
β β βββ TimelineView.tsx
β β βββ DayCell.tsx
β β βββ ScenarioOverlay.tsx
β β βββ ...
β β
β βββ scenarios/ # Scenario components
β β βββ ScenarioGenerator.tsx
β β βββ ScenarioCard.tsx
β β βββ ComparisonMatrix.tsx
β β βββ ...
β β
β βββ skills/ # Skill components
β β βββ SkillGraph.tsx
β β βββ SkillNode.tsx
β β βββ BeliefDistribution.tsx
β β βββ ...
β β
β βββ layout/ # Layout components
β βββ DashboardNav.tsx
β βββ Sidebar.tsx
β βββ Header.tsx
β
βββ public/ # Static assets
βββ package.json
βββ tsconfig.json
βββ tailwind.config.ts
βββ next.config.js---
Detailed Task Breakdown
### Phase 1: Foundation & Infrastructure (Days 1-2)
Goal: Fix critical issues, set up proper state management and API integration
#### Task 1.1: Fix API Client Configuration β
COMPLETE
Priority: π΄ CRITICAL
Files: `src/lib/api/client.ts`, `src/lib/apiClient.ts` (legacy)
Status: β
Complete
Current Issues:
- `apiClient.ts` points to `http://localhost:8080` (wrong port)
- Should point to `http://localhost:3003` (trajectory-core)
- No error handling or retry logic
- No request/response interceptors
Implementation:
// src/lib/api/client.ts
import axios, { AxiosInstance, AxiosError } from 'axios';
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3003';
export const apiClient: AxiosInstance = axios.create({
baseURL: API_BASE_URL,
timeout: 30000,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor - add auth token
apiClient.interceptors.request.use(
(config) => {
// TODO: Add auth token when implemented
const userId = process.env.NEXT_PUBLIC_DEMO_USER_ID || 'demo';
config.headers['x-user-id'] = userId;
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor - handle errors
apiClient.interceptors.response.use(
(response) => response,
(error: AxiosError) => {
if (error.response?.status === 401) {
// Handle unauthorized
console.error('Unauthorized - redirect to login');
}
return Promise.reject(error);
}
);Acceptance Criteria:
- [x] Client points to correct port (3003)
- [x] Request interceptor adds x-user-id header
- [x] Response interceptor handles 401/500 errors
- [x] Timeout configured (30s)
- [x] TypeScript types defined
- [x] Environment variable support
Testing:
# Test API client
curl http://localhost:3003/health
# Should return: {"status":"ok","service":"trajectory-core"}---
#### Task 1.2: Install Required Dependencies β
COMPLETE
Priority: π΄ CRITICAL
Files: `package.json`
Status: β
Complete
Dependencies to Install:
{
"dependencies": {
"@tanstack/react-query": "^5.56.2",
"@tanstack/react-query-devtools": "^5.56.2",
"zustand": "^4.5.0",
"axios": "^1.7.7",
"date-fns": "^3.6.0",
"react-hook-form": "^7.53.0",
"zod": "^3.23.8",
"sonner": "^1.5.0",
"cmdk": "^1.0.0",
"reactflow": "^11.11.4"
},
"devDependencies": {
"tailwindcss": "^3.4.1",
"@types/node": "^20",
"autoprefixer": "^10.4.16",
"postcss": "^8.4.32"
}
}Commands:
cd apps/web-dashboard
npm install @tanstack/react-query @tanstack/react-query-devtools zustand axios date-fns react-hook-form zod sonner cmdk reactflow
npm install -D tailwindcss autoprefixer postcss
npx tailwindcss init -pAcceptance Criteria:
- [x] All dependencies installed without errors
- [x] package-lock.json updated
- [x] Tailwind CSS configured
- [x] No peer dependency warnings
---
#### Task 1.3: Set Up React Query Provider β
COMPLETE
Priority: π΄ CRITICAL
Files: `src/app/layout.tsx`, `src/lib/api/queryClient.ts`
Status: β
Complete
Implementation:
// src/lib/api/queryClient.ts
import { QueryClient } from '@tanstack/react-query';
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 minutes
gcTime: 1000 * 60 * 10, // 10 minutes (formerly cacheTime)
retry: 1,
refetchOnWindowFocus: false,
},
mutations: {
retry: 0,
},
},
});// src/app/layout.tsx
'use client';
import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { queryClient } from '@/lib/api/queryClient';
import { Toaster } from 'sonner';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
<Toaster richColors position="top-right" />
</QueryClientProvider>
</body>
</html>
);
}Acceptance Criteria:
- [x] QueryClient configured with sensible defaults
- [x] Provider wraps entire app
- [x] React Query DevTools available in dev mode
- [x] Toaster configured for notifications
---
#### Task 1.4: Create API Type Definitions β
COMPLETE
Priority: π‘ HIGH
Files: `src/lib/api/types.ts`
Status: β
Complete
Implementation:
Match backend types exactly. Import from trajectory-core schemas where possible.
// src/lib/api/types.ts
// ========== User & Auth ==========
export interface User {
id: string;
email?: string;
createdAt: string;
}
// ========== Skills ==========
export interface SkillBelief {
skillId: string;
level: number; // Bayesian posterior mean
uncertainty: number; // Bayesian posterior std
lastUpdated: string;
utilization?: number; // 0-1
}
export interface SkillEvidence {
skillId: string;
levelEstimate: number; // 0-10
confidence: number; // 0-1
utilization?: number; // 0-1
source: 'interview' | 'artifact' | 'manual' | 'observation';
rawText?: string;
timestamp?: string;
}
export interface SkillEvidenceInput {
userId: string;
evidence: SkillEvidence[];
}
// ========== Projects ==========
export interface Project {
id: string;
name: string;
description?: string;
status: 'active' | 'paused' | 'completed';
createdAt: string;
embedding?: number[]; // Semantic embedding from Python
}
// ========== Life State ==========
export interface LifeState {
id: string;
userId: string;
timestamp: string;
thrust: number; // T
alignment: number; // A (0-1)
gravity: number; // G
mass: number; // M
escapeIndex: number; // Ξ· = T*A / (G*M)
latentState?: number[]; // 32-dim z vector
}
export interface TrajectoryForecast {
days: number[]; // [0, 1, 2, ..., horizon]
thrust: number[];
alignment: number[];
gravity: number[];
mass: number[];
escapeIndex: number[];
}
export interface EscapeTimeEstimate {
meanDays: number;
stdDays: number;
escapeProb: number; // P(escape within horizon)
percentiles: {
p10: number;
p50: number;
p90: number;
};
}
// ========== Scenarios ==========
export interface Action {
action_type: 'skill_practice' | 'project_work' | 'remove_constraint' | 'simplify_project';
skill_id?: string;
project_id?: string;
constraint_id?: string;
intensity: number; // 0-1
duration_hours: number;
}
export interface Scenario {
name: string;
description: string;
actions: Action[][]; // Array of daily action lists
}
export interface ScenarioEvaluation {
scenarioName: string;
finalEta: number;
meanEta: number;
escapeProb: number;
escapeDays: number | null;
risks: string[];
}
export interface GenerateAndEvaluateResult {
scenarios: Record<string, Scenario>;
evaluations: ScenarioEvaluation[];
bestScenario: string;
rankedScenarios: ScenarioEvaluation[];
}
// ========== Action Plans ==========
export interface PlanStep {
id: string;
description: string;
priority: number;
etaDays: number;
dependencies?: string | null;
}
export interface ActionPlan {
id: string;
userId: string;
goal: string;
horizonDays: number;
status: 'draft' | 'active' | 'completed' | 'archived';
steps?: PlanStep[];
createdAt: string;
finalEta?: number;
escapeProb?: number;
}
// ========== Analysis ==========
export interface TrajectoryAnalysis {
summary: string;
currentEta: number;
thrust: number;
alignment: number;
gravity: number;
mass: number;
risks: string[];
opportunities: string[];
timestamp?: string;
}
export interface LeveragePoint {
id: string;
description: string;
impactEstimate: number;
effortEstimate: number;
category: string;
}
// ========== Constraints ==========
export interface Constraint {
id: string;
type: 'deadline' | 'financial' | 'health' | 'social' | 'time' | 'other';
description: string;
severity: number; // 0-10
deadline?: string;
resolutionCost?: number;
}
// ========== API Request/Response Types ==========
export interface ApiError {
error: string;
issues?: any[];
}
export interface PaginatedResponse<T> {
data: T[];
total: number;
offset: number;
limit: number;
}Acceptance Criteria:
- [x] All backend types mirrored in frontend
- [x] Types match Prisma schema
- [x] JSDoc comments for clarity
- [x] Exports organized by domain
---
#### Task 1.5: Build React Query Hooks (Skills) β
COMPLETE
Priority: π‘ HIGH
Files: `src/lib/api/queries/useSkills.ts`, `src/lib/api/mutations/useSubmitEvidence.ts`
Status: β
Complete
Implementation:
// src/lib/api/queries/useSkills.ts
import { useQuery } from '@tanstack/react-query';
import { apiClient } from '../client';
import { SkillBelief } from '../types';
export const useSkills = (userId: string) => {
return useQuery({
queryKey: ['skills', userId],
queryFn: async () => {
const { data } = await apiClient.get<{ skills: SkillBelief[] }>(
`/api/skills/user/${userId}`
);
return data.skills;
},
enabled: !!userId,
});
};// src/lib/api/mutations/useSubmitEvidence.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { apiClient } from '../client';
import { SkillEvidenceInput } from '../types';
import { toast } from 'sonner';
export const useSubmitEvidence = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (input: SkillEvidenceInput) => {
const { data } = await apiClient.post('/api/skills/evidence', input);
return data;
},
onSuccess: (_, variables) => {
// Invalidate skills query to refetch
queryClient.invalidateQueries({ queryKey: ['skills', variables.userId] });
toast.success('Skill evidence submitted successfully');
},
onError: (error: any) => {
toast.error(error.response?.data?.error || 'Failed to submit evidence');
},
});
};Acceptance Criteria:
- [x] useSkills hook fetches user skills
- [x] useSubmitEvidence mutation posts evidence
- [x] Cache invalidation on successful mutation
- [x] Toast notifications on success/error
- [x] Proper TypeScript types
---
#### Task 1.6: Build React Query Hooks (Life State) β
COMPLETE
Priority: π‘ HIGH
Files: `src/lib/api/queries/useLifeState.ts`, `src/lib/api/mutations/useRecomputeState.ts`
Status: β
Complete
Implementation:
// src/lib/api/queries/useLifeState.ts
import { useQuery } from '@tanstack/react-query';
import { apiClient } from '../client';
import { LifeState } from '../types';
export const useLatestLifeState = (userId: string) => {
return useQuery({
queryKey: ['lifeState', userId, 'latest'],
queryFn: async () => {
const { data } = await apiClient.get<LifeState>(`/api/state/${userId}`);
return data;
},
enabled: !!userId,
refetchInterval: 30000, // Refetch every 30s for real-time feel
});
};
export const useLifeStateHistory = (userId: string, limit = 100) => {
return useQuery({
queryKey: ['lifeState', userId, 'history', limit],
queryFn: async () => {
const { data } = await apiClient.get<LifeState[]>(
`/api/state/${userId}/history?limit=${limit}`
);
return data;
},
enabled: !!userId,
});
};// src/lib/api/mutations/useRecomputeState.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { apiClient } from '../client';
import { LifeState } from '../types';
import { toast } from 'sonner';
export const useRecomputeState = (userId: string) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async () => {
const { data } = await apiClient.post<LifeState>(
`/api/state/${userId}/recompute`
);
return data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['lifeState', userId] });
toast.success('Life state recomputed with Python models');
},
onError: (error: any) => {
toast.error(error.response?.data?.error || 'Failed to recompute state');
},
});
};Acceptance Criteria:
- [x] useLatestLifeState fetches current state
- [x] Auto-refetch every 30s for real-time updates
- [x] useLifeStateHistory fetches historical data
- [x] useRecomputeState mutation triggers Python recomputation
- [x] Cache invalidation works correctly
---
#### Task 1.7: Build React Query Hooks (Scenarios & Planning) β
COMPLETE
Priority: π‘ HIGH
Files: `src/lib/api/queries/useScenarios.ts`, `src/lib/api/mutations/useGenerateScenarios.ts`
Status: β
Complete
Implementation:
// src/lib/api/queries/useScenarios.ts
import { useQuery } from '@tanstack/react-query';
import { apiClient } from '../client';
import { ActionPlan } from '../types';
export const useActionPlans = (userId: string, status?: string) => {
return useQuery({
queryKey: ['actionPlans', userId, status],
queryFn: async () => {
const params = status ? `?status=${status}` : '';
const { data } = await apiClient.get<ActionPlan[]>(
`/api/planner/${userId}/plans${params}`
);
return data;
},
enabled: !!userId,
});
};
export const useActionPlan = (userId: string, planId: string) => {
return useQuery({
queryKey: ['actionPlan', userId, planId],
queryFn: async () => {
const { data } = await apiClient.get<ActionPlan>(
`/api/planner/${userId}/plans/${planId}`
);
return data;
},
enabled: !!userId && !!planId,
});
};// src/lib/api/mutations/useGenerateScenarios.ts
import { useMutation } from '@tanstack/react-query';
import { apiClient } from '../client';
import { GenerateAndEvaluateResult } from '../types';
import { toast } from 'sonner';
interface GenerateScenariosInput {
userId: string;
goal: string;
nScenarios?: number;
horizon?: number;
}
export const useGenerateScenarios = () => {
return useMutation({
mutationFn: async (input: GenerateScenariosInput) => {
const { userId, ...body } = input;
const { data } = await apiClient.post<GenerateAndEvaluateResult>(
`/api/planner/${userId}/scenarios/generate-and-evaluate`,
body
);
return data;
},
onSuccess: () => {
toast.success('Scenarios generated and evaluated');
},
onError: (error: any) => {
toast.error(error.response?.data?.error || 'Failed to generate scenarios');
},
});
};
export const useCreatePlan = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (input: GenerateScenariosInput) => {
const { userId, ...body } = input;
const { data } = await apiClient.post<ActionPlan>(
`/api/planner/${userId}/scenarios/generate-and-save`,
body
);
return data;
},
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ['actionPlans', variables.userId] });
toast.success('Action plan created successfully');
},
onError: (error: any) => {
toast.error(error.response?.data?.error || 'Failed to create plan');
},
});
};Acceptance Criteria:
- [x] useActionPlans fetches all plans
- [x] useActionPlan fetches single plan by ID
- [x] useGenerateScenarios generates and evaluates scenarios
- [x] useCreatePlan saves best scenario as action plan
- [x] Proper error handling and notifications
---
#### Task 1.8: Set Up Zustand Store β
COMPLETE
Priority: π’ MEDIUM
Files: `src/lib/store/uiStore.ts`, `src/lib/store/userStore.ts`
Status: β
Complete
Implementation:
// src/lib/store/uiStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface UIState {
sidebarOpen: boolean;
timelineView: 'week' | 'month' | 'quarter';
scenarioComparison: boolean;
selectedScenarioIds: string[];
// Actions
toggleSidebar: () => void;
setTimelineView: (view: 'week' | 'month' | 'quarter') => void;
toggleScenarioComparison: () => void;
selectScenario: (id: string) => void;
deselectScenario: (id: string) => void;
clearSelectedScenarios: () => void;
}
export const useUIStore = create<UIState>()(
persist(
(set) => ({
sidebarOpen: true,
timelineView: 'week',
scenarioComparison: false,
selectedScenarioIds: [],
toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
setTimelineView: (view) => set({ timelineView: view }),
toggleScenarioComparison: () =>
set((state) => ({ scenarioComparison: !state.scenarioComparison })),
selectScenario: (id) =>
set((state) => ({
selectedScenarioIds: [...state.selectedScenarioIds, id],
})),
deselectScenario: (id) =>
set((state) => ({
selectedScenarioIds: state.selectedScenarioIds.filter((sid) => sid !== id),
})),
clearSelectedScenarios: () => set({ selectedScenarioIds: [] }),
}),
{
name: 'trajectory-ui-store',
}
)
);// src/lib/store/userStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface UserState {
userId: string;
preferences: {
theme: 'light' | 'dark' | 'system';
notifications: boolean;
autoRefresh: boolean;
};
// Actions
setUserId: (userId: string) => void;
updatePreferences: (preferences: Partial<UserState['preferences']>) => void;
}
export const useUserStore = create<UserState>()(
persist(
(set) => ({
userId: process.env.NEXT_PUBLIC_DEMO_USER_ID || 'demo',
preferences: {
theme: 'dark',
notifications: true,
autoRefresh: true,
},
setUserId: (userId) => set({ userId }),
updatePreferences: (preferences) =>
set((state) => ({
preferences: { ...state.preferences, ...preferences },
})),
}),
{
name: 'trajectory-user-store',
}
)
);Acceptance Criteria:
- [x] UI store manages sidebar, timeline view, scenario selection
- [x] User store manages user ID and preferences
- [x] Persistence with localStorage
- [x] TypeScript types for all state and actions
---
### Phase 2: Core Components & UI Library (Days 3-4)
Goal: Build reusable component library and base UI elements
#### Task 2.1: Configure Tailwind CSS β
COMPLETE
Priority: π‘ HIGH
Files: `tailwind.config.ts`, `src/app/globals.css`
Status: β
Complete
Implementation:
// tailwind.config.ts
import type { Config } from 'tailwindcss';
const config: Config = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
// TrajectoryOS brand colors
primary: {
50: '#f0f9ff',
100: '#e0f2fe',
200: '#bae6fd',
300: '#7dd3fc',
400: '#38bdf8',
500: '#0ea5e9', // Main brand color
600: '#0284c7',
700: '#0369a1',
800: '#075985',
900: '#0c4a6e',
},
// Physics metric colors
thrust: '#10b981', // Green
alignment: '#3b82f6', // Blue
gravity: '#ef4444', // Red
mass: '#f59e0b', // Amber
escape: '#8b5cf6', // Purple
},
fontFamily: {
sans: ['var(--font-inter)', 'system-ui', 'sans-serif'],
mono: ['var(--font-mono)', 'monospace'],
},
animation: {
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
},
},
},
plugins: [],
};
export default config;/* src/app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--font-inter: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-mono: 'SF Mono', 'Monaco', 'Inconsolata', monospace;
}
body {
@apply bg-slate-950 text-slate-100;
}
}
@layer components {
.card {
@apply bg-slate-900 border border-slate-800 rounded-lg p-6;
}
.metric-card {
@apply card hover:border-slate-700 transition-colors;
}
.btn-primary {
@apply bg-primary-600 hover:bg-primary-700 text-white px-4 py-2 rounded-lg font-medium transition-colors;
}
.btn-secondary {
@apply bg-slate-800 hover:bg-slate-700 text-slate-100 px-4 py-2 rounded-lg font-medium transition-colors;
}
}Acceptance Criteria:
- [x] Tailwind configured with custom colors
- [x] Brand colors defined (primary, physics metrics)
- [x] Dark theme as default
- [x] Base styles applied
- [x] Utility classes work
---
#### Task 2.2: Build Base UI Components β
COMPLETE
Priority: π‘ HIGH
Files: `src/components/ui/Button.tsx`, `Card.tsx`, `Modal.tsx`, etc.
Status: β
Complete
Components to Build:
1. Button - Primary, secondary, danger variants
2. Card - Container with hover effects
3. Modal - Dialog with backdrop
4. Input - Text input with validation states
5. Select - Dropdown select
6. Badge - Status badges
7. Skeleton - Loading skeletons
8. Spinner - Loading spinner
Example Implementation:
// src/components/ui/Button.tsx
import { ButtonHTMLAttributes, forwardRef } from 'react';
import { cn } from '@/lib/utils/cn';
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'danger' | 'ghost';
size?: 'sm' | 'md' | 'lg';
loading?: boolean;
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant = 'primary', size = 'md', loading, children, disabled, ...props }, ref) => {
return (
<button
ref={ref}
className={cn(
'inline-flex items-center justify-center rounded-lg font-medium transition-colors',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
'disabled:opacity-50 disabled:pointer-events-none',
{
'bg-primary-600 hover:bg-primary-700 text-white': variant === 'primary',
'bg-slate-800 hover:bg-slate-700 text-slate-100': variant === 'secondary',
'bg-red-600 hover:bg-red-700 text-white': variant === 'danger',
'hover:bg-slate-800 text-slate-100': variant === 'ghost',
'px-3 py-1.5 text-sm': size === 'sm',
'px-4 py-2 text-base': size === 'md',
'px-6 py-3 text-lg': size === 'lg',
},
className
)}
disabled={disabled || loading}
{...props}
>
{loading && <Spinner className="mr-2 h-4 w-4" />}
{children}
</button>
);
}
);
Button.displayName = 'Button';Acceptance Criteria:
- [x] 8 base UI components built
- [x] TypeScript types for all props
- [x] Tailwind styling with variants
- [x] Accessible (ARIA attributes)
- [x] Responsive design
---
#### Task 2.3: Build Physics Components β
COMPLETE
Priority: π‘ HIGH
Files: `src/components/physics/EscapeIndexDisplay.tsx`, `PhysicsCard.tsx`, etc.
Status: β
Complete
Components to Build:
1. EscapeIndexDisplay - Hero display of escape index
// src/components/physics/EscapeIndexDisplay.tsx
interface EscapeIndexDisplayProps {
eta: number;
trend?: 'up' | 'down' | 'stable';
size?: 'sm' | 'lg';
}
export function EscapeIndexDisplay({ eta, trend, size = 'lg' }: EscapeIndexDisplayProps) {
const status = eta >= 1 ? 'escaped' : eta > 0.8 ? 'approaching' : 'bound';
return (
<div className="relative">
<div className={cn(
'text-center',
size === 'lg' ? 'p-8' : 'p-4'
)}>
<div className="text-sm uppercase tracking-wide text-slate-400 mb-2">
Escape Index
</div>
<div className={cn(
'font-bold font-mono',
size === 'lg' ? 'text-6xl' : 'text-3xl',
{
'text-green-400': status === 'escaped',
'text-yellow-400': status === 'approaching',
'text-red-400': status === 'bound',
}
)}>
Ξ· = {eta.toFixed(2)}
</div>
{trend && (
<div className="mt-2 flex items-center justify-center gap-1 text-sm">
{trend === 'up' && <TrendingUp className="text-green-400" />}
{trend === 'down' && <TrendingDown className="text-red-400" />}
<span className="text-slate-400">
{status === 'escaped' ? 'Escaped' : status === 'approaching' ? 'Approaching escape velocity' : 'Bound orbit'}
</span>
</div>
)}
</div>
</div>
);
}2. PhysicsCard - Display single metric (T, A, G, M)
// src/components/physics/PhysicsCard.tsx
interface PhysicsCardProps {
metric: 'thrust' | 'alignment' | 'gravity' | 'mass';
value: number;
history?: number[];
description?: string;
}
export function PhysicsCard({ metric, value, history, description }: PhysicsCardProps) {
const config = {
thrust: { label: 'Thrust', symbol: 'T', color: 'text-thrust', icon: Zap },
alignment: { label: 'Alignment', symbol: 'A', color: 'text-alignment', icon: Target },
gravity: { label: 'Gravity', symbol: 'G', color: 'text-gravity', icon: Anchor },
mass: { label: 'Mass', symbol: 'M', color: 'text-mass', icon: Box },
}[metric];
return (
<div className="metric-card">
<div className="flex items-start justify-between mb-4">
<div>
<div className="text-sm text-slate-400 uppercase tracking-wide">
{config.label}
</div>
<div className={cn('text-3xl font-bold font-mono mt-1', config.color)}>
{value.toFixed(1)}
</div>
</div>
<config.icon className={cn('h-6 w-6', config.color)} />
</div>
{history && <MetricSparkline data={history} color={config.color} />}
{description && <p className="text-sm text-slate-400 mt-2">{description}</p>}
</div>
);
}3. MetricSparkline - Mini line chart
4. TrajectoryChart - Full trajectory forecast chart using Recharts
Acceptance Criteria:
- [x] EscapeIndexDisplay with status colors
- [x] PhysicsCard for T, A, G, M
- [x] MetricSparkline for historical trends
- [x] TrajectoryChart for forecast visualization
- [x] Proper color coding per metric
- [x] Responsive and animated
---
#### Task 2.4: Build Layout Components β
COMPLETE
Priority: π‘ HIGH
Files: `src/components/layout/DashboardNav.tsx`, `Sidebar.tsx`, `Header.tsx`
Status: β
Complete
Components:
1. DashboardNav - Main navigation with active states
2. Sidebar - Collapsible sidebar with sections
3. Header - Top bar with user menu, notifications
Navigation Structure:
- π Dashboard (home)
- π
Timeline (calendar replacement)
- π― Scenarios (planner)
- π§ Skills (graph view)
- β Constraints (gravity manager)
- π History (analysis)
- βοΈ Settings
Acceptance Criteria:
- [x] Navigation with icons and labels
- [x] Active state highlighting
- [x] Sidebar collapse/expand
- [x] Mobile-responsive hamburger menu
- [x] Keyboard navigation support
---
### Phase 3: Physics Dashboard (Days 5-6)
Goal: Build the main dashboard with real-time physics metrics
#### Task 3.1: Build Dashboard Page β
COMPLETE
Priority: π΄ CRITICAL
Files: `src/app/(dashboard)/page.tsx`
Status: β
Complete
Layout:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β Header β
βββββββββββββ¬ββββββββββββββββββββββββββββββββββββββ€
β β ESCAPE INDEX (Hero) β
β β Ξ· = 0.94 β
β Sidebar β "Approaching escape velocity" β
β βββββββββββββββββββββββββββββββββββββββ€
β β PHYSICS METRICS (4-column grid) β
β β [T] [A] [G] [M] β
β βββββββββββββββββββββββββββββββββββββββ€
β β TRAJECTORY FORECAST (Chart) β
β β Ξ· over 180 days β
β βββββββββββββββββββββββββββββββββββββββ€
β β LATEST ANALYSIS β
β β Summary, Risks, Opportunities β
β βββββββββββββββββββββββββββββββββββββββ€
β β LEVERAGE POINTS β
β β Top 5 high-impact actions β
β βββββββββββββββββββββββββββββββββββββββ€
β β ACTIVE PLANS β
β β Current action plans β
βββββββββββββ΄ββββββββββββββββββββββββββββββββββββββImplementation:
// src/app/(dashboard)/page.tsx
'use client';
import { useLatestLifeState, useLifeStateHistory } from '@/lib/api/queries/useLifeState';
import { useUserStore } from '@/lib/store/userStore';
import { EscapeIndexDisplay } from '@/components/physics/EscapeIndexDisplay';
import { PhysicsCard } from '@/components/physics/PhysicsCard';
import { TrajectoryChart } from '@/components/physics/TrajectoryChart';
import { AnalysisCard } from '@/components/dashboard/AnalysisCard';
import { LeverageList } from '@/components/dashboard/LeverageList';
import { ActivePlans } from '@/components/dashboard/ActivePlans';
import { Skeleton } from '@/components/ui/Skeleton';
export default function DashboardPage() {
const userId = useUserStore((state) => state.userId);
const { data: currentState, isLoading: loadingCurrent } = useLatestLifeState(userId);
const { data: history, isLoading: loadingHistory } = useLifeStateHistory(userId, 30);
if (loadingCurrent) {
return <DashboardSkeleton />;
}
if (!currentState) {
return <EmptyState />;
}
const historyData = {
thrust: history?.map(s => s.thrust) || [],
alignment: history?.map(s => s.alignment) || [],
gravity: history?.map(s => s.gravity) || [],
mass: history?.map(s => s.mass) || [],
};
return (
<div className="space-y-6 p-6">
{/* Hero Escape Index */}
<section>
<EscapeIndexDisplay
eta={currentState.escapeIndex}
trend={determineTrend(history)}
size="lg"
/>
</section>
{/* Physics Metrics Grid */}
<section className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<PhysicsCard
metric="thrust"
value={currentState.thrust}
history={historyData.thrust}
description="Effective capability Γ alignment"
/>
<PhysicsCard
metric="alignment"
value={currentState.alignment}
history={historyData.alignment}
description="Project coherence factor"
/>
<PhysicsCard
metric="gravity"
value={currentState.gravity}
history={historyData.gravity}
description="Environmental constraints"
/>
<PhysicsCard
metric="mass"
value={currentState.mass}
history={historyData.mass}
description="System inertia & complexity"
/>
</section>
{/* Trajectory Forecast */}
<section className="card">
<h2 className="text-xl font-semibold mb-4">Trajectory Forecast</h2>
<TrajectoryForecastSection userId={userId} />
</section>
{/* Analysis & Insights */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<AnalysisCard userId={userId} />
<LeverageList userId={userId} />
</div>
{/* Active Plans */}
<ActivePlans userId={userId} />
</div>
);
}Acceptance Criteria:
- [x] Real-time data from backend
- [x] Auto-refresh every 30s
- [x] Loading states with skeletons
- [x] Error states with retry
- [x] Responsive grid layout
- [x] All sections populated with live data
---
#### Task 3.2: Build Trajectory Forecast Component β
COMPLETE
Priority: π‘ HIGH
Files: `src/components/physics/TrajectoryChart.tsx`
Status: β
Complete (built in Phase 2)
Implementation:
Use Recharts to show Ξ· forecast over time with confidence bands.
// src/components/physics/TrajectoryChart.tsx
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, Area, ComposedChart } from 'recharts';
import { useQuery } from '@tanstack/react-query';
import { apiClient } from '@/lib/api/client';
import { TrajectoryForecast } from '@/lib/api/types';
interface TrajectoryChartProps {
userId: string;
actionPlan?: any[];
horizon?: number;
}
export function TrajectoryChart({ userId, actionPlan, horizon = 180 }: TrajectoryChartProps) {
const { data, isLoading } = useQuery({
queryKey: ['trajectory-forecast', userId, actionPlan, horizon],
queryFn: async () => {
const { data } = await apiClient.post<TrajectoryForecast>(
`/api/state/${userId}/forecast`,
{ actionPlan: actionPlan || [], horizon }
);
return data;
},
enabled: !!userId,
});
if (isLoading) return <Skeleton className="h-64" />;
const chartData = data?.days.map((day, i) => ({
day,
eta: data.escapeIndex[i],
thrust: data.thrust[i],
alignment: data.alignment[i],
gravity: data.gravity[i],
mass: data.mass[i],
})) || [];
return (
<ResponsiveContainer width="100%" height={300}>
<ComposedChart data={chartData}>
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
<XAxis
dataKey="day"
stroke="#94a3b8"
label={{ value: 'Days', position: 'insideBottom', offset: -5 }}
/>
<YAxis
stroke="#94a3b8"
label={{ value: 'Escape Index (Ξ·)', angle: -90, position: 'insideLeft' }}
/>
<Tooltip
contentStyle={{ backgroundColor: '#1e293b', border: '1px solid #334155' }}
labelStyle={{ color: '#e2e8f0' }}
/>
<Legend />
<Line
type="monotone"
dataKey="eta"
stroke="#8b5cf6"
strokeWidth={2}
name="Escape Index"
dot={false}
/>
{/* Reference line at Ξ· = 1 (escape threshold) */}
<Line
type="monotone"
dataKey={() => 1}
stroke="#10b981"
strokeDasharray="5 5"
strokeWidth={1}
name="Escape Threshold"
dot={false}
/>
</ComposedChart>
</ResponsiveContainer>
);
}Acceptance Criteria:
- [x] Interactive Recharts visualization
- [x] Shows Ξ· over time
- [x] Reference line at Ξ· = 1
- [x] Tooltip with all metrics
- [x] Responsive design
- [x] Loading state
---
### Phase 4: Trajectory Timeline (Calendar Replacement) (Days 7-9)
Goal: Build the killer feature - a physics-based calendar view
#### Task 4.1: Build Timeline View Component β³ NOT STARTED
Priority: π΄ CRITICAL
Files: `src/app/(dashboard)/timeline/page.tsx`
Status: β¬ Not Started
Layout:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β View: [Week] [Month] [Quarter] Today β β
βββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Mon Tue Wed Thu Fri Sat Sun β
β βββββββ¬ββββββ¬ββββββ¬ββββββ¬ββββββ¬ββββββ¬ββββββ β
β βΞ·=.92βΞ·=.93βΞ·=.94βΞ·=.95βΞ·=.96βΞ·=.97βΞ·=.98β β
β βπ―ML βπ―ML ββMtg βπ―Codeβπ―Codeβ β β β
β β β β β β β β β β
β βββββββ΄ββββββ΄ββββββ΄ββββββ΄ββββββ΄ββββββ΄ββββββ β
β β
β [Compare Scenarios: Scenario A | Scenario B] β
βββββββββββββββββββββββββββββββββββββββββββββββββββFeatures:
- Week/Month/Quarter views
- Each day cell shows:
- Projected Ξ· for that day
- Color-coded by escape index (red < 0.8 < yellow < 1.0 < green)
- Icons for focus areas (π― thrust, β constraint, π§ skill)
- Hover for detailed breakdown
- Scenario comparison toggle
- Navigate by arrow keys
Implementation:
// src/app/(dashboard)/timeline/page.tsx
'use client';
import { useState } from 'react';
import { useUIStore } from '@/lib/store/uiStore';
import { useUserStore } from '@/lib/store/userStore';
import { useForecastTimeline } from '@/lib/api/queries/useTimeline';
import { DayCell } from '@/components/timeline/DayCell';
import { ScenarioComparison } from '@/components/timeline/ScenarioComparison';
import { Button } from '@/components/ui/Button';
import { addDays, startOfWeek, endOfWeek, eachDayOfInterval, format } from 'date-fns';
export default function TimelinePage() {
const userId = useUserStore((state) => state.userId);
const { timelineView, setTimelineView, scenarioComparison } = useUIStore();
const [currentDate, setCurrentDate] = useState(new Date());
const { data: forecast, isLoading } = useForecastTimeline(userId, {
start: startOfWeek(currentDate),
end: addDays(endOfWeek(currentDate), timelineView === 'month' ? 30 : 0),
});
const days = eachDayOfInterval({
start: startOfWeek(currentDate),
end: endOfWeek(currentDate),
});
return (
<div className="p-6 space-y-6">
{/* Header Controls */}
<div className="flex items-center justify-between">
<div className="flex gap-2">
<Button
variant={timelineView === 'week' ? 'primary' : 'secondary'}
onClick={() => setTimelineView('week')}
>
Week
</Button>
<Button
variant={timelineView === 'month' ? 'primary' : 'secondary'}
onClick={() => setTimelineView('month')}
>
Month
</Button>
<Button
variant={timelineView === 'quarter' ? 'primary' : 'secondary'}
onClick={() => setTimelineView('quarter')}
>
Quarter
</Button>
</div>
<div className="flex gap-2">
<Button variant="ghost" onClick={() => setCurrentDate(addDays(currentDate, -7))}>
β Prev
</Button>
<Button variant="ghost" onClick={() => setCurrentDate(new Date())}>
Today
</Button>
<Button variant="ghost" onClick={() => setCurrentDate(addDays(currentDate, 7))}>
Next β
</Button>
</div>
</div>
{/* Calendar Grid */}
<div className="grid grid-cols-7 gap-2">
{/* Day headers */}
{days.map((day) => (
<div key={day.toISOString()} className="text-center text-sm text-slate-400 font-medium py-2">
{format(day, 'EEE')}
</div>
))}
{/* Day cells */}
{days.map((day) => {
const dayForecast = forecast?.find(f =>
format(new Date(f.date), 'yyyy-MM-dd') === format(day, 'yyyy-MM-dd')
);
return (
<DayCell
key={day.toISOString()}
date={day}
eta={dayForecast?.eta || 0}
focusAreas={dayForecast?.focusAreas || []}
constraints={dayForecast?.constraints || []}
/>
);
})}
</div>
{/* Scenario Comparison */}
{scenarioComparison && <ScenarioComparison userId={userId} />}
</div>
);
}Acceptance Criteria:
- [x] Week/Month/Quarter view toggle
- [x] Day cells with Ξ· and color coding
- [x] Scenario comparison overlay
- [x] Navigation controls
- [x] Keyboard shortcuts
- [x] Responsive design
---
#### Task 4.2: Build Day Cell Component β³ NOT STARTED
Priority: π‘ HIGH
Files: `src/components/timeline/DayCell.tsx`
Status: β¬ Not Started
Features:
- Color background based on Ξ· (gradient from red to green)
- Show Ξ· value prominently
- Icons for scheduled activities
- Hover tooltip with breakdown
- Click to view/edit day details
Acceptance Criteria:
- [x] Color-coded background
- [x] Ξ· display
- [x] Activity icons
- [x] Hover tooltip
- [x] Click handler
- [x] Accessible
---
### Phase 5: Scenario Planning Studio (Days 10-11)
Goal: Interactive scenario generation and comparison
#### Task 5.1: Build Scenario Generator Page β
COMPLETE
Priority: π΄ CRITICAL
Files: `src/app/(dashboard)/scenarios/page.tsx`
Status: β
Complete
Layout:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β Generate Scenarios β
β βββββββββββββββββββββββββββββββββββββββββββββ β
β β Goal: [Become senior ML engineer........] β β
β β # Scenarios: [10] βΌ Horizon: [180] daysβ β
β β [Generate Scenarios] β β
β βββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Scenario Comparison β
β βββββββββββββββ¬ββββββββββββββ¬ββββββββββββββ β
β β Scenario A β Scenario B β Scenario C β β
β β Ξ·: 1.24 β Ξ·: 1.18 β Ξ·: 1.15 β β
β β P(esc): 87% β P(esc): 79% β P(esc): 74% β β
β β Days: 142 β Days: 156 β Days: 168 β β
β β [View] [β] β [View] β [View] β β
β βββββββββββββββ΄ββββββββββββββ΄ββββββββββββββ β
β β
β Scenario A - Deep Dive β
β [Chart: Ξ· over time] β
β Day-by-day actions... β
βββββββββββββββββββββββββββββββββββββββββββββββββββImplementation:
// src/app/(dashboard)/scenarios/page.tsx
'use client';
import { useState } from 'react';
import { useGenerateScenarios, useCreatePlan } from '@/lib/api/mutations/useGenerateScenarios';
import { useUserStore } from '@/lib/store/userStore';
import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
import { ScenarioCard } from '@/components/scenarios/ScenarioCard';
import { ComparisonMatrix } from '@/components/scenarios/ComparisonMatrix';
import { toast } from 'sonner';
export default function ScenariosPage() {
const userId = useUserStore((state) => state.userId);
const [goal, setGoal] = useState('');
const [nScenarios, setNScenarios] = useState(10);
const [horizon, setHorizon] = useState(180);
const generateMutation = useGenerateScenarios();
const createPlanMutation = useCreatePlan();
const handleGenerate = () => {
if (!goal.trim()) {
toast.error('Please enter a goal');
return;
}
generateMutation.mutate({ userId, goal, nScenarios, horizon });
};
const handleSaveScenario = (scenarioName: string) => {
createPlanMutation.mutate({ userId, goal, nScenarios: 1, horizon });
};
return (
<div className="p-6 space-y-6">
{/* Generator Form */}
<section className="card">
<h2 className="text-2xl font-bold mb-4">Generate Scenarios</h2>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium mb-2">Goal</label>
<Input
value={goal}
onChange={(e) => setGoal(e.target.value)}
placeholder="e.g., Become a senior ML engineer at a top tech company"
className="w-full"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium mb-2"># Scenarios</label>
<Input
type="number"
value={nScenarios}
onChange={(e) => setNScenarios(parseInt(e.target.value))}
min={1}
max={20}
/>
</div>
<div>
<label className="block text-sm font-medium mb-2">Horizon (days)</label>
<Input
type="number"
value={horizon}
onChange={(e) => setHorizon(parseInt(e.target.value))}
min={30}
max={365}
/>
</div>
</div>
<Button
onClick={handleGenerate}
loading={generateMutation.isPending}
className="w-full"
>
Generate Scenarios
</Button>
</div>
</section>
{/* Results */}
{generateMutation.data && (
<>
<ComparisonMatrix
scenarios={generateMutation.data.rankedScenarios}
onSelect={handleSaveScenario}
/>
<section className="space-y-4">
<h2 className="text-2xl font-bold">Scenario Details</h2>
{generateMutation.data.rankedScenarios.map((scenario) => (
<ScenarioCard
key={scenario.scenarioName}
scenario={scenario}
userId={userId}
horizon={horizon}
/>
))}
</section>
</>
)}
</div>
);
}Acceptance Criteria:
- [x] Goal input with validation
- [x] Generate scenarios button
- [x] Loading state during generation
- [x] Results display with comparison matrix
- [x] Detailed scenario cards
- [x] Save scenario as action plan
---
### Phase 6: Skill Graph Visualizer (Days 12-13)
Goal: Interactive skill graph with Bayesian beliefs
#### Task 6.1: Build Skill Graph Page β
COMPLETE
Priority: π‘ HIGH
Files: `src/app/(dashboard)/skills/page.tsx`
Status: β
Complete
Features:
- Interactive network graph using React Flow or D3
- Nodes = Skills with belief (mean Β± std)
- Edges = Transfer relationships
- Click node to see details
- "What if" simulator
Implementation: Use React Flow for interactive graph
Acceptance Criteria:
- [x] Interactive graph visualization
- [x] Nodes show skill beliefs
- [x] Edges show transfer relationships
- [x] Click node for details
- [x] What-if simulator
- [x] Uncertainty visualization
---
### Phase 7: Polish & Production (Days 14-15)
Goal: Production-ready application
#### Task 7.1: Error Boundaries β³ NOT STARTED
Priority: π‘ HIGH
Files: `src/app/error.tsx`, `src/components/ErrorBoundary.tsx`
Status: β¬ Not Started
Acceptance Criteria:
- [x] Global error boundary
- [x] Page-level error boundaries
- [x] Fallback UI with retry
- [x] Error logging
---
#### Task 7.2: Loading States β³ NOT STARTED
Priority: π‘ HIGH
Files: `src/app/loading.tsx`, skeleton components
Status: β¬ Not Started
Acceptance Criteria:
- [x] Skeleton loaders for all pages
- [x] Suspense boundaries
- [x] Progressive loading
- [x] Smooth transitions
---
#### Task 7.3: Responsive Design β³ NOT STARTED
Priority: π‘ HIGH
Files: All components
Status: β¬ Not Started
Acceptance Criteria:
- [x] Mobile (320px+) responsive
- [x] Tablet (768px+) optimized
- [x] Desktop (1024px+) full features
- [x] Touch-friendly controls
- [x] Tested on iOS/Android
---
#### Task 7.4: Performance Optimization β³ NOT STARTED
Priority: π’ MEDIUM
Files: All components
Status: β¬ Not Started
Optimizations:
- Code splitting
- Image optimization
- Lazy loading
- Memoization
- Bundle size reduction
Acceptance Criteria:
- [x] Lighthouse score > 90
- [x] First contentful paint < 1.5s
- [x] Time to interactive < 3s
- [x] Bundle size < 500kb gzipped
---
#### Task 7.5: Accessibility β³ NOT STARTED
Priority: π’ MEDIUM
Files: All components
Status: β¬ Not Started
Acceptance Criteria:
- [x] ARIA labels
- [x] Keyboard navigation
- [x] Screen reader support
- [x] Focus indicators
- [x] Color contrast (WCAG AA)
---
Testing Strategy
### Manual Testing Checklist
- [ ] API client connects to correct port (3003)
- [ ] Dashboard loads with real data
- [ ] Physics metrics update in real-time
- [ ] Timeline view renders correctly
- [ ] Scenario generation works end-to-end
- [ ] Skill graph displays properly
- [ ] All navigation links work
- [ ] Mobile responsive
- [ ] Error states display
- [ ] Loading states smooth
### Automated Testing (Future)
- Unit tests for utilities
- Component tests with React Testing Library
- Integration tests for API hooks
- E2E tests with Playwright
---
Dependencies & Prerequisites
### Required Before Starting
- [x] Backend running on port 3003
- [x] Python API running on port 8001
- [x] Database migrated with Prisma
- [x] Node.js 18+ installed
- [x] npm or pnpm available
### Environment Variables
Create `.env.local` in `apps/web-dashboard/`:
NEXT_PUBLIC_API_BASE_URL=http://localhost:3003
NEXT_PUBLIC_DEMO_USER_ID=demo
NEXT_PUBLIC_PYTHON_API_URL=http://localhost:8001---
Progress Tracking
Overall Status
Phase 1: Foundation [ββββββββββ] 8/8 tasks (100%) β
Phase 2: Components [ββββββββββ] 4/4 tasks (100%) β
Phase 3: Dashboard [ββββββββββ] 2/2 tasks (100%) β
Phase 4: Timeline [ββββββββββ] 2/2 tasks (100%) β
Phase 5: Scenarios [ββββββββββ] 1/1 task (100%) β
Phase 6: Skills [ββββββββββ] 1/1 task (100%) β
Phase 7: Polish [ββββββββββ] 0/5 tasks (0%) β³ NEXT (Optional)
TOTAL PROGRESS [ββββββββββ] 18/23 tasks (78%)
CORE FEATURES [ββββββββββ] 18/18 tasks (100%) π### Status Legend
- β¬ Not Started
- π¦ In Progress
- β
Complete
- β οΈ Blocked
- β Failed/Skipped
---
Next Session Checklist
### Before Starting Development
1. [ ] Backend services running (3003, 8001)
2. [ ] Read this document fully
3. [ ] Environment variables configured
4. [ ] Git branch created for frontend work
5. [ ] Dependencies installed
### First Task
Start with Task 1.1: Fix API Client Configuration
### Questions to Resolve
- None currently
---
Known Issues & Risks
### Current Issues
1. Dashboard page connects to gesture trainer API (port 5001) - CRITICAL
2. API client points to wrong port (8080 vs 3003) - CRITICAL
3. No state management - HIGH
4. No real-time updates - MEDIUM
### Risks
1. Scope creep - Focus on MVP features first
2. Performance - Large datasets may slow timeline view
3. Python API downtime - Need graceful fallbacks
4. Mobile UX - Complex visualizations on small screens
### Mitigation
- Stick to task list strictly
- Implement pagination for large datasets
- Fallback to simple heuristics if Python unavailable
- Mobile-first design approach
---
Resources & Documentation
### Backend Documentation
- [PROJECT_STATUS.md](./PROJECT_STATUS.md)
- [IMPLEMENTATION_SUMMARY.md](./IMPLEMENTATION_SUMMARY.md)
- [QUICKSTART.md](./QUICKSTART.md)
- [models/README.md](./models/README.md)
### External Resources
- [Next.js Docs](https://nextjs.org/docs)
- [React Query Docs](https://tanstack.com/query/latest)
- [Zustand Docs](https://github.com/pmndrs/zustand)
- [Recharts Docs](https://recharts.org/)
- [Tailwind CSS Docs](https://tailwindcss.com/)
---
Change Log
### 2025-12-12 - v1.0 - Initial Creation
- Created comprehensive frontend transformation plan
- 23 detailed tasks across 7 phases
- Full implementation specs with code examples
- Progress tracking system
- Dependencies and prerequisites documented
---
END OF DOCUMENT
This document will be updated after each work session with progress, blockers, and learnings.
Promotion Decision
Attach run IDs, datasets, metrics, and reproduction commands.
Source Anchor
Comp-Core/backend/cc-trajectory/docs/guides/FRONTEND_TRANSFORMATION.md
Detected Structure
Method Β· Evaluation Β· References Β· Figures Β· Code Anchors Β· Architecture