feat(documents): badge undated rows instead of a bare em-dash

DocumentRow rendered a bare em-dash for null-dated letters — a glyph a
screen reader announces as nothing. Both breakpoints now render the single
DocumentDate component unconditionally (no {#if}/—/{:else}), so the cue
cannot drift; its unknown state is a neutral metadata chip ("Datum
unbekannt", text-ink-3, ≥4.5:1 both themes) with a non-color calendar glyph,
never red/amber. Present dates render at honest precision via
formatDocumentDate ("Juni 1916", not a fabricated day).

Refs #668
This commit is contained in:
Marcel
2026-05-27 18:48:45 +02:00
parent f1fc3dc1ce
commit bca3f34cec
3 changed files with 56 additions and 19 deletions

View File

@@ -28,13 +28,21 @@ const showRawLine = $derived(
</script>
<span class="inline-flex flex-col">
<span class="inline-flex items-center gap-1">
{#if isUnknown}
<!-- Non-color cue (WCAG 1.4.1): a calendar-with-question glyph. The visible
"Datum unbekannt" text is the redundant textual cue, so the icon is
decorative and hidden from assistive tech (per Leonie's a11y note). -->
{#if isUnknown}
<!--
Neutral metadata chip (#668): an undated letter is an absence, NOT an error,
so the chip is neutral (text-ink-3 on bg-surface, ≥4.5:1 in both themes) —
never red/amber. Matches the archive-metadata chip pattern in the row. The
non-color cue (WCAG 1.4.1) is the calendar-with-question glyph; the visible
"Datum unbekannt" text is the redundant textual cue and IS the accessibility,
so it is announced inline (never aria-hidden) while the icon is decorative.
-->
<span
data-testid="undated-badge"
class="inline-flex items-center gap-1 rounded border border-line px-1.5 py-0.5 font-sans text-[10px] tracking-widest text-ink-3 uppercase"
>
<svg
class="h-3.5 w-3.5 shrink-0 text-ink-3"
class="h-3 w-3 shrink-0 text-ink-3"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
@@ -48,9 +56,13 @@ const showRawLine = $derived(
<path d="M9 16a1.5 1.5 0 0 1 3 0c0 1-1.5 1.2-1.5 2.2" />
<path d="M10.5 21h.01" />
</svg>
{/if}
<span>{label}</span>
</span>
{label}
</span>
{:else}
<span class="inline-flex items-center gap-1">
<span>{label}</span>
</span>
{/if}
{#if showRawLine}
<!-- Visible secondary line (WCAG 1.4.13 — not tooltip-only). raw is untrusted
verbatim spreadsheet text; rendered via default Svelte interpolation, which

View File

@@ -168,16 +168,12 @@ function safeTagColor(color: string | null | undefined): string {
document DETAIL page, never in list/search rows — list rows surface only the
honest label to keep scan-rows compact. showRaw={false} enforces this; the
DocumentListItem payload also intentionally omits metaDateRaw. -->
{#if doc.documentDate}
<DocumentDate
iso={doc.documentDate}
precision={doc.metaDatePrecision}
end={doc.metaDateEnd}
showRaw={false}
/>
{:else}
{/if}
<DocumentDate
iso={doc.documentDate}
precision={doc.metaDatePrecision}
end={doc.metaDateEnd}
showRaw={false}
/>
</div>
<div class="flex items-start gap-2">
<ProgressRing percentage={item.completionPercentage} />

View File

@@ -73,6 +73,35 @@ describe('DocumentRow title', () => {
});
});
// ─── Date rendering (#668) ──────────────────────────────────────────────────
describe('DocumentRow date rendering', () => {
it('renders a "Datum unbekannt" badge for an undated document', async () => {
const item = makeItem({ documentDate: undefined, metaDatePrecision: 'UNKNOWN' });
render(DocumentRow, { item });
// The badge text appears (once per breakpoint block).
await expect.element(page.getByText('Datum unbekannt').first()).toBeInTheDocument();
});
it('does not render a bare em-dash for an undated document', async () => {
const item = makeItem({ documentDate: undefined, metaDatePrecision: 'UNKNOWN' });
render(DocumentRow, { item });
await expect.element(page.getByText('—', { exact: true }).first()).not.toBeInTheDocument();
});
it('renders the full date for a day-precision document', async () => {
const item = makeItem({ documentDate: '1943-12-24', metaDatePrecision: 'DAY' });
render(DocumentRow, { item });
await expect.element(page.getByText(/24\. Dezember 1943/).first()).toBeInTheDocument();
});
it('renders month precision honestly without fabricating a day', async () => {
const item = makeItem({ documentDate: '1916-06-01', metaDatePrecision: 'MONTH' });
render(DocumentRow, { item });
await expect.element(page.getByText(/Juni 1916/).first()).toBeInTheDocument();
});
});
// ─── Snippet ──────────────────────────────────────────────────────────────────
describe('DocumentRow snippet', () => {