Files
bea-edit/agents.md
T
rehlert 57b38fe2c7
Build and Push Docker Image / build (push) Successful in 54s
bea settings
2026-08-27 18:15:22 +02:00

272 lines
15 KiB
Markdown

# Project Agent Guide
## Project overview
This repository contains **juri-merger**, a German-language SvelteKit application branded as
**beA-Edit**. Users add one or more ZIP archives in the browser, inspect and reorder supported
attachments, optionally group attachments into named sub-documents, and export merged PDFs.
Nested ZIP archives and XJustiz metadata are handled client-side; there is no upload, server-side
archive processing, database, analytics, or persistence of case data.
The application also contains German imprint and privacy-policy routes.
## Technology stack
- SvelteKit 2 and Svelte 5, with runes mode forced for project source files
- TypeScript in strict mode
- Vite 8
- Tailwind CSS 4 through `@tailwindcss/vite`
- shadcn-svelte 1 with the `nova` style and CSS-variable theme tokens
- bits-ui, tailwind-variants, clsx, and tailwind-merge
- Lucide Svelte icons plus project-specific SVG icon components
- fflate for ZIP extraction and image-export ZIP creation
- pdf-lib for PDF assembly, `@cantoo/pdf-lib` for encryption, and pdfjs-dist for rendering
- `@sveltejs/adapter-node`
- pnpm with a committed lockfile
- Prettier with Svelte and Tailwind plugins
Use **pnpm**, not npm or yarn. The production container uses Node 24 and pnpm 11.15.1.
## Commands
```sh
pnpm install # Install dependencies
pnpm dev # Start the development server
pnpm build # Create the adapter-node production build
pnpm preview # Preview the production build
pnpm check # Run Svelte and TypeScript diagnostics
pnpm check:watch # Run diagnostics in watch mode
pnpm test:e2e # Run the Playwright toolbox tests in Chromium
pnpm lint # Check repository formatting with Prettier
pnpm format # Format the repository with Prettier
```
For code changes, run at least:
```sh
pnpm check
pnpm test:e2e
pnpm lint
pnpm build
```
For documentation-only changes, a targeted Prettier check is sufficient. If pre-existing warnings
or formatting failures remain outside changed files, report them instead of making unrelated edits.
The Docker image can be built from the repository root. It installs with `--frozen-lockfile`, builds
and prunes dependencies, then runs `node build/index.js` as a non-root user on port 3000.
## Repository structure
```text
src/
├── app.html # German HTML shell
├── app.d.ts # SvelteKit application declarations
├── lib/
│ ├── actions/ripple.ts # Button ripple Svelte action
│ ├── assets/ # Favicon and local Nunito fonts
│ ├── components/
│ │ ├── AttachmentPreview.svelte # Browser-rendered PDF/image thumbnails
│ │ ├── BeaArchiveProcessing.svelte
│ │ ├── BeaWorkspaceControls.svelte # beA workspace toolbar, settings popover, dialogs
│ │ ├── ProcessedZipArchiveEditor.svelte
│ │ ├── SubDocumentEditor.svelte
│ │ ├── FileDropzone.svelte # Shared PDF/image/ZIP file picker and drop target
│ │ ├── ZipFilePicker.svelte # Compact ZIP picker button with hidden resettable input
│ │ ├── PdfPageGrid.svelte # Accessible PDF thumbnail selection grid
│ │ ├── RasterizationWarning.svelte
│ │ ├── ZipDropzone.svelte # beA-specific wrapper around FileDropzone
│ │ ├── icons/ # Project-specific SVG components
│ │ └── ui/ # Local shadcn-svelte component source
│ ├── services/
│ │ ├── xml-reading.service.ts # Namespace-tolerant XJustiz parsing
│ │ └── zip-inflating.service.ts # Async fflate wrapper
│ ├── pdf-processing.ts # Reusable PDF merge helper
│ ├── pdf-thumbnails.ts # Managed pdfjs thumbnail loading and cleanup
│ ├── utils.ts # cn() and shared component utility types
│ └── zip-processing.ts # Archive model, transforms, and PDF export
└── routes/
├── +layout.svelte # Shared logo header and branded footer
├── +page.svelte # Toolbox hub and global beA ZIP drop target
├── layout.css # Tailwind, shadcn, theme, fonts, global CSS
├── tools/
│ ├── +layout.svelte # Shared back-navigation shell
│ ├── bea/+page.svelte # Existing ZIP archive workflow
│ └── {merge,separate,watermark,encrypt,decrypt,compress,convert}/
├── datenschutz/+page.svelte # Privacy policy
└── impressum/+page.svelte # Imprint
static/ # Public logo, footer art, and robots.txt
components.json # shadcn-svelte registry configuration
Dockerfile # Node adapter production image
plan/ # Design and implementation notes
```
Generated directories such as `.svelte-kit/`, `build/`, and `node_modules/` must not be edited or
committed.
## Application flow and state
- `/` is the toolbox hub. A ZIP dropped anywhere on that route is queued in memory and forwarded to
`/tools/bea`; the file is not uploaded or persisted.
- `tools/bea/+page.svelte` owns selected files, processed archives, pending work, and the
thumbnail-width preference. Thumbnail width is the only persisted UI setting and uses localStorage.
- The loaded workspace renders `BeaWorkspaceControls.svelte`, a top-aligned toolbar with the
workspace status and two archive lifecycle actions:
- **“Neue ZIP bearbeiten”** (replace) opens a ZIP picker and, after the user confirms the
replacement dialog, calls `replaceWithZipFiles(files)` which resets the workspace and enqueues the
new files against the incremented generation. Files picked for replacement stay in temporary
component state until confirmation; cancelling the picker or the dialog must never mutate the
current workspace.
- **“Weitere ZIP hinzufügen”** (append) lives in the settings popover, uses the plain append path
(`handleFilesSelected`), and never clears current archives. Keep the two labels distinct:
replace is “Neue ZIP bearbeiten”, append is “Weitere ZIP hinzufügen”.
- `BeaWorkspaceControls.svelte` owns only ephemeral UI state (popover/dialog open state and pending
replacement files). The route remains the source of truth for files, jobs, archives, and the
persisted thumbnail width.
- `resetWorkspace()` is the single teardown helper: it increments `archiveGeneration` and clears
selected files, processed archives, and jobs. Never clear those arrays without going through it.
- Accepted ZIP types and the file filter for the dropzone and both compact pickers are centralized in
`src/lib/zip-selection.ts`; `ZipFilePicker.svelte` resets its input after every selection so the
same file can be chosen again.
- ZIP files are processed concurrently. `archiveGeneration` prevents results from an old batch from
reappearing after the user deletes all archives **or** confirms a replacement.
- `ProcessedZipArchiveEditor.svelte` edits one archive and emits immutable replacements through
`onArchiveChange`; the route-level archive array remains the source of truth.
- Attachments may remain loose or belong to exactly one sub-document. Drag-and-drop supports
reordering and moving attachments between lists, while sub-document headers can be reordered.
- Selection mode creates sub-documents from loose attachments. Exiting with **Fertig** or Escape
clears the pending selection.
- Per-archive export supports one flattened PDF or separate PDFs for sub-documents. Each
sub-document also has its own direct download action that exports only that sub-document's
attachments. Bulk export from the page always creates one flattened PDF per archive.
When changing archive transforms, preserve these invariants:
- An attachment move must not duplicate or discard data.
- Sub-document IDs remain stable across edits and reordering.
- Single-PDF order is all sub-documents in order, each attachment in order, followed by loose
attachments in order.
- Dissolving a sub-document appends its attachments to the loose list.
- State helpers in `zip-processing.ts` should return new archive/list objects rather than mutating
component-owned state.
## ZIP, XML, and PDF processing
All case-file processing is intentionally local to the browser.
- `extractZipArchives` recursively inflates nested ZIP files.
- Ignore macOS metadata (`__MACOSX`, `.DS_Store`, and `._*`) and unsupported entries.
- `xjustiz_nachricht.xml` is metadata, not an export attachment. Its sender, receiver, and declared
document names determine the generated PDF name and preferred attachment order.
- XJustiz element matching must remain namespace tolerant. Malformed or absent metadata falls back
safely instead of preventing extraction.
- Supported attachments are PDFs and the image extensions listed in `zip-processing.ts`. Update the
extension classification and downstream preview/export behavior together when adding formats.
- PDF attachments are copied with pdf-lib. JPEG and PNG files can be embedded directly; other image
formats are decoded through browser image/canvas APIs and converted to PNG first.
- `AttachmentPreview.svelte` dynamically loads pdfjs-dist in `onMount`, copies PDF bytes before
rendering, and revokes object URLs during cleanup. Preserve those memory and data-integrity
safeguards.
- Empty archives/export groups should continue to fail explicitly rather than creating invalid PDFs.
Browser-only APIs used by this flow include `File`, `DOMParser`, `Image`, canvas, `Blob`, `URL`,
localStorage, and `crypto.randomUUID()`. Do not invoke browser-only work during SSR or at module
initialization. Keep it in event handlers, `onMount`, effects guarded by hydration, or functions only
called in the browser.
## Svelte conventions
- Use Svelte 5 runes and current event syntax: `$props()`, `$state`, `$derived`, `$effect`, snippets,
`{@render ...}`, and handlers such as `onclick` rather than legacy APIs.
- Keep component scripts typed with `<script lang="ts">`.
- Define a local `Props` type for non-trivial component props.
- Use callback props for child-to-parent updates; do not mutate prop objects in place.
- Use `$lib/...` aliases for application imports instead of long relative paths.
- Name reusable components in PascalCase and route files according to SvelteKit conventions.
- Preserve semantic HTML, accessible names, visible focus states, and keyboard alternatives. Do not
make drag-and-drop the only way to perform a critical action.
- Clean up global listeners, object URLs, PDF loading tasks, and other browser resources.
The Vite configuration forces runes mode for project files, so do not introduce legacy Svelte
component patterns.
## shadcn-svelte and component conventions
`components.json` configures the official shadcn-svelte registry, the `nova` style, Lucide icons, and
these aliases:
- UI components: `$lib/components/ui`
- General components: `$lib/components`
- Utilities: `$lib/utils`
Use existing local UI components before creating replacements. Follow the exports actually present
on disk: the card is composed through `import * as Card from '$lib/components/ui/card'`, while the
current button is a default import from `$lib/components/ui/button.svelte`.
When adding a registry component, use the project runner, inspect every generated file, and preserve
local customizations:
```sh
pnpm dlx shadcn-svelte@latest add <component>
```
Do not overwrite or bulk-update customized components without explicit approval. Use `cn()` for
conditional class merging and existing variants before adding one-off component styling.
## Styling conventions
Use Tailwind utilities in Svelte markup and semantic theme tokens from `src/routes/layout.css`.
- Prefer `bg-background`, `text-foreground`, `bg-primary`, `text-muted-foreground`, `border-border`,
and other configured semantic utilities over raw brand colors.
- Preserve the established Nominandum visual language: pale blue-gray background, white cards, dark
blue primary text, red/coral secondary actions, rounded containers, and Nunito typography.
- Use `flex`/`grid` with `gap-*`, responsive variants, and `size-*` when width and height are equal.
- Use `cn()` rather than constructing partial Tailwind class names. Keep complete class names visible
to Tailwind's scanner.
- Prefer component variants and theme variables over overriding UI-component colors in call sites.
- Add global CSS only for imports, tokens, font faces, animations, or behavior that utilities cannot
reasonably express.
- Tailwind 4 is configured through Vite and `layout.css`; there is no `tailwind.config` file.
- Maintain contrast, responsive layouts, drag-state feedback, and visible focus treatment.
Theme-level changes belong in `src/routes/layout.css`. Do not reintroduce the removed Skeleton theme
or refer to the old `nom-theme.css` architecture.
## Formatting
Follow the checked-in Prettier configuration:
- Tabs for indentation
- Single quotes in scripts and styles where supported
- No trailing commas
- 100-character print width
- Svelte-aware and Tailwind-aware formatting
Use `pnpm exec prettier --write <changed-files>` for targeted formatting. Avoid formatting unrelated
files solely to clean up existing differences.
## Product and content constraints
- User-facing copy is German unless a task explicitly requests another language.
- Preserve legal text in `datenschutz/+page.svelte` and `impressum/+page.svelte` unless explicitly
asked to change it.
- Keep the Nominandum logo, footer artwork, and theme consistent across routes.
- ZIP selection accepts multiple files matching `.zip`, `application/zip`, or
`application/x-zip-compressed`.
- Resetting the file input after selection is intentional so the same archive can be selected again.
- Do not add uploads, network requests, server processing, analytics, or case-data persistence without
an explicit requirement and privacy review.
## Change discipline
- Keep changes scoped and preserve unrelated work, including untracked files.
- Inspect existing components, archive helpers, and theme tokens before adding abstractions.
- Keep browser processing behavior and archive-order invariants covered when editing shared helpers.
- Do not hand-edit generated output or lockfile contents. Update the lockfile only through pnpm when
dependency changes are required.
- Avoid dependencies when the task can be completed with Svelte, browser APIs, Tailwind, or existing
packages.
- After implementation, review changed files, run relevant checks, and mention remaining warnings or
limitations.