diff --git a/frontend/src/lib/timeline/LetterCard.svelte b/frontend/src/lib/timeline/LetterCard.svelte index 593156fc..c56b048c 100644 --- a/frontend/src/lib/timeline/LetterCard.svelte +++ b/frontend/src/lib/timeline/LetterCard.svelte @@ -11,11 +11,31 @@ type TimelineEntryDTO = components['schemas']['TimelineEntryDTO']; * A single archive letter on the timeline: sender → receiver, title, and a * precision-aware date chip, linking to the document. Names/titles are * OCR/import-derived — rendered via default `{...}` escaping with - * `whitespace-pre-line` for line breaks (REQ-021); never the raw-HTML directive. + * `whitespace-pre-line` for line breaks (REQ-010); never the raw-HTML directive. + * + * Inside an event cluster the card sits in the contained event card and renders as + * the `.lcard.ev` `compact` variant (#850, REQ-002): tighter row, and the redundant + * date chip is dropped when the title already embeds the date. The per-letter tag + * chip can be suppressed via `suppressTagChip` for callers that already convey it. */ -let { entry }: { entry: TimelineEntryDTO } = $props(); +let { + entry, + variant = 'plain', + suppressTagChip = false, + compact = false +}: { + entry: TimelineEntryDTO; + variant?: 'plain' | 'event'; + suppressTagChip?: boolean; + compact?: boolean; +} = $props(); +const isEventVariant = $derived(variant === 'event'); const dateLabel = $derived(timelineDateLabel(entry.eventDate, entry.precision, entry.eventDateEnd)); +// Inside an event card the year frames the time, and these archive titles already +// embed the date — so the compact in-card letter drops the redundant date chip when a +// title is present, halving the row height and killing the duplicate date (#850). +const showDate = $derived(!compact || !entry.title); const sender = $derived(entry.senderName === '' ? m.timeline_unknown_person() : entry.senderName); const receiver = $derived( entry.receiverName === '' ? m.timeline_unknown_person() : entry.receiverName @@ -28,28 +48,37 @@ const receiver = $derived( {#if entry.title} - + {entry.title} {/if} - + {sender} {receiver} - {#if dateLabel} + {#if dateLabel && showDate} · {dateLabel} {/if} - {#if entry.rootTagName} + {#if entry.rootTagName && !suppressTagChip} + (#835 §3); absent when the letter has no tag (REQ-006), and suppressed when + the caller already conveys the topic (suppressTagChip). --> {/if} diff --git a/frontend/src/lib/timeline/LetterCard.svelte.spec.ts b/frontend/src/lib/timeline/LetterCard.svelte.spec.ts index b60c0f5f..f7957637 100644 --- a/frontend/src/lib/timeline/LetterCard.svelte.spec.ts +++ b/frontend/src/lib/timeline/LetterCard.svelte.spec.ts @@ -127,3 +127,46 @@ describe('LetterCard', () => { expect(chip?.textContent).toContain('Familie'); }); }); + +describe('LetterCard — event-cluster variants (#850, REQ-002)', () => { + it('carries the .lcard.ev class in the event variant (REQ-002)', () => { + render(LetterCard, { entry: makeEntry(), variant: 'event' }); + expect(document.querySelector('a.lcard.ev')).not.toBeNull(); + }); + + it('is a plain card with no .ev marker by default (REQ-006)', () => { + render(LetterCard, { entry: makeEntry() }); + expect(document.querySelector('a.ev')).toBeNull(); + }); + + it('suppresses the per-letter tag chip when asked, even with a root tag', () => { + render(LetterCard, { + entry: makeEntry({ rootTagName: 'Krieg', rootTagColor: 'sienna' }), + suppressTagChip: true + }); + expect(document.querySelector('[data-testid="tag-chip"]')).toBeNull(); + }); + + it('still shows the per-letter tag chip when not suppressed', () => { + render(LetterCard, { entry: makeEntry({ rootTagName: 'Krieg', rootTagColor: 'sienna' }) }); + expect(document.querySelector('[data-testid="tag-chip"]')).not.toBeNull(); + }); + + it('drops the redundant date line in the compact variant when a title is present (#850)', () => { + // Inside an event card the year already frames the time, and these archive titles + // embed the date — so the compact in-card letter omits the date chip. + render(LetterCard, { entry: makeEntry({ title: 'H-0023 – 6. Juli 1916' }), compact: true }); + expect(document.querySelector('[data-testid="letter-date"]')).toBeNull(); + expect(document.body.textContent).toContain('Karl Raddatz'); // sender still shown + }); + + it('keeps the date in the compact variant when the letter has no title (#850)', () => { + render(LetterCard, { entry: makeEntry({ title: undefined }), compact: true }); + expect(document.querySelector('[data-testid="letter-date"]')).not.toBeNull(); + }); + + it('renders the compact variant on a single tighter row (#850)', () => { + render(LetterCard, { entry: makeEntry(), compact: true }); + expect(document.querySelector('a.lcard.compact')).not.toBeNull(); + }); +});