mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-22 02:56:52 +03:00
Sync generated provider output
This commit is contained in:
@@ -175,6 +175,14 @@
|
|||||||
let pickedAnchorViewportTop = null;
|
let pickedAnchorViewportTop = null;
|
||||||
let pendingVariantAnchorRetryObserver = null;
|
let pendingVariantAnchorRetryObserver = null;
|
||||||
let pendingAcceptedSession = 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 variantObserver = null;
|
||||||
let variantSelectionInFlight = false;
|
let variantSelectionInFlight = false;
|
||||||
let variantSelectionPromise = null;
|
let variantSelectionPromise = null;
|
||||||
@@ -6474,12 +6482,20 @@
|
|||||||
break;
|
break;
|
||||||
case 'complete':
|
case 'complete':
|
||||||
case 'accept':
|
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;
|
if (maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'agent_done':
|
case 'agent_done':
|
||||||
// The deterministic accept has already committed the reviewed DOM
|
// The deterministic accept has already committed the reviewed DOM
|
||||||
// and fenced generation. Carbonize may continue in the background;
|
// and fenced generation. Carbonize may continue in the background;
|
||||||
// it must not hold the foreground picker hostage.
|
// 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;
|
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'discarded':
|
case 'discarded':
|
||||||
@@ -6491,11 +6507,27 @@
|
|||||||
case 'error':
|
case 'error':
|
||||||
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
||||||
pendingAcceptedSession = null;
|
pendingAcceptedSession = null;
|
||||||
|
awaitingAcceptResult = null;
|
||||||
setLiveState('CYCLING');
|
setLiveState('CYCLING');
|
||||||
updateBarContent('cycling');
|
updateBarContent('cycling');
|
||||||
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
||||||
break;
|
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;
|
if (maybeCompleteSteer(msg)) break;
|
||||||
console.error('[impeccable] Error:', msg.message);
|
console.error('[impeccable] Error:', msg.message);
|
||||||
showToast('Error: ' + msg.message, 5000);
|
showToast('Error: ' + msg.message, 5000);
|
||||||
@@ -6958,6 +6990,9 @@
|
|||||||
stripManualEditRuntimeState(selectedElement);
|
stripManualEditRuntimeState(selectedElement);
|
||||||
|
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7037,6 +7072,9 @@
|
|||||||
|
|
||||||
stopVoice({ suppressSubmit: true });
|
stopVoice({ suppressSubmit: true });
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7868,6 +7906,7 @@ void main() {
|
|||||||
markSessionHandled();
|
markSessionHandled();
|
||||||
setLiveState('CONFIRMED');
|
setLiveState('CONFIRMED');
|
||||||
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
||||||
|
awaitingAcceptResult = { id: acceptedSessionId };
|
||||||
scheduleAcceptCleanup(pending);
|
scheduleAcceptCleanup(pending);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -175,6 +175,14 @@
|
|||||||
let pickedAnchorViewportTop = null;
|
let pickedAnchorViewportTop = null;
|
||||||
let pendingVariantAnchorRetryObserver = null;
|
let pendingVariantAnchorRetryObserver = null;
|
||||||
let pendingAcceptedSession = 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 variantObserver = null;
|
||||||
let variantSelectionInFlight = false;
|
let variantSelectionInFlight = false;
|
||||||
let variantSelectionPromise = null;
|
let variantSelectionPromise = null;
|
||||||
@@ -6474,12 +6482,20 @@
|
|||||||
break;
|
break;
|
||||||
case 'complete':
|
case 'complete':
|
||||||
case 'accept':
|
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;
|
if (maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'agent_done':
|
case 'agent_done':
|
||||||
// The deterministic accept has already committed the reviewed DOM
|
// The deterministic accept has already committed the reviewed DOM
|
||||||
// and fenced generation. Carbonize may continue in the background;
|
// and fenced generation. Carbonize may continue in the background;
|
||||||
// it must not hold the foreground picker hostage.
|
// 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;
|
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'discarded':
|
case 'discarded':
|
||||||
@@ -6491,11 +6507,27 @@
|
|||||||
case 'error':
|
case 'error':
|
||||||
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
||||||
pendingAcceptedSession = null;
|
pendingAcceptedSession = null;
|
||||||
|
awaitingAcceptResult = null;
|
||||||
setLiveState('CYCLING');
|
setLiveState('CYCLING');
|
||||||
updateBarContent('cycling');
|
updateBarContent('cycling');
|
||||||
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
||||||
break;
|
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;
|
if (maybeCompleteSteer(msg)) break;
|
||||||
console.error('[impeccable] Error:', msg.message);
|
console.error('[impeccable] Error:', msg.message);
|
||||||
showToast('Error: ' + msg.message, 5000);
|
showToast('Error: ' + msg.message, 5000);
|
||||||
@@ -6958,6 +6990,9 @@
|
|||||||
stripManualEditRuntimeState(selectedElement);
|
stripManualEditRuntimeState(selectedElement);
|
||||||
|
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7037,6 +7072,9 @@
|
|||||||
|
|
||||||
stopVoice({ suppressSubmit: true });
|
stopVoice({ suppressSubmit: true });
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7868,6 +7906,7 @@ void main() {
|
|||||||
markSessionHandled();
|
markSessionHandled();
|
||||||
setLiveState('CONFIRMED');
|
setLiveState('CONFIRMED');
|
||||||
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
||||||
|
awaitingAcceptResult = { id: acceptedSessionId };
|
||||||
scheduleAcceptCleanup(pending);
|
scheduleAcceptCleanup(pending);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -175,6 +175,14 @@
|
|||||||
let pickedAnchorViewportTop = null;
|
let pickedAnchorViewportTop = null;
|
||||||
let pendingVariantAnchorRetryObserver = null;
|
let pendingVariantAnchorRetryObserver = null;
|
||||||
let pendingAcceptedSession = 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 variantObserver = null;
|
||||||
let variantSelectionInFlight = false;
|
let variantSelectionInFlight = false;
|
||||||
let variantSelectionPromise = null;
|
let variantSelectionPromise = null;
|
||||||
@@ -6474,12 +6482,20 @@
|
|||||||
break;
|
break;
|
||||||
case 'complete':
|
case 'complete':
|
||||||
case 'accept':
|
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;
|
if (maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'agent_done':
|
case 'agent_done':
|
||||||
// The deterministic accept has already committed the reviewed DOM
|
// The deterministic accept has already committed the reviewed DOM
|
||||||
// and fenced generation. Carbonize may continue in the background;
|
// and fenced generation. Carbonize may continue in the background;
|
||||||
// it must not hold the foreground picker hostage.
|
// 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;
|
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'discarded':
|
case 'discarded':
|
||||||
@@ -6491,11 +6507,27 @@
|
|||||||
case 'error':
|
case 'error':
|
||||||
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
||||||
pendingAcceptedSession = null;
|
pendingAcceptedSession = null;
|
||||||
|
awaitingAcceptResult = null;
|
||||||
setLiveState('CYCLING');
|
setLiveState('CYCLING');
|
||||||
updateBarContent('cycling');
|
updateBarContent('cycling');
|
||||||
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
||||||
break;
|
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;
|
if (maybeCompleteSteer(msg)) break;
|
||||||
console.error('[impeccable] Error:', msg.message);
|
console.error('[impeccable] Error:', msg.message);
|
||||||
showToast('Error: ' + msg.message, 5000);
|
showToast('Error: ' + msg.message, 5000);
|
||||||
@@ -6958,6 +6990,9 @@
|
|||||||
stripManualEditRuntimeState(selectedElement);
|
stripManualEditRuntimeState(selectedElement);
|
||||||
|
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7037,6 +7072,9 @@
|
|||||||
|
|
||||||
stopVoice({ suppressSubmit: true });
|
stopVoice({ suppressSubmit: true });
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7868,6 +7906,7 @@ void main() {
|
|||||||
markSessionHandled();
|
markSessionHandled();
|
||||||
setLiveState('CONFIRMED');
|
setLiveState('CONFIRMED');
|
||||||
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
||||||
|
awaitingAcceptResult = { id: acceptedSessionId };
|
||||||
scheduleAcceptCleanup(pending);
|
scheduleAcceptCleanup(pending);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -175,6 +175,14 @@
|
|||||||
let pickedAnchorViewportTop = null;
|
let pickedAnchorViewportTop = null;
|
||||||
let pendingVariantAnchorRetryObserver = null;
|
let pendingVariantAnchorRetryObserver = null;
|
||||||
let pendingAcceptedSession = 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 variantObserver = null;
|
||||||
let variantSelectionInFlight = false;
|
let variantSelectionInFlight = false;
|
||||||
let variantSelectionPromise = null;
|
let variantSelectionPromise = null;
|
||||||
@@ -6474,12 +6482,20 @@
|
|||||||
break;
|
break;
|
||||||
case 'complete':
|
case 'complete':
|
||||||
case 'accept':
|
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;
|
if (maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'agent_done':
|
case 'agent_done':
|
||||||
// The deterministic accept has already committed the reviewed DOM
|
// The deterministic accept has already committed the reviewed DOM
|
||||||
// and fenced generation. Carbonize may continue in the background;
|
// and fenced generation. Carbonize may continue in the background;
|
||||||
// it must not hold the foreground picker hostage.
|
// 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;
|
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'discarded':
|
case 'discarded':
|
||||||
@@ -6491,11 +6507,27 @@
|
|||||||
case 'error':
|
case 'error':
|
||||||
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
||||||
pendingAcceptedSession = null;
|
pendingAcceptedSession = null;
|
||||||
|
awaitingAcceptResult = null;
|
||||||
setLiveState('CYCLING');
|
setLiveState('CYCLING');
|
||||||
updateBarContent('cycling');
|
updateBarContent('cycling');
|
||||||
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
||||||
break;
|
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;
|
if (maybeCompleteSteer(msg)) break;
|
||||||
console.error('[impeccable] Error:', msg.message);
|
console.error('[impeccable] Error:', msg.message);
|
||||||
showToast('Error: ' + msg.message, 5000);
|
showToast('Error: ' + msg.message, 5000);
|
||||||
@@ -6958,6 +6990,9 @@
|
|||||||
stripManualEditRuntimeState(selectedElement);
|
stripManualEditRuntimeState(selectedElement);
|
||||||
|
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7037,6 +7072,9 @@
|
|||||||
|
|
||||||
stopVoice({ suppressSubmit: true });
|
stopVoice({ suppressSubmit: true });
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7868,6 +7906,7 @@ void main() {
|
|||||||
markSessionHandled();
|
markSessionHandled();
|
||||||
setLiveState('CONFIRMED');
|
setLiveState('CONFIRMED');
|
||||||
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
||||||
|
awaitingAcceptResult = { id: acceptedSessionId };
|
||||||
scheduleAcceptCleanup(pending);
|
scheduleAcceptCleanup(pending);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -175,6 +175,14 @@
|
|||||||
let pickedAnchorViewportTop = null;
|
let pickedAnchorViewportTop = null;
|
||||||
let pendingVariantAnchorRetryObserver = null;
|
let pendingVariantAnchorRetryObserver = null;
|
||||||
let pendingAcceptedSession = 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 variantObserver = null;
|
||||||
let variantSelectionInFlight = false;
|
let variantSelectionInFlight = false;
|
||||||
let variantSelectionPromise = null;
|
let variantSelectionPromise = null;
|
||||||
@@ -6474,12 +6482,20 @@
|
|||||||
break;
|
break;
|
||||||
case 'complete':
|
case 'complete':
|
||||||
case 'accept':
|
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;
|
if (maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'agent_done':
|
case 'agent_done':
|
||||||
// The deterministic accept has already committed the reviewed DOM
|
// The deterministic accept has already committed the reviewed DOM
|
||||||
// and fenced generation. Carbonize may continue in the background;
|
// and fenced generation. Carbonize may continue in the background;
|
||||||
// it must not hold the foreground picker hostage.
|
// 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;
|
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'discarded':
|
case 'discarded':
|
||||||
@@ -6491,11 +6507,27 @@
|
|||||||
case 'error':
|
case 'error':
|
||||||
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
||||||
pendingAcceptedSession = null;
|
pendingAcceptedSession = null;
|
||||||
|
awaitingAcceptResult = null;
|
||||||
setLiveState('CYCLING');
|
setLiveState('CYCLING');
|
||||||
updateBarContent('cycling');
|
updateBarContent('cycling');
|
||||||
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
||||||
break;
|
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;
|
if (maybeCompleteSteer(msg)) break;
|
||||||
console.error('[impeccable] Error:', msg.message);
|
console.error('[impeccable] Error:', msg.message);
|
||||||
showToast('Error: ' + msg.message, 5000);
|
showToast('Error: ' + msg.message, 5000);
|
||||||
@@ -6958,6 +6990,9 @@
|
|||||||
stripManualEditRuntimeState(selectedElement);
|
stripManualEditRuntimeState(selectedElement);
|
||||||
|
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7037,6 +7072,9 @@
|
|||||||
|
|
||||||
stopVoice({ suppressSubmit: true });
|
stopVoice({ suppressSubmit: true });
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7868,6 +7906,7 @@ void main() {
|
|||||||
markSessionHandled();
|
markSessionHandled();
|
||||||
setLiveState('CONFIRMED');
|
setLiveState('CONFIRMED');
|
||||||
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
||||||
|
awaitingAcceptResult = { id: acceptedSessionId };
|
||||||
scheduleAcceptCleanup(pending);
|
scheduleAcceptCleanup(pending);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -175,6 +175,14 @@
|
|||||||
let pickedAnchorViewportTop = null;
|
let pickedAnchorViewportTop = null;
|
||||||
let pendingVariantAnchorRetryObserver = null;
|
let pendingVariantAnchorRetryObserver = null;
|
||||||
let pendingAcceptedSession = 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 variantObserver = null;
|
||||||
let variantSelectionInFlight = false;
|
let variantSelectionInFlight = false;
|
||||||
let variantSelectionPromise = null;
|
let variantSelectionPromise = null;
|
||||||
@@ -6474,12 +6482,20 @@
|
|||||||
break;
|
break;
|
||||||
case 'complete':
|
case 'complete':
|
||||||
case 'accept':
|
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;
|
if (maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'agent_done':
|
case 'agent_done':
|
||||||
// The deterministic accept has already committed the reviewed DOM
|
// The deterministic accept has already committed the reviewed DOM
|
||||||
// and fenced generation. Carbonize may continue in the background;
|
// and fenced generation. Carbonize may continue in the background;
|
||||||
// it must not hold the foreground picker hostage.
|
// 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;
|
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'discarded':
|
case 'discarded':
|
||||||
@@ -6491,11 +6507,27 @@
|
|||||||
case 'error':
|
case 'error':
|
||||||
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
||||||
pendingAcceptedSession = null;
|
pendingAcceptedSession = null;
|
||||||
|
awaitingAcceptResult = null;
|
||||||
setLiveState('CYCLING');
|
setLiveState('CYCLING');
|
||||||
updateBarContent('cycling');
|
updateBarContent('cycling');
|
||||||
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
||||||
break;
|
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;
|
if (maybeCompleteSteer(msg)) break;
|
||||||
console.error('[impeccable] Error:', msg.message);
|
console.error('[impeccable] Error:', msg.message);
|
||||||
showToast('Error: ' + msg.message, 5000);
|
showToast('Error: ' + msg.message, 5000);
|
||||||
@@ -6958,6 +6990,9 @@
|
|||||||
stripManualEditRuntimeState(selectedElement);
|
stripManualEditRuntimeState(selectedElement);
|
||||||
|
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7037,6 +7072,9 @@
|
|||||||
|
|
||||||
stopVoice({ suppressSubmit: true });
|
stopVoice({ suppressSubmit: true });
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7868,6 +7906,7 @@ void main() {
|
|||||||
markSessionHandled();
|
markSessionHandled();
|
||||||
setLiveState('CONFIRMED');
|
setLiveState('CONFIRMED');
|
||||||
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
||||||
|
awaitingAcceptResult = { id: acceptedSessionId };
|
||||||
scheduleAcceptCleanup(pending);
|
scheduleAcceptCleanup(pending);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -175,6 +175,14 @@
|
|||||||
let pickedAnchorViewportTop = null;
|
let pickedAnchorViewportTop = null;
|
||||||
let pendingVariantAnchorRetryObserver = null;
|
let pendingVariantAnchorRetryObserver = null;
|
||||||
let pendingAcceptedSession = 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 variantObserver = null;
|
||||||
let variantSelectionInFlight = false;
|
let variantSelectionInFlight = false;
|
||||||
let variantSelectionPromise = null;
|
let variantSelectionPromise = null;
|
||||||
@@ -6474,12 +6482,20 @@
|
|||||||
break;
|
break;
|
||||||
case 'complete':
|
case 'complete':
|
||||||
case 'accept':
|
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;
|
if (maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'agent_done':
|
case 'agent_done':
|
||||||
// The deterministic accept has already committed the reviewed DOM
|
// The deterministic accept has already committed the reviewed DOM
|
||||||
// and fenced generation. Carbonize may continue in the background;
|
// and fenced generation. Carbonize may continue in the background;
|
||||||
// it must not hold the foreground picker hostage.
|
// 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;
|
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'discarded':
|
case 'discarded':
|
||||||
@@ -6491,11 +6507,27 @@
|
|||||||
case 'error':
|
case 'error':
|
||||||
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
||||||
pendingAcceptedSession = null;
|
pendingAcceptedSession = null;
|
||||||
|
awaitingAcceptResult = null;
|
||||||
setLiveState('CYCLING');
|
setLiveState('CYCLING');
|
||||||
updateBarContent('cycling');
|
updateBarContent('cycling');
|
||||||
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
||||||
break;
|
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;
|
if (maybeCompleteSteer(msg)) break;
|
||||||
console.error('[impeccable] Error:', msg.message);
|
console.error('[impeccable] Error:', msg.message);
|
||||||
showToast('Error: ' + msg.message, 5000);
|
showToast('Error: ' + msg.message, 5000);
|
||||||
@@ -6958,6 +6990,9 @@
|
|||||||
stripManualEditRuntimeState(selectedElement);
|
stripManualEditRuntimeState(selectedElement);
|
||||||
|
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7037,6 +7072,9 @@
|
|||||||
|
|
||||||
stopVoice({ suppressSubmit: true });
|
stopVoice({ suppressSubmit: true });
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7868,6 +7906,7 @@ void main() {
|
|||||||
markSessionHandled();
|
markSessionHandled();
|
||||||
setLiveState('CONFIRMED');
|
setLiveState('CONFIRMED');
|
||||||
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
||||||
|
awaitingAcceptResult = { id: acceptedSessionId };
|
||||||
scheduleAcceptCleanup(pending);
|
scheduleAcceptCleanup(pending);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -175,6 +175,14 @@
|
|||||||
let pickedAnchorViewportTop = null;
|
let pickedAnchorViewportTop = null;
|
||||||
let pendingVariantAnchorRetryObserver = null;
|
let pendingVariantAnchorRetryObserver = null;
|
||||||
let pendingAcceptedSession = 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 variantObserver = null;
|
||||||
let variantSelectionInFlight = false;
|
let variantSelectionInFlight = false;
|
||||||
let variantSelectionPromise = null;
|
let variantSelectionPromise = null;
|
||||||
@@ -6474,12 +6482,20 @@
|
|||||||
break;
|
break;
|
||||||
case 'complete':
|
case 'complete':
|
||||||
case 'accept':
|
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;
|
if (maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'agent_done':
|
case 'agent_done':
|
||||||
// The deterministic accept has already committed the reviewed DOM
|
// The deterministic accept has already committed the reviewed DOM
|
||||||
// and fenced generation. Carbonize may continue in the background;
|
// and fenced generation. Carbonize may continue in the background;
|
||||||
// it must not hold the foreground picker hostage.
|
// 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;
|
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'discarded':
|
case 'discarded':
|
||||||
@@ -6491,11 +6507,27 @@
|
|||||||
case 'error':
|
case 'error':
|
||||||
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
||||||
pendingAcceptedSession = null;
|
pendingAcceptedSession = null;
|
||||||
|
awaitingAcceptResult = null;
|
||||||
setLiveState('CYCLING');
|
setLiveState('CYCLING');
|
||||||
updateBarContent('cycling');
|
updateBarContent('cycling');
|
||||||
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
||||||
break;
|
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;
|
if (maybeCompleteSteer(msg)) break;
|
||||||
console.error('[impeccable] Error:', msg.message);
|
console.error('[impeccable] Error:', msg.message);
|
||||||
showToast('Error: ' + msg.message, 5000);
|
showToast('Error: ' + msg.message, 5000);
|
||||||
@@ -6958,6 +6990,9 @@
|
|||||||
stripManualEditRuntimeState(selectedElement);
|
stripManualEditRuntimeState(selectedElement);
|
||||||
|
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7037,6 +7072,9 @@
|
|||||||
|
|
||||||
stopVoice({ suppressSubmit: true });
|
stopVoice({ suppressSubmit: true });
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7868,6 +7906,7 @@ void main() {
|
|||||||
markSessionHandled();
|
markSessionHandled();
|
||||||
setLiveState('CONFIRMED');
|
setLiveState('CONFIRMED');
|
||||||
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
||||||
|
awaitingAcceptResult = { id: acceptedSessionId };
|
||||||
scheduleAcceptCleanup(pending);
|
scheduleAcceptCleanup(pending);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -175,6 +175,14 @@
|
|||||||
let pickedAnchorViewportTop = null;
|
let pickedAnchorViewportTop = null;
|
||||||
let pendingVariantAnchorRetryObserver = null;
|
let pendingVariantAnchorRetryObserver = null;
|
||||||
let pendingAcceptedSession = 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 variantObserver = null;
|
||||||
let variantSelectionInFlight = false;
|
let variantSelectionInFlight = false;
|
||||||
let variantSelectionPromise = null;
|
let variantSelectionPromise = null;
|
||||||
@@ -6474,12 +6482,20 @@
|
|||||||
break;
|
break;
|
||||||
case 'complete':
|
case 'complete':
|
||||||
case 'accept':
|
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;
|
if (maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'agent_done':
|
case 'agent_done':
|
||||||
// The deterministic accept has already committed the reviewed DOM
|
// The deterministic accept has already committed the reviewed DOM
|
||||||
// and fenced generation. Carbonize may continue in the background;
|
// and fenced generation. Carbonize may continue in the background;
|
||||||
// it must not hold the foreground picker hostage.
|
// 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;
|
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'discarded':
|
case 'discarded':
|
||||||
@@ -6491,11 +6507,27 @@
|
|||||||
case 'error':
|
case 'error':
|
||||||
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
||||||
pendingAcceptedSession = null;
|
pendingAcceptedSession = null;
|
||||||
|
awaitingAcceptResult = null;
|
||||||
setLiveState('CYCLING');
|
setLiveState('CYCLING');
|
||||||
updateBarContent('cycling');
|
updateBarContent('cycling');
|
||||||
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
||||||
break;
|
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;
|
if (maybeCompleteSteer(msg)) break;
|
||||||
console.error('[impeccable] Error:', msg.message);
|
console.error('[impeccable] Error:', msg.message);
|
||||||
showToast('Error: ' + msg.message, 5000);
|
showToast('Error: ' + msg.message, 5000);
|
||||||
@@ -6958,6 +6990,9 @@
|
|||||||
stripManualEditRuntimeState(selectedElement);
|
stripManualEditRuntimeState(selectedElement);
|
||||||
|
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7037,6 +7072,9 @@
|
|||||||
|
|
||||||
stopVoice({ suppressSubmit: true });
|
stopVoice({ suppressSubmit: true });
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7868,6 +7906,7 @@ void main() {
|
|||||||
markSessionHandled();
|
markSessionHandled();
|
||||||
setLiveState('CONFIRMED');
|
setLiveState('CONFIRMED');
|
||||||
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
||||||
|
awaitingAcceptResult = { id: acceptedSessionId };
|
||||||
scheduleAcceptCleanup(pending);
|
scheduleAcceptCleanup(pending);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -175,6 +175,14 @@
|
|||||||
let pickedAnchorViewportTop = null;
|
let pickedAnchorViewportTop = null;
|
||||||
let pendingVariantAnchorRetryObserver = null;
|
let pendingVariantAnchorRetryObserver = null;
|
||||||
let pendingAcceptedSession = 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 variantObserver = null;
|
||||||
let variantSelectionInFlight = false;
|
let variantSelectionInFlight = false;
|
||||||
let variantSelectionPromise = null;
|
let variantSelectionPromise = null;
|
||||||
@@ -6474,12 +6482,20 @@
|
|||||||
break;
|
break;
|
||||||
case 'complete':
|
case 'complete':
|
||||||
case 'accept':
|
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;
|
if (maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'agent_done':
|
case 'agent_done':
|
||||||
// The deterministic accept has already committed the reviewed DOM
|
// The deterministic accept has already committed the reviewed DOM
|
||||||
// and fenced generation. Carbonize may continue in the background;
|
// and fenced generation. Carbonize may continue in the background;
|
||||||
// it must not hold the foreground picker hostage.
|
// 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;
|
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'discarded':
|
case 'discarded':
|
||||||
@@ -6491,11 +6507,27 @@
|
|||||||
case 'error':
|
case 'error':
|
||||||
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
||||||
pendingAcceptedSession = null;
|
pendingAcceptedSession = null;
|
||||||
|
awaitingAcceptResult = null;
|
||||||
setLiveState('CYCLING');
|
setLiveState('CYCLING');
|
||||||
updateBarContent('cycling');
|
updateBarContent('cycling');
|
||||||
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
||||||
break;
|
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;
|
if (maybeCompleteSteer(msg)) break;
|
||||||
console.error('[impeccable] Error:', msg.message);
|
console.error('[impeccable] Error:', msg.message);
|
||||||
showToast('Error: ' + msg.message, 5000);
|
showToast('Error: ' + msg.message, 5000);
|
||||||
@@ -6958,6 +6990,9 @@
|
|||||||
stripManualEditRuntimeState(selectedElement);
|
stripManualEditRuntimeState(selectedElement);
|
||||||
|
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7037,6 +7072,9 @@
|
|||||||
|
|
||||||
stopVoice({ suppressSubmit: true });
|
stopVoice({ suppressSubmit: true });
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7868,6 +7906,7 @@ void main() {
|
|||||||
markSessionHandled();
|
markSessionHandled();
|
||||||
setLiveState('CONFIRMED');
|
setLiveState('CONFIRMED');
|
||||||
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
||||||
|
awaitingAcceptResult = { id: acceptedSessionId };
|
||||||
scheduleAcceptCleanup(pending);
|
scheduleAcceptCleanup(pending);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -175,6 +175,14 @@
|
|||||||
let pickedAnchorViewportTop = null;
|
let pickedAnchorViewportTop = null;
|
||||||
let pendingVariantAnchorRetryObserver = null;
|
let pendingVariantAnchorRetryObserver = null;
|
||||||
let pendingAcceptedSession = 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 variantObserver = null;
|
||||||
let variantSelectionInFlight = false;
|
let variantSelectionInFlight = false;
|
||||||
let variantSelectionPromise = null;
|
let variantSelectionPromise = null;
|
||||||
@@ -6474,12 +6482,20 @@
|
|||||||
break;
|
break;
|
||||||
case 'complete':
|
case 'complete':
|
||||||
case 'accept':
|
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;
|
if (maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'agent_done':
|
case 'agent_done':
|
||||||
// The deterministic accept has already committed the reviewed DOM
|
// The deterministic accept has already committed the reviewed DOM
|
||||||
// and fenced generation. Carbonize may continue in the background;
|
// and fenced generation. Carbonize may continue in the background;
|
||||||
// it must not hold the foreground picker hostage.
|
// 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;
|
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'discarded':
|
case 'discarded':
|
||||||
@@ -6491,11 +6507,27 @@
|
|||||||
case 'error':
|
case 'error':
|
||||||
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
||||||
pendingAcceptedSession = null;
|
pendingAcceptedSession = null;
|
||||||
|
awaitingAcceptResult = null;
|
||||||
setLiveState('CYCLING');
|
setLiveState('CYCLING');
|
||||||
updateBarContent('cycling');
|
updateBarContent('cycling');
|
||||||
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
||||||
break;
|
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;
|
if (maybeCompleteSteer(msg)) break;
|
||||||
console.error('[impeccable] Error:', msg.message);
|
console.error('[impeccable] Error:', msg.message);
|
||||||
showToast('Error: ' + msg.message, 5000);
|
showToast('Error: ' + msg.message, 5000);
|
||||||
@@ -6958,6 +6990,9 @@
|
|||||||
stripManualEditRuntimeState(selectedElement);
|
stripManualEditRuntimeState(selectedElement);
|
||||||
|
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7037,6 +7072,9 @@
|
|||||||
|
|
||||||
stopVoice({ suppressSubmit: true });
|
stopVoice({ suppressSubmit: true });
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7868,6 +7906,7 @@ void main() {
|
|||||||
markSessionHandled();
|
markSessionHandled();
|
||||||
setLiveState('CONFIRMED');
|
setLiveState('CONFIRMED');
|
||||||
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
||||||
|
awaitingAcceptResult = { id: acceptedSessionId };
|
||||||
scheduleAcceptCleanup(pending);
|
scheduleAcceptCleanup(pending);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -175,6 +175,14 @@
|
|||||||
let pickedAnchorViewportTop = null;
|
let pickedAnchorViewportTop = null;
|
||||||
let pendingVariantAnchorRetryObserver = null;
|
let pendingVariantAnchorRetryObserver = null;
|
||||||
let pendingAcceptedSession = 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 variantObserver = null;
|
||||||
let variantSelectionInFlight = false;
|
let variantSelectionInFlight = false;
|
||||||
let variantSelectionPromise = null;
|
let variantSelectionPromise = null;
|
||||||
@@ -6474,12 +6482,20 @@
|
|||||||
break;
|
break;
|
||||||
case 'complete':
|
case 'complete':
|
||||||
case 'accept':
|
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;
|
if (maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'agent_done':
|
case 'agent_done':
|
||||||
// The deterministic accept has already committed the reviewed DOM
|
// The deterministic accept has already committed the reviewed DOM
|
||||||
// and fenced generation. Carbonize may continue in the background;
|
// and fenced generation. Carbonize may continue in the background;
|
||||||
// it must not hold the foreground picker hostage.
|
// 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;
|
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'discarded':
|
case 'discarded':
|
||||||
@@ -6491,11 +6507,27 @@
|
|||||||
case 'error':
|
case 'error':
|
||||||
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
||||||
pendingAcceptedSession = null;
|
pendingAcceptedSession = null;
|
||||||
|
awaitingAcceptResult = null;
|
||||||
setLiveState('CYCLING');
|
setLiveState('CYCLING');
|
||||||
updateBarContent('cycling');
|
updateBarContent('cycling');
|
||||||
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
||||||
break;
|
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;
|
if (maybeCompleteSteer(msg)) break;
|
||||||
console.error('[impeccable] Error:', msg.message);
|
console.error('[impeccable] Error:', msg.message);
|
||||||
showToast('Error: ' + msg.message, 5000);
|
showToast('Error: ' + msg.message, 5000);
|
||||||
@@ -6958,6 +6990,9 @@
|
|||||||
stripManualEditRuntimeState(selectedElement);
|
stripManualEditRuntimeState(selectedElement);
|
||||||
|
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7037,6 +7072,9 @@
|
|||||||
|
|
||||||
stopVoice({ suppressSubmit: true });
|
stopVoice({ suppressSubmit: true });
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7868,6 +7906,7 @@ void main() {
|
|||||||
markSessionHandled();
|
markSessionHandled();
|
||||||
setLiveState('CONFIRMED');
|
setLiveState('CONFIRMED');
|
||||||
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
||||||
|
awaitingAcceptResult = { id: acceptedSessionId };
|
||||||
scheduleAcceptCleanup(pending);
|
scheduleAcceptCleanup(pending);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -175,6 +175,14 @@
|
|||||||
let pickedAnchorViewportTop = null;
|
let pickedAnchorViewportTop = null;
|
||||||
let pendingVariantAnchorRetryObserver = null;
|
let pendingVariantAnchorRetryObserver = null;
|
||||||
let pendingAcceptedSession = 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 variantObserver = null;
|
||||||
let variantSelectionInFlight = false;
|
let variantSelectionInFlight = false;
|
||||||
let variantSelectionPromise = null;
|
let variantSelectionPromise = null;
|
||||||
@@ -6474,12 +6482,20 @@
|
|||||||
break;
|
break;
|
||||||
case 'complete':
|
case 'complete':
|
||||||
case 'accept':
|
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;
|
if (maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'agent_done':
|
case 'agent_done':
|
||||||
// The deterministic accept has already committed the reviewed DOM
|
// The deterministic accept has already committed the reviewed DOM
|
||||||
// and fenced generation. Carbonize may continue in the background;
|
// and fenced generation. Carbonize may continue in the background;
|
||||||
// it must not hold the foreground picker hostage.
|
// 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;
|
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'discarded':
|
case 'discarded':
|
||||||
@@ -6491,11 +6507,27 @@
|
|||||||
case 'error':
|
case 'error':
|
||||||
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
||||||
pendingAcceptedSession = null;
|
pendingAcceptedSession = null;
|
||||||
|
awaitingAcceptResult = null;
|
||||||
setLiveState('CYCLING');
|
setLiveState('CYCLING');
|
||||||
updateBarContent('cycling');
|
updateBarContent('cycling');
|
||||||
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
||||||
break;
|
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;
|
if (maybeCompleteSteer(msg)) break;
|
||||||
console.error('[impeccable] Error:', msg.message);
|
console.error('[impeccable] Error:', msg.message);
|
||||||
showToast('Error: ' + msg.message, 5000);
|
showToast('Error: ' + msg.message, 5000);
|
||||||
@@ -6958,6 +6990,9 @@
|
|||||||
stripManualEditRuntimeState(selectedElement);
|
stripManualEditRuntimeState(selectedElement);
|
||||||
|
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7037,6 +7072,9 @@
|
|||||||
|
|
||||||
stopVoice({ suppressSubmit: true });
|
stopVoice({ suppressSubmit: true });
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7868,6 +7906,7 @@ void main() {
|
|||||||
markSessionHandled();
|
markSessionHandled();
|
||||||
setLiveState('CONFIRMED');
|
setLiveState('CONFIRMED');
|
||||||
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
||||||
|
awaitingAcceptResult = { id: acceptedSessionId };
|
||||||
scheduleAcceptCleanup(pending);
|
scheduleAcceptCleanup(pending);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -175,6 +175,14 @@
|
|||||||
let pickedAnchorViewportTop = null;
|
let pickedAnchorViewportTop = null;
|
||||||
let pendingVariantAnchorRetryObserver = null;
|
let pendingVariantAnchorRetryObserver = null;
|
||||||
let pendingAcceptedSession = 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 variantObserver = null;
|
||||||
let variantSelectionInFlight = false;
|
let variantSelectionInFlight = false;
|
||||||
let variantSelectionPromise = null;
|
let variantSelectionPromise = null;
|
||||||
@@ -6474,12 +6482,20 @@
|
|||||||
break;
|
break;
|
||||||
case 'complete':
|
case 'complete':
|
||||||
case 'accept':
|
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;
|
if (maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'agent_done':
|
case 'agent_done':
|
||||||
// The deterministic accept has already committed the reviewed DOM
|
// The deterministic accept has already committed the reviewed DOM
|
||||||
// and fenced generation. Carbonize may continue in the background;
|
// and fenced generation. Carbonize may continue in the background;
|
||||||
// it must not hold the foreground picker hostage.
|
// 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;
|
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'discarded':
|
case 'discarded':
|
||||||
@@ -6491,11 +6507,27 @@
|
|||||||
case 'error':
|
case 'error':
|
||||||
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
||||||
pendingAcceptedSession = null;
|
pendingAcceptedSession = null;
|
||||||
|
awaitingAcceptResult = null;
|
||||||
setLiveState('CYCLING');
|
setLiveState('CYCLING');
|
||||||
updateBarContent('cycling');
|
updateBarContent('cycling');
|
||||||
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
||||||
break;
|
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;
|
if (maybeCompleteSteer(msg)) break;
|
||||||
console.error('[impeccable] Error:', msg.message);
|
console.error('[impeccable] Error:', msg.message);
|
||||||
showToast('Error: ' + msg.message, 5000);
|
showToast('Error: ' + msg.message, 5000);
|
||||||
@@ -6958,6 +6990,9 @@
|
|||||||
stripManualEditRuntimeState(selectedElement);
|
stripManualEditRuntimeState(selectedElement);
|
||||||
|
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7037,6 +7072,9 @@
|
|||||||
|
|
||||||
stopVoice({ suppressSubmit: true });
|
stopVoice({ suppressSubmit: true });
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7868,6 +7906,7 @@ void main() {
|
|||||||
markSessionHandled();
|
markSessionHandled();
|
||||||
setLiveState('CONFIRMED');
|
setLiveState('CONFIRMED');
|
||||||
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
||||||
|
awaitingAcceptResult = { id: acceptedSessionId };
|
||||||
scheduleAcceptCleanup(pending);
|
scheduleAcceptCleanup(pending);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -175,6 +175,14 @@
|
|||||||
let pickedAnchorViewportTop = null;
|
let pickedAnchorViewportTop = null;
|
||||||
let pendingVariantAnchorRetryObserver = null;
|
let pendingVariantAnchorRetryObserver = null;
|
||||||
let pendingAcceptedSession = 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 variantObserver = null;
|
||||||
let variantSelectionInFlight = false;
|
let variantSelectionInFlight = false;
|
||||||
let variantSelectionPromise = null;
|
let variantSelectionPromise = null;
|
||||||
@@ -6474,12 +6482,20 @@
|
|||||||
break;
|
break;
|
||||||
case 'complete':
|
case 'complete':
|
||||||
case 'accept':
|
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;
|
if (maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'agent_done':
|
case 'agent_done':
|
||||||
// The deterministic accept has already committed the reviewed DOM
|
// The deterministic accept has already committed the reviewed DOM
|
||||||
// and fenced generation. Carbonize may continue in the background;
|
// and fenced generation. Carbonize may continue in the background;
|
||||||
// it must not hold the foreground picker hostage.
|
// 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;
|
if (msg.data?.carbonize === true && maybeCompleteAcceptedSession(msg)) break;
|
||||||
break;
|
break;
|
||||||
case 'discarded':
|
case 'discarded':
|
||||||
@@ -6491,11 +6507,27 @@
|
|||||||
case 'error':
|
case 'error':
|
||||||
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
|
||||||
pendingAcceptedSession = null;
|
pendingAcceptedSession = null;
|
||||||
|
awaitingAcceptResult = null;
|
||||||
setLiveState('CYCLING');
|
setLiveState('CYCLING');
|
||||||
updateBarContent('cycling');
|
updateBarContent('cycling');
|
||||||
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
showToast('Could not complete accept cleanup. Try Accept again.', 5000);
|
||||||
break;
|
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;
|
if (maybeCompleteSteer(msg)) break;
|
||||||
console.error('[impeccable] Error:', msg.message);
|
console.error('[impeccable] Error:', msg.message);
|
||||||
showToast('Error: ' + msg.message, 5000);
|
showToast('Error: ' + msg.message, 5000);
|
||||||
@@ -6958,6 +6990,9 @@
|
|||||||
stripManualEditRuntimeState(selectedElement);
|
stripManualEditRuntimeState(selectedElement);
|
||||||
|
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7037,6 +7072,9 @@
|
|||||||
|
|
||||||
stopVoice({ suppressSubmit: true });
|
stopVoice({ suppressSubmit: true });
|
||||||
pendingAcceptedSession = null;
|
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();
|
currentSessionId = id8();
|
||||||
expectedVariants = selectedCount;
|
expectedVariants = selectedCount;
|
||||||
arrivedVariants = 0;
|
arrivedVariants = 0;
|
||||||
@@ -7868,6 +7906,7 @@ void main() {
|
|||||||
markSessionHandled();
|
markSessionHandled();
|
||||||
setLiveState('CONFIRMED');
|
setLiveState('CONFIRMED');
|
||||||
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
document.documentElement.dataset.impeccableAcceptToPickingMs = String(Date.now() - acceptPayload.clientSentAt);
|
||||||
|
awaitingAcceptResult = { id: acceptedSessionId };
|
||||||
scheduleAcceptCleanup(pending);
|
scheduleAcceptCleanup(pending);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user