A desktop-first Pomodoro timer built with Next.js, TypeScript, Tailwind CSS, and Space Grotesk. The product direction is an immersive focus workspace inspired by study-with-me timers: a clean countdown, automatic Pomodoro sequencing, visual progress, aesthetic backgrounds, todos, alert sounds, and optional cloud-synced settings.
The current implementation contains the core timer experience. The full v1 product scope is documented in PRD.md.
Implemented:
- Pomodoro, short break, and long break modes
- Default session lengths: 25 / 5 / 15 minutes
- Start, pause, and reset controls
- Drift-safe countdown based on
Date.now()instead of interval-only decrements - Automatic sequence: Pomodoro -> short break, with every fourth Pomodoro leading to a long break
- Four-dot Pomodoro cycle progress indicator
- Next.js App Router, TypeScript, Tailwind CSS, and Space Grotesk setup
Planned for v1:
- Video background themes with poster fallbacks
- Settings modal for durations, theme, sound, notifications, and account state
- Right-rail todo list with local persistence
- Alert sounds and browser notifications
- Live tab-title countdown and favicon updates
- Collapsible Spotify or YouTube lofi embed
- Optional Supabase sign-in with local-to-cloud migration
- FastAPI-backed settings and todo routes proxied under
/api/py/*
- Framework: Next.js App Router
- Language: TypeScript
- UI: React, Tailwind CSS
- Typography:
@fontsource/space-grotesk - Planned API: FastAPI, exposed through Next.js rewrites
- Planned persistence: localStorage by default, Supabase for optional cloud sync
- Node.js 20 or newer
- pnpm
pnpm installpnpm devOpen http://localhost:3000.
pnpm build
pnpm startCloud sync is optional — the app runs fully anonymously against localStorage. To enable sign-in and cloud-synced settings/todos:
-
Create a project at supabase.com.
-
Copy your project URL and anon key into a local
.env.localfile at the repo root:NEXT_PUBLIC_SUPABASE_URL=... NEXT_PUBLIC_SUPABASE_ANON_KEY=...A template lives in
.env.local.example..env.localis gitignored. -
Open the Supabase SQL editor for your project and run
supabase/schema.sql. This creates thesettingsandtodostables and enables Row-Level Security so each user can only read and write their own rows.
The Python backend in api/ implements the settings and todo routes listed under "Planned Backend Integration". Next.js rewrites /api/py/:path* to http://127.0.0.1:8000/api/py/:path* in development (see next.config.ts). On Vercel, the api/*.py files deploy as Python serverless functions automatically.
The Python process reads the same Supabase credentials as the frontend, without the NEXT_PUBLIC_ prefix:
SUPABASE_URL=...
SUPABASE_ANON_KEY=...
Put them in .env.local (already gitignored) or export them in the shell that runs uvicorn. The backend uses the user's Authorization: Bearer <jwt> header to construct a Supabase client; Row-Level Security enforces per-user isolation server-side.
Install Python deps (one time):
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtThen, in a second terminal alongside pnpm dev:
uvicorn api.index:app --reload --port 8000Smoke test: curl http://127.0.0.1:8000/api/py/health should return {"ok":true}.
pnpm dev # Start the local development server
pnpm build # Build the production app
pnpm start # Start the production server
pnpm lint # Run ESLint
pnpm typecheck # Run TypeScript without emitting filesapp/
layout.tsx Root layout, metadata, and font imports
page.tsx Main timer page
globals.css Tailwind import and global font variable
components/
Timer.tsx Timer display and composition
ModePills.tsx Pomodoro / short break / long break selector
Controls.tsx Start, pause, and reset controls
lib/
timer/
sequence.ts Mode labels, default durations, and sequence state machine
useTimer.ts Drift-safe timer hook
PRD.md Product requirements and v1 roadmap
The timer avoids accumulating drift by storing an absolute end timestamp when a session starts:
endsAt = Date.now() + remainingMs;Each animation frame computes the remaining time from the current clock value. This keeps the countdown accurate when the browser throttles background tabs or the device sleeps briefly.
The sequence state machine lives in lib/timer/sequence.ts:
- Completing a Pomodoro increments the cycle count.
- Pomodoros 1-3 advance to a short break.
- Pomodoro 4 advances to a long break and resets the cycle count.
- Completing any break advances back to Pomodoro mode.
Default durations are currently defined in lib/timer/sequence.ts:
export const DEFAULT_DURATIONS = {
pom: 25,
short: 5,
long: 15,
};Future settings work will move these values into user-editable state with localStorage persistence and optional Supabase sync.
The repository already reserves a rewrite for the planned FastAPI backend:
source: "/api/py/:path*",
destination: "http://127.0.0.1:8000/api/py/:path*",The intended v1 API surface includes:
GET /api/py/settingsPUT /api/py/settingsGET /api/py/todosPOST /api/py/todosPATCH /api/py/todos/:idDELETE /api/py/todos/:id
These routes are not required for the current timer-only implementation.
The v1 delivery plan is tracked in PRD.md. Near-term milestones:
- Add theme manifest and background video layer.
- Add audio, browser notifications, tab title updates, and favicon countdown state.
- Add todos with localStorage persistence.
- Add settings modal and bind it to timer state.
- Add Supabase auth, schema, row-level security, and local-to-cloud sync.
- Polish metadata, accessibility, compressed assets, and deployment setup.
Before shipping a change:
pnpm typecheck
pnpm lint
pnpm buildManual smoke test:
- Open the app and confirm the timer starts at
25:00. - Start, pause, and reset the timer.
- Switch between Pomodoro, short break, and long break modes.
- Let a Pomodoro complete and confirm the app advances to a short break.
- Complete four Pomodoros and confirm the app advances to a long break.
No license has been added yet. Add one before distributing or accepting external contributions.
