# Feature: Timer Breaks (Pause During Work) ## Overview Allow users to take breaks while a timer is running. When on break, elapsed time is frozen and break time is tracked. When resumed, break time accumulates and is subtracted from the displayed work time. ## User Experience ### Timer States 1. **Running** — normal state, elapsed time ticking 2. **On Break** — elapsed time frozen, break time ticking, Stop/Cancel buttons disabled 3. **Stopped** — no timer active ### UI Changes (TimerWidget) - Add a **"Break"** button (amber, `Pause` icon) next to Stop when timer is running - When on break: - Change pulsing dot color from red to amber - Elapsed time frozen at net work time - Show break time below: `Break: Xm XXs` (live-ticking) - Replace "Break" button with **"Resume"** button (green, `Play` icon) - **Disable** Stop and Cancel buttons (tooltip: "Resume before stopping") ### Duration Calculations - **Work time (displayed):** `now - startTime - totalBreakSeconds` - Where `totalBreakSeconds = (breakMinutes * 60) + (now - breakStart if on break)` - When on break: frozen at `(breakStart - startTime - breakMinutes * 60)` - **Break time (displayed):** `breakMinutes * 60 + (now - breakStart if on break)` ## Implementation ### 1. Database Schema (`backend/prisma/schema.prisma`) Add two fields to `OngoingTimer`: ```prisma model OngoingTimer { // ... existing fields ... breakMinutes Int @default(0) @map("break_minutes") breakStart DateTime? @map("break_start") @db.Timestamptz() } ``` Run: `npx prisma migrate dev --name add_timer_break_fields` ### 2. Backend Service (`backend/src/services/timer.service.ts`) **New method `startBreak(userId)`:** - Get ongoing timer, throw `NotFoundError` if none - Check `timer.breakStart` is null (not already on break), throw `BadRequestError` if on break - Update: `breakStart = new Date()` - Return updated timer **New method `endBreak(userId)`:** - Get ongoing timer, throw `NotFoundError` if none - Check `timer.breakStart` is not null, throw `BadRequestError` if not on break - Calculate additional break minutes: `Math.floor((now - breakStart) / 60000)` - Update: `breakMinutes += additionalMinutes`, `breakStart = null` - Return updated timer **Modify `stop(userId)`:** - Before creating time entry, check `timer.breakStart` is null — throw `BadRequestError("Cannot stop timer while on break")` if break is active - When creating `TimeEntry`, set `breakMinutes: timer.breakMinutes` **Modify `cancel(userId)`:** - Check `timer.breakStart` is null — throw `BadRequestError("Cannot cancel timer while on break")` if break is active ### 3. Backend Routes (`backend/src/routes/timer.routes.ts`) Add two new routes (both require auth, no body validation): ``` POST /api/timer/break → timerService.startBreak(userId) POST /api/timer/resume → timerService.endBreak(userId) ``` ### 4. MCP Tools (`backend/src/routes/mcp.routes.ts`) Add two MCP tools: `pause_timer` and `resume_timer`. ### 5. Frontend Types (`frontend/src/types/index.ts`) Update `OngoingTimer` interface: ```typescript export interface OngoingTimer { // ... existing fields ... breakMinutes: number; breakStart: string | null; } ``` ### 6. Frontend API (`frontend/src/api/timer.ts`) Add two methods: ```typescript startBreak: async (): Promise => { ... } endBreak: async (): Promise => { ... } ``` ### 7. Frontend TimerContext (`frontend/src/contexts/TimerContext.tsx`) - Add `breakSeconds` state (live-updating, similar to `elapsedSeconds`) - Expose `isOnBreak` derived boolean (`ongoingTimer?.breakStart !== null`) - Update elapsed time calculation: - Running: `(now - startTime) - (breakMinutes * 60) - (now - breakStart if on break)` - On break: `(breakStart - startTime) - (breakMinutes * 60)` (frozen) - Break seconds: `(breakMinutes * 60) + (now - breakStart if on break)` - Add `startBreak()` and `endBreak()` callbacks - Expose `breakSeconds` and `isOnBreak` in context value ### 8. Frontend TimerWidget (`frontend/src/components/TimerWidget.tsx`) - Import `Pause` icon from lucide-react - Add Break/Resume button between project selector and Stop button - Show break time display when `breakSeconds > 0` or `isOnBreak` - Change dot color to amber when on break - Disable Stop/Cancel when on break with tooltip ## Files to Modify (in order) | # | File | Change | |---|------|--------| | 1 | `backend/prisma/schema.prisma` | Add `breakMinutes`, `breakStart` to `OngoingTimer` | | 2 | `backend/src/services/timer.service.ts` | Add `startBreak()`, `endBreak()`, modify `stop()` and `cancel()` | | 3 | `backend/src/routes/timer.routes.ts` | Add `/break` and `/resume` routes | | 4 | `backend/src/routes/mcp.routes.ts` | Add `pause_timer` and `resume_timer` MCP tools | | 5 | `frontend/src/types/index.ts` | Add `breakMinutes`, `breakStart` to `OngoingTimer` | | 6 | `frontend/src/api/timer.ts` | Add `startBreak()`, `endBreak()` API methods | | 7 | `frontend/src/contexts/TimerContext.tsx` | Add break state, `breakSeconds`, `isOnBreak`, break methods | | 8 | `frontend/src/components/TimerWidget.tsx` | Add break UI (button, display, disabled states) | ## Edge Cases - Break start must be after timer start (always true since break is clicked after start) - Break duration naturally cannot exceed work duration (breakStart > startTime) - On stop: reject if break is active (user must resume first) - On cancel: reject if break is active (user must resume first) - Break minutes accumulate across multiple break/resume cycles - Timer refetch (every 60s) will sync break state from server ## Verification - Run `npm run lint` in both `frontend/` and `backend/` - Run `npm run build` in both `frontend/` and `backend/` - Manual testing: start timer → break → verify elapsed frozen, break ticking → resume → verify break added to total → stop → verify time entry has correct breakMinutes