From 4dcf8e2242bebf9a7899822a7cd76355a0f001ff Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 12 Jun 2026 18:14:20 +0200 Subject: [PATCH] feat(person): render precise life dates on cards, hover card, and mention dropdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cards compose aria-hidden * / † glyphs in markup so screen readers only announce the dates; PersonSummaryDTO list card stays year-shaped by design (ADR-039). MentionDropdown subtitle wraps instead of truncating so DAY-precision ranges fit at 320px. Co-Authored-By: Claude Fable 5 --- .../TranscriptionReadView.svelte.test.ts | 6 +- frontend/src/lib/person/PersonCard.svelte | 12 ++- .../src/lib/person/PersonCard.svelte.test.ts | 21 ++++++ .../src/lib/person/PersonHoverCard.svelte | 24 ++++-- .../lib/person/PersonHoverCard.svelte.spec.ts | 72 ++++++++++++++++-- .../shared/discussion/MentionDropdown.svelte | 14 +++- .../discussion/MentionDropdown.svelte.test.ts | 30 +++++++- .../PersonMentionEditor.svelte.spec.ts | 9 ++- .../src/routes/persons/[id]/PersonCard.svelte | 24 ++++-- .../persons/[id]/PersonCard.svelte.test.ts | 75 ++++++++++++++++++- 10 files changed, 254 insertions(+), 33 deletions(-) diff --git a/frontend/src/lib/document/transcription/TranscriptionReadView.svelte.test.ts b/frontend/src/lib/document/transcription/TranscriptionReadView.svelte.test.ts index ce9f2985..9e2b2d06 100644 --- a/frontend/src/lib/document/transcription/TranscriptionReadView.svelte.test.ts +++ b/frontend/src/lib/document/transcription/TranscriptionReadView.svelte.test.ts @@ -335,8 +335,10 @@ describe('TranscriptionReadView — person-mention rendering', () => { displayName: 'Auguste Raddatz', personType: 'PERSON', familyMember: true, - birthYear: 1882, - deathYear: 1944 + birthDate: '1882-01-01', + birthDatePrecision: 'YEAR', + deathDate: '1944-01-01', + deathDatePrecision: 'YEAR' }) }); }) diff --git a/frontend/src/lib/person/PersonCard.svelte b/frontend/src/lib/person/PersonCard.svelte index f1c38093..8ac098ee 100644 --- a/frontend/src/lib/person/PersonCard.svelte +++ b/frontend/src/lib/person/PersonCard.svelte @@ -1,6 +1,5 @@
@@ -91,10 +97,16 @@ let {

„{person.alias}"

{/if} - - {#if person.birthYear || person.deathYear} + + {#if birthText || deathText}

- {formatLifeDateRange(person.birthYear, person.deathYear)} + {#if birthText} + {birthText} + {/if} + {#if birthText && deathText}–{/if} + {#if deathText} + {deathText} + {/if}

{:else}
diff --git a/frontend/src/routes/persons/[id]/PersonCard.svelte.test.ts b/frontend/src/routes/persons/[id]/PersonCard.svelte.test.ts index b2c7c4fc..efb63660 100644 --- a/frontend/src/routes/persons/[id]/PersonCard.svelte.test.ts +++ b/frontend/src/routes/persons/[id]/PersonCard.svelte.test.ts @@ -82,15 +82,84 @@ describe('PersonCard', () => { await expect.element(page.getByText(/Annerl/)).toBeVisible(); }); - it('renders the life-date range when birthYear or deathYear are present', async () => { + it('renders DAY-precision life dates as full localized dates', async () => { render(PersonCard, { props: { - person: { ...basePerson, birthYear: 1899, deathYear: 1972 }, + person: { + ...basePerson, + birthDate: '1901-03-14', + birthDatePrecision: 'DAY' as const, + deathDate: '1944-11-02', + deathDatePrecision: 'DAY' as const + }, canWrite: false } }); - await expect.element(page.getByText(/1899/)).toBeVisible(); + await expect.element(page.getByText(/14\. März 1901/)).toBeVisible(); + await expect.element(page.getByText(/2\. November 1944/)).toBeVisible(); + }); + + it('wraps the * and † glyphs in aria-hidden spans', async () => { + const { container } = render(PersonCard, { + props: { + person: { + ...basePerson, + birthDate: '1901-03-14', + birthDatePrecision: 'DAY' as const, + deathDate: '1944-11-02', + deathDatePrecision: 'DAY' as const + }, + canWrite: false + } + }); + + const hidden = [...container.querySelectorAll('span[aria-hidden="true"]')].map((el) => + el.textContent?.trim() + ); + expect(hidden).toContain('*'); + expect(hidden).toContain('†'); + }); + + it('renders birth-only without dash or dagger', async () => { + const { container } = render(PersonCard, { + props: { + person: { + ...basePerson, + birthDate: '1901-03-14', + birthDatePrecision: 'DAY' as const + }, + canWrite: false + } + }); + + await expect.element(page.getByText(/14\. März 1901/)).toBeVisible(); + expect(container.textContent).not.toContain('–'); + expect(container.textContent).not.toContain('†'); + }); + + it('renders APPROX-precision legacy dates with the ca. prefix', async () => { + render(PersonCard, { + props: { + person: { + ...basePerson, + birthDate: '1882-01-01', + birthDatePrecision: 'APPROX' as const + }, + canWrite: false + } + }); + + await expect.element(page.getByText(/ca\. 1882/)).toBeVisible(); + }); + + it('renders no life-date line when both dates are missing', async () => { + const { container } = render(PersonCard, { + props: { person: basePerson, canWrite: false } + }); + + expect(container.textContent).not.toContain('*'); + expect(container.textContent).not.toContain('†'); }); it('renders the notes section when notes are provided', async () => {