Description
CopyButton (app/components/copy-button.tsx:37) fires
setTimeout(() => setCopied(false), 2000) on every copy with no stored id and no cleanup. It
assumes exactly one click at a time and that the component stays mounted for the full 2s.
Steps to Reproduce
- Rapid re-click: click Copy at t=0 (timer A, reverts at 2.0s). Click again at t=1.5s
(copied → true, timer B). Timer A still fires at t=2.0s and flips the checkmark back to the
copy icon 0.5s early, contradicting the just-shown feedback.
- Unmount mid-window: copy a row's value, then let the sessions table re-paginate/filter
(unmounting that row) before 2s elapses → setCopied runs on an unmounted component →
React "state update on unmounted component" warning; the leaked timer pins text in its closure.
Expected Behavior
Only the latest timer should control the revert; no state updates after unmount.
Failproof AI Version
0.0.14-beta.1
Node.js / Bun Version
Bun 1.3+
Operating System
macOS
Additional Context
Store the timeout id in a useRef, clearTimeout it before re-arming, and clear it in a
useEffect(() => () => clearTimeout(ref.current), []) cleanup. (Bonus: the <button> is also
missing type="button" at :53.)
Description
CopyButton(app/components/copy-button.tsx:37) firessetTimeout(() => setCopied(false), 2000)on every copy with no stored id and no cleanup. Itassumes exactly one click at a time and that the component stays mounted for the full 2s.
Steps to Reproduce
(
copied→ true, timer B). Timer A still fires at t=2.0s and flips the checkmark back to thecopy icon 0.5s early, contradicting the just-shown feedback.
(unmounting that row) before 2s elapses →
setCopiedruns on an unmounted component →React "state update on unmounted component" warning; the leaked timer pins
textin its closure.Expected Behavior
Only the latest timer should control the revert; no state updates after unmount.
Failproof AI Version
0.0.14-beta.1
Node.js / Bun Version
Bun 1.3+
Operating System
macOS
Additional Context
Store the timeout id in a
useRef,clearTimeoutit before re-arming, and clear it in auseEffect(() => () => clearTimeout(ref.current), [])cleanup. (Bonus: the<button>is alsomissing
type="button"at:53.)