refactoring, numbering plan

This commit is contained in:
rehlert committed 2026-10-28 16:09:53 +01:00
1 parent b5279434b2
commit 3b04039aa9
47 files changed
+3485 -953

No files matched your search

+237
View File
@@ -0,0 +1,237 @@
# 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.