function createIssueSheet({ fetchJson, storage, createOperationId = () => globalThis.crypto?.randomUUID?.() || String(Date.now()) + '-' + Math.random() }) { let commentRequest = null; let closeRequest = null; let releaseRequest = null; let labelRequest = null; const issuePath = item => 'api/v1/repos/' + item.repository.split('/').map(encodeURIComponent).join('/') + '/issues/' + encodeURIComponent(item.number); const draftKey = item => 'stackchain.issue-comment.v1:' + item.repository + '#' + item.number; const operationKey = item => draftKey(item) + ':operation'; const labelDraftKey = item => 'stackchain.issue-labels.v1:' + item.repository + '#' + item.number; return { load(item) { return fetchJson(issuePath(item) + '/detail', { headers: { Accept: 'application/json' }, }); }, loadLabels(item) { return fetchJson(issuePath(item) + '/labels', { headers: { Accept: 'application/json' }, }); }, loadDraft(item) { try { return storage?.getItem(draftKey(item)) || ''; } catch (_error) { return ''; } }, saveDraft(item, body) { try { if ((storage?.getItem(draftKey(item)) || '') !== body) storage?.removeItem(operationKey(item)); storage?.setItem(draftKey(item), body); } catch (_error) { /* The textarea remains the fallback. */ } }, loadLabelDraft(item) { try { const value = JSON.parse(storage?.getItem(labelDraftKey(item)) || '[]'); return Array.isArray(value) ? value.filter(Number.isInteger) : []; } catch (_error) { return []; } }, updateLabels(item, labelIds) { if (labelRequest) return labelRequest; try { storage?.setItem(labelDraftKey(item), JSON.stringify(labelIds)); } catch (_error) { /* The checked controls remain the fallback. */ } labelRequest = fetchJson(issuePath(item) + '/labels', { method: 'PATCH', headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ label_ids: labelIds }), }).then(result => { if (result?.number !== item.number || !Array.isArray(result.labels)) { throw new Error('Issue labels were not confirmed.'); } try { storage?.removeItem(labelDraftKey(item)); } catch (_error) { /* The upstream label set is authoritative. */ } return result; }).finally(() => { labelRequest = null; }); return labelRequest; }, close(item) { if (closeRequest) return closeRequest; closeRequest = fetchJson(issuePath(item) + '/close', { method: 'PATCH', headers: { Accept: 'application/json' }, }).then(result => { if (result?.state !== 'closed') throw new Error('Issue closure was not confirmed.'); return result; }).finally(() => { closeRequest = null; }); return closeRequest; }, release(item, currentLogin) { if (releaseRequest) return releaseRequest; releaseRequest = fetchJson(issuePath(item) + '/release', { method: 'PATCH', headers: { Accept: 'application/json' }, }).then(result => { if ( result?.number !== item.number || !Array.isArray(result.assignees) || result.assignees.includes(currentLogin) ) { throw new Error('Issue release was not confirmed.'); } return result; }).finally(() => { releaseRequest = null; }); return releaseRequest; }, comment(item, body) { if (commentRequest) return commentRequest; this.saveDraft(item, body); let operationId; try { operationId = storage?.getItem(operationKey(item)) || String(createOperationId()).slice(0, 128); storage?.setItem(operationKey(item), operationId); } catch (_error) { operationId = String(createOperationId()).slice(0, 128); } commentRequest = fetchJson(issuePath(item) + '/comments', { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', 'Idempotency-Key': operationId }, body: JSON.stringify({ body }), }).then(result => { try { storage?.removeItem(draftKey(item)); } catch (_error) { /* The upstream comment is authoritative. */ } try { storage?.removeItem(operationKey(item)); } catch (_error) { /* A confirmed result no longer needs replay identity. */ } return result; }).finally(() => { commentRequest = null; }); return commentRequest; }, }; } if (typeof module !== 'undefined' && module.exports) module.exports = createIssueSheet;