From ec0e4dfa45fd7d6c424cfdbcb934eb91a1c7d453 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 14 Jun 2026 21:38:02 +0200 Subject: [PATCH] fix(timeline): track the meta-line counts to the filtered view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The /zeitstrahl header sub-line counted the unfiltered timeline, so a hidden layer (e.g. Letters off) still showed its entries in the totals ("1 Brief" with no letters on screen) — the documented D1 limitation. Derive the meta from filteredTimeline so the range and letter/event counts always match what is actually rendered. hasContent stays on the full timeline so the filter bar and meta line still appear whenever the archive has content. Refs #780 Co-Authored-By: Claude Opus 4.8 --- frontend/src/routes/zeitstrahl/+page.svelte | 10 ++++++---- .../src/routes/zeitstrahl/page.svelte.spec.ts | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/frontend/src/routes/zeitstrahl/+page.svelte b/frontend/src/routes/zeitstrahl/+page.svelte index 73ecb287..7126a5ba 100644 --- a/frontend/src/routes/zeitstrahl/+page.svelte +++ b/frontend/src/routes/zeitstrahl/+page.svelte @@ -8,14 +8,11 @@ import type { PageData } from './$types'; let { data }: { data: PageData } = $props(); -const meta = $derived(timelineMeta(data.timeline)); const hasContent = $derived(data.timeline.years.length > 0 || data.timeline.undated.length > 0); // Layer-filter state (#780). Layer hiding is client-side only — the whole // timeline is loaded once by #779's SSR load and we derive a filtered view of -// it here; there is no goto, no URL param, and no extra fetch. Known limitation -// (D1): the meta-line counts above stay on the unfiltered timeline, so they -// include entries the active toggles hide. +// it here; there is no goto, no URL param, and no extra fetch. let personalOn = $state(true); let historicalOn = $state(true); let lettersOn = $state(true); @@ -27,6 +24,11 @@ const filteredEmpty = $derived( filteredTimeline.years.length === 0 && filteredTimeline.undated.length === 0 ); +// Meta-line figures track the *filtered* view, so the header counts always +// match what is actually on screen once layers are toggled off (#780 — this +// closes the prior D1 limitation, where the counts stayed on the full timeline). +const meta = $derived(timelineMeta(filteredTimeline)); + function resetFilters() { personalOn = true; historicalOn = true; diff --git a/frontend/src/routes/zeitstrahl/page.svelte.spec.ts b/frontend/src/routes/zeitstrahl/page.svelte.spec.ts index d3c03ce3..3266c171 100644 --- a/frontend/src/routes/zeitstrahl/page.svelte.spec.ts +++ b/frontend/src/routes/zeitstrahl/page.svelte.spec.ts @@ -196,4 +196,20 @@ describe('/zeitstrahl layer filter (#780)', () => { await page.getByTestId('timeline-filter-empty-reset').click(); await expect.element(page.getByText('Brief Eins')).toBeVisible(); }); + + it('recomputes the meta-line counts from the filtered view, so a hidden layer drops out of the totals (#780, resolves D1)', async () => { + render(Page, { data: pageData(mixed()) }); + const meta = page.getByTestId('timeline-meta'); + // all layers on → the one letter and the two events are counted + await expect.element(meta).toHaveTextContent(m.timeline_letters_count_singular()); + await expect.element(meta).toHaveTextContent(m.timeline_events_count({ count: 2 })); + + await openBar(); + await page.getByTestId('timeline-filter-letters').click(); + + // the hidden letter leaves the count instead of lingering as "1 Brief"; + // the event total is untouched + await expect.element(meta).not.toHaveTextContent(m.timeline_letters_count_singular()); + await expect.element(meta).toHaveTextContent(m.timeline_events_count({ count: 2 })); + }); });