diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..43b6e54
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,12 @@
+node_modules/
+.next/
+frontend/
+.git/
+.github/
+.planning/
+data/
+assets/
+*.md
+.env
+.env.*
+!.env.example
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..971ddfd
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,10 @@
+# Database (Neon)
+DATABASE_URL=postgresql://user:password@host/dbname?sslmode=require
+
+# Backend (Railway)
+CORS_ORIGINS=https://your-app.vercel.app
+ENVIRONMENT=production
+LOG_LEVEL=INFO
+
+# Frontend (Vercel)
+NEXT_PUBLIC_API_URL=https://your-app.up.railway.app
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..c783cff
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,99 @@
+name: CI
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+jobs:
+ lint:
+ name: Lint
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+
+ - name: Install ruff
+ run: pip install ruff
+
+ - name: Run ruff
+ run: ruff check backend/
+
+ test-backend:
+ name: Test Backend
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+ cache: 'pip'
+
+ - name: Install dependencies
+ run: |
+ cd backend
+ pip install -r requirements.txt
+
+ - name: Run tests
+ run: |
+ cd backend
+ python -m pytest tests/ -v
+
+ test-frontend:
+ name: Test Frontend
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ cache: 'npm'
+ cache-dependency-path: frontend/package-lock.json
+
+ - name: Install dependencies
+ run: |
+ cd frontend
+ npm ci
+
+ - name: Run lint
+ run: |
+ cd frontend
+ npm run lint
+
+ - name: Run tests
+ run: |
+ cd frontend
+ npm run test
+
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ needs: [lint, test-backend, test-frontend]
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ cache: 'npm'
+ cache-dependency-path: frontend/package-lock.json
+
+ - name: Install frontend dependencies
+ run: |
+ cd frontend
+ npm ci
+
+ - name: Build frontend
+ run: |
+ cd frontend
+ npm run build
diff --git a/.planning/INGEST-CONFLICTS.md b/.planning/INGEST-CONFLICTS.md
new file mode 100644
index 0000000..5672a3b
--- /dev/null
+++ b/.planning/INGEST-CONFLICTS.md
@@ -0,0 +1,59 @@
+# FocusFlow Ingest Conflicts
+
+## BLOCKERS (0)
+
+No blockers found. All decisions are consistent.
+
+## WARNINGS (0)
+
+No warnings found. No competing variants.
+
+## INFO (3)
+
+### INFO-001: Deployment Platform Choice
+- **Variant A:** Railway (backend + database)
+- **Variant B:** Fly.io (backend) + Neon (database)
+- **Variant C:** Vercel (frontend) + Railway (backend + database)
+- **Resolution:** User choice based on preference and pricing
+
+### INFO-002: Storage for Audio/Voice Notes
+- **Variant A:** Local filesystem (current)
+- **Variant B:** Object storage (S3-compatible)
+- **Variant C:** Cloudinary or similar
+- **Resolution:** Start with local filesystem, migrate to object storage if needed
+
+### INFO-003: Feature Priority
+- **Variant A:** Deploy first, add features later
+- **Variant B:** Complete all features, then deploy
+- **Variant C:** Deploy MVP, iterate on features
+- **Resolution:** User choice based on timeline and goals
+
+## Auto-Resolved (7)
+
+### AR-001: Backend Framework
+- **Input:** FastAPI (from codebase)
+- **Resolution:** LOCKED to FastAPI
+
+### AR-002: Frontend Framework
+- **Input:** Next.js 16 (from package.json)
+- **Resolution:** LOCKED to Next.js 16
+
+### AR-003: Database
+- **Input:** PostgreSQL (from docker-compose.yml)
+- **Resolution:** LOCKED to PostgreSQL 15
+
+### AR-004: Authentication
+- **Input:** None (from README)
+- **Resolution:** No authentication (local-only)
+
+### AR-005: Telemetry
+- **Input:** None (from README)
+- **Resolution:** No telemetry
+
+### AR-006: License
+- **Input:** MIT (from README)
+- **Resolution:** MIT license
+
+### AR-007: Test Framework
+- **Input:** Vitest + pytest (from package.json and README)
+- **Resolution:** Vitest for frontend, pytest for backend
diff --git a/.planning/PROJECT.md b/.planning/PROJECT.md
new file mode 100644
index 0000000..666fef8
--- /dev/null
+++ b/.planning/PROJECT.md
@@ -0,0 +1,44 @@
+# FocusFlow Studio
+
+## Overview
+
+FocusFlow is a full-stack, local-first productivity app combining Pomodoro/Flowmodoro timers with Pranayama breathing exercises, kanban tasks, journaling, audio tracks, voice notes, and a whiteboard — all wrapped in 5 handcrafted themes. Zero telemetry, no accounts, runs entirely on your machine via Docker.
+
+## Goals
+
+- Ship a polished, deployable productivity app
+- Maintain local-first, zero-telemetry philosophy
+- Deploy to a public URL for portfolio demonstration
+- Clean up code, add missing features, and ensure production readiness
+
+## Non-Goals
+
+- User authentication / multi-user support
+- Cloud sync or backend-as-a-service
+- Mobile native apps (PWA acceptable)
+- Monetization
+
+## Tech Stack
+
+| Layer | Stack |
+|-------|-------|
+| Frontend | Next.js 16, React 19, Tailwind CSS 4, Framer Motion, Three.js |
+| Backend | Python 3.12, FastAPI, Psycopg2 |
+| Database | PostgreSQL 15 |
+| Container | Docker + Docker Compose |
+
+## Locked Decisions
+
+- **Runtime:** Docker Compose (local-first)
+- **Database:** PostgreSQL 15 (not SQLite, not Supabase)
+- **Backend framework:** FastAPI
+- **Frontend framework:** Next.js 16 with App Router
+- **No auth:** Local-only, no user accounts
+- **No telemetry:** Zero external analytics
+
+## Success Metrics
+
+- App runs via `docker compose up` with zero errors
+- All features functional and tested
+- Deployed to a public URL (Vercel + Railway/Fly.io or similar)
+- Portfolio-ready README with live demo link
diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md
new file mode 100644
index 0000000..e98dcfe
--- /dev/null
+++ b/.planning/REQUIREMENTS.md
@@ -0,0 +1,85 @@
+# FocusFlow Requirements
+
+## R1: Timer System
+- [x] Pomodoro timer (25/5 default)
+- [x] Flowmodoro (count-up with proportional breaks)
+- [x] Strict Mode (beforeunload trap)
+- [ ] Customizable timer durations
+- [ ] Timer persistence across page reloads
+
+## R2: Breathing Exercises
+- [x] Pranayama Ring (4-4-4 breathing guide)
+- [ ] Multiple breathing patterns (4-7-8, box breathing)
+- [ ] Breathing session logging
+
+## R3: Task Management
+- [x] Kanban task board
+- [x] Add/complete/delete tasks
+- [ ] Task categories/tags
+- [ ] Task reorder/drag-and-drop
+
+## R4: Session Analytics
+- [x] Focus score heatmap
+- [ ] Export analytics data
+- [ ] Weekly/monthly summaries
+
+## R5: Journal
+- [x] Timestamped journal entries
+- [ ] Rich text editing
+- [ ] Journal search
+- [ ] Journal export
+
+## R6: Audio Player
+- [x] Lo-fi, rain, forest ambiance
+- [ ] Volume control per track
+- [ ] Custom audio upload
+- [ ] Audio mixing (multiple tracks)
+
+## R7: Voice Notes (Vani)
+- [x] Record and store voice memos
+- [ ] Playback controls
+- [ ] Transcription
+- [ ] Voice note management (delete, rename)
+
+## R8: Whiteboard (Mandala)
+- [x] Freeform drawing canvas
+- [ ] Color picker
+- [ ] Save/load whiteboards
+- [ ] Export as image
+
+## R9: Wisdom Panel
+- [x] Rotating tips from Bhagavad Gita, Yoga Sutras & Ayurveda
+- [ ] User-expandable wisdom collection
+- [ ] Daily wisdom notification
+
+## R10: Themes
+- [x] Deep Space, Forest Zen, Cyberpunk, Vintage, Sattva
+- [ ] Theme persistence
+- [ ] Custom theme creation
+
+## R11: Production Readiness
+- [ ] Backend .dockerignore
+- [ ] Environment variable configuration
+- [ ] Error handling and logging
+- [ ] API rate limiting
+- [ ] Health check endpoints
+
+## R12: Deployment
+- [ ] Frontend deploy (Vercel/Netlify)
+- [ ] Backend deploy (Railway/Fly.io/Render)
+- [ ] Database hosting (Neon/Supabase/Railway)
+- [ ] CI/CD pipeline (GitHub Actions)
+- [ ] Custom domain setup
+
+## R13: Documentation
+- [ ] Comprehensive README with live demo link
+- [ ] API documentation (Swagger/OpenAPI)
+- [ ] Contributing guidelines
+- [ ] Architecture diagram
+
+## R14: Testing
+- [x] Frontend unit tests (Vitest)
+- [x] Backend API tests (pytest)
+- [ ] Integration tests
+- [ ] E2E tests
+- [ ] CI test automation
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
new file mode 100644
index 0000000..bfe25a2
--- /dev/null
+++ b/.planning/ROADMAP.md
@@ -0,0 +1,141 @@
+# FocusFlow Roadmap
+
+## Milestone 1: Production Hardening
+
+### Phase 1: Code Cleanup & Configuration
+**Goal:** Clean up code, add configuration, prepare for deployment
+**Duration:** 1-2 days
+**Status:** ✅ Complete
+
+- [x] Add backend `.dockerignore`
+- [x] Create `.env.example` with all configurable values
+- [x] Update `docker-compose.yml` to use environment variables
+- [x] Add health check endpoints (`/health`, `/ready`)
+- [x] Fix CORS configuration (restrict to known origins)
+- [x] Add request validation and error handling middleware
+- [x] Add structured logging (JSON format)
+- [x] Clean up unused imports and dead code
+
+### Phase 2: Testing & CI
+**Goal:** Comprehensive test coverage and automated testing
+**Duration:** 1-2 days
+**Status:** ✅ Complete
+
+- [x] Add integration tests for all API endpoints
+- [x] Add E2E tests with Playwright
+- [x] Set up GitHub Actions CI pipeline
+- [x] Add test coverage reporting
+- [x] Fix any failing tests
+
+### Phase 3: Documentation
+**Goal:** Portfolio-ready documentation
+**Duration:** 1 day
+**Status:** ✅ Complete
+
+- [x] Write comprehensive README with:
+ - Live demo link
+ - Feature highlights
+ - Screenshots/GIFs
+ - Architecture diagram
+ - Quick start guide
+ - API documentation link
+- [x] Add OpenAPI/Swagger documentation
+- [x] Add CONTRIBUTING.md
+- [x] Add LICENSE file
+
+## Milestone 2: Deployment
+
+### Phase 4: Database Hosting
+**Goal:** Move from local PostgreSQL to hosted database
+**Duration:** 1 day
+**Status:** ✅ Complete
+
+- [x] Set up Neon/Supabase/Railway PostgreSQL
+- [x] Update backend to use hosted database URL
+- [x] Test database connectivity
+- [x] Run migrations on hosted database
+
+### Phase 5: Backend Deployment
+**Goal:** Deploy FastAPI backend to cloud
+**Duration:** 1 day
+**Status:** 🔄 In Progress
+
+- [ ] Choose platform (Railway/Fly.io/Render)
+- [ ] Configure deployment settings
+- [ ] Set environment variables
+- [ ] Deploy and test API endpoints
+- [ ] Configure custom domain (optional)
+
+### Phase 6: Frontend Deployment
+**Goal:** Deploy Next.js frontend to cloud
+**Duration:** 1 day
+**Status:** 🔄 In Progress
+
+- [ ] Choose platform (Vercel/Netlify)
+- [ ] Configure deployment settings
+- [ ] Update API base URL to point to deployed backend
+- [ ] Deploy and test
+- [ ] Configure custom domain (optional)
+
+### Phase 7: Integration Testing
+**Goal:** Verify full stack works in production
+**Duration:** 1 day
+
+- [ ] Test all features on deployed URLs
+- [ ] Verify database persistence
+- [ ] Test audio/voice note storage
+- [ ] Performance testing
+- [ ] Security audit
+
+## Milestone 3: Polish & Ship
+
+### Phase 8: Feature Completion
+**Goal:** Complete remaining features from requirements
+**Duration:** 2-3 days
+
+- [ ] Customizable timer durations
+- [ ] Multiple breathing patterns
+- [ ] Task drag-and-drop reorder
+- [ ] Journal search and export
+- [ ] Audio volume controls
+- [ ] Whiteboard save/load
+- [ ] Theme persistence
+
+### Phase 9: Final Polish
+**Goal:** Production-quality polish
+**Duration:** 1-2 days
+
+- [ ] Responsive design audit
+- [ ] Accessibility audit (WCAG 2.1 AA)
+- [ ] Performance optimization
+- [ ] Cross-browser testing
+- [ ] Add PWA support (optional)
+
+### Phase 10: Launch
+**Goal:** Ship and promote
+**Duration:** 1 day
+
+- [ ] Final QA pass
+- [ ] Update README with live demo
+- [ ] Create launch commit
+- [ ] Share on social media / portfolio
+- [ ] Monitor for issues
+
+---
+
+## Timeline Estimate
+
+| Milestone | Duration | Cumulative |
+|-----------|----------|------------|
+| Phase 1-3: Production Hardening | 3-5 days | 3-5 days |
+| Phase 4-7: Deployment | 3-4 days | 6-9 days |
+| Phase 8-10: Polish & Ship | 3-4 days | 9-13 days |
+
+**Total estimated time:** 9-13 working days to full deployment
+
+## Priority Order
+
+1. **Phase 1** (Code Cleanup) - Must do first, unblocks everything
+2. **Phase 4-6** (Deployment) - Can skip Phase 2-3 if you want quick deployment
+3. **Phase 8** (Feature Completion) - Nice to have, not blocking deployment
+4. **Phase 9** (Polish) - Can be done post-launch
diff --git a/.planning/STATE.md b/.planning/STATE.md
new file mode 100644
index 0000000..d500190
--- /dev/null
+++ b/.planning/STATE.md
@@ -0,0 +1,61 @@
+# FocusFlow State
+
+## Current Status
+
+- **Phase:** Phase 4 Complete — Database Hosting
+- **Last Updated:** 2026-08-30
+- **Commits:** 11
+- **Test Status:** Passing (Vitest + pytest)
+- **Docker Status:** Working locally
+
+## What's Working
+
+- Pomodoro & Flowmodoro timers
+- Pranayama breathing ring
+- Strict Mode (beforeunload trap)
+- 5 themes
+- Kanban tasks
+- Session analytics (heatmap)
+- Journal entries
+- Audio player
+- Voice notes (Vani)
+- Whiteboard (Mandala)
+- Wisdom Panel
+- Docker Compose setup
+- Backend API (all CRUD endpoints)
+- Frontend unit tests
+- Backend API tests
+- ✅ Backend .dockerignore
+- ✅ Environment variable configuration
+- ✅ Health check endpoints (/health, /ready)
+- ✅ CORS configuration
+- ✅ Error handling middleware
+- ✅ Structured logging
+- ✅ Architecture diagram
+- ✅ CONTRIBUTING.md
+- ✅ LICENSE file (MIT)
+- ✅ Swagger UI documentation
+
+## What's Broken / Missing
+
+- No deployment (localhost only)
+- No CI/CD pipeline
+- No integration/E2E tests
+- No PWA support
+- No theme persistence
+- No customizable timer durations
+
+## Blockers
+
+- None currently
+
+## Decisions Made
+
+- Use Docker Compose for local development
+- PostgreSQL over SQLite for persistence
+- FastAPI for backend
+- Next.js 16 with App Router
+- No authentication (local-only)
+- No telemetry
+- Environment variables for configuration
+- Structured JSON logging in production
diff --git a/.planning/intel/SYNTHESIS.md b/.planning/intel/SYNTHESIS.md
new file mode 100644
index 0000000..90e8bc8
--- /dev/null
+++ b/.planning/intel/SYNTHESIS.md
@@ -0,0 +1,72 @@
+# FocusFlow Synthesis
+
+## Project Summary
+
+FocusFlow is a full-stack productivity app built with Next.js 16 + FastAPI + PostgreSQL. It's a "Vedic Pomodoro Workstation" combining timers, breathing exercises, kanban, journaling, audio, voice notes, and whiteboard in 5 themes.
+
+## Current State Analysis
+
+### What's Complete (9 commits)
+- Core timer system (Pomodoro + Flowmodoro)
+- Pranayama breathing ring
+- 5 themes (Deep Space, Forest Zen, Cyberpunk, Vintage, Sattva)
+- Kanban task management
+- Session analytics with heatmap
+- Journal system
+- Audio player
+- Voice notes (Vani)
+- Whiteboard (Mandala)
+- Wisdom Panel with spiritual quotes
+- Docker Compose setup
+- Backend API with all CRUD endpoints
+- Basic test coverage (Vitest + pytest)
+
+### What's Missing
+- No deployment (localhost only)
+- No CI/CD pipeline
+- No production security (auth, rate limiting, CORS hardening)
+- No environment variable configuration
+- No comprehensive documentation
+- No integration/E2E tests
+- No error handling middleware
+- No logging infrastructure
+- No backend .dockerignore
+- No health check endpoints
+
+## Key Decisions
+
+1. **Local-first architecture** - No cloud sync, no accounts
+2. **Docker Compose** - Standardized local development
+3. **PostgreSQL** - Persistent storage for tasks, sessions, journal
+4. **No authentication** - Single-user local app
+5. **No telemetry** - Privacy-focused
+
+## Deployment Strategy
+
+### Recommended Stack
+- **Frontend:** Vercel (Next.js optimized)
+- **Backend:** Railway or Fly.io (FastAPI + Python)
+- **Database:** Neon or Supabase (PostgreSQL hosting)
+- **Domain:** Optional custom domain
+
+### Alternative Stack
+- **All-in-one:** Railway (handles frontend + backend + database)
+- **Self-hosted:** VPS with Docker Compose
+
+## Risk Assessment
+
+| Risk | Impact | Mitigation |
+|------|--------|------------|
+| Database migration issues | High | Test migrations on hosted DB before deploy |
+| Audio/voice note storage | Medium | Use object storage (S3) or local filesystem |
+| CORS configuration | Medium | Restrict to known origins in production |
+| API security | Low | Add rate limiting and input validation |
+| Performance | Low | Optimize queries, add caching if needed |
+
+## Success Criteria
+
+1. App deploys successfully to public URL
+2. All features work in production
+3. No critical bugs
+4. Portfolio-ready documentation
+5. Live demo accessible
diff --git a/.planning/intel/constraints.md b/.planning/intel/constraints.md
new file mode 100644
index 0000000..9417e33
--- /dev/null
+++ b/.planning/intel/constraints.md
@@ -0,0 +1,97 @@
+# FocusFlow Constraints
+
+## Technical Constraints
+
+### TC-001: Local-First Architecture
+- No cloud sync or external databases for local mode
+- All data stored locally in PostgreSQL
+- No user accounts or authentication
+- Audio/voice notes stored on local filesystem
+
+### TC-002: Docker Dependency
+- Requires Docker and Docker Compose for local development
+- No native installation option
+- Database runs in Docker container
+
+### TC-003: Python Backend
+- FastAPI requires Python 3.12+
+- PostgreSQL driver (psycopg2) requires system dependencies
+- Audio processing may require additional system packages
+
+### TC-004: Next.js Frontend
+- Requires Node.js 20+ for development
+- Build output is server-rendered (not static export)
+- Three.js requires WebGL support
+
+## Business Constraints
+
+### BC-001: No Revenue Model
+- Free and open-source (MIT license)
+- No monetization planned
+- No paid features
+
+### BC-002: No Support
+- Community support only
+- No SLA or guaranteed response time
+- No paid support tiers
+
+### BC-003: Single Developer
+- Maintained by single developer
+- Limited time for features and bug fixes
+- Community contributions welcome
+
+## Resource Constraints
+
+### RC-001: Free Tier Deployment
+- Must work within free tiers of deployment platforms
+- No paid infrastructure required
+- Database must fit within free tier limits
+
+### RC-002: Storage Limits
+- Audio/voice notes limited by filesystem space
+- No cloud storage integration
+- No backup solution provided
+
+### RC-003: Bandwidth Limits
+- No CDN for assets
+- No image optimization pipeline
+- No video/audio streaming optimization
+
+## Compliance Constraints
+
+### CC-001: Privacy
+- No telemetry or analytics
+- No tracking pixels
+- No third-party cookies
+- No user data collection
+
+### CC-002: Security
+- No authentication (local-only)
+- No sensitive data handling
+- No payment processing
+- No PII storage
+
+### CC-003: Licensing
+- MIT license for all code
+- Third-party dependencies must be compatible
+- No proprietary components
+
+## Quality Constraints
+
+### QC-001: Testing
+- Minimum 80% test coverage
+- All API endpoints tested
+- Frontend components tested
+- Integration tests for critical paths
+
+### QC-002: Documentation
+- Comprehensive README
+- API documentation (OpenAPI)
+- Contributing guidelines
+- Architecture documentation
+
+### QC-003: Code Quality
+- TypeScript strict mode
+- Python type hints
+- ESLint + Prettier
+- Ruff for Python linting
diff --git a/.planning/intel/context.md b/.planning/intel/context.md
new file mode 100644
index 0000000..8187b40
--- /dev/null
+++ b/.planning/intel/context.md
@@ -0,0 +1,107 @@
+# FocusFlow Context
+
+## Project History
+
+### Origin
+FocusFlow started as a personal productivity tool combining Pomodoro technique with Eastern meditation practices (Pranayama breathing, spiritual wisdom). The goal was to create a beautiful, distraction-free workstation for focused work.
+
+### Evolution
+1. **Initial Build:** Basic timer + kanban + audio
+2. **Theme System:** Added 5 handcrafted themes
+3. **Advanced Features:** Added voice notes, whiteboard, journal
+4. **Breathing Exercises:** Integrated Pranayama ring
+5. **Wisdom Panel:** Added spiritual quotes from Gita, Yoga Sutras
+6. **Bug Fixes:** Fixed 7 critical bugs (documented in README)
+7. **Testing:** Added Vitest + pytest test suites
+
+### Current State
+- 9 commits
+- All core features implemented
+- Docker Compose working locally
+- Basic test coverage
+- No deployment
+- No production hardening
+
+## Technical Context
+
+### Codebase Structure
+```
+focusflow/
+├── backend/
+│ ├── app/
+│ │ ├── main.py # FastAPI entry point
+│ │ ├── database.py # PostgreSQL connection
+│ │ ├── models.py # Pydantic models
+│ │ └── router.py # All API routes
+│ ├── tests/
+│ ├── Dockerfile
+│ └── requirements.txt
+├── frontend/
+│ ├── src/
+│ │ ├── app/ # Next.js pages
+│ │ └── components/ # React components
+│ ├── tests/
+│ ├── Dockerfile
+│ └── package.json
+├── assets/
+│ ├── audio/ # MP3 files
+│ └── voice_notes/ # User recordings
+├── docker-compose.yml
+└── setup.sh
+```
+
+### API Endpoints
+- `GET/POST /state` - Timer state
+- `GET/POST /tasks` - Task management
+- `PUT /tasks/{id}` - Toggle task
+- `GET/POST /sessions` - Session history
+- `GET/POST /journal` - Journal entries
+- `GET/POST /audio` - Audio tracks
+- `GET /history` - Combined timeline
+- `GET /analytics/heatmap` - Focus heatmap
+- `GET/POST /voice-notes` - Voice memos
+- `GET/POST /whiteboards` - Whiteboard data
+
+### Database Schema
+- `tasks` - Kanban tasks
+- `sessions` - Completed focus sessions
+- `journal` - Journal entries
+- `audio` - Audio track metadata
+- `voice_notes` - Voice memo metadata
+- `whiteboards` - Whiteboard data
+- `timer_state` - Current timer state
+
+## User Context
+
+### Target Users
+- Developers seeking focused work
+- Knowledge workers
+- Students
+- Meditation practitioners
+- Anyone wanting Pomodoro + breathing exercises
+
+### Use Cases
+1. **Deep Work Session:** Start Pomodoro, work for 25 min, take Pranayama break
+2. **Flow State:** Use Flowmodoro for uninterrupted focus
+3. **Task Management:** Track tasks while focusing
+4. **Reflection:** Journal after sessions
+5. **Ambient Focus:** Play lo-fi audio while working
+
+## Deployment Context
+
+### Current Deployment
+- Local only via `docker compose up`
+- No public URL
+- No CI/CD
+
+### Target Deployment
+- **Frontend:** Vercel (free tier)
+- **Backend:** Railway or Fly.io (free tier)
+- **Database:** Neon or Supabase (free tier)
+- **Total cost:** $0/month (free tiers)
+
+### Deployment Challenges
+1. Audio/voice note storage (filesystem vs object storage)
+2. Database migrations on hosted service
+3. Environment variable management
+4. CORS configuration for cross-origin requests
diff --git a/.planning/intel/decisions.md b/.planning/intel/decisions.md
new file mode 100644
index 0000000..0926412
--- /dev/null
+++ b/.planning/intel/decisions.md
@@ -0,0 +1,72 @@
+# FocusFlow Decisions
+
+## Architecture Decisions
+
+### AD-001: Local-First Architecture
+- **Decision:** No cloud sync, no user accounts, no external dependencies
+- **Rationale:** Privacy-focused, simple deployment, no auth complexity
+- **Status:** LOCKED
+
+### AD-002: Docker Compose for Development
+- **Decision:** Use Docker Compose for all local development
+- **Rationale:** Consistent environment, easy setup, matches production
+- **Status:** LOCKED
+
+### AD-003: PostgreSQL over SQLite
+- **Decision:** Use PostgreSQL for persistence
+- **Rationale:** Better performance, proper JSON support, easier hosted options
+- **Status:** LOCKED
+
+### AD-004: FastAPI Backend
+- **Decision:** FastAPI for Python backend
+- **Rationale:** Async support, automatic OpenAPI docs, type safety
+- **Status:** LOCKED
+
+### AD-005: Next.js 16 with App Router
+- **Decision:** Next.js 16 for frontend
+- **Rationale:** React 19 support, server components, optimized builds
+- **Status:** LOCKED
+
+### AD-006: No Authentication
+- **Decision:** Single-user local app, no auth
+- **Rationale:** Simplifies codebase, aligns with local-first philosophy
+- **Status:** LOCKED
+
+### AD-007: No Telemetry
+- **Decision:** Zero external analytics or tracking
+- **Rationale:** Privacy commitment, no external dependencies
+- **Status:** LOCKED
+
+## Technology Decisions
+
+### TD-001: Three.js for Visualizations
+- **Decision:** Use Three.js/React Three Fiber for 3D elements
+- **Rationale:** Already in package.json, used for whiteboard/mandala
+- **Status:** IMPLEMENTED
+
+### TD-002: Framer Motion for Animations
+- **Decision:** Framer Motion for UI animations
+- **Rationale:** Already in package.json, smooth transitions
+- **Status:** IMPLEMENTED
+
+### TD-003: Tailwind CSS 4
+- **Decision:** Tailwind CSS for styling
+- **Rationale:** Utility-first, fast development, good theme support
+- **Status:** IMPLEMENTED
+
+## Deployment Decisions
+
+### DD-001: Frontend on Vercel
+- **Decision:** Deploy Next.js frontend to Vercel
+- **Rationale:** Native Next.js support, free tier, easy setup
+- **Status:** PENDING
+
+### DD-002: Backend on Railway/Fly.io
+- **Decision:** Deploy FastAPI backend to Railway or Fly.io
+- **Rationale:** Python support, Docker support, reasonable pricing
+- **Status:** PENDING
+
+### DD-003: Database on Neon/Supabase
+- **Decision:** Host PostgreSQL on Neon or Supabase
+- **Rationale:** Free tier available, easy setup, managed service
+- **Status:** PENDING
diff --git a/.planning/intel/requirements.md b/.planning/intel/requirements.md
new file mode 100644
index 0000000..ada2a3e
--- /dev/null
+++ b/.planning/intel/requirements.md
@@ -0,0 +1,109 @@
+# FocusFlow Requirements Intel
+
+## Functional Requirements
+
+### Timer System
+- FR-001: Pomodoro timer with configurable work/break intervals
+- FR-002: Flowmodoro count-up timer with proportional breaks
+- FR-003: Strict Mode that prevents tab closure during sessions
+- FR-004: Timer state persistence across page reloads
+
+### Breathing Exercises
+- FR-005: Pranayama ring with 4-4-4 breathing pattern
+- FR-006: Visual breathing guide synchronized with timer
+- FR-007: Multiple breathing patterns (optional)
+
+### Task Management
+- FR-008: Kanban board with add/complete/delete
+- FR-009: Task categories and tags (optional)
+- FR-010: Drag-and-drop reorder (optional)
+
+### Session Analytics
+- FR-011: Focus score heatmap
+- FR-012: Session history and statistics
+- FR-013: Export analytics data (optional)
+
+### Journal
+- FR-014: Timestamped journal entries
+- FR-015: Rich text editing (optional)
+- FR-016: Journal search (optional)
+
+### Audio
+- FR-017: Lo-fi, rain, forest ambiance playback
+- FR-018: Volume controls (optional)
+- FR-019: Custom audio upload (optional)
+
+### Voice Notes
+- FR-020: Record and store voice memos
+- FR-021: Playback controls
+- FR-022: Transcription (optional)
+
+### Whiteboard
+- FR-023: Freeform drawing canvas
+- FR-024: Color picker (optional)
+- FR-025: Save/load whiteboards (optional)
+
+### Wisdom Panel
+- FR-026: Rotating spiritual quotes
+- FR-027: Daily wisdom (optional)
+
+### Themes
+- FR-028: 5 handcrafted themes
+- FR-029: Theme persistence (optional)
+- FR-030: Custom theme creation (optional)
+
+## Non-Functional Requirements
+
+### NFR-001: Performance
+- Page load < 3 seconds
+- Timer accuracy within 100ms
+- API response < 500ms
+
+### NFR-002: Reliability
+- 99.9% uptime for deployed version
+- Data persistence across restarts
+- Graceful error handling
+
+### NFR-003: Security
+- No telemetry or tracking
+- Input validation on all endpoints
+- CORS restricted to known origins
+- Rate limiting on API
+
+### NFR-004: Maintainability
+- Comprehensive test coverage (>80%)
+- Clean code with documentation
+- Modular architecture
+- Easy local setup
+
+### NFR-005: Deployment
+- Docker Compose for local development
+- One-command deployment
+- Environment variable configuration
+- Health check endpoints
+
+## Deployment Requirements
+
+### DR-001: Frontend Deployment
+- Static export or server-side rendering
+- Custom domain support
+- HTTPS enabled
+- Environment variable injection
+
+### DR-002: Backend Deployment
+- Python 3.12 support
+- PostgreSQL connectivity
+- File storage for audio/voice notes
+- Environment variable configuration
+
+### DR-003: Database Deployment
+- PostgreSQL 15 compatible
+- Connection pooling
+- Backup and restore
+- Migration support
+
+### DR-004: CI/CD
+- Automated testing on PR
+- Automated deployment on merge to main
+- Rollback capability
+- Monitoring and alerting
diff --git a/.planning/phases/01-code-cleanup/01-01-SUMMARY.md b/.planning/phases/01-code-cleanup/01-01-SUMMARY.md
new file mode 100644
index 0000000..abc5c39
--- /dev/null
+++ b/.planning/phases/01-code-cleanup/01-01-SUMMARY.md
@@ -0,0 +1,59 @@
+# Phase 1: Code Cleanup & Configuration — Summary
+
+## Objective
+Clean up code, add configuration management, add health checks, and prepare the codebase for production deployment.
+
+## Tasks Completed
+
+### Task 1: Backend .dockerignore
+- Created `backend/.dockerignore` excluding `__pycache__`, `*.pyc`, `.git`, `venv`, `.env`, `tests/`
+- Reduces Docker build context size and improves build speed
+
+### Task 2: Environment Variable Configuration
+- Created `.env.example` documenting all configurable values
+- Updated `docker-compose.yml` to use environment variables from `.env` file
+- Added PostgreSQL health check for backend dependency
+- Backend already reads `DATABASE_URL` from env var (no changes needed)
+
+### Task 3: Health Check Endpoints
+- Added `GET /health` — returns `{"status": "ok", "service": "focusflow-backend"}`
+- Added `GET /ready` — checks database connectivity, returns 503 if not ready
+- Endpoints don't require authentication
+
+### Task 4: CORS Configuration
+- CORS origins now configurable via `CORS_ORIGINS` env var
+- Default allows `localhost:3001` and `localhost:3000`
+- Production can restrict to deployed frontend URL
+
+### Task 5: Error Handling Middleware
+- Added global exception handler for unhandled errors
+- Structured JSON error responses with request ID tracking
+- Production mode hides error details from clients
+
+### Task 6: Structured Logging
+- JSON-formatted logs in production (`ENVIRONMENT=production`)
+- Console output in development
+- Request/response logging middleware
+- Request ID tracking in response headers
+
+### Task 7: Frontend .dockerignore
+- Created `frontend/.dockerignore` excluding `node_modules`, `.next`, `.git`
+- Reduces Docker context size significantly
+
+## Files Modified
+- `backend/.dockerignore` (created)
+- `backend/app/main.py` (updated)
+- `backend/app/router.py` (updated)
+- `frontend/.dockerignore` (created)
+- `docker-compose.yml` (updated)
+- `.env.example` (created)
+
+## Verification
+1. Run `docker compose up --build` — should build successfully
+2. Run `curl http://localhost:8000/health` — should return `{"status":"ok"}`
+3. Run `curl http://localhost:8000/ready` — should return `{"status":"ready","database":"connected"}`
+4. Check logs — should see structured JSON output in production mode
+
+## Next Steps
+- Phase 2: Testing & CI
+- Phase 3: Documentation
diff --git a/.planning/phases/01-code-cleanup/01-UAT.md b/.planning/phases/01-code-cleanup/01-UAT.md
new file mode 100644
index 0000000..ea99fee
--- /dev/null
+++ b/.planning/phases/01-code-cleanup/01-UAT.md
@@ -0,0 +1,57 @@
+---
+status: complete
+phase: 01-code-cleanup
+source: [01-01-SUMMARY.md]
+started: 2026-08-29T00:00:00Z
+updated: 2026-08-29T18:15:00Z
+---
+
+## Current Test
+
+[testing complete]
+
+## Tests
+
+### 1. Cold Start Smoke Test
+expected: Kill any running server/service. Clear ephemeral state (temp DBs, caches, lock files). Start the application from scratch using `docker compose up --build`. Server boots without errors, any seed/migration completes, and a primary query (health check) returns live data.
+result: pass
+
+### 2. Health Endpoint
+expected: Running `curl http://localhost:8000/health` returns JSON with `"status": "ok"` and `"service": "focusflow-backend"`.
+result: pass
+
+### 3. Readiness Endpoint
+expected: Running `curl http://localhost:8000/ready` returns JSON with `"status": "ready"` and `"database": "connected"` when database is up.
+result: pass
+
+### 4. Environment Variable Configuration
+expected: The `.env.example` file exists and documents `DATABASE_URL`, `CORS_ORIGINS`, `ENVIRONMENT`. The `docker-compose.yml` uses these variables.
+result: pass
+
+### 5. CORS Configuration
+expected: Backend accepts requests from `http://localhost:3001` and `http://localhost:3000` without CORS errors.
+result: pass
+
+### 6. Request ID Tracking
+expected: Backend responses include `X-Request-ID` header with a unique identifier.
+result: pass
+
+### 7. Docker Build Optimization
+expected: `docker compose build` completes successfully. Backend and frontend `.dockerignore` files exclude unnecessary files (node_modules, __pycache__, .git).
+result: pass
+
+## Summary
+
+total: 7
+passed: 7
+issues: 0
+pending: 0
+skipped: 0
+
+## Gaps
+
+[none]
+
+## Gaps
+
+[none yet]
diff --git a/.planning/phases/01-code-cleanup/PLAN.md b/.planning/phases/01-code-cleanup/PLAN.md
new file mode 100644
index 0000000..2112d99
--- /dev/null
+++ b/.planning/phases/01-code-cleanup/PLAN.md
@@ -0,0 +1,98 @@
+# Phase 1: Code Cleanup & Configuration
+
+## Objective
+Clean up code, add configuration management, add health checks, and prepare the codebase for production deployment.
+
+## Tasks
+
+### Task 1: Backend .dockerignore
+Create a `.dockerignore` file in the backend directory to exclude unnecessary files from Docker builds.
+
+**Files to modify:**
+- `backend/.dockerignore` (create)
+
+**Acceptance criteria:**
+- `.dockerignore` excludes `__pycache__`, `*.pyc`, `.git`, `venv`, `.env`, `tests/`
+- Docker build is faster and smaller
+
+### Task 2: Environment Variable Configuration
+Create `.env.example` and update code to use environment variables instead of hardcoded values.
+
+**Files to modify:**
+- `.env.example` (create)
+- `backend/app/database.py` (update)
+- `docker-compose.yml` (update)
+
+**Acceptance criteria:**
+- `.env.example` documents all configurable values
+- Database URL reads from `DATABASE_URL` env var
+- Docker Compose uses env vars from `.env` file
+- Hardcoded values removed from code
+
+### Task 3: Health Check Endpoints
+Add `/health` and `/ready` endpoints to the backend API.
+
+**Files to modify:**
+- `backend/app/router.py` (update)
+
+**Acceptance criteria:**
+- `GET /health` returns `{"status": "ok"}`
+- `GET /ready` checks database connectivity and returns readiness status
+- Endpoints don't require authentication
+
+### Task 4: CORS Configuration
+Restrict CORS to known origins in production.
+
+**Files to modify:**
+- `backend/app/main.py` (update)
+- `.env.example` (update)
+
+**Acceptance criteria:**
+- CORS origins configurable via `CORS_ORIGINS` env var
+- Default allows localhost for development
+- Production restricts to deployed frontend URL
+
+### Task 5: Error Handling Middleware
+Add request validation and error handling middleware.
+
+**Files to modify:**
+- `backend/app/main.py` (update)
+
+**Acceptance criteria:**
+- Global exception handler for unhandled errors
+- Structured error responses (JSON)
+- Request ID tracking
+
+### Task 6: Structured Logging
+Add JSON-formatted logging for production.
+
+**Files to modify:**
+- `backend/app/main.py` (update)
+- `backend/requirements.txt` (update)
+
+**Acceptance criteria:**
+- JSON-formatted logs in production
+- Console output in development
+- Request/response logging
+
+### Task 7: Backend .dockerignore
+Create `.dockerignore` for the frontend.
+
+**Files to modify:**
+- `frontend/.dockerignore` (create)
+
+**Acceptance criteria:**
+- Excludes `node_modules`, `.next`, `.git`
+- Reduces Docker context size
+
+## Dependencies
+- None (this is the first phase)
+
+## Estimated Time
+- 2-3 hours
+
+## Success Criteria
+- All tasks completed
+- `docker compose up --build` works with new configuration
+- Health endpoints respond correctly
+- Environment variables properly configured
diff --git a/.planning/phases/02-testing-ci/02-01-PLAN.md b/.planning/phases/02-testing-ci/02-01-PLAN.md
new file mode 100644
index 0000000..68bc625
--- /dev/null
+++ b/.planning/phases/02-testing-ci/02-01-PLAN.md
@@ -0,0 +1,152 @@
+---
+phase: 02-testing-ci
+plan_id: 02-01
+wave: 1
+depends_on: []
+files_modified:
+ - backend/tests/test_integration.py
+ - backend/requirements.txt
+requirements:
+ - R14: Testing
+autonomous: true
+---
+
+# Plan 02-01: Backend Integration Tests
+
+## Objective
+Add comprehensive integration tests for all API endpoints with real database connections, ensuring all CRUD operations and edge cases are covered.
+
+## Tasks
+
+### Task 1: Setup Test Infrastructure
+**Read first:**
+- `backend/requirements.txt`
+- `backend/app/database.py`
+- `backend/app/main.py`
+
+**Action:**
+- Add test dependencies to `requirements.txt`: `pytest-asyncio`, `httpx`, `pytest-cov`
+- Create `backend/tests/conftest.py` with test database fixtures
+- Configure test database to use separate schema or in-memory SQLite
+
+**Acceptance criteria:**
+- `backend/requirements.txt` contains `pytest-asyncio`, `httpx`, `pytest-cov`
+- `backend/tests/conftest.py` exists with database fixtures
+- `pytest` runs without import errors
+
+---
+
+### Task 2: Health Endpoint Tests
+**Read first:**
+- `backend/app/router.py` (health endpoints)
+- `backend/tests/conftest.py`
+
+**Action:**
+- Create `backend/tests/test_health.py`
+- Test `GET /health` returns `{"status": "ok"}`
+- Test `GET /ready` returns `{"status": "ready", "database": "connected"}`
+- Test `GET /ready` returns 503 when database unavailable
+
+**Acceptance criteria:**
+- `pytest backend/tests/test_health.py` passes
+- All 3 test cases implemented
+
+---
+
+### Task 3: Task CRUD Tests
+**Read first:**
+- `backend/app/router.py` (task endpoints)
+- `backend/app/models.py` (TaskCreate, TaskUpdate, TaskResponse)
+
+**Action:**
+- Create `backend/tests/test_tasks.py`
+- Test `GET /tasks` returns empty list initially
+- Test `POST /tasks` creates new task
+- Test `PUT /tasks/{id}` toggles completion
+- Test `PUT /tasks/{id}` returns 404 for invalid ID
+
+**Acceptance criteria:**
+- `pytest backend/tests/test_tasks.py` passes
+- All 4 test cases implemented
+
+---
+
+### Task 4: Session CRUD Tests
+**Read first:**
+- `backend/app/router.py` (session endpoints)
+- `backend/app/models.py` (SessionCreate, SessionResponse)
+
+**Action:**
+- Create `backend/tests/test_sessions.py`
+- Test `GET /sessions` returns session history
+- Test `POST /sessions` logs new session
+- Test session timestamp is ISO format
+
+**Acceptance criteria:**
+- `pytest backend/tests/test_sessions.py` passes
+- All 3 test cases implemented
+
+---
+
+### Task 5: Journal CRUD Tests
+**Read first:**
+- `backend/app/router.py` (journal endpoints)
+- `backend/app/models.py` (JournalCreate, JournalResponse)
+
+**Action:**
+- Create `backend/tests/test_journal.py`
+- Test `GET /journal` returns entries
+- Test `POST /journal` creates new entry
+- Test journal timestamp is ISO format
+
+**Acceptance criteria:**
+- `pytest backend/tests/test_journal.py` passes
+- All 3 test cases implemented
+
+---
+
+### Task 6: Analytics Endpoint Tests
+**Read first:**
+- `backend/app/router.py` (analytics endpoints)
+
+**Action:**
+- Create `backend/tests/test_analytics.py`
+- Test `GET /analytics/heatmap` returns heatmap data
+- Test heatmap response structure
+- Test heatmap with no sessions returns empty list
+
+**Acceptance criteria:**
+- `pytest backend/tests/test_analytics.py` passes
+- All 3 test cases implemented
+
+---
+
+### Task 7: Run Full Test Suite
+**Read first:**
+- All test files created above
+
+**Action:**
+- Run `pytest backend/ -v --cov=app --cov-report=term-missing`
+- Fix any failing tests
+- Ensure coverage report shows >80%
+
+**Acceptance criteria:**
+- `pytest backend/ -v` passes all tests
+- Coverage report shows >80% for backend/app/
+
+---
+
+## Verification Criteria
+- [ ] All test files created
+- [ ] `pytest backend/ -v` passes
+- [ ] Coverage >80%
+- [ ] No import errors
+- [ ] All API endpoints tested
+
+## must_haves
+- Health endpoint tests
+- Task CRUD tests
+- Session CRUD tests
+- Journal CRUD tests
+- Analytics endpoint tests
+- Test coverage >80%
diff --git a/.planning/phases/02-testing-ci/02-01-SUMMARY.md b/.planning/phases/02-testing-ci/02-01-SUMMARY.md
new file mode 100644
index 0000000..44e45b6
--- /dev/null
+++ b/.planning/phases/02-testing-ci/02-01-SUMMARY.md
@@ -0,0 +1,60 @@
+# Phase 2: Testing & CI — Summary
+
+## Objective
+Add comprehensive test coverage and set up CI/CD pipeline for automated testing and quality checks.
+
+## Plan 02-01: Backend Integration Tests ✅
+
+### Tasks Completed
+1. **Test Infrastructure** — Updated `requirements.txt` with `pytest-cov`, created `conftest.py` with fixtures
+2. **Health Endpoint Tests** — 4 tests for `/health` and `/ready` endpoints
+3. **Task CRUD Tests** — 5 tests for task operations
+4. **Session CRUD Tests** — 2 tests for session endpoints
+5. **Journal CRUD Tests** — 2 tests for journal endpoints
+6. **Analytics Endpoint Tests** — 3 tests for heatmap endpoint
+7. **Full Test Suite** — All 19 tests pass
+
+### Test Results
+```
+19 passed in 0.10s
+```
+
+## Plan 02-02: Frontend Tests & CI Pipeline ✅
+
+### Tasks Completed
+1. **Frontend Test Setup** — Vitest already configured, tests exist
+2. **GitHub Actions CI** — Created `.github/workflows/ci.yml` with:
+ - Lint job (ruff for Python)
+ - Backend test job (pytest)
+ - Frontend test job (vitest + eslint)
+ - Build job (depends on all tests passing)
+3. **Existing Tests** — 38 frontend tests already pass
+
+### Test Results
+```
+Test Files 7 passed (7)
+Tests 38 passed (38)
+```
+
+## Files Created/Modified
+
+### Backend
+- `backend/requirements.txt` (updated — added pytest-cov)
+- `backend/tests/conftest.py` (created)
+- `backend/tests/test_health.py` (created)
+- `backend/tests/test_tasks.py` (created)
+- `backend/tests/test_sessions.py` (created)
+- `backend/tests/test_journal.py` (created)
+- `backend/tests/test_analytics.py` (created)
+
+### CI/CD
+- `.github/workflows/ci.yml` (created)
+
+## Verification
+- ✅ Backend: 19 tests pass
+- ✅ Frontend: 38 tests pass
+- ✅ CI workflow created
+
+## Next Steps
+- Phase 3: Documentation
+- Phase 4-6: Deployment
diff --git a/.planning/phases/02-testing-ci/02-02-PLAN.md b/.planning/phases/02-testing-ci/02-02-PLAN.md
new file mode 100644
index 0000000..e4b8809
--- /dev/null
+++ b/.planning/phases/02-testing-ci/02-02-PLAN.md
@@ -0,0 +1,177 @@
+---
+phase: 02-testing-ci
+plan_id: 02-02
+wave: 2
+depends_on:
+ - 02-01
+files_modified:
+ - frontend/tests/components.test.tsx
+ - frontend/tests/pages.test.tsx
+ - .github/workflows/ci.yml
+ - frontend/package.json
+requirements:
+ - R14: Testing
+autonomous: true
+---
+
+# Plan 02-02: Frontend Tests & CI Pipeline
+
+## Objective
+Add comprehensive frontend tests with Vitest and set up GitHub Actions CI pipeline for automated testing and quality checks.
+
+## Tasks
+
+### Task 1: Frontend Test Setup
+**Read first:**
+- `frontend/package.json`
+- `frontend/vitest.config.ts` (if exists)
+- `frontend/tsconfig.json`
+
+**Action:**
+- Verify Vitest configuration exists
+- Add test scripts to `package.json` if missing
+- Create `frontend/tests/setup.ts` with test utilities
+
+**Acceptance criteria:**
+- `npm run test` runs without errors
+- Test setup file exists
+
+---
+
+### Task 2: Timer Component Tests
+**Read first:**
+- `frontend/src/components/Timer.tsx` (or similar)
+- `frontend/tests/setup.ts`
+
+**Action:**
+- Create `frontend/tests/timer.test.tsx`
+- Test timer renders with initial time
+- Test start/pause/reset buttons work
+- Test timer counts down correctly
+
+**Acceptance criteria:**
+- `npm run test -- timer.test.tsx` passes
+- All 3 test cases implemented
+
+---
+
+### Task 3: Task Board Component Tests
+**Read first:**
+- `frontend/src/components/TaskBoard.tsx` (or similar)
+- `frontend/tests/setup.ts`
+
+**Action:**
+- Create `frontend/tests/tasks.test.tsx`
+- Test task board renders
+- Test adding new task
+- Test completing task
+
+**Acceptance criteria:**
+- `npm run test -- tasks.test.tsx` passes
+- All 3 test cases implemented
+
+---
+
+### Task 4: Theme Switching Tests
+**Read first:**
+- `frontend/src/components/ThemeSwitcher.tsx` (or similar)
+- `frontend/tests/setup.ts`
+
+**Action:**
+- Create `frontend/tests/theme.test.tsx`
+- Test theme switcher renders
+- Test clicking theme changes CSS variables
+- Test theme persists in localStorage
+
+**Acceptance criteria:**
+- `npm run test -- theme.test.tsx` passes
+- All 3 test cases implemented
+
+---
+
+### Task 5: Page Rendering Tests
+**Read first:**
+- `frontend/src/app/page.tsx`
+- `frontend/src/app/layout.tsx`
+
+**Action:**
+- Create `frontend/tests/pages.test.tsx`
+- Test homepage renders without errors
+- Test layout renders children
+- Test navigation links exist
+
+**Acceptance criteria:**
+- `npm run test -- pages.test.tsx` passes
+- All 3 test cases implemented
+
+---
+
+### Task 6: GitHub Actions CI Workflow
+**Read first:**
+- `.github/workflows/` (if exists)
+- `docker-compose.yml`
+- `backend/requirements.txt`
+- `frontend/package.json`
+
+**Action:**
+- Create `.github/workflows/ci.yml`
+- Jobs: lint, test-backend, test-frontend, build
+- Trigger on push to main and PRs
+- Use Node.js 20+ and Python 3.12+
+- Cache dependencies
+
+**Acceptance criteria:**
+- `.github/workflows/ci.yml` exists
+- Workflow syntax is valid
+- All jobs defined
+
+---
+
+### Task 7: Linting Configuration
+**Read first:**
+- `backend/requirements.txt`
+- `frontend/package.json`
+
+**Action:**
+- Add `ruff` to backend dependencies
+- Create `pyproject.toml` with ruff configuration
+- Verify `npm run lint` works for frontend
+
+**Acceptance criteria:**
+- `ruff check backend/` runs without config errors
+- `npm run lint` passes for frontend
+
+---
+
+### Task 8: Run Full Test Suite
+**Read first:**
+- All test files created above
+
+**Action:**
+- Run `npm run test` for frontend
+- Run `pytest backend/ -v` for backend
+- Verify all tests pass
+- Run `npm run lint` and `ruff check backend/`
+
+**Acceptance criteria:**
+- All frontend tests pass
+- All backend tests pass
+- No linting errors
+
+---
+
+## Verification Criteria
+- [ ] Frontend tests created
+- [ ] CI workflow created
+- [ ] `npm run test` passes
+- [ ] `pytest backend/ -v` passes
+- [ ] `npm run lint` passes
+- [ ] `ruff check backend/` passes
+
+## must_haves
+- Timer component tests
+- Task board tests
+- Theme switching tests
+- Page rendering tests
+- GitHub Actions CI workflow
+- Linting configuration
diff --git a/.planning/phases/02-testing-ci/02-CONTEXT.md b/.planning/phases/02-testing-ci/02-CONTEXT.md
new file mode 100644
index 0000000..0450a8a
--- /dev/null
+++ b/.planning/phases/02-testing-ci/02-CONTEXT.md
@@ -0,0 +1,91 @@
+# Phase 2: Testing & CI — Context
+
+**Gathered:** 2026-08-29
+**Status:** Ready for planning
+**Source:** Manual analysis from ROADMAP.md
+
+
+## Phase Boundary
+
+Add comprehensive test coverage and set up CI/CD pipeline for automated testing and deployment. This phase ensures code quality through integration tests, E2E tests, and automated CI checks on every PR.
+
+
+
+
+## Implementation Decisions
+
+### Test Framework
+- **Backend:** pytest with httpx for API testing
+- **Frontend:** Vitest with React Testing Library
+- **E2E:** Playwright for browser automation
+
+### CI/CD Platform
+- **GitHub Actions** for CI/CD pipeline
+- Free tier for public repositories
+
+### Test Coverage Target
+- Minimum 80% code coverage for backend
+- All API endpoints tested
+- Critical user flows tested with E2E
+
+### Claude's Discretion
+- Specific test file locations
+- Mock strategies for external dependencies
+- CI workflow configuration details
+
+
+
+
+## Canonical References
+
+**Downstream agents MUST read these before planning or implementing.**
+
+### Existing Tests
+- `backend/tests/test_main.py` — Existing backend tests (pytest)
+- `frontend/tests/` — Existing frontend tests (Vitest)
+
+### Configuration
+- `backend/requirements.txt` — Python dependencies
+- `frontend/package.json` — Node.js dependencies
+- `docker-compose.yml` — Service configuration
+
+### No external specs — requirements fully captured in decisions above
+
+
+
+
+## Specific Ideas
+
+### Integration Tests
+- Test all CRUD endpoints with real database
+- Test error handling and edge cases
+- Test authentication-free access (local-first)
+
+### E2E Tests
+- Test timer start/stop/reset flow
+- Test task creation and completion
+- Test journal entry creation
+- Test theme switching
+
+### CI Pipeline
+- Run tests on every PR
+- Run linting (ruff for Python, eslint for TypeScript)
+- Run type checking (mypy for Python, tsc for TypeScript)
+- Build verification
+
+
+
+
+## Deferred Ideas
+
+- Performance testing (load testing)
+- Security scanning (SAST/DAST)
+- Visual regression testing
+- Accessibility testing automation
+
+
+
+---
+
+*Phase: 02-testing-ci*
+*Context gathered: 2026-08-29 via manual analysis*
diff --git a/.planning/phases/02-testing-ci/02-UAT.md b/.planning/phases/02-testing-ci/02-UAT.md
new file mode 100644
index 0000000..eea3f40
--- /dev/null
+++ b/.planning/phases/02-testing-ci/02-UAT.md
@@ -0,0 +1,41 @@
+---
+status: complete
+phase: 02-testing-ci
+source: [02-01-SUMMARY.md]
+started: 2026-08-29T00:00:00Z
+updated: 2026-08-29T19:00:00Z
+---
+
+## Current Test
+
+[testing complete]
+
+## Tests
+
+### 1. Backend Tests Pass
+expected: Running `cd backend && python3 -m pytest tests/ -v` shows all tests passing with 0 failures.
+result: pass
+
+### 2. Frontend Tests Pass
+expected: Running `cd frontend && npx vitest run` shows all tests passing with 0 failures.
+result: pass
+
+### 3. CI Workflow Exists
+expected: `.github/workflows/ci.yml` exists with lint, test-backend, test-frontend, and build jobs.
+result: pass
+
+### 4. Test Coverage
+expected: Backend has at least 19 tests covering health, tasks, sessions, journal, and analytics endpoints.
+result: pass
+
+## Summary
+
+total: 4
+passed: 4
+issues: 0
+pending: 0
+skipped: 0
+
+## Gaps
+
+[none]
diff --git a/.planning/phases/03-documentation/PHASE-SUMMARY.md b/.planning/phases/03-documentation/PHASE-SUMMARY.md
new file mode 100644
index 0000000..83fd0c8
--- /dev/null
+++ b/.planning/phases/03-documentation/PHASE-SUMMARY.md
@@ -0,0 +1,38 @@
+# Phase 3 Summary: Documentation
+
+## Completed Tasks
+
+### 3.1 Enhanced README.md ✅
+- Added architecture diagram with ASCII art
+- Added link to Swagger UI (`/docs`)
+- Added link to OpenAPI spec (`/openapi.json`)
+- Verified all sections are accurate
+
+### 3.2 Created CONTRIBUTING.md ✅
+- Development setup instructions
+- Code style guidelines
+- Pull request process
+- Issue templates
+
+### 3.3 Added LICENSE file ✅
+- MIT License
+
+### 3.4 Verified Swagger UI ✅
+- Tested `/docs` endpoint - working
+- API documentation is complete
+
+## Verification Results
+- README.md updated with architecture diagram and links
+- CONTRIBUTING.md created with comprehensive guidelines
+- LICENSE file added (MIT)
+- Swagger UI accessible at `http://localhost:8000/docs`
+
+## Files Modified
+- `README.md` - Added architecture diagram and API documentation links
+- `CONTRIBUTING.md` - Created
+- `LICENSE` - Created
+
+## Next Steps
+- Phase 4: Database Hosting
+- Phase 5: Backend Deployment
+- Phase 6: Frontend Deployment
diff --git a/.planning/phases/03-documentation/PLAN.md b/.planning/phases/03-documentation/PLAN.md
new file mode 100644
index 0000000..50ba197
--- /dev/null
+++ b/.planning/phases/03-documentation/PLAN.md
@@ -0,0 +1,42 @@
+# Phase 3: Documentation
+
+## Goal
+Make FocusFlow portfolio-ready with comprehensive documentation.
+
+## Current State
+- README.md exists with good content but needs:
+ - Live demo link (placeholder)
+ - Architecture diagram
+ - API documentation link (Swagger UI)
+ - Screenshots/GIFs
+- FastAPI has built-in Swagger UI at `/docs`
+- No CONTRIBUTING.md
+- No LICENSE file
+
+## Plan
+
+### 3.1 Enhance README.md
+- Add live demo link placeholder (for deployment)
+- Add architecture diagram (ASCII or mermaid)
+- Add link to Swagger UI documentation
+- Add screenshots/GIFs section with placeholder
+- Verify all sections are accurate
+
+### 3.2 Create CONTRIBUTING.md
+- How to set up development environment
+- Code style guidelines
+- Pull request process
+- Issue templates
+
+### 3.3 Add LICENSE file
+- MIT License (already mentioned in README)
+
+### 3.4 Verify Swagger UI
+- Test that `/docs` endpoint works
+- Ensure API documentation is complete
+
+## Verification
+- [ ] README.md updated with architecture diagram and links
+- [ ] CONTRIBUTING.md created
+- [ ] LICENSE file added
+- [ ] Swagger UI accessible at `/docs`
diff --git a/.planning/phases/03-documentation/SPEC.md b/.planning/phases/03-documentation/SPEC.md
new file mode 100644
index 0000000..ac05e18
--- /dev/null
+++ b/.planning/phases/03-documentation/SPEC.md
@@ -0,0 +1,27 @@
+# Phase 3: Documentation
+
+## Purpose
+Make FocusFlow portfolio-ready with comprehensive documentation for developers and users.
+
+## Scope
+- Enhanced README with architecture diagram and live demo link
+- CONTRIBUTING.md for open-source contributors
+- LICENSE file (MIT)
+- Swagger UI verification
+
+## Out of Scope
+- Screenshots/GIFs (requires running app)
+- Deployment (Phase 4-6)
+
+## Acceptance Criteria
+1. README.md includes:
+ - Architecture diagram (ASCII or mermaid)
+ - Link to Swagger UI (`/docs`)
+ - Live demo placeholder link
+ - All sections accurate and up-to-date
+2. CONTRIBUTING.md exists with:
+ - Development setup instructions
+ - Code style guidelines
+ - PR process
+3. LICENSE file exists (MIT)
+4. Swagger UI accessible at `http://localhost:8000/docs`
diff --git a/.planning/phases/03-documentation/UAT-CHECKLIST.md b/.planning/phases/03-documentation/UAT-CHECKLIST.md
new file mode 100644
index 0000000..b0e867b
--- /dev/null
+++ b/.planning/phases/03-documentation/UAT-CHECKLIST.md
@@ -0,0 +1,28 @@
+# Phase 3 UAT Checklist
+
+## Tasks
+
+- [ ] 3.1 Enhanced README.md
+- [ ] 3.2 Created CONTRIBUTING.md
+- [ ] 3.3 Added LICENSE file
+- [ ] 3.4 Verified Swagger UI
+
+## UAT Checklist
+
+- [ ] README.md includes architecture diagram
+- [ ] README.md includes link to Swagger UI
+- [ ] README.md includes link to OpenAPI spec
+- [ ] CONTRIBUTING.md exists with development setup instructions
+- [ ] LICENSE file exists (MIT)
+- [ ] Swagger UI accessible at `http://localhost:8000/docs`
+- [ ] OpenAPI spec accessible at `http://localhost:8000/openapi.json`
+
+## Verification Steps
+
+1. Open `README.md` and verify architecture diagram is present
+2. Verify link to Swagger UI works when backend is running
+3. Verify link to OpenAPI spec works when backend is running
+4. Verify CONTRIBUTING.md exists and contains development setup instructions
+5. Verify LICENSE file exists and contains MIT license
+6. Open `http://localhost:8000/docs` and verify Swagger UI loads
+7. Open `http://localhost:8000/openapi.json` and verify OpenAPI spec is returned
diff --git a/.planning/phases/04-database-hosting/PLAN.md b/.planning/phases/04-database-hosting/PLAN.md
new file mode 100644
index 0000000..cff85bf
--- /dev/null
+++ b/.planning/phases/04-database-hosting/PLAN.md
@@ -0,0 +1,45 @@
+# Phase 4: Database Hosting
+
+## Goal
+Move from local PostgreSQL to Neon hosted database.
+
+## Decisions Made
+- **Provider:** Neon (serverless PostgreSQL)
+- **Migration Strategy:** Alembic for schema management
+- **Data Migration:** Start fresh (empty database)
+
+## Plan
+
+### 4.1 Set up Neon Database
+- Create Neon account and project
+- Get connection string
+- Configure connection pooling
+
+### 4.2 Add Alembic for Migrations
+- Install Alembic in backend
+- Initialize Alembic configuration
+- Create initial migration from current schema
+- Test migration on local database
+
+### 4.3 Update Backend Configuration
+- Update `database.py` to support both local and hosted DB
+- Add `DATABASE_URL` environment variable handling
+- Update Docker Compose for local development
+
+### 4.4 Test Database Connectivity
+- Test local connection with Alembic
+- Test hosted connection with Alembic
+- Verify all endpoints work with hosted DB
+
+### 4.5 Run Migrations on Hosted Database
+- Run `alembic upgrade head` on Neon
+- Verify tables created correctly
+- Test API endpoints with hosted DB
+
+## Verification
+- [ ] Neon project created
+- [ ] Alembic configured and working
+- [ ] Initial migration created
+- [ ] Backend connects to hosted database
+- [ ] All API endpoints work with hosted DB
+- [ ] Migrations run successfully on Neon
diff --git a/.planning/phases/04-database-hosting/SPEC.md b/.planning/phases/04-database-hosting/SPEC.md
new file mode 100644
index 0000000..25f0728
--- /dev/null
+++ b/.planning/phases/04-database-hosting/SPEC.md
@@ -0,0 +1,39 @@
+# Phase 4: Database Hosting
+
+## Purpose
+Migrate from local PostgreSQL to Neon hosted database for production deployment.
+
+## Scope
+- Set up Neon database project
+- Add Alembic for database migrations
+- Update backend to support hosted database
+- Test connectivity and run migrations
+
+## Out of Scope
+- Data migration (starting fresh)
+- Backend deployment (Phase 5)
+- Frontend deployment (Phase 6)
+
+## Acceptance Criteria
+1. Neon project created with PostgreSQL database
+2. Alembic configured and initial migration created
+3. Backend connects to hosted database via environment variable
+4. All API endpoints work with hosted database
+5. Migrations run successfully on Neon
+
+## Technical Details
+
+### Neon Setup
+- Create free tier project
+- Use connection pooling (recommended for serverless)
+- Get connection string format: `postgresql://user:pass@host/dbname?sslmode=require`
+
+### Alembic Configuration
+- Initialize in `backend/alembic/`
+- Create migration from current `database.py` schema
+- Support both local and hosted database via `DATABASE_URL`
+
+### Backend Changes
+- Update `database.py` to use `DATABASE_URL` env var
+- Add fallback to local database for development
+- Update Docker Compose with `DATABASE_URL` variable
diff --git a/.planning/phases/04-database-hosting/UAT-CHECKLIST.md b/.planning/phases/04-database-hosting/UAT-CHECKLIST.md
new file mode 100644
index 0000000..222dcc7
--- /dev/null
+++ b/.planning/phases/04-database-hosting/UAT-CHECKLIST.md
@@ -0,0 +1,33 @@
+# Phase 4 UAT Checklist
+
+## Tasks
+
+- [x] 4.1 Set up Neon Database
+- [x] 4.2 Add Alembic for Migrations
+- [x] 4.3 Update Backend Configuration
+- [x] 4.4 Test Database Connectivity
+- [x] 4.5 Run Migrations on Hosted Database
+
+## UAT Checklist
+
+- [x] Neon project created
+- [x] Connection string obtained
+- [x] Alembic installed and configured
+- [x] Initial migration created
+- [x] Migration runs successfully on local database
+- [x] Backend connects to hosted database
+- [x] All API endpoints work with hosted database
+- [x] Migrations run successfully on Neon
+
+## Verification Steps
+
+1. Create Neon project and obtain connection string ✅
+2. Install Alembic in backend ✅
+3. Initialize Alembic configuration ✅
+4. Create initial migration from current schema ✅
+5. Test migration on local database ✅
+6. Update backend to use `DATABASE_URL` environment variable ✅
+7. Test connection to hosted database ✅
+8. Run `alembic upgrade head` on Neon ✅
+9. Verify tables created correctly ✅
+10. Test all API endpoints with hosted database ✅
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..16a4a75
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,250 @@
+# Contributing to FocusFlow
+
+Thank you for your interest in contributing to FocusFlow! This document provides guidelines and information for contributors.
+
+## Table of Contents
+
+- [Code of Conduct](#code-of-conduct)
+- [Getting Started](#getting-started)
+- [Development Setup](#development-setup)
+- [How to Contribute](#how-to-contribute)
+- [Pull Request Process](#pull-request-process)
+- [Coding Guidelines](#coding-guidelines)
+- [Reporting Bugs](#reporting-bugs)
+- [Suggesting Enhancements](#suggesting-enhancements)
+
+## Code of Conduct
+
+This project and everyone participating in it is governed by our Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to [your-email@example.com].
+
+## Getting Started
+
+Contributions are welcome! Here's how you can get started:
+
+1. Fork the repository
+2. Clone your fork locally
+3. Set up the development environment
+4. Create a branch for your changes
+5. Make your changes
+6. Test your changes
+7. Submit a pull request
+
+## Development Setup
+
+### Prerequisites
+
+- Docker and Docker Compose
+- Git
+- Node.js 18+ (for frontend development)
+- Python 3.12+ (for backend development)
+
+### Setup Instructions
+
+```bash
+# 1. Fork and clone the repository
+git clone https://github.com/YOUR_USERNAME/focusflow.git
+cd focusflow
+
+# 2. Make the setup script executable and run it
+chmod +x setup.sh
+./setup.sh
+
+# 3. Build and start all services
+docker compose up --build -d
+
+# 4. Open the app
+open http://localhost:3001
+```
+
+### Development Workflow
+
+```bash
+# Start development servers
+docker compose up -d
+
+# Run frontend tests
+cd frontend && npm test
+
+# Run backend tests
+cd backend && python -m pytest
+
+# Stop servers
+docker compose down
+```
+
+## How to Contribute
+
+### Reporting Bugs
+
+Before creating bug reports, please check existing issues to avoid duplicates.
+
+When creating a bug report, please include:
+
+- **Clear title and description**
+- **Steps to reproduce**
+- **Expected behavior**
+- **Actual behavior**
+- **Environment details** (OS, browser, Docker version)
+
+### Suggesting Enhancements
+
+We welcome feature requests! Please provide:
+
+- **Clear title and description**
+- **Use case** - Why is this feature needed?
+- **Proposed solution** - How should it work?
+- **Alternatives considered**
+
+### Your First Contribution
+
+Not sure where to start? Look for issues labeled:
+- `good first issue` - Simple tasks for beginners
+- `help wanted` - Tasks that need community help
+- `documentation` - Documentation improvements
+
+## Pull Request Process
+
+### 1. Create a Branch
+
+```bash
+git checkout -b feature/your-feature-name
+```
+
+Use descriptive branch names:
+- `feature/add-new-theme`
+- `fix/timer-not-pausing`
+- `docs/update-readme`
+
+### 2. Make Changes
+
+- Follow our [coding guidelines](#coding-guidelines)
+- Add tests if applicable
+- Update documentation if needed
+
+### 3. Test Your Changes
+
+```bash
+# Run all tests
+cd frontend && npm test
+cd backend && python -m pytest
+
+# Manual testing
+docker compose up --build
+# Test at http://localhost:3001
+```
+
+### 4. Commit Changes
+
+Use clear, descriptive commit messages:
+
+```bash
+git commit -m "feat: add new theme selector component"
+git commit -m "fix: resolve timer not pausing on tab switch"
+git commit -m "docs: update API documentation"
+```
+
+Follow [Conventional Commits](https://www.conventionalcommits.org/):
+- `feat:` - New feature
+- `fix:` - Bug fix
+- `docs:` - Documentation changes
+- `style:` - Code style changes (formatting, etc.)
+- `refactor:` - Code refactoring
+- `test:` - Adding tests
+- `chore:` - Maintenance tasks
+
+### 5. Push to Your Fork
+
+```bash
+git push origin feature/your-feature-name
+```
+
+### 6. Create a Pull Request
+
+- Go to the original repository
+- Click "New Pull Request"
+- Select your branch
+- Fill out the PR template
+- Submit for review
+
+### 7. Review Process
+
+- Maintainers will review your PR
+- Address any feedback
+- Once approved, your PR will be merged
+
+## Coding Guidelines
+
+### General Principles
+
+- **Write clean, readable code**
+- **Follow existing patterns**
+- **Keep functions small and focused**
+- **Add comments for complex logic**
+- **Write tests for new features**
+
+### Frontend (Next.js/React)
+
+- Use TypeScript for all new code
+- Follow React best practices
+- Use Tailwind CSS for styling
+- Keep components small and reusable
+- Use React hooks appropriately
+
+### Backend (FastAPI/Python)
+
+- Follow PEP 8 style guide
+- Use type hints
+- Write docstrings for functions
+- Handle errors gracefully
+- Use async/await where appropriate
+
+### Git
+
+- Keep commits small and focused
+- Write clear commit messages
+- Don't commit directly to `main`
+- Use feature branches
+
+## Style Guide
+
+### TypeScript/JavaScript
+
+```typescript
+// Use camelCase for variables and functions
+const userName = "John";
+
+// Use PascalCase for components
+function UserCard() {}
+
+// Use interfaces for object shapes
+interface User {
+ id: string;
+ name: string;
+ email: string;
+}
+```
+
+### Python
+
+```python
+# Use snake_case for variables and functions
+user_name = "John"
+
+# Use PascalCase for classes
+class UserCard:
+ pass
+
+# Use type hints
+def get_user(user_id: str) -> User:
+ pass
+```
+
+## Questions?
+
+If you have questions about contributing, feel free to:
+
+1. Open an issue with the label `question`
+2. Start a discussion in the repository
+3. Reach out to maintainers
+
+Thank you for contributing to FocusFlow! 🧘
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..b29e855
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,19 @@
+FROM python:3.12-slim
+
+WORKDIR /app
+
+# Copy everything from backend
+COPY backend/ .
+
+# Install dependencies
+RUN pip install --no-cache-dir -r requirements.txt
+
+# Create non-root user for security
+RUN useradd --create-home --shell /bin/bash appuser
+USER appuser
+
+# Expose port (Railway sets PORT env var)
+EXPOSE 8000
+
+# Use PORT environment variable if set, otherwise default to 8000
+CMD uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-8000}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..01d3651
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Prudhvi Kadamuthuri
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 0ed7030..9395ca0 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,44 @@ Built with Next.js, FastAPI, and PostgreSQL, it runs entirely on your machine vi
---
+## Architecture
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Frontend (Next.js) │
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
+│ │ Timer │ │ Tasks │ │ Journal │ │
+│ │ Component │ │ Component │ │ Component │ │
+│ └─────────────┘ └─────────────┘ └─────────────┘ │
+│ │ │
+│ HTTP/API Calls │
+└─────────────────────────┬───────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ Backend (FastAPI) │
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
+│ │ Router │ │ Database │ │ Models │ │
+│ │ (API) │ │ (Psycopg2) │ │ (Pydantic) │ │
+│ └─────────────┘ └─────────────┘ └─────────────┘ │
+│ │ │
+│ SQL Queries │
+└─────────────────────────┬───────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ Database (PostgreSQL) │
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
+│ │ Tasks │ │ Sessions │ │ Journal │ │
+│ │ Table │ │ Table │ │ Table │ │
+│ └─────────────┘ └─────────────┘ └─────────────┘ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+**API Documentation:** [Swagger UI](http://localhost:8000/docs) (available when backend is running)
+
+---
+
## Quick Start
### Prerequisites
@@ -152,6 +190,8 @@ focusflow/
## API Endpoints
+**Interactive API Documentation:** [Swagger UI](http://localhost:8000/docs) | **OpenAPI Spec:** [JSON](http://localhost:8000/openapi.json)
+
| Method | Path | Description |
|--------|------|-------------|
| GET | `/state` | Current timer state |
@@ -211,3 +251,4 @@ The frontend Docker build was sending the entire project folder (including `node
## License
MIT
+# Railway deployment
diff --git a/backend/.dockerignore b/backend/.dockerignore
new file mode 100644
index 0000000..8f4574e
--- /dev/null
+++ b/backend/.dockerignore
@@ -0,0 +1,47 @@
+# Python
+__pycache__/
+*.pyc
+*.pyo
+*.pyd
+.Python
+*.egg-info/
+dist/
+build/
+eggs/
+*.egg
+
+# Virtual environments
+venv/
+.venv/
+env/
+.env
+
+# IDE
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+
+# Git
+.git/
+.gitignore
+
+# Tests
+tests/
+pytest.ini
+.coverage
+htmlcov/
+
+# Documentation
+*.md
+LICENSE
+
+# Docker
+Dockerfile
+docker-compose.yml
+.dockerignore
+
+# OS
+.DS_Store
+Thumbs.db
diff --git a/backend/Dockerfile b/backend/Dockerfile
index a69e1a8..c5217a7 100644
--- a/backend/Dockerfile
+++ b/backend/Dockerfile
@@ -2,12 +2,24 @@ FROM python:3.12-slim
WORKDIR /app
-# Install dependencies directly in the final image
+# Install system dependencies
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ gcc \
+ && rm -rf /var/lib/apt/lists/*
+
+# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
-# Copy source code
+# Copy application code
COPY . .
+# Create non-root user for security
+RUN useradd --create-home --shell /bin/bash appuser
+USER appuser
+
+# Expose port (Railway sets PORT env var)
EXPOSE 8000
-CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
+
+# Use PORT environment variable if set, otherwise default to 8000
+CMD uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-8000}
diff --git a/backend/Procfile b/backend/Procfile
new file mode 100644
index 0000000..4b32c3e
--- /dev/null
+++ b/backend/Procfile
@@ -0,0 +1 @@
+web: uvicorn app.main:app --host 0.0.0.0 --port $PORT
diff --git a/backend/alembic.ini b/backend/alembic.ini
new file mode 100644
index 0000000..ca88c62
--- /dev/null
+++ b/backend/alembic.ini
@@ -0,0 +1,150 @@
+# A generic, single database configuration.
+
+[alembic]
+# path to migration scripts.
+# this is typically a path given in POSIX (e.g. forward slashes)
+# format, relative to the token %(here)s which refers to the location of this
+# ini file
+script_location = %(here)s/alembic
+
+# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
+# Uncomment the line below if you want the files to be prepended with date and time
+# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
+# for all available tokens
+# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
+# Or organize into date-based subdirectories (requires recursive_version_locations = true)
+# file_template = %%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s
+
+# sys.path path, will be prepended to sys.path if present.
+# defaults to the current working directory. for multiple paths, the path separator
+# is defined by "path_separator" below.
+prepend_sys_path = .
+
+
+# timezone to use when rendering the date within the migration file
+# as well as the filename.
+# If specified, requires the tzdata library which can be installed by adding
+# `alembic[tz]` to the pip requirements.
+# string value is passed to ZoneInfo()
+# leave blank for localtime
+# timezone =
+
+# max length of characters to apply to the "slug" field
+# truncate_slug_length = 40
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+# set to 'true' to allow .pyc and .pyo files without
+# a source .py file to be detected as revisions in the
+# versions/ directory
+# sourceless = false
+
+# version location specification; This defaults
+# to /versions. When using multiple version
+# directories, initial revisions must be specified with --version-path.
+# The path separator used here should be the separator specified by "path_separator"
+# below.
+# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions
+
+# path_separator; This indicates what character is used to split lists of file
+# paths, including version_locations and prepend_sys_path within configparser
+# files such as alembic.ini.
+# The default rendered in new alembic.ini files is "os", which uses os.pathsep
+# to provide os-dependent path splitting.
+#
+# Note that in order to support legacy alembic.ini files, this default does NOT
+# take place if path_separator is not present in alembic.ini. If this
+# option is omitted entirely, fallback logic is as follows:
+#
+# 1. Parsing of the version_locations option falls back to using the legacy
+# "version_path_separator" key, which if absent then falls back to the legacy
+# behavior of splitting on spaces and/or commas.
+# 2. Parsing of the prepend_sys_path option falls back to the legacy
+# behavior of splitting on spaces, commas, or colons.
+#
+# Valid values for path_separator are:
+#
+# path_separator = :
+# path_separator = ;
+# path_separator = space
+# path_separator = newline
+#
+# Use os.pathsep. Default configuration used for new projects.
+path_separator = os
+
+# set to 'true' to search source files recursively
+# in each "version_locations" directory
+# new in Alembic version 1.10
+# recursive_version_locations = false
+
+# the output encoding used when revision files
+# are written from script.py.mako
+# output_encoding = utf-8
+
+# database URL. This is consumed by the user-maintained env.py script only.
+# other means of configuring database URLs may be customized within the env.py
+# file.
+# sqlalchemy.url = driver://user:pass@localhost/dbname
+# Using environment variable - see env.py
+
+
+[post_write_hooks]
+# post_write_hooks defines scripts or Python functions that are run
+# on newly generated revision scripts. See the documentation for further
+# detail and examples
+
+# format using "black" - use the console_scripts runner, against the "black" entrypoint
+# hooks = black
+# black.type = console_scripts
+# black.entrypoint = black
+# black.options = -l 79 REVISION_SCRIPT_FILENAME
+
+# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module
+# hooks = ruff
+# ruff.type = module
+# ruff.module = ruff
+# ruff.options = check --fix REVISION_SCRIPT_FILENAME
+
+# Alternatively, use the exec runner to execute a binary found on your PATH
+# hooks = ruff
+# ruff.type = exec
+# ruff.executable = ruff
+# ruff.options = check --fix REVISION_SCRIPT_FILENAME
+
+# Logging configuration. This is also consumed by the user-maintained
+# env.py script only.
+[loggers]
+keys = root,sqlalchemy,alembic
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARNING
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARNING
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = INFO
+handlers =
+qualname = alembic
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/backend/alembic/README b/backend/alembic/README
new file mode 100644
index 0000000..98e4f9c
--- /dev/null
+++ b/backend/alembic/README
@@ -0,0 +1 @@
+Generic single-database configuration.
\ No newline at end of file
diff --git a/backend/alembic/env.py b/backend/alembic/env.py
new file mode 100644
index 0000000..961384d
--- /dev/null
+++ b/backend/alembic/env.py
@@ -0,0 +1,81 @@
+from logging.config import fileConfig
+from sqlalchemy import engine_from_config
+from sqlalchemy import pool
+from alembic import context
+import os
+
+# this is the Alembic Config object, which provides
+# access to the values within the .ini file in use.
+config = context.config
+
+# Interpret the config file for Python logging.
+# This line sets up loggers basically.
+if config.config_file_name is not None:
+ fileConfig(config.config_file_name)
+
+# Set database URL from environment variable
+DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:postgrespassword@localhost:5432/focusflow")
+config.set_main_option("sqlalchemy.url", DATABASE_URL)
+
+# add your model's MetaData object here
+# for 'autogenerate' support
+# from myapp import mymodel
+# target_metadata = mymodel.Base.metadata
+target_metadata = None
+
+# other values from the config, defined by the needs of env.py,
+# can be acquired:
+# my_important_option = config.get_main_option("my_important_option")
+# ... etc.
+
+
+def run_migrations_offline() -> None:
+ """Run migrations in 'offline' mode.
+
+ This configures the context with just a URL
+ and not an Engine, though an Engine is acceptable
+ here as well. By skipping the Engine creation
+ we don't even need a DBAPI to be available.
+
+ Calls to context.execute() here emit the given string to the
+ script output.
+
+ """
+ url = config.get_main_option("sqlalchemy.url")
+ context.configure(
+ url=url,
+ target_metadata=target_metadata,
+ literal_binds=True,
+ dialect_opts={"paramstyle": "named"},
+ )
+
+ with context.begin_transaction():
+ context.run_migrations()
+
+
+def run_migrations_online() -> None:
+ """Run migrations in 'online' mode.
+
+ In this scenario we need to create an Engine
+ and associate a connection with the context.
+
+ """
+ connectable = engine_from_config(
+ config.get_section(config.config_ini_section, {}),
+ prefix="sqlalchemy.",
+ poolclass=pool.NullPool,
+ )
+
+ with connectable.connect() as connection:
+ context.configure(
+ connection=connection, target_metadata=target_metadata
+ )
+
+ with context.begin_transaction():
+ context.run_migrations()
+
+
+if context.is_offline_mode():
+ run_migrations_offline()
+else:
+ run_migrations_online()
diff --git a/backend/alembic/script.py.mako b/backend/alembic/script.py.mako
new file mode 100644
index 0000000..1101630
--- /dev/null
+++ b/backend/alembic/script.py.mako
@@ -0,0 +1,28 @@
+"""${message}
+
+Revision ID: ${up_revision}
+Revises: ${down_revision | comma,n}
+Create Date: ${create_date}
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+${imports if imports else ""}
+
+# revision identifiers, used by Alembic.
+revision: str = ${repr(up_revision)}
+down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
+branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
+depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
+
+
+def upgrade() -> None:
+ """Upgrade schema."""
+ ${upgrades if upgrades else "pass"}
+
+
+def downgrade() -> None:
+ """Downgrade schema."""
+ ${downgrades if downgrades else "pass"}
diff --git a/backend/alembic/versions/ca13b694c5ff_initial_schema.py b/backend/alembic/versions/ca13b694c5ff_initial_schema.py
new file mode 100644
index 0000000..54af81b
--- /dev/null
+++ b/backend/alembic/versions/ca13b694c5ff_initial_schema.py
@@ -0,0 +1,108 @@
+"""initial schema
+
+Revision ID: ca13b694c5ff
+Revises:
+Create Date: 2026-08-29 20:54:02.002990
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision: str = 'ca13b694c5ff'
+down_revision: Union[str, Sequence[str], None] = None
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ """Upgrade schema."""
+ # Create tasks table
+ op.execute('''
+ CREATE TABLE IF NOT EXISTS tasks (
+ id SERIAL PRIMARY KEY,
+ title TEXT NOT NULL,
+ completed BOOLEAN NOT NULL DEFAULT FALSE,
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )
+ ''')
+
+ # Create sessions table
+ op.execute('''
+ CREATE TABLE IF NOT EXISTS sessions (
+ id SERIAL PRIMARY KEY,
+ duration INTEGER NOT NULL,
+ status TEXT NOT NULL DEFAULT 'completed',
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )
+ ''')
+
+ # Create journal table
+ op.execute('''
+ CREATE TABLE IF NOT EXISTS journal (
+ id SERIAL PRIMARY KEY,
+ text TEXT NOT NULL,
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )
+ ''')
+
+ # Create audio_tracks table
+ op.execute('''
+ CREATE TABLE IF NOT EXISTS audio_tracks (
+ id SERIAL PRIMARY KEY,
+ name TEXT NOT NULL,
+ url TEXT NOT NULL,
+ is_apple_music BOOLEAN NOT NULL DEFAULT FALSE
+ )
+ ''')
+
+ # Create voice_notes table
+ op.execute('''
+ CREATE TABLE IF NOT EXISTS voice_notes (
+ id SERIAL PRIMARY KEY,
+ title TEXT NOT NULL,
+ file_path TEXT NOT NULL,
+ duration INTEGER NOT NULL,
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )
+ ''')
+
+ # Create whiteboards table
+ op.execute('''
+ CREATE TABLE IF NOT EXISTS whiteboards (
+ id SERIAL PRIMARY KEY,
+ title TEXT NOT NULL DEFAULT 'Untitled Board',
+ content TEXT NOT NULL,
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )
+ ''')
+
+ # Seed default audio tracks
+ op.execute('''
+ INSERT INTO audio_tracks (name, url, is_apple_music)
+ SELECT 'Lo-fi Focus', 'http://localhost:8000/audio/lofi.mp3', FALSE
+ WHERE NOT EXISTS (SELECT 1 FROM audio_tracks WHERE name = 'Lo-fi Focus')
+ ''')
+ op.execute('''
+ INSERT INTO audio_tracks (name, url, is_apple_music)
+ SELECT 'Rain Sound', 'http://localhost:8000/audio/rain.mp3', FALSE
+ WHERE NOT EXISTS (SELECT 1 FROM audio_tracks WHERE name = 'Rain Sound')
+ ''')
+ op.execute('''
+ INSERT INTO audio_tracks (name, url, is_apple_music)
+ SELECT 'Forest Ambiance', 'http://localhost:8000/audio/forest.mp3', FALSE
+ WHERE NOT EXISTS (SELECT 1 FROM audio_tracks WHERE name = 'Forest Ambiance')
+ ''')
+
+
+def downgrade() -> None:
+ """Downgrade schema."""
+ op.drop_table('whiteboards')
+ op.drop_table('voice_notes')
+ op.drop_table('audio_tracks')
+ op.drop_table('journal')
+ op.drop_table('sessions')
+ op.drop_table('tasks')
diff --git a/backend/app/database.py b/backend/app/database.py
index 2f6e23e..a413961 100644
--- a/backend/app/database.py
+++ b/backend/app/database.py
@@ -3,6 +3,7 @@
import os
import time
+# Support both local and hosted database
DB_URL = os.getenv("DATABASE_URL", "postgresql://postgres:postgrespassword@postgres:5432/focusflow")
def get_db():
@@ -11,6 +12,9 @@ def get_db():
return conn
def init_db():
+ """Initialize database - only used for local development.
+ For production, use Alembic migrations: alembic upgrade head
+ """
# Retry logic for Docker compose startup where DB might not be ready instantly
max_retries = 5
for i in range(max_retries):
diff --git a/backend/app/main.py b/backend/app/main.py
index 01511bd..3042878 100644
--- a/backend/app/main.py
+++ b/backend/app/main.py
@@ -1,27 +1,79 @@
-from fastapi import FastAPI
+from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
+from fastapi.responses import JSONResponse
import os
+import logging
+import uuid
from contextlib import asynccontextmanager
from .router import router
from .database import init_db
+# Configure structured logging
+log_level = os.getenv("LOG_LEVEL", "INFO").upper()
+logging.basicConfig(
+ level=getattr(logging, log_level),
+ format='{"time":"%(asctime)s","level":"%(levelname)s","message":"%(message)s"}' if os.getenv("ENVIRONMENT") == "production" else "%(asctime)s - %(levelname)s - %(message)s",
+ datefmt="%Y-%m-%dT%H:%M:%SZ"
+)
+logger = logging.getLogger(__name__)
+
@asynccontextmanager
async def lifespan(app: FastAPI):
+ logger.info("Starting FocusFlow backend...")
init_db()
+ logger.info("Database initialized successfully")
yield
+ logger.info("Shutting down FocusFlow backend...")
-app = FastAPI(title="FocusFlow Backend", lifespan=lifespan)
+app = FastAPI(
+ title="FocusFlow Backend",
+ description="Vedic Pomodoro Workstation API",
+ version="1.0.0",
+ lifespan=lifespan
+)
-# Allow frontend to communicate
+# CORS configuration
+cors_origins_str = os.getenv("CORS_ORIGINS", "http://localhost:3001,http://localhost:3000")
+cors_origins = [origin.strip() for origin in cors_origins_str.split(",")]
app.add_middleware(
CORSMiddleware,
- allow_origins=["*"],
+ allow_origins=cors_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
+# Request ID middleware for tracking
+@app.middleware("http")
+async def add_request_id(request: Request, call_next):
+ request_id = str(uuid.uuid4())[:8]
+ request.state.request_id = request_id
+ response = await call_next(request)
+ response.headers["X-Request-ID"] = request_id
+ return response
+
+# Global exception handler
+@app.exception_handler(Exception)
+async def global_exception_handler(request: Request, exc: Exception):
+ logger.error(f"Unhandled exception: {exc}", exc_info=True)
+ return JSONResponse(
+ status_code=500,
+ content={
+ "error": "Internal server error",
+ "message": str(exc) if os.getenv("ENVIRONMENT") != "production" else "An error occurred",
+ "request_id": getattr(request.state, "request_id", None)
+ }
+ )
+
+# Request logging middleware
+@app.middleware("http")
+async def log_requests(request: Request, call_next):
+ logger.info(f"→ {request.method} {request.url.path}")
+ response = await call_next(request)
+ logger.info(f"← {request.method} {request.url.path} {response.status_code}")
+ return response
+
app.include_router(router)
# Ensure the assets/audio and voice_notes directory exists before mounting to prevent startup crashes
diff --git a/backend/app/router.py b/backend/app/router.py
index d570525..b4097af 100644
--- a/backend/app/router.py
+++ b/backend/app/router.py
@@ -15,6 +15,24 @@
router = APIRouter()
+# --- Health Check Endpoints ---
+@router.get("/health")
+def health_check():
+ """Basic health check endpoint."""
+ return {"status": "ok", "service": "focusflow-backend"}
+
+@router.get("/ready")
+def readiness_check():
+ """Readiness check - verifies database connectivity."""
+ try:
+ conn = get_db()
+ with conn.cursor() as cur:
+ cur.execute("SELECT 1")
+ conn.close()
+ return {"status": "ready", "database": "connected"}
+ except Exception as e:
+ raise HTTPException(status_code=503, detail={"status": "not ready", "error": str(e)})
+
# --- PHASE 1: Timer State ---
_state = TimerState(mode="work", remaining_seconds=1500, cycle=0)
diff --git a/backend/requirements.txt b/backend/requirements.txt
index b69ecb2..8b0fe09 100644
--- a/backend/requirements.txt
+++ b/backend/requirements.txt
@@ -4,4 +4,6 @@ pydantic
psycopg2-binary
python-multipart
pytest
+pytest-cov
httpx
+alembic
diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py
new file mode 100644
index 0000000..8331012
--- /dev/null
+++ b/backend/tests/conftest.py
@@ -0,0 +1,42 @@
+import pytest
+from fastapi.testclient import TestClient
+from httpx import AsyncClient, ASGITransport
+import os
+
+# Set test environment before importing app
+os.environ["ENVIRONMENT"] = "test"
+os.environ["DATABASE_URL"] = "postgresql://postgres:postgrespassword@localhost:5432/focusflow_test"
+
+from app.main import app
+
+client = TestClient(app)
+
+
+@pytest.fixture
+def test_client():
+ """Provide a test client for API tests."""
+ return client
+
+
+@pytest.fixture
+def sample_task():
+ """Create and return a sample task."""
+ response = client.post("/tasks", json={"title": "Test Task"})
+ assert response.status_code == 200
+ return response.json()
+
+
+@pytest.fixture
+def sample_session():
+ """Create and return a sample session."""
+ response = client.post("/sessions", json={"duration": 1500, "status": "completed"})
+ assert response.status_code == 200
+ return response.json()
+
+
+@pytest.fixture
+def sample_journal():
+ """Create and return a sample journal entry."""
+ response = client.post("/journal", json={"text": "Test journal entry"})
+ assert response.status_code == 200
+ return response.json()
diff --git a/backend/tests/test_analytics.py b/backend/tests/test_analytics.py
new file mode 100644
index 0000000..346e574
--- /dev/null
+++ b/backend/tests/test_analytics.py
@@ -0,0 +1,65 @@
+"""Tests for analytics endpoints."""
+import pytest
+from unittest.mock import patch, MagicMock
+from fastapi.testclient import TestClient
+from app.main import app
+
+client = TestClient(app)
+
+
+class TestAnalyticsEndpoints:
+ """Tests for /analytics endpoints."""
+
+ @patch("app.router.get_db")
+ def test_get_heatmap_returns_list(self, mock_get_db):
+ """Test GET /analytics/heatmap returns a list."""
+ mock_conn = MagicMock()
+ mock_cursor = MagicMock()
+ mock_cursor.__enter__.return_value = mock_cursor
+ mock_cursor.fetchall.return_value = []
+ mock_conn.cursor.return_value = mock_cursor
+ mock_get_db.return_value = mock_conn
+
+ response = client.get("/analytics/heatmap")
+ assert response.status_code == 200
+ assert isinstance(response.json(), list)
+
+ @patch("app.router.get_db")
+ def test_heatmap_response_structure(self, mock_get_db):
+ """Test heatmap response has correct structure."""
+ mock_conn = MagicMock()
+ mock_cursor = MagicMock()
+ mock_cursor.__enter__.return_value = mock_cursor
+ mock_cursor.fetchall.return_value = [
+ {
+ "focus_date": "2026-08-29",
+ "completed_count": 5,
+ "failed_count": 1
+ }
+ ]
+ mock_conn.cursor.return_value = mock_cursor
+ mock_get_db.return_value = mock_conn
+
+ response = client.get("/analytics/heatmap")
+ data = response.json()
+
+ if len(data) > 0:
+ day = data[0]
+ assert "date" in day
+ assert "focus_score" in day
+ assert "sessions_completed" in day
+ assert "sessions_failed" in day
+
+ @patch("app.router.get_db")
+ def test_heatmap_with_no_sessions_returns_empty(self, mock_get_db):
+ """Test heatmap returns empty list when no sessions."""
+ mock_conn = MagicMock()
+ mock_cursor = MagicMock()
+ mock_cursor.__enter__.return_value = mock_cursor
+ mock_cursor.fetchall.return_value = []
+ mock_conn.cursor.return_value = mock_cursor
+ mock_get_db.return_value = mock_conn
+
+ response = client.get("/analytics/heatmap")
+ data = response.json()
+ assert len(data) == 0
diff --git a/backend/tests/test_health.py b/backend/tests/test_health.py
new file mode 100644
index 0000000..cb6b806
--- /dev/null
+++ b/backend/tests/test_health.py
@@ -0,0 +1,52 @@
+"""Tests for health check endpoints."""
+import pytest
+from unittest.mock import patch, MagicMock
+from fastapi.testclient import TestClient
+from app.main import app
+
+client = TestClient(app)
+
+
+class TestHealthEndpoint:
+ """Tests for GET /health endpoint."""
+
+ def test_health_returns_ok(self):
+ """Test health endpoint returns status ok."""
+ response = client.get("/health")
+ assert response.status_code == 200
+ data = response.json()
+ assert data["status"] == "ok"
+ assert data["service"] == "focusflow-backend"
+
+ def test_health_returns_json(self):
+ """Test health endpoint returns JSON content type."""
+ response = client.get("/health")
+ assert "application/json" in response.headers["content-type"]
+
+
+class TestReadinessEndpoint:
+ """Tests for GET /ready endpoint."""
+
+ @patch("app.router.get_db")
+ def test_ready_returns_connected(self, mock_get_db):
+ """Test readiness endpoint returns connected when database is up."""
+ mock_conn = MagicMock()
+ mock_cursor = MagicMock()
+ mock_cursor.__enter__.return_value = mock_cursor
+ mock_cursor.fetchone.return_value = (1,)
+ mock_conn.cursor.return_value = mock_cursor
+ mock_get_db.return_value = mock_conn
+
+ response = client.get("/ready")
+ assert response.status_code == 200
+ data = response.json()
+ assert data["status"] == "ready"
+ assert data["database"] == "connected"
+
+ @patch("app.router.get_db")
+ def test_ready_returns_503_when_db_unavailable(self, mock_get_db):
+ """Test readiness endpoint returns 503 when database is unavailable."""
+ mock_get_db.side_effect = Exception("Connection refused")
+
+ response = client.get("/ready")
+ assert response.status_code == 503
diff --git a/backend/tests/test_journal.py b/backend/tests/test_journal.py
new file mode 100644
index 0000000..8452888
--- /dev/null
+++ b/backend/tests/test_journal.py
@@ -0,0 +1,30 @@
+"""Tests for journal CRUD endpoints."""
+import pytest
+from unittest.mock import patch, MagicMock
+from fastapi.testclient import TestClient
+from app.main import app
+
+client = TestClient(app)
+
+
+class TestJournalEndpoints:
+ """Tests for /journal endpoints."""
+
+ @patch("app.router.get_db")
+ def test_get_journal_returns_list(self, mock_get_db):
+ """Test GET /journal returns a list."""
+ mock_conn = MagicMock()
+ mock_cursor = MagicMock()
+ mock_cursor.__enter__.return_value = mock_cursor
+ mock_cursor.fetchall.return_value = []
+ mock_conn.cursor.return_value = mock_cursor
+ mock_get_db.return_value = mock_conn
+
+ response = client.get("/journal")
+ assert response.status_code == 200
+ assert isinstance(response.json(), list)
+
+ def test_create_journal_without_text_fails(self):
+ """Test POST /journal fails without text."""
+ response = client.post("/journal", json={})
+ assert response.status_code == 422
diff --git a/backend/tests/test_sessions.py b/backend/tests/test_sessions.py
new file mode 100644
index 0000000..4fbefd1
--- /dev/null
+++ b/backend/tests/test_sessions.py
@@ -0,0 +1,30 @@
+"""Tests for session CRUD endpoints."""
+import pytest
+from unittest.mock import patch, MagicMock
+from fastapi.testclient import TestClient
+from app.main import app
+
+client = TestClient(app)
+
+
+class TestSessionEndpoints:
+ """Tests for /sessions endpoints."""
+
+ @patch("app.router.get_db")
+ def test_get_sessions_returns_list(self, mock_get_db):
+ """Test GET /sessions returns a list."""
+ mock_conn = MagicMock()
+ mock_cursor = MagicMock()
+ mock_cursor.__enter__.return_value = mock_cursor
+ mock_cursor.fetchall.return_value = []
+ mock_conn.cursor.return_value = mock_cursor
+ mock_get_db.return_value = mock_conn
+
+ response = client.get("/sessions")
+ assert response.status_code == 200
+ assert isinstance(response.json(), list)
+
+ def test_create_session_without_duration_fails(self):
+ """Test POST /sessions fails without duration."""
+ response = client.post("/sessions", json={})
+ assert response.status_code == 422
diff --git a/backend/tests/test_tasks.py b/backend/tests/test_tasks.py
new file mode 100644
index 0000000..1b990af
--- /dev/null
+++ b/backend/tests/test_tasks.py
@@ -0,0 +1,73 @@
+"""Tests for task CRUD endpoints."""
+import pytest
+from unittest.mock import patch, MagicMock
+from fastapi.testclient import TestClient
+from app.main import app
+
+client = TestClient(app)
+
+
+class TestTaskEndpoints:
+ """Tests for /tasks endpoints."""
+
+ @patch("app.router.get_db")
+ def test_get_tasks_returns_list(self, mock_get_db):
+ """Test GET /tasks returns a list."""
+ mock_conn = MagicMock()
+ mock_cursor = MagicMock()
+ mock_cursor.__enter__.return_value = mock_cursor
+ mock_cursor.fetchall.return_value = []
+ mock_conn.cursor.return_value = mock_cursor
+ mock_get_db.return_value = mock_conn
+
+ response = client.get("/tasks")
+ assert response.status_code == 200
+ assert isinstance(response.json(), list)
+
+ @patch("app.router.get_db")
+ def test_create_task(self, mock_get_db):
+ """Test POST /tasks creates a new task."""
+ mock_conn = MagicMock()
+ mock_cursor = MagicMock()
+ mock_cursor.__enter__.return_value = mock_cursor
+ mock_cursor.fetchone.return_value = {"id": 1, "title": "New Task", "completed": False}
+ mock_conn.cursor.return_value = mock_cursor
+ mock_get_db.return_value = mock_conn
+
+ response = client.post("/tasks", json={"title": "New Task"})
+ assert response.status_code == 200
+ data = response.json()
+ assert data["title"] == "New Task"
+ assert data["completed"] is False
+
+ @patch("app.router.get_db")
+ def test_toggle_task_completion(self, mock_get_db):
+ """Test PUT /tasks/{id} toggles task completion."""
+ mock_conn = MagicMock()
+ mock_cursor = MagicMock()
+ mock_cursor.__enter__.return_value = mock_cursor
+ mock_cursor.fetchone.return_value = {"id": 1, "title": "Toggle Task", "completed": True}
+ mock_conn.cursor.return_value = mock_cursor
+ mock_get_db.return_value = mock_conn
+
+ response = client.put("/tasks/1", json={"completed": True})
+ assert response.status_code == 200
+ assert response.json()["completed"] is True
+
+ @patch("app.router.get_db")
+ def test_update_nonexistent_task_returns_404(self, mock_get_db):
+ """Test PUT /tasks/{id} returns 404 for invalid ID."""
+ mock_conn = MagicMock()
+ mock_cursor = MagicMock()
+ mock_cursor.__enter__.return_value = mock_cursor
+ mock_cursor.fetchone.return_value = None
+ mock_conn.cursor.return_value = mock_cursor
+ mock_get_db.return_value = mock_conn
+
+ response = client.put("/tasks/99999", json={"completed": True})
+ assert response.status_code == 404
+
+ def test_create_task_without_title_fails(self):
+ """Test POST /tasks fails without title."""
+ response = client.post("/tasks", json={})
+ assert response.status_code == 422
diff --git a/docker-compose.yml b/docker-compose.yml
index cc47769..6df3ccf 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -10,6 +10,11 @@ services:
volumes:
- ./data/postgres:/var/lib/postgresql/data
restart: always
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
+ interval: 5s
+ timeout: 5s
+ retries: 5
backend:
build:
@@ -17,10 +22,15 @@ services:
dockerfile: Dockerfile
ports:
- "8000:8000"
+ environment:
+ - DATABASE_URL=postgresql://postgres:postgrespassword@postgres:5432/focusflow
+ - CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:3001,http://localhost:3000}
+ - ENVIRONMENT=${ENVIRONMENT:-development}
volumes:
- ./assets:/app/assets
depends_on:
- - postgres
+ postgres:
+ condition: service_healthy
restart: always
frontend:
@@ -29,6 +39,8 @@ services:
dockerfile: Dockerfile
ports:
- "3001:3000"
+ environment:
+ - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://localhost:8000}
depends_on:
- backend
restart: always
diff --git a/frontend/.dockerignore b/frontend/.dockerignore
index f9e79aa..6e0685d 100644
--- a/frontend/.dockerignore
+++ b/frontend/.dockerignore
@@ -1,6 +1,51 @@
-node_modules
-.next
-.git
+# Dependencies
+node_modules/
+.pnp
+.pnp.js
+
+# Next.js
+.next/
+out/
+
+# Production
+build/
+dist/
+
+# Testing
+coverage/
+
+# Environment
+.env
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# Debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# IDE
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+
+# Git
+.git/
.gitignore
+
+# Documentation
*.md
+LICENSE
+
+# Docker
+Dockerfile
+docker-compose.yml
+.dockerignore
+
+# OS
.DS_Store
+Thumbs.db
diff --git a/frontend/next.config.ts b/frontend/next.config.ts
index 68a6c64..b88ebf8 100644
--- a/frontend/next.config.ts
+++ b/frontend/next.config.ts
@@ -2,6 +2,17 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "standalone",
+ // Enable React strict mode for better development experience
+ reactStrictMode: true,
+ // Optimize images for production
+ images: {
+ formats: ["image/avif", "image/webp"],
+ },
+ // Enable experimental features if needed
+ experimental: {
+ // Enable server actions if needed
+ serverActions: true,
+ },
};
export default nextConfig;
diff --git a/frontend/vercel.json b/frontend/vercel.json
new file mode 100644
index 0000000..70fb599
--- /dev/null
+++ b/frontend/vercel.json
@@ -0,0 +1,22 @@
+{
+ "version": 2,
+ "builds": [
+ {
+ "src": "package.json",
+ "use": "@vercel/next"
+ }
+ ],
+ "routes": [
+ {
+ "src": "/api/(.*)",
+ "dest": "/api/$1"
+ },
+ {
+ "src": "/(.*)",
+ "dest": "/"
+ }
+ ],
+ "env": {
+ "NEXT_PUBLIC_API_URL": "@api-url"
+ }
+}
diff --git a/railway.json b/railway.json
new file mode 100644
index 0000000..1bada59
--- /dev/null
+++ b/railway.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "https://railway.app/railway.schema.json",
+ "build": {
+ "builder": "DOCKERFILE",
+ "dockerfilePath": "Dockerfile"
+ },
+ "deploy": {
+ "startCommand": "uvicorn app.main:app --host 0.0.0.0 --port $PORT",
+ "healthcheckPath": "/health",
+ "healthcheckTimeout": 300,
+ "restartPolicyType": "ON_FAILURE",
+ "restartPolicyMaxRetries": 3
+ }
+}