11 KiB
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 6in 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", defaultAnlage). The stamp text is${prefix} ${number}with a single space — no leading zeros in the stamp (Anlage K 6, notAnlage 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
loadPdfPageRendererfrom 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:
{
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, notBatchFileList— 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.
- per row: drag handle, order badge, computed label („Anlage K 6“), file name, renamed preview
(
- 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.
- Bezeichnung: preset chips + text input (
- 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“ →
runBatchover 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
drawTextOverlayfromsrc/lib/pdf-text-overlay.ts(introduced byplan/done/page-numbers-plan.md) withframed: true, bold font,compensatePageRotation: true. - Uses
src/lib/pdf-errors.tsfor 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.svelteintosrc/lib/batch.ts(downloadBatchZip(items, zipBaseName),batchStatusLabel,batchStatusClass) and reuse it fromBatchFileListandExhibitFileListwithout behaviour changes.
Naming helper (src/lib/exhibit-naming.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;
processresolvesnumbers.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—runBatchitem 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
- Shared helpers (
pdf-text-overlay,pdf-errors, page renderer) + stamp/compress regression. - Batch refactor:
runBatchitem context, ZIP/status extraction,BatchFileListunchanged in behaviour. - Naming helper + list component with reorder, live labels and selection.
- Route: options, stamping, per-row results, ZIP, error rows.
- Preview panel.
- Registry/icon/docs.
- 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 namedAnlage_K_06_…,Anlage_K_07_…,Anlage_K_08_…; page 1 of each contains the label, page 2 is untouched (getPageContentStreamCountunchanged). - 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, targetedpnpm 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 toAnlage. - 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.