From 8cd6cf3c062dfc210e507f3a5f7349819af1a959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 11 Oct 2026 13:40:19 +0200 Subject: [PATCH] feat(timeline): open on three months unless the URL names a zoom --- e2e/timeline.spec.ts | 34 +++++++++++++++-------- src/components/timeline/timeline-view.tsx | 6 ++-- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/e2e/timeline.spec.ts b/e2e/timeline.spec.ts index 07cf90bc4..e9ec9e2a3 100644 --- a/e2e/timeline.spec.ts +++ b/e2e/timeline.spec.ts @@ -67,6 +67,18 @@ test("is off by default: no navigation entry, a deep link meets the notice", asy await expect(page.locator(navEntry)).toHaveCount(0); }); +test("opens on three months when the URL names no zoom", async ({ page }) => { + await mockTimeline(page, "ready", TODAY); + await page.goto("/settings/modules"); + await setTimelineModule(page, true); + await page.goto("/timeline"); + await expect( + page.locator('[data-slot="timeline-zoom"] [data-value="quarter"]'), + ).toHaveAttribute("aria-checked", "true", { timeout: 20_000 }); + // The next test starts from the module switched off. + await setTimelineModule(page, false); +}); + test("switching it on opens the readiness inventory, which leads to the timeline", async ({ page, }) => { @@ -113,7 +125,7 @@ test("the timeline hands the selected day to the day view through ?day=", async }) => { await setTimelineModuleOn(page); const log = await mockTimeline(page, "full", TODAY); - await page.goto("/timeline"); + await page.goto("/timeline?zoom=all"); const chart = page.locator('[data-slot="timeline-chart"]'); await expect(chart).toBeVisible({ timeout: 20_000 }); @@ -196,7 +208,7 @@ test("value lines: quarterly means, gaps kept, short ones bridged, as many as th }) => { await setTimelineModuleOn(page); const log = await mockTimeline(page, "full", TODAY); - await page.goto("/timeline"); + await page.goto("/timeline?zoom=all"); const sys = page.locator('svg [data-series="BLOOD_PRESSURE_SYS"]'); await expect(sys).toBeVisible({ timeout: 20_000 }); await expect( @@ -346,7 +358,7 @@ test("each mean's dot sits on the middle of its text and never wraps apart from // A narrow chart: the selection bar's means wrap between units only. await page.setViewportSize({ width: 800, height: 900 }); - await page.goto("/timeline"); + await page.goto("/timeline?zoom=all"); await expect(page.locator('[data-slot="timeline-chart"]')).toBeVisible({ timeout: 20_000, }); @@ -360,7 +372,7 @@ test("each mean's dot sits on the middle of its text and never wraps apart from // The phone chronicle's means follow the same rule. await page.setViewportSize({ width: 390, height: 844 }); - await page.goto("/timeline"); + await page.goto("/timeline?zoom=all"); await check("timeline-chronicle-means"); await testInfo.attach("chronicle-means-390", { body: await page.screenshot(), @@ -373,7 +385,7 @@ test("one medication is one row: its doses cut the bar, its pause is a gap in it }) => { await setTimelineModuleOn(page); await mockTimeline(page, "full", TODAY); - await page.goto("/timeline"); + await page.goto("/timeline?zoom=all"); const lane = page.locator('svg [data-lane="medications"]'); await expect(lane).toBeVisible({ timeout: 20_000 }); // The medication's own span is not drawn beside its course. @@ -423,7 +435,7 @@ test("a chosen range lives in the URL: reload keeps it, Back returns to it", asy }) => { await setTimelineModuleOn(page); const log = await mockTimeline(page, "full", TODAY); - await page.goto("/timeline"); + await page.goto("/timeline?zoom=all"); const chart = page.locator('[data-slot="timeline-chart"]'); await expect(chart).toBeVisible({ timeout: 20_000 }); const desktop = page.locator('[data-slot="timeline-desktop"]'); @@ -555,7 +567,7 @@ test("on a phone the chronicle replaces the chart and opens a day per row", asyn await setTimelineModuleOn(page); await mockTimeline(page, "full", TODAY); await page.setViewportSize({ width: 390, height: 844 }); - await page.goto("/timeline"); + await page.goto("/timeline?zoom=all"); const chronicle = page.locator('[data-slot="timeline-chronicle"]'); await expect(chronicle).toBeVisible({ timeout: 20_000 }); @@ -596,7 +608,7 @@ test("on a phone the chronicle replaces the chart and opens a day per row", asyn expect((await chronicle.boundingBox())!.y).toBe(listTop); await sheet.locator('[data-slot="timeline-range-apply"]').click(); await expect(page).toHaveURL(/[?&]zoom=range&from=\d{4}-\d{2}-\d{2}/); - await page.goto("/timeline"); + await page.goto("/timeline?zoom=all"); await expect(chronicle).toBeVisible({ timeout: 20_000 }); await page @@ -703,7 +715,7 @@ test("a value point reads on its own, by pointer and by keyboard", async ({ await setTimelineModuleOn(page); await mockTimeline(page, "full", TODAY); await page.setViewportSize({ width: 1440, height: 900 }); - await page.goto("/timeline"); + await page.goto("/timeline?zoom=all"); const targets = page.locator('[data-slot="timeline-point-target"]'); await expect(targets.first()).toBeAttached({ timeout: 20_000 }); @@ -868,7 +880,7 @@ test("the day's strip stays at the right edge, opens and closes the day, and nev for (const width of [1280, 1440, 1920]) { await page.setViewportSize({ width, height: 900 }); - await page.goto("/timeline"); + await page.goto("/timeline?zoom=all"); await expect( page.locator('[data-slot="timeline-chart"] > svg'), ).toBeVisible({ timeout: 20_000 }); @@ -929,7 +941,7 @@ test("the day's strip stays at the right edge, opens and closes the day, and nev await page.setViewportSize({ width: 1440, height: 900 }); for (const colorScheme of ["light", "dark"] as const) { await page.emulateMedia({ colorScheme }); - await page.goto("/timeline"); + await page.goto("/timeline?zoom=all"); await toggle.click(); await expect(panel).toHaveAttribute("data-shell", "docked"); await testInfo.attach(`day-strip-1440-${colorScheme}`, { diff --git a/src/components/timeline/timeline-view.tsx b/src/components/timeline/timeline-view.tsx index 0be0e8eec..d375c18b4 100644 --- a/src/components/timeline/timeline-view.tsx +++ b/src/components/timeline/timeline-view.tsx @@ -146,10 +146,10 @@ export function TimelineView() { const canAddLifeEvent = !caps.inSharedRecord; // The zoom and a chosen range live in the URL (`timeline-url.ts`), so Back - // and a bookmark return to them. Without a `zoom`, an incoming `?day=` - // opens three months around that day and anything else the whole record. + // and a bookmark return to them. Without a `zoom` the page opens on three + // months: around an incoming `?day=`, otherwise up to today. const urlState = parseTimelineUrl(searchParams, today); - const zoom: TimelineZoom = urlState.zoom ?? (dayParam ? "quarter" : "all"); + const zoom: TimelineZoom = urlState.zoom ?? "quarter"; const range = zoom === "range" ? urlState.range : null; const [grouping, setGrouping] = useState("month"); // The selection follows `?day=` (Back, Forward, the day panel's previous