Extends issue #294 (new-document split-panel) with bulk uploads. When a user drops N files, every metadata field applies once to all of them — only the title is per-file, pre-filled from the filename and editable inline. A single save POST creates N documents.
A single vertical flow: drop zone on top, then a Gilt für alle metadata card, then stacked file cards (thumbnail · editable title · remove). No split panel, no tabs. Scrolling down reveals all files; the save bar sticks to the bottom.
Reuses the DocumentEditLayout from issue #294 and adds a horizontal file-switcher strip under the PDF preview. Right column splits into two cards: Gilt nur für diese Datei (title only, mint accent) and Gilt für alle N Dokumente (everything else). When N=1 the switcher disappears and the screen is byte-identical to #294.
Shared metadata sticks at the top of the page. Below, each file is a collapsed card; clicking a card expands it to show the PDF preview + title field inline. Only one card is expanded at a time. Scales well to 20+ files — the list stays readable, you only look at the PDFs you want to verify.
All three concepts meet the core requirement (shared metadata + per-file title + one save). Graded against what matters for the senior audience, the responsive constraint, and the #294 architectural commitment.
| Dimension | A · Stack | B · Split-Panel | C · Accordion |
|---|---|---|---|
| Reuses #294 layout | ✕ | ✓ | ✕ |
| Single-file mode unchanged | rewrite | identical | different |
| PDF visible before save | no | always | one at a time |
| Works at 320px | native | via tab collapse | native |
| Scales to 20 files | long scroll | switcher scrolls | collapsed list |
| New Svelte components | 3 new | 1 new (switcher) | 4 new |
| Familiar pattern | yes | yes (post-#294) | new to app |
Concept B treats bulk upload as a polymorphic state of the existing single-document layout rather than a separate screen. A user who drops one file gets exactly the #294 experience. A user who drops five gets the same screen plus a horizontal file-switcher and a two-card split (Nur diese Datei vs. Gilt für alle). Nothing about the single-file flow changes.
FileSwitcherStrip) + two cards in the form.POST /api/documents/quick-upload accepts N files in one multipart).| Element | Tailwind | Px / value | Note |
|---|---|---|---|
| Count pill "N werden erstellt" | bg-accent text-primary rounded-full px-3 py-1 text-sm font-bold |
14px · 700 | brand-mint on brand-navy |
| "Alle verwerfen" link | ml-auto text-sm font-bold text-red-600 hover:text-red-800 focus-visible:outline-2 focus-visible:outline-red-600 |
14px / 44px target | confirm dialog before wiping |
| Element | Tailwind | Px / value | Note |
|---|---|---|---|
| Strip container | flex items-center gap-1 bg-ink/95 px-2 py-2 border-t border-ink/80 |
height 48px | under the PDF toolbar, on the dark panel |
| Arrow buttons | h-10 w-10 rounded-sm bg-white/8 text-surface/60 hover:bg-white/15 focus-visible:outline-2 |
40×40 (44 w/padding) | aria-label="Vorherige Datei" / "Nächste Datei" |
| File chip (inactive) | px-3 py-2 rounded-sm bg-white/6 text-sm font-bold text-surface/55 whitespace-nowrap hover:bg-white/12 |
14px / h 40px | horizontal scroll container uses snap-x snap-mandatory |
| File chip (active) | ... bg-accent text-primary + aria-current="true" |
14px / h 40px | mint pill, primary text — 7.2:1 contrast passes AAA |
| Chip number prefix | bg-primary/25 rounded-sm px-1 mr-2 text-xs font-extrabold |
12px / 800 | "1", "2", … — for quick scanning |
| Element | Tailwind | Px / value | Note |
|---|---|---|---|
| Card container | bg-accent/20 border border-accent rounded-sm p-4 mb-4 |
padding 16px | mint tint signals "different per file" |
| Scope badge | bg-primary/90 text-accent rounded-sm px-2 py-1 text-xs font-extrabold uppercase tracking-wide |
12px · 800 | Paraglide key: bulk_only_this_file |
| Title input | h-11 text-base font-semibold text-ink bg-white border border-line rounded-sm px-3 focus-visible:border-ink focus-visible:ring-2 focus-visible:ring-ink/20 |
44px min-height · 16px | pre-filled from filename without extension |
| Element | Tailwind | Px / value | Note |
|---|---|---|---|
| Card container | bg-surface border border-line rounded-sm p-4 mb-3 |
padding 16px | neutral (no accent tint) |
| Scope badge | bg-accent text-primary rounded-sm px-2 py-1 text-xs font-extrabold uppercase tracking-wide |
12px · 800 | Paraglide: bulk_shared_count ("Gilt für alle {count}") |
| Field grid | grid grid-cols-1 md:grid-cols-2 gap-3 |
12px gap | single column at 320px, two at ≥ 768px |
| Element | Tailwind | Px / value | Note |
|---|---|---|---|
| Primary save button | h-11 px-5 bg-green-700 hover:bg-green-800 text-white font-extrabold rounded-sm text-sm focus-visible:ring-2 focus-visible:ring-green-900 |
44px min · 14px | label {count} speichern → (plural-aware Paraglide) |
| "Als Platzhalter" (outline) | h-11 px-4 border border-line bg-white text-ink-3 font-bold rounded-sm text-sm |
44px | posts with metadataComplete=false for all |
| Element | Tailwind | Px / value | Note |
|---|---|---|---|
| Panel mode switch | reuses DocumentEditLayout's existing tab collapse — "Vorschau / Angaben" tabs | tab height 48px | already shipped with #294 |
| File switcher stays on "Vorschau" tab | snap-x snap-mandatory overflow-x-auto |
h 44px | horizontal swipe; arrow buttons removed at mobile |
filename.replace(/\.(pdf|jpe?g|png|tiff?)$/i, '').replace(/[_-]+/g, ' ').trim(). Marks the title input as suggested until the user edits it (mint left border, same treatment as #294's filename-derived fields)./api/documents/quick-upload with N files + JSON metadata object containing shared fields + titles array. Backend maps title[i] to files[i] by index. Response splits into created[] / updated[] / errors[] — show a summary toast + inline error markers per file for the errors[] list.bg-red-600/20 text-red-800 border border-red-600), show inline error in the chip's tooltip, don't block the rest of the batch from retrying.role="status" aria-live="polite".aria-current="true" + a ▸ caret prefix so it's distinguishable for color-blind users.