Skip to content

Commit 493da68

Browse files
tyler-daneclaude
andcommitted
fix(web): keep the signed-out message alive and unqueued
Two ways the one message telling a user their write failed could never reach them: - signOut raised a toast and then assigned window.location, and a document navigation tears the toast down. The early return meant this only happened on routes other than the calendar, which is exactly where the message was needed. Route in-app instead, the same way SessionExpiredToast already reaches the router. - ToastContainer allowed one toast at a time, so any routine toast already on screen silently queued out a critical one behind it. Adds coverage for the navigating branch, which had none. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 9555328 commit 493da68

3 files changed

Lines changed: 35 additions & 3 deletions

File tree

packages/web/src/api/util/api.util.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,4 +268,28 @@ describe("handleErrorResponse", () => {
268268
expect(signOutSpy).toHaveBeenCalledTimes(1);
269269
signOutSpy.mockRestore();
270270
});
271+
272+
it("leaves a non-calendar route through the router, not a document navigation", async () => {
273+
// A document navigation tears down the toast signOut just raised, so on
274+
// every route that actually needed it the "you've been signed out" message
275+
// was destroyed before it could be read. Routing in-app keeps it alive.
276+
window.history.pushState({}, "", "/day/2026-07-31");
277+
const navigate = mock((_options: { to: string }) => Promise.resolve());
278+
mock.module("@web/routers", () => ({ router: { navigate } }));
279+
const signOutSpy = spyOn(session, "signOut").mockResolvedValue(undefined);
280+
const error = createApiError(
281+
{ status: Status.UNAUTHORIZED },
282+
{ url: "/event" },
283+
);
284+
285+
await expect(
286+
handleErrorResponse(error, { onGoogleRevoked: undefined }),
287+
).rejects.toBe(error);
288+
289+
expect(signOutSpy).toHaveBeenCalledTimes(1);
290+
expect(navigate).toHaveBeenCalledTimes(1);
291+
expect(navigate).toHaveBeenCalledWith({ to: "/week" });
292+
signOutSpy.mockRestore();
293+
window.history.pushState({}, "", "/week");
294+
});
271295
});

packages/web/src/api/util/api.util.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
import { session } from "@web/auth/compass/session/Session";
88
import { ENV_WEB } from "@web/common/constants/env.constants";
99
import { DEFAULT_CALENDAR_ROUTE } from "@web/common/constants/routes";
10-
import { assignLocation } from "@web/common/utils/browser/browser-navigation.util";
1110
import {
1211
showErrorToast,
1312
showSessionExpiredToast,
@@ -91,7 +90,13 @@ export const signOut = async (status: SignoutStatus) => {
9190
if (window.location.pathname.startsWith(DEFAULT_CALENDAR_ROUTE)) {
9291
return;
9392
}
94-
assignLocation(DEFAULT_CALENDAR_ROUTE);
93+
// Navigate in-app rather than assigning window.location: a document
94+
// navigation tears down the toast we just raised, so the one message telling
95+
// the user they were signed out was destroyed on every route that needed it.
96+
// Imported dynamically for the same module-cycle reason SessionExpiredToast
97+
// documents (this file sits on the API error path the router pulls back in).
98+
const { router } = await import("@web/routers");
99+
await router.navigate({ to: DEFAULT_CALENDAR_ROUTE });
95100
};
96101

97102
export const getRequestUrl = (url: string): string => {

packages/web/src/components/CompassProvider/CompassProvider.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ function ThemeAwareToastContainer() {
4040
draggable
4141
pauseOnHover
4242
theme={theme === "dark-abyss" ? "dark" : "light"}
43-
limit={1}
43+
// Not 1: with a single slot, a routine toast already on screen (an undo
44+
// notice, an autosave) silently queues out a critical one behind it, so
45+
// "your session expired" or "that didn't save" could never appear.
46+
limit={3}
4447
transition={Slide}
4548
/>
4649
);

0 commit comments

Comments
 (0)