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

11 KiB
Raw Permalink Blame History

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:

{
	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)

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.