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
setComposeMode(true)
then
selectThread(null)
. Both worked individually. But
selectThread
had a side effect resetting
composeMode: false
. The button did nothing. 54 bugs were found by systematic debugging — this one was missed.

传统调试会检查:
  • 函数是否存在?(是否缺失关联)
  • 是否会崩溃?(运行时错误)
  • 返回值类型是否正确?(数据流)
但它不会检查:
  • 最终UI状态是否与按钮标签承诺的一致?
  • 函数B是否悄悄抵消了函数A刚完成的操作?
  • 共享状态(Zustand/Redux/context)是否存在抵消预期操作的副作用?
真实案例:“新建邮件”按钮先调用
setComposeMode(true)
,再调用
selectThread(null)
。两个函数单独运行都正常,但
selectThread
有一个副作用会重置
composeMode: false
,导致按钮完全失效。常规系统调试发现了54个bug,但唯独漏掉了这一个。

How 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
selectThread
resets
composeMode
.
Output 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
在审计任何交互点之前,为每个状态存储的操作构建副作用映射:
针对作用域内的每个Zustand存储/React context:
  针对每个操作/设置函数:
    - 它设置了哪些字段?
    - 它是否会作为副作用重置其他字段?
    - 记录:actionName → {sets: [...], resets: [...]}
这是关键的参考依据。如果不知道
selectThread
会重置
composeMode
,就无法发现那个“新建邮件”按钮的bug。
输出格式:
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}
  ...

危险重置(会清除非自身所属状态的操作):
  selectThread → 重置composeMode(属于setComposeMode的管辖范围)
  reset → 重置所有状态

Step 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:
针对目标区域内的每个按钮/开关/表单提交:
交互点:[按钮标签] 在 [组件:行号]
  处理函数: onClick → {
    调用1: functionA() → 设置 {X: true}
    调用2: functionB() → 设置 {Y: null} 重置 {X: false}  ← 冲突
  }
  预期:用户看到[按钮标签承诺的效果描述]
  实际:X为false,因为functionB重置了它
  结论:BUG — [问题描述]
检查以下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)     // 设置X = true
  setState_B(null)     // 副作用:重置X = false
}
// 结果:X为false,第一次调用毫无意义

Pattern 2: Async Race

模式2:异步竞态

handler() {
  fetchA().then(() => setState({ loading: false }))
  fetchB().then(() => setState({ loading: true }))
}
// Result: final loading state depends on which resolves first
handler() {
  fetchA().then(() => setState({ loading: false }))
  fetchB().then(() => setState({ loading: true }))
}
// 结果:最终loading状态取决于哪个请求先完成

Pattern 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)  // 捕获了过期的count值
  setCount(count + 1)  // 同样使用过期值 — 最终只增加了1,而非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
// 按钮显示“保存”,但处理函数仅做验证,从未实际执行保存操作
// 按钮显示“删除”,但处理函数仅设置了一个标记,未调用API
// 按钮显示“发送”,但对应的API端点已被移除/损坏

Pattern 5: Conditional Dead Path

模式5:条件死路径

handler() {
  if (someState) {        // someState is ALWAYS false at this point
    doTheActualThing()    // never reached
  }
}
handler() {
  if (someState) {        // 此处someState始终为false
    doTheActualThing()    // 永远不会执行到
  }
}

Pattern 6: useEffect Interference

模式6:useEffect干扰

// Button sets stateX = true
// A useEffect watches stateX and resets it to false
// User sees nothing happen
// 按钮设置stateX = true
// 某个useEffect监听stateX并将其重置为false
// 用户看不到任何变化

Step 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: [严重程度: CRITICAL/HIGH/MEDIUM/LOW]
  交互点: [按钮标签] 在 [文件:行号]
    模式: [顺序抵消 / 异步竞态 / 闭包过期 / 缺失状态转换 / 死路径 / useEffect干扰]
  处理函数: [函数名或内联代码]
  追踪:
    1. [调用] → 设置 {字段: 值}
    2. [调用] → 重置 {字段: 值}  ← 冲突
  预期: [用户预期的效果]
  实际: [实际发生的情况]
  修复方案: [具体修复建议]

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.

代理1:映射所有状态存储(步骤1)— 作为其他所有代理的共享上下文
代理2:仪表板(任务、笔记、日志、想法)
代理3:聊天(DanteChatColumn、JustChatPage)
代理4:邮件(ThreadList、DraftArea、EmailsPage)
代理5:项目(ProjectsPage、ProjectOverviewTab、NewProjectWizard)
代理6:CRM(所有子标签页)
代理7:个人资料、设置、保险箱、通知
代理8:管理套件(所有页面)
代理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
  • 常规调试未发现问题,但用户反馈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
    /superpowers:systematic-debugging
    (which finds the other 54 bug types)
  • Run BEFORE
    /superpowers:verification-before-completion
    (which verifies fixes work)
  • Feeds into
    /superpowers:test-driven-development
    — every bug found here should get a test

  • /superpowers:systematic-debugging
    (可排查其他54类bug)之后运行
  • /superpowers:verification-before-completion
    (验证修复是否有效)之前运行
  • /superpowers:test-driven-development
    提供输入 — 此处发现的每个bug都应编写对应的测试用例

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
    selectThread
    resets
    composeMode
  • 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)   // ✓ 设置composeMode = true
  useEmailStore.getState().selectThread(null)      // ✗ 重置composeMode = false
}}
存储定义:
selectThread: (thread) => set({
  selectedThread: thread,
  selectedThreadId: thread?.id ?? null,
  messages: [],
  drafts: [],
  selectedDraft: null,
  summary: null,
  composeMode: false,     // ← 这个静默重置导致按钮失效
  composeData: null,
  redraftOpen: false,
})
常规系统调试未发现该问题,因为:
  • 按钮有onClick处理函数(未失效)
  • 两个函数都存在(无关联缺失)
  • 两个函数都不会崩溃(无运行时错误)
  • 数据类型正确(无类型不匹配)
Click-Path审计发现了该问题,因为:
  • 步骤1映射出
    selectThread
    会重置
    composeMode
  • 步骤2追踪处理函数:调用1设置为true,调用2重置为false
  • 结论:顺序抵消 — 最终状态与按钮意图矛盾