Files
Laufzettel/docs/issues/039-bedingte-schritte.md
T
Florian WildeandClaude Opus 5 a2330ef682
Prüfung / pruefung (push) Waiting to run
Prüfung / e2e (push) Blocked by required conditions
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

10 KiB
Raw Blame History

#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 voraus: geltendeAbschnitte und nurGeltende sind die Stelle, an der das sitzt.

Zu entscheiden

  • 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.
    
  • 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.

  • 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.

  • 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

  • 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)
  • 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
  • Auswertung bei jedem Schreiben an einem Schrittergebnis; das Ergebnis in den Lauf
  • Abschlussregel: ein ausgeblendeter Pflichtschritt hält den Abschluss nicht auf (dieselbe Regel wie bei #031, dieselbe Funktion)
  • Fortschrittsbalken und Zählwerte rechnen nur mit dem, was gilt
  • Editor: Bedingung an Abschnitt und Schritt setzen, mit Auswahl der Variable bzw. des Schritts aus derselben Version; eine Vorschau, die sagt „gilt, wenn …“
  • 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
  • Abarbeitung: ausgeblendete Abschnitte verschwinden, ohne die Nummerierung zu verschieben (01 · Vorbereitung kommt aus der Position und wird nie gespeichert)
  • Druckansicht und Archiv zeigen, was galt
  • 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
  • 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.
  • Anleitung Kapitel 04 (Vorlagen): wann eine Bedingung das richtige Mittel ist und wann eine zweite Vorlage

Akzeptanzkriterien

  • Test: ein Lauf mit konto_typ = AD hat die 5 AD-Schritte und keinen der 4 Entra-Schritte
  • Test: dasselbe umgekehrt, und bei nicht gesetzter Variable gelten beide — eine Bedingung, die nicht entschieden werden kann, blendet nichts aus
  • Test: eine Bedingung auf einem Schrittergebnis schlägt um, während der Lauf läuft, und der Abschnitt erscheint
  • Test: ein ausgeblendeter Pflichtschritt hält den Abschluss nicht auf
  • Test: ein Schrittergebnis mit Notiz bleibt stehen, wenn seine Bedingung umschlägt, und ist danach wieder da
  • Test: Export und Import einer Vorlage mit Bedingungen ergeben dieselbe Struktur, und der Schritt-Verweis zeigt auf den richtigen Schritt der neuen Version
  • Test: ein archivierter Lauf zeigt genau die Schritte, die bei seinem Abschluss galten
  • 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, 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.