subdocument-splitting-plan
This commit is contained in:
1 parent
f09384d2e6
commit
ca06ca2050
47 files changed
+4097
-136
No files matched your search
@@ -0,0 +1,188 @@
|
||||
# Plan: Download an individual sub-document
|
||||
|
||||
**Status: Done**
|
||||
|
||||
## Goal
|
||||
|
||||
Each sub-document displayed inside a processed ZIP archive shall have its own download action. The
|
||||
action creates and downloads one PDF containing only that sub-document's attachments, in their
|
||||
current order.
|
||||
|
||||
This is an additional interaction. The existing archive-level export modes remain available:
|
||||
|
||||
- **Eine PDF** continues to flatten all sub-documents and loose attachments into one PDF.
|
||||
- **Getrennt** continues to download every non-empty sub-document plus the loose-attachment PDF.
|
||||
- The new sub-document action downloads only the selected sub-document and does not include loose
|
||||
attachments or sibling sub-documents.
|
||||
|
||||
## Current implementation
|
||||
|
||||
- `ProcessedZipArchiveEditor.svelte` owns archive-level download state and the browser download helper.
|
||||
- `SubDocumentEditor.svelte` renders each sub-document but currently exposes only edit, drag/drop, and
|
||||
dissolve callbacks.
|
||||
- `mergeAttachments()` in `src/lib/zip-processing.ts` already creates a PDF from an ordered attachment
|
||||
list.
|
||||
- `buildArchiveExport(archive, 'separate')` already defines the desired sub-document naming convention:
|
||||
`{archiveName} - {subDocumentName}.pdf`. It currently creates exports for every sub-document, so it
|
||||
should not be called for an individual download.
|
||||
|
||||
## UX and behavior
|
||||
|
||||
Add a **PDF herunterladen** button to each sub-document header, before **Auflösen**.
|
||||
|
||||
- Clicking it downloads one PDF for that sub-document.
|
||||
- The PDF page order matches `subDocument.attachments` exactly.
|
||||
- The filename remains consistent with separate archive export:
|
||||
`{archiveName without .pdf} - {subDocumentName without .pdf}.pdf`.
|
||||
- The action is disabled when the sub-document has no attachments.
|
||||
- While an individual PDF is being generated, its button reads **PDF wird erstellt** and cannot be
|
||||
triggered again.
|
||||
- Only one PDF-generation operation may run in an archive editor at a time. While an individual
|
||||
download is active, archive-level and sibling download actions are disabled; while archive-level
|
||||
export is active, individual actions are disabled.
|
||||
- A failure leaves the archive and sub-document unchanged and shows the existing inline error area
|
||||
with a sub-document-specific German message.
|
||||
- Existing drag/drop, rename, selection, dissolve, and archive export behavior must remain unchanged.
|
||||
|
||||
## Implementation
|
||||
|
||||
### 1. Add a focused export helper
|
||||
|
||||
Update `src/lib/zip-processing.ts` with an exported helper:
|
||||
|
||||
```ts
|
||||
export const buildSubDocumentExport = async (
|
||||
archiveName: string,
|
||||
subDocument: SubDocument
|
||||
): Promise<ExportUnit>
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
1. Reject an empty attachment list with the same explicit empty-export behavior used by
|
||||
`mergeAttachments()`.
|
||||
2. Merge `subDocument.attachments` by calling `mergeAttachments()`.
|
||||
3. Return one `ExportUnit` using the existing archive/sub-document filename convention.
|
||||
4. Keep `.pdf` normalization centralized through the existing `stripPdfExtension()` helper.
|
||||
|
||||
Refactor the `mode === 'separate'` branch of `buildArchiveExport()` to call
|
||||
`buildSubDocumentExport()` for each non-empty sub-document. This ensures bulk-separate and
|
||||
individual exports cannot drift in PDF contents or naming. Preserve sequential generation and the
|
||||
existing loose-attachment export behavior.
|
||||
|
||||
Do not construct a temporary `ProcessedZipArchive` and do not invoke `buildArchiveExport(...,
|
||||
'separate')` for one item: both approaches encode the wrong scope and risk exporting unrelated
|
||||
attachments later.
|
||||
|
||||
### 2. Add individual download orchestration to the archive editor
|
||||
|
||||
Update `src/lib/components/ProcessedZipArchiveEditor.svelte`:
|
||||
|
||||
- Import `buildSubDocumentExport`.
|
||||
- Add `downloadingSubDocumentId: string | null` state alongside `isMerging` and `mergeError`.
|
||||
- Derive whether any export is active from `isMerging || downloadingSubDocumentId !== null`.
|
||||
- Add `downloadSubDocument(subDocument: SubDocument)` that:
|
||||
1. Returns when another export is active or the sub-document is empty.
|
||||
2. Stores the selected sub-document ID and clears `mergeError`.
|
||||
3. Calls `buildSubDocumentExport(archiveName, subDocument)`.
|
||||
4. Passes the returned bytes and name to the existing `downloadPdfBytes()` helper.
|
||||
5. On failure, logs context including the archive and sub-document and sets
|
||||
`Teildokument „{name}“ konnte nicht als PDF erstellt werden.`.
|
||||
6. Clears the active ID in `finally`.
|
||||
- Update `downloadArchive()` to guard against any active export, not only `isMerging`.
|
||||
- Disable the archive-level download button and export-mode controls while an individual export is
|
||||
active. The mode controls should remain visible and retain their selected value.
|
||||
- Pass these props to every `SubDocumentEditor`:
|
||||
- `onDownload={() => downloadSubDocument(subDocument)}`
|
||||
- `isDownloading={downloadingSubDocumentId === subDocument.id}`
|
||||
- `downloadDisabled={isMerging || downloadingSubDocumentId !== null}`
|
||||
|
||||
The browser download implementation stays in the parent so Blob/object-URL creation, filename
|
||||
normalization, loading coordination, and errors remain centralized.
|
||||
|
||||
### 3. Add the sub-document header action
|
||||
|
||||
Update `src/lib/components/SubDocumentEditor.svelte`:
|
||||
|
||||
- Import the existing local `Button` component.
|
||||
- Extend `Props` with:
|
||||
- `onDownload?: () => void`
|
||||
- `isDownloading?: boolean`
|
||||
- `downloadDisabled?: boolean`
|
||||
- Give all new props safe defaults.
|
||||
- Render a bordered button before **Auflösen**:
|
||||
- Normal label: **PDF herunterladen**
|
||||
- Active label: **PDF wird erstellt**
|
||||
- Disabled when `downloadDisabled`, `isDownloading`, or `attachments.length === 0`
|
||||
- Keep the control as a native button through the shared `Button` component, with a clear accessible
|
||||
label that includes the sub-document name if the visible label alone is ambiguous in assistive
|
||||
technology.
|
||||
- Prevent the button from participating in drag initiation in the same way as the existing name and
|
||||
dissolve controls.
|
||||
- Keep **Auflösen** visually distinct as the destructive action; do not change its behavior.
|
||||
|
||||
Use existing button variants and semantic theme utilities. Do not introduce a new UI dependency or a
|
||||
second download utility in the child component.
|
||||
|
||||
## Files changed
|
||||
|
||||
- `src/lib/zip-processing.ts`
|
||||
- Add `buildSubDocumentExport()`.
|
||||
- Reuse it from separate archive export.
|
||||
- `src/lib/components/ProcessedZipArchiveEditor.svelte`
|
||||
- Coordinate individual export state, errors, and browser download.
|
||||
- `src/lib/components/SubDocumentEditor.svelte`
|
||||
- Add the individual download button and callback props.
|
||||
- `agents.md`
|
||||
- Clarify that per-archive separate export and direct per-sub-document export are both supported.
|
||||
|
||||
No route, server endpoint, persistence, package, lockfile, or Docker changes are required.
|
||||
|
||||
## Verification
|
||||
|
||||
### Automated checks
|
||||
|
||||
Run:
|
||||
|
||||
```sh
|
||||
pnpm exec prettier --write \
|
||||
src/lib/zip-processing.ts \
|
||||
src/lib/components/ProcessedZipArchiveEditor.svelte \
|
||||
src/lib/components/SubDocumentEditor.svelte \
|
||||
agents.md
|
||||
pnpm check
|
||||
pnpm lint
|
||||
pnpm build
|
||||
```
|
||||
|
||||
There is no test framework in the repository. Do not add one solely for this feature. If a test
|
||||
framework is introduced separately, cover `buildSubDocumentExport()` with non-empty, empty, ordering,
|
||||
and filename cases.
|
||||
|
||||
### Manual scenarios
|
||||
|
||||
1. Create an archive with two sub-documents and at least one loose attachment.
|
||||
2. Download the first sub-document and verify exactly one PDF is triggered.
|
||||
3. Confirm the PDF contains only the first sub-document's attachments and preserves their displayed
|
||||
order.
|
||||
4. Confirm the filename is `{archive} - {sub-document}.pdf` without duplicate `.pdf` suffixes.
|
||||
5. Download the second sub-document and verify it has independent contents and naming.
|
||||
6. Confirm an empty sub-document's download action is disabled.
|
||||
7. Start an individual export and verify duplicate, sibling, and archive-level export actions cannot
|
||||
start until it finishes.
|
||||
8. Force an invalid PDF/image merge and verify a German inline error appears, loading state clears,
|
||||
and editor state is unchanged.
|
||||
9. Verify **Eine PDF** still exports all sub-documents followed by loose attachments.
|
||||
10. Verify **Getrennt** still exports every non-empty sub-document and the loose-attachment PDF.
|
||||
11. Verify rename, reorder, move, selection, Escape, and **Auflösen** interactions still work.
|
||||
12. Verify the new control remains usable on a narrow viewport and by keyboard.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- Every non-empty sub-document has a directly accessible download action.
|
||||
- The action downloads one PDF containing only that sub-document's ordered attachments.
|
||||
- Individual and archive-level exports use the same filename convention and merge implementation.
|
||||
- Empty sub-documents cannot be exported.
|
||||
- Loading and error states are visible, duplicate operations are prevented, and state always resets.
|
||||
- Existing archive-level export behavior and all editor interactions are preserved.
|
||||
- `pnpm check`, `pnpm lint`, and `pnpm build` pass without new issues.
|
||||
Reference in new issue
Block a user