Back to .md Directory

Vesper — Implementation Plan

> Source of truth for what to build. Every task must be checked off before moving to the next.

May 2, 2026
0 downloads
0 views
ai
View source

Vesper — Implementation Plan

Source of truth for what to build. Every task must be checked off before moving to the next.


Phase 1: Foundation

Goal: Project scaffold, Supabase config, auth flow, basic journal CRUD. Exit Criteria: User can sign up, log in, write entries, view/edit/delete them. Sessions persist. Data scoped via RLS.

1.1 Project Scaffold

  • Initialize React 19 + Vite frontend with Tailwind CSS v4
    • Files: frontend/vite.config.js, frontend/src/index.css
    • Deps: react, react-dom, tailwindcss, @tailwindcss/vite
  • Create frontend directory structure (/src/components, /src/pages, /src/lib, /src/hooks)
  • Initialize FastAPI backend with modular structure (/app/api, /app/core, /app/models, /app/services/ai)
    • Files: backend/app/main.py, backend/app/core/config.py
  • Create requirements.txt with all backend dependencies
    • File: backend/requirements.txt
    • Deps: fastapi, uvicorn, supabase, langchain, google-generativeai, sentence-transformers, reportlab

1.2 Supabase Configuration

  • Create Supabase project and obtain credentials
  • Enable pgvector extension via SQL editor (CREATE EXTENSION IF NOT EXISTS vector;)
  • Create entries table with schema from PRD
    • Schema: id (uuid PK), user_id (uuid FK), content (text), created_at, updated_at, mood_score (float), themes (text[]), distortions (jsonb), observation (text), embedding (vector(384)), analyzed (boolean)
    • File: supabase_init.sql
  • Create reports table with schema from PRD
    • Schema: id (uuid PK), user_id (uuid FK), created_at, week_start (date), dominant_emotion (text), top_themes (text[]), emotional_arc (text), ai_observation (text), pdf_url (text)
    • File: supabase_init.sql
  • Configure Row Level Security (RLS) policies on both tables
    • Policy: Users can only SELECT/INSERT/UPDATE/DELETE their own rows (auth.uid() = user_id)
    • File: supabase_init.sql
  • Add updated_at auto-update trigger on entries table
    • File: supabase_init.sql (set_updated_at function + trigger)
  • Add pgvector IVFFlat cosine index on entries.embedding
    • File: supabase_init.sql
  • Wire Supabase client in backend (app/core/supabase.py)
    • Deps: supabase Python SDK

1.3 Authentication Flow

  • Install @supabase/supabase-js and react-router-dom in frontend
    • File: frontend/src/lib/supabase.js, frontend/.env.local
  • Build combined Sign Up / Login page with mode toggle
    • File: frontend/src/pages/Auth.jsx
  • Implement sign out functionality
    • File: frontend/src/pages/Dashboard.jsx (Sign Out button)
  • Add session persistence (auto-restore on refresh via getSession() + onAuthStateChange)
    • File: frontend/src/hooks/useAuth.jsx
  • Create auth context/provider to wrap the app
    • File: frontend/src/hooks/useAuth.jsx (AuthProvider + useAuth hook)
  • Add protected route wrapper (redirect to /auth if unauthenticated)
    • File: frontend/src/components/ProtectedRoute.jsx
    • Deps: react-router-dom

1.4 Journal Entry CRUD (Backend)

  • POST /entries — Create new entry
    • File: backend/app/api/entries.py
  • GET /entries — List all entries for current user (newest first)
  • GET /entries/{id} — Get single entry with analysis
  • GET /entries/{id}/analysis — AI analysis polling stub (Phase 2 will activate)
  • PUT /entries/{id} — Update entry content
  • DELETE /entries/{id} — Delete entry
  • Add JWT extraction dependency (Bearer token → RLS passthrough)
    • File: backend/app/core/auth.py
  • Create Pydantic models for entry request/response
    • File: backend/app/models/schemas.py (EntryCreate, EntryUpdate, EntryResponse, DeleteResponse)
  • Register entries router in main.py

1.5 Journal Editor (Frontend)

  • Create authenticated API client
    • File: frontend/src/lib/api.js (Bearer token injection via supabase.auth.getSession())
  • Implement auto-save with 3-second debounce
    • File: frontend/src/hooks/useAutoSave.js (useDebounce + useAutoSave hooks)
  • Build main editor component — textarea with create-or-update auto-save
    • File: frontend/src/components/JournalEditor.jsx
  • Visual save state indicator: Draft → Saving… → Saved
  • Add timestamp display and word count in editor toolbar
  • Integrate JournalEditor into Dashboard
    • File: frontend/src/pages/Dashboard.jsx

1.6 Entry List / History View

  • Build scrollable entry list as sidebar (sorted by date, newest first)
    • File: frontend/src/components/Sidebar.jsx
  • Wire edit action: clicking entry loads content + id into JournalEditor
    • Implemented via onSelectEntry callback + React key remount pattern in Dashboard.jsx
  • Wire delete action: trash icon with confirm()DELETE /entries/{id} → removes from list
  • Loading skeleton and empty state in Sidebar

1.7 Basic Dashboard Shell

  • Build placeholder dashboard (completed ahead of schedule in Phase 1.3)
    • File: frontend/src/pages/Dashboard.jsx
  • Set up React Router with core routes (/auth, /dashboard, wildcard)
    • File: frontend/src/App.jsx
  • Core routes expansion (/entries, /editor, /drift, /reports) — added as features are built
  • Create nav/sidebar component
    • File: frontend/src/components/Sidebar.jsx

Phase 2: Intelligence

Goal: AI analysis engine, embeddings, insight panel. Exit Criteria: Every saved entry returns structured AI insights (mood, themes, distortions, observation). Embeddings stored.

2.1 LLM Setup — LiteLLM / OpenAI

  • Configure LiteLLM proxy integration using AsyncOpenAI SDK
    • File: backend/app/services/ai/analyzer.py
    • Deps: openai, langchain-openai
    • Env vars: LITELLM_API_KEY, LITELLM_BASE_URL, LITELLM_MODEL
  • Create analysis prompt with structured JSON output (System + User messages)
    • Output: {mood_score: float, themes: list[str], distortions: list[str], observation: str}
    • Uses response_format={"type": "json_object"} for reliable parsing
  • Add Pydantic output schema with CBT distortion literals and field validation
    • File: backend/app/services/ai/analyzer.py (AnalysisResult model)

2.2 Async Analysis Pipeline

  • Trigger background analysis task after entry save (POST + PUT)
    • File: backend/app/services/ai/pipeline.py + backend/app/api/entries.py
    • Uses FastAPI BackgroundTasks — HTTP response sent before analysis runs
  • Update entry row in Supabase with analysis results once complete
  • Set analyzed = true flag on entry after successful analysis
  • Handle failures: entry saved even if AI fails; stores "Analysis unavailable" fallback
  • GET /entries/{id}/analysis — Polling endpoint live (returns analyzed flag + fields)
    • File: backend/app/api/entries.py

2.3 Sentence-Transformers Embeddings

  • Load all-MiniLM-L6-v2 model lazily, cached in memory after first call
    • File: backend/app/services/ai/analyzer.py (embed_text(), _get_embedder())
  • Generate 384-dim vector embedding per entry concurrently with Gemini call
  • Store embedding in embedding column (pgvector) via asyncio.gather()

2.4 Insight Panel UI

  • Build right-side insight panel component
    • File: frontend/src/components/InsightPanel.jsx
  • Display mood score with visual SVG arc (HSL colour-coded 1-10)
  • Display themes as styled violet pill tags
  • Display cognitive distortions as amber warning cards
  • Display AI observation in italic text
  • Show pulsing loading skeleton while analysis is running
  • Show "No distortions flagged" emerald state when none detected
  • Show "Analysis unavailable" fallback (handled by backend pipeline)
  • Poll /entries/{id}/analysis every 3s until analyzed: true
    • File: frontend/src/hooks/useAnalysis.js

2.5 Semantic Search

  • Supabase RPC match_entries(query_embedding, match_count) — pgvector cosine similarity
    • File: match_entries_rpc.sql (run in Supabase SQL Editor)
    • Uses SECURITY INVOKER so auth.uid() scopes results to the caller
  • POST /entries/search endpoint — embeds query, calls RPC, returns ranked results
    • File: backend/app/api/entries.py
  • searchEntries(query, limit) API client function
    • File: frontend/src/lib/api.js
  • Search input in Sidebar with 500ms debounce
    • File: frontend/src/components/Sidebar.jsx
  • Swap chronological list for semantic results when query is active
  • Similarity percentage badge on each search result row
  • Clear (×) button to return to normal timeline view

Phase 3: Differentiation

Goal: Drift Timeline, Weekly Reports, PDF export, dashboard completion. Exit Criteria: Timeline renders with theme filtering. Reports work end-to-end with PDF download.

3.1 Drift Timeline Backend

  • GET /drift/themes — all distinct themes across user's entries
    • File: backend/app/api/drift.py
  • GET /drift/timeline?theme=X — mood scores chronologically (optional theme filter)
    • File: backend/app/api/drift.py
  • /drift/search — covered by Phase 2.5's POST /entries/search (reused)

3.2 Drift Timeline UI

  • Build Drift Timeline page
    • File: frontend/src/pages/DriftTimeline.jsx
    • Deps: recharts (installed)
  • Clickable theme pills filter timeline (active pill highlighted in violet)
  • Recharts AreaChart with violet gradient fill; colour-coded dots (red/amber/lime/emerald)
  • Custom tooltip: date, mood score, observation snippet, theme mini-pills
  • Average mood callout in top-right corner
  • Empty + loading states with graceful messaging
  • /drift route added to App.jsx
  • Journal / Drift nav toggle added to Sidebar.jsx bottom nav

3.3 Weekly Report Backend

  • LiteLLM chain: ingest last 7 entries → structured report
    • Output: {dominant_emotion, top_themes[], emotional_arc, ai_observation}
    • File: backend/app/services/ai/report.py
  • POST /reports/generate — synthesise AI report, save to Supabase, return
    • File: backend/app/api/reports.py
  • GET /reports — list all past reports (newest first)
  • GET /reports/{id} — get single report

3.4 PDF Export

  • ReportLab layout: title, emotion tile, theme bullets, arc paragraph, italic observation box
    • File: backend/app/services/pdf.py
  • GET /reports/{id}/pdf — runs ReportLab in thread pool, streams as application/pdf (triggers download)
    • File: backend/app/api/reports.py

3.5 Weekly Report UI

  • ReportCard — emotion badge, theme pills, arc excerpt, observation, PDF download button
    • File: frontend/src/components/ReportCard.jsx
  • Reports page with Generate New Report button, 2-column card grid, loading skeleton, empty state
    • File: frontend/src/pages/Reports.jsx
  • /reports route added to App.jsx
  • Reports tab added to Sidebar 3-column nav (Journal / Drift / Reports)
  • downloadReportPdf uses blob URL — triggers native browser download without navigation

3.6 Dashboard Completion

  • GET /dashboard/stats — streak, 7-day sparkline, latest analysis
    • File: backend/app/api/dashboard.py
  • Streak counter (flame icon, colour scales by streak length)
    • File: frontend/src/components/StreakCounter.jsx
  • 7-day mood sparkline (recharts, no axes/grid, mini tooltip)
    • File: frontend/src/components/MoodSparkline.jsx
  • Quick access links: New Entry, Drift Timeline, Generate Report
    • File: frontend/src/components/QuickLinks.jsx
  • Dashboard stats bar integrated above editor
    • File: frontend/src/pages/Dashboard.jsx
  • Stats refresh on every entry save (via refreshTick)

Phase 4: Frontend & Design

Goal: UI polish, v0 components, responsive layout, motion, accessibility. Exit Criteria: App looks production-quality, consistent across pages, no placeholder UI.

4.1 Visual Identity

  • Define color palette, typography, motion principles
    • File: frontend/src/index.css (Tailwind theme configuration)
  • Web fonts — Lora (serif) imported via Google Fonts, Inter via system stack

4.2 Component Design

  • Design and integrate: auth pages (login/signup + full landing page)
  • Design and integrate: journal editor layout with insight panel
  • Design and integrate: Drift Timeline page
  • Design and integrate: Weekly Report card
  • Design and integrate: Dashboard layout

4.3 Responsive Layout

  • Ensure all pages work on desktop (primary) and tablet screens
  • Sidebar collapses cleanly on smaller viewports (hamburger toggle at ≤900px)

4.4 Motion & Transitions

  • Subtle breathing animations on insight cards (insightPulse keyframe)
  • Chart animate-in on Drift Timeline (recharts built-in)
  • Smooth page transitions — framer-motion AnimatePresence

4.5 Empty States & Edge Cases

  • Empty state: no entries yet (WelcomeCenter)
  • Empty state: no themes detected (Drift)
  • Empty state: report not yet generated (Reports page)
  • Minimum word count prompt for short entries (<20 words)

4.6 Accessibility

  • Keyboard navigation on all interactive elements
  • Sufficient color contrast (WCAG AA teal palette)
  • Focus states on buttons, links, and form fields (:focus-visible rings)

Phase 5: Deployment & QA

Goal: Ship to Vercel + Render, smoke test, README. Exit Criteria: Full app live on production URLs, all features pass smoke testing.

5.1 Frontend Deployment

  • Push frontend to GitHub
  • Connect repo to Vercel (manual step)
  • Configure environment variables — see frontend/.env.example

5.2 Backend Deployment

  • Push backend to GitHub
  • render.yaml created (IaC — Render detects automatically)
  • Environment variables documented — see backend/.env.example
  • CORS updated to FRONTEND_URL env var (LiteLLM/OpenAI, not Gemini)
  • Connect repo to Render + set env vars (manual step)

5.3 Environment Configuration

  • Separate .env for development and production
  • No API keys committed — .gitignore audited and patched in frontend
    • File: .gitignore

5.4 Smoke Testing

  • Smoke test checklist written — File: SMOKE_TEST.md
  • Run smoke tests on live production URLs (post-deploy)

5.5 Bug Fixes

  • Address any issues found during smoke testing

5.6 README & Documentation

  • Comprehensive README written with setup instructions, architecture overview, deployment guide
    • File: README.md

Related Documents