function createTodayWrapUp({ todayWork, laterWork, todaySync }) { let items = []; const selected = new Set(); function open(candidates) { const current = new Set(todayWork.read()); const byIdentity = new Map((candidates || []).map(item => [todayWork.identity(item), item])); items = todayWork.read().flatMap(identity => current.has(identity) && byIdentity.has(identity) ? [byIdentity.get(identity)] : [] ); selected.clear(); return snapshot(); } function choose(identity, schedule) { if (!items.some(item => todayWork.identity(item) === identity)) return false; if (schedule) selected.add(identity); else selected.delete(identity); return true; } function snapshot() { return items.map(item => ({ item, identity:todayWork.identity(item), schedule:selected.has(todayWork.identity(item)), })); } async function finish() { const wake = laterWork.presetUntil('tomorrow'); let scheduled = 0; for (const item of items) { const identity = todayWork.identity(item); if (!selected.has(identity) || !todayWork.contains(item)) continue; const deferred = laterWork.defer(item, wake, {handoff:'today'}); if (deferred !== 'deferred') throw new Error('Tomorrow could not be saved. Your Today plan is unchanged.'); if (!todaySync.enqueue('remove', identity)) { laterWork.restore?.(item); throw new Error('Today sync could not be queued. Your Today plan is unchanged.'); } if (todayWork.contains(item) && !todayWork.remove(item)) { throw new Error('Today could not be updated. Retry wrap-up.'); } scheduled += 1; } await todaySync.flush(); return { scheduled, left:items.length - scheduled, wake_at:wake.toISOString() }; } return { open, choose, snapshot, finish }; } function createTodayWrapUpView({ controller, qs, escapeHtml, onComplete = () => {}, onClose = () => {} }) { let actualMinutes = {}; let workedItems = []; function close() { qs('#today-wrap-up-sheet').hidden = true; document.body.classList.remove('task-overlay-open'); onClose(); } function render() { const rows = controller.snapshot(); qs('#today-wrap-up-items').innerHTML = rows.map(row => { const title = String(row.item.title || row.identity).slice(0, 180); const context = String(row.item.key || row.item.repository || 'Work item').slice(0, 180); return '
' + escapeHtml(title) + '' + '' + escapeHtml(context) + '
'; }).join('') || '

Nothing unfinished remains in Today.

'; qs('#today-wrap-up-items').querySelectorAll('[data-wrap-up-identity]').forEach(input => { input.addEventListener('change', () => controller.choose(input.dataset.wrapUpIdentity, input.checked)); }); } function open(items, recapActualMinutes = {}, recapWorkedItems = []) { actualMinutes = { ...recapActualMinutes }; workedItems = recapWorkedItems.map(item => ({...item})); controller.open(items); qs('#today-wrap-up-status').textContent = ''; render(); qs('#today-wrap-up-sheet').hidden = false; document.body.classList.add('task-overlay-open'); requestAnimationFrame(() => (qs('#today-wrap-up-items input') || qs('#finish-today-wrap-up')).focus()); } async function finish(button) { button.disabled = true; qs('#today-wrap-up-status').textContent = 'Saving tomorrow’s plan…'; try { const tomorrowItems = controller.snapshot().filter(row => row.schedule).map(row => ({ identity:row.identity, title:String(row.item.title || row.identity).slice(0, 180), context:String(row.item.key || row.item.repository || 'Work item').slice(0, 180), })); const result = await controller.finish(); qs('#my-work-action-status').textContent = result.scheduled + ' scheduled for tomorrow · ' + result.left + ' left in Today.'; close(); onComplete(result, actualMinutes, workedItems, tomorrowItems); } catch (error) { qs('#today-wrap-up-status').textContent = error.message || 'Wrap-up could not be saved. Retry when ready.'; } finally { button.disabled = false; } } function bind() { qs('#close-today-wrap-up').addEventListener('click', close); qs('#finish-today-wrap-up').addEventListener('click', event => finish(event.currentTarget)); } return { open, close, finish, render, bind }; } function setupTodayWrapUp(options) { const controller = createTodayWrapUp(options); const view = createTodayWrapUpView({ ...options, controller }); view.bind(); return view; } if (typeof module !== 'undefined' && module.exports) { createTodayWrapUp.createView = createTodayWrapUpView; createTodayWrapUp.setup = setupTodayWrapUp; module.exports = createTodayWrapUp; }