Smart Pomodoro
Calendar blocks became recoverable app-local focus sessions with planning, presets, runtime state, and history.

How the project is put together
6 layers / 5 directed links
100%
- 01Interface
Today cockpit, timer, presets, history, settings, and active focus experience.
Next.js / React 19 / Tailwind CSS / Three.js - 02Application
Calendar-derived sessions, recovery, progress, recommendations, and UI state orchestration.
TanStack Query / Timer runtime / Shared planning logic - 03Services/API
Auth, calendars, sessions, presets, settings, and smart planning endpoints.
NestJS / Zod / Google APIs / Drizzle - 04Data
Users, settings, OAuth tokens, sessions, presets, progress, and completion history.
PostgreSQL / Drizzle ORM / Migrations - 05Auth/Permissions
Read-only calendar access with server-side token encryption and HTTP-only session cookies.
Google OAuth / Signed cookies / Encrypted tokens - 06Runtime
Workspace dev flow with local Docker Postgres and intended DigitalOcean/Dokploy deployment.
pnpm / Turborepo / Docker Postgres / Dokploy target
From broken workflow to operating system
Study plans lived in the calendar while actual timer sessions lived somewhere else.
A Next/Nest product reads focus calendar blocks, generates local intervals, and runs a resilient focus timer.
The workflow constraint
Focus planning usually lives apart from the calendar, so planned study blocks and actual timer sessions drift apart. The product needed to respect calendar intent while keeping timer state local and recoverable.
What changed
Decisions and trade-offs
Read-only Calendar integration
The app needed calendar context without taking over the user's calendar or filling it with generated focus/break entries.
Decision: Read selected focus calendar events and generate intervals inside the app instead of writing generated sessions back to Google Calendar.
Trade-off: Calendar remains cleaner and safer, but focus-session completion data must live in the app database.
Timestamp-based timer over interval counting
Browser timers can drift when tabs are backgrounded or the device sleeps.
Decision: Calculate active session progress from timestamps and persisted session state rather than trusting every interval tick.
Trade-off: Slightly more runtime state management, but recovery and completion behavior are much more reliable.
NestJS API over server-only Next routes
The app has auth, calendar, sessions, presets, settings, and planning domains that benefit from backend module boundaries.
Decision: Use NestJS with Drizzle/PostgreSQL as a separate API layer behind the Next frontend.
Trade-off: More services to run locally, but cleaner domain boundaries and deployment flexibility.
Constraints, architecture, and proof
Next.js App Router renders Today, Timer, Presets, History, and Settings. TanStack Query talks to a NestJS API for auth, calendars, presets, sessions, settings, and smart planning. Drizzle persists state in PostgreSQL, while Google OAuth tokens are encrypted and sessions use signed HTTP-only cookies.
Deployment, security, and maintenance
Signed HTTP-only cookies, encrypted OAuth tokens, read-only calendar scope, persisted session progress/completion state, and shared pure planning logic reduce calendar, auth, and timer failure risk.