# Enrich / Edit — Unified Design Spec

Keeps the current card-based layout. Applies three targeted changes to reduce friction: a required-fields progress bar above the form, required fields moved to the first row within each card, and a proper no-PDF upload state in the left panel.

Leonie Voss · UI/UX · 2026-04-17 · Familienarchiv
enrich edit-document unified spec field reordering

## 1 — Field Priority Within Cards

Both cards keep their existing structure. Fields are reordered so a user can fill the essentials top-to-bottom without skipping around. Optional fields still live in the same card — they're just pushed below a thin divider.

1
WhoWhenSection — swap rows
Currently the grid is: Date | Location (row 1) → Sender | Receivers (row 2). Required fields are split across both rows. New order: Date | Sender (row 1, both required) → Receivers | Location (row 2, both optional). No code changes to the grid — just reorder the four `<div>` elements inside the existing `grid-cols-2`.
2
DescriptionSection — promote Title, push Archive Location last
Title is the most important field but currently sits below "Aufbewahrungsort" in the component's source order. New order: Title (full row, first) → Tags → Summary → Aufbewahrungsort (last, below an "Optional" divider). The divider signals that everything below it is supplementary — no need to fill it during enrichment.
3
Required-fields progress bar — above the form scroll
A 3 px strip between the top bar and the form panel's scroll area. Tracks how many required fields (Title, Date, Sender) are non-empty. Green fill grows left to right. Count badge (e.g. "2 / 3") to the right. Zero JS overhead — computed as a `$derived` value from existing bindable state.
WhoWhenSection
Jetzt
DatumPflicht
OrtOptional
AbsenderPflicht
EmpfängerOptional
→
Neu
DatumPflicht
Absender↑ hochgezogen
EmpfängerOptional
OrtOptional

Row 1 is now entirely required. A user who fills Date + Sender and moves on has completed all mandatory who/when data.

DescriptionSection
Jetzt
TitelPflicht
AufbewahrungsortOptional
SchlagworteOptional
KurzinhaltOptional
→
Neu
TitelPflicht
Schlagworte↑ hochgezogen
KurzinhaltOptional
AufbewahrungsortOptional

Title stays first. Tags move up to row 2 (more useful than archive location during enrichment). Archive Location drops below the "Optional" divider — it's an administrative field, not enrichment work.

## 2 — Full Page with PDF

The familiar split-pane layout with the reorganized form. Required-fields bar shows 2 of 3 filled; the one missing required field (Sender) is visible immediately at the top of the first card.

← Zur Liste
Brief an Tante Hilde, Sommer 1952
12 verbleibend
⊕
⊖
↺
Seite 1 / 3
☷
Pflichtfelder
2 / 3
Wer & Wann
Datum *
15.07.1952
Absender *
Person suchen …
Empfänger
Hilde Brandt ×
+
Ort
München
Beschreibung
Titel *
Brief an Tante Hilde — München 1952
Schlagworte
Familie ×
Krieg ×
+ Schlagwort
Kurzinhalt
Optional
Aufbewahrungsort
z. B. Karton 3, Regal B
Überspringen
Speichern
Speichern & Geprüft
1
Required bar at 2/3 — Datum and Titel are filled; Absender is empty. The bar gives an at-a-glance completion signal without adding visual weight to individual fields.
2
Absender focused — it's the only required field still empty, so it receives focus on load via `autofocus` or a Svelte `$effect` that targets the first empty required input. No user action needed to know where to start.
3
Aufbewahrungsort below the "Optional" divider — visually de-prioritized without being removed. Editors doing bulk enrichment can ignore it entirely; archivists who need it will still find it.

## 3 — No-PDF State

When a document has no file yet (status `PLACEHOLDER` from Excel import), the left panel shows the same dark background but with a centered upload zone. The original filename is shown if known. Drag-and-drop is supported; clicking the CTA opens the file picker. Once a file is selected it uploads and the panel transitions to the normal PDF viewer.

Kein Dokument hochgeladen
← Zur Liste
scan-2024-03-15-brief.pdf
12 verbleibend
↑
scan-2024-03-15-brief.pdf
Noch keine Datei hochgeladen
Datei auswählen
oder Datei hier ablegen
Pflichtfelder
0 / 3
Wer & Wann
Datum *
TT.MM.JJJJ
Absender *
Person suchen …
Überspringen
Speichern
Speichern & Geprüft
Datei wird hochgeladen
← Zur Liste
scan-2024-03-15-brief.pdf
12 verbleibend
scan-2024-03-15-brief.pdf
Wird hochgeladen …
Abbrechen
Pflichtfelder
0 / 3
Wer & Wann
Datum *
TT.MM.JJJJ
Absender *
Person suchen …
Überspringen
Speichern
Speichern & Geprüft
1
Dark background is consistent — the same dark panel color as the loading state prevents a jarring flash when the PDF loads. No separate empty-state design needed for the panel chrome.
2
Filename always visible — `doc.originalFilename` is always populated. Showing it in the upload zone reassures the user they're uploading the right file, and in the top bar title it gives context while the form is empty.
3
Form stays usable during upload — metadata can be filled while the file uploads in parallel. The upload and form save are independent operations; both are sent on "Speichern".
4
Replace file (edit mode) — when a file already exists and the user is on the edit page, the left panel shows the PDF normally. A "Datei ersetzen" ghost button sits in the PDF toolbar — same panel, just an additional toolbar action.

## Implementation Reference

Exact Tailwind classes and component-level changes. All three improvements are additive — no existing card structure or component API needs to change.

Element | File | Change | Tailwind / Code
--- | --- | --- | ---
WhoWhenSection field order | `WhoWhenSection.svelte` | Reorder the four `<div>` children of the `grid-cols-2` container: Date → Sender → Receivers → Location | No class changes. Grid order is source order.
DescriptionSection field order | `DescriptionSection.svelte` | Move `documentLocation` field to last. Move Tags before Summary. | No class changes to existing fields.
Optional divider inside DescriptionSection | `DescriptionSection.svelte` | Add divider element before `documentLocation` field. | `<div class="flex items-center gap-2 my-3"><div class="flex-1 border-t border-line"></div><span class="text-[9px] font-bold uppercase tracking-widest text-ink-3">Optional</span><div class="flex-1 border-t border-line"></div></div>`
Required-fields progress bar | `enrich/[id]/+page.svelte` | Add strip between `.enrich-bar` and the split pane. Derive count from bindable state already in scope. | Strip: `flex items-center gap-3 border-b border-line bg-surface px-6 py-1.5`. Track: `h-0.5 flex-1 rounded-full bg-line`. Fill: `h-full rounded-full bg-brand-navy transition-all duration-300` with `style="width:{pct}%"`.
Required fields derivation | `enrich/[id]/+page.svelte` | Derive from existing state variables, no new props needed. | `const requiredFilled = $derived([doc.title || titleValue, dateIso, senderId].filter(Boolean).length);` — 3 total required fields in enrich mode.
Auto-focus first empty required field | `WhoWhenSection.svelte` | Add `autofocus` on the Date input when `initialDateIso` is empty; on the Sender PersonTypeahead otherwise. | `{#if !initialDateIso}<input ... autofocus />{:else}<PersonTypeahead ... autofocus />{/if}`
No-PDF upload zone | `enrich/[id]/+page.svelte` (new conditional block) | Conditionally render upload zone in the left panel when `!doc.filePath`. | Zone: `flex-1 flex items-center justify-center bg-[#4A4846]`. Inner: `border border-dashed border-white/20 rounded-sm p-8 flex flex-col items-center gap-3 text-center`.
Upload zone drag-and-drop | `enrich/[id]/+page.svelte` | Add `ondragover` / `ondrop` to zone element. On drop, trigger same upload action as the file input. | `ondragover={(e) => { e.preventDefault(); isDragging = true; }}` + `ondrop={(e) => { e.preventDefault(); handleFile(e.dataTransfer?.files[0]); }}`. Dragging state: `border-brand-mint bg-mint/5` transition.
Upload zone — original filename | `enrich/[id]/+page.svelte` | Show `doc.originalFilename` inside the zone as a label. Always available from the load function. | `<p class="text-xs font-medium text-white/50 truncate max-w-[200px]">{doc.originalFilename}</p>`
Uploading / progress state | `enrich/[id]/+page.svelte` | Use existing `FileLoader` hook's `isLoading` state. Show indeterminate progress bar when loading. | Progress track: `w-full h-0.5 bg-white/10 rounded-full overflow-hidden`. Fill: `h-full bg-brand-mint/70 animate-[slide_1.4s_ease-in-out_infinite]`.
Replace file button (edit mode) | `FileSectionEdit.svelte` | Move "Datei ersetzen" from the card in the form scroll to a ghost button in the PDF toolbar. Toolbar is visible in both states. | Button: `ml-auto text-[10px] font-bold uppercase tracking-widest text-white/40 hover:text-white/70 transition-colors`. Label: "Datei ersetzen". Hidden input triggers file picker.