260 lines
13 KiB
Markdown
260 lines
13 KiB
Markdown
# Plan: Start-page redesign — PDF toolbox
|
||
|
||
## Goal
|
||
|
||
Today the start page (`src/routes/+page.svelte`) offers exactly one entry point: the ZIP dropzone
|
||
that starts the beA archive workflow. This redesign turns the start page into a **toolbox hub**:
|
||
|
||
1. The existing **ZIP upload** (beA workflow) stays available — visually prominent as the
|
||
"hero" action of the product.
|
||
2. Eight additional tools become reachable as tiles from the start page:
|
||
- **Wasserzeichen** — add a watermark to a PDF
|
||
- **Signieren** — sign a PDF
|
||
- **Passwort setzen** — encrypt a PDF with a password
|
||
- **Passwort entfernen** — unlock a password-protected PDF
|
||
- **Konvertieren** — convert a PDF
|
||
- **Komprimieren** — compress a PDF
|
||
- **Seiten entfernen** — remove pages from (separate) a PDF
|
||
- **PDFs zusammenfügen** — merge PDFs
|
||
|
||
All processing remains **100 % client-side** (no uploads, no network requests, no persistence),
|
||
in line with the project's privacy constraints.
|
||
|
||
---
|
||
|
||
## Target UX
|
||
|
||
### Start page (`/`)
|
||
|
||
- The Nominandum header/footer and overall visual language stay untouched.
|
||
- A compact hero area with the product name and a one-line value proposition.
|
||
- A **large primary tile** for the beA workflow ("ZIP-Archiv bearbeiten") — it keeps the
|
||
full-height dropzone feel: dragging a ZIP anywhere on the start page still starts the workflow.
|
||
- A **grid of secondary tool tiles** (2–3 columns responsive, `grid` + `gap-*`, white cards on the
|
||
pale blue-gray background, Lucide icon + German title + one-line description, visible focus and
|
||
keyboard operability — tiles are real links/buttons, not divs).
|
||
- German copy throughout (project constraint).
|
||
|
||
### Tool pages
|
||
|
||
- Each tool gets its **own route** under `src/routes/tools/…` (see D1):
|
||
`tools/bea`, `tools/merge`, `tools/separate`, `tools/watermark`, `tools/sign`,
|
||
`tools/encrypt`, `tools/decrypt`, `tools/convert`, `tools/compress`.
|
||
- A shared layout (`tools/+layout.svelte`) provides a consistent shell: back-link to `/`,
|
||
tool title, and the file-pick area.
|
||
- The existing ZIP workflow component tree (`ZipDropzone`, `ProcessedZipArchiveEditor`,
|
||
`SubDocumentEditor`, `AttachmentPreview`) moves to `tools/bea` essentially unchanged.
|
||
|
||
---
|
||
|
||
## Technical approach per tool
|
||
|
||
| Tool | Route | Input | Processing | Output |
|
||
| ----------------------- | ----------------- | ------------------------ | ------------------------------------ | ---------------------- |
|
||
| beA workflow | `tools/bea` | `.zip` (multiple) | unchanged existing pipeline | merged/separate PDFs |
|
||
| Merge | `tools/merge` | multiple PDFs | pdf-lib copy of pages in order | one PDF |
|
||
| Separate / remove pages | `tools/separate` | one PDF | pdf-lib copy with page selection | one PDF |
|
||
| Watermark | `tools/watermark` | one PDF | pdf-lib overlay on every page | one PDF |
|
||
| Sign | `tools/sign` | one PDF | pdf-lib places signature image | one PDF |
|
||
| Encrypt | `tools/encrypt` | one PDF | encryption library (see D5) | password-protected PDF |
|
||
| Unlock | `tools/decrypt` | protected PDF + password | pdfjs (password) → rebuild (see D6) | unprotected PDF |
|
||
| Compress | `tools/compress` | one PDF | pdfjs rasterize + re-encode (see D7) | smaller PDF |
|
||
| Convert | `tools/convert` | one PDF | pdfjs render pages (see D4) | images or PDF variant |
|
||
|
||
### 1. Merge PDFs
|
||
|
||
- Load each selected PDF with pdf-lib, copy pages in the (reorderable) list order into a new
|
||
document. This is a generalization of the existing `mergeProcessedZipArchive` logic — extract a
|
||
reusable `mergePdfFiles(files: File[]): Promise<Uint8Array>` helper.
|
||
|
||
### 2. Separate / remove pages
|
||
|
||
- Render page thumbnails (reuse the pdfjs pattern from `AttachmentPreview.svelte`), show a page
|
||
grid with selection; the user deselects/removes pages and downloads the remaining document.
|
||
- Page selection must also work with keyboard focus (project accessibility rule).
|
||
|
||
### 3. Watermark
|
||
|
||
- Options: text (or uploaded PNG/JPG image), position (center/diagonal/corners), opacity, font
|
||
size, page range (all / selected). Drawn with pdf-lib `drawText`/`drawImage` on each selected page.
|
||
- No new dependency.
|
||
|
||
### 4. Sign
|
||
|
||
- Default proposal (see D3): **visual signature**. The user draws a signature in a canvas element
|
||
(mouse/touch/pen), or types a name in a handwriting-like font, or uploads a signature image.
|
||
The signature is exported as a transparent PNG and placed by clicking/tapping the desired
|
||
position on a rendered page (pdfjs preview + pdf-lib placement).
|
||
- PAdES/cryptographic signing is **not** part of the default scope.
|
||
|
||
### 5. Set password (encrypt)
|
||
|
||
- pdf-lib does **not** support encryption → a new dependency is unavoidable (see D5).
|
||
Primary candidate: `@cantoo/pdf-lib` (drop-in fork of pdf-lib with AES encryption support),
|
||
alternative: `qpdf-wasm`.
|
||
- UI: password + confirmation field, optional permissions flags (print/copy allowed?).
|
||
|
||
### 6. Unlock (remove password)
|
||
|
||
- pdfjs-dist can open password-protected PDFs when given the password; pdf-lib cannot load them.
|
||
- Default approach (see D6): open with pdfjs using the password, then **rasterize** each page to a
|
||
canvas and rebuild a PDF via pdf-lib. Known tradeoff: text becomes images (no selectable text,
|
||
larger file, no vector quality). This is documented to the user in the UI.
|
||
- Failing passwords must surface a clear German error message, not a console-only error.
|
||
|
||
### 7. Compress
|
||
|
||
- Browser-only compression is realistically rasterization-based: render each page with pdfjs at a
|
||
chosen DPI/scale, encode as JPEG at a chosen quality, rebuild the PDF.
|
||
- UI presents target presets (e.g. "Starke Komprimierung" / "Ausgewogen" / "Leicht") and shows the
|
||
resulting size estimate before download. Same vector→raster caveat as unlock, shown in the UI.
|
||
|
||
### 8. Convert
|
||
|
||
- Default scope (see D4): **PDF → images** (PNG or JPEG, one file per page, downloaded as separate
|
||
files or a ZIP via fflate — fflate is already a dependency).
|
||
- Other directions (images/Office → PDF) already exist in the beA flow for images; extending
|
||
convert beyond PDF→images is out of scope for v1.
|
||
|
||
---
|
||
|
||
## Shared infrastructure (new `src/lib` pieces)
|
||
|
||
- **`PdfDropzone` component** — generalization of `ZipDropzone` with configurable `accept` filter
|
||
(`.pdf` vs `.zip`) and drag state. `ZipDropzone` is refactored on top of it (or replaced).
|
||
- **`usePdfThumbnails` helper / `PdfPageGrid` component** — shared page-thumbnail rendering for
|
||
separate/sign/watermark/convert/compress, modeled on `AttachmentPreview.svelte`'s memory rules
|
||
(dynamic pdfjs import, byte copies before render, revoked object URLs, destroyed loading tasks).
|
||
- **`downloadBlob()` helper** — the current inline `downloadPdfBytes` in `+page.svelte` moves to a
|
||
shared `src/lib/download.ts` and gains a generic filename/MIME signature.
|
||
- **Tool registry** (`src/lib/tools.ts`) — typed list of `{ slug, title, description, icon }`
|
||
consumed by the start page tile grid, so adding a tool later means one entry + one route.
|
||
- All browser-only work stays in event handlers / `onMount` (SSR-safe, per `agents.md`).
|
||
- File-size/memory guard: cap or warn on very large PDFs before rasterization-heavy tools
|
||
(unlock/compress/convert).
|
||
|
||
## Dependencies
|
||
|
||
- **New runtime dependency** (only for encrypt): `@cantoo/pdf-lib` **or** `qpdf-wasm` — decision D5.
|
||
All other tools run on the existing stack (pdf-lib, pdfjs-dist, fflate).
|
||
|
||
---
|
||
|
||
## Implementation phases
|
||
|
||
Each phase ends with `pnpm check`, `pnpm lint`, `pnpm build` and a manual browser pass
|
||
(prettier formatting on changed files).
|
||
|
||
### Phase 0 — Groundwork
|
||
|
||
1. Create `tools/+layout.svelte` shell and `src/lib/tools.ts` registry.
|
||
2. Move the existing ZIP workflow from `+page.svelte` to `tools/bea/+page.svelte` unchanged
|
||
(keep the settings popover, thumbnail width localStorage, bulk export).
|
||
3. Build the new start page: hero + beA tile + (initially) tool tiles that link to stub routes
|
||
with a "coming soon" state? — no: see D8; default is to ship phases together and leave
|
||
tiles hidden until their tool exists.
|
||
4. Update `agents.md` repository structure and flow sections.
|
||
|
||
### Phase 1 — No-new-dependency tools
|
||
|
||
1. `PdfDropzone`, `download.ts`, `PdfPageGrid`/thumbnail helper.
|
||
2. `tools/merge` (reusable `mergePdfFiles` helper).
|
||
3. `tools/separate` (page grid, selection, export).
|
||
4. `tools/watermark`.
|
||
|
||
### Phase 2 — Signature
|
||
|
||
1. `tools/sign`: canvas signature capture (draw/type/upload), placement preview, export.
|
||
|
||
### Phase 3 — Password tools
|
||
|
||
1. Add the chosen encryption dependency; `tools/encrypt`.
|
||
2. `tools/decrypt` (pdfjs password + rasterized rebuild) with explicit quality warning in UI.
|
||
|
||
### Phase 4 — Rasterization tools
|
||
|
||
1. `tools/compress` (DPI/quality presets, size preview).
|
||
2. `tools/convert` (PDF → PNG/JPEG, separate files or ZIP).
|
||
|
||
### Phase 5 — Polish
|
||
|
||
1. Start-page tile copy and icons review (German, Nominandum visual language).
|
||
2. Final `check`/`lint`/`build`, update `agents.md`, README feature list.
|
||
|
||
---
|
||
|
||
## Open decisions (please confirm before implementation)
|
||
|
||
Defaults are marked ✅.
|
||
|
||
### D1 — One page per tool vs. single-page app
|
||
|
||
- ✅ **A. Separate routes** (`/tools/<slug>`). Better deep-linking/bookmarking, per-tool state is
|
||
isolated, back-navigation is natural, and SvelteKit gives us the structure for free.
|
||
- B. Single start page where each tile opens an in-place panel/modal. Keeps everything on `/` but
|
||
concentrates a lot of state in one component and hurts URL shareability.
|
||
|
||
### D2 — Where does the ZIP workflow live?
|
||
|
||
- ✅ **A. `/tools/bea`, start page shows it as the large primary tile.** Drag-a-ZIP-anywhere on the
|
||
start page still routes into the workflow, preserving today's fastest path.
|
||
- B. ZIP workflow stays on `/` and tools branch off it. Rejected: makes `/` a mixed
|
||
dropzone+dashboard and complicates the page.
|
||
|
||
### D3 — What does "sign" mean?
|
||
|
||
- ✅ **A. Visual signature** (drawn/typed/uploaded image placed on the page). No crypto, no
|
||
certificates, no PAdES.
|
||
- B. Cryptographic/PAdES signing. Large scope (certificate handling, new heavy dependencies,
|
||
legal implications) — recommend a separate project if needed.
|
||
|
||
### D4 — What does "convert" mean (v1)?
|
||
|
||
- ✅ **A. PDF → PNG/JPEG** (per-page images, optionally bundled into a ZIP with fflate).
|
||
- B. PDF → PDF/A. There is no solid browser-only PDF/A converter; would need a new heavy
|
||
dependency or a server.
|
||
- C. Images/Office → PDF. Image→PDF already exists inside the beA flow; Office formats are
|
||
out of scope.
|
||
|
||
### D5 — Encryption dependency
|
||
|
||
- ✅ **A. `@cantoo/pdf-lib`** — API-compatible fork of pdf-lib with encryption; smallest migration
|
||
risk (drop-in swap of the pdf-lib import in the encrypt tool only).
|
||
- B. `qpdf-wasm` — battle-tested engine, but WASM bundle size and wiring cost are significant.
|
||
- C. Server-side encryption — violates the no-upload constraint; rejected.
|
||
|
||
### D6 — Unlock strategy
|
||
|
||
- ✅ **A. pdfjs (with password) → rasterize pages → rebuild with pdf-lib.** Works fully in the
|
||
browser; document the vector→raster quality loss in the UI before the user commits.
|
||
- B. Await/patch pdf-lib fork decryption support — currently not available for browser use.
|
||
- C. Server-side qpdf — violates the no-upload constraint; rejected.
|
||
|
||
### D7 — Compress strategy
|
||
|
||
- ✅ **Rasterize-and-re-encode** (pdfjs render + JPEG at chosen quality). Predictable, browser-only;
|
||
shows the same quality caveat as unlock.
|
||
- B. "Structure-only" optimization (remove metadata, deduplicate objects). pdf-lib offers little
|
||
here; savings are usually marginal. Could be added as a light pre-step within the same tool.
|
||
|
||
### D8 — Release strategy for tiles
|
||
|
||
- ✅ **A. Ship it whole / hide unfinished tiles.** Only tiles whose route works are rendered
|
||
(driven by the tool registry), so phases can be merged without dead links.
|
||
- B. Show all tiles immediately with "in Kürze verfügbar" badges — acceptable only if the
|
||
redesign itself must go live before all tools are done.
|
||
|
||
---
|
||
|
||
## Risks and mitigations
|
||
|
||
- **Rasterization quality/memory** (unlock, compress, convert): large PDFs can exhaust browser
|
||
memory when rendering many pages at high DPI. Mitigate with sequential page processing, DPI
|
||
caps, and an upfront file-size warning.
|
||
- **`@cantoo/pdf-lib` maturity**: it is a fork; keep the pdf-lib import isolated to the encrypt
|
||
tool so a swap back (or to qpdf-wasm) stays local.
|
||
- **Scope creep on "sign"/"convert"**: the open decisions above pin v1 scope deliberately; crypto
|
||
signing and PDF/A conversion are explicitly out.
|
||
- **Regression risk for the existing workflow**: Phase 0 is a pure move of the current page;
|
||
verify sub-document editing, XJustiz naming, and bulk export still work after relocation.
|