summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSolstice <solstice@local>2026-06-09 01:10:46 -0700
committerSolstice <solstice@local>2026-06-09 01:10:46 -0700
commit887c0bc6f968f80ac90220f24bb578438e05708a (patch)
tree563925e9bc82ae0eee582dc9128ea753d0082ab0
parent4e2d978eb5fc9457d5b913bc10faf1266e6dcda4 (diff)
fix: resolve final release blockers
-rw-r--r--src-tauri/src/lib.rs66
-rw-r--r--src-tauri/src/state.rs26
-rw-r--r--src-tauri/src/storage.rs108
-rw-r--r--src-tauri/src/timer.rs35
-rw-r--r--src/components/TimerView.tsx23
-rw-r--r--src/hooks/useTimerEvents.ts44
-rw-r--r--src/store/audioStore.ts14
-rw-r--r--src/store/taskStore.ts7
-rw-r--r--src/store/timerStore.ts25
9 files changed, 279 insertions, 69 deletions
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index c298fdf..aff41cc 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -23,6 +23,17 @@ fn app_data_dir(app: &AppHandle) -> std::path::PathBuf {
app.path().app_data_dir().expect("Failed to resolve app data dir")
}
+fn persist_timer_snapshot(
+ timer: &TimerState,
+ data: &State<'_, AppDataWrapper>,
+ app: &AppHandle,
+) -> Result<(), String> {
+ let mut app_data = data.data.lock().unwrap();
+ app_data.current_task_id = timer.current_task_id.clone();
+ app_data.timer_snapshot = Some(timer.snapshot());
+ storage::save(&app_data_dir(app), &app_data)
+}
+
fn sync_audio_ducking(audio: &AudioState, phase: TimerPhase) {
if let Some(engine) = audio.0.lock().unwrap().as_mut() {
if audio::should_duck_for_phase(phase) {
@@ -49,19 +60,33 @@ fn get_timer_status(timer: State<'_, TimerStateWrapper>) -> serde_json::Value {
}
#[tauri::command]
-fn start_timer(timer: State<'_, TimerStateWrapper>) {
+fn start_timer(
+ timer: State<'_, TimerStateWrapper>,
+ data: State<'_, AppDataWrapper>,
+ app: AppHandle,
+) -> Result<(), String> {
let mut ts = timer.0.lock().unwrap();
ts.running = true;
+ persist_timer_snapshot(&ts, &data, &app)
}
#[tauri::command]
-fn pause_timer(timer: State<'_, TimerStateWrapper>) {
+fn pause_timer(
+ timer: State<'_, TimerStateWrapper>,
+ data: State<'_, AppDataWrapper>,
+ app: AppHandle,
+) -> Result<(), String> {
let mut ts = timer.0.lock().unwrap();
ts.running = false;
+ persist_timer_snapshot(&ts, &data, &app)
}
#[tauri::command]
-fn reset_timer(timer: State<'_, TimerStateWrapper>, data: State<'_, AppDataWrapper>) {
+fn reset_timer(
+ timer: State<'_, TimerStateWrapper>,
+ data: State<'_, AppDataWrapper>,
+ app: AppHandle,
+) -> Result<(), String> {
let mut ts = timer.0.lock().unwrap();
let total = {
let app_data = data.data.lock().unwrap();
@@ -74,6 +99,7 @@ fn reset_timer(timer: State<'_, TimerStateWrapper>, data: State<'_, AppDataWrapp
ts.remaining_secs = total;
ts.total_secs = total;
ts.running = false;
+ persist_timer_snapshot(&ts, &data, &app)
}
#[tauri::command]
@@ -82,7 +108,7 @@ fn skip_phase(
data: State<'_, AppDataWrapper>,
audio: State<'_, AudioState>,
app: AppHandle,
-) {
+) -> Result<(), String> {
use tauri::Emitter;
let mut ts = timer.0.lock().unwrap();
let app_data = data.data.lock().unwrap();
@@ -111,6 +137,7 @@ fn skip_phase(
ts.running = false;
let phase = ts.phase;
let session_count = ts.session_count;
+ persist_timer_snapshot(&ts, &data, &app)?;
drop(ts);
sync_audio_ducking(&audio, phase);
@@ -122,6 +149,8 @@ fn skip_phase(
session_count,
},
);
+
+ Ok(())
}
#[tauri::command]
@@ -132,19 +161,17 @@ fn set_current_task(
app: AppHandle,
) -> Result<(), String> {
{
- let mut app_data = data.data.lock().unwrap();
- // Validate task exists if Some
+ let app_data = data.data.lock().unwrap();
if let Some(ref tid) = task_id {
if !app_data.tasks.iter().any(|t| &t.id == tid) {
return Err(format!("Task {} not found", tid));
}
}
- app_data.current_task_id = task_id.clone();
- storage::save(&app_data_dir(&app), &app_data)?;
}
{
let mut ts = timer.0.lock().unwrap();
ts.current_task_id = task_id;
+ persist_timer_snapshot(&ts, &data, &app)?;
}
Ok(())
}
@@ -170,7 +197,6 @@ fn update_settings(
{
let mut app_data = data.data.lock().unwrap();
app_data.settings = settings.clone();
- storage::save(&app_data_dir(&app), &app_data)?;
}
// Restart timer at new work duration
{
@@ -180,6 +206,7 @@ fn update_settings(
ts.remaining_secs = settings.work_duration_secs;
ts.running = false;
ts.session_count = 0;
+ persist_timer_snapshot(&ts, &data, &app)?;
}
sync_audio_ducking(&audio, TimerPhase::Work);
Ok(())
@@ -199,6 +226,14 @@ fn add_task(
data: State<'_, AppDataWrapper>,
app: AppHandle,
) -> Result<Task, String> {
+ let name = name.trim().to_string();
+ if name.is_empty() {
+ return Err("Task name cannot be empty".to_string());
+ }
+ if total_sessions == 0 {
+ return Err("total_sessions must be at least 1".to_string());
+ }
+
let task = Task {
id: Uuid::new_v4().to_string(),
name,
@@ -268,6 +303,7 @@ fn delete_task(
let mut ts = timer.0.lock().unwrap();
if ts.current_task_id.as_deref() == Some(&id) {
ts.current_task_id = None;
+ persist_timer_snapshot(&ts, &data, &app)?;
}
}
Ok(())
@@ -363,14 +399,14 @@ pub fn run() {
// Load persisted data
let app_data = storage::load(&data_dir);
let work_secs = app_data.settings.work_duration_secs;
- let current_task_id = app_data.current_task_id.clone();
+ let timer_snapshot = app_data.timer_snapshot.clone();
let data_arc = Arc::new(Mutex::new(app_data));
- let timer_arc = Arc::new(Mutex::new({
- let mut ts = TimerState::new(work_secs);
- ts.current_task_id = current_task_id;
- ts
- }));
+ let timer_arc = Arc::new(Mutex::new(
+ timer_snapshot
+ .map(TimerState::from_snapshot)
+ .unwrap_or_else(|| TimerState::new(work_secs))
+ ));
// Register managed state
app.manage(TimerStateWrapper(Arc::clone(&timer_arc)));
diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs
index 8f3d52b..c6b4d03 100644
--- a/src-tauri/src/state.rs
+++ b/src-tauri/src/state.rs
@@ -1,6 +1,6 @@
use std::sync::{Arc, Mutex};
use serde::{Deserialize, Serialize};
-use crate::storage::AppData;
+use crate::storage::{AppData, TimerSnapshot};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
@@ -10,7 +10,7 @@ pub enum TimerPhase {
LongBreak,
}
-#[derive(Debug, Clone, Serialize)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimerState {
pub phase: TimerPhase,
pub remaining_secs: u64,
@@ -39,4 +39,26 @@ impl TimerState {
current_task_id: None,
}
}
+
+ pub fn from_snapshot(snapshot: TimerSnapshot) -> Self {
+ Self {
+ phase: snapshot.phase,
+ remaining_secs: snapshot.remaining_secs,
+ total_secs: snapshot.total_secs,
+ running: snapshot.running,
+ session_count: snapshot.session_count,
+ current_task_id: snapshot.current_task_id,
+ }
+ }
+
+ pub fn snapshot(&self) -> TimerSnapshot {
+ TimerSnapshot {
+ phase: self.phase,
+ remaining_secs: self.remaining_secs,
+ total_secs: self.total_secs,
+ running: self.running,
+ session_count: self.session_count,
+ current_task_id: self.current_task_id.clone(),
+ }
+ }
}
diff --git a/src-tauri/src/storage.rs b/src-tauri/src/storage.rs
index 8e2f148..2e726a3 100644
--- a/src-tauri/src/storage.rs
+++ b/src-tauri/src/storage.rs
@@ -2,6 +2,8 @@ use std::fs;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
+use crate::state::TimerPhase;
+
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Settings {
pub work_duration_secs: u64,
@@ -32,10 +34,22 @@ pub struct Task {
}
#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct TimerSnapshot {
+ pub phase: TimerPhase,
+ pub remaining_secs: u64,
+ pub total_secs: u64,
+ pub running: bool,
+ pub session_count: u32,
+ pub current_task_id: Option<String>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppData {
pub settings: Settings,
pub tasks: Vec<Task>,
pub current_task_id: Option<String>,
+ #[serde(default)]
+ pub timer_snapshot: Option<TimerSnapshot>,
}
impl Default for AppData {
@@ -44,6 +58,7 @@ impl Default for AppData {
settings: Settings::default(),
tasks: Vec::new(),
current_task_id: None,
+ timer_snapshot: None,
}
}
}
@@ -77,7 +92,100 @@ pub fn save(app_data_dir: &PathBuf, data: &AppData) -> Result<(), String> {
let tmp_path = app_data_dir.join("data.json.tmp");
fs::write(&tmp_path, contents)
.map_err(|e| format!("Failed to write temp data file: {}", e))?;
+
+ if cfg!(windows) {
+ if path.exists() {
+ fs::remove_file(&path)
+ .map_err(|e| format!("Failed to remove existing data file: {}", e))?;
+ }
+ }
+
fs::rename(&tmp_path, &path)
.map_err(|e| format!("Failed to finalize data file: {}", e))?;
Ok(())
}
+
+#[cfg(test)]
+mod tests {
+ use super::{load, save, AppData, Settings, Task, TimerSnapshot};
+ use crate::state::TimerPhase;
+ use std::fs;
+ use std::time::{SystemTime, UNIX_EPOCH};
+
+ fn temp_dir(label: &str) -> std::path::PathBuf {
+ let unique = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .unwrap()
+ .as_nanos();
+ std::env::temp_dir().join(format!("solstice-storage-{label}-{unique}"))
+ }
+
+ #[test]
+ fn load_preserves_persisted_timer_snapshot() {
+ let dir = temp_dir("load-timer-snapshot");
+ fs::create_dir_all(&dir).unwrap();
+
+ let json = serde_json::json!({
+ "settings": {
+ "work_duration_secs": 1500,
+ "short_break_secs": 300,
+ "long_break_secs": 900,
+ "sessions_before_long_break": 4
+ },
+ "tasks": [],
+ "current_task_id": "task-1",
+ "timer_snapshot": {
+ "phase": "short_break",
+ "remaining_secs": 120,
+ "total_secs": 300,
+ "running": false,
+ "session_count": 2,
+ "current_task_id": "task-1"
+ }
+ })
+ .to_string();
+ fs::write(dir.join("data.json"), json).unwrap();
+
+ let data = load(&dir);
+
+ let snapshot = data.timer_snapshot.expect("expected timer snapshot");
+ assert_eq!(snapshot.phase, TimerPhase::ShortBreak);
+ assert_eq!(snapshot.remaining_secs, 120);
+ assert_eq!(snapshot.total_secs, 300);
+ assert_eq!(snapshot.session_count, 2);
+ assert_eq!(snapshot.current_task_id.as_deref(), Some("task-1"));
+ }
+
+ #[test]
+ fn save_persists_timer_snapshot() {
+ let dir = temp_dir("save-timer-snapshot");
+ let data = AppData {
+ settings: Settings::default(),
+ tasks: vec![Task {
+ id: "task-1".to_string(),
+ name: "Deep work".to_string(),
+ total_sessions: 4,
+ remaining_sessions: 3,
+ completed: false,
+ created_at: "2026-06-09T00:00:00Z".to_string(),
+ }],
+ current_task_id: Some("task-1".to_string()),
+ timer_snapshot: Some(TimerSnapshot {
+ phase: TimerPhase::Work,
+ remaining_secs: 600,
+ total_secs: 1500,
+ running: true,
+ session_count: 1,
+ current_task_id: Some("task-1".to_string()),
+ }),
+ };
+
+ save(&dir, &data).unwrap();
+ let reloaded = load(&dir);
+
+ let snapshot = reloaded.timer_snapshot.expect("expected timer snapshot");
+ assert!(snapshot.running);
+ assert_eq!(snapshot.remaining_secs, 600);
+ assert_eq!(snapshot.total_secs, 1500);
+ }
+}
diff --git a/src-tauri/src/timer.rs b/src-tauri/src/timer.rs
index 322795e..02ae285 100644
--- a/src-tauri/src/timer.rs
+++ b/src-tauri/src/timer.rs
@@ -8,6 +8,12 @@ use crate::audio::{self, AudioEngine};
use crate::state::{TimerPhase, TimerState};
use crate::storage::{self, AppData};
+fn persist_timer_state(data: &mut AppData, timer: &TimerState, data_dir: &std::path::PathBuf) {
+ data.current_task_id = timer.current_task_id.clone();
+ data.timer_snapshot = Some(timer.snapshot());
+ let _ = storage::save(data_dir, data);
+}
+
// LOCK ORDERING: When acquiring multiple locks, always take timer_state
// before app_data to prevent deadlock. Never acquire app_data first
// while timer_state is not held.
@@ -68,20 +74,22 @@ pub fn spawn_timer_thread(
session_count: ts.session_count,
current_task_id: ts.current_task_id.clone(),
};
+
+ {
+ let mut data = data_arc.lock().unwrap();
+ persist_timer_state(&mut data, &ts, &data_dir);
+ }
+
let _ = app_handle.emit("timer-tick", tick);
// Check if phase has reached zero
if ts.remaining_secs == 0 {
ts.running = false;
+ let mut emit_completed = false;
+ let mut completed_task_id = None;
match ts.phase {
TimerPhase::Work => {
- // Emit completion before transitioning
- let completed = CompletedPayload {
- task_id: ts.current_task_id.clone(),
- };
- let _ = app_handle.emit("timer-completed", completed);
-
// Decrement task remaining sessions
let task_id = ts.current_task_id.clone();
{
@@ -96,8 +104,9 @@ pub fn spawn_timer_thread(
}
}
}
- let _ = storage::save(&data_dir, &data);
}
+ emit_completed = true;
+ completed_task_id = task_id;
ts.session_count += 1;
@@ -129,6 +138,11 @@ pub fn spawn_timer_thread(
}
}
+ {
+ let mut data = data_arc.lock().unwrap();
+ persist_timer_state(&mut data, &ts, &data_dir);
+ }
+
// Emit phase-changed after state is updated
if let Some(engine) = audio_arc.lock().unwrap().as_mut() {
if audio::should_duck_for_phase(ts.phase) {
@@ -138,6 +152,13 @@ pub fn spawn_timer_thread(
}
}
+ if emit_completed {
+ let completed = CompletedPayload {
+ task_id: completed_task_id,
+ };
+ let _ = app_handle.emit("timer-completed", completed);
+ }
+
let phase_changed = PhaseChangedPayload {
phase: ts.phase,
session_count: ts.session_count,
diff --git a/src/components/TimerView.tsx b/src/components/TimerView.tsx
index 896c03a..b43973c 100644
--- a/src/components/TimerView.tsx
+++ b/src/components/TimerView.tsx
@@ -54,6 +54,7 @@ export function TimerView() {
const { phase, remainingSecs, totalSecs, running, sessionCount, currentTaskId } =
useTimerStore();
const tasks = useTaskStore((s) => s.tasks);
+ const syncFromBackend = useTimerStore((s) => s.syncFromBackend);
const currentTask = tasks.find((t) => t.id === currentTaskId) ?? null;
@@ -61,10 +62,22 @@ export function TimerView() {
const dashOffset = CIRCUMFERENCE * (1 - progress);
const arcColor = phaseColor(phase);
- const handleStart = () => invoke('start_timer');
- const handlePause = () => invoke('pause_timer');
- const handleSkip = () => invoke('skip_phase');
- const handleReset = () => invoke('reset_timer');
+ const handleStart = async () => {
+ await invoke('start_timer');
+ await syncFromBackend();
+ };
+ const handlePause = async () => {
+ await invoke('pause_timer');
+ await syncFromBackend();
+ };
+ const handleSkip = async () => {
+ await invoke('skip_phase');
+ await syncFromBackend();
+ };
+ const handleReset = async () => {
+ await invoke('reset_timer');
+ await syncFromBackend();
+ };
return (
<div
@@ -200,7 +213,7 @@ function GhostButton({
onClick,
children,
}: {
- onClick: () => void;
+ onClick: () => Promise<void>;
children: React.ReactNode;
}) {
return (
diff --git a/src/hooks/useTimerEvents.ts b/src/hooks/useTimerEvents.ts
index aebc780..a0d5154 100644
--- a/src/hooks/useTimerEvents.ts
+++ b/src/hooks/useTimerEvents.ts
@@ -1,6 +1,5 @@
import { useEffect, useRef } from 'react';
import { listen } from '@tauri-apps/api/event';
-import { invoke } from '@tauri-apps/api/core';
import { useTimerStore, TimerTickPayload } from '../store/timerStore';
import { useTaskStore } from '../store/taskStore';
@@ -9,15 +8,6 @@ interface PhaseChangedPayload {
session_count: number;
}
-interface TimerStatus {
- phase: 'work' | 'short_break' | 'long_break';
- remaining_secs: number;
- total_secs: number;
- running: boolean;
- session_count: number;
- current_task_id: string | null;
-}
-
interface CompletedPayload {
task_id: string | null;
}
@@ -25,7 +15,7 @@ interface CompletedPayload {
export function useTimerEvents(
onCompleted: (taskId: string | null) => void,
) {
- const setTimerTick = useTimerStore((s) => s.setTimerTick);
+ const syncFromBackend = useTimerStore((s) => s.syncFromBackend);
const setRunning = useTimerStore((s) => s.setRunning);
const fetchTasks = useTaskStore((s) => s.fetchTasks);
@@ -41,16 +31,8 @@ export function useTimerEvents(
async function setup() {
// Bootstrap initial state from backend
try {
- const status = await invoke<TimerStatus>('get_timer_status');
+ await syncFromBackend();
if (cancelled) return;
- setTimerTick({
- phase: status.phase,
- remaining_secs: status.remaining_secs,
- total_secs: status.total_secs,
- session_count: status.session_count,
- current_task_id: status.current_task_id,
- });
- setRunning(status.running);
} catch (e) {
console.error('Failed to get timer status:', e);
}
@@ -61,24 +43,24 @@ export function useTimerEvents(
try {
const [unlistenTick, unlistenCompleted, unlistenPhaseChanged] = await Promise.all([
listen<TimerTickPayload>('timer-tick', (event) => {
- setTimerTick(event.payload);
- setRunning(true);
+ useTimerStore.setState({
+ phase: event.payload.phase,
+ remainingSecs: event.payload.remaining_secs,
+ totalSecs: event.payload.total_secs,
+ running: true,
+ sessionCount: event.payload.session_count,
+ currentTaskId: event.payload.current_task_id,
+ });
}),
listen<CompletedPayload>('timer-completed', async (event) => {
setRunning(false);
onCompletedRef.current(event.payload.task_id ?? null);
await fetchTasks();
+ await syncFromBackend();
}),
listen<PhaseChangedPayload>('timer-phase-changed', async (_event) => {
try {
- const status = await invoke<TimerStatus>('get_timer_status');
- setTimerTick({
- phase: status.phase,
- remaining_secs: status.remaining_secs,
- total_secs: status.total_secs,
- session_count: status.session_count,
- current_task_id: status.current_task_id,
- });
+ await syncFromBackend();
} catch (e) {
console.error('Failed to re-sync timer status:', e);
}
@@ -104,5 +86,5 @@ export function useTimerEvents(
cancelled = true;
unlisteners.forEach((fn) => fn());
};
- }, [setTimerTick, setRunning, fetchTasks]); // onCompleted excluded — updated via ref
+ }, [syncFromBackend, setRunning, fetchTasks]); // onCompleted excluded — updated via ref
}
diff --git a/src/store/audioStore.ts b/src/store/audioStore.ts
index 39cbf8b..36d593d 100644
--- a/src/store/audioStore.ts
+++ b/src/store/audioStore.ts
@@ -53,15 +53,15 @@ export const useAudioStore = create<AudioStore>((set, get) => ({
try {
if (sound === 'none') {
await invoke('stop_ambient');
- set({ playing: false, sound: 'none' });
+ await get().fetchStatus();
return;
}
await invoke('play_ambient', { sound });
- set({ available: true, playing: true, sound });
+ await get().fetchStatus();
} catch (error) {
console.error('play_ambient error:', error);
- set({ available: false, playing: false, sound: 'none' });
+ await get().fetchStatus();
}
},
@@ -71,14 +71,10 @@ export const useAudioStore = create<AudioStore>((set, get) => ({
try {
await invoke('set_ambient_volume', { volume: nextVolume });
- set({ available: true });
+ await get().fetchStatus();
} catch (error) {
console.error('set_ambient_volume error:', error);
- set({
- available: false,
- playing: false,
- sound: get().sound,
- });
+ await get().fetchStatus();
}
},
}));
diff --git a/src/store/taskStore.ts b/src/store/taskStore.ts
index 94386ac..2a3a8be 100644
--- a/src/store/taskStore.ts
+++ b/src/store/taskStore.ts
@@ -1,5 +1,6 @@
import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core';
+import { useTimerStore } from './timerStore';
export interface Task {
id: string;
@@ -79,5 +80,11 @@ export const useTaskStore = create<TaskStore>((set) => ({
setCurrentTask: async (id) => {
await invoke('set_current_task', { taskId: id });
+ useTimerStore.getState().setCurrentTaskId(id);
+ try {
+ await useTimerStore.getState().syncFromBackend();
+ } catch (e) {
+ console.error('syncFromBackend after set_current_task error:', e);
+ }
},
}));
diff --git a/src/store/timerStore.ts b/src/store/timerStore.ts
index 6a81e20..79170a4 100644
--- a/src/store/timerStore.ts
+++ b/src/store/timerStore.ts
@@ -1,3 +1,4 @@
+import { invoke } from '@tauri-apps/api/core';
import { create } from 'zustand';
export type TimerPhase = 'work' | 'short_break' | 'long_break';
@@ -10,6 +11,15 @@ export interface TimerTickPayload {
current_task_id: string | null;
}
+export interface TimerStatus {
+ phase: TimerPhase;
+ remaining_secs: number;
+ total_secs: number;
+ running: boolean;
+ session_count: number;
+ current_task_id: string | null;
+}
+
interface TimerState {
phase: TimerPhase;
remainingSecs: number;
@@ -17,8 +27,10 @@ interface TimerState {
running: boolean;
sessionCount: number;
currentTaskId: string | null;
+ syncFromBackend: () => Promise<void>;
setTimerTick: (payload: TimerTickPayload) => void;
setRunning: (running: boolean) => void;
+ setCurrentTaskId: (currentTaskId: string | null) => void;
}
export const useTimerStore = create<TimerState>((set) => ({
@@ -29,6 +41,18 @@ export const useTimerStore = create<TimerState>((set) => ({
sessionCount: 0,
currentTaskId: null,
+ syncFromBackend: async () => {
+ const status = await invoke<TimerStatus>('get_timer_status');
+ set({
+ phase: status.phase,
+ remainingSecs: status.remaining_secs,
+ totalSecs: status.total_secs,
+ running: status.running,
+ sessionCount: status.session_count,
+ currentTaskId: status.current_task_id,
+ });
+ },
+
setTimerTick: (payload) =>
set({
phase: payload.phase,
@@ -39,4 +63,5 @@ export const useTimerStore = create<TimerState>((set) => ({
}),
setRunning: (running) => set({ running }),
+ setCurrentTaskId: (currentTaskId) => set({ currentTaskId }),
}));