Skip to content

Commit 57f0e40

Browse files
committed
fix(frontend): clear yoke from session route writes
Date filters can be cleared while AnalyticsPage is unmounted, such as from session detail. In that path the page-level clear effect cannot run, so the stored yoke range can survive and later reseed the cleared dates when the analytics view remounts. Clear the stored yoke from the shared sessions route writeback whenever date/window intent is removed from the URL params. This keeps sidebar clears, detail-route clears, and the analytics remount path aligned.
1 parent 919cf64 commit 57f0e40

4 files changed

Lines changed: 71 additions & 0 deletions

File tree

frontend/src/App.svelte

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,15 @@
3333
import { starred } from "./lib/stores/starred.svelte.js";
3434
import { pins } from "./lib/stores/pins.svelte.js";
3535
import { settings } from "./lib/stores/settings.svelte.js";
36+
import { yokedDates } from "./lib/stores/yokedDates.svelte.js";
3637
import { setAuthToken, getAuthToken, setServerUrl, getBase } from "./lib/api/runtime.js";
3738
import { setupVisibilityHealthCheck } from "./lib/utils/health.js";
3839
import { registerShortcuts } from "./lib/utils/keyboard.js";
3940
import { shouldAutoSwitchTranscriptModeToNormal } from "./lib/utils/transcript-mode.js";
4041
import {
4142
filterParamsEqual,
4243
hasFilterParams,
44+
sessionDateIntentCleared,
4345
sessionRouteParamsForDetailExit,
4446
sessionRouteParamsForFilters,
4547
} from "./lib/stores/sessionRouteParams.js";
@@ -228,6 +230,14 @@
228230
messageListRef?.scrollToOrdinal(ordinal);
229231
}
230232
233+
function clearYokeForClearedSessionDates(
234+
nextParams: Record<string, string>,
235+
): void {
236+
if (sessionDateIntentCleared(router.params, nextParams)) {
237+
yokedDates.clear();
238+
}
239+
}
240+
231241
let lastDetailFilterParamsSignature: string | null = $state(null);
232242
233243
// React to route changes: reload sessions and apply URL params.
@@ -315,11 +325,13 @@
315325
lastDetailFilterParamsSignature !== filterParamsSignature &&
316326
!filterParamsEqual(router.params, nextParams)
317327
) {
328+
clearYokeForClearedSessionDates(nextParams);
318329
router.replaceParams(nextParams);
319330
}
320331
lastDetailFilterParamsSignature = filterParamsSignature;
321332
return;
322333
}
334+
clearYokeForClearedSessionDates(nextParams);
323335
router.navigateToSession(activeId, nextParams);
324336
lastDetailFilterParamsSignature = filterParamsSignature;
325337
} else {
@@ -339,6 +351,7 @@
339351
filterParams,
340352
router.params,
341353
);
354+
clearYokeForClearedSessionDates(nextParams);
342355
router.navigateFromSession(nextParams);
343356
lastDetailFilterParamsSignature = null;
344357
}
@@ -360,6 +373,7 @@
360373
if (route !== "sessions") return;
361374
if (router.sessionId) return;
362375
if (filterParamsEqual(router.params, newParams)) return;
376+
clearYokeForClearedSessionDates(newParams);
363377
router.replaceParams(newParams);
364378
});
365379
});

frontend/src/App.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,28 @@ describe("App session URL date state", () => {
106106
);
107107
});
108108

109+
it("clears stored yoke when session date params are removed while analytics is unmounted", () => {
110+
const syncUrlBlock = appSourceSlice(
111+
"// Sync active session to URL.",
112+
"\n\n // URL write-back",
113+
);
114+
const writeBackBlock = appSourceSlice(
115+
"// URL write-back",
116+
"\n\n function showAbout",
117+
);
118+
119+
expect(source).toContain("import { yokedDates");
120+
expect(source).toContain("function clearYokeForClearedSessionDates");
121+
expect(source).toContain("sessionDateIntentCleared(");
122+
expect(source).toContain("yokedDates.clear();");
123+
expect(syncUrlBlock).toContain(
124+
"clearYokeForClearedSessionDates(nextParams);",
125+
);
126+
expect(writeBackBlock).toContain(
127+
"clearYokeForClearedSessionDates(newParams);",
128+
);
129+
});
130+
109131
it("clears detail filter signatures outside session detail routes", () => {
110132
const syncUrlBlock = appSourceSlice(
111133
"// Sync active session to URL.",

frontend/src/lib/stores/sessionRouteParams.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { describe, expect, it } from "vite-plus/test";
22
import {
33
SESSION_FILTER_KEYS,
4+
sessionDateIntentCleared,
45
sessionRouteParamsForDetailExit,
56
sessionRouteParamsForFilters,
67
} from "./sessionRouteParams.js";
@@ -102,6 +103,25 @@ describe("session route params", () => {
102103
expect(params).toEqual({ project: "agentsview" });
103104
});
104105

106+
it("detects removed session date intent", () => {
107+
expect(sessionDateIntentCleared(
108+
{
109+
date_from: "2026-05-21",
110+
date_to: "2026-06-20",
111+
window_days: "30",
112+
},
113+
{ project: "agentsview" },
114+
)).toBe(true);
115+
expect(sessionDateIntentCleared(
116+
{ window_days: "30" },
117+
{ window_days: "30", project: "agentsview" },
118+
)).toBe(false);
119+
expect(sessionDateIntentCleared(
120+
{ project: "agentsview" },
121+
{ project: "agentsview" },
122+
)).toBe(false);
123+
});
124+
105125
it("prefers direct detail URL params over saved filters on exit", () => {
106126
const params = sessionRouteParamsForDetailExit(
107127
{ project: "saved", agent: "codex" },

frontend/src/lib/stores/sessionRouteParams.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,21 @@ function hasFixedSessionDateParams(
3131
return !!params["date"] || !!params["date_from"] || !!params["date_to"];
3232
}
3333

34+
function hasSessionDateIntent(
35+
params: Record<string, string>,
36+
): boolean {
37+
return hasFixedSessionDateParams(params) ||
38+
!!params[SESSION_ANALYTICS_WINDOW_PARAM];
39+
}
40+
41+
export function sessionDateIntentCleared(
42+
currentParams: Record<string, string>,
43+
nextParams: Record<string, string>,
44+
): boolean {
45+
return hasSessionDateIntent(currentParams) &&
46+
!hasSessionDateIntent(nextParams);
47+
}
48+
3449
function isValidWindowDaysParam(raw: string | undefined): raw is string {
3550
if (!raw) return false;
3651
const n = Number.parseInt(raw, 10);

0 commit comments

Comments
 (0)