Read pomodoso-spec.md for the complete product and technical context. This file contains operational rules and conventions for working on the codebase.
- Backend: Rust + Axum + sqlx + PostgreSQL
- Frontend (web app): React + Vite + TypeScript
- Frontend (extension): Chrome MV3 + React + TypeScript
- Local storage on clients: IndexedDB via Dexie.js
- Monorepo: pnpm workspaces, optionally Turborepo for build orchestration
- Real-time: WebSockets between clients and backend
pomodoso/
├── apps/
│ ├── backend/ # Rust, self-contained Cargo project
│ ├── web/ # React + Vite web app
│ ├── extension/ # Chrome MV3 extension
│ └── marketing/ # Landing page (optional)
├── packages/
│ ├── shared/ # TS types, sync engine, validation schemas
│ ├── ui/ # Shared React components
│ └── api-client/ # HTTP + WS client
├── docs/
│ ├── decisions/ # ADRs (architectural decision records)
│ └── mockups/
├── infra/
└── .github/workflows/
strict: truealways. Noanyunless explicitly justified with a comment.- Prefer named exports over default exports for shared code.
- Types live in
packages/shared/src/types. Apps consume from there.
cargo fmt+cargo clippymandatory before committing.- Use
sqlx::query!macros for compile-time SQL verification. - Errors with
thiserrorfor libraries,anyhowfor application code. - Async with
tokio.
- Database tables:
snake_case, singular (task,pomodoro_session). - API endpoints: kebab-case in URLs,
snake_casekeys in JSON bodies. - TypeScript interfaces and types:
PascalCase. - Rust structs and enums:
PascalCase. - Files: kebab-case for TS, snake_case for Rust modules.
- Conventional commits:
feat:,fix:,refactor:,chore:,docs:,test:. - Main branch is
main. Nodevelopbranch. - Feature branches:
feat/<short-description>orfix/<short-description>. - Squash and merge on PRs.
-
Entitlements gate every paid feature. Never check
plan === "pro"in the UI. Always checkentitlements.features.<flag>. See spec section 15. -
Sync is built but disabled in MVP. All users start as Free. Free users don't open WebSocket connections nor call sync push/pull endpoints. The sync engine code exists from day 1 but is gated.
-
All IDs are client-generated UUIDs. Never use database auto-increment IDs. This enables offline creation and conflict-free sync.
-
Soft delete via
deleted_at. NeverDELETE FROMdirectly. Sync needs tombstones. -
Every syncable entity has
created_at,updated_at,deleted_at(nullable),synced_at(nullable). -
Workspaces own work-context data. Tasks, projects, pomodoro sessions and task orders carry a
workspace_idFK. The exceptions are:user,workspace,workspace_member,subscription, and the user-scoped entitiesuser_setting,device,detection_rule,habit,habit_log— these are keyed byuser_idand sync globally (habits are personal, not per-work-context; see spec v0.10). User-scoped entities are pushed/pulled outside the workspace loop insync.rs. -
Last-Write-Wins (LWW) at the record level. Conflicts resolve by comparing
updated_at. No field-level merging. -
The pomodoro timer is server-authoritative when sync is enabled, device-local when disabled. UI shows the active timer from server state.
-
Ticket parsing is DOM-first, no auth required. Use Open Graph metadata first, provider-specific selectors second. Linear/GitHub API integrations are post-MVP optional enrichment.
-
No AI features in MVP. Listed as nice-to-have in spec but explicitly out of scope for v1.
- Don't add new top-level dependencies without checking they're necessary. Prefer standard library and existing deps.
- Don't write generic abstractions before duplication appears. WET (Write Everything Twice) before refactoring.
- Don't mix Rust and Node tooling in the same directory. Backend lives in
apps/backendand uses Cargo only. - Don't introduce a state management library (Redux, Zustand, etc.) without strong justification. React state + TanStack Query is the default.
- Don't put business logic in components. Components consume hooks; hooks consume
@pomodoso/shared. - Don't call third-party APIs from the client (extension or web app). All external API calls go through the backend.
The extension uses Dexie.js (extension/src/db.ts). When adding or removing a table:
- Add a new Dexie version with the schema migration.
- Update
extension/src/backup.ts— add the new table toEXPECTED_TABLES, theexportDb()reads, and theimportDb()clears/bulkPuts. Omitting a table silently breaks import/export for that data. - If the table contains sensitive data (e.g. OAuth tokens), add its key to
EXCLUDED_SETTINGSinstead of including it in the backup.
When implementing a feature from the spec:
- Read the relevant spec section first. Don't infer from the code; the spec is source of truth.
- Update the schema before the code. New tables go in
apps/backend/migrations/with a numbered SQL file. - Define types in
packages/sharedfirst. Both apps consume from there. - Backend endpoint before frontend integration. Get the API working with
curlbefore wiring UI. - Add to entitlements if it's a paid feature. Default to
falsefor the free tier. - Test the sync path. Any new entity must round-trip through push/pull correctly.
The product is built in three phases (spec section 5):
- Phase 0: Foundation (auth, schema, sync engine, basic infra).
- Phase 1: MVP core (pomodoro, work log, tasks, habits, daily/weekly reports, single workspace, no sync active).
- Phase 2: Calendar integration, advanced reports, history heatmap, multi-workspace, sync activated for paid users.
- Phase 3: AI features, API integrations (Linear/GitHub OAuth), custom providers.
Do not implement Phase 2 or 3 features unless explicitly asked. Even if a feature is "easy" to add, scope creep is the enemy of shipping.
- MVP: Everyone is on the Free plan. No billing infrastructure. No Stripe.
- The
subscriptiontable exists from day 1. Every new user gets a row withplan = "free",status = "active". - Owner override: Use
subscription.feature_overrides(JSONB) to enable paid features for specific users during development. - No upgrade flow in MVP. Show "Coming soon" or grayed-out states for paid features.
# Install all dependencies
pnpm install
# Run backend (from apps/backend)
cd apps/backend && cargo run
# Run web app dev server
pnpm --filter web dev
# Build extension for side-loading
pnpm --filter extension build
# Run all tests
pnpm test # frontend
cd apps/backend && cargo test # backend
# Database migrations
cd apps/backend && sqlx migrate run- Check the spec first. The spec has decisions; trust them.
- If the spec is silent, ask before assuming.
- If you must assume, document the assumption in
docs/decisions/as an ADR. - Smaller PRs are always better than larger ones.
The spec (pomodoso-spec.md) is the source of truth. When you make a decision that contradicts or extends the spec:
- Update the spec in the same PR.
- Bump the version in the spec header.
- Add a line to the spec changelog explaining what changed.