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:
@@ -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
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user