click-path-audit
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese/click-path-audit — Behavioural Flow Audit
/click-path-audit — 行为流审计
Find bugs that static code reading misses: state interaction side effects, race conditions between sequential calls, and handlers that silently undo each other.
找出静态代码检查无法发现的bug:状态交互副作用、顺序调用间的竞态条件,以及会悄悄抵消彼此操作的处理函数。
The Problem This Solves
解决的问题
Traditional debugging checks:
- Does the function exist? (missing wiring)
- Does it crash? (runtime errors)
- Does it return the right type? (data flow)
But it does NOT check:
- Does the final UI state match what the button label promises?
- Does function B silently undo what function A just did?
- Does shared state (Zustand/Redux/context) have side effects that cancel the intended action?
Real example: A "New Email" button called then . Both worked individually. But had a side effect resetting . The button did nothing. 54 bugs were found by systematic debugging — this one was missed.
setComposeMode(true)selectThread(null)selectThreadcomposeMode: false传统调试检查的内容:
- 函数是否存在?(是否缺少关联)
- 函数是否会崩溃?(运行时错误)
- 函数返回值类型是否正确?(数据流检查)
但传统调试不会检查:
- 最终UI状态是否与按钮标签承诺的一致?
- 函数B是否悄悄抵消了函数A刚完成的操作?
- 共享状态(Zustand/Redux/context)是否存在抵消预期操作的副作用?
真实案例:某个「新建邮件」按钮先调用,再调用。两个函数单独运行都正常,但有一个副作用:会将重置为。最终按钮点击后毫无反应。系统调试找出了54个bug,但唯独漏掉了这个。
setComposeMode(true)selectThread(null)selectThreadcomposeModefalseHow It Works
工作原理
For EVERY interactive touchpoint in the target area:
1. IDENTIFY the handler (onClick, onSubmit, onChange, etc.)
2. TRACE every function call in the handler, IN ORDER
3. For EACH function call:
a. What state does it READ?
b. What state does it WRITE?
c. Does it have SIDE EFFECTS on shared state?
d. Does it reset/clear any state as a side effect?
4. CHECK: Does any later call UNDO a state change from an earlier call?
5. CHECK: Is the FINAL state what the user expects from the button label?
6. CHECK: Are there race conditions (async calls that resolve in wrong order)?针对目标区域内的每一个交互式交互点:
1. 识别处理函数(onClick、onSubmit、onChange等)
2. 按顺序追踪处理函数中的每一次调用
3. 针对每一次函数调用:
a. 它读取了哪些状态?
b. 它修改了哪些状态?
c. 它是否会对共享状态产生副作用?
d. 它是否会作为副作用重置/清空某些状态?
4. 检查:后续调用是否抵消了之前调用的状态变更?
5. 检查:最终状态是否符合用户对按钮标签的预期?
6. 检查:是否存在竞态条件(异步调用按错误顺序完成)?Execution Steps
执行步骤
Step 1: Map State Stores
步骤1:梳理状态存储
Before auditing any touchpoint, build a side-effect map of every state store action:
For each Zustand store / React context in scope:
For each action/setter:
- What fields does it set?
- Does it RESET other fields as a side effect?
- Document: actionName → {sets: [...], resets: [...]}This is the critical reference. The "New Email" bug was invisible without knowing that resets .
selectThreadcomposeModeOutput format:
STORE: emailStore
setComposeMode(bool) → sets: {composeMode}
selectThread(thread|null) → sets: {selectedThread, selectedThreadId, messages, drafts, selectedDraft, summary} RESETS: {composeMode: false, composeData: null, redraftOpen: false}
setDraftGenerating(bool) → sets: {draftGenerating}
...
DANGEROUS RESETS (actions that clear state they don't own):
selectThread → resets composeMode (owned by setComposeMode)
reset → resets everything在审计任何交互点之前,为每个状态存储的操作建立副作用映射:
For each Zustand store / React context in scope:
For each action/setter:
- What fields does it set?
- Does it RESET other fields as a side effect?
- Document: actionName → {sets: [...], resets: [...]}这是关键的参考依据。如果没有提前知道会重置,那个「新建邮件」的bug就完全无法被发现。
selectThreadcomposeMode输出格式:
STORE: emailStore
setComposeMode(bool) → sets: {composeMode}
selectThread(thread|null) → sets: {selectedThread, selectedThreadId, messages, drafts, selectedDraft, summary} RESETS: {composeMode: false, composeData: null, redraftOpen: false}
setDraftGenerating(bool) → sets: {draftGenerating}
...
DANGEROUS RESETS (actions that clear state they don't own):
selectThread → resets composeMode (owned by setComposeMode)
reset → resets everythingStep 2: Audit Each Touchpoint
步骤2:审计每个交互点
For each button/toggle/form submit in the target area:
TOUCHPOINT: [Button label] in [Component:line]
HANDLER: onClick → {
call 1: functionA() → sets {X: true}
call 2: functionB() → sets {Y: null} RESETS {X: false} ← CONFLICT
}
EXPECTED: User sees [description of what button label promises]
ACTUAL: X is false because functionB reset it
VERDICT: BUG — [description]Check each of these bug patterns:
针对目标区域内的每个按钮/开关/表单提交:
TOUCHPOINT: [Button label] in [Component:line]
HANDLER: onClick → {
call 1: functionA() → sets {X: true}
call 2: functionB() → sets {Y: null} RESETS {X: false} ← CONFLICT
}
EXPECTED: User sees [description of what button label promises]
ACTUAL: X is false because functionB reset it
VERDICT: BUG — [description]检查以下bug模式:
Pattern 1: Sequential Undo
模式1:顺序抵消
handler() {
setState_A(true) // sets X = true
setState_B(null) // side effect: resets X = false
}
// Result: X is false. First call was pointless.handler() {
setState_A(true) // sets X = true
setState_B(null) // side effect: resets X = false
}
// Result: X is false. First call was pointless.Pattern 2: Async Race
模式2:异步竞态
handler() {
fetchA().then(() => setState({ loading: false }))
fetchB().then(() => setState({ loading: true }))
}
// Result: final loading state depends on which resolves firsthandler() {
fetchA().then(() => setState({ loading: false }))
fetchB().then(() => setState({ loading: true }))
}
// Result: final loading state depends on which resolves firstPattern 3: Stale Closure
模式3:闭包过期
const [count, setCount] = useState(0)
const handler = useCallback(() => {
setCount(count + 1) // captures stale count
setCount(count + 1) // same stale count — increments by 1, not 2
}, [count])const [count, setCount] = useState(0)
const handler = useCallback(() => {
setCount(count + 1) // captures stale count
setCount(count + 1) // same stale count — increments by 1, not 2
}, [count])Pattern 4: Missing State Transition
模式4:缺失状态转换
// Button says "Save" but handler only validates, never actually saves
// Button says "Delete" but handler sets a flag without calling the API
// Button says "Send" but the API endpoint is removed/broken// Button says "Save" but handler only validates, never actually saves
// Button says "Delete" but handler sets a flag without calling the API
// Button says "Send" but the API endpoint is removed/brokenPattern 5: Conditional Dead Path
模式5:条件死路径
handler() {
if (someState) { // someState is ALWAYS false at this point
doTheActualThing() // never reached
}
}handler() {
if (someState) { // someState is ALWAYS false at this point
doTheActualThing() // never reached
}
}Pattern 6: useEffect Interference
模式6:useEffect干扰
// Button sets stateX = true
// A useEffect watches stateX and resets it to false
// User sees nothing happen// Button sets stateX = true
// A useEffect watches stateX and resets it to false
// User sees nothing happenStep 3: Report
步骤3:生成报告
For each bug found:
CLICK-PATH-NNN: [severity: CRITICAL/HIGH/MEDIUM/LOW]
Touchpoint: [Button label] in [file:line]
Pattern: [Sequential Undo / Async Race / Stale Closure / Missing Transition / Dead Path / useEffect Interference]
Handler: [function name or inline]
Trace:
1. [call] → sets {field: value}
2. [call] → RESETS {field: value} ← CONFLICT
Expected: [what user expects]
Actual: [what actually happens]
Fix: [specific fix]针对每个发现的bug:
CLICK-PATH-NNN: [severity: CRITICAL/HIGH/MEDIUM/LOW]
Touchpoint: [Button label] in [file:line]
Pattern: [Sequential Undo / Async Race / Stale Closure / Missing Transition / Dead Path / useEffect Interference]
Handler: [function name or inline]
Trace:
1. [call] → sets {field: value}
2. [call] → RESETS {field: value} ← CONFLICT
Expected: [what user expects]
Actual: [what actually happens]
Fix: [specific fix]Scope Control
范围控制
This audit is expensive. Scope it appropriately:
- Full app audit: Use when launching or after major refactor. Launch parallel agents per page.
- Single page audit: Use after building a new page or after a user reports a broken button.
- Store-focused audit: Use after modifying a Zustand store — audit all consumers of the changed actions.
该审计的成本较高,需合理限定范围:
- 全应用审计:适用于应用发布前或重大重构后。可按页面分配并行处理任务。
- 单页面审计:适用于新页面开发完成后,或用户反馈某页面按钮失效时。
- 聚焦存储的审计:适用于修改Zustand存储之后,需审计所有调用了该存储修改操作的代码。
Recommended agent split for full app:
全应用审计的推荐分工:
Agent 1: Map ALL state stores (Step 1) — this is shared context for all other agents
Agent 2: Dashboard (Tasks, Notes, Journal, Ideas)
Agent 3: Chat (DanteChatColumn, JustChatPage)
Agent 4: Emails (ThreadList, DraftArea, EmailsPage)
Agent 5: Projects (ProjectsPage, ProjectOverviewTab, NewProjectWizard)
Agent 6: CRM (all sub-tabs)
Agent 7: Profile, Settings, Vault, Notifications
Agent 8: Management Suite (all pages)Agent 1 MUST complete first. Its output is input for all other agents.
Agent 1: Map ALL state stores (Step 1) — this is shared context for all other agents
Agent 2: Dashboard (Tasks, Notes, Journal, Ideas)
Agent 3: Chat (DanteChatColumn, JustChatPage)
Agent 4: Emails (ThreadList, DraftArea, EmailsPage)
Agent 5: Projects (ProjectsPage, ProjectOverviewTab, NewProjectWizard)
Agent 6: CRM (all sub-tabs)
Agent 7: Profile, Settings, Vault, Notifications
Agent 8: Management Suite (all pages)Agent 1必须最先完成,其输出结果是其他所有任务的共享参考依据。
When to Use
适用场景
- After systematic debugging finds "no bugs" but users report broken UI
- After modifying any Zustand store action (check all callers)
- After any refactor that touches shared state
- Before release, on critical user flows
- When a button "does nothing" — this is THE tool for that
- 系统调试未发现bug,但用户反馈UI失效时
- 修改任何Zustand存储操作后(需检查所有调用方)
- 涉及共享状态的重大重构之后
- 发布前,针对核心用户流程的审计
- 按钮点击后无反应时(这是排查此类问题的专属工具)
When NOT to Use
不适用场景
- For API-level bugs (wrong response shape, missing endpoint) — use systematic-debugging
- For styling/layout issues — visual inspection
- For performance issues — profiling tools
- API层面的bug(响应格式错误、缺失接口)—— 应使用系统调试工具
- 样式/布局问题—— 应使用视觉检查
- 性能问题—— 应使用性能分析工具
Integration with Other Skills
与其他技能的集成
- Run AFTER (which finds the other 54 bug types)
/superpowers:systematic-debugging - Run BEFORE (which verifies fixes work)
/superpowers:verification-before-completion - Feeds into — every bug found here should get a test
/superpowers:test-driven-development
- 需在(可排查其他54类bug)之后执行
/superpowers:systematic-debugging - 需在(验证修复是否有效)之前执行
/superpowers:verification-before-completion - 可为提供输入—— 此处发现的每个bug都应编写对应的测试用例
/superpowers:test-driven-development
Example: The Bug That Inspired This Skill
示例:启发该技能的bug案例
ThreadList.tsx "New Email" button:
onClick={() => {
useEmailStore.getState().setComposeMode(true) // ✓ sets composeMode = true
useEmailStore.getState().selectThread(null) // ✗ RESETS composeMode = false
}}Store definition:
selectThread: (thread) => set({
selectedThread: thread,
selectedThreadId: thread?.id ?? null,
messages: [],
drafts: [],
selectedDraft: null,
summary: null,
composeMode: false, // ← THIS silent reset killed the button
composeData: null,
redraftOpen: false,
})Systematic debugging missed it because:
- The button has an onClick handler (not dead)
- Both functions exist (no missing wiring)
- Neither function crashes (no runtime error)
- The data types are correct (no type mismatch)
Click-path audit catches it because:
- Step 1 maps resets
selectThreadcomposeMode - Step 2 traces the handler: call 1 sets true, call 2 resets false
- Verdict: Sequential Undo — final state contradicts button intent
ThreadList.tsx中的「新建邮件」按钮:
onClick={() => {
useEmailStore.getState().setComposeMode(true) // ✓ sets composeMode = true
useEmailStore.getState().selectThread(null) // ✗ RESETS composeMode = false
}}存储定义:
selectThread: (thread) => set({
selectedThread: thread,
selectedThreadId: thread?.id ?? null,
messages: [],
drafts: [],
selectedDraft: null,
summary: null,
composeMode: false, // ← THIS silent reset killed the button
composeData: null,
redraftOpen: false,
})系统调试为何漏掉了它?
- 按钮有对应的onClick处理函数(不是死按钮)
- 两个函数都存在(没有缺失关联)
- 两个函数都不会崩溃(无运行时错误)
- 数据类型都正确(无类型不匹配)
点击路径审计为何能发现它?
- 步骤1梳理出会重置
selectThreadcomposeMode - 步骤2追踪处理函数:第一次调用将值设为true,第二次调用将其重置为false
- 结论:顺序抵消——最终状态与按钮的预期功能矛盾