# Plan: beA settings menu and “next ZIP” workflow **Status: Done** ## Current review The loaded-archive state in `src/routes/tools/bea/+page.svelte` uses a native `
` menu fixed in the lower-right corner. I reviewed it at 1440 × 900 and 390 × 844 with a real one-PDF ZIP. ### What is not working - The floating gear is detached from the archive it controls and competes with the footer/content edge. On mobile, the open panel overlaps the archive card. - Two long actions are forced side by side into a 288 px panel. Their labels visibly clip to “Archive als PDF” and “Archive”, so the difference between export and delete is unclear. - The emoji gear does not match the app's Lucide icon language. Its accessible name is the English word “Settings” in an otherwise German interface. - View configuration, bulk export, and destructive session deletion are mixed without hierarchy. “Alle ZIP-Archive löschen” receives nearly the same visual weight as export and has no confirmation. - “Thumbnail Breite” is an implementation-oriented, partly English label. The control should be described as the user-visible result: “Vorschaugröße”. - Native `
` does not provide the expected menu behavior here: no reliable outside-click dismissal, weak focus management, and no deliberate focus return. - Once any ZIP has loaded, the dropzone disappears. Although the route can append files internally, the user has no control to open another ZIP or begin a clean next task. - The loaded archive is vertically centered in a viewport-height container. This creates a large gap below “beA-Edit” on both desktop and mobile and makes the fixed settings control feel even more disconnected. ## Product decision Use two explicit archive lifecycle actions: 1. **“Neue ZIP bearbeiten”** is the prominent next-task action. It replaces the current workspace only after the user has selected a valid ZIP and confirmed the replacement. This directly serves the requested “I am done; give me the next ZIP” workflow. 2. **“Weitere ZIP hinzufügen”** remains available inside the actions panel for users intentionally working with multiple ZIPs at once. It appends to the current workspace, preserving the behavior already supported by `handleFilesSelected`. The replacement confirmation is shown _after_ file selection. Cancelling either the system picker or confirmation must leave the current work untouched. The confirmation says that the current view and unexported changes will be removed, while files already downloaded remain on the device. Do not try to infer that a user is “done” from a download click. A download does not prove that every wanted output was saved, and the browser cannot reliably verify what happened to the file afterward. ## UX direction ### Desktop ```text ← Zurück beA-Edit 1 ZIP · 1 Archiv geöffnet [+ Neue ZIP bearbeiten] [⚙ Einstellungen] ────────────────────────────────────────────────────────────────────────────────────────── ┌ Current archive editor ────────────────────────────────────────────────────────────────┐ │ archive name [Auswählen] [PDF herunterladen] │ │ attachment previews … │ └────────────────────────────────────────────────────────────────────────────────────────┘ ``` Settings popover, aligned to the right edge of the toolbar: ```text ┌ Einstellungen ─────────────────────────────┐ │ ANSICHT │ │ Vorschaugröße 200 px │ │ Klein ─────────●────────────── Groß │ │ │ │ ARCHIV-AKTIONEN │ │ [+ Weitere ZIP hinzufügen] │ │ [⇩ Alle Archive als PDF herunterladen] │ shown only when useful │ ───────────────────────────────────────── │ │ [🗑 Aktuelle Bearbeitung leeren] │ quiet destructive action └───────────────────────────────────────────┘ ``` ### Mobile The toolbar becomes a two-row block immediately below the title and remains in normal document flow. The status occupies the first row; the two buttons share the second row. The popover uses `width: min(22rem, calc(100vw - 2rem))`, stacks every action vertically, and relies on collision-aware positioning rather than fixed bottom/right coordinates. No control or panel may cover the archive header. The loaded workspace becomes top-aligned (`justify-start`) instead of viewport-centered, removing the large empty band between the page heading and first archive. ## Visual system Preserve the existing Nominandum identity rather than introducing a new theme: - **Ink** `#122F62`: headings, primary actions, and active controls. - **Workspace** `#EEF4F6`: page background. - **Paper** `#FFFFFF`: editor and popover surfaces. - **Handoff coral** `#EA544B`: restrained accent only; do not use it to make deletion look primary. - **Success** `#4E8A72`: completed processing state. - Typography remains **Nunito** for interface copy; use the existing Geist utility face only where numeric/file metadata already uses it. - Use existing radii and semantic CSS variables from `src/routes/layout.css`; no theme-level CSS changes should be necessary. The distinctive element is the **archive handoff row**: current-work status on the left and “Neue ZIP bearbeiten” on the right. It expresses the actual legal-document rhythm—finish one beA export, hand it off, open the next—rather than presenting a generic floating settings cog. Self-critique: a card full of settings tabs would be a generic dashboard solution and is unnecessary for one view preference. The revised direction removes the floating action, keeps the next-case task visible, and spends visual emphasis only on the archive handoff. ## Interaction details ### Toolbar - Status copy derives from state: - `1 ZIP · 1 Archiv geöffnet` - `2 ZIPs · 3 Archive geöffnet` - while work is pending, append `· 1 wird geöffnet` in an `aria-live="polite"` status. - “Neue ZIP bearbeiten” uses Lucide `FilePlus2` and remains available after the first archive or an error. It opens a ZIP-only file picker. - “Einstellungen” uses Lucide `Settings2`, has a visible German label at desktop sizes, and retains an explicit accessible name at all widths. - Do not make the toolbar fixed or sticky in v1. Normal flow avoids covering previews and footer content; sticky behavior can be reconsidered after testing long archives. ### New ZIP replacement 1. User activates “Neue ZIP bearbeiten”. 2. The system file picker opens and accepts the same ZIP types as `ZipDropzone`. 3. If the picker is cancelled, nothing changes. 4. If valid files are selected, show an `AlertDialog`: - title: **“Aktuelle Bearbeitung ersetzen?”** - description: **“Archive und nicht heruntergeladene Änderungen in dieser Ansicht werden entfernt. Bereits heruntergeladene PDFs bleiben erhalten.”** - cancel: **“Abbrechen”** - confirm: **“Neue ZIP öffnen”** 5. On confirm, increment `archiveGeneration`, clear selected files, processed archives, and jobs, then enqueue the newly selected files against the new generation. 6. If extraction fails, show the existing processing error state for the new ZIP and allow retry or removal; do not restore the old workspace implicitly. Keep the selected replacement files in temporary component state until confirmation. This prevents a cancelled dialog from mutating or clearing the current workspace. Reset the underlying file input after every selection so choosing the same ZIP again still fires `change`. ### Append, export, and clear - “Weitere ZIP hinzufügen” uses the existing append path and never clears current archives. - Show bulk export only when there are at least two processed archives; one archive already has its own clearer “PDF herunterladen” action. - Rename bulk export to **“Alle Archive als PDF herunterladen”**. - Rename deletion to **“Aktuelle Bearbeitung leeren”** and place it after a separator as a quiet destructive menu item with `Trash2`. - Clearing requires its own `AlertDialog`; confirmation calls the existing reset path and returns the user to the initial ZIP dropzone. - Disable replacement, append, export, and clear while a destructive transition is being committed, but allow a new selection while archive extraction is in progress; generation guarding prevents stale results from returning. ### View setting - Replace the raw range with the shadcn `Slider` labeled **“Vorschaugröße”**. - Keep the current `100–400 px` range, `1 px` step, clamp function, and `thumbnailWidth` localStorage key for backward compatibility. - Display the current value as tabular numerals and include “Klein”/“Groß” endpoint hints. - The label and current value must be associated with the slider for assistive technology. Arrow-key adjustment remains available. ## Component and state design ### New `BeaWorkspaceControls.svelte` Create `src/lib/components/BeaWorkspaceControls.svelte` to keep the already state-heavy route from absorbing menu markup. Suggested props: ```ts type Props = { selectedZipCount: number; processedArchiveCount: number; pendingZipCount: number; thumbnailWidth: number; onThumbnailWidthChange: (value: number) => void; onRequestReplacement: () => void; onAppendFiles: (files: File[]) => void; onExportAll: () => void; onClear: () => void; }; ``` The component owns only ephemeral UI state (popover/dialog open state and temporarily selected replacement files). The route remains the source of truth for files, jobs, archives, and the persisted thumbnail width. Centralize the ZIP accept string and validation used by the initial dropzone and both compact pickers. Prefer extracting a small reusable ZIP picker/input rather than duplicating MIME and extension checks inside the controls component. ### Route changes In `src/routes/tools/bea/+page.svelte`: - Replace `deleteAllZipArchives` with a single `resetWorkspace()` helper that increments `archiveGeneration` and clears all three state arrays. - Add `replaceWithZipFiles(files)` that calls `resetWorkspace()` and then `handleFilesSelected(files)`; the new jobs must capture the incremented generation. - Keep `handleFilesSelected` as the append operation. - Render `BeaWorkspaceControls` whenever at least one selected file/job/archive exists. - Remove the fixed `
` block. - Top-align the loaded-workspace container and render status/controls before processing and editor cards. - Preserve `thumbnailWidth` hydration and persistence exactly as today. ### shadcn-svelte pieces Add only the required official components with the pnpm CLI, reviewing generated files and imports: - `popover` - `slider` - `separator` - `alert-dialog` Do not overwrite the customized button component. The project currently imports `$lib/components/ui/button.svelte` directly, so adapt generated imports to the local export shape if the registry assumes a barrel. Use Lucide Svelte icons (`FilePlus2`, `Settings2`, `Download`, `Trash2`) with `data-icon` inside buttons. ## Files - `src/routes/tools/bea/+page.svelte` — workspace lifecycle, reset/replace handlers, and layout. - `src/lib/components/BeaWorkspaceControls.svelte` — new toolbar, popover, dialogs, and picker coordination. - `src/lib/components/ZipDropzone.svelte` and/or a new small ZIP picker helper — shared accept and selection behavior. - `src/lib/components/ui/{popover,slider,separator,alert-dialog}/` — official shadcn-svelte source. - `tests/e2e/toolbox.spec.ts` — archive handoff, menu, keyboard, and responsive coverage. - `agents.md` — document append versus replace semantics and the shared reset invariant. No changes are expected in `zip-processing.ts`, archive transforms, export ordering, or server code. ## Implementation phases 1. **Shared ZIP selection and state helpers** - Centralize accepted ZIP types. - Introduce `resetWorkspace` and `replaceWithZipFiles`. - Verify generation guarding when replacement occurs during extraction. 2. **Workspace toolbar and layout** - Add the top-aligned status/action row. - Implement the replacement picker and confirmation without removing current work on cancel. - Remove the fixed gear and viewport-centering. 3. **Settings popover** - Add the responsive popover, slider, append, conditional bulk export, and confirmed clear. - Verify keyboard focus, Escape/outside-click dismissal, and focus return. 4. **Tests and polish** - Add e2e coverage below. - Run desktop/mobile browser review and remove any clipped or overlapping labels. - Update `agents.md` and run the full validation set. ## Verification ### Automated Add Playwright cases for: - Load ZIP A → “Neue ZIP bearbeiten” appears → select ZIP B → cancel confirmation → ZIP A remains. - Repeat replacement → confirm → only ZIP B is shown and no late result from ZIP A can reappear. - Cancel the system file picker → workspace is unchanged. - Select the same ZIP twice through replacement → the second `change` is processed. - “Weitere ZIP hinzufügen” appends ZIP B and both archive editors remain visible. - Bulk export is absent for one processed archive and available for two or more. - “Aktuelle Bearbeitung leeren” cancel preserves state; confirm returns to the initial dropzone. - Slider changes preview width, works with arrow keys, and survives a reload through localStorage. - Settings opens with a German accessible name, closes on Escape/outside click, and returns focus to its trigger. - At 390 × 844, toolbar actions and every popover label fit without horizontal clipping; the panel stays inside the viewport and does not cover the archive header. Then run: ```sh pnpm check pnpm test:e2e pnpm lint pnpm build ``` ### Manual - Review 1440 × 900 and 390 × 844 with one archive, multiple archives, an extraction in progress, and an extraction error. - Confirm file processing remains entirely local and no case data is persisted. - Confirm tab order is title/status → new ZIP → settings → popover controls → archive controls. - Confirm reduced-motion mode introduces no menu animation requirement. ## Risks and mitigations - **Accidental loss during replacement:** current state is not cleared until valid files are selected and the user confirms. - **Stale async extraction:** every reset increments `archiveGeneration`; old jobs cannot append results afterward. - **Duplicate picker logic:** centralize accept/filter/reset behavior before adding compact actions. - **Component bloat for a small menu:** use one popover and shared alert-dialog primitives; do not add tabs, a permanent sidebar, or a settings route. - **Ambiguous “new” versus “additional”:** keep the labels distinct and stable everywhere: “Neue ZIP bearbeiten” replaces; “Weitere ZIP hinzufügen” appends. ## Out of scope - Persisting archives or edits across reloads. - Detecting whether the operating system completed a download. - Undoing a confirmed workspace replacement. - Changing PDF composition, attachment ordering, or sub-document behavior. - A global application settings page.