Skip to content

Commit b53ffb8

Browse files
committed
fix(frontend): restore dates on session detail entry
A direct session detail route initializes the same filtered sidebar as the Sessions dashboard. Apply explicit or linked date state at the route-entry boundary so the first sidebar request cannot escape the selected range. VALID (fixed): #1 -- restore Sessions dates before loading a direct detail route.
1 parent d440054 commit b53ffb8

3 files changed

Lines changed: 105 additions & 2 deletions

File tree

frontend/e2e/session-list.spec.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,38 @@ test.describe("Session list", () => {
106106
expect(requestUrl.searchParams.get("date_from")).toBe(expectedFrom);
107107
expect(requestUrl.searchParams.get("date_to")).toBe(expectedTo);
108108
});
109+
110+
test("linked dates reach the first request on direct detail entry", async ({
111+
page,
112+
}) => {
113+
const sessionId = await sp.sessionItems.first().getAttribute(
114+
"data-session-id",
115+
);
116+
expect(sessionId).toBeTruthy();
117+
118+
await page.locator(".kit-date-range-picker__trigger").click();
119+
await page.getByRole("button", { name: "90d", exact: true }).click();
120+
await expect(page).toHaveURL(/window_days=90/);
121+
const selectedUrl = new URL(page.url());
122+
const expectedFrom = selectedUrl.searchParams.get("date_from");
123+
const expectedTo = selectedUrl.searchParams.get("date_to");
124+
125+
await page.getByRole("button", { name: "Settings" }).click();
126+
await page
127+
.getByRole("checkbox", { name: "Link date ranges across pages" })
128+
.check();
129+
130+
const requestPromise = page.waitForRequest((request) =>
131+
new URL(request.url()).pathname.endsWith(
132+
"/api/v1/sessions/sidebar-index",
133+
)
134+
);
135+
await page.goto(`/sessions/${encodeURIComponent(sessionId!)}`);
136+
const requestUrl = new URL((await requestPromise).url());
137+
138+
expect(requestUrl.searchParams.get("date_from")).toBe(expectedFrom);
139+
expect(requestUrl.searchParams.get("date_to")).toBe(expectedTo);
140+
await expect(page).toHaveURL(/date_from=/);
141+
await expect(page).toHaveURL(/date_to=/);
142+
});
109143
});

frontend/src/App.svelte

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,10 +344,14 @@
344344
untrack(() => {
345345
previousDateRestoreRoute = route;
346346
const sid = router.sessionId;
347-
if (!sid && route === "sessions" && hasFilterParams(params)) {
347+
if (
348+
route === "sessions" &&
349+
hasFilterParams(params) &&
350+
(!sid || enteringSessions)
351+
) {
348352
sessions.initFromParams(params);
349353
}
350-
if (!sid && enteringSessions) {
354+
if (enteringSessions) {
351355
const entryParams = sessionEntryDateParams(params);
352356
if (entryParams) router.replaceParams(entryParams);
353357
}

frontend/src/App.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,71 @@ describe("findUserPromptOrdinal", () => {
292292
});
293293

294294
describe("App analytics date navigation", () => {
295+
it("applies an enabled shared range when entering session detail", async () => {
296+
const sessionLoadDates: Array<{
297+
dateFrom: string;
298+
dateTo: string;
299+
}> = [];
300+
vi.stubGlobal(
301+
"ResizeObserver",
302+
class {
303+
observe() {}
304+
unobserve() {}
305+
disconnect() {}
306+
},
307+
);
308+
vi.spyOn(settings, "load").mockResolvedValue();
309+
vi.spyOn(starred, "load").mockResolvedValue();
310+
vi.spyOn(sync, "loadStatus").mockResolvedValue();
311+
vi.spyOn(sync, "loadStats").mockResolvedValue();
312+
vi.spyOn(sync, "loadVersion").mockResolvedValue();
313+
vi.spyOn(sync, "checkForUpdate").mockResolvedValue();
314+
vi.spyOn(sync, "startPolling").mockImplementation(() => {});
315+
vi.spyOn(sync, "watchSession").mockImplementation(() => {});
316+
vi.spyOn(sessions, "load").mockImplementation(() => {
317+
sessionLoadDates.push({
318+
dateFrom: sessions.filters.dateFrom,
319+
dateTo: sessions.filters.dateTo,
320+
});
321+
return Promise.resolve();
322+
});
323+
vi.spyOn(sessions, "loadProjects").mockResolvedValue();
324+
vi.spyOn(sessions, "loadAgents").mockResolvedValue();
325+
vi.spyOn(sessions, "attachSidebar").mockReturnValue(() => {});
326+
vi.spyOn(sessions, "navigateToSession").mockImplementation(async (id) => {
327+
sessions.activeSessionId = id;
328+
});
329+
vi.spyOn(analytics, "fetchAll").mockResolvedValue();
330+
vi.spyOn(analytics, "fetchSignalsForInsights").mockResolvedValue();
331+
vi.spyOn(insights, "load").mockResolvedValue();
332+
333+
window.history.replaceState(null, "", "/insights");
334+
router.route = "insights";
335+
router.params = {};
336+
router.sessionId = null;
337+
yokedDates.setEnabled(true);
338+
yokedDates.updateFromPanel({
339+
from: "2026-05-01",
340+
to: "2026-05-31",
341+
mode: "fixed",
342+
});
343+
344+
component = mount(App, { target: document.body });
345+
await flushEffects();
346+
sessionLoadDates.length = 0;
347+
348+
router.navigateToSession("session-1");
349+
await flushEffects();
350+
351+
expect(sessionLoadDates[0]).toEqual({
352+
dateFrom: "2026-05-01",
353+
dateTo: "2026-05-31",
354+
});
355+
expect(router.sessionId).toBe("session-1");
356+
expect(router.params.date_from).toBe("2026-05-01");
357+
expect(router.params.date_to).toBe("2026-05-31");
358+
});
359+
295360
it("applies an enabled shared range to the first Sessions load", async () => {
296361
const sessionLoadFilters: Array<{
297362
project: string;

0 commit comments

Comments
 (0)