<!-- Generated by scripts/build-prompts.mjs — do not edit by hand. -->
# Build me this tool: Verarbeitungsverzeichnis

*Everything below is the functional specification. Follow it as written; where it is silent, use your judgement and say what you assumed.*

## Start here

Use the **openToolbox** template: https://github.com/m-dohmen/openToolbox. Read `AGENTS.md` in that repository first — it is the authority on the schema shape, the field types and the rules that break a single-file build. Everything domain-specific goes into one file, `src/domain.js`.

> If you have the openToolbox skill installed (`claude plugin install opentoolbox@opentoolbox`), just paste this file — it fetches the template itself.

## The problem this solves

Under Art. 30 GDPR almost every company must keep a register of processing activities. Almost none keeps one worth the name, and the reason is rarely unwillingness — it is collection. The answers sit with twelve different people: the application files with HR, the newsletter tool with marketing, the camera system with the caretaker. Whoever tries to fill it in centrally writes twelve emails and gets nine replies. The tool therefore has to work as something you send out: each department reports its own processing, saves, sends it back.

## What it has to be at the end

One self-contained HTML file, opened by double-click, no server and no installation. The file is also the database: saving writes a new HTML file with the records embedded in it.

## The record

One record is a **Verarbeitung**, several are **Verarbeitungen**.

**Fields**

| Key | Label | Type | Detail |
| --- | --- | --- | --- |
| `name` | Verarbeitungstätigkeit | text | required |
| `area` | Bereich | enum | one of: Personal · Vertrieb · Marketing · IT · Buchhaltung · Empfang |
| `contact` | Ansprechperson | text | table header `Kontakt` |
| `purpose` | Zweck der Verarbeitung | text | multi-line, table header `Zweck` |
| `subjects` | Betroffene | enum | one of: Beschäftigte · Bewerberinnen und Bewerber · Kundinnen und Kunden · Interessenten · Lieferanten · Besucher |
| `categories` | Datenkategorien | text | multi-line, table header `Daten` |
| `basis` | Rechtsgrundlage | enum | one of: Vertrag (Art. 6 I b) · Rechtliche Pflicht (Art. 6 I c) · Berechtigtes Interesse (Art. 6 I f) · Einwilligung (Art. 6 I a) · noch zu klären, table header `Grundlage` |
| `system` | System / Ablage | text | table header `System` |
| `processor` | Auftragsverarbeiter | text | table header `AV` |
| `thirdCountry` | Drittlandtransfer | enum | one of: nein · ja · unklar, table header `Drittland` |
| `retention` | Löschfrist | text | table header `Frist` |
| `risk` | Risiko | enum | one of: gering · mittel · hoch |
| `status` | Status | enum | one of: gemeldet · in Prüfung · freigegeben · nachzubessern |
| `reviewed` | Zuletzt geprüft | date | table header `Geprüft` |
| `note` | Notiz | text | multi-line |
| `age` | Tage seit Prüfung | computed | calculated, never stored, table header `Alter` |

**Presentation**

- Headline column: `name`
- Second line under the headline: `area`
- Table columns, in this order: `name`, `area`, `basis`, `subjects`, `retention`, `age`, `status`
- Sidebar filters: `area`, `status`, `risk`
- No longer counts as open when: `r.status === 'freigegeben'`
- Flagged red when: `!r.reviewed || r.reviewed < iso(-365)`

**Calculated fields**

These are derived on every render and never written into the record — a stored derivation goes stale the moment one of its inputs changes.

- `age` (Tage seit Prüfung):

  ```js
  const reviewed = localDateFromIso(r.reviewed)
  if (!reviewed) return ''
  /* Ganze lokale Tage über die Date.UTC-Differenz der Tageskompo-
     nenten - exakt ganzzahlig, ohne Rundungsrettung und ohne den
     Mix aus UTC-Mitternacht und Lokal-Mitternacht. */
  const now = new Date()
  return (
    (Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()) -
      Date.UTC(reviewed.getFullYear(), reviewed.getMonth(), reviewed.getDate())) /
    86400000
  )
  ```

**Validation rules**

Conditions between fields. They must be enforced in one place so that the edit form, the CSV import and anything the AI proposes all pass through the same check.

- **When** `r.basis === 'Einwilligung (Art. 6 I a)'` → **Then** `note`
  **Message:** „Bei einer Einwilligung gehört in die Notiz, wie und wo sie eingeholt wird.“
- **When** `r.thirdCountry === 'ja'` → **Then** `processor`
  **Message:** „Bei einem Drittlandtransfer muss stehen, wer dort verarbeitet.“
- **When** `r.status === 'freigegeben'` → **Then** `purpose`, `categories`, `retention`, `reviewed`
  **Message:** „Freigeben lässt sich nur, was Zweck, Datenkategorien, Löschfrist und Prüfdatum hat.“
- **When** `r.risk === 'hoch'` → **Then** `note`
  **Message:** „Ein hohes Risiko braucht eine Begründung — es kann eine Folgenabschätzung auslösen.“

## Dashboard

Tiles over the whole record set, not the filtered view.

- A single number: **Verarbeitungen** — the record count · „im Verzeichnis“
- A single number: **Prüfung überfällig** — the record count (only records matching a filter): `isOverdue(r)` · „nie oder vor über einem Jahr“
- A single number: **Offene Fragen** — the record count (only records matching a filter): `r.basis === 'noch zu klären' || r.thirdCountry === 'unklar'` · „Rechtsgrundlage oder Drittland unklar“
- A ring per value of `status`
- Bars per value of `area`, measuring the record count — **Verarbeitungen je Bereich**
- Bars per value of `risk`, measuring the record count — **Verteilung nach Risiko**

## Guided entry

A short sequence of steps for someone who has to report one thing and does not know the tool. Nothing is written until the last step is confirmed — abandoning it must leave nothing behind.

Title: **Eine Verarbeitung melden**

> Fünf kurze Schritte. Wenn Sie etwas nicht wissen, lassen Sie es leer — ein unvollständiger Eintrag ist besser als keiner. Gespeichert wird erst am Ende.

1. **Step 1** — Was und wer: fields: `name`, `area`, `contact`
2. **Step 2** — Zweck und Daten: fields: `purpose`, `subjects`, `categories`
3. **Step 3** — Rechtsgrundlage: fields: `basis`, `note`
4. **Step 4** — Wo liegt es: fields: `system`, `processor`, `thirdCountry`, `retention`
5. **Step 5** — Risiko: fields: `risk`
   shown only when: `drafts.records?.subjects === 'Beschäftigte' || drafts.records?.subjects === 'Bewerberinnen und Bewerber' || drafts.records?.thirdCountry === 'ja'`
6. **Step 6** — Prüfen: summary generated from the schema

Closing screen: “Danke — das ist aufgenommen. Bitte die Datei speichern und zurücksenden.”

## Defaults

Set these in `DEFAULT_SETTINGS`, `DEFAULT_COLORS` and `DEFAULT_HOME` in `src/app.jsx`.

- Title: **Verarbeitungsverzeichnis**
- Subtitle: Art. 30 DSGVO — gemeldet von den Fachbereichen
- File name: `verarbeitungsverzeichnis`
- Version: `1.0`
- Interface language: `de`
- Opens as: guided entry (the list is hidden)
- Colours: `accent` #3a5a8c · `band` #1b2333 · `flag` #b0472e · `ok` #3a7a5e · `pending` #c08a12

## Start page

The app opens on this text. It is a small Markdown subset — headings, lists, quotes, bold, italic, inline code and links. Use it verbatim:

```markdown
# Verzeichnis von Verarbeitungstätigkeiten

Nach **Art. 30 DSGVO** muss fast jedes Unternehmen eines führen. Fast keines führt eines, das den
Namen verdient — und der Grund ist selten Unwille.

Es ist die Erhebung. Die Angaben liegen bei zwölf verschiedenen Leuten: die Bewerbungsmappen bei
der Personalstelle, das Newsletter-Werkzeug im Marketing, die Videoanlage beim Hausmeister. Wer das
zentral ausfüllen will, schreibt zwölf Mails und bekommt neun Antworten.

## Was diese Demo zeigt

Diese Datei ist im **Erfassungsmodus** ausgeliefert: sie öffnet direkt im Meldeformular und zeigt
die Liste gar nicht. Gedacht ist sie zum Weiterschicken — jeder Fachbereich meldet seine eigene
Verarbeitung, speichert und schickt zurück. Die Rückläufer führt man mit *Datei abgleichen*
zusammen.

Umschalten auf das vollständige Werkzeug: **Einstellungen → Anwendung → Öffnet als**.

> Erfundene Einträge, Veranschaulichung der Struktur. Keine Rechtsberatung.
```

## Demo data

Add 10 realistic demo records so the file is not empty on first open. Invent them in the style of the examples above; they are illustration, not the user’s data. Tell the user their own data goes in through **Import CSV → replace all**.

## Done when

- `npm run build` produces one self-contained `dist/index.html`
- `npm test` passes
- the file opens by double-click and shows the demo records
- the calculated fields show values, and the rules refuse a record that violates them
- the settings, colours and start page match the specification above

## Before handing it over

Decide these rather than leaving them to the recipient: set `copyright` to whoever owns the tool, replace the header link that points at the openToolbox repository, and switch `examplePrompts` off if the recipient only enters data. Mention the usage counter (Settings → Security) at handover rather than letting someone find it in a network log.

---

*Generated from `examples/gdpr-processing.domain.js` — the working source of the [live demo](https://m-dohmen.github.io/openToolbox/demos/gdpr-processing/). Regenerate with `npm run prompts`.*

*All data in the demo is invented. It illustrates the structure of such a tool — it is not legal advice and not proof of anyone’s compliance.*
