function createDraftInbox({ storage, getCurrentLogin = () => '', 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 parseOutbox(raw) { try { const record = JSON.parse(raw); if (![1, 2, 3].includes(record?.version) || !Array.isArray(record.items)) return []; const currentLogin = String(getCurrentLogin() || '').trim(); return record.items.filter(item => item && typeof item.id === 'string' && typeof item.repository === 'string' && typeof item.title === 'string' ).map(item => { const ownerLogin = String(item.ownerLogin || '').trim(); const quarantined = !ownerLogin || !currentLogin || ownerLogin !== currentLogin; return { id: 'stackchain.issue-outbox.v1:' + item.id, outbox_id: item.id, kind: 'issue-outbox', status: item.status === 'completion' ? 'completion' : (item.status === 'attention' ? 'attention' : 'queued'), label: item.deliveryState === 'uncertain' ? 'Verify delivery' : (item.status === 'completion' ? 'Created · ready to start' : (item.status === 'attention' ? 'Needs attention' : 'Queued issue')), continuation: item.status === 'completion' && item.completionIntent === 'create-and-start', delivery_state: item.deliveryState, repository: item.repository, title: textPreview(item.title) || 'Untitled queued issue', preview: textPreview([item.title, item.error || item.body].filter(Boolean).join(' — ')), copy_text: [item.title, item.body].filter(Boolean).join('\n\n'), quarantined, ownership: quarantined ? 'Queued by ' + (ownerLogin || 'an unknown account') + (currentLogin ? ' — current account is ' + currentLogin : ' — account confirmation unavailable') : '', updated_at: Number(item.queuedAt || 0), }; }); } catch (_error) { return []; } } function parseAuthoredOutbox(raw) { try { const record = JSON.parse(raw); if (![1, 2].includes(record?.version) || !Array.isArray(record.items)) return []; const currentLogin = String(getCurrentLogin() || '').trim(); return record.items.filter(item => item && typeof item.id === 'string' && typeof item.body === 'string') .map(item => { const isUpdate = item.kind === 'update-reply'; const isReview = item.kind === 'pull-review'; const routeKind = isReview ? 'review' : (item.kind === 'pull-comment' ? 'pull' : 'issue'); const target = isUpdate ? 'Update #' + item.notificationId : item.repository + '#' + item.number; const inlineFeedback = isReview && Array.isArray(item.comments) ? item.comments.map(comment => String(comment.path || 'File') + ' · ' + (comment.new_position ? 'new line ' + comment.new_position : 'old line ' + comment.old_position) + ': ' + String(comment.body || '') ).filter(Boolean) : []; const copyText = [item.body, inlineFeedback.length ? 'Inline feedback\n' + inlineFeedback.join('\n') : ''] .filter(Boolean).join('\n\n'); const ownerLogin = String(item.ownerLogin || '').trim(); const quarantined = !ownerLogin || !currentLogin || ownerLogin !== currentLogin; return { id: 'stackchain.authored-outbox.v1:' + item.id, outbox_id: item.id, outbox_kind: item.kind, kind: 'authored-outbox', status: item.status === 'attention' ? 'attention' : 'queued', label: item.deliveryState === 'uncertain' ? 'Verify delivery' : (item.status === 'attention' ? 'Needs attention' : (isReview ? 'Queued review' : 'Queued message')), delivery_state: item.deliveryState, repository: isUpdate ? '' : item.repository, title: target, preview: textPreview([item.error, item.body].filter(Boolean).join(' — ')), copy_text: copyText, ...(isReview ? { head_sha: item.expectedHeadSha } : {}), quarantined, ownership: quarantined ? 'Queued by ' + (ownerLogin || 'an unknown account') + (currentLogin ? ' — current account is ' + currentLogin : ' — account confirmation unavailable') : '', updated_at: Number(item.queuedAt || 0), route: isUpdate ? { kind:'update', notification_id:item.notificationId } : { kind:routeKind, repository:item.repository, number:item.number }, }; }); } catch (_error) { return []; } } 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; } if (key === 'stackchain.issue-outbox.v1') { parseOutbox(raw).forEach(item => drafts.push(item)); seen.add(key); return; } if (key === 'stackchain.authored-outbox.v1') { parseAuthoredOutbox(raw).forEach(item => drafts.push(item)); seen.add(key); 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;