<!-- Generated by scripts/build-prompts.mjs — do not edit by hand. -->
# Bau mir dieses Werkzeug: Klassenfahrt

*Alles Folgende ist die fachliche Anforderung. Halte dich daran; wo sie schweigt, entscheide selbst und sage, was du angenommen hast.*

## Ausgangspunkt

Nutze die Vorlage **openToolbox**: https://github.com/m-dohmen/openToolbox. Lies dort zuerst `AGENTS.md` — sie ist maßgeblich für Schemaform, Feldtypen und die Regeln, an denen ein Einzeldatei-Build zerbricht. Alles Fachliche kommt in eine einzige Datei, `src/domain.js`.

> Ist der openToolbox-Skill installiert (`claude plugin install opentoolbox@opentoolbox`), genügt es, diese Datei einzufügen — er holt sich die Vorlage selbst.

## Das Problem dahinter

Eine Klassenfahrt steht an. 28 Zettel gehen raus, 19 kommen zurück, drei ohne Unterschrift, einer mit einer Allergie auf der Rückseite. Zwei Tage vor Abfahrt fehlt das Geld von vier Familien. Die Klassenlehrerin führt das in einer Tabelle, die sie nicht teilen darf, weil Allergien und Schwimmfähigkeit darin stehen. Fast nichts daran ist eine Zahl — es sind Zustände, und die einzige Summe, auf die es ankommt, ist der offene Rest. Die Fragen jedes Montagmorgen („wessen Zettel fehlt noch?", „wer hat nicht gezahlt?") wiederholen sich wochenweise — sie gehören als gespeicherte Ansichten an den Listenkopf, nicht jede Woche von Hand zusammengeklickt. Der Einverständnis-Fluss selbst ist ein kleines Kanban: offen → ausstehend → verweigert, beim Eintreffen der Zettel von links nach rechts geräumt.

## Was am Ende dastehen muss

Eine einzelne, in sich geschlossene HTML-Datei, per Doppelklick zu öffnen, ohne Server und ohne Installation. Die Datei ist zugleich die Datenbank: Speichern schreibt eine neue HTML-Datei mit den eingebetteten Datensätzen.

## Der Datensatz

Ein Datensatz ist **Kind**, mehrere sind **Kinder**.

**Felder**

| Schlüssel | Beschriftung | Typ | Näheres |
| --- | --- | --- | --- |
| `name` | Kind | text | Pflicht |
| `guardian` | Erziehungsberechtigte | text | Tabellenkopf `Eltern` |
| `phone` | Telefon für Notfälle | text | Tabellenkopf `Telefon` |
| `consent` | Einverständnis | enum | einer von: ausstehend · liegt vor · verweigert, Tabellenkopf `Einv.` |
| `consentForm` | Unterschriebener Zettel | attachment | hochgeladene Datei, im Datensatz abgelegt, Tabellenkopf `Zettel` |
| `payment` | Zahlung | enum | einer von: offen · teilweise · vollständig · Zuschuss beantragt · erlassen |
| `paid` | Bezahlt (€) | number | Tabellenkopf `€` |
| `swim` | Schwimmabzeichen | enum | einer von: ja · nein · unbekannt, Tabellenkopf `Schwimmen` |
| `diet` | Essen (Allergien, vegetarisch …) | text | Tabellenkopf `Essen` |
| `medical` | Medizinisches | text | mehrzeilig, Tabellenkopf `Medizin` |
| `roomWish` | Zimmerwunsch | text | Tabellenkopf `Zimmer` |
| `note` | Notiz | text | mehrzeilig |
| `open` | Noch offen (€) | computed | berechnet, nie gespeichert, Tabellenkopf `Offen` |

**Darstellung**

- Führende Spalte: `name`
- Zweite Zeile darunter: `guardian`
- Tabellenspalten, in dieser Reihenfolge: `name`, `guardian`, `consent`, `payment`, `paid`, `open`, `swim`
- Filter in der Seitenleiste: `consent`, `payment`, `swim`
- In der Übersicht summiert: `paid`
- Zählt nicht mehr als offen, wenn: `r.consent === 'verweigert' || (r.consent === 'liegt vor' && (r.payment === 'vollständig' || r.payment === 'erlassen'))`
- Rot markiert, wenn: `!isDone(r)`

**Berechnete Felder**

Sie werden bei jeder Anzeige gerechnet und nie in den Datensatz geschrieben — eine gespeicherte Ableitung ist in dem Moment falsch, in dem sich eine ihrer Quellen ändert.

- `open` (Noch offen (€)):

  ```js
  if (r.payment === 'erlassen') return 0
  return Math.max(0, FEE - (Number(r.paid) || 0))
  ```

**Prüfregeln**

Bedingungen zwischen Feldern. Sie müssen an einer Stelle greifen, damit Formular, CSV-Import und die Vorschläge der KI durch dieselbe Prüfung laufen.

- **Wenn** `r.consent === 'liegt vor'` → **Dann** `guardian`, `phone`
  **Meldung:** „Zum Einverständnis gehört, wer unterschrieben hat und wie man diese Person erreicht.“
- **Wenn** `r.payment === 'vollständig'` → **Dann** `Number(r.paid) >= FEE`
  **Meldung:** „Vollständig heißt 185 € — sonst stimmt die Kassenaufstellung nicht.“
- **Wenn** `r.payment === 'erlassen'` → **Dann** `note`
  **Meldung:** „Ein Erlass gehört begründet — die Kasse wird geprüft.“
- **Wenn** `Boolean(r.medical?.trim())` → **Dann** `phone`
  **Meldung:** „Wo Medizinisches steht, muss eine Telefonnummer daneben stehen.“

**Gespeicherte Ansichten**

Benannte Kombinationen aus Suchbegriff, Feldfiltern und Sortierung, die das Dropdown am Listenkopf anbietet. Was hier steht, liefert das Werkzeug mit; eigene Sichten legen die Empfänger unter `settings.views` an. Merge: gleicher Name = letzter Stand gewinnt.

`name` (eindeutig), `query` (wie das Suchfeld), `filters` (`{ field: spec }`, wobei `spec` mit nur `v` die gleichnamige Facette setzt, mit `op` einen Feldfilter) und `sort` (`{ key, dir }`, `dir` ist `1` oder `-1`). `entity` ist optional und der Multi-Entität vorbehalten.

- Vorschlag: **Alle** — —; sort: `name ↑`
- Vorschlag: **Zettel ausstehend** — Einverständnis = ausstehend; sort: `name ↑`
- Vorschlag: **Geld offen** — Zahlung = offen; sort: `open ↓`

**Board**

Eine optionale Kanban-Sicht pro Entität, erreichbar aus der Reiterleiste neben *Liste* und *Dashboard*. Ohne diese Deklaration gibt es die Ansicht gar nicht — gleiche Haltung wie beim Dashboard und beim Wizard: erst die Erklärung im Schema schaltet sie frei.

`columnField` (Schlüssel eines vorhandenen enum-Feldes — seine `values` bestimmen die Spalten in dieser Reihenfolge, der erste Wert steht links), `cardFields` (bis zu drei weitere Feldschlüssel, die auf jeder Karte unter dem Titel erscheinen; weglassen = die ersten drei Nicht-Titel-, Nicht-Spalten-, Nicht-Computed-, Nicht-Attachment-Felder), und `limit` (Kartenobergrenze pro Spalte, Standard `50`). Eine Karte per Drag in eine andere Spalte läuft über denselben `mutate`-Pfad wie das Formular — die Verschiebung landet in der Undo-Historie und im Änderungsprotokoll. Schreibgeschützte Kopien zeigen das Board ohne Ziehfunktion.

- `columnField` — `consent` (Einverständnis); Spalten folgen den enum-`values` in deklarierter Reihenfolge.
- `cardFields` — *Erziehungsberechtigte*, *Telefon für Notfälle*, *Zahlung*.
- Datensätze mit leerem oder nicht mehr gültigem Wert landen rechts in einem kleinen Reservoir „Nicht zugeordnet".

## Dashboard

Kacheln über den gesamten Bestand, nicht über die gefilterte Ansicht.

- Eine Zahl: **Kinder** — der Anzahl · „in der Klasse“
- Eine Zahl: **Noch offen** — der Anzahl (nur Datensätze, die einem Filter entsprechen): `!isDone(r)` · „Zettel oder Geld fehlt“
- Eine Zahl: **Eingegangen** — `paid` (Bezahlt (€)) · „€ von 2590 €“
- Ein Ring je Ausprägung von `payment`
- Balken je Ausprägung von `consent`, gemessen an der Anzahl — **Einverständnisse**
- Balken je Ausprägung von `swim`, gemessen an der Anzahl — **Schwimmabzeichen**

## Geführte Erfassung

Eine kurze Schrittfolge für jemanden, der eine Sache melden soll und das Werkzeug nicht kennt. Geschrieben wird nichts, bevor der letzte Schritt bestätigt ist — ein Abbruch darf nichts hinterlassen.

Titel: **Rückläufer aufnehmen**

> Ein Kind nach dem anderen. Was noch fehlt, einfach leer lassen — die Übersicht zeigt es nachher von selbst an.

1. **Schritt 1** — Kind: Felder: `name`, `guardian`, `phone`
2. **Schritt 2** — Zettel und Geld: Felder: `consent`, `payment`, `paid`
3. **Schritt 3** — Besonderheiten: Felder: `swim`, `diet`, `medical`, `roomWish`
   nur wenn: `drafts.records?.consent !== 'verweigert'`
4. **Schritt 4** — Klassenliste einlesen: CSV-Upload, zahlt in denselben Durchlauf ein
5. **Schritt 5** — Prüfen: Zusammenfassung, aus dem Schema erzeugt

Abschluss: „Aufgenommen. Nicht vergessen: Datei speichern.“

## Vorgaben

In `DEFAULT_SETTINGS`, `DEFAULT_COLORS` und `DEFAULT_HOME` in `src/app.jsx` setzen.

- Titel: **Klassenfahrt**
- Untertitel: Rückläufer, Zahlungen und wer noch fehlt
- Dateiname: `klassenfahrt`
- Version: `1.0`
- Oberflächensprache: `de`
- Öffnet als: vollständiges Werkzeug
- Farben: `accent` #a33a63 · `band` #2b1823 · `flag` #b4442e · `ok` #3f7a5c · `pending` #c08a12

## Startseite

Die Anwendung öffnet auf diesem Text. Er ist ein kleiner Markdown-Teilsatz — Überschriften, Listen, Zitate, fett, kursiv, Inline-Code und Verweise. Wörtlich übernehmen:

```markdown
# Klassenfahrt

28 Zettel gehen raus, 19 kommen zurück, drei ohne Unterschrift, einer mit einer Allergie auf der
Rückseite. Zwei Tage vor Abfahrt fehlt das Geld von vier Familien.

## Was diese Demo zeigt

- **Ein Bestand fast ohne Zahlen**: Zustände, Ja/Nein, ein offener Restbetrag, der sich selbst
  ausrechnet.
- **Gespeicherte Ansichten** — drei Vorschläge im Dropdown am Listenkopf („Alle", „Zettel
  ausstehend", „Geld offen"), die morgens und vor jeder Überweisung dieselbe Tastaturabfolge
  ersparen. Eigene Sichten legt man in den Einstellungen an; eine davon als Start-Ansicht markiert
  öffnet die Datei immer in genau diesem Zustand.
- **Kanban-Board** — der Reiter „Board" ordnet die Kinder nach Einverständnis (ausstehend, liegt
  vor, verweigert). Eine Karte per Drag nach „liegt vor" verschieben trägt sich ins
  Änderungsprotokoll ein und lässt sich mit Strg+Z zurücknehmen; die Tastatur übernehmen Pfeil-
  und Eingabetaste.
- **Regeln, die dem Alltag folgen** — wo Medizinisches steht, muss eine Telefonnummer daneben
  stehen.
- **Der Grund, warum diese Datei verschlüsselt gehört**: hier stehen Gesundheitsangaben von
  Kindern. Einstellungen → Sicherheit → *Verschlüsseln*. Ohne Passphrase ist die Datei danach ein
  Klumpen — auch für Sie.

> Erfundene Namen und Angaben.
```

## Beispieldaten

Lege 14 realistische Beispieldatensätze an, damit die Datei beim ersten Öffnen nicht leer ist. Erfinde sie im Stil der Felder oben; sie sind Anschauung, nicht die Daten des Nutzers. Sag ihm, dass seine eigenen Daten über **Import CSV → alle ersetzen** hineinkommen.

## Fertig, wenn

- `npm run build` eine einzelne, geschlossene `dist/index.html` erzeugt
- `npm test` durchläuft
- die Datei per Doppelklick öffnet und die Beispieldatensätze zeigt
- die berechneten Felder Werte zeigen und die Regeln einen verstoßenden Datensatz abweisen
- Einstellungen, Farben und Startseite der Vorgabe oben entsprechen

## Vor der Übergabe

Entscheide das selbst, statt es dem Empfänger zu überlassen: `copyright` auf den Eigentümer des Werkzeugs setzen, den Kopfzeilen-Verweis auf das openToolbox-Repository ersetzen und `examplePrompts` abschalten, wenn der Empfänger nur Daten pflegt. Den Aufrufzähler (Einstellungen → Sicherheit) bei der Übergabe ansprechen, statt ihn später in einem Netzwerkprotokoll entdecken zu lassen.

---

*Erzeugt aus `examples/school-trip.domain.js` — dem laufenden Quelltext der [Live-Demo](https://m-dohmen.github.io/openToolbox/demos/school-trip/). Neu erzeugen mit `npm run prompts`.*

*Alle Daten der Demo sind erfunden. Sie veranschaulicht die Struktur eines solchen Werkzeugs — sie ist keine Rechtsberatung und kein Nachweis von Konformität.*
