(function (root, factory) { if (typeof module === 'object' && module.exports) module.exports = factory; else root.createUpdateTriageSession = factory; })(typeof self !== 'undefined' ? self : this, function createUpdateTriageSession(options) { const key = options.key || 'stackchain.update-triage.v1'; const identity = item => Number.isInteger(item?.notification_id) ? String(item.notification_id) : ''; const login = () => String(options.getLogin() || '').trim(); let state = null; let running = false; let lastOutcome = null; function read() { const owner = login(); if (!owner) return null; try { const value = JSON.parse(options.storage.getItem(key) || 'null'); if (![1, 2].includes(value?.version) || value.login !== owner || !Array.isArray(value.identities) || !value.identities.length || typeof value.current !== 'string' || !Array.isArray(value.completed)) return null; const identities = value.identities.filter(value => typeof value === 'string' && value); const completed = value.completed.filter(value => identities.includes(value)); const kept = Array.isArray(value.kept) ? value.kept.filter(value => identities.includes(value)) : []; if (!identities.length || !identities.includes(value.current)) return null; return { version:2, login:owner, identities, current:value.current, completed, kept }; } catch (_) { return null; } } function persist() { if (!state) return; options.storage.setItem(key, JSON.stringify(state)); } function available() { const byIdentity = new Map((options.getItems() || []).map(item => [identity(item), item])); return state.identities.filter(id => !state.completed.includes(id) && byIdentity.has(id)) .map(id => byIdentity.get(id)); } function nextAvailable() { if (!running || !state) return null; const candidates = available().filter(item => identity(item) !== state.current); return candidates.find(item => state.identities.indexOf(identity(item)) > state.identities.indexOf(state.current)) || candidates[0] || null; } function finish() { const unread = new Set((options.getItems() || []).map(identity).filter(Boolean)); const keptIdentities = state ? state.kept.filter(id => unread.has(id)) : []; const reviewed = state ? state.completed.length : 0; const outcome = { reviewed, resolved:reviewed - keptIdentities.length, kept:keptIdentities.length, keptIdentities }; lastOutcome = outcome; running = false; state = null; options.storage.removeItem(key); options.onFinish(outcome); return false; } function openCurrent(preferredIndex = null) { const items = available(); if (!items.length) return finish(); let index = items.findIndex(item => identity(item) === state.current); if (index < 0) index = Math.min(preferredIndex ?? 0, items.length - 1); state.current = identity(items[index]); persist(); const originalIndex = state.identities.indexOf(state.current); const progress = { index:originalIndex + 1, total:state.identities.length, }; if (items[index]?.update_reason) progress.reason = String(items[index].update_reason); options.onProgress(progress); options.onOpen(items[index]); return true; } function advance(openNext = true) { if (!running || !state) return false; const originalIndex = state.identities.indexOf(state.current); if (!state.completed.includes(state.current)) state.completed.push(state.current); const candidates = available(); const next = candidates.find(item => state.identities.indexOf(identity(item)) > originalIndex) || candidates[0]; if (!next) return finish(); state.current = identity(next); if (openNext) return openCurrent(); persist(); const progress = { index:state.identities.indexOf(state.current) + 1, total:state.identities.length, }; if (next?.update_reason) progress.reason = String(next.update_reason); options.onProgress(progress); return true; } return { active: () => running, resumable: () => Boolean(read()), start() { const identities = (options.getItems() || []).map(identity).filter(Boolean); if (!identities.length) return finish(); state = { version:2, login:login(), identities, current:identities[0], completed:[], kept:[] }; if (!state.login) return false; running = true; return openCurrent(); }, resume() { state = read(); if (!state) return false; running = true; return openCurrent(state.identities.indexOf(state.current)); }, reconcile() { if (!running || !state) return false; return openCurrent(state.identities.indexOf(state.current)); }, completeAndNext: advance, acceptCompleted: () => advance(false), keepUnreadAndNext() { if (!running || !state) return false; if (!state.kept.includes(state.current)) state.kept.push(state.current); return advance(); }, reviewKept(identities = lastOutcome?.keptIdentities || []) { const unread = new Set((options.getItems() || []).map(identity).filter(Boolean)); const kept = (identities || []).filter(id => unread.has(id)); if (!kept.length) return false; state = { version:2, login:login(), identities:kept, current:kept[0], completed:[], kept:[] }; if (!state.login) return false; running = true; return openCurrent(); }, next: nextAvailable, items: () => state ? available().slice() : [], end: finish, }; });