(function (root, factory) { if (typeof module === 'object' && module.exports) module.exports = factory; else root.createMobileStartDay = factory; })(typeof self !== 'undefined' ? self : this, function createMobileStartDay(options) { const checkpointKey = 'stackchain.mobile-start-day.v1'; const storage = options.storage || (typeof localStorage !== 'undefined' ? localStorage : null); const reviewOrder = [ ['delivery', 'Delivery recovery'], ['agenda', 'Agenda'], ['attention', 'Attention'], ['update', 'Updates'], ['filed', 'Filed'], ['following', 'Following'], ]; const anonymousItems = new WeakMap(); let anonymousItemSequence = 0; function count(value) { return Math.max(0, Number(value) || 0); } function itemIdentity(item) { if (item === null || item === undefined) return ''; if (typeof item !== 'object') return String(item); const explicit = item.outbox_id || item.id || item.key || item.url; if (explicit !== null && explicit !== undefined && String(explicit)) { return String(item.kind || 'item') + ':' + String(explicit); } if (!anonymousItems.has(item)) anonymousItems.set(item, 'anonymous:' + (++anonymousItemSequence)); return anonymousItems.get(item); } function reviewPhases(counts) { if (!options.getPhaseItems) { return reviewOrder .map(([name, label]) => ({name, label, count: count(counts[name])})) .filter(phase => phase.count > 0); } const items = options.getPhaseItems() || {}; const seen = new Set(); return reviewOrder.flatMap(([name, label]) => { let phaseCount = 0; (items[name] || []).forEach(item => { const identity = itemIdentity(item); if (!identity || seen.has(identity)) return; seen.add(identity); phaseCount += 1; }); return phaseCount ? [{name, label, count:phaseCount}] : []; }); } function localDay() { const now = new Date(); const pad = value => String(value).padStart(2, '0'); return now.getFullYear() + '-' + pad(now.getMonth() + 1) + '-' + pad(now.getDate()); } function identity() { try { return { login: String(options.getLogin ? options.getLogin() : '').trim(), day: String(options.getDay ? options.getDay() : localDay()), }; } catch (_) { return {login:'', day:''}; } } function checkpoint() { if (!storage) return null; const current = identity(); if (!current.login || !current.day) return null; try { const saved = JSON.parse(storage.getItem(checkpointKey) || 'null'); return saved?.login === current.login && saved?.day === current.day ? saved : null; } catch (_) { return null; } } function saveCheckpoint(phase = null) { if (!storage) return false; const current = identity(); if (!current.login || !current.day) return false; try { storage.setItem(checkpointKey, JSON.stringify({...current, phase})); return true; } catch (_) { return false; } } function clearCheckpoint() { if (!storage || !checkpoint()) return false; try { storage.removeItem(checkpointKey); return true; } catch (_) { return false; } } function briefing() { const counts = options.getCounts ? options.getCounts() : {}; let phases = reviewPhases(counts); const followingUnavailable = counts.followingUnavailable === true; if (followingUnavailable) { phases = phases.filter(phase => phase.name !== 'following'); phases.push({name:'following', label:'Following unavailable', count:count(counts.following)}); } const total = phases.reduce((sum, phase) => sum + phase.count, 0); const today = count(counts.today); const delivery = phases.find(phase => phase.name === 'delivery')?.count || 0; const other = total - delivery; const next = phases.length ? phases[0].name : (today ? 'today' : 'find'); const nextLabel = next === 'delivery' ? 'Review Delivery' : (next === 'following' && followingUnavailable ? 'Retry Following' : (phases.length ? 'Review ' + phases[0].label : (today ? 'Continue Today' : 'Find Work'))); const followingRetry = next === 'following' && followingUnavailable; return { total, next, label: nextLabel, summary: followingRetry ? 'Following needs retry before Today · ' + today + ' planned' : delivery ? delivery + (delivery === 1 ? ' delivery needs' : ' deliveries need') + ' action before Today' + (other ? ' · ' + other + ' other ' + (other === 1 ? 'item' : 'items') : '') + ' · ' + today + ' planned' : total ? total + ' items before Today · ' + today + ' planned' : (today ? 'Review clear · ' + today + ' planned' : 'Review clear · Today is empty'), phases, }; } function startNext() { const next = briefing().next; if (reviewOrder.some(([name]) => name === next)) saveCheckpoint(next); else clearCheckpoint(); options.openQueue(next); return next; } function state() { const current = briefing(); return {active:Boolean(checkpoint()), next:current.next, label:current.label}; } function completePhase(phase) { const saved = checkpoint(); if (!saved || (saved.phase && saved.phase !== phase)) return false; const current = render(); saveCheckpoint(); if (options.onHandoff) options.onHandoff(current); return true; } function reconcile({authoritative = false, authoritativePhases = []} = {}) { const saved = checkpoint(); if (!saved?.phase || (!authoritative && !authoritativePhases.includes(saved.phase))) return false; const counts = options.getCounts ? options.getCounts() : {}; if (count(counts[saved.phase]) > 0) return false; return completePhase(saved.phase); } function finish() { const cleared = clearCheckpoint(); render(); return cleared; } function render() { const current = briefing(); if (!options.elements) return current; options.elements.summary.textContent = current.summary; options.elements.phases.textContent = current.phases.length ? current.phases.map(phase => phase.label + ' ' + phase.count).join(' · ') : 'All urgent queues reviewed'; options.elements.action.textContent = checkpoint() ? 'Resume preparation · ' + current.label : current.label; if (options.elements.finish) options.elements.finish.hidden = !checkpoint(); return current; } function start() { render(); if (options.elements) options.elements.action.addEventListener('click', startNext); } return {briefing, completePhase, finish, reconcile, render, start, startNext, state}; });