subdocument-splitting-plan

This commit is contained in:
rehlert committed 2026-10-06 22:52:35 +02:00
1 parent f09384d2e6
commit ca06ca2050
47 files changed
+4097 -136

No files matched your search

+40 -14
View File
@@ -3,8 +3,9 @@
## Project overview
This repository contains **juri-merger**, a German-language SvelteKit application branded as
**beA-Edit**. Users add one or more ZIP archives in the browser, inspect and reorder supported
attachments, optionally group attachments into named sub-documents, and export merged PDFs.
**beA-Edit**. Users add one or more beA ZIP archives or PDF files in the browser, inspect and
reorder supported attachments, group attachments into named sub-documents (including page-level
PDF splitting), and export merged PDFs.
Nested ZIP archives and XJustiz metadata are handled client-side; there is no upload, server-side
archive processing, database, analytics, or persistence of case data.
@@ -69,10 +70,12 @@ src/
│ ├── assets/ # Favicon and local Nunito fonts
│ ├── components/
│ │ ├── AttachmentPreview.svelte # Browser-rendered PDF/image thumbnails
│ │ ├── AttachmentMenu.svelte # Per-attachment dropdown menu (move, split, remove)
│ │ ├── BatchFileList.svelte # Shared batch rows: status chips, downloads, retry
│ │ ├── BeaArchiveProcessing.svelte
│ │ ├── BeaWorkspaceControls.svelte # beA workspace toolbar, settings popover, dialogs
│ │ ├── ProcessedZipArchiveEditor.svelte
│ │ ├── PdfSplitDialog.svelte # Page-level PDF splitting into Teildokumente
│ │ ├── SubDocumentEditor.svelte
│ │ ├── FileDropzone.svelte # Shared PDF/image/ZIP file picker and drop target
│ │ ├── ZipFilePicker.svelte # Compact ZIP picker button with hidden resettable input
@@ -142,8 +145,8 @@ committed.
## Application flow and state
- `/` is the toolbox hub. A ZIP dropped anywhere on that route is queued in memory and forwarded to
`/tools/bea`; the file is not uploaded or persisted.
- `/` is the toolbox hub. A beA ZIP or PDF dropped anywhere on that route is queued in memory and
forwarded to `/tools/bea`; the file is not uploaded or persisted.
- `/tools/metadata` is a single-PDF inspection tool. `src/lib/pdf-metadata.ts` reads document
properties with pdf-lib only (no rasterization), returns a discriminated `{ status: 'encrypted' }`
result for password-protected files, and detects embedded-file markers by scanning raw bytes.
@@ -153,41 +156,61 @@ committed.
thumbnail-width preference. Thumbnail width is the only persisted UI setting and uses localStorage.
- The loaded workspace renders `BeaWorkspaceControls.svelte`, a top-aligned toolbar with the
workspace status and two archive lifecycle actions:
- **“Neue ZIP bearbeiten”** (replace) opens a ZIP picker and, after the user confirms the
- **“Neue Datei bearbeiten”** (replace) opens a file picker and, after the user confirms the
replacement dialog, calls `replaceWithZipFiles(files)` which resets the workspace and enqueues the
new files against the incremented generation. Files picked for replacement stay in temporary
component state until confirmation; cancelling the picker or the dialog must never mutate the
current workspace.
- **“Weitere ZIP hinzufügen”** (append) lives in the settings popover, uses the plain append path
- **“Weitere Datei hinzufügen”** (append) lives in the settings popover, uses the plain append path
(`handleFilesSelected`), and never clears current archives. Keep the two labels distinct:
replace is “Neue ZIP bearbeiten”, append is “Weitere ZIP hinzufügen”.
replace is “Neue Datei bearbeiten”, append is “Weitere Datei hinzufügen”.
- `BeaWorkspaceControls.svelte` owns only ephemeral UI state (popover/dialog open state and pending
replacement files). The route remains the source of truth for files, jobs, archives, and the
persisted thumbnail width.
- `resetWorkspace()` is the single teardown helper: it increments `archiveGeneration` and clears
selected files, processed archives, and jobs. Never clear those arrays without going through it.
- Accepted ZIP types and the file filter for the dropzone and both compact pickers are centralized in
`src/lib/zip-selection.ts`; `ZipFilePicker.svelte` resets its input after every selection so the
- Accepted file types for the workspace (beA ZIP **and** PDF) are centralized in
`src/lib/zip-selection.ts` and shared by the dropzone, the start-page drop target, and both
compact pickers; `ZipFilePicker.svelte` resets its input after every selection so the
same file can be chosen again.
- ZIP files are processed concurrently. `archiveGeneration` prevents results from an old batch from
reappearing after the user deletes all archives **or** confirms a replacement.
- Workspace files are processed concurrently. PDF files become single-attachment archives via
`pdfFileToArchive`; ZIP files go through `extractZipArchives`. `archiveGeneration` prevents
results from an old batch from reappearing after the user deletes all archives **or** confirms a
replacement.
- `ProcessedZipArchiveEditor.svelte` edits one archive and emits immutable replacements through
`onArchiveChange`; the route-level archive array remains the source of truth.
- Attachments may remain loose or belong to exactly one sub-document. Drag-and-drop supports
reordering and moving attachments between lists, while sub-document headers can be reordered.
- Selection mode creates sub-documents from loose attachments. Exiting with **Fertig** or Escape
clears the pending selection.
- **Gruppieren** (grouping mode) offers archive-wide multi-select over loose and sub-document
attachments: create a new Teildokument from the selection, move the selection into an existing
Teildokument, select all/none. Exiting with **Fertig** or Escape clears the pending selection.
New Teildokumente are numbered via `nextSubDocumentNumber` (max existing trailing number, not
list length), and the created block's name input is focused and scrolled into view.
- Each attachment card has an **“⋯” menu** (`AttachmentMenu.svelte`) as the keyboard/touch path:
move into an existing or new Teildokument, move back to the loose list, split PDFs, and remove
(with confirmation). It must never become the only way to perform an action that drag-and-drop
also covers.
- **PDF splitting**: `PdfSplitDialog.svelte` opens per PDF attachment, renders pages through
`loadPdfDocument` + `PdfPageGrid`, and assigns page sets to named groups (manual selection,
“Jede Seite einzeln”, or “Alle N Seiten”). Cut is the default; “Seiten im Original behalten”
switches to copy. `applyPdfSplit` replaces the source with the remaining pages (or removes it),
inserts the new Teildokumente after the source's parent block, and the synthetic attachments
get unique `#seiten-…` path suffixes because `path` is the identity for keyed lists, drag
payloads, and selection.
- Per-archive export supports one flattened PDF or separate PDFs for sub-documents. Each
sub-document also has its own direct download action that exports only that sub-document's
attachments. Bulk export from the page always creates one flattened PDF per archive.
When changing archive transforms, preserve these invariants:
- An attachment move must not duplicate or discard data.
- An attachment move must not duplicate or discard data. Splitting cuts pages by default and only
duplicates them when the user explicitly chooses copy mode.
- Sub-document IDs remain stable across edits and reordering.
- Single-PDF order is all sub-documents in order, each attachment in order, followed by loose
attachments in order.
- Dissolving a sub-document appends its attachments to the loose list.
- Splitting inserts the new Teildokumente directly after the source's parent block (loose source:
appended at the end) so the reading order is preserved.
- State helpers in `zip-processing.ts` should return new archive/list objects rather than mutating
component-owned state.
@@ -205,6 +228,9 @@ All case-file processing is intentionally local to the browser.
extension classification and downstream preview/export behavior together when adding formats.
- PDF attachments are copied with pdf-lib. JPEG and PNG files can be embedded directly; other image
formats are decoded through browser image/canvas APIs and converted to PNG first.
- Page-set copies (separate/extract tools, PDF splitting) share
`copyPagesToNewPdf(sourceBytes, pageIndexes)` in `src/lib/pdf-processing.ts` so the copy
semantics cannot drift apart.
- `AttachmentPreview.svelte` dynamically loads pdfjs-dist in `onMount`, copies PDF bytes before
rendering, and revokes object URLs during cleanup. Preserve those memory and data-integrity
safeguards.