316 lines
16 KiB
Markdown
316 lines
16 KiB
Markdown
# 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:
|
||
|
||
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 `<details>` 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.
|