/click-path-audit — Behavioural Flow Audit
Finde Fehler, die das statische Code-Lesen übersieht: Nebenwirkungen von Zustandsinteraktionen, Wettlaufsituationen zwischen sequenziellen Aufrufen und Handler, die sich stillschweigend gegenseitig rückgängig machen.
Das Problem, das dies löst
Traditionelles Debuggen prüft:
- Existiert die Funktion? (fehlende Verdrahtung)
- Stürzt sie ab? (Laufzeitfehler)
- Gibt sie den richtigen Typ zurück? (Datenfluss)
Aber es prüft NICHT:
- Entspricht der endgültige UI-Zustand dem, was das Button-Label verspricht?
- Macht Funktion B stillschweigend rückgängig, was Funktion A gerade getan hat?
- Hat der gemeinsame Zustand (Zustand/Redux/Kontext) Nebenwirkungen, die die beabsichtigte Aktion aufheben?
Realbeispiel: Ein „Neue E-Mail“-Button rief setComposeMode(true) und dann selectThread(null) auf. Beide funktionierten einzeln. Aber selectThread hatte eine Nebenwirkung, die composeMode: false zurücksetzte. Der Button tat nichts. 54 Fehler wurden durch systematisches Debuggen gefunden – dieser wurde übersehen.
So funktioniert es
Für JEDEN interaktiven Touchpoint im Zielbereich:
1. IDENTIFIZIERE den Handler (onClick, onSubmit, onChange usw.)
2. VERFOLGE jeden Funktionsaufruf im Handler, IN REIHENFOLGE
3. Für JEDEN Funktionsaufruf:
a. Welchen Zustand LIEST er?
b. Welchen Zustand SCHREIBT er?
c. Hat er NEBENWIRKUNGEN auf gemeinsamen Zustand?
d. Setzt/löscht er einen Zustand als Nebenwirkung?
4. PRÜFE: Macht ein späterer Aufruf eine Zustandsänderung aus einem früheren Aufruf RÜCKGÄNGIG?
5. PRÜFE: Ist der ENDZUSTAND das, was der Benutzer vom Button-Label erwartet?
6. PRÜFE: Gibt es Wettlaufsituationen (asynchrone Aufrufe, die in der falschen Reihenfolge auflösen)?
Ausführungsschritte
Schritt 1: Zustandsspeicher kartieren
Erstelle vor dem Auditieren eines Touchpoints eine Nebenwirkungskarte jeder Zustandsspeicher-Aktion:
Für jeden Zustand-Store / React-Kontext im Gültigkeitsbereich:
Für jede Aktion / jeden Setter:
- Welche Felder setzt er?
- Setzt er ANDERE Felder als Nebenwirkung zurück?
- Dokumentiere: aktionsName → {setzt: [...], setztZurück: [...]}
Dies ist die kritische Referenz. Der „Neue E-Mail“-Fehler war unsichtbar, ohne zu wissen, dass selectThread composeMode zurücksetzt.
Ausgabeformat:
STORE: emailStore
setComposeMode(bool) → setzt: {composeMode}
selectThread(thread|null) → setzt: {selectedThread, selectedThreadId, messages, drafts, selectedDraft, summary} SETZT ZURÜCK: {composeMode: false, composeData: null, redraftOpen: false}
setDraftGenerating(bool) → setzt: {draftGenerating}
...
GEFÄHRLICHE ZURÜCKSETZUNGEN (Aktionen, die Zustand löschen, der ihnen nicht gehört):
selectThread → setzt composeMode zurück (gehört zu setComposeMode)
reset → setzt alles zurück
Schritt 2: Jeden Touchpoint auditieren
Für jeden Button/Toggle/Formular-Absendung im Zielbereich:
TOUCHPOINT: [Button-Label] in [Component:Zeile]
HANDLER: onClick → {
aufruf 1: functionA() → setzt {X: true}
aufruf 2: functionB() → setzt {Y: null} SETZT ZURÜCK {X: false} ← KONFLIKT
}
ERWARTET: Benutzer sieht [Beschreibung dessen, was das Button-Label verspricht]
IST-ZUSTAND: X ist false, weil functionB es zurückgesetzt hat
URTEIL: FEHLER — [Beschreibung]
Prüfe jedes dieser Fehlermuster:
Muster 1: Sequenzielles Rückgängigmachen
handler() {
setState_A(true) // setzt X = true
setState_B(null) // Nebenwirkung: setzt X = false zurück
}
// Ergebnis: X ist false. Erster Aufruf war sinnlos.
Muster 2: Asynchrone Wettlaufsituation
handler() {
fetchA().then(() => setState({ loading: false }))
fetchB().then(() => setState({ loading: true }))
}
// Ergebnis: Endgültiger Ladezustand hängt davon ab, welcher zuerst auflöst
Muster 3: Abgestandener Abschluss
const [count, setCount] = useState(0)
const handler = useCallback(() => {
setCount(count + 1) // erfasst veralteten count
setCount(count + 1) // gleicher veralteter count — erhöht um 1, nicht um 2
}, [count])
Muster 4: Fehlender Zustandsübergang
// Button sagt „Speichern“, aber Handler validiert nur, speichert nie
// Button sagt „Löschen“, aber Handler setzt ein Flag, ohne die API aufzurufen
// Button sagt „Senden“, aber der API-Endpunkt ist entfernt/defekt
Muster 5: Bedingter toter Pfad
handler() {
if (someState) { // someState ist zu diesem Zeitpunkt IMMER false
doTheActualThing() // wird nie erreicht
}
}
Muster 6: useEffect-Interferenz
// Button setzt stateX = true
// Ein useEffect überwacht stateX und setzt es auf false zurück
// Benutzer sieht nichts passieren
Schritt 3: Bericht
Für jeden gefundenen Fehler:
CLICK-PATH-NNN: [Schweregrad: KRITISCH/HOCH/MITTEL/NIEDRIG]
Touchpoint: [Button-Label] in [Datei:Zeile]
Muster: [Sequenzielles Rückgängigmachen / Asynchrone Wettlaufsituation / Abgestandener Abschluss / Fehlender Übergang / Toter Pfad / useEffect-Interferenz]
Handler: [Funktionsname oder inline]
Ablauf:
1. [Aufruf] → setzt {Feld: Wert}
2. [Aufruf] → SETZT ZURÜCK {Feld: Wert} ← KONFLIKT
Erwartet: [was der Benutzer erwartet]
Ist-Zustand: [was tatsächlich passiert]
Korrektur: [spezifische Korrektur]
Umfangssteuerung
Dieses Audit ist aufwendig. Begrenze es angemessen:
- Vollständiges App-Audit: Verwenden beim Start oder nach größerem Refactoring. Starte parallele Agenten pro Seite.
- Einzelseiten-Audit: Verwenden nach dem Erstellen einer neuen Seite oder wenn ein Benutzer einen defekten Button meldet.
- Speicherfokussiertes Audit: Verwenden nach Änderung eines Zustand-Stores – auditiere alle Verwender der geänderten Aktionen.
Empfohlene Agentenaufteilung für vollständige App:
Agent 1: Kartiere ALLE Zustandsspeicher (Schritt 1) – dies ist der gemeinsame Kontext für alle anderen Agenten
Agent 2: Dashboard (Aufgaben, Notizen, Journal, Ideen)
Agent 3: Chat (DanteChatColumn, JustChatPage)
Agent 4: E-Mails (ThreadList, DraftArea, EmailsPage)
Agent 5: Projekte (ProjectsPage, ProjectOverviewTab, NewProjectWizard)
Agent 6: CRM (alle Unterregisterkarten)
Agent 7: Profil, Einstellungen, Tresor, Benachrichtigungen
Agent 8: Management-Suite (alle Seiten)
Agent 1 MUSS zuerst abschließen. Seine Ausgabe ist die Eingabe für alle anderen Agenten.
Wann verwenden
- Wenn systematisches Debuggen „keine Fehler“ findet, Benutzer aber defekte UI melden
- Nach Änderung einer Zustand-Store-Aktion (alle Aufrufer prüfen)
- Nach einem Refactoring, das gemeinsamen Zustand betrifft
- Vor der Veröffentlichung, bei kritischen Benutzerabläufen
- Wenn ein Button „nichts tut“ – dies ist DAS Werkzeug dafür
Wann NICHT verwenden
- Für API-Level-Fehler (falsche Antwortstruktur, fehlender Endpunkt) – verwende systematisches Debuggen
- Für Stil-/Layout-Probleme – visuelle Inspektion
- Für Leistungsprobleme – Profiling-Tools
Integration mit anderen Fähigkeiten
- Nach
/superpowers:systematic-debuggingausführen (das die anderen 54 Fehlertypen findet) - Vor
/superpowers:verification-before-completionausführen (das prüft, ob Korrekturen funktionieren) - Liefert Eingaben für
/superpowers:test-driven-development– jeder hier gefundene Fehler sollte einen Test bekommen
Beispiel: Der Fehler, der diese Fähigkeit inspirierte
ThreadList.tsx „Neue E-Mail“-Button:
onClick={() => {
useEmailStore.getState().setComposeMode(true) // ✓ setzt composeMode = true
useEmailStore.getState().selectThread(null) // ✗ SETZT composeMode = false ZURÜCK
}}
Store-Definition:
selectThread: (thread) => set({
selectedThread: thread,
selectedThreadId: thread?.id ?? null,
messages: [],
drafts: [],
selectedDraft: null,
summary: null,
composeMode: false, // ← Dieses stille Zurücksetzen hat den Button lahmgelegt
composeData: null,
redraftOpen: false,
})
Systematisches Debuggen übersah es, weil:
- Der Button hat einen onClick-Handler (nicht tot)
- Beide Funktionen existieren (keine fehlende Verdrahtung)
- Keine der Funktionen stürzt ab (kein Laufzeitfehler)
- Die Datentypen sind korrekt (kein Typkonflikt)
Das Klickpfad-Audit fängt es, weil:
- Schritt 1 kartiert, dass
selectThreadcomposeModezurücksetzt - Schritt 2 verfolgt den Handler: Aufruf 1 setzt true, Aufruf 2 setzt false zurück
- Urteil: Sequenzielles Rückgängigmachen – Endzustand widerspricht der Button-Intention