fix(timeline): render undated events as pills/bands, not letter cards
The undated bucket is assembled from all entries, so it can contain events as well as letters. Rendering every undated entry with LetterCard produced a dead /documents/undefined link and "Unknown -> Unknown" for events. Dispatch on kind/type like YearBand does (WorldBand/EventPill/ LetterCard). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,9 @@ import * as m from '$lib/paraglide/messages.js';
|
|||||||
import YearBand from './YearBand.svelte';
|
import YearBand from './YearBand.svelte';
|
||||||
import GapSpan from './GapSpan.svelte';
|
import GapSpan from './GapSpan.svelte';
|
||||||
import LetterCard from './LetterCard.svelte';
|
import LetterCard from './LetterCard.svelte';
|
||||||
|
import EventPill from './EventPill.svelte';
|
||||||
|
import WorldBand from './WorldBand.svelte';
|
||||||
|
import { entryKey } from './entryKey';
|
||||||
import type { components } from '$lib/generated/api';
|
import type { components } from '$lib/generated/api';
|
||||||
|
|
||||||
type TimelineDTO = components['schemas']['TimelineDTO'];
|
type TimelineDTO = components['schemas']['TimelineDTO'];
|
||||||
@@ -59,8 +62,22 @@ const isEmpty = $derived(timeline.years.length === 0 && timeline.undated.length
|
|||||||
<section data-testid="undated-section" class="mx-auto mt-8 max-w-3xl">
|
<section data-testid="undated-section" class="mx-auto mt-8 max-w-3xl">
|
||||||
<h2 class="mb-3 font-serif text-sm font-bold text-ink-2">{m.timeline_undated_section()}</h2>
|
<h2 class="mb-3 font-serif text-sm font-bold text-ink-2">{m.timeline_undated_section()}</h2>
|
||||||
<ul class="space-y-2">
|
<ul class="space-y-2">
|
||||||
{#each timeline.undated as entry (entry.documentId ?? entry.title)}
|
<!-- The undated bucket is filtered from ALL entries, so it can hold
|
||||||
<li><LetterCard entry={entry} /></li>
|
events as well as letters. Dispatch on kind/type exactly like
|
||||||
|
YearBand — an event rendered as a LetterCard would link to
|
||||||
|
/documents/undefined and read "Unknown → Unknown" (REQ-007/008/009). -->
|
||||||
|
{#each timeline.undated as entry (entryKey(entry))}
|
||||||
|
<li>
|
||||||
|
{#if entry.kind === 'EVENT'}
|
||||||
|
{#if entry.type === 'HISTORICAL'}
|
||||||
|
<WorldBand entry={entry} />
|
||||||
|
{:else}
|
||||||
|
<EventPill entry={entry} />
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<LetterCard entry={entry} />
|
||||||
|
{/if}
|
||||||
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -89,6 +89,70 @@ describe('TimelineView', () => {
|
|||||||
expect(document.querySelector('[data-testid="undated-section"]')).not.toBeNull();
|
expect(document.querySelector('[data-testid="undated-section"]')).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders an undated curated EVENT as a pill, not a broken letter card (REQ-008/016)', () => {
|
||||||
|
render(TimelineView, {
|
||||||
|
timeline: makeTimelineDTO({
|
||||||
|
undated: [
|
||||||
|
makeEntry({
|
||||||
|
kind: 'EVENT',
|
||||||
|
type: 'PERSONAL',
|
||||||
|
derived: false,
|
||||||
|
eventId: 'e1',
|
||||||
|
precision: 'UNKNOWN',
|
||||||
|
eventDate: undefined,
|
||||||
|
title: 'Auswanderung',
|
||||||
|
senderName: '',
|
||||||
|
receiverName: '',
|
||||||
|
documentId: undefined
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
// The event renders inside the undated section…
|
||||||
|
expect(document.querySelector('[data-testid="undated-section"]')).not.toBeNull();
|
||||||
|
expect(document.body.textContent).toContain('Auswanderung');
|
||||||
|
// …as an EventPill (its edit affordance), never as a letter card linking
|
||||||
|
// to /documents/undefined with "Unbekannt → Unbekannt".
|
||||||
|
expect(document.querySelector('[data-testid="event-edit"]')).not.toBeNull();
|
||||||
|
expect(document.querySelector('a[href="/documents/undefined"]')).toBeNull();
|
||||||
|
expect(document.body.textContent).not.toContain('Unbekannt');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders an undated HISTORICAL EVENT as a world band, not a letter card (REQ-009/016)', () => {
|
||||||
|
render(TimelineView, {
|
||||||
|
timeline: makeTimelineDTO({
|
||||||
|
undated: [
|
||||||
|
makeEntry({
|
||||||
|
kind: 'EVENT',
|
||||||
|
type: 'HISTORICAL',
|
||||||
|
derived: false,
|
||||||
|
precision: 'UNKNOWN',
|
||||||
|
eventDate: undefined,
|
||||||
|
title: 'Weltwirtschaftskrise',
|
||||||
|
senderName: '',
|
||||||
|
receiverName: '',
|
||||||
|
documentId: undefined
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
expect(document.querySelector('[data-testid="undated-section"]')).not.toBeNull();
|
||||||
|
expect(document.body.textContent).toContain('Weltwirtschaftskrise');
|
||||||
|
// HISTORICAL → WorldBand carries the sr-only "Weltgeschehen" cue (REQ-018),
|
||||||
|
// not a broken document link.
|
||||||
|
expect(document.body.textContent).toContain('Weltgeschehen');
|
||||||
|
expect(document.querySelector('a[href="/documents/undefined"]')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('still renders an undated LETTER as a letter card (REQ-016)', () => {
|
||||||
|
render(TimelineView, {
|
||||||
|
timeline: makeTimelineDTO({
|
||||||
|
undated: [makeEntry({ precision: 'UNKNOWN', eventDate: undefined, documentId: 'u1' })]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
expect(document.querySelector('a[href="/documents/u1"]')).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it('renders two derived events in one band without key collision (no-double-null-key)', () => {
|
it('renders two derived events in one band without key collision (no-double-null-key)', () => {
|
||||||
const a = makeEntry({
|
const a = makeEntry({
|
||||||
kind: 'EVENT',
|
kind: 'EVENT',
|
||||||
|
|||||||
Reference in New Issue
Block a user