From 887c0bc6f968f80ac90220f24bb578438e05708a Mon Sep 17 00:00:00 2001 From: Solstice Date: Tue, 9 Jun 2026 01:10:46 -0700 Subject: fix: resolve final release blockers --- src/components/TimerView.tsx | 23 ++++++++++++++++++----- src/hooks/useTimerEvents.ts | 44 +++++++++++++------------------------------- src/store/audioStore.ts | 14 +++++--------- src/store/taskStore.ts | 7 +++++++ src/store/timerStore.ts | 25 +++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 45 deletions(-) (limited to 'src') 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 (
void; + onClick: () => Promise; 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('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('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('timer-completed', async (event) => { setRunning(false); onCompletedRef.current(event.payload.task_id ?? null); await fetchTasks(); + await syncFromBackend(); }), listen('timer-phase-changed', async (_event) => { try { - const status = await invoke('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((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((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((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; setTimerTick: (payload: TimerTickPayload) => void; setRunning: (running: boolean) => void; + setCurrentTaskId: (currentTaskId: string | null) => void; } export const useTimerStore = create((set) => ({ @@ -29,6 +41,18 @@ export const useTimerStore = create((set) => ({ sessionCount: 0, currentTaskId: null, + syncFromBackend: async () => { + const status = await invoke('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((set) => ({ }), setRunning: (running) => set({ running }), + setCurrentTaskId: (currentTaskId) => set({ currentTaskId }), })); -- cgit v1.3-2-g0d8e