Grand Diomande Research Β· Full HTML 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%)

Embodied Trajectory Systems proposal experiment writeup candidate score 52 .md

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:

typescript
// 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:

bash
# 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:

json
{
  "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:

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

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

typescript
// 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,
    },
  },
});
typescript
// 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.

typescript
// 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:

typescript
// 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,
  });
};
typescript
// 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:

typescript
// 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,
  });
};
typescript
// 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:

typescript
// 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,
  });
};
typescript
// 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:

typescript
// 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',
    }
  )
);
typescript
// 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:

typescript
// 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;
css
/* 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:

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

typescript
// 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)

typescript
// 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:

typescript
// 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.

typescript
// 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:

typescript
// 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:

typescript
// 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/`:

bash
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