Vesper — Implementation Plan
Breaks a journal app build into 5 sequential phases with checkboxes, exit criteria, and file references for each task.
What this file does
Breaks a journal app build into 5 sequential phases with checkboxes, exit criteria, and file references for each task.
When to use it
- Planning a full-stack app with AI features and a clear delivery timeline
- Tracking progress on a multi-phase project with defined exit criteria
- Onboarding a new developer to the exact build order and file responsibilities
- Auditing a project against its original specification after completion
Assumes this stack
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
- Files:
- 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
- Files:
- Create
requirements.txtwith all backend dependencies- File:
backend/requirements.txt - Deps: fastapi, uvicorn, supabase, langchain, google-generativeai, sentence-transformers, reportlab
- File:
1.2 Supabase Configuration
- Create Supabase project and obtain credentials
- Enable
pgvectorextension via SQL editor (CREATE EXTENSION IF NOT EXISTS vector;) - Create
entriestable 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
reportstable 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
- Policy: Users can only SELECT/INSERT/UPDATE/DELETE their own rows (
- Add
updated_atauto-update trigger onentriestable- File:
supabase_init.sql(set_updated_atfunction + trigger)
- File:
- Add pgvector IVFFlat cosine index on
entries.embedding- File:
supabase_init.sql
- File:
- Wire Supabase client in backend (
app/core/supabase.py)- Deps:
supabasePython SDK
- Deps:
1.3 Authentication Flow
- Install
@supabase/supabase-jsandreact-router-domin frontend- File:
frontend/src/lib/supabase.js,frontend/.env.local
- File:
- Build combined Sign Up / Login page with mode toggle
- File:
frontend/src/pages/Auth.jsx
- File:
- Implement sign out functionality
- File:
frontend/src/pages/Dashboard.jsx(Sign Out button)
- File:
- Add session persistence (auto-restore on refresh via
getSession()+onAuthStateChange)- File:
frontend/src/hooks/useAuth.jsx
- File:
- Create auth context/provider to wrap the app
- File:
frontend/src/hooks/useAuth.jsx(AuthProvider+useAuthhook)
- File:
- Add protected route wrapper (redirect to /auth if unauthenticated)
- File:
frontend/src/components/ProtectedRoute.jsx - Deps:
react-router-dom
- File:
1.4 Journal Entry CRUD (Backend)
-
POST /entries— Create new entry- File:
backend/app/api/entries.py
- File:
-
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
- File:
- Create Pydantic models for entry request/response
- File:
backend/app/models/schemas.py(EntryCreate,EntryUpdate,EntryResponse,DeleteResponse)
- File:
- Register entries router in
main.py
1.5 Journal Editor (Frontend)
- Create authenticated API client
- File:
frontend/src/lib/api.js(Bearer token injection viasupabase.auth.getSession())
- File:
- Implement auto-save with 3-second debounce
- File:
frontend/src/hooks/useAutoSave.js(useDebounce+useAutoSavehooks)
- File:
- Build main editor component — textarea with create-or-update auto-save
- File:
frontend/src/components/JournalEditor.jsx
- File:
- Visual save state indicator: Draft → Saving… → Saved
- Add timestamp display and word count in editor toolbar
- Integrate
JournalEditorinto Dashboard- File:
frontend/src/pages/Dashboard.jsx
- File:
1.6 Entry List / History View
- Build scrollable entry list as sidebar (sorted by date, newest first)
- File:
frontend/src/components/Sidebar.jsx
- File:
- Wire edit action: clicking entry loads content + id into
JournalEditor- Implemented via
onSelectEntrycallback + Reactkeyremount pattern inDashboard.jsx
- Implemented via
- 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
- File:
- Set up React Router with core routes (
/auth,/dashboard, wildcard)- File:
frontend/src/App.jsx
- File:
- Core routes expansion (
/entries,/editor,/drift,/reports) — added as features are built - Create nav/sidebar component
- File:
frontend/src/components/Sidebar.jsx
- File:
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
AsyncOpenAISDK- File:
backend/app/services/ai/analyzer.py - Deps:
openai,langchain-openai - Env vars:
LITELLM_API_KEY,LITELLM_BASE_URL,LITELLM_MODEL
- File:
- 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
- Output:
- Add Pydantic output schema with CBT distortion literals and field validation
- File:
backend/app/services/ai/analyzer.py(AnalysisResultmodel)
- File:
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
- File:
- Update entry row in Supabase with analysis results once complete
- Set
analyzed = trueflag 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
- File:
2.3 Sentence-Transformers Embeddings
- Load
all-MiniLM-L6-v2model lazily, cached in memory after first call- File:
backend/app/services/ai/analyzer.py(embed_text(),_get_embedder())
- File:
- Generate 384-dim vector embedding per entry concurrently with Gemini call
- Store embedding in
embeddingcolumn (pgvector) viaasyncio.gather()
2.4 Insight Panel UI
- Build right-side insight panel component
- File:
frontend/src/components/InsightPanel.jsx
- File:
- 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}/analysisevery 3s untilanalyzed: true- File:
frontend/src/hooks/useAnalysis.js
- File:
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 INVOKERsoauth.uid()scopes results to the caller
- File:
-
POST /entries/searchendpoint — embeds query, calls RPC, returns ranked results- File:
backend/app/api/entries.py
- File:
-
searchEntries(query, limit)API client function- File:
frontend/src/lib/api.js
- File:
- Search input in Sidebar with 500ms debounce
- File:
frontend/src/components/Sidebar.jsx
- File:
- 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
- File:
-
GET /drift/timeline?theme=X— mood scores chronologically (optional theme filter)- File:
backend/app/api/drift.py
- File:
-
/drift/search— covered by Phase 2.5'sPOST /entries/search(reused)
3.2 Drift Timeline UI
- Build Drift Timeline page
- File:
frontend/src/pages/DriftTimeline.jsx - Deps:
recharts(installed)
- File:
- 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
-
/driftroute added toApp.jsx - Journal / Drift nav toggle added to
Sidebar.jsxbottom 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
- Output:
-
POST /reports/generate— synthesise AI report, save to Supabase, return- File:
backend/app/api/reports.py
- File:
-
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
- File:
-
GET /reports/{id}/pdf— runs ReportLab in thread pool, streams asapplication/pdf(triggers download)- File:
backend/app/api/reports.py
- File:
3.5 Weekly Report UI
-
ReportCard— emotion badge, theme pills, arc excerpt, observation, PDF download button- File:
frontend/src/components/ReportCard.jsx
- File:
- Reports page with
Generate New Reportbutton, 2-column card grid, loading skeleton, empty state- File:
frontend/src/pages/Reports.jsx
- File:
-
/reportsroute added toApp.jsx - Reports tab added to Sidebar 3-column nav (Journal / Drift / Reports)
-
downloadReportPdfuses 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
- File:
- Streak counter (flame icon, colour scales by streak length)
- File:
frontend/src/components/StreakCounter.jsx
- File:
- 7-day mood sparkline (recharts, no axes/grid, mini tooltip)
- File:
frontend/src/components/MoodSparkline.jsx
- File:
- Quick access links: New Entry, Drift Timeline, Generate Report
- File:
frontend/src/components/QuickLinks.jsx
- File:
- Dashboard stats bar integrated above editor
- File:
frontend/src/pages/Dashboard.jsx
- File:
- 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)
- File:
- 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 (
insightPulsekeyframe) - 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-visiblerings)
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.yamlcreated (IaC — Render detects automatically) - Environment variables documented — see
backend/.env.example - CORS updated to
FRONTEND_URLenv var (LiteLLM/OpenAI, not Gemini) - Connect repo to Render + set env vars (manual step)
5.3 Environment Configuration
- Separate
.envfor development and production - No API keys committed —
.gitignoreaudited and patched in frontend- File:
.gitignore
- File:
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
- File:
What's inside
5 phases, 20 sub-sections, each with task checkboxes, file paths, and exit criteria
Change this for your project
- Replace
di5ha/vesper-journalwith your own repository name - Replace
LITELLM_API_KEY,LITELLM_BASE_URL,LITELLM_MODELenv vars with your LLM provider config - Replace
all-MiniLM-L6-v2with your chosen embedding model if different - Replace
FRONTEND_URLin CORS config with your deployed frontend domain
Where it goes
Keep it in your repository where the agent or team that needs it will read it.
Worth borrowing
- Checkbox-based task list with explicit file paths ties each step to a deliverable
- Exit criteria at phase start prevents moving on before a feature is fully working
- Background task pattern for AI analysis keeps API responses fast while processing happens asynchronously
Related Documents
Building SupportX AI Assist: A Multi-Agent IT Support System
Describes building a multi-agent IT support system with AutoGen, Azure AI Search, and Gemini embeddings for instant issue resolution and automatic escalation.
Intelligent Document Query Platform — GitHub-ready Low-Level Design (LLD)
Provides a copy-ready low-level design for a serverless document query platform with vector search and LLM integration.
Graph Matching with Topological Features
Teaches enhanced graph matching by combining spatial distances with node2vec and commute times embeddings, then applying the Hungarian algorithm.
Pulse — Life Cofounder | Build Log
Documents a full-stack monorepo that ingests LinkedIn and GitHub data, generates embeddings in-browser, and provides a RAG chat with an AI cofounder.