feat(timeline): layer filter (Personal / Historical / Letters) for /zeitstrahl #843

Merged
marcel merged 8 commits from feat/issue-780-timeline-layer-filter into main 2026-06-14 22:11:39 +02:00
2 changed files with 22 additions and 4 deletions
Showing only changes of commit 096b4a0f4a - Show all commits

View File

@@ -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;

View File

@@ -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 }));
});
});