Sync generated provider output

This commit is contained in:
github-actions[bot]
2026-07-27 01:09:00 +00:00
parent cdcce9116e
commit 7380ecb153
15 changed files with 585 additions and 0 deletions
@@ -175,6 +175,14 @@
let pickedAnchorViewportTop = null;
let pendingVariantAnchorRetryObserver = null;
let pendingAcceptedSession = null;
// Survives cleanupAcceptedSession on purpose: the id of an accept whose
// POST was acknowledged (intent durable, epoch fenced) but whose actual
// source promotion hasn't reported back yet. Accept is optimistic, so the
// teardown nulls pendingAcceptedSession long before live-accept.mjs runs;
// this marker is what lets the SSE 'error' branch still recognize a late
// accept failure and say the variant was not saved (issue #384). Released
// when the real accept result arrives or a new session starts.
let awaitingAcceptResult = null;
let variantObserver = null;
let variantSelectionInFlight = false;
let variantSelectionPromise = null;
@@ -6474,12 +6482,20 @@
break;
case 'complete':
case 'accept':
// The real accept result arrived: the awaited failure window closed.
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (maybeCompleteAcceptedSession(msg)) break;
break;
case 'agent_done':
// The deterministic accept has already committed the reviewed DOM
// and fenced generation. Carbonize may continue in the background;
// it must not hold the foreground picker hostage.
// Only a carbonize agent_done is provably accept-side: accept
// unlocks at the first variant, so a late generation agent_done
// for the same session id can still arrive after Accept and must
// not close the awaited failure window early (the SSE broadcast
// carries no sourceEventType to tell the two apart).
if (msg.data?.carbonize === true && awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
break;
case 'discarded':
@@ -6491,11 +6507,27 @@
case 'error':
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
pendingAcceptedSession = null;
awaitingAcceptResult = null;
setLiveState('CYCLING');
updateBarContent('cycling');
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
break;
}
// The optimistic teardown already released the session, so the
// CYCLING recovery above can no longer match; without this branch
// the failure fell through to the generic toast and the user had
// no hint their variant was never written (issue #384).
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) {
awaitingAcceptResult = null;
console.error('[impeccable] Accept failed after teardown:', msg.message);
// Hedged on purpose: a carbonize-phase failure raises this same
// error after the source WAS promoted, so "was not saved" would
// overclaim. Normalize the server message's terminal punctuation
// so the two sentences don't run together.
const acceptFailDetail = String(msg.message || 'unknown error').trim().replace(/[.!?]?$/, '.');
showToast('Accept failed: ' + acceptFailDetail + ' The variant may not have been saved. If the change is missing, pick the element and generate again.', 8000);
break;
}
if (maybeCompleteSteer(msg)) break;
console.error('[impeccable] Error:', msg.message);
showToast('Error: ' + msg.message, 5000);
@@ -6958,6 +6990,9 @@
stripManualEditRuntimeState(selectedElement);
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7037,6 +7072,9 @@
stopVoice({ suppressSubmit: true });
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7868,6 +7906,7 @@ void main() {
markSessionHandled();
setLiveState('CONFIRMED');
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
awaitingAcceptResult = { id: acceptedSessionId };
scheduleAcceptCleanup(pending);
})
.catch(() => {
@@ -175,6 +175,14 @@
let pickedAnchorViewportTop = null;
let pendingVariantAnchorRetryObserver = null;
let pendingAcceptedSession = null;
// Survives cleanupAcceptedSession on purpose: the id of an accept whose
// POST was acknowledged (intent durable, epoch fenced) but whose actual
// source promotion hasn't reported back yet. Accept is optimistic, so the
// teardown nulls pendingAcceptedSession long before live-accept.mjs runs;
// this marker is what lets the SSE 'error' branch still recognize a late
// accept failure and say the variant was not saved (issue #384). Released
// when the real accept result arrives or a new session starts.
let awaitingAcceptResult = null;
let variantObserver = null;
let variantSelectionInFlight = false;
let variantSelectionPromise = null;
@@ -6474,12 +6482,20 @@
break;
case 'complete':
case 'accept':
// The real accept result arrived: the awaited failure window closed.
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (maybeCompleteAcceptedSession(msg)) break;
break;
case 'agent_done':
// The deterministic accept has already committed the reviewed DOM
// and fenced generation. Carbonize may continue in the background;
// it must not hold the foreground picker hostage.
// Only a carbonize agent_done is provably accept-side: accept
// unlocks at the first variant, so a late generation agent_done
// for the same session id can still arrive after Accept and must
// not close the awaited failure window early (the SSE broadcast
// carries no sourceEventType to tell the two apart).
if (msg.data?.carbonize === true && awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
break;
case 'discarded':
@@ -6491,11 +6507,27 @@
case 'error':
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
pendingAcceptedSession = null;
awaitingAcceptResult = null;
setLiveState('CYCLING');
updateBarContent('cycling');
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
break;
}
// The optimistic teardown already released the session, so the
// CYCLING recovery above can no longer match; without this branch
// the failure fell through to the generic toast and the user had
// no hint their variant was never written (issue #384).
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) {
awaitingAcceptResult = null;
console.error('[impeccable] Accept failed after teardown:', msg.message);
// Hedged on purpose: a carbonize-phase failure raises this same
// error after the source WAS promoted, so "was not saved" would
// overclaim. Normalize the server message's terminal punctuation
// so the two sentences don't run together.
const acceptFailDetail = String(msg.message || 'unknown error').trim().replace(/[.!?]?$/, '.');
showToast('Accept failed: ' + acceptFailDetail + ' The variant may not have been saved. If the change is missing, pick the element and generate again.', 8000);
break;
}
if (maybeCompleteSteer(msg)) break;
console.error('[impeccable] Error:', msg.message);
showToast('Error: ' + msg.message, 5000);
@@ -6958,6 +6990,9 @@
stripManualEditRuntimeState(selectedElement);
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7037,6 +7072,9 @@
stopVoice({ suppressSubmit: true });
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7868,6 +7906,7 @@ void main() {
markSessionHandled();
setLiveState('CONFIRMED');
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
awaitingAcceptResult = { id: acceptedSessionId };
scheduleAcceptCleanup(pending);
})
.catch(() => {
@@ -175,6 +175,14 @@
let pickedAnchorViewportTop = null;
let pendingVariantAnchorRetryObserver = null;
let pendingAcceptedSession = null;
// Survives cleanupAcceptedSession on purpose: the id of an accept whose
// POST was acknowledged (intent durable, epoch fenced) but whose actual
// source promotion hasn't reported back yet. Accept is optimistic, so the
// teardown nulls pendingAcceptedSession long before live-accept.mjs runs;
// this marker is what lets the SSE 'error' branch still recognize a late
// accept failure and say the variant was not saved (issue #384). Released
// when the real accept result arrives or a new session starts.
let awaitingAcceptResult = null;
let variantObserver = null;
let variantSelectionInFlight = false;
let variantSelectionPromise = null;
@@ -6474,12 +6482,20 @@
break;
case 'complete':
case 'accept':
// The real accept result arrived: the awaited failure window closed.
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (maybeCompleteAcceptedSession(msg)) break;
break;
case 'agent_done':
// The deterministic accept has already committed the reviewed DOM
// and fenced generation. Carbonize may continue in the background;
// it must not hold the foreground picker hostage.
// Only a carbonize agent_done is provably accept-side: accept
// unlocks at the first variant, so a late generation agent_done
// for the same session id can still arrive after Accept and must
// not close the awaited failure window early (the SSE broadcast
// carries no sourceEventType to tell the two apart).
if (msg.data?.carbonize === true && awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
break;
case 'discarded':
@@ -6491,11 +6507,27 @@
case 'error':
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
pendingAcceptedSession = null;
awaitingAcceptResult = null;
setLiveState('CYCLING');
updateBarContent('cycling');
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
break;
}
// The optimistic teardown already released the session, so the
// CYCLING recovery above can no longer match; without this branch
// the failure fell through to the generic toast and the user had
// no hint their variant was never written (issue #384).
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) {
awaitingAcceptResult = null;
console.error('[impeccable] Accept failed after teardown:', msg.message);
// Hedged on purpose: a carbonize-phase failure raises this same
// error after the source WAS promoted, so "was not saved" would
// overclaim. Normalize the server message's terminal punctuation
// so the two sentences don't run together.
const acceptFailDetail = String(msg.message || 'unknown error').trim().replace(/[.!?]?$/, '.');
showToast('Accept failed: ' + acceptFailDetail + ' The variant may not have been saved. If the change is missing, pick the element and generate again.', 8000);
break;
}
if (maybeCompleteSteer(msg)) break;
console.error('[impeccable] Error:', msg.message);
showToast('Error: ' + msg.message, 5000);
@@ -6958,6 +6990,9 @@
stripManualEditRuntimeState(selectedElement);
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7037,6 +7072,9 @@
stopVoice({ suppressSubmit: true });
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7868,6 +7906,7 @@ void main() {
markSessionHandled();
setLiveState('CONFIRMED');
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
awaitingAcceptResult = { id: acceptedSessionId };
scheduleAcceptCleanup(pending);
})
.catch(() => {
@@ -175,6 +175,14 @@
let pickedAnchorViewportTop = null;
let pendingVariantAnchorRetryObserver = null;
let pendingAcceptedSession = null;
// Survives cleanupAcceptedSession on purpose: the id of an accept whose
// POST was acknowledged (intent durable, epoch fenced) but whose actual
// source promotion hasn't reported back yet. Accept is optimistic, so the
// teardown nulls pendingAcceptedSession long before live-accept.mjs runs;
// this marker is what lets the SSE 'error' branch still recognize a late
// accept failure and say the variant was not saved (issue #384). Released
// when the real accept result arrives or a new session starts.
let awaitingAcceptResult = null;
let variantObserver = null;
let variantSelectionInFlight = false;
let variantSelectionPromise = null;
@@ -6474,12 +6482,20 @@
break;
case 'complete':
case 'accept':
// The real accept result arrived: the awaited failure window closed.
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (maybeCompleteAcceptedSession(msg)) break;
break;
case 'agent_done':
// The deterministic accept has already committed the reviewed DOM
// and fenced generation. Carbonize may continue in the background;
// it must not hold the foreground picker hostage.
// Only a carbonize agent_done is provably accept-side: accept
// unlocks at the first variant, so a late generation agent_done
// for the same session id can still arrive after Accept and must
// not close the awaited failure window early (the SSE broadcast
// carries no sourceEventType to tell the two apart).
if (msg.data?.carbonize === true && awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
break;
case 'discarded':
@@ -6491,11 +6507,27 @@
case 'error':
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
pendingAcceptedSession = null;
awaitingAcceptResult = null;
setLiveState('CYCLING');
updateBarContent('cycling');
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
break;
}
// The optimistic teardown already released the session, so the
// CYCLING recovery above can no longer match; without this branch
// the failure fell through to the generic toast and the user had
// no hint their variant was never written (issue #384).
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) {
awaitingAcceptResult = null;
console.error('[impeccable] Accept failed after teardown:', msg.message);
// Hedged on purpose: a carbonize-phase failure raises this same
// error after the source WAS promoted, so "was not saved" would
// overclaim. Normalize the server message's terminal punctuation
// so the two sentences don't run together.
const acceptFailDetail = String(msg.message || 'unknown error').trim().replace(/[.!?]?$/, '.');
showToast('Accept failed: ' + acceptFailDetail + ' The variant may not have been saved. If the change is missing, pick the element and generate again.', 8000);
break;
}
if (maybeCompleteSteer(msg)) break;
console.error('[impeccable] Error:', msg.message);
showToast('Error: ' + msg.message, 5000);
@@ -6958,6 +6990,9 @@
stripManualEditRuntimeState(selectedElement);
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7037,6 +7072,9 @@
stopVoice({ suppressSubmit: true });
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7868,6 +7906,7 @@ void main() {
markSessionHandled();
setLiveState('CONFIRMED');
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
awaitingAcceptResult = { id: acceptedSessionId };
scheduleAcceptCleanup(pending);
})
.catch(() => {
@@ -175,6 +175,14 @@
let pickedAnchorViewportTop = null;
let pendingVariantAnchorRetryObserver = null;
let pendingAcceptedSession = null;
// Survives cleanupAcceptedSession on purpose: the id of an accept whose
// POST was acknowledged (intent durable, epoch fenced) but whose actual
// source promotion hasn't reported back yet. Accept is optimistic, so the
// teardown nulls pendingAcceptedSession long before live-accept.mjs runs;
// this marker is what lets the SSE 'error' branch still recognize a late
// accept failure and say the variant was not saved (issue #384). Released
// when the real accept result arrives or a new session starts.
let awaitingAcceptResult = null;
let variantObserver = null;
let variantSelectionInFlight = false;
let variantSelectionPromise = null;
@@ -6474,12 +6482,20 @@
break;
case 'complete':
case 'accept':
// The real accept result arrived: the awaited failure window closed.
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (maybeCompleteAcceptedSession(msg)) break;
break;
case 'agent_done':
// The deterministic accept has already committed the reviewed DOM
// and fenced generation. Carbonize may continue in the background;
// it must not hold the foreground picker hostage.
// Only a carbonize agent_done is provably accept-side: accept
// unlocks at the first variant, so a late generation agent_done
// for the same session id can still arrive after Accept and must
// not close the awaited failure window early (the SSE broadcast
// carries no sourceEventType to tell the two apart).
if (msg.data?.carbonize === true && awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
break;
case 'discarded':
@@ -6491,11 +6507,27 @@
case 'error':
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
pendingAcceptedSession = null;
awaitingAcceptResult = null;
setLiveState('CYCLING');
updateBarContent('cycling');
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
break;
}
// The optimistic teardown already released the session, so the
// CYCLING recovery above can no longer match; without this branch
// the failure fell through to the generic toast and the user had
// no hint their variant was never written (issue #384).
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) {
awaitingAcceptResult = null;
console.error('[impeccable] Accept failed after teardown:', msg.message);
// Hedged on purpose: a carbonize-phase failure raises this same
// error after the source WAS promoted, so "was not saved" would
// overclaim. Normalize the server message's terminal punctuation
// so the two sentences don't run together.
const acceptFailDetail = String(msg.message || 'unknown error').trim().replace(/[.!?]?$/, '.');
showToast('Accept failed: ' + acceptFailDetail + ' The variant may not have been saved. If the change is missing, pick the element and generate again.', 8000);
break;
}
if (maybeCompleteSteer(msg)) break;
console.error('[impeccable] Error:', msg.message);
showToast('Error: ' + msg.message, 5000);
@@ -6958,6 +6990,9 @@
stripManualEditRuntimeState(selectedElement);
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7037,6 +7072,9 @@
stopVoice({ suppressSubmit: true });
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7868,6 +7906,7 @@ void main() {
markSessionHandled();
setLiveState('CONFIRMED');
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
awaitingAcceptResult = { id: acceptedSessionId };
scheduleAcceptCleanup(pending);
})
.catch(() => {
@@ -175,6 +175,14 @@
let pickedAnchorViewportTop = null;
let pendingVariantAnchorRetryObserver = null;
let pendingAcceptedSession = null;
// Survives cleanupAcceptedSession on purpose: the id of an accept whose
// POST was acknowledged (intent durable, epoch fenced) but whose actual
// source promotion hasn't reported back yet. Accept is optimistic, so the
// teardown nulls pendingAcceptedSession long before live-accept.mjs runs;
// this marker is what lets the SSE 'error' branch still recognize a late
// accept failure and say the variant was not saved (issue #384). Released
// when the real accept result arrives or a new session starts.
let awaitingAcceptResult = null;
let variantObserver = null;
let variantSelectionInFlight = false;
let variantSelectionPromise = null;
@@ -6474,12 +6482,20 @@
break;
case 'complete':
case 'accept':
// The real accept result arrived: the awaited failure window closed.
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (maybeCompleteAcceptedSession(msg)) break;
break;
case 'agent_done':
// The deterministic accept has already committed the reviewed DOM
// and fenced generation. Carbonize may continue in the background;
// it must not hold the foreground picker hostage.
// Only a carbonize agent_done is provably accept-side: accept
// unlocks at the first variant, so a late generation agent_done
// for the same session id can still arrive after Accept and must
// not close the awaited failure window early (the SSE broadcast
// carries no sourceEventType to tell the two apart).
if (msg.data?.carbonize === true && awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
break;
case 'discarded':
@@ -6491,11 +6507,27 @@
case 'error':
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
pendingAcceptedSession = null;
awaitingAcceptResult = null;
setLiveState('CYCLING');
updateBarContent('cycling');
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
break;
}
// The optimistic teardown already released the session, so the
// CYCLING recovery above can no longer match; without this branch
// the failure fell through to the generic toast and the user had
// no hint their variant was never written (issue #384).
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) {
awaitingAcceptResult = null;
console.error('[impeccable] Accept failed after teardown:', msg.message);
// Hedged on purpose: a carbonize-phase failure raises this same
// error after the source WAS promoted, so "was not saved" would
// overclaim. Normalize the server message's terminal punctuation
// so the two sentences don't run together.
const acceptFailDetail = String(msg.message || 'unknown error').trim().replace(/[.!?]?$/, '.');
showToast('Accept failed: ' + acceptFailDetail + ' The variant may not have been saved. If the change is missing, pick the element and generate again.', 8000);
break;
}
if (maybeCompleteSteer(msg)) break;
console.error('[impeccable] Error:', msg.message);
showToast('Error: ' + msg.message, 5000);
@@ -6958,6 +6990,9 @@
stripManualEditRuntimeState(selectedElement);
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7037,6 +7072,9 @@
stopVoice({ suppressSubmit: true });
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7868,6 +7906,7 @@ void main() {
markSessionHandled();
setLiveState('CONFIRMED');
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
awaitingAcceptResult = { id: acceptedSessionId };
scheduleAcceptCleanup(pending);
})
.catch(() => {
@@ -175,6 +175,14 @@
let pickedAnchorViewportTop = null;
let pendingVariantAnchorRetryObserver = null;
let pendingAcceptedSession = null;
// Survives cleanupAcceptedSession on purpose: the id of an accept whose
// POST was acknowledged (intent durable, epoch fenced) but whose actual
// source promotion hasn't reported back yet. Accept is optimistic, so the
// teardown nulls pendingAcceptedSession long before live-accept.mjs runs;
// this marker is what lets the SSE 'error' branch still recognize a late
// accept failure and say the variant was not saved (issue #384). Released
// when the real accept result arrives or a new session starts.
let awaitingAcceptResult = null;
let variantObserver = null;
let variantSelectionInFlight = false;
let variantSelectionPromise = null;
@@ -6474,12 +6482,20 @@
break;
case 'complete':
case 'accept':
// The real accept result arrived: the awaited failure window closed.
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (maybeCompleteAcceptedSession(msg)) break;
break;
case 'agent_done':
// The deterministic accept has already committed the reviewed DOM
// and fenced generation. Carbonize may continue in the background;
// it must not hold the foreground picker hostage.
// Only a carbonize agent_done is provably accept-side: accept
// unlocks at the first variant, so a late generation agent_done
// for the same session id can still arrive after Accept and must
// not close the awaited failure window early (the SSE broadcast
// carries no sourceEventType to tell the two apart).
if (msg.data?.carbonize === true && awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
break;
case 'discarded':
@@ -6491,11 +6507,27 @@
case 'error':
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
pendingAcceptedSession = null;
awaitingAcceptResult = null;
setLiveState('CYCLING');
updateBarContent('cycling');
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
break;
}
// The optimistic teardown already released the session, so the
// CYCLING recovery above can no longer match; without this branch
// the failure fell through to the generic toast and the user had
// no hint their variant was never written (issue #384).
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) {
awaitingAcceptResult = null;
console.error('[impeccable] Accept failed after teardown:', msg.message);
// Hedged on purpose: a carbonize-phase failure raises this same
// error after the source WAS promoted, so "was not saved" would
// overclaim. Normalize the server message's terminal punctuation
// so the two sentences don't run together.
const acceptFailDetail = String(msg.message || 'unknown error').trim().replace(/[.!?]?$/, '.');
showToast('Accept failed: ' + acceptFailDetail + ' The variant may not have been saved. If the change is missing, pick the element and generate again.', 8000);
break;
}
if (maybeCompleteSteer(msg)) break;
console.error('[impeccable] Error:', msg.message);
showToast('Error: ' + msg.message, 5000);
@@ -6958,6 +6990,9 @@
stripManualEditRuntimeState(selectedElement);
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7037,6 +7072,9 @@
stopVoice({ suppressSubmit: true });
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7868,6 +7906,7 @@ void main() {
markSessionHandled();
setLiveState('CONFIRMED');
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
awaitingAcceptResult = { id: acceptedSessionId };
scheduleAcceptCleanup(pending);
})
.catch(() => {
@@ -175,6 +175,14 @@
let pickedAnchorViewportTop = null;
let pendingVariantAnchorRetryObserver = null;
let pendingAcceptedSession = null;
// Survives cleanupAcceptedSession on purpose: the id of an accept whose
// POST was acknowledged (intent durable, epoch fenced) but whose actual
// source promotion hasn't reported back yet. Accept is optimistic, so the
// teardown nulls pendingAcceptedSession long before live-accept.mjs runs;
// this marker is what lets the SSE 'error' branch still recognize a late
// accept failure and say the variant was not saved (issue #384). Released
// when the real accept result arrives or a new session starts.
let awaitingAcceptResult = null;
let variantObserver = null;
let variantSelectionInFlight = false;
let variantSelectionPromise = null;
@@ -6474,12 +6482,20 @@
break;
case 'complete':
case 'accept':
// The real accept result arrived: the awaited failure window closed.
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (maybeCompleteAcceptedSession(msg)) break;
break;
case 'agent_done':
// The deterministic accept has already committed the reviewed DOM
// and fenced generation. Carbonize may continue in the background;
// it must not hold the foreground picker hostage.
// Only a carbonize agent_done is provably accept-side: accept
// unlocks at the first variant, so a late generation agent_done
// for the same session id can still arrive after Accept and must
// not close the awaited failure window early (the SSE broadcast
// carries no sourceEventType to tell the two apart).
if (msg.data?.carbonize === true && awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
break;
case 'discarded':
@@ -6491,11 +6507,27 @@
case 'error':
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
pendingAcceptedSession = null;
awaitingAcceptResult = null;
setLiveState('CYCLING');
updateBarContent('cycling');
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
break;
}
// The optimistic teardown already released the session, so the
// CYCLING recovery above can no longer match; without this branch
// the failure fell through to the generic toast and the user had
// no hint their variant was never written (issue #384).
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) {
awaitingAcceptResult = null;
console.error('[impeccable] Accept failed after teardown:', msg.message);
// Hedged on purpose: a carbonize-phase failure raises this same
// error after the source WAS promoted, so "was not saved" would
// overclaim. Normalize the server message's terminal punctuation
// so the two sentences don't run together.
const acceptFailDetail = String(msg.message || 'unknown error').trim().replace(/[.!?]?$/, '.');
showToast('Accept failed: ' + acceptFailDetail + ' The variant may not have been saved. If the change is missing, pick the element and generate again.', 8000);
break;
}
if (maybeCompleteSteer(msg)) break;
console.error('[impeccable] Error:', msg.message);
showToast('Error: ' + msg.message, 5000);
@@ -6958,6 +6990,9 @@
stripManualEditRuntimeState(selectedElement);
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7037,6 +7072,9 @@
stopVoice({ suppressSubmit: true });
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7868,6 +7906,7 @@ void main() {
markSessionHandled();
setLiveState('CONFIRMED');
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
awaitingAcceptResult = { id: acceptedSessionId };
scheduleAcceptCleanup(pending);
})
.catch(() => {
@@ -175,6 +175,14 @@
let pickedAnchorViewportTop = null;
let pendingVariantAnchorRetryObserver = null;
let pendingAcceptedSession = null;
// Survives cleanupAcceptedSession on purpose: the id of an accept whose
// POST was acknowledged (intent durable, epoch fenced) but whose actual
// source promotion hasn't reported back yet. Accept is optimistic, so the
// teardown nulls pendingAcceptedSession long before live-accept.mjs runs;
// this marker is what lets the SSE 'error' branch still recognize a late
// accept failure and say the variant was not saved (issue #384). Released
// when the real accept result arrives or a new session starts.
let awaitingAcceptResult = null;
let variantObserver = null;
let variantSelectionInFlight = false;
let variantSelectionPromise = null;
@@ -6474,12 +6482,20 @@
break;
case 'complete':
case 'accept':
// The real accept result arrived: the awaited failure window closed.
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (maybeCompleteAcceptedSession(msg)) break;
break;
case 'agent_done':
// The deterministic accept has already committed the reviewed DOM
// and fenced generation. Carbonize may continue in the background;
// it must not hold the foreground picker hostage.
// Only a carbonize agent_done is provably accept-side: accept
// unlocks at the first variant, so a late generation agent_done
// for the same session id can still arrive after Accept and must
// not close the awaited failure window early (the SSE broadcast
// carries no sourceEventType to tell the two apart).
if (msg.data?.carbonize === true && awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
break;
case 'discarded':
@@ -6491,11 +6507,27 @@
case 'error':
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
pendingAcceptedSession = null;
awaitingAcceptResult = null;
setLiveState('CYCLING');
updateBarContent('cycling');
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
break;
}
// The optimistic teardown already released the session, so the
// CYCLING recovery above can no longer match; without this branch
// the failure fell through to the generic toast and the user had
// no hint their variant was never written (issue #384).
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) {
awaitingAcceptResult = null;
console.error('[impeccable] Accept failed after teardown:', msg.message);
// Hedged on purpose: a carbonize-phase failure raises this same
// error after the source WAS promoted, so "was not saved" would
// overclaim. Normalize the server message's terminal punctuation
// so the two sentences don't run together.
const acceptFailDetail = String(msg.message || 'unknown error').trim().replace(/[.!?]?$/, '.');
showToast('Accept failed: ' + acceptFailDetail + ' The variant may not have been saved. If the change is missing, pick the element and generate again.', 8000);
break;
}
if (maybeCompleteSteer(msg)) break;
console.error('[impeccable] Error:', msg.message);
showToast('Error: ' + msg.message, 5000);
@@ -6958,6 +6990,9 @@
stripManualEditRuntimeState(selectedElement);
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7037,6 +7072,9 @@
stopVoice({ suppressSubmit: true });
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7868,6 +7906,7 @@ void main() {
markSessionHandled();
setLiveState('CONFIRMED');
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
awaitingAcceptResult = { id: acceptedSessionId };
scheduleAcceptCleanup(pending);
})
.catch(() => {
@@ -175,6 +175,14 @@
let pickedAnchorViewportTop = null;
let pendingVariantAnchorRetryObserver = null;
let pendingAcceptedSession = null;
// Survives cleanupAcceptedSession on purpose: the id of an accept whose
// POST was acknowledged (intent durable, epoch fenced) but whose actual
// source promotion hasn't reported back yet. Accept is optimistic, so the
// teardown nulls pendingAcceptedSession long before live-accept.mjs runs;
// this marker is what lets the SSE 'error' branch still recognize a late
// accept failure and say the variant was not saved (issue #384). Released
// when the real accept result arrives or a new session starts.
let awaitingAcceptResult = null;
let variantObserver = null;
let variantSelectionInFlight = false;
let variantSelectionPromise = null;
@@ -6474,12 +6482,20 @@
break;
case 'complete':
case 'accept':
// The real accept result arrived: the awaited failure window closed.
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (maybeCompleteAcceptedSession(msg)) break;
break;
case 'agent_done':
// The deterministic accept has already committed the reviewed DOM
// and fenced generation. Carbonize may continue in the background;
// it must not hold the foreground picker hostage.
// Only a carbonize agent_done is provably accept-side: accept
// unlocks at the first variant, so a late generation agent_done
// for the same session id can still arrive after Accept and must
// not close the awaited failure window early (the SSE broadcast
// carries no sourceEventType to tell the two apart).
if (msg.data?.carbonize === true && awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
break;
case 'discarded':
@@ -6491,11 +6507,27 @@
case 'error':
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
pendingAcceptedSession = null;
awaitingAcceptResult = null;
setLiveState('CYCLING');
updateBarContent('cycling');
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
break;
}
// The optimistic teardown already released the session, so the
// CYCLING recovery above can no longer match; without this branch
// the failure fell through to the generic toast and the user had
// no hint their variant was never written (issue #384).
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) {
awaitingAcceptResult = null;
console.error('[impeccable] Accept failed after teardown:', msg.message);
// Hedged on purpose: a carbonize-phase failure raises this same
// error after the source WAS promoted, so "was not saved" would
// overclaim. Normalize the server message's terminal punctuation
// so the two sentences don't run together.
const acceptFailDetail = String(msg.message || 'unknown error').trim().replace(/[.!?]?$/, '.');
showToast('Accept failed: ' + acceptFailDetail + ' The variant may not have been saved. If the change is missing, pick the element and generate again.', 8000);
break;
}
if (maybeCompleteSteer(msg)) break;
console.error('[impeccable] Error:', msg.message);
showToast('Error: ' + msg.message, 5000);
@@ -6958,6 +6990,9 @@
stripManualEditRuntimeState(selectedElement);
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7037,6 +7072,9 @@
stopVoice({ suppressSubmit: true });
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7868,6 +7906,7 @@ void main() {
markSessionHandled();
setLiveState('CONFIRMED');
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
awaitingAcceptResult = { id: acceptedSessionId };
scheduleAcceptCleanup(pending);
})
.catch(() => {
@@ -175,6 +175,14 @@
let pickedAnchorViewportTop = null;
let pendingVariantAnchorRetryObserver = null;
let pendingAcceptedSession = null;
// Survives cleanupAcceptedSession on purpose: the id of an accept whose
// POST was acknowledged (intent durable, epoch fenced) but whose actual
// source promotion hasn't reported back yet. Accept is optimistic, so the
// teardown nulls pendingAcceptedSession long before live-accept.mjs runs;
// this marker is what lets the SSE 'error' branch still recognize a late
// accept failure and say the variant was not saved (issue #384). Released
// when the real accept result arrives or a new session starts.
let awaitingAcceptResult = null;
let variantObserver = null;
let variantSelectionInFlight = false;
let variantSelectionPromise = null;
@@ -6474,12 +6482,20 @@
break;
case 'complete':
case 'accept':
// The real accept result arrived: the awaited failure window closed.
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (maybeCompleteAcceptedSession(msg)) break;
break;
case 'agent_done':
// The deterministic accept has already committed the reviewed DOM
// and fenced generation. Carbonize may continue in the background;
// it must not hold the foreground picker hostage.
// Only a carbonize agent_done is provably accept-side: accept
// unlocks at the first variant, so a late generation agent_done
// for the same session id can still arrive after Accept and must
// not close the awaited failure window early (the SSE broadcast
// carries no sourceEventType to tell the two apart).
if (msg.data?.carbonize === true && awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
break;
case 'discarded':
@@ -6491,11 +6507,27 @@
case 'error':
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
pendingAcceptedSession = null;
awaitingAcceptResult = null;
setLiveState('CYCLING');
updateBarContent('cycling');
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
break;
}
// The optimistic teardown already released the session, so the
// CYCLING recovery above can no longer match; without this branch
// the failure fell through to the generic toast and the user had
// no hint their variant was never written (issue #384).
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) {
awaitingAcceptResult = null;
console.error('[impeccable] Accept failed after teardown:', msg.message);
// Hedged on purpose: a carbonize-phase failure raises this same
// error after the source WAS promoted, so "was not saved" would
// overclaim. Normalize the server message's terminal punctuation
// so the two sentences don't run together.
const acceptFailDetail = String(msg.message || 'unknown error').trim().replace(/[.!?]?$/, '.');
showToast('Accept failed: ' + acceptFailDetail + ' The variant may not have been saved. If the change is missing, pick the element and generate again.', 8000);
break;
}
if (maybeCompleteSteer(msg)) break;
console.error('[impeccable] Error:', msg.message);
showToast('Error: ' + msg.message, 5000);
@@ -6958,6 +6990,9 @@
stripManualEditRuntimeState(selectedElement);
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7037,6 +7072,9 @@
stopVoice({ suppressSubmit: true });
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7868,6 +7906,7 @@ void main() {
markSessionHandled();
setLiveState('CONFIRMED');
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
awaitingAcceptResult = { id: acceptedSessionId };
scheduleAcceptCleanup(pending);
})
.catch(() => {
@@ -175,6 +175,14 @@
let pickedAnchorViewportTop = null;
let pendingVariantAnchorRetryObserver = null;
let pendingAcceptedSession = null;
// Survives cleanupAcceptedSession on purpose: the id of an accept whose
// POST was acknowledged (intent durable, epoch fenced) but whose actual
// source promotion hasn't reported back yet. Accept is optimistic, so the
// teardown nulls pendingAcceptedSession long before live-accept.mjs runs;
// this marker is what lets the SSE 'error' branch still recognize a late
// accept failure and say the variant was not saved (issue #384). Released
// when the real accept result arrives or a new session starts.
let awaitingAcceptResult = null;
let variantObserver = null;
let variantSelectionInFlight = false;
let variantSelectionPromise = null;
@@ -6474,12 +6482,20 @@
break;
case 'complete':
case 'accept':
// The real accept result arrived: the awaited failure window closed.
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (maybeCompleteAcceptedSession(msg)) break;
break;
case 'agent_done':
// The deterministic accept has already committed the reviewed DOM
// and fenced generation. Carbonize may continue in the background;
// it must not hold the foreground picker hostage.
// Only a carbonize agent_done is provably accept-side: accept
// unlocks at the first variant, so a late generation agent_done
// for the same session id can still arrive after Accept and must
// not close the awaited failure window early (the SSE broadcast
// carries no sourceEventType to tell the two apart).
if (msg.data?.carbonize === true && awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
break;
case 'discarded':
@@ -6491,11 +6507,27 @@
case 'error':
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
pendingAcceptedSession = null;
awaitingAcceptResult = null;
setLiveState('CYCLING');
updateBarContent('cycling');
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
break;
}
// The optimistic teardown already released the session, so the
// CYCLING recovery above can no longer match; without this branch
// the failure fell through to the generic toast and the user had
// no hint their variant was never written (issue #384).
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) {
awaitingAcceptResult = null;
console.error('[impeccable] Accept failed after teardown:', msg.message);
// Hedged on purpose: a carbonize-phase failure raises this same
// error after the source WAS promoted, so "was not saved" would
// overclaim. Normalize the server message's terminal punctuation
// so the two sentences don't run together.
const acceptFailDetail = String(msg.message || 'unknown error').trim().replace(/[.!?]?$/, '.');
showToast('Accept failed: ' + acceptFailDetail + ' The variant may not have been saved. If the change is missing, pick the element and generate again.', 8000);
break;
}
if (maybeCompleteSteer(msg)) break;
console.error('[impeccable] Error:', msg.message);
showToast('Error: ' + msg.message, 5000);
@@ -6958,6 +6990,9 @@
stripManualEditRuntimeState(selectedElement);
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7037,6 +7072,9 @@
stopVoice({ suppressSubmit: true });
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7868,6 +7906,7 @@ void main() {
markSessionHandled();
setLiveState('CONFIRMED');
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
awaitingAcceptResult = { id: acceptedSessionId };
scheduleAcceptCleanup(pending);
})
.catch(() => {
@@ -175,6 +175,14 @@
let pickedAnchorViewportTop = null;
let pendingVariantAnchorRetryObserver = null;
let pendingAcceptedSession = null;
// Survives cleanupAcceptedSession on purpose: the id of an accept whose
// POST was acknowledged (intent durable, epoch fenced) but whose actual
// source promotion hasn't reported back yet. Accept is optimistic, so the
// teardown nulls pendingAcceptedSession long before live-accept.mjs runs;
// this marker is what lets the SSE 'error' branch still recognize a late
// accept failure and say the variant was not saved (issue #384). Released
// when the real accept result arrives or a new session starts.
let awaitingAcceptResult = null;
let variantObserver = null;
let variantSelectionInFlight = false;
let variantSelectionPromise = null;
@@ -6474,12 +6482,20 @@
break;
case 'complete':
case 'accept':
// The real accept result arrived: the awaited failure window closed.
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (maybeCompleteAcceptedSession(msg)) break;
break;
case 'agent_done':
// The deterministic accept has already committed the reviewed DOM
// and fenced generation. Carbonize may continue in the background;
// it must not hold the foreground picker hostage.
// Only a carbonize agent_done is provably accept-side: accept
// unlocks at the first variant, so a late generation agent_done
// for the same session id can still arrive after Accept and must
// not close the awaited failure window early (the SSE broadcast
// carries no sourceEventType to tell the two apart).
if (msg.data?.carbonize === true && awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
break;
case 'discarded':
@@ -6491,11 +6507,27 @@
case 'error':
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
pendingAcceptedSession = null;
awaitingAcceptResult = null;
setLiveState('CYCLING');
updateBarContent('cycling');
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
break;
}
// The optimistic teardown already released the session, so the
// CYCLING recovery above can no longer match; without this branch
// the failure fell through to the generic toast and the user had
// no hint their variant was never written (issue #384).
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) {
awaitingAcceptResult = null;
console.error('[impeccable] Accept failed after teardown:', msg.message);
// Hedged on purpose: a carbonize-phase failure raises this same
// error after the source WAS promoted, so "was not saved" would
// overclaim. Normalize the server message's terminal punctuation
// so the two sentences don't run together.
const acceptFailDetail = String(msg.message || 'unknown error').trim().replace(/[.!?]?$/, '.');
showToast('Accept failed: ' + acceptFailDetail + ' The variant may not have been saved. If the change is missing, pick the element and generate again.', 8000);
break;
}
if (maybeCompleteSteer(msg)) break;
console.error('[impeccable] Error:', msg.message);
showToast('Error: ' + msg.message, 5000);
@@ -6958,6 +6990,9 @@
stripManualEditRuntimeState(selectedElement);
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7037,6 +7072,9 @@
stopVoice({ suppressSubmit: true });
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7868,6 +7906,7 @@ void main() {
markSessionHandled();
setLiveState('CONFIRMED');
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
awaitingAcceptResult = { id: acceptedSessionId };
scheduleAcceptCleanup(pending);
})
.catch(() => {
@@ -175,6 +175,14 @@
let pickedAnchorViewportTop = null;
let pendingVariantAnchorRetryObserver = null;
let pendingAcceptedSession = null;
// Survives cleanupAcceptedSession on purpose: the id of an accept whose
// POST was acknowledged (intent durable, epoch fenced) but whose actual
// source promotion hasn't reported back yet. Accept is optimistic, so the
// teardown nulls pendingAcceptedSession long before live-accept.mjs runs;
// this marker is what lets the SSE 'error' branch still recognize a late
// accept failure and say the variant was not saved (issue #384). Released
// when the real accept result arrives or a new session starts.
let awaitingAcceptResult = null;
let variantObserver = null;
let variantSelectionInFlight = false;
let variantSelectionPromise = null;
@@ -6474,12 +6482,20 @@
break;
case 'complete':
case 'accept':
// The real accept result arrived: the awaited failure window closed.
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (maybeCompleteAcceptedSession(msg)) break;
break;
case 'agent_done':
// The deterministic accept has already committed the reviewed DOM
// and fenced generation. Carbonize may continue in the background;
// it must not hold the foreground picker hostage.
// Only a carbonize agent_done is provably accept-side: accept
// unlocks at the first variant, so a late generation agent_done
// for the same session id can still arrive after Accept and must
// not close the awaited failure window early (the SSE broadcast
// carries no sourceEventType to tell the two apart).
if (msg.data?.carbonize === true && awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
break;
case 'discarded':
@@ -6491,11 +6507,27 @@
case 'error':
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
pendingAcceptedSession = null;
awaitingAcceptResult = null;
setLiveState('CYCLING');
updateBarContent('cycling');
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
break;
}
// The optimistic teardown already released the session, so the
// CYCLING recovery above can no longer match; without this branch
// the failure fell through to the generic toast and the user had
// no hint their variant was never written (issue #384).
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) {
awaitingAcceptResult = null;
console.error('[impeccable] Accept failed after teardown:', msg.message);
// Hedged on purpose: a carbonize-phase failure raises this same
// error after the source WAS promoted, so "was not saved" would
// overclaim. Normalize the server message's terminal punctuation
// so the two sentences don't run together.
const acceptFailDetail = String(msg.message || 'unknown error').trim().replace(/[.!?]?$/, '.');
showToast('Accept failed: ' + acceptFailDetail + ' The variant may not have been saved. If the change is missing, pick the element and generate again.', 8000);
break;
}
if (maybeCompleteSteer(msg)) break;
console.error('[impeccable] Error:', msg.message);
showToast('Error: ' + msg.message, 5000);
@@ -6958,6 +6990,9 @@
stripManualEditRuntimeState(selectedElement);
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7037,6 +7072,9 @@
stopVoice({ suppressSubmit: true });
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7868,6 +7906,7 @@ void main() {
markSessionHandled();
setLiveState('CONFIRMED');
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
awaitingAcceptResult = { id: acceptedSessionId };
scheduleAcceptCleanup(pending);
})
.catch(() => {
@@ -175,6 +175,14 @@
let pickedAnchorViewportTop = null;
let pendingVariantAnchorRetryObserver = null;
let pendingAcceptedSession = null;
// Survives cleanupAcceptedSession on purpose: the id of an accept whose
// POST was acknowledged (intent durable, epoch fenced) but whose actual
// source promotion hasn't reported back yet. Accept is optimistic, so the
// teardown nulls pendingAcceptedSession long before live-accept.mjs runs;
// this marker is what lets the SSE 'error' branch still recognize a late
// accept failure and say the variant was not saved (issue #384). Released
// when the real accept result arrives or a new session starts.
let awaitingAcceptResult = null;
let variantObserver = null;
let variantSelectionInFlight = false;
let variantSelectionPromise = null;
@@ -6474,12 +6482,20 @@
break;
case 'complete':
case 'accept':
// The real accept result arrived: the awaited failure window closed.
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (maybeCompleteAcceptedSession(msg)) break;
break;
case 'agent_done':
// The deterministic accept has already committed the reviewed DOM
// and fenced generation. Carbonize may continue in the background;
// it must not hold the foreground picker hostage.
// Only a carbonize agent_done is provably accept-side: accept
// unlocks at the first variant, so a late generation agent_done
// for the same session id can still arrive after Accept and must
// not close the awaited failure window early (the SSE broadcast
// carries no sourceEventType to tell the two apart).
if (msg.data?.carbonize === true && awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) awaitingAcceptResult = null;
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
break;
case 'discarded':
@@ -6491,11 +6507,27 @@
case 'error':
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
pendingAcceptedSession = null;
awaitingAcceptResult = null;
setLiveState('CYCLING');
updateBarContent('cycling');
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
break;
}
// The optimistic teardown already released the session, so the
// CYCLING recovery above can no longer match; without this branch
// the failure fell through to the generic toast and the user had
// no hint their variant was never written (issue #384).
if (awaitingAcceptResult?.id && msg.id === awaitingAcceptResult.id) {
awaitingAcceptResult = null;
console.error('[impeccable] Accept failed after teardown:', msg.message);
// Hedged on purpose: a carbonize-phase failure raises this same
// error after the source WAS promoted, so "was not saved" would
// overclaim. Normalize the server message's terminal punctuation
// so the two sentences don't run together.
const acceptFailDetail = String(msg.message || 'unknown error').trim().replace(/[.!?]?$/, '.');
showToast('Accept failed: ' + acceptFailDetail + ' The variant may not have been saved. If the change is missing, pick the element and generate again.', 8000);
break;
}
if (maybeCompleteSteer(msg)) break;
console.error('[impeccable] Error:', msg.message);
showToast('Error: ' + msg.message, 5000);
@@ -6958,6 +6990,9 @@
stripManualEditRuntimeState(selectedElement);
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7037,6 +7072,9 @@
stopVoice({ suppressSubmit: true });
pendingAcceptedSession = null;
// A new session supersedes any accept still awaiting its result; a late
// failure toast for the previous session would only mislead here.
awaitingAcceptResult = null;
currentSessionId = id8();
expectedVariants = selectedCount;
arrivedVariants = 0;
@@ -7868,6 +7906,7 @@ void main() {
markSessionHandled();
setLiveState('CONFIRMED');
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
awaitingAcceptResult = { id: acceptedSessionId };
scheduleAcceptCleanup(pending);
})
.catch(() => {