Files
Laufzettel/docs/issues/039-bedingte-schritte.md
T
Florian WildeandClaude Opus 5 a2330ef682
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
feat(vorlagen): bedingte Abschnitte und Schritte (#039)
Ein Abschnitt oder ein einzelner Schritt erscheint nur, wenn seine Bedingung
zutrifft — abhaengig von einer Variable des Laufs oder vom Zustand eines
anderen Schritts. Der echte Fall steht im Seed: "Mitarbeiter-Onboarding" traegt
beide Konto-Abschnitte, fuenf AD-Schritte und vier fuer Entra ID, und
`konto_typ` sagt beim Start, welcher gilt. Bisher klickte ein Techniker die
anderen neun bei jedem Onboarding weg.

Entscheidungen, alle im Issue begruendet:

- Laufend ausgewertet, nicht beim Start festgeschrieben. Fuer die
  Abteilungsabschnitte (#031) ist Festschreiben richtig; fuer Bedingungen nicht
  — die Antwort auf "ist Pruefen fehlgeschlagen?" entsteht erst waehrend der
  Arbeit. Ein struktureller Test prueft, dass alle drei Schichten
  (Abschlussregel, Abarbeitung, Druck) die Auswertung rufen.
- Nicht entscheidbar heisst: gilt. Ein Lauf ohne konto_typ zeigt beide
  Abschnitte — genau wie vorher. Ein Abschnitt zu viel ist sichtbar und
  korrigierbar, ein fehlender faellt niemandem auf.
- OFFEN ist eine Antwort, keine Luecke: waere es unentscheidbar, waere ein
  bedingter Abschnitt von Anfang an sichtbar.
- Eine Bedingung, kein Ausdrucksbaum. Kein und, kein oder.
- Verwiesen wird ueber Titel-Pfade, nicht ueber Kennungen: vier Wege schreiben
  eine Struktur neu und geben neue Kennungen.

Zwei stille Datenverluste gefunden, der zweite aelter als dieser Meilenstein:

- Der Import verwarf `nurFuerAbteilungen`. Der Export schreibt es seit #031, das
  Schema nimmt es an, der Import setzte es nicht — eine exportierte Vorlage
  verlor ihre Abteilungsabschnitte und galt dort fuer alle.
- Die tiefe Kopie verwarf es ebenfalls, womit jedes "Bearbeiten" einer Vorlage
  alle Einschraenkungen verlor. Ursache war ein von Hand getippter Zwischentyp,
  der weniger Felder nannte als die Abfrage liefert; jetzt aus Prisma
  abgeleitet, beide Kopierwege teilen eine Stelle, und ein Test vergleicht Kopie
  und Original Feld fuer Feld.

Gefunden hat beides die Klickprobe am gebauten Stand, nicht die Suite: im Editor
stand "Bedingung gesetzt", nach "Bearbeiten" war sie weg. Dreizehn Gegenproben
liefen, alle wurden rot — und der teuerste Befund lag ausserhalb von allen.

1499 Vitest-Tests (vorher 1457), 115 E2E-Ablaeufe gruen.

Closes #39

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-23 14:54:10 +02:00

165 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# #039 M23 – Bedingte Schritte und Abschnitte
**Status:** erledigt
**Im Tracker:** –
**Meilenstein:** v0.3.0
**Labels:** phase-3, backend, frontend
**Geschätzt:** 12 h **Tatsächlich:** 5 h 20 min
## Ziel
Eine Checkliste enthält heute **alles**, und was nicht zutrifft, wird von Hand auf
`NICHT_ZUTREFFEND` gesetzt. Der echte Fall steht im Seed: „Mitarbeiter-Onboarding“ trägt **beide**
Konto-Abschnitte — „Konto anlegen (Active Directory)“ mit 5 Schritten und „Konto anlegen (Entra
ID)“ mit 4 —, obwohl in jedem einzelnen Lauf nur einer gilt. Die Variable `konto_typ` sagt es
bereits; niemand wertet sie aus.
Neun Schritte, die ein Techniker bei jedem Onboarding wegklickt. Die Abschnitts-Aktion aus #013
macht es erträglich, aber sie ist ein Pflaster: der Fortschrittsbalken zählt die Schritte mit, und
im Archiv steht später eine Checkliste mit einem Abschnitt, den niemand je gebraucht hat.
Setzt [#031](031-abteilungsabschnitte.md) voraus: `geltendeAbschnitte` und `nurGeltende` sind die
Stelle, an der das sitzt.
## Zu entscheiden
- [x] **Wann wird ausgewertet?** Das ist die Entscheidung, an der alles hängt.
Für Abteilungen (#031) wird beim **Start festgeschrieben** — die Abteilung des Laufs steht
dann fest, und ein archivierter Lauf muss genau die Schritte zeigen, die damals galten.
Für Bedingungen reicht das **nicht**: eine Bedingung auf einer Variable (`konto_typ`) ließe
sich beim Start auswerten, eine Bedingung auf einem **Schrittergebnis** („wenn Schritt 3 =
nein, dann Abschnitt B“) nicht — die Antwort entsteht erst während der Arbeit.
Empfehlung: **laufend auswerten, Ergebnis mitschreiben.** Die Auswertung läuft bei jeder
Änderung an einem Schrittergebnis; was dabei herauskommt, wird in den Lauf geschrieben (wie
`geltendeAbschnitte`). Damit gilt für das Archiv weiter „der Lauf weiß, was galt“, und der
Ereignisstrom aus #033 trägt die Änderung ohnehin schon zur zweiten Sitzung.
- [x] **Woran darf eine Bedingung hängen?** Empfehlung: an einer **Variable** des Laufs und an
einem **Schrittergebnis** derselben Version. Nicht an Gerätetyp, Abteilung oder Rolle — für
Abteilungen gibt es #031, und je mehr Quellen, desto schwerer zu erklären, warum ein Schritt
fehlt.
- [x] **Wie wird sie gespeichert?** Empfehlung: ein Json-Feld `bedingung` an `VorlagenAbschnitt`
und `VorlagenSchritt`, mit einem engen Schema — `{ art: "variable", schluessel, operator,
wert }` oder `{ art: "schritt", schrittId, zustand }`. Json, weil `konfig` es schon so macht
und ein neuer Operator dann keine Migration braucht.
**Eine Bedingung, kein Ausdrucksbaum.** Kein `und`/`oder`, keine Verschachtelung: das wäre
eine kleine Sprache, die niemand im Editor pflegen kann, und ein Abschnitt, der aus
unerklärlichen Gründen fehlt, ist schlimmer als einer zu viel.
- [x] **Was passiert mit einem Schrittergebnis, das schon Daten trägt**, wenn die Bedingung
umschlägt? Empfehlung: die Zeile **bleibt** und wird nur ausgeblendet. Löschen hieße, eine
Notiz zu verlieren, die jemand geschrieben hat; und schlägt die Bedingung zurück, ist sie
wieder da.
## Aufgaben
- [x] Migration: `bedingung Json?` an `VorlagenAbschnitt` und `VorlagenSchritt`, dazu ein CHECK, der
sicherstellt, dass eine Bedingung auf einen Schritt **derselben Version** zeigt (sonst zeigt
sie nach dem Veröffentlichen ins Leere)
- [x] `src/lib/bedingungen.ts`, framework-frei: Schema, Auswertung, und die Liste der Schritte und
Abschnitte, die gelten. **Dieselbe Stelle für alle drei Schichten**, wie
`src/lib/geltende-abschnitte.ts`
- [x] Auswertung bei jedem Schreiben an einem `Schrittergebnis`; das Ergebnis in den Lauf
- [x] Abschlussregel: ein ausgeblendeter Pflichtschritt hält den Abschluss **nicht** auf (dieselbe
Regel wie bei #031, dieselbe Funktion)
- [x] Fortschrittsbalken und Zählwerte rechnen nur mit dem, was gilt
- [x] Editor: Bedingung an Abschnitt und Schritt setzen, mit Auswahl der Variable bzw. des Schritts
**aus derselben Version**; eine Vorschau, die sagt „gilt, wenn …“
- [x] Warnung im Editor bei einer Bedingung auf einen Schritt, der **nach** dem bedingten steht —
sie wäre beim Abarbeiten noch offen, und der Abschnitt erschiene erst rückwirkend
- [x] Abarbeitung: ausgeblendete Abschnitte verschwinden, ohne die Nummerierung zu verschieben
(`01 · Vorbereitung` kommt aus der Position und wird nie gespeichert)
- [x] Druckansicht und Archiv zeigen, was galt
- [x] Export/Import: die Bedingung wandert mit, und ein Verweis auf einen Schritt wird dabei auf die
**neue** Kennung umgeschrieben — sonst zeigt sie in der zweiten Installation ins Leere
- [x] Seed: „Mitarbeiter-Onboarding“ bekommt die Bedingung auf `konto_typ`, die beiden
Konto-Abschnitte bleiben drin und schließen sich gegenseitig aus. Das ist die Gegenprobe am
echten Fall.
- [x] Anleitung Kapitel 04 (Vorlagen): wann eine Bedingung das richtige Mittel ist und wann eine
zweite Vorlage
## Akzeptanzkriterien
- [x] Test: ein Lauf mit `konto_typ = AD` hat die 5 AD-Schritte und **keinen** der 4 Entra-Schritte
- [x] Test: dasselbe umgekehrt, und bei nicht gesetzter Variable gelten **beide** — eine Bedingung,
die nicht entschieden werden kann, blendet nichts aus
- [x] Test: eine Bedingung auf einem Schrittergebnis schlägt um, **während** der Lauf läuft, und der
Abschnitt erscheint
- [x] Test: ein ausgeblendeter Pflichtschritt hält den Abschluss nicht auf
- [x] Test: ein Schrittergebnis mit Notiz bleibt stehen, wenn seine Bedingung umschlägt, und ist
danach wieder da
- [x] Test: Export und Import einer Vorlage mit Bedingungen ergeben dieselbe Struktur, und der
Schritt-Verweis zeigt auf den richtigen Schritt der **neuen** Version
- [x] Test: ein archivierter Lauf zeigt genau die Schritte, die bei seinem Abschluss galten
- [x] Am Handy kein horizontales Scrollen
## Beim Umsetzen dazugekommen
- **Die Entscheidungen aus dem Issue sind so gefallen wie empfohlen**, und zwei davon haben sich im
Code als richtig erwiesen:
- **Laufend auswerten, nicht beim Start festschreiben.** Eine Bedingung auf einer Variable ließe
sich wie #031 beim Start festhalten; eine auf einem Schrittergebnis nicht — und genau die ist der
interessante Fall (ein Abschnitt „Nacharbeit“, der erscheint, wenn der Restore-Test
fehlschlägt).
- **Nicht entscheidbar heißt: gilt.** Ein Lauf ohne `konto_typ` zeigt beide Konto-Abschnitte —
genau wie vor diesem Meilenstein. Eine erfundene Antwort wäre die Verschlechterung.
- **`OFFEN` ist eine Antwort, keine Lücke.** Der Unterschied zur Variable musste scharf gezogen
werden: eine nicht gesetzte Variable ist nicht entscheidbar, ein offener Schritt dagegen klar
„noch nicht erfüllt“. Wäre `OFFEN` unentscheidbar, wäre ein bedingter Abschnitt von Anfang an
sichtbar — das Gegenteil des Zwecks.
- **Verwiesen wird über Titel-Pfade, nicht über Kennungen**, und das war die größte Einzelerkenntnis.
**Vier** Wege schreiben eine Struktur neu und geben dabei neue Kennungen: Import, Export, Seed und
`entwurfSpeichern`. Eine Bedingung auf einer Kennung überlebt keinen davon. `bedingungAufloesen`
ist für alle vier dieselbe Stelle; dass eine Bedingung auf **dieselbe Version** zeigt, hält
zusätzlich ein Trigger.
- **Ein struktureller Test bewacht die drei Schichten.** Abschlussregel, Abarbeitung und Druck
müssen alle `geltendeStruktur` rufen; vergisst eine es, sieht der Techniker andere Schritte als
der Ausdruck. Der Test liest die drei Dateien — dieselbe Art Prüfung wie „jede Seite prüft selbst“
(#038).
- **Zwei Schemas für dieselbe Form liefen fast auseinander.** Austauschformat und Editor-Struktur
brauchen die Pfad-Form; ich hatte sie zweimal geschrieben, und TypeScript meldete es erst, als die
beiden Typen aufeinandertrafen. Jetzt steht `PfadBedingungSchema` in `src/lib/bedingungen.ts`, und
`vorlagen-format.ts` importiert es.
## Zwei stille Datenverluste, gefunden bei der Klickprobe
Beides hätte keine Suite gezeigt, und das zweite war **älter als dieser Meilenstein**:
- **Der Import verwarf `nurFuerAbteilungen`.** Der Export schreibt die Abteilungseinschränkung seit
[#031](031-abteilungsabschnitte.md), das Schema nimmt sie an — und der Import setzte sie nicht.
Eine Vorlage, die jemand in eine zweite Installation brachte, verlor ihre Abteilungsabschnitte:
sie galten dort für **alle**. Also mehr Schritte statt weniger, und niemand merkt es.
- **Die tiefe Kopie verwarf sie ebenfalls — und seit heute auch die Bedingung.** „Bearbeiten“ einer
Vorlage erzeugt eine tiefe Kopie als Entwurf (#009); `vorlageDuplizieren` dasselbe. Beide hatten
die Kopie einzeln ausgeschrieben, und beide vergaßen dieselben Felder. Damit verlor **jede
Bearbeitung** alle Abteilungseinschränkungen.
**Die Ursache war ein von Hand getippter Zwischentyp.** `VersionMitInhalt` nannte weniger Felder,
als die Abfrage liefert; die Felder waren da, der Code konnte sie nicht sehen. Jetzt ist der Typ
aus Prisma abgeleitet — ein neues Feld am Modell ist dort automatisch vorhanden.
Gefunden hat es die **Klickprobe am gebauten Stand**: der Editor zeigte „Bedingung gesetzt“ in der
veröffentlichten Version, und nach „Bearbeiten“ war sie weg. Das ist genau der Grund, warum diese
Probe in `CLAUDE.md` steht.
Bewacht wird es jetzt von einem Test, der Kopie und Original **Feld für Feld** vergleicht — nicht
stichprobenweise. So fällt auch das nächste Feld auf, das jemand ergänzt und beim Kopieren
vergisst.
- **Ein Mangel an der neuen Komponente**, aufgefallen beim Klicken: die Auswahlfelder standen in
einem umhüllenden `<label>`, und damit schluckte ihr zugänglicher Name den Text der gewählten
Option mit („VariableKontotyp“). Für einen Screenreader ist das eine Bezeichnung, die sich beim
Umschalten ändert. Jetzt `htmlFor`/`id` — derselbe Fehler wie bei der Adresse im Verzeichnis-Formular
(#016).
## Was offen bleibt
- **Kein `und`/`oder`**, bewusst. Wer zwei Bedingungen braucht, nimmt zwei Abschnitte.
- **Ein Baustein-Schritt hat keine Bedingung** (der Abschnitt, in dem er steht, darf eine haben): ein
Baustein ist wiederverwendbar, und eine Bedingung auf einer Variable der Zielvorlage wäre dort
nicht entscheidbar.
- **Zwei Schritte mit demselben Titel im selben Abschnitt** sind über den Titel-Pfad nicht
unterscheidbar; die Bedingung zeigt dann auf den ersten. Benannt in `src/lib/bedingungen.ts` —
zwei gleich benannte Schritte nebeneinander kann auch ein Mensch nicht unterscheiden.