function createDraftInbox({ storage, now = () => Date.now() }) { const indexKey = 'stackchain.draft-index.v1'; function readIndex() { try { const value = JSON.parse(storage?.getItem(indexKey) || '{}'); return value && typeof value === 'object' && !Array.isArray(value) ? value : {}; } catch (_error) { return {}; } } function writeIndex(index) { try { storage?.setItem(indexKey, JSON.stringify(index)); } catch (_error) { /* Draft discovery remains available without metadata. */ } } function keys() { try { return Array.from({ length: Number(storage?.length || 0) }, (_, index) => storage.key(index)) .filter(Boolean); } catch (_error) { return []; } } function textPreview(value) { return String(value || '').replace(/\s+/g, ' ').trim().slice(0, 180); } function parseTarget(value) { const match = String(value || '').match(/^(.+)#(\d+)$/); return match ? { repository: match[1], number: Number(match[2]) } : null; } function parse(key, raw) { let match; if (key === 'stackchain.issue-capture.v1') { try { const value = JSON.parse(raw); const preview = textPreview([value?.title, value?.body].filter(Boolean).join(' — ')); if (!preview && !value?.repository) return null; return { kind: 'new-issue', label: 'New issue', repository: String(value?.repository || ''), title: textPreview(value?.title) || 'Untitled new issue', preview, }; } catch (_error) { return null; } } match = key.match(/^stackchain\.(issue|pull)-comment\.v1:(.+#\d+)$/); if (match) { const target = parseTarget(match[2]); const preview = textPreview(raw); if (!target || !preview) return null; const routeKind = match[1] === 'issue' ? 'issue' : 'pull'; return { kind: routeKind + '-comment', label: routeKind === 'issue' ? 'Issue comment' : 'PR comment', ...target, title: target.repository + '#' + target.number, preview, route: { kind: routeKind, ...target }, }; } match = key.match(/^stackchain\.issue-content\.v1:(.+#\d+)$/); if (match) { const target = parseTarget(match[1]); try { const value = JSON.parse(raw); const preview = textPreview([value?.title, value?.body].filter(Boolean).join(' — ')); if (!target || !preview || typeof value?.expectedUpdatedAt !== 'string') return null; return { kind: 'issue-edit', label: 'Issue edit', ...target, title: target.repository + '#' + target.number, preview, route: { kind: 'issue', ...target }, }; } catch (_error) { return null; } } match = key.match(/^stackchain\.update-reply\.v1\.(\d+)$/); if (match) { const preview = textPreview(raw); if (!preview) return null; const notificationId = Number(match[1]); return { kind: 'update-reply', label: 'Update reply', notification_id: notificationId, title: 'Update #' + notificationId, preview, route: { kind: 'update', notification_id: notificationId }, }; } match = key.match(/^stackchain\.review-draft\.v1:(.+#\d+)@([^:]+)$/); if (match) { const target = parseTarget(match[1]); try { const value = JSON.parse(raw); const notes = Object.values(value?.notes || {}); const comments = Array.isArray(value?.comments) ? value.comments.map(item => item?.body) : []; const preview = textPreview([value?.summary, ...notes, ...comments].filter(Boolean).join(' — ')); if (!target || !preview) return null; return { kind: 'review', label: 'PR review', ...target, head_sha: match[2], title: target.repository + '#' + target.number, preview, route: { kind: 'review', ...target }, }; } catch (_error) { return null; } } return null; } function list() { const index = readIndex(); const seen = new Set(); const drafts = []; keys().forEach(key => { if (key === indexKey || key.endsWith(':operation')) return; let raw; try { raw = storage.getItem(key); } catch (_error) { return; } const parsed = parse(key, raw); if (!parsed) return; seen.add(key); const fingerprint = String(raw); const previous = index[key]; if (!previous || previous.fingerprint !== fingerprint) { index[key] = { fingerprint, updated_at: Number(now()) }; } drafts.push({ id: key, ...parsed, updated_at: index[key].updated_at }); }); Object.keys(index).forEach(key => { if (!seen.has(key)) delete index[key]; }); writeIndex(index); return drafts.sort((left, right) => Number(right.updated_at || 0) - Number(left.updated_at || 0) || left.id.localeCompare(right.id) ); } function discard(id) { if (typeof id !== 'string' || !parse(id, (() => { try { return storage?.getItem(id); } catch (_error) { return null; } })())) return false; try { storage?.removeItem(id); storage?.removeItem(id + ':operation'); const index = readIndex(); delete index[id]; writeIndex(index); return true; } catch (_error) { return false; } } return { list, discard }; } if (typeof module !== 'undefined' && module.exports) module.exports = createDraftInbox;