# Plan: Batch-Verarbeitung in den Einzel-PDF-Tools ## Goal Several tools process exactly one PDF at a time, but real legal work is repetitive: compress ten scans, watermark five drafts, encrypt three exhibits. This plan generalizes the single-file tools to accept **multiple files**, process them sequentially with per-file status, and download results individually or as one ZIP. --- ## Open decisions (defaults marked ✅) ### D1 — Rollout scope - ✅ **A. Phased rollout, starting with compress.** Compress is the highest-repetition tool and already has the cleanest input→output shape. Phase 2: watermark (needs option application per file), stamp/page-numbers (same pattern). Phase 3: encrypt/decrypt (decrypt needs a password for all files — one shared password input). - B. All tools at once. Too much surface; the shared pattern should prove itself first. ### D2 — UX model - ✅ **A. Always multi-file.** The dropzone accepts multiple PDFs from day one; a single dropped file simply renders a one-row list. No mode switch. Download: one button per finished row plus "Alle als ZIP", which appears once ≥2 files are done. - B. Explicit "Batch-Modus" toggle. Extra clicks and duplicated code paths. ### D3 — Failure behavior - ✅ **Continue on error.** A failing file is marked with a German error message in its row; the queue keeps processing the rest. "Erneut versuchen" per failed row. This matches how lawyers will use it (one broken scan must not kill the run). --- ## UX Per retrofitted tool page: 1. `PdfDropzone` with `multiple` enabled; dropping more files appends to the current list (does not replace it) — matches how the start page queues ZIPs. 2. **File list** below the options card: one row per file — name, size, status chip (Wartet / Verarbeite… / Fertig ✓ mit neuer Größe / Fehler), per-row download button (enabled when done), per-row remove, "Erneut versuchen" on error. 3. Progress line during a run: "Datei 3 von 7 wird verarbeitet…". 4. Footer actions: **"Alle herunterladen"** (ZIP via fflate, named `{toolname}_{date}.zip`, e.g. `komprimiert_2026-02-12.zip`) visible when ≥2 results exist. 5. Options (preset, watermark settings, password…) apply to **all** files in the run; changing options while a run is active affects only subsequent files (documented in helper text). ## Technical approach ### Shared batch engine (`src/lib/components/BatchFileList.svelte` + `src/lib/batch.ts`) ```ts // src/lib/batch.ts export type BatchItemStatus = 'pending' | 'processing' | 'done' | 'error'; export type BatchItem = { id: string; // crypto.randomUUID() file: File; status: BatchItemStatus; resultBytes?: Uint8Array; resultName?: string; errorMessage?: string | null; }; export const runBatch = async ( items: BatchItem[], process: (file: File) => Promise<{ bytes: Uint8Array; name: string }>, update: (id: string, patch: Partial) => void ): Promise; ``` - `runBatch` iterates **sequentially** (memory protection: only one PDF in flight), calls `update` per lifecycle step so the component re-renders status chips, and never throws — per-item errors are caught and written to the item. - `BatchFileList.svelte` renders rows, emits callbacks (`onDownload(item)`, `onRemove(id)`, `onRetry(id)`); row buttons are real buttons with accessible labels including the file name. - Result bytes stay in memory until downloaded or removed; revocation concerns don't apply (downloads use the existing `downloadBlob` object-URL pattern with timeout revocation). A summary line shows total result size so users notice runaway memory situations. - Very large runs: warn (>20 files or >25 MB per file in rasterizing tools) with a German note; do not hard-block. ### Retrofit pilot: compress - Extract per-file compression into `compressWithPreset` (already planned in `bea-size-limit-plan.md` — coordinate: whichever ships first creates `pdf-compression.ts`). - Page state becomes `items = $state([])` + options; the old single-result UI (`compressedBytes` etc.) is replaced by the list. Zielgröße mode + batch: each file runs the preset loop independently. - Rasterization guards apply per file: files >100 MB are immediately marked as error rows with the rasterization message instead of blocking the whole batch. ### Rollout pattern for later tools - Watermark/stamp/page-numbers: identical wiring; the pdf-lib overlay loop is already per-page and per-file load is isolated. - Encrypt: one password for all files (password inputs stay at the top); per-file failure shows which file broke. - Decrypt: one shared password input + rasterization caveat once, per-file status as above. ## Files changed - `src/lib/batch.ts` — new batch engine. - `src/lib/components/BatchFileList.svelte` — new shared list component. - `src/routes/tools/compress/+page.svelte` — pilot retrofit. - (Phase 2/3) watermark/stamp/page-numbers/encrypt/decrypt pages. - `tests/e2e/toolbox.spec.ts` — spec blocks per retrofitted tool. - `agents.md` — document the batch pattern for future tools. ## Implementation phases 1. `batch.ts` + `BatchFileList.svelte` (build against a mock process function first). 2. Compress pilot retrofit (depends on `pdf-compression.ts` extraction). 3. E2E for compress batch: 2 PDFs → both done, ZIP contains 2 entries; one corrupt file → error row, other still succeeds; retry works. 4. Phase 2 tools, Phase 3 tools (separate PRs). 5. Polish: copy, memory summary line, warning thresholds. ## Verification - Single file dropped → identical outcome to today's single-file flow (same output bytes for the same preset). - Mixed success/failure batch: statuses correct, ZIP includes only successful results. - Remove a finished row → its bytes freed (no download offered afterwards). - Option change mid-run affects only following files. - `pnpm check`, `pnpm lint`, `pnpm build`, `pnpm test:e2e` pass. ## Risks - **Memory with many results held simultaneously**: 30 compressed PDFs × several MB can strain low-end machines. Mitigations: per-row remove, total-size summary, and a follow-up "download and clear" convenience. Do not stream to disk (browser constraint). - **Sequential processing is slow for many large files** — inherent to memory safety; progress line sets expectations. ## Out of scope - Parallel processing. - Cross-tool pipelines (compress → watermark in one run) — separate roadmap item. - Per-file option overrides (v1 applies one option set to the whole batch).