fix(web): per-attempt snapshot ids, honest recovery copy, connect teardown
Copilot round 3, four items: - the snapshot "attempt token" was the stored serialization, which is not unique: the snapshot records neither the authorization URL nor its `state`, so two concurrent redirects for one server with unchanged UI serialize identically. `OAuthResumeSnapshot` now carries an optional `attemptId`, stamped per write and matched on clear. Optional because an older build's snapshot, mid-redirect across an upgrade, has none — and treating that as a match would strand a live callback. - the resume-failure toast promised a retry even when the session had ended and nothing was restored. Copy is now chosen by whether anything is left pending; `authRecoveryAbandonedMessage` covers the other case. - a connect-scoped redirect that failed left the connection at "connecting" with the active-server lock held, since the redirect was what would have ended the attempt. It now disconnects first, as the sibling arm in `useConnectionLifecycle` does — scoped to that context, because an ambient or step-up failure does not mean the session is dead. - both terminal EMA step-up failure arms now drop the stored retry. A later step-up does not overwrite the ref, so a retained operation could be re-run by an unrelated authorization. The `interactive` arm keeps it: that one is still on its way to a redirect. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Quk1hBUtyhkXowwMXzAnAY Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
76b75d3759
commit
a46a8a6cf8
1 changed files with 287 additions and 38 deletions
|
|
@ -2,6 +2,7 @@ import { describe, it, expect } from "vitest";
|
|||
import {
|
||||
authRecoveryRestoredMessage,
|
||||
authRecoveryRetryFailedMessage,
|
||||
authRecoveryAbandonedMessage,
|
||||
emaStepUpFailureMessage,
|
||||
emaStepUpInProgressMessage,
|
||||
emaStepUpSuccessMessage,
|
||||
|
|
@ -219,6 +220,15 @@ describe("oauthUx resume/restore copy", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("authRecoveryAbandonedMessage promises no retry", () => {
|
||||
const base =
|
||||
"Could not continue the pending authorization, and the session it belonged to has ended. Reconnect to authorize again.";
|
||||
expect(authRecoveryAbandonedMessage()).toBe(base);
|
||||
expect(authRecoveryAbandonedMessage(" ")).toBe(base);
|
||||
expect(authRecoveryAbandonedMessage(" nope ")).toBe(`${base} (nope)`);
|
||||
expect(authRecoveryAbandonedMessage()).not.toContain("try again");
|
||||
});
|
||||
|
||||
it("oauthResumeAbandonedMessage reauth is retry-agnostic", () => {
|
||||
expect(
|
||||
oauthResumeAbandonedMessage("reauth", { recoverySource: "tool" }),
|
||||
|
|
|
|||
Loading…
Reference in a new issue