Files
bea-edit/plan/done/metadata-plan.md
T
2026-10-06 22:52:35 +02:00

140 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Plan: PDF-Metadaten — anzeigen und bereinigen
## Goal
Add a new tool **"Metadaten"** (`/tools/metadata`) with two jobs:
1. **Inspect** a PDF: show document metadata (title, author, subject, keywords, creator,
producer, creation/modification dates), page count, file size, and encryption state —
lawyers need to know what a document reveals before forwarding it.
2. **Clean** a PDF: strip the metadata fields and re-save, so the cleaned file can be shared
without leaking software, author names, or edit history.
No rasterization is involved — this tool works directly with pdf-lib and preserves the document
fully (vector text stays selectable). No new dependencies.
---
## Open decisions (defaults marked ✅)
### D1 — Cleaning strategy
- ✅ **A. Neutralize fields + re-save with pdf-lib.** Set title/author/subject/keywords to empty
strings, creator/producer to neutral values, reset creation/modification dates to the cleaning
moment. Saving through pdf-lib rebuilds the file without unreferenced objects. Simple and
dependency-free.
- B. Also attempt to remove embedded files/attachments. pdf-lib has no reliable public API to
enumerate or remove all embedded-file entries; defer to D2.
### D2 — Embedded-file detection
- ✅ **A. Best-effort heuristic warning.** Scan the raw bytes for `/EmbeddedFile` and
`/EmbeddedFiles` markers; if found, show "Die Datei enthält möglicherweise eingebettete
Anlagen. Diese werden beim Bereinigen nicht entfernt." No removal in v1.
- B. No detection at all. Rejected: silent ignorance is worse than an honest warning.
### D3 — Encrypted PDFs
- ✅ **A. Friendly rejection.** pdf-lib cannot load encrypted files; show a German hint pointing
to `tools/decrypt` first. No silent failure.
---
## UX
- Route `/tools/metadata`, new entry in `src/lib/tools.ts`:
`{ slug: 'metadata', title: 'Metadaten', description: 'Zeigen Sie Metadaten eines PDFs an und entfernen Sie sie.', icon: 'info' }`
- icon mapping on the start page.
- Flow: `PdfDropzone` (single PDF) → results card with:
- **Info table** (label/value rows, empty values shown as "–"): Titel, Autor, Betreff,
Schlüsselwörter, Erstellt mit (creator), PDF-Erzeuger (producer), Erstellt am, Geändert am,
Seiten, Größe, Verschlüsselung (Ja/Nein — from load success/failure is insufficient; use
pdf-lib's load error for encrypted detection and show it as a state, or report "Unbekannt"
when not determinable).
- **Embedded-file warning block** when the heuristic fires (amber styling consistent with
existing warning patterns).
- **Quality note**: "Beim Bereinigen bleibt der Inhalt vollständig erhalten (kein Qualitätsverlust)."
- Actions:
- **"Bereinigt herunterladen"** (primary, disabled for encrypted files) → cleaned PDF named
`{baseName}_bereinigt.pdf`.
- Secondary: "Original herunterladen" is _not_ offered (the user already has the original).
- German inline error area for corrupt/encrypted files, matching existing tool pages.
## Technical approach
New route `src/routes/tools/metadata/+page.svelte` plus a small lib module
`src/lib/pdf-metadata.ts`:
```ts
export type PdfMetadataInfo = {
title: string;
author: string;
subject: string;
keywords: string[];
creator: string;
producer: string;
creationDate: Date | null;
modificationDate: Date | null;
pageCount: number;
embeddedFileSuspected: boolean;
};
export const readPdfMetadata = async (bytes: Uint8Array): Promise<PdfMetadataInfo>;
export const stripPdfMetadata = async (bytes: Uint8Array): Promise<Uint8Array>;
```
- `readPdfMetadata`: `PDFDocument.load(bytes)` (with `{ ignoreEncryption: false }` default so
encrypted files throw), then the `getTitle()`/`getAuthor()`/`getSubject()`/`getKeywords()`/
`getCreator()`/`getProducer()`/`getCreationDate()`/`getModificationDate()`/`getPageCount()`
accessors. Encryption detection: catch the pdf-lib `EncryptedPDFError` and return a distinct
`{ encrypted: true }` result shape (discriminated union) so the UI can branch.
- `stripPdfMetadata`: load, apply setters (`setTitle('')`, `setAuthor('')`, `setSubject('')`,
`setKeywords([])`, `setCreator('')`, `setProducer('beA-Edit')`, `setCreationDate(new Date())`,
`setModificationDate(new Date())`), `save()`.
- Embedded-file heuristic: decode bytes with `TextDecoder` once and search for the two marker
strings; cheap, runs before pdf-lib load.
- Reading metadata does **not** need pdfjs or thumbnails — no rasterization limits apply; large
files are fine because pdf-lib parses lazily enough for this purpose.
## Files changed
- `src/lib/pdf-metadata.ts` — new.
- `src/routes/tools/metadata/+page.svelte` — new route.
- `src/lib/tools.ts`, `src/routes/+page.svelte` — registry entry + icon map.
- `tests/e2e/toolbox.spec.ts` — new spec block.
- `agents.md` — structure update.
## Implementation phases
1. `pdf-metadata.ts` with read + strip + heuristic.
2. Route UI: info table, warning block, clean action, encrypted-file branch.
3. Registry entry + icon.
4. Playwright tests: PDF created with `createPdf` + set metadata fields → fields displayed;
clean download loads with pdf-lib and has empty author/title; encrypted-PDF rejection hint
(reuse the encrypt tool to produce a fixture inline).
5. Polish, German copy review, `pnpm check`/`lint`/`build`/`test:e2e`.
## Verification
- Metadata of a known PDF renders completely; empty fields show "–".
- Cleaned PDF: open in viewer, metadata fields empty; content and page count identical; file
still selectable/searchable (no rasterization happened).
- Encrypted input shows the decrypt hint instead of crashing.
- `/EmbeddedFile`-containing fixture triggers the amber warning.
- Standard command gauntlet passes.
## Risks
- **XMP metadata**: pdf-lib's setters touch the Info dict; a separate XMP metadata stream can
survive. Mitigation: note in the UI copy that cleaning covers standard document properties.
(Full XMP removal could be a follow-up by deleting the metadata stream object.)
- **Producer fingerprint**: setting `beA-Edit` as producer is itself a small fingerprint; neutral
but acceptable — alternative is an empty string; pick empty string if reviewers prefer.
## Out of scope
- Removing embedded files/attachments (see D2).
- XMP stream removal (follow-up).
- Metadata editing (rename title, etc.) — inspection + removal only.
- Hooking metadata cleaning into the beA export pipeline.