fix(timeline): give the event-card letter count a screen-reader label

The bare "· {count}" spans in both the same-year and cross-year headers
announced as "· 2" with no context. Each now pairs the aria-hidden visible
count with an sr-only "{count} Briefe" via a new Paraglide key
(timeline_cluster_letter_count, present in de/en/es). Fixes review
finding #6 (count half).

Refs #850
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-15 22:23:24 +02:00
parent 30384fa53b
commit a68f7ee527
6 changed files with 30 additions and 2 deletions

View File

@@ -100,6 +100,14 @@ describe('message key parity', () => {
expect(es).toMatchObject({ timeline_tag_chip_label: 'Tema' });
});
// #850 finding #6: the event-card letter count carries an sr-only "{count} Briefe" so the
// bare "· 2" never announces to a screen reader without context.
it('zeitstrahl cluster letter-count key is present in all locales (#850 finding #6)', () => {
expect(de).toHaveProperty('timeline_cluster_letter_count');
expect(en).toHaveProperty('timeline_cluster_letter_count');
expect(es).toHaveProperty('timeline_cluster_letter_count');
});
// #780 REQ-010: the layer-filter strings are Paraglide keys in every locale.
// timeline_filter_trigger (0 active) and timeline_filter_trigger_active ({count},
// ≥1 active) are distinct keys so the trigger never reads "Filter (0 aktiv)".

View File

@@ -86,7 +86,11 @@ const hiddenCount = $derived(letters.length - CLUSTER_PREVIEW);
>{event.title}</span
>
<span class="block font-sans text-xs text-ink-3">
{eventSubtitle} <span data-testid="event-count">· {count}</span>
{eventSubtitle}
<span data-testid="event-count">
<span aria-hidden="true">· {count}</span>
<span class="sr-only">{m.timeline_cluster_letter_count({ count })}</span>
</span>
</span>
</span>
{#if canEdit}
@@ -111,7 +115,10 @@ const hiddenCount = $derived(letters.length - CLUSTER_PREVIEW);
<GlyphLabel glyph="✉" label={m.timeline_letter_glyph_label()} />
{title}
</span>
<span data-testid="event-count" class="font-sans text-xs text-ink-3">· {count}</span>
<span data-testid="event-count" class="font-sans text-xs text-ink-3">
<span aria-hidden="true">· {count}</span>
<span class="sr-only">{m.timeline_cluster_letter_count({ count })}</span>
</span>
</header>
{/if}

View File

@@ -108,6 +108,16 @@ describe('EventCluster — contained event card (#850)', () => {
expect(srOnly?.textContent).toBe(m.timeline_letter_glyph_label());
});
it('gives the letter count an sr-only "{count} Briefe" label so "· 2" is not announced bare (finding #6)', () => {
render(EventCluster, { letters: letters(2), title: 'Briefe von der Front' });
const count = document.querySelector('[data-testid="event-count"]') as HTMLElement;
// the visible "· 2" stays aria-hidden; the sr-only sibling carries the meaning
expect(count.querySelector('[aria-hidden="true"]')?.textContent).toContain('· 2');
expect(count.querySelector('.sr-only')?.textContent).toBe(
m.timeline_cluster_letter_count({ count: 2 })
);
});
it('renders an HTML-bearing event title verbatim as text, never as markup (REQ-010)', () => {
render(EventCluster, {
letters: letters(1),