16 KiB
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 <details> 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
<details>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:
- “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.
- “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
← 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:
┌ 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öffnet2 ZIPs · 3 Archive geöffnet- while work is pending, append
· 1 wird geöffnetin anaria-live="polite"status.
- “Neue ZIP bearbeiten” uses Lucide
FilePlus2and 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
- User activates “Neue ZIP bearbeiten”.
- The system file picker opens and accepts the same ZIP types as
ZipDropzone. - If the picker is cancelled, nothing changes.
- 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”
- On confirm, increment
archiveGeneration, clear selected files, processed archives, and jobs, then enqueue the newly selected files against the new generation. - 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
Sliderlabeled “Vorschaugröße”. - Keep the current
100–400 pxrange,1 pxstep, clamp function, andthumbnailWidthlocalStorage 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:
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
deleteAllZipArchiveswith a singleresetWorkspace()helper that incrementsarchiveGenerationand clears all three state arrays. - Add
replaceWithZipFiles(files)that callsresetWorkspace()and thenhandleFilesSelected(files); the new jobs must capture the incremented generation. - Keep
handleFilesSelectedas the append operation. - Render
BeaWorkspaceControlswhenever at least one selected file/job/archive exists. - Remove the fixed
<details>block. - Top-align the loaded-workspace container and render status/controls before processing and editor cards.
- Preserve
thumbnailWidthhydration and persistence exactly as today.
shadcn-svelte pieces
Add only the required official components with the pnpm CLI, reviewing generated files and imports:
popoversliderseparatoralert-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.svelteand/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
- Shared ZIP selection and state helpers
- Centralize accepted ZIP types.
- Introduce
resetWorkspaceandreplaceWithZipFiles. - Verify generation guarding when replacement occurs during extraction.
- 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.
- Settings popover
- Add the responsive popover, slider, append, conditional bulk export, and confirmed clear.
- Verify keyboard focus, Escape/outside-click dismissal, and focus return.
- Tests and polish
- Add e2e coverage below.
- Run desktop/mobile browser review and remove any clipped or overlapping labels.
- Update
agents.mdand 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
changeis 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:
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.