fix: recover orphaned authored operations (#319)
This commit is contained in:
1 parent
b5858046ee
commit
dc18004e02
15 files changed
+321
-18
No files matched your search
@@ -77,6 +77,7 @@ function createAuthoredOutbox({ storage, fetchJson, coordinator, backgroundSync,
|
||||
status: 'queued',
|
||||
};
|
||||
delete updated.error;
|
||||
delete updated.deliveryState;
|
||||
return updated;
|
||||
}));
|
||||
return updated;
|
||||
@@ -134,6 +135,7 @@ function createAuthoredOutbox({ storage, fetchJson, coordinator, backgroundSync,
|
||||
...candidate,
|
||||
status: 'attention',
|
||||
error: String(error.message || 'Message needs attention').slice(0, 240),
|
||||
...(error.code === 'delivery_uncertain' ? { deliveryState: 'uncertain' } : {}),
|
||||
} : candidate));
|
||||
}
|
||||
return { error, transient: !permanent };
|
||||
@@ -170,7 +172,17 @@ function createAuthoredOutbox({ storage, fetchJson, coordinator, backgroundSync,
|
||||
if (!currentLogin || item.ownerLogin !== currentLogin) {
|
||||
return { confirmed: [], remaining: read(), blocked: 1 };
|
||||
}
|
||||
const queued = item.status === 'attention' ? update(id, item) : item;
|
||||
let queued = item;
|
||||
if (item.status === 'attention') {
|
||||
queued = {
|
||||
...item,
|
||||
operationId: String(makeId()).slice(0, 128),
|
||||
status: 'queued',
|
||||
};
|
||||
delete queued.error;
|
||||
delete queued.deliveryState;
|
||||
write(read().map(candidate => candidate.id === id ? queued : candidate));
|
||||
}
|
||||
const outcome = await sendItem(queued, currentLogin);
|
||||
return { confirmed: outcome.result ? [outcome.result] : [], remaining: read(), blocked: 0 };
|
||||
}
|
||||
@@ -189,6 +201,7 @@ function createAuthoredOutbox({ storage, fetchJson, coordinator, backgroundSync,
|
||||
...item,
|
||||
status: 'attention',
|
||||
error: String(background.error || 'Message needs attention').slice(0, 240),
|
||||
...(background.deliveryState ? { deliveryState: background.deliveryState } : {}),
|
||||
}];
|
||||
return [item];
|
||||
});
|
||||
|
||||
@@ -182,7 +182,10 @@ function createIssueSyncStore({ transaction, indexedDB = globalThis.indexedDB, n
|
||||
claimBatch,
|
||||
complete: id => update(id, item => ({ ...item, status: 'sent', claimUntil: 0 })),
|
||||
release: id => update(id, item => ({ ...item, status: 'queued', claimUntil: 0 })),
|
||||
fail: (id, error) => update(id, item => ({ ...item, status: 'attention', claimUntil: 0, error })),
|
||||
fail: (id, error, deliveryState) => update(id, item => ({
|
||||
...item, status: 'attention', claimUntil: 0, error,
|
||||
...(deliveryState ? { deliveryState } : {}),
|
||||
})),
|
||||
snapshot: () => transact(async records =>
|
||||
(await records.getAll()).filter(item => item.recordType !== 'receipt-preference')),
|
||||
countBlocked: ownerLogin => transact(async records =>
|
||||
@@ -301,7 +304,11 @@ function createBackgroundIssueSync({
|
||||
throw error;
|
||||
}
|
||||
if (status >= 400 && status < 500) {
|
||||
await store.fail(item.id, String(error?.message || 'Issue needs attention').slice(0, 240));
|
||||
await store.fail(
|
||||
item.id,
|
||||
String(error?.message || 'Issue needs attention').slice(0, 240),
|
||||
error?.code === 'delivery_uncertain' ? 'uncertain' : undefined,
|
||||
);
|
||||
return { attention: true, error, receipt: receiptFor(item, 'attention') };
|
||||
}
|
||||
await store.release(item.id);
|
||||
|
||||
@@ -144,8 +144,9 @@
|
||||
const response = await fetch(url, options);
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
const error = new Error(payload.error || payload.detail || 'Review request failed.');
|
||||
const error = new Error(payload.error || payload.detail?.message || payload.detail || 'Review request failed.');
|
||||
error.status = response.status;
|
||||
error.code = payload.detail?.code;
|
||||
throw error;
|
||||
}
|
||||
return payload;
|
||||
@@ -709,16 +710,17 @@
|
||||
list.innerHTML = lastDrafts.length ? lastDrafts.map((item, index) => {
|
||||
const isOutbox = item.kind === 'issue-outbox' || item.kind === 'authored-outbox';
|
||||
const isUnfiled = item.kind === 'unfiled-issue';
|
||||
const sendLabel = item.delivery_state === 'uncertain' ? 'Verified not posted — retry' : 'Send now';
|
||||
const outboxActions = item.quarantined ?
|
||||
'<button class="draft-copy" data-draft-index="' + index + '" type="button">Copy content</button>' +
|
||||
'<button class="draft-discard" data-draft-index="' + index + '" type="button">Discard</button>' :
|
||||
item.kind === 'issue-outbox' ?
|
||||
'<button class="draft-edit" data-draft-index="' + index + '" type="button">Edit</button>' +
|
||||
'<button class="draft-send" data-draft-index="' + index + '" type="button">Send now</button>' +
|
||||
'<button class="draft-send" data-draft-index="' + index + '" type="button">' + sendLabel + '</button>' +
|
||||
'<button class="draft-discard" data-draft-index="' + index + '" type="button">Discard</button>' :
|
||||
item.kind === 'authored-outbox' ?
|
||||
'<button class="draft-resume" data-draft-index="' + index + '" type="button">Open message</button>' +
|
||||
'<button class="draft-send" data-draft-index="' + index + '" type="button">Send now</button>' +
|
||||
'<button class="draft-send" data-draft-index="' + index + '" type="button">' + sendLabel + '</button>' +
|
||||
'<button class="draft-discard" data-draft-index="' + index + '" type="button">Discard</button>' :
|
||||
'<button class="draft-resume" data-draft-index="' + index + '" type="button">' +
|
||||
(isUnfiled ? 'Choose repository' : 'Resume draft') + '</button>' +
|
||||
|
||||
+6
-2
@@ -113,7 +113,9 @@ function createDraftInbox({ storage, getCurrentLogin = () => '', now = () => Dat
|
||||
outbox_id: item.id,
|
||||
kind: 'issue-outbox',
|
||||
status: item.status === 'attention' ? 'attention' : 'queued',
|
||||
label: item.status === 'attention' ? 'Needs attention' : 'Queued issue',
|
||||
label: item.deliveryState === 'uncertain' ? 'Verify delivery' :
|
||||
(item.status === 'attention' ? 'Needs attention' : 'Queued issue'),
|
||||
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(' — ')),
|
||||
@@ -144,7 +146,9 @@ function createDraftInbox({ storage, getCurrentLogin = () => '', now = () => Dat
|
||||
outbox_id: item.id,
|
||||
kind: 'authored-outbox',
|
||||
status: item.status === 'attention' ? 'attention' : 'queued',
|
||||
label: item.status === 'attention' ? 'Needs attention' : 'Queued message',
|
||||
label: item.deliveryState === 'uncertain' ? 'Verify delivery' :
|
||||
(item.status === 'attention' ? 'Needs attention' : 'Queued message'),
|
||||
delivery_state: item.deliveryState,
|
||||
repository: isUpdate ? '' : item.repository,
|
||||
title: target,
|
||||
preview: textPreview([item.error, item.body].filter(Boolean).join(' — ')),
|
||||
|
||||
@@ -89,6 +89,7 @@ function createIssueOutbox({ storage, fetchJson, coordinator, backgroundSync, ge
|
||||
if (nextMilestoneId === undefined) delete updated.milestoneId;
|
||||
if (nextDueDate === undefined) delete updated.dueDate;
|
||||
delete updated.error;
|
||||
delete updated.deliveryState;
|
||||
return updated;
|
||||
}), mirror);
|
||||
return updated;
|
||||
@@ -152,6 +153,7 @@ function createIssueOutbox({ storage, fetchJson, coordinator, backgroundSync, ge
|
||||
if (status >= 400 && status < 500) {
|
||||
write(read().map(candidate => candidate.id === item.id ? {
|
||||
...candidate, status: 'attention', error: String(error.message || 'Issue needs attention').slice(0, 240),
|
||||
...(error.code === 'delivery_uncertain' ? { deliveryState: 'uncertain' } : {}),
|
||||
} : candidate));
|
||||
}
|
||||
return { error, transient: !(status >= 400 && status < 500) };
|
||||
@@ -188,8 +190,18 @@ function createIssueOutbox({ storage, fetchJson, coordinator, backgroundSync, ge
|
||||
if (!currentLogin || item.ownerLogin !== currentLogin) {
|
||||
return { confirmed: [], remaining: read(), blocked: 1 };
|
||||
}
|
||||
update(id, item);
|
||||
const result = await sendItem({ ...item, status: 'queued' }, currentLogin);
|
||||
let queued = item;
|
||||
if (item.status === 'attention') {
|
||||
queued = {
|
||||
...item,
|
||||
operationId: String(operationId()).slice(0, 128),
|
||||
status: 'queued',
|
||||
};
|
||||
delete queued.error;
|
||||
delete queued.deliveryState;
|
||||
write(read().map(candidate => candidate.id === id ? queued : candidate));
|
||||
}
|
||||
const result = await sendItem(queued, currentLogin);
|
||||
return { confirmed: result.issue ? [result.issue] : [], remaining: read(), blocked: 0 };
|
||||
}
|
||||
|
||||
@@ -205,6 +217,7 @@ function createIssueOutbox({ storage, fetchJson, coordinator, backgroundSync, ge
|
||||
if (background?.status === 'sent') return [];
|
||||
if (background?.status === 'attention') return [{
|
||||
...item, status: 'attention', error: String(background.error || 'Issue needs attention').slice(0, 240),
|
||||
...(background.deliveryState ? { deliveryState: background.deliveryState } : {}),
|
||||
}];
|
||||
return [item];
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const BASE = new URL('./', self.location.href).pathname;
|
||||
importScripts(BASE + 'static/background-issue-sync.js');
|
||||
const CACHE = 'stackchain-dashboard-shell-v33';
|
||||
const CACHE = 'stackchain-dashboard-shell-v34';
|
||||
const OUTAGE_STATUSES = new Set([500, 502, 503, 504]);
|
||||
const SHELL = [
|
||||
BASE,
|
||||
@@ -86,8 +86,9 @@ async function fetchJson(url, options = {}) {
|
||||
const response = await fetch(new URL(url, self.location.origin), requestOptions);
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
const error = new Error(payload.error || payload.detail || 'Background issue delivery failed.');
|
||||
const error = new Error(payload.error || payload.detail?.message || payload.detail || 'Background issue delivery failed.');
|
||||
error.status = response.status;
|
||||
error.code = payload.detail?.code;
|
||||
throw error;
|
||||
}
|
||||
return payload;
|
||||
|
||||
Reference in new issue
Block a user