Implement Pomodoro timer/focus session feature - #34
Conversation
…utton group for enhanced task management
…or improved task management
…ent and mode selection
…interaction behavior
There was a problem hiding this comment.
Pull request overview
This PR adds a client-side Pomodoro / focus session feature with a global timer panel, task-level “start focus session” controls, and supporting context/hooks/components, along with initial unit tests.
Changes:
- Introduces a global
PomodoroProvider+PomodoroPanelto manage and display focus sessions across the app. - Adds task details sidebar controls to start/restart a focus session for a specific task and choose a focus mode.
- Adds supporting UI (
ButtonGroup), hooks, alarm/notification helper, and test coverage for the new modules.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/client/App.tsx | Wraps app with PomodoroProvider and mounts the global PomodoroPanel. |
| src/client/context/pomodoro-context.tsx | Implements Pomodoro/Flowtime state machine, ticking, mode config, and actions. |
| src/client/hooks/use-kitchen-timer-alarm.ts | Adds Web Audio + Notification alarm helper for phase transitions. |
| src/client/components/pomodoro-panel.tsx | Provides the collapsible timer UI and controls. |
| src/client/hooks/use-task-pomodoro-action.ts | Encapsulates per-task “start/restart session” behavior (with confirm-on-replace). |
| src/client/components/task-focus-session-action.tsx | Renders task-level focus session CTA + mode selector. |
| src/client/components/task-details-content.tsx | Integrates the task focus session action into the task details sidebar. |
| src/client/components/ui/button-group.tsx | Adds a reusable component to visually group buttons/selects. |
| src/client/tests/pomodoro-context.test.tsx | Tests basic session start mode selection + mode-change restrictions. |
| src/client/tests/use-task-pomodoro-action.test.tsx | Tests per-task session start/restart and confirm-to-replace behavior. |
| src/client/tests/task-focus-session-action.test.tsx | Tests rendering and disabled/click behavior of the task action UI. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…rove session handling
…st stopwatch logic
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Phase complete — trigger alarm and transition | ||
| const currentState = getState() | ||
| const alarmMsg = | ||
| prev.phase === 'work' | ||
| ? `Work phase complete for "${currentState.taskName ?? 'task'}". Time to rest!` | ||
| : `Rest complete for "${currentState.taskName ?? 'task'}". Ready for the next interval!` | ||
|
|
||
| playAlarm(alarmMsg) | ||
|
|
||
| if (prev.phase === 'work') { | ||
| // Transition to rest | ||
| const restSeconds = mode.restSeconds ?? prev.flowtimeRestMinutes * 60 | ||
| return { | ||
| ...prev, | ||
| ...elapsedUpdate, | ||
| phase: 'rest', | ||
| secondsRemaining: restSeconds, | ||
| } | ||
| } else { | ||
| // Rest complete → go idle, keep elapsed totals, reset for next round | ||
| clearTick() | ||
| return { |
There was a problem hiding this comment.
startTick triggers side effects (playAlarm and clearTick) inside the setState functional updater. React expects state updaters to be pure; in StrictMode/dev or under update rebasing this can fire alarms multiple times or clear intervals unexpectedly. Move these side effects out of the updater (e.g., compute the next state in the interval callback, then call playAlarm/clearTick based on the transition, or trigger them from a useEffect that watches phase/status transitions).
| // Rest complete → go idle, keep elapsed totals, reset for next round | ||
| clearTick() | ||
| return { | ||
| ...prev, | ||
| ...elapsedUpdate, | ||
| status: 'idle', | ||
| phase: 'work', | ||
| secondsRemaining: mode.workSeconds, | ||
| } |
There was a problem hiding this comment.
When rest completes, the state transitions to idle but keeps taskId/taskName and (for Flowtime) leaves flowtimeWorkSeconds unchanged. This makes the panel show a stale +MM:SS stopwatch value after rest finishes, even though a new work interval hasn’t started. Consider resetting flowtimeWorkSeconds (and/or clearing taskId/taskName if an idle state should mean “no active session”).
| const hasActiveSession = state.taskId !== null | ||
| const isDifferentTask = hasActiveSession && state.taskId !== task.id | ||
|
|
||
| if (isDifferentTask) { | ||
| const activeTaskName = state.taskName ?? 'the current task' | ||
| const confirmed = window.confirm(`Replace current Pomodoro session for "${activeTaskName}"?`) | ||
| if (!confirmed) return | ||
| } |
There was a problem hiding this comment.
hasActiveSession is derived only from state.taskId !== null, but the context can be status: 'idle' while keeping taskId set (e.g., after rest completes). That will prompt to “replace current session” even when no timer is running. Use state.status !== 'idle' (or similar) as part of the active-session check so confirmation only happens for running/paused sessions.
There was a problem hiding this comment.
This is wanted behaviour for now
| // Only allow changing mode when idle or paused | ||
| setState((prev) => { | ||
| if (prev.status === 'running') return prev | ||
| return { ...prev, mode } |
There was a problem hiding this comment.
setMode updates only mode when status is paused/idle, but it leaves secondsRemaining (and phase) untouched. This can put the timer into an inconsistent state (e.g., paused with “Standard” selected but remaining time from the previous mode), and subsequent transitions/rest durations will use the new mode’s config. If mode switching while paused is intended, update the timing fields to match the newly selected mode (or disallow mode changes once a session has started, even if paused).
| return { ...prev, mode } | |
| const nextMode = POMODORO_MODES[mode] | |
| return { | |
| ...prev, | |
| mode, | |
| phase: 'work', | |
| secondsRemaining: nextMode.workSeconds ?? 0, | |
| flowtimeWorkSeconds: nextMode.workSeconds === null ? 0 : prev.flowtimeWorkSeconds, | |
| } |
…ndex for better visibility
This pull request introduces a Pomodoro/focus session feature to the client application, allowing users to start and control focus timers directly from the UI. The main changes include adding a Pomodoro timer panel, integrating focus session controls into task details, and introducing supporting components and hooks.
Pomodoro Timer Feature Integration:
PomodoroProviderandPomodoroPanelto theAppcomponent, enabling Pomodoro timer state and controls across the application. (src/client/App.tsx)PomodoroPanelcomponent, providing a UI for starting, pausing, resetting, and ending Pomodoro or Flowtime sessions, with support for rest duration selection and mode switching. (src/client/components/pomodoro-panel.tsx)Task Focus Session Controls:
src/client/components/task-details-content.tsx)TaskFocusSessionActioncomponent, which provides a button and mode selector for starting a focus session from a task. (src/client/components/task-focus-session-action.tsx)useTaskPomodoroActionhook for managing state and actions related to starting Pomodoro sessions for tasks. (src/client/hooks/use-task-pomodoro-action)UI Component Enhancements:
ButtonGroupcomponent for grouping buttons and selectors in a visually consistent way, supporting both horizontal and vertical orientations. (src/client/components/ui/button-group.tsx)