diff --git a/docs/specs/enrichment-list-block-spec.html b/docs/specs/enrichment-list-block-spec.html new file mode 100644 index 00000000..40dc858e --- /dev/null +++ b/docs/specs/enrichment-list-block-spec.html @@ -0,0 +1,577 @@ + + +
+ + +A dedicated list-block on the dashboard, placed between the Resume strip and the MissionControlStrip, surfaces the documents that were just uploaded and still need metadata. It replaces the orphaned DashboardNeedsMetadata.svelte component with a spec'd, senior-friendly row design.
Why here, not a 4th column in MissionControlStrip: (1) strip is already at 3-column visual capacity; (2) batch-upload reality (10–15 docs arriving at once) makes a count tile useless — seniors need to see which docs are waiting; (3) placing it directly under the DropZone sightline makes upload→enrich the most visually-coupled pair on the page.
+Scope of this spec: the block itself (layout, row anatomy, empty/loading/error states, a11y, responsive behavior at 320/768/1440). The post-upload success banner is included as a coupled interaction. The strip redesign, pagination, and the other three pipeline stages (segment / transcribe / review) are not in scope — they earn their own specs.
+The block is a single <section> landmark with an eyebrow heading, a divided list of up to 5 rows, and an optional footer link when more than 5 docs are pending. At incompleteDocs.length === 0 the block renders nothing — no "all clear" state, no empty card. A clean dashboard means no work to do.
/enrich. This caps block height so the dashboard stays navigable, and reinforces /enrich as the canonical "long queue" page.The entire row is one <a href="/enrich/{id}">. No nested interactive elements — one tap, one destination. Touch target: 72px minimum row height on mobile, 64px on desktop. Both exceed the 48px WCAG 2.2 AA floor for the senior audience.
Visual differentiation between PDF / JPG / PNG / TIF. Not decorative — gives seniors a scannable category cue without reading. Uses redundant color + text ("PDF", "JPG") so it passes WCAG 1.4.1.
+Title in font-serif text-base (16px mobile) / text-lg (18px desktop). Truncated with text-ellipsis on narrow viewports. Relative time ("vor 2 Min.") in font-sans text-xs text-ink-2.
Navigation affordance. aria-hidden="true". Becomes slightly more prominent on hover (opacity-30 → 70).
Hover: bg-brand-mint/10 wash. Focus-visible: ring-2 ring-brand-navy ring-offset-2 inside the row (visible against row bg). Active: bg-brand-mint/20.
IncompleteDocumentDTO only carries id and title. To render "vor 2 Min." and the file-type badge, add uploadedAt: Instant and mimeType: String. Small, cheap backend change. Without these fields the block still works (skip meta line, use a generic doc icon) but the senior-facing UX is meaningfully worse.length === 0)Render null. The block disappears entirely. Seniors don't need a "nothing to do" card — absence is clear.
On initial dashboard SSR this data comes in with the page load — no spinner needed. If you later add client-side refresh, use a skeleton (3 rows of gray blocks at 72px height, animate-pulse, respects prefers-reduced-motion).
If the /api/documents/incomplete call fails, render the block in a muted error state: eyebrow reads "Liste konnte nicht geladen werden", with a retry link. Do not suppress — the user needs to know their queue may be out of date.
Transient success banner above the block. Auto-dismiss after 8s, with a manual close X. Content: "12 Dokumente hochgeladen" + "Jetzt ergänzen →" CTA → scrolls/focuses the list block. Use aria-live="polite" so screen readers announce the count.
/enrich/{firstId} is disorienting. The banner gives them the moment without taking control away. The list block becomes the persistent landing spot for every return visit.| Breakpoint | Row height | Title size | Meta visibility | Padding |
|---|---|---|---|---|
| 320px | +72px | +text-base (16px) | +Relative time only | +px-3 py-3 | +
| 768px | +72px | +text-lg (18px) | +Time + page count | +px-5 py-4 | +
| 1440px | +64px | +text-lg (18px) | +Time + page count | +px-6 py-4 | +
1fr minus the DropZone sidebar — not the full viewport. At 1440px total width, the block lands around 900–960px wide.Rewire and extend frontend/src/lib/components/DashboardNeedsMetadata.svelte. The component already exists and is correctly typed — the changes are: new row anatomy, file-type icon, relative time, 5-item cap with footer, a11y landmark, and focus/hover states.
| Region | Tailwind | Pixel values | Notes |
|---|---|---|---|
| Section wrapper | +<section aria-labelledby="enrich-heading" class="mb-6"> |
+ margin-bottom 24px | +Landmark for SR nav | +
| Eyebrow row | +flex items-center justify-between mb-3 px-1 |
+ mb 12px | +Title + count badge | +
| Eyebrow heading | +text-xs font-bold uppercase tracking-widest text-gray-500 |
+ 12px / 700 | +Not gray-400 — must pass AA on sand bg | +
| Count badge | +bg-brand-navy text-white text-xs font-bold px-2 py-0.5 rounded-full |
+ 12px / 700 | +Use aria-live="polite" |
+
| List container | +bg-white border border-line rounded-sm shadow-sm overflow-hidden |
+ border 1px, radius 2px | +Matches card pattern | +
| List element | +<ol class="divide-y divide-line-2"> |
+ divider 1px | +Ordered — upload time is the order | +
| Row link | +group flex items-center gap-3 px-3 py-3 md:px-5 md:py-4 min-h-[72px] lg:min-h-[64px] hover:bg-brand-mint/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-navy focus-visible:ring-inset transition-colors |
+ min-height 72px mobile / 64px desktop | +Whole row is the <a> | +
| File-type badge | +shrink-0 w-5 h-5 md:w-6 md:h-6 rounded-sm flex items-center justify-center text-[10px] font-bold |
+ 20×20px mobile / 24×24 desktop | +Color per mime: red/PDF, green/JPG|PNG, purple/TIF | +
| Title | +font-serif text-base md:text-lg text-ink group-hover:underline truncate |
+ 16px mobile / 18px desktop | +Single-line truncate | +
| Meta line | +font-sans text-xs text-ink-2 mt-0.5 |
+ 12px | +"vor N Min./Std." + optional "· N Seiten" | +
| Chevron | +shrink-0 w-5 h-5 opacity-30 group-hover:opacity-70 transition-opacity |
+ 20×20px | +Use aria-hidden="true" |
+
| Footer (when >5) | +border-t border-line bg-brand-sand/20 px-5 py-3 flex justify-end |
+ py 12px | +Only render when length > 5 |
+
| Footer link | +font-sans text-xs font-bold uppercase tracking-widest text-brand-navy hover:underline |
+ 12px / 700 | +"Alle {n} anzeigen →" | +
<section aria-labelledby="enrich-heading"> with an <h2 id="enrich-heading">. SR users reach the block via landmark nav.<ol>, not <div>. Upload time is the implicit order.aria-live="polite" so SR announces "12 Dokumente benötigen Metadaten" when the number changes after upload.focus-visible:ring-2 ring-brand-navy ring-inset. Outer ring would clip; inner ring is always visible against hover/active bg.text-ink on white = 14.5:1 (AAA). Meta text-ink-2 on white — must verify ≥4.5:1 in both light and dark mode.prefers-reduced-motion (transition-duration 0.01ms).role="status" aria-live="polite"; manual dismiss button labeled aria-label="Benachrichtigung schließen".All colors come from existing tokens (bg-white, text-ink, text-ink-2, border-line, bg-surface). No hard-coded hex values. Dark mode inherits the remapped tokens.
dark:text-red-300 instead of text-red-600). Run axe-playwright in both themes per project convention./enrich (by upload date, by file type, by uploader).| Test | What it verifies |
|---|---|
| Vitest: component renders | With 0 docs, renders nothing. With 1–5, no footer. With 6+, footer link shows "Alle {n} anzeigen". |
| Vitest: count badge | Badge reflects incompleteDocs.length, not capped list length. |
| Playwright: axe, light mode | Dashboard passes a11y with block populated. Focus ring visible on keyboard nav through rows. |
| Playwright: axe, dark mode | Same as above with [data-theme="dark"]. File-type badge contrast ratios verified. |
| Playwright: 320/768/1440 screenshots | Block renders at all three breakpoints without overflow or truncation beyond title. |
| Playwright: upload → banner → click CTA | After DropZone fires with 3+ files: banner appears, list block populates, CTA scrolls/focuses list. Banner auto-dismisses at 8s. Manual X dismisses immediately. |
| Playwright: keyboard flow | Tab from DropZone reaches banner (if present), then list rows in order, then footer link. Enter on row navigates to /enrich/{id}. |
+ Leonie Voss · UX Lead · Familienarchiv · 2026-04-20 +
+ +