8.7 KiB
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.svelteowns archive-level download state and the browser download helper.SubDocumentEditor.svelterenders each sub-document but currently exposes only edit, drag/drop, and dissolve callbacks.mergeAttachments()insrc/lib/zip-processing.tsalready 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.attachmentsexactly. - 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:
export const buildSubDocumentExport = async (
archiveName: string,
subDocument: SubDocument
): Promise<ExportUnit>
Behavior:
- Reject an empty attachment list with the same explicit empty-export behavior used by
mergeAttachments(). - Merge
subDocument.attachmentsby callingmergeAttachments(). - Return one
ExportUnitusing the existing archive/sub-document filename convention. - Keep
.pdfnormalization centralized through the existingstripPdfExtension()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 | nullstate alongsideisMergingandmergeError. - Derive whether any export is active from
isMerging || downloadingSubDocumentId !== null. - Add
downloadSubDocument(subDocument: SubDocument)that:- Returns when another export is active or the sub-document is empty.
- Stores the selected sub-document ID and clears
mergeError. - Calls
buildSubDocumentExport(archiveName, subDocument). - Passes the returned bytes and name to the existing
downloadPdfBytes()helper. - On failure, logs context including the archive and sub-document and sets
Teildokument „{name}“ konnte nicht als PDF erstellt werden.. - Clears the active ID in
finally.
- Update
downloadArchive()to guard against any active export, not onlyisMerging. - 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
Buttoncomponent. - Extend
Propswith:onDownload?: () => voidisDownloading?: booleandownloadDisabled?: 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, orattachments.length === 0
- Keep the control as a native button through the shared
Buttoncomponent, 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.
- Add
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:
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
- Create an archive with two sub-documents and at least one loose attachment.
- Download the first sub-document and verify exactly one PDF is triggered.
- Confirm the PDF contains only the first sub-document's attachments and preserves their displayed order.
- Confirm the filename is
{archive} - {sub-document}.pdfwithout duplicate.pdfsuffixes. - Download the second sub-document and verify it has independent contents and naming.
- Confirm an empty sub-document's download action is disabled.
- Start an individual export and verify duplicate, sibling, and archive-level export actions cannot start until it finishes.
- Force an invalid PDF/image merge and verify a German inline error appears, loading state clears, and editor state is unchanged.
- Verify Eine PDF still exports all sub-documents followed by loose attachments.
- Verify Getrennt still exports every non-empty sub-document and the loose-attachment PDF.
- Verify rename, reorder, move, selection, Escape, and Auflösen interactions still work.
- 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, andpnpm buildpass without new issues.