Files
bea-edit/plan/done/exhibit-numbering-plan.md
2026-10-28 16:09:53 +01:00

238 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Plan: Anlagen-Bezeichnung — Anlagen nummerieren und benennen
## Goal
Add a new tool **„Anlagen nummerieren“** (`/tools/exhibits`): users drop any number of PDFs, sort
them, choose an exhibit label („Anlage“, „Anlage K“, „Anlage B“, „Anlage XYZ“ or free text), a start
number and the page scope, and receive each PDF stamped with its exhibit number — optionally renamed
to `Anlage_K_06_Originalname.pdf` for court submission.
Context: mail thread with Nikolai Klute (26.09.2026 / 06.10.2026). The requirement is explicitly
_one PDF = one Anlage = one number_, with sortable order, configurable label and counter start, and
optional filename prefixing. Per-page counters are the separate pagination plan.
Related: `plan/done/page-numbers-plan.md` (shared overlay/renderer), `plan/done/stamp-plan.md`,
`plan/done/batch-processing-plan.md`.
---
## Open decisions (defaults marked ✅)
### D1 — Numbering unit
- ✅ **A. One uploaded PDF = one Anlage = one number.** An „Anlage K 6“ may have many pages; the
stamp identifies the document, not the sheet.
- B. Group several PDFs into one Anlage. Not requested; deferred (a group would need its own reorder
UI inside a group).
### D2 — Stamped pages
- ✅ **A. First page by default,** checkbox „Auf jeder Seite stempeln“ for every page of that
exhibit.
- B. Every page by default. Real exhibit stamps usually mark the first page; the stamp tool already
covers „everywhere“.
### D3 — Look
- ✅ **A. Framed exhibit stamp:** `Anlage K 6` in title case, HelveticaBold 14 pt (slider 10–24),
black `#1a1a1a` (color input included, same control as the stamp tool), 2 pt border, 6 pt padding,
no rotation, no date, default position **oben rechts**, 20 pt margin; the six other positions are
selectable.
- B. Reuse the red authority stamp look. Wrong signal: an exhibit label is a designation, not an
endorsement.
### D4 — Label and presets
- ✅ **A. Preset chips + editable text.** Chips `Anlage`, `Anlage K`, `Anlage B`, `Anlage XYZ`
(`maxlength="40"`, default `Anlage`). The stamp text is `${prefix} ${number}` with a single space —
no leading zeros in the stamp (`Anlage K 6`, not `Anlage K 06`).
- B. Fixed list only. Firms use their own exhibit letters.
### D5 — Filename renaming
- ✅ **A. Optional, default on.** Pattern `{prefixSlug}_{paddedNumber}_{originalBase}.pdf`, e.g.
`Anlage_K_06_Klageschrift.pdf`. Prefix slug: trim, collapse whitespace to `_`, strip
`/\:*?"<>|` and control characters, keep German letters. Padding = `max(2, digits of highest
assigned number)`, so 1…99 → `06`, 1…100 → `006`.
Renaming off keeps the original file name exactly (ZIP duplicate names are still deduplicated by
the batch ZIP helper).
- B. Always rename. Users sometimes want to control file names themselves.
### D6 — Ordering
- ✅ **A. Drag-and-drop with a visible handle, plus up/down buttons.** Buttons are the keyboard and
Playwright path; both call the same `moveItem(from, to)`. Reordering is disabled while a run is
active.
- B. Up/down only. Too slow for 30 exhibits.
### D7 — Number freezing
- ✅ **A. Numbers are frozen when a run starts** (`itemId → start + index`). Results keep the number
they were stamped with even if the user reorders afterwards; the list shows
„Bereits erstellte Anlagen behalten ihre Nummer.“ Retry uses the frozen number. A clean renumber
means removing results and running again.
- B. Live renumbering after processing. Would silently invalidate downloaded files and produce
duplicate numbers.
### D8 — Deliverables
- ✅ **A. Per-row downloads + one ZIP „Alle herunterladen“** via the existing batch ZIP engine
(`anlagen_2026-10-06.zip`). A merged „Anlagenkonvolut“ stays out of scope — „PDFs zusammenfügen“
exists.
- B. Always one merged PDF. Not requested and destroys per-exhibit files.
### D9 — Preview
- ✅ **A. Preview only for the selected row**, page 1 rendered on demand (shared
`loadPdfPageRenderer` from the pagination plan), overlay scaled like the stamp preview; selecting
another row disposes the previous handle.
- B. Thumbnail per row. Renders every dropped PDF completely — memory trap.
---
## UX
Route `/tools/exhibits`, title „Anlagen nummerieren“, registry entry next to `stamp`:
```ts
{
slug: 'exhibits',
title: 'Anlagen nummerieren',
description: 'Nummerieren und benennen Sie Anlagen für die Einreichung — mit Anlagenstempel.',
icon: 'files'
}
```
- **Header/description:** „Versehen Sie mehrere PDFs mit fortlaufenden Anlagen-Bezeichnungen und
benennen Sie die Dateien passend um.“
- **FileDropzone** (multiple) — „PDFs als Anlagen hinzufügen“; appending keeps the current list
(same dedupe by name + size as the stamp tool).
- **Numbered list** (`ExhibitFileList.svelte`, not `BatchFileList` — it needs reordering and number
labels):
- per row: drag handle, order badge, computed label („Anlage K 6“), file name, renamed preview
(`→ Anlage_K_06_Klageschrift.pdf`), status chip, per-row download / retry / remove, and a
„Vorschau“ selection.
- live label updates for pending rows when order, prefix or start number changes; done rows show
the frozen label.
- **Options card**
- Bezeichnung: preset chips + text input (`maxlength="40"`), live example „Anlage K 6“.
- Startnummer (number input, default 1, min 0) + hint „3 Dateien → Anlage K 6, 7, 8“.
- „Auf jeder Seite stempeln“ checkbox (default off).
- Position (default oben rechts), Schriftgröße slider 10–24 (default 14), Farbe (default
`#1a1a1a`).
- „Dateien umbenennen“ checkbox (default on) with the filename pattern as helper text.
- **Preview card:** selected row, page 1, overlay of the row's label at the chosen position; page
count of the selected file shown (requires loading it — the renderer reports both).
- **Primary action** „Anlagen erstellen“ → `runBatch` over all pending rows; then per-row downloads
and the ZIP action. While running: „Datei 3 von 7 wird verarbeitet…“ (batch pattern) and
reordering disabled.
- Errors: encrypted PDFs become per-row error rows with the shared decryption hint; an empty prefix
blocks the run with a German validation hint; too-long labels use the overlay's fit/error path.
## Technical approach
### Shared overlay and errors
- Uses `drawTextOverlay` from `src/lib/pdf-text-overlay.ts` (introduced by
`plan/done/page-numbers-plan.md`) with `framed: true`, bold font, `compensatePageRotation: true`.
- Uses `src/lib/pdf-errors.ts` for the encrypted-PDF message.
- If the exhibit plan ships first, it creates both modules and the pagination plan reuses them.
### Batch engine
- Extend `runBatch`'s callback to `(file: File, item: BatchItem) => Promise<BatchProcessResult>` so
the tool can resolve the frozen number per row (existing `(file) => …` callbacks remain valid).
- Keep `shouldProcess`, per-row error formatting and the sequential loop unchanged.
- Extract the ZIP/status logic that currently lives inline in `BatchFileList.svelte` into
`src/lib/batch.ts` (`downloadBatchZip(items, zipBaseName)`, `batchStatusLabel`,
`batchStatusClass`) and reuse it from `BatchFileList` and `ExhibitFileList` without behaviour
changes.
### Naming helper (`src/lib/exhibit-naming.ts`)
```ts
export const buildExhibitLabel = (prefix: string, number: number) => `${prefix.trim()} ${number}`;
export const buildExhibitFileName = (options: {
prefix: string;
number: number;
highestNumber: number;
originalName: string;
}): string => {
const base = options.originalName.replace(/\.pdf$/i, '');
const slug = options.prefix
.trim()
.replace(/\s+/g, '_')
.replace(/[/\\:*?"<>|\u0000-\u001f]/g, '');
const width = Math.max(2, String(options.highestNumber).length);
return `${slug}_${String(options.number).padStart(width, '0')}_${base}.pdf`;
};
```
### Stamping
- Load the PDF with pdf-lib, embed `StandardFonts.HelveticaBold`, draw the label on page 0 (or all
pages), save.
- Result name from the naming helper (or the original name when renaming is off). The batch result
keeps the bytes in memory for download/ZIP.
- Numbering is frozen at run start; `process` resolves `numbers.get(item.id) ?? freeze(item)`.
## Files changed
- `src/routes/tools/exhibits/+page.svelte` — new route.
- `src/lib/components/ExhibitFileList.svelte` — reorderable list with status/downloads.
- `src/lib/exhibit-naming.ts` — label + filename helpers.
- `src/lib/batch.ts`, `src/lib/components/BatchFileList.svelte` — `runBatch` item context, shared
ZIP/status helpers.
- `src/lib/pdf-text-overlay.ts`, `src/lib/pdf-errors.ts`, `src/lib/pdf-thumbnails.ts` — shared
helpers (whichever plan ships first creates them).
- `src/lib/tools.ts`, `src/routes/+page.svelte` — registry + icon map (`Files`).
- `tests/e2e/exhibits.spec.ts` — new spec.
- `agents.md` — route, batch and shared-helper documentation.
## Implementation phases
1. Shared helpers (`pdf-text-overlay`, `pdf-errors`, page renderer) + stamp/compress regression.
2. Batch refactor: `runBatch` item context, ZIP/status extraction, `BatchFileList` unchanged in
behaviour.
3. Naming helper + list component with reorder, live labels and selection.
4. Route: options, stamping, per-row results, ZIP, error rows.
5. Preview panel.
6. Registry/icon/docs.
7. Playwright coverage + command gauntlet.
## Verification
- Three PDFs (1, 2, 3 pages), reorder the second to the front, prefix `Anlage K`, start 6, renaming
on → labels „Anlage K 6/7/8“, downloads named `Anlage_K_06_…`, `Anlage_K_07_…`, `Anlage_K_08_…`;
page 1 of each contains the label, page 2 is untouched (`getPageContentStreamCount` unchanged).
- Start 98 with three files → padding switches to three digits (`098`, `099`, `100`).
- Renaming off → downloads keep the original names; ZIP entries deduplicate on collisions.
- „Auf jeder Seite stempeln“ → every page of every exhibit contains the label.
- Reorder after a run → done rows keep their label and the hint is visible.
- Prefix validation (empty), encrypted PDF (row error, queue continues), 30-file ZIP.
- Single-page PDFs do not break the run.
- `pnpm check`, targeted `pnpm test:e2e`, `pnpm build`, Prettier on touched files.
## Risks
- **Number drift after edits:** mitigated by frozen numbers + hint; no silent renumber.
- **Drag-and-drop accessibility:** buttons remain the primary path and the tested one; DnD must have
a visible handle and keyboard alternative.
- **Memory:** results stay in memory like every batch tool; the existing warning (>20 files or
> 25 MB) applies. The preview renders only one page — never a thumbnail array.
- **Filename edge cases:** slugs from unusual prefixes (`Anlage K/1`) must not produce path
separators or empty names; validate and fall back to `Anlage`.
- **Rotated scans:** the shared overlay compensates page rotation for the new tools; stamp output
must stay unchanged.
- **Already-prefixed input files:** re-running the tool on its own output stacks prefixes. Document
it; no auto-detection in v1.
## Out of scope
- Grouping multiple PDFs into one Anlage.
- Anlagenverzeichnis / index page (a merged table of contents is a follow-up candidate).
- One merged „Anlagenkonvolut“ export.
- Per-page counters (pagination plan).
- Persisted prefixes or exhibit libraries.