subdocument-splitting-plan
This commit is contained in:
1 parent
f09384d2e6
commit
ca06ca2050
47 files changed
+4097
-136
No files matched your search
@@ -0,0 +1,143 @@
|
||||
# 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<BatchItem>) => void
|
||||
): Promise<void>;
|
||||
```
|
||||
|
||||
- `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<BatchItem[]>([])` + 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).
|
||||
Reference in new issue
Block a user