Preserve elapsed time when switching between schedule blocks

Previously, clicking a different block always fired 'start' which reset
elapsed to zero — returning to a block lost all accumulated time.

Store changes:
- Add blockElapsedCache (blockId → elapsed seconds) that persists across
  block switches within a session
- On 'pause' WS event: write the block's total elapsed into the cache
- On 'start' WS event: restore elapsed from cache (0 if never worked)
- On applySnapshot: seed cache with server-computed elapsed for the
  current block (so reloading preserves state correctly)
- Clear cache when the session ends

Dashboard selectBlock changes:
- Auto-pause the currently running block before switching to another
- Clicking the active block while paused now sends 'resume' instead of
  doing nothing
- Clicking the already-running active block is a no-op

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 21:22:25 -08:00
parent 4a14f8b3f1
commit b00d4ee99e
2 changed files with 28 additions and 7 deletions

View File

@@ -10,6 +10,7 @@ export const useScheduleStore = defineStore('schedule', () => {
const isPaused = ref(false) const isPaused = ref(false)
const blockStartedAt = ref(null) // Date.now() ms when current counting period started const blockStartedAt = ref(null) // Date.now() ms when current counting period started
const blockElapsedOffset = ref(0) // seconds already elapsed before blockStartedAt const blockElapsedOffset = ref(0) // seconds already elapsed before blockStartedAt
const blockElapsedCache = ref({}) // blockId → total elapsed seconds (survives block switches)
const dayStartTime = ref(null) // "HH:MM:SS" string or null const dayStartTime = ref(null) // "HH:MM:SS" string or null
const dayEndTime = ref(null) // "HH:MM:SS" string or null const dayEndTime = ref(null) // "HH:MM:SS" string or null
@@ -39,9 +40,10 @@ export const useScheduleStore = defineStore('schedule', () => {
if (snapshot.child) child.value = snapshot.child if (snapshot.child) child.value = snapshot.child
dayStartTime.value = snapshot.day_start_time || null dayStartTime.value = snapshot.day_start_time || null
dayEndTime.value = snapshot.day_end_time || null dayEndTime.value = snapshot.day_end_time || null
// Restore elapsed time from server-computed value // Restore elapsed time from server-computed value and seed the per-block cache
const serverElapsed = snapshot.block_elapsed_seconds || 0 const serverElapsed = snapshot.block_elapsed_seconds || 0
if (snapshot.session?.current_block_id && serverElapsed > 0) { if (snapshot.session?.current_block_id && serverElapsed > 0) {
blockElapsedCache.value[snapshot.session.current_block_id] = serverElapsed
blockElapsedOffset.value = serverElapsed blockElapsedOffset.value = serverElapsed
// Only start the live counter if the block is actually running (not paused) // Only start the live counter if the block is actually running (not paused)
blockStartedAt.value = isPaused.value ? null : Date.now() blockStartedAt.value = isPaused.value ? null : Date.now()
@@ -68,21 +70,25 @@ export const useScheduleStore = defineStore('schedule', () => {
isPaused.value = false isPaused.value = false
blockStartedAt.value = null blockStartedAt.value = null
blockElapsedOffset.value = 0 blockElapsedOffset.value = 0
blockElapsedCache.value = {}
dayStartTime.value = null dayStartTime.value = null
dayEndTime.value = null dayEndTime.value = null
return return
} }
// Pause — accumulate elapsed, stop counting // Pause — accumulate elapsed, save to cache, stop counting
if (event.event === 'pause') { if (event.event === 'pause') {
if (blockStartedAt.value) { if (blockStartedAt.value) {
blockElapsedOffset.value += Math.floor((Date.now() - blockStartedAt.value) / 1000) blockElapsedOffset.value += Math.floor((Date.now() - blockStartedAt.value) / 1000)
} }
if (event.block_id) {
blockElapsedCache.value[event.block_id] = blockElapsedOffset.value
}
blockStartedAt.value = null blockStartedAt.value = null
isPaused.value = true isPaused.value = true
} }
// Start (new block) — reset elapsed, begin counting // Start — restore cached elapsed if returning to a previously worked block
if (event.event === 'start') { if (event.event === 'start') {
blockElapsedOffset.value = 0 blockElapsedOffset.value = blockElapsedCache.value[event.block_id] || 0
blockStartedAt.value = Date.now() blockStartedAt.value = Date.now()
isPaused.value = false isPaused.value = false
} }

View File

@@ -217,10 +217,25 @@ async function sendAction(type) {
await scheduleStore.sendTimerAction(scheduleStore.session.id, type) await scheduleStore.sendTimerAction(scheduleStore.session.id, type)
} }
function selectBlock(block) { async function selectBlock(block) {
if (!scheduleStore.session) return if (!scheduleStore.session) return
scheduleStore.session.current_block_id = block.id
scheduleStore.isPaused = false const currentId = scheduleStore.session.current_block_id
// Clicking the current block while paused → resume it
if (block.id === currentId && scheduleStore.isPaused) {
scheduleStore.sendTimerAction(scheduleStore.session.id, 'resume')
return
}
// Clicking the current block while running → do nothing
if (block.id === currentId) return
// Switching to a different block — pause the current one first if it's running
if (currentId && !scheduleStore.isPaused) {
await scheduleStore.sendTimerAction(scheduleStore.session.id, 'pause', currentId)
}
scheduleStore.sendTimerAction(scheduleStore.session.id, 'start', block.id) scheduleStore.sendTimerAction(scheduleStore.session.id, 'start', block.id)
} }