feat: add click-path-audit skill — finds state interaction bugs#729
Conversation
New debugging skill that traces every button/touchpoint through its full state change sequence. Catches bugs where functions individually work but cancel each other out via shared state side effects. Covers 6 bug patterns: 1. Sequential Undo — call B resets what call A just set 2. Async Race — double-click bypasses state-based loading guards 3. Stale Closure — useCallback captures old value 4. Missing State Transition — handler doesn't do what label says 5. Conditional Dead Path — condition always false, action unreachable 6. useEffect Interference — effect undoes button action Battle-tested: found 48 bugs in a production React+Zustand app that systematic debugging (54 bugs found separately) completely missed.
📝 WalkthroughWalkthroughAdded a new skill documentation file describing a behavioral flow audit process for debugging UI interactions. The document outlines a workflow for tracing handler-invoked function calls, recording state field operations, detecting conflicts, and specifying required bug reports with integration points to other debugging skills. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~15 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Tip CodeRabbit can use oxc to improve the quality of JavaScript and TypeScript code reviews.Add a configuration file to your project to customize how CodeRabbit runs oxc. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
skills/click-path-audit/SKILL.md (2)
53-57: Include Redux explicitly in the state-store mapping step.Step 1 currently scopes mapping to Zustand and React Context only; please add Redux here too so execution guidance matches the stated compatibility.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@skills/click-path-audit/SKILL.md` around lines 53 - 57, Update the state-store mapping step text to explicitly include Redux alongside Zustand and React Context: change the line that begins "For each Zustand store / React context in scope:" to mention Redux (e.g., "For each Zustand store / React context / Redux store in scope:") so the instructions and subsequent action/setter documentation apply to Redux stores as well; ensure any later references to "store" or "state-store" in the same section reflect this inclusion.
210-210: Add an explicitExamplessection heading.To align with the skills doc template, consider renaming this heading to
## Examplesand keeping this case as the first example beneath it. As per coding guidelines: “Skills should be formatted as Markdown with clear sections for When to Use, How It Works, and Examples”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@skills/click-path-audit/SKILL.md` at line 210, Rename the heading "## Example: The Bug That Inspired This Skill" to "## Examples" and move its current content to be the first example under that new "## Examples" section so the document follows the skill template; ensure the casing exactly matches "## Examples" and preserve the existing example text and order beneath it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@skills/click-path-audit/SKILL.md`:
- Line 166: Replace the phrase "Single page audit" with the hyphenated compound
adjective "Single-page audit" in the SKILL.md content (look for the exact string
"Single page audit" in the line that reads "- **Single page audit:** Use after
building a new page or after a user reports a broken button."); update that
markdown line to "- **Single-page audit:** Use after building a new page or
after a user reports a broken button." to restore correct grammar.
---
Nitpick comments:
In `@skills/click-path-audit/SKILL.md`:
- Around line 53-57: Update the state-store mapping step text to explicitly
include Redux alongside Zustand and React Context: change the line that begins
"For each Zustand store / React context in scope:" to mention Redux (e.g., "For
each Zustand store / React context / Redux store in scope:") so the instructions
and subsequent action/setter documentation apply to Redux stores as well; ensure
any later references to "store" or "state-store" in the same section reflect
this inclusion.
- Line 210: Rename the heading "## Example: The Bug That Inspired This Skill" to
"## Examples" and move its current content to be the first example under that
new "## Examples" section so the document follows the skill template; ensure the
casing exactly matches "## Examples" and preserve the existing example text and
order beneath it.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 799acaec-23ca-46f3-8d8c-bca4f39527be
📒 Files selected for processing (1)
skills/click-path-audit/SKILL.md
| 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. |
There was a problem hiding this comment.
Use hyphenated compound adjective at Line 166.
Change “Single page audit” to “Single-page audit” for grammar consistency.
🧰 Tools
🪛 LanguageTool
[grammar] ~166-~166: Use a hyphen to join words.
Context: ...nch parallel agents per page. - Single page audit: Use after building a new p...
(QB_NEW_EN_HYPHEN)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@skills/click-path-audit/SKILL.md` at line 166, Replace the phrase "Single
page audit" with the hyphenated compound adjective "Single-page audit" in the
SKILL.md content (look for the exact string "Single page audit" in the line that
reads "- **Single page audit:** Use after building a new page or after a user
reports a broken button."); update that markdown line to "- **Single-page
audit:** Use after building a new page or after a user reports a broken button."
to restore correct grammar.
Greptile SummaryThis PR adds a new community skill ( Key issues found:
Confidence Score: 3/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User clicks button] --> B[Step 1: Map state store actions]
B --> C{Build side-effect map}
C --> C1["action → {sets: [...], resets: [...]}"]
C1 --> D[Step 2: Audit each touchpoint]
D --> E[Trace handler call sequence in order]
E --> F{Check 6 bug patterns}
F --> P1["Pattern 1: Sequential Undo\n(call B resets what call A set)"]
F --> P2["Pattern 2: Async Race\n(two fetches resolve in wrong order)"]
F --> P3["Pattern 3: Stale Closure\n(stale value captured in callback)"]
F --> P4["Pattern 4: Missing Transition\n(button label promises action never taken)"]
F --> P5["Pattern 5: Conditional Dead Path\n(guard condition always false)"]
F --> P6["Pattern 6: useEffect Interference\n(effect resets state after button sets it)"]
P1 --> G[Step 3: Report bug]
P2 --> G
P3 --> G
P4 --> G
P5 --> G
P6 --> G
G --> H["CLICK-PATH-NNN: severity\nTouchpoint / Pattern / Trace / Fix"]
Last reviewed commit: "feat: add click-path..." |
| - 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 |
There was a problem hiding this comment.
Broken skill references — namespace
superpowers: does not exist
The integration section references three skills using a /superpowers: namespace prefix that does not exist in this repository:
/superpowers:systematic-debugging/superpowers:verification-before-completion/superpowers:test-driven-development
Running git ls-files skills/ reveals no superpowers/ directory and no files matching this namespace pattern. As a result, users following this guidance will get errors or silent no-ops when attempting to chain these skills together.
The equivalent skills in this repo appear to be:
/tdd-workflow(maps tosuperpowers:test-driven-development)
The other two don't have clear equivalents in the current skills/ tree and should either be removed or replaced with the correct skill invocation paths.
| - 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 | |
| - Run AFTER `/systematic-debugging` (which finds the other 54 bug types) | |
| - Run BEFORE `/verification-loop` (which verifies fixes work) | |
| - Feeds into `/tdd-workflow` — every bug found here should get a test |
| #### Pattern 3: Stale Closure | ||
| ``` | ||
| 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]) | ||
| ``` |
There was a problem hiding this comment.
Stale Closure example is technically misleading
The example includes count in useCallback's dependency array ([count]), yet the inline comment says // captures stale count. This is contradictory: when a dependency is correctly listed, React regenerates the callback whenever count changes, so the closure is not stale.
The two consecutive setCount(count + 1) calls do both resolve to the same value, but this is because React batches synchronous state updates within a single event handler — not because of a stale closure. The actual stale-closure pattern occurs when the dependency array is empty or incorrect (e.g., []), causing an outdated count to be captured across renders.
As written, this example teaches the wrong mental model for two different bugs at once, which will reduce its effectiveness as a debugging reference.
| #### Pattern 3: Stale Closure | |
| ``` | |
| 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 3: Stale Closure | |
| ```javascript | |
| const [count, setCount] = useState(0) | |
| const handler = useCallback(() => { | |
| setCount(count + 1) // captures stale count from closure | |
| setCount(count + 1) // same stale count — increments by 1, not 2 | |
| }, []) // ← missing `count` in deps — closure never refreshes |
|
|
||
| ``` | ||
| 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. |
There was a problem hiding this comment.
App-specific component names make skill non-reusable for the community
The recommended agent-split template hard-codes component names that belong to the author's private production app (DanteChatColumn, JustChatPage, ThreadList, DraftArea, EmailsPage, ProjectsPage, ProjectOverviewTab, NewProjectWizard, CRM). A community skill contributor should replace these with generic placeholders so that adopters can fill in their own app structure.
For example:
| ``` | |
| 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 page(s)>
Agent 3: <Chat / messaging page(s)>
Agent 4: <Email / inbox page(s)>
Agent 5: <Projects / tasks page(s)>
Agent 6: <CRM / contacts page(s)>
Agent 7: <Settings / profile page(s)>
Agent 8: <Any remaining page(s)>
Replace each agent's scope with the actual page names in your app.
| #### Pattern 2: Async Race | ||
| ``` | ||
| handler() { | ||
| fetchA().then(() => setState({ loading: false })) | ||
| fetchB().then(() => setState({ loading: true })) | ||
| } | ||
| // Result: final loading state depends on which resolves first | ||
| ``` |
There was a problem hiding this comment.
Async Race pattern doesn't cover the dominant finding described in the PR
The PR description states that 30 of 48 bugs were "async race conditions where React useState guards could be bypassed by double-clicks in the same render frame" with "ref-based synchronous guards" as the universal fix. However, Pattern 2 illustrates a completely different scenario — two concurrent fetch calls whose .then callbacks land in an unpredictable order. That is a valid race, but it's not the one that was the dominant finding.
Developers reading this skill will have no guidance on how to prevent or fix the double-click guard bypass pattern. Consider adding a dedicated Pattern 2b (or expanding Pattern 2) with the ref-based guard anti-pattern and fix, e.g.:
// Double-click bypass — useState guard is not synchronous
const [isLoading, setIsLoading] = useState(false)
const handler = async () => {
if (isLoading) return // ← guard reads STALE state mid-render
setIsLoading(true)
await doWork()
setIsLoading(false)
}
// Fix: use a ref for the synchronous guard
const isLoadingRef = useRef(false)
const handlerFixed = async () => {
if (isLoadingRef.current) return // ← synchronous, not batched
isLoadingRef.current = true
await doWork()
isLoadingRef.current = false
}There was a problem hiding this comment.
2 issues found across 1 file
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="skills/click-path-audit/SKILL.md">
<violation number="1" location="skills/click-path-audit/SKILL.md:116">
P3: This “Stale Closure” example is incorrect with `[count]` in the dependency array, because the callback is refreshed when `count` changes. Update the example to intentionally omit `count` (or use a different scenario) so it actually demonstrates a stale closure bug.</violation>
<violation number="2" location="skills/click-path-audit/SKILL.md:204">
P2: Use valid local skill command paths here; the `/superpowers:...` references are not resolvable in this repository, so users following this integration guidance will hit command errors.</violation>
</file>
Since this is your first cubic review, here's how it works:
- cubic automatically reviews your code and comments on bugs and improvements
- Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
- Add one-off context when rerunning by tagging
@cubic-dev-aiwith guidance or docs links (includingllms.txt) - Ask questions if you need clarification on any suggestion
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
|
|
||
| ## Integration with Other Skills | ||
|
|
||
| - Run AFTER `/superpowers:systematic-debugging` (which finds the other 54 bug types) |
There was a problem hiding this comment.
P2: Use valid local skill command paths here; the /superpowers:... references are not resolvable in this repository, so users following this integration guidance will hit command errors.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At skills/click-path-audit/SKILL.md, line 204:
<comment>Use valid local skill command paths here; the `/superpowers:...` references are not resolvable in this repository, so users following this integration guidance will hit command errors.</comment>
<file context>
@@ -0,0 +1,244 @@
+
+## 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
</file context>
| const handler = useCallback(() => { | ||
| setCount(count + 1) // captures stale count | ||
| setCount(count + 1) // same stale count — increments by 1, not 2 | ||
| }, [count]) |
There was a problem hiding this comment.
P3: This “Stale Closure” example is incorrect with [count] in the dependency array, because the callback is refreshed when count changes. Update the example to intentionally omit count (or use a different scenario) so it actually demonstrates a stale closure bug.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At skills/click-path-audit/SKILL.md, line 116:
<comment>This “Stale Closure” example is incorrect with `[count]` in the dependency array, because the callback is refreshed when `count` changes. Update the example to intentionally omit `count` (or use a different scenario) so it actually demonstrates a stale closure bug.</comment>
<file context>
@@ -0,0 +1,244 @@
+const handler = useCallback(() => {
+ setCount(count + 1) // captures stale count
+ setCount(count + 1) // same stale count — increments by 1, not 2
+}, [count])
+```
+
</file context>
affaan-m
left a comment
There was a problem hiding this comment.
High-quality skill for state interaction bugs. Well-structured, addresses a real gap.
…an-m#729) New debugging skill that traces every button/touchpoint through its full state change sequence. Catches bugs where functions individually work but cancel each other out via shared state side effects. Covers 6 bug patterns: 1. Sequential Undo — call B resets what call A just set 2. Async Race — double-click bypasses state-based loading guards 3. Stale Closure — useCallback captures old value 4. Missing State Transition — handler doesn't do what label says 5. Conditional Dead Path — condition always false, action unreachable 6. useEffect Interference — effect undoes button action Battle-tested: found 48 bugs in a production React+Zustand app that systematic debugging (54 bugs found separately) completely missed.
…an-m#729) New debugging skill that traces every button/touchpoint through its full state change sequence. Catches bugs where functions individually work but cancel each other out via shared state side effects. Covers 6 bug patterns: 1. Sequential Undo — call B resets what call A just set 2. Async Race — double-click bypasses state-based loading guards 3. Stale Closure — useCallback captures old value 4. Missing State Transition — handler doesn't do what label says 5. Conditional Dead Path — condition always false, action unreachable 6. useEffect Interference — effect undoes button action Battle-tested: found 48 bugs in a production React+Zustand app that systematic debugging (54 bugs found separately) completely missed.
Summary
New debugging skill that fills a gap no existing skill covers: tracing button handlers through shared state to find bugs where functions individually work but cancel each other out.
The problem
Systematic debugging checks: does the function exist? Does it crash? Does it return the right type? But it does NOT check: does the final UI state match what the button label promises?
Real-world results
We ran systematic debugging on a production React + Zustand app and found 54 bugs. Then a user reported a button that "does nothing." Systematic debugging had missed it because:
The root cause:
selectThread(null)had a side effect resettingcomposeMode: false, undoing whatsetComposeMode(true)had just set. Two functions that work perfectly individually, but cancel each other out.We created this skill and ran it across the entire app — found 48 additional bugs that systematic debugging completely missed.
What the skill does
Dominant finding
30 of 48 bugs were async race conditions where React
useStateguards could be bypassed by double-clicks in the same render frame. The universal fix: ref-based synchronous guards.Works with
Any React codebase using Zustand, Redux, or React Context for shared state management.
Test plan
Summary by cubic
Add the
click-path-auditdebugging skill to trace button click paths and catch state interaction bugs that make UI actions appear to do nothing. It maps shared state side effects and flags six common patterns like sequential undo and async races.skills/click-path-audit/SKILL.md.zustand,redux, or React context; no external dependencies.Written for commit 2a86673. Summary will update on new commits.
Summary by CodeRabbit