fix(devtools): prevent StateEditor from freezing the page on live state - #1058
Conversation
The State/Payload and App Config editors deep-synced the live reactive
state back into itself under a { deep: true } watcher. For any state
containing an array (or nested object), each sync produced a fresh
identity that re-triggered the watcher, causing an unbounded reactivity
loop that froze the whole page whenever those tabs were shown.
Snapshot the state into a detached proxy instead of mutating it, and
drive updates off the existing revision signal rather than a deep
watcher over the (potentially huge) state object.
Closes #972
Deploying nuxt-devtools with
|
| Latest commit: |
c0feb3f
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://42156892.nuxt-devtools.pages.dev |
| Branch Preview URL: | https://fix-state-editor-freeze-loop.nuxt-devtools.pages.dev |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthrough
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/devtools/client/components/StateEditor.vue`:
- Around line 27-45: The clone function’s JSON serialization strips Symbol
values and fails on nested BigInt values. Replace the JSON.stringify/parse path
in clone with a detached non-JSON deep-cloning approach that preserves supported
values, while retaining primitive handling and avoiding mutation of props.state;
add coverage for Symbol, nested BigInt, and nested Symbol states.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 0ad29143-9e54-4a2c-8e4f-e417ad70c39d
📒 Files selected for processing (1)
packages/devtools/client/components/StateEditor.vue
Replace JSON.parse(JSON.stringify()) with structured-clone-es, which
tolerates circular references, preserves richer types (Map/Set/Date),
and drops functions/symbols via { lossy: true } instead of throwing.
Problem
Fixes #972 — the whole browser tab becomes unresponsive shortly after Nuxt DevTools loads (users reported it freezing on the App Config / Payload tabs, and it correlates with
useStatedata). Related to #761 and #936.Root cause
StateEditor.vueset up a{ deep: true }watcher over the live reactive state and, on every tick, randeepSync(state, props.state)— copying the live reactive state back into itself. For arrays it didto[key] = from[key].slice(), producing a brand-new array identity every run, which the deep watcher immediately re-detected and re-fired. AnyuseState/payload/app-config value containing an array (or nested object) therefore triggered an unbounded reactivity loop that pinned the main thread and froze the page.One
StateEditoris mounted per top-level state key, so the loop fired as soon as an affected tab was shown — matching the reports of the page hanging right after DevTools opens on those tabs.Extra hazards removed along the way: the deep watcher forced full recursive traversal/subscription of the entire (potentially huge) payload, and
deepSynchad no cycle guard or depth cap.Fix
proxyfor the JSON editor and never mutateprops.state.revisionsignal (already bumped on every host reactivity update) instead of a deep watcher over the state object, debounced to avoid thrashing.Behaviour is unchanged for normal use — the editors still show and refresh live state — but the freeze is eliminated.
This PR was created with the help of an agent.