function createTodayTimer({ storage, getLogin, now = () => Date.now() }) { const key = () => { const login = String(getLogin?.() || '').trim().toLowerCase(); return login ? 'stackchain.today-timer.v1.' + encodeURIComponent(login) : ''; }; const empty = () => ({ version:1, active_identity:'', entries:{} }); const read = () => { const ownerKey = key(); if (!ownerKey || !storage) return empty(); try { const value = JSON.parse(storage.getItem(ownerKey) || 'null'); if (value?.version !== 1 || typeof value.active_identity !== 'string' || !value.entries || typeof value.entries !== 'object') return empty(); return value; } catch (_error) { return empty(); } }; const write = state => { const ownerKey = key(); if (!ownerKey || !storage) return false; try { storage.setItem(ownerKey, JSON.stringify(state)); return true; } catch (_error) { return false; } }; const settle = (state, at = now()) => { const entry = state.entries[state.active_identity]; if (!entry?.running) return state; entry.elapsed_ms = Math.max(0, Number(entry.elapsed_ms) || 0) + Math.max(0, at - Number(entry.started_at ?? at)); entry.started_at = null; entry.running = false; return state; }; const snapshot = (identity = '') => { const state = read(); const selected = identity || state.active_identity; const entry = state.entries[selected]; if (!selected || !entry) return { identity:selected, elapsed_ms:0, running:false }; const elapsed = Math.max(0, Number(entry.elapsed_ms) || 0) + (entry.running ? Math.max(0, now() - Number(entry.started_at ?? now())) : 0); return { identity:selected, elapsed_ms:elapsed, running:Boolean(entry.running) }; }; return { activate(identity) { if (!key() || typeof identity !== 'string' || !identity) return false; const state = read(); if (state.active_identity === identity && state.entries[identity]?.running) return true; settle(state); state.active_identity = identity; const entry = state.entries[identity] || { elapsed_ms:0, started_at:null, running:false }; entry.started_at = now(); entry.running = true; state.entries[identity] = entry; return write(state); }, pause() { const state = read(); settle(state); return write(state); }, resume() { const state = read(); const entry = state.entries[state.active_identity]; if (!entry) return false; if (!entry.running) { entry.started_at = now(); entry.running = true; } return write(state); }, stop() { const state = read(); settle(state); return write(state); }, recapEntries() { const state = read(); settle(state); write(state); return Object.entries(state.entries).map(([identity, entry]) => ({ identity, elapsed_ms:Math.max(0, Number(entry.elapsed_ms) || 0), })).filter(entry => entry.elapsed_ms > 0); }, clearRecap() { return write(empty()); }, snapshot, }; } function createTodayTimerView({ timer, isActive, queryAll, formatEstimate }) { let progress = null; let runway = null; const elapsed = milliseconds => { const seconds = Math.max(0, Math.floor(Number(milliseconds || 0) / 1000)); const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const remainder = String(seconds % 60).padStart(2, '0'); return hours ? hours + ':' + String(minutes).padStart(2, '0') + ':' + remainder : minutes + ':' + remainder; }; const render = () => { if (!progress) return; const snapshot = timer.snapshot(); const timing = isActive() && snapshot.identity ? ' · ' + elapsed(snapshot.elapsed_ms) + (runway?.current_minutes ? ' / ' + formatEstimate(runway.current_minutes) : '') : ''; queryAll('[data-work-session-progress]').forEach(element => { element.textContent = 'Item ' + progress.index + ' of ' + progress.total + timing + (runway?.remaining_minutes ? ' · ' + formatEstimate(runway.remaining_minutes) + ' remaining' : ''); }); queryAll('[data-work-session-timer-toggle]').forEach(button => { button.hidden = !isActive(); button.textContent = snapshot.running ? 'Pause timer' : 'Resume timer'; button.setAttribute('aria-pressed', String(!snapshot.running)); }); }; return { open(identity, active) { active ? timer.activate(identity) : timer.stop(); render(); }, finish() { timer.stop(); progress = null; runway = null; }, update(nextProgress, nextRunway) { progress = nextProgress; runway = nextRunway; render(); }, reset() { progress = null; runway = null; }, toggle() { const state = timer.snapshot(); state.running ? timer.pause() : timer.resume(); render(); }, render, }; } if (typeof module !== 'undefined' && module.exports) { createTodayTimer.createView = createTodayTimerView; module.exports = createTodayTimer; }