From c952ef061b2417b1ef5300178f0a40440ba50a96 Mon Sep 17 00:00:00 2001 From: "Anthony Fu (via agent)" Date: Fri, 30 Oct 2026 00:33:29 +0000 Subject: [PATCH 1/2] fix(client): stop re-traversing the payload on every state change The host client refreshed the DevTools UI through a deep `watch` over `nuxt.payload` (plus color mode and metrics). A deep watch re-traverses its whole source on every mutation, synchronously, so with a sizeable payload each change (e.g. every tick of a slider bound to `useState`) became a long task and the page froze, with the panel open or not. Track the same sources with an effect whose scheduler is debounced: a mutation only schedules the refresh, and the sources are traversed again (re-collecting dependencies) once the changes settle. Co-Authored-By: Claude Opus 5.5 --- .../src/runtime/plugins/view/client.ts | 53 +++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/packages/devtools/src/runtime/plugins/view/client.ts b/packages/devtools/src/runtime/plugins/view/client.ts index 0c5084cfbe..db8a313907 100644 --- a/packages/devtools/src/runtime/plugins/view/client.ts +++ b/packages/devtools/src/runtime/plugins/view/client.ts @@ -10,7 +10,7 @@ import { NuxtDevtoolsInspectPanel } from '@nuxt/devtools/webcomponents' import { createHooks } from 'hookable' import { debounce } from 'perfect-debounce' import { events as inspectorEvents, hasData as inspectorHasData, state as inspectorState } from 'vite-plugin-vue-tracer/client/overlay' -import { computed, markRaw, nextTick, reactive, ref, shallowReactive, shallowRef, toRef, watch } from 'vue' +import { computed, effect, isRef, markRaw, nextTick, reactive, ref, shallowReactive, shallowRef, toRef } from 'vue' // eslint-disable-next-line ts/ban-ts-comment // @ts-ignore tsconfig import { useAppConfig } from '#imports' @@ -303,14 +303,25 @@ function setupReactivity(client: NuxtDevtoolsHostClient, router: Router | undefi }, 100, { trailing: true }) // trigger update for reactivity - watch(() => [ - client.nuxt.payload, - client.app.colorMode.value, - client.metrics.loading(), - timeMetric, - ], () => { - refreshReactivity() - }, { deep: true }) + // + // Not a `watch(..., { deep: true })`: that re-traverses the whole payload + // on every single mutation, so with a large payload each tick of a slider + // bound to `useState` froze the page. Here a mutation only schedules the + // refresh; the sources are traversed again (re-collecting dependencies) + // once, when the changes settle. + const runner = effect(() => { + traverse([ + client.nuxt.payload, + client.app.colorMode.value, + client.metrics.loading(), + timeMetric, + ]) + }, { + scheduler: debounce(() => { + runner() + client.hooks.callHook('host:update:reactivity') + }, 100, { trailing: true }), + }) // trigger update for route change router?.afterEach(() => { @@ -321,3 +332,27 @@ function setupReactivity(client: NuxtDevtoolsHostClient, router: Router | undefi refreshReactivity() }) } + +/** + * Read every nested value, so the running effect tracks all of them. Same + * coverage as a deep `watch`: refs, arrays, Maps, Sets and plain objects. + */ +function traverse(value: unknown, seen = new Set()): void { + if (!value || typeof value !== 'object' || seen.has(value) || (value as any).__v_skip) + return + seen.add(value) + if (isRef(value)) { + traverse(value.value, seen) + } + else if (Array.isArray(value)) { + for (const item of value) + traverse(item, seen) + } + else if (value instanceof Map || value instanceof Set) { + value.forEach((item: unknown) => traverse(item, seen)) + } + else if (Object.prototype.toString.call(value) === '[object Object]') { + for (const key in value) + traverse((value as Record)[key], seen) + } +} From 1f5c002c78871114cf8c91b821a2380afe4271b4 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 00:37:09 +0000 Subject: [PATCH 2/2] [autofix.ci] apply automated fixes --- skills-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/skills-lock.json b/skills-lock.json index ac14d15a02..d7ce4f62b0 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -39,14 +39,14 @@ "source": "mattpocock/skills", "sourceType": "github", "skillPath": "skills/engineering/code-review/SKILL.md", - "computedHash": "57cfd4ceb011f05e58a1a4f4cad3b2e03419d371f8990f4bcb32ff0d435a039f", + "computedHash": "c2745e2be960b82b9fddd82cf45265f42431e7b045c403f297b25f0f311e9210", "via": "@antfu/skills" }, "codebase-design": { "source": "mattpocock/skills", "sourceType": "github", "skillPath": "skills/engineering/codebase-design/SKILL.md", - "computedHash": "b2888e4f4e8c0af3bfd557575a64b905c44e6248c3101d72077816882b3a92b9", + "computedHash": "aa11a96c08451f4ff86052d5da94004a8c97bb4ea41b2ac8a4300c189f66c89c", "via": "@antfu/skills" }, "create-adaptable-composable": { @@ -95,7 +95,7 @@ "source": "mattpocock/skills", "sourceType": "github", "skillPath": "skills/productivity/grilling/SKILL.md", - "computedHash": "ef1641a2047b71b60f1942cd35efb47d3afc9d584dff5a0b01f8c7db923aaa08", + "computedHash": "e40cc1d348f6d935e28f1e24474302d7368f0a400429271a43b35fe154891dc5", "via": "@antfu/skills" }, "handoff": { @@ -137,7 +137,7 @@ "source": "mattpocock/skills", "sourceType": "github", "skillPath": "skills/engineering/research/SKILL.md", - "computedHash": "c8c1cba327a6f824b554cd978079a3dadd7406d73174f8fb9bfef58824691970", + "computedHash": "5d85b64ecceed6b3f6d5996dc8af07180be8d1ff11ef3d26eda2acff078fbd39", "via": "@antfu/skills" }, "simple-english": {