Skip to content

refactor: move SSE to devtools RPC#314

Open
huang-julien wants to merge 2 commits intomainfrom
refactor/migrateRPC
Open

refactor: move SSE to devtools RPC#314
huang-julien wants to merge 2 commits intomainfrom
refactor/migrateRPC

Conversation

@huang-julien
Copy link
Copy Markdown
Member

🔗 Linked issue

📚 Description

This PRs move SSE to devtools RPC so we stick close to devtool's API. (and also to test devtools v4 with vite-devtools)

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 3, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@nuxt/hints@314

commit: e60ebaa

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 3, 2026

📝 Walkthrough

Walkthrough

The PR replaces SSE-based realtime plumbing for nuxt-hints with an RPC-based approach. It removes SSE server handlers, SSE-related types, and multiple client SSE plugins; adds RPC type definitions and a server-side RPC bridge that forwards Nitro hook events to a global RPC broadcaster; updates devtools initialization to expose a server RPC broadcast; and consolidates client realtime handling into the devtools client connection callback. Tests for the RPC bridge were added and the SSE endpoint test removed.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor: move SSE to devtools RPC' accurately and concisely describes the main change: migrating from Server-Sent Events to devtools RPC for hints delivery.
Description check ✅ Passed The description explains the motivation for moving SSE to devtools RPC and mentions the goal of testing devtools v4 with vite-devtools, which is directly related to the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/migrateRPC

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/app/app.vue`:
- Around line 33-43: The fetch handlers for lazyLoadHints and
htmlValidateReports currently replace the live stores (lazyLoadHints.value =
data ?? [] and htmlValidateReports.value = data ?? []), which can drop newer
live RPC updates; instead merge the fetched array into the existing store and
dedupe using the same key/logic used by the bootstrap hydration (i.e., take
lazyLoadHints and htmlValidateReports, append the fetched items (data ?? []),
then run the same dedupe/merge routine you use elsewhere to remove duplicates
and preserve newer entries). Locate the assignments in the
useHintsFeature('lazyLoad') and useHintsFeature('htmlValidate') blocks and
replace the direct overwrite with a merge+dedupe into lazyLoadHints.value and
htmlValidateReports.value respectively.
- Line 18: Move the three nuxtApp.provide(...) calls so they execute immediately
after the corresponding ref declarations (the refs backing
nuxtApp.$hydrationMismatches, nuxtApp.$lazyLoadHints, and
nuxtApp.$htmlValidateReports) instead of inside onDevtoolsClientConnected; keep
the onDevtoolsClientConnected callback only mutating those refs' .value
properties when new data arrives. Specifically, locate the ref declarations for
hydrationMismatches, lazyLoadHints, and htmlValidateReports, call
nuxtApp.provide('$hydrationMismatches', hydrationMismatches) (and similarly for
the other two) directly after those refs are created, and remove the provide
calls from the onDevtoolsClientConnected handler so the handler only updates the
refs' values.

In `@test/unit/core/rpc-bridge.test.ts`:
- Around line 63-67: The test currently wraps hooks.callHook(...) in a
synchronous arrow function which only checks for sync throws; instead call the
async hook and assert its resolution: replace the inline function with await
expect(hooks.callHook('hints:hydration:mismatch', { id: '1', componentName:
'Test', fileLocation: '/test.vue', htmlPreHydration: '', htmlPostHydration: ''
})).resolves.toBeUndefined() (or .resolves.not.toThrow equivalent) so any
rejected Promise will fail the test; keep the globalThis.__nuxtHintsRpcBroadcast
= undefined setup and target the hooks.callHook invocation to fix the assertion.
🪄 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

Run ID: 759f7974-9c11-476a-b3e8-b89f321d5e9e

📥 Commits

Reviewing files that changed from the base of the PR and between 354d198 and 1430770.

📒 Files selected for processing (20)
  • client/app/app.vue
  • client/app/plugins/0.sse.ts
  • client/app/plugins/html-validate.ts
  • client/app/plugins/hydration.ts
  • client/app/plugins/lazy-load.ts
  • client/app/utils/routes.ts
  • src/devtools.ts
  • src/module.ts
  • src/runtime/core/rpc-types.ts
  • src/runtime/core/server/rpc-bridge.ts
  • src/runtime/core/server/sse.ts
  • src/runtime/core/server/types.ts
  • src/runtime/html-validate/handlers/nitro-handlers.plugin.ts
  • src/runtime/hydration/nitro.plugin.ts
  • src/runtime/hydration/types.ts
  • src/runtime/hydration/utils.ts
  • src/runtime/lazy-load/nitro.plugin.ts
  • src/runtime/types.d.ts
  • test/unit/core/rpc-bridge.test.ts
  • test/unit/core/sse.test.ts
💤 Files with no reviewable changes (12)
  • src/runtime/types.d.ts
  • client/app/utils/routes.ts
  • src/runtime/html-validate/handlers/nitro-handlers.plugin.ts
  • src/runtime/lazy-load/nitro.plugin.ts
  • client/app/plugins/0.sse.ts
  • src/runtime/core/server/sse.ts
  • src/runtime/hydration/nitro.plugin.ts
  • client/app/plugins/hydration.ts
  • src/runtime/hydration/types.ts
  • client/app/plugins/html-validate.ts
  • test/unit/core/sse.test.ts
  • client/app/plugins/lazy-load.ts

const lazyLoadHints = ref<ComponentLazyLoadData[]>([])
const htmlValidateReports = ref<HtmlValidateReport[]>([])

const nuxtApp = useNuxtApp()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Nuxt 3 / Vue 3, if nuxtApp.provide()runs after child components mount, do existing consumers readinguseNuxtApp().provides[key] reactively pick up the new value?

💡 Result:

No, existing consumers reading useNuxtApp.provides[key] do not reactively pick up the new value if nuxtApp.provide runs after child components have mounted. NuxtApp.provide is distinct from Vue's app.provide. It adds values to a separate provides object on the nuxtApp instance, accessible via useNuxtApp.provides[key] or as $key. This is not the Vue provide/inject system and lacks Vue's reactivity system for late provides. Official docs show examples where provide and access happen in the same scope (e.g., plugins before components mount), with immediate access like console.log(nuxtApp.$hello('name')). No mention of reactivity for later provides. Issue #21022 confirms "The way that Nuxt 'provides' is different from the Vue provide - they work differently." Child components mount after plugins (Nuxt lifecycle: plugins -> middleware -> page render/mount). If provide runs post-mount (e.g., in a late hook), existing const { $key } = useNuxtApp in setup captures the nuxtApp reference but provides[key] is a plain property set later, not triggering reactivity in already-executed setups. For reactivity, use useState instead, which is SSR-friendly refs shared via payload. Sources confirm nuxtApp.provide timing is early (plugins), but question assumes post-mount scenario where no reactivity occurs.

Citations:


🏁 Script executed:

fd -type f "app.vue" | head -20

Repository: nuxt/hints

Length of output: 224


🏁 Script executed:

fd -type f "host.ts"

Repository: nuxt/hints

Length of output: 224


🏁 Script executed:

git ls-files | grep -E "(app\.vue|composables)" | head -20

Repository: nuxt/hints

Length of output: 366


🏁 Script executed:

cat -n client/app/app.vue | head -100

Repository: nuxt/hints

Length of output: 4443


🏁 Script executed:

cat -n client/app/composables/host.ts

Repository: nuxt/hints

Length of output: 2281


🏁 Script executed:

rg "hydrationMismatches|lazyLoadHints|htmlValidateReports" --type ts --type vue -A 2 -B 2

Repository: nuxt/hints

Length of output: 81


🏁 Script executed:

rg "useNuxtApp.*provides" --type ts --type vue

Repository: nuxt/hints

Length of output: 81


🏁 Script executed:

rg "hydrationMismatches|lazyLoadHints|htmlValidateReports" -A 2 -B 2

Repository: nuxt/hints

Length of output: 8503


🏁 Script executed:

rg "nuxtApp\$|useNuxtApp\(\)" -A 2 | head -100

Repository: nuxt/hints

Length of output: 5693


Move nuxtApp.provide() calls immediately after ref declarations.

The three nuxtApp.provide() calls at lines 80-82 are deferred to the onDevtoolsClientConnected callback. Pages and components (hydration.vue, html-validate.vue, component-lazy-load.vue, and the FeatureCards) access these via nuxtApp.$hydrationMismatches, nuxtApp.$lazyLoadHints, and nuxtApp.$htmlValidateReports. If these components mount before the client connects, they will receive undefined. Since nuxtApp.provide() is not part of Vue's reactivity system, late-binding provides do not trigger reactive updates in already-mounted consumers. Set these provides immediately after the refs are declared, and only mutate the ref values in the callback.

♻️ Suggested restructuring
 const hydrationMismatches = ref<(HydrationMismatchPayload | LocalHydrationMismatch)[]>([])
 const lazyLoadHints = ref<ComponentLazyLoadData[]>([])
 const htmlValidateReports = ref<HtmlValidateReport[]>([])
 
 const nuxtApp = useNuxtApp()
+nuxtApp.provide('hydrationMismatches', hydrationMismatches)
+nuxtApp.provide('lazyLoadHints', lazyLoadHints)
+nuxtApp.provide('htmlValidateReports', htmlValidateReports)
 
 onDevtoolsClientConnected((client) => {
@@
-  nuxtApp.provide('hydrationMismatches', hydrationMismatches)
-  nuxtApp.provide('lazyLoadHints', lazyLoadHints)
-  nuxtApp.provide('htmlValidateReports', htmlValidateReports)
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/app/app.vue` at line 18, Move the three nuxtApp.provide(...) calls so
they execute immediately after the corresponding ref declarations (the refs
backing nuxtApp.$hydrationMismatches, nuxtApp.$lazyLoadHints, and
nuxtApp.$htmlValidateReports) instead of inside onDevtoolsClientConnected; keep
the onDevtoolsClientConnected callback only mutating those refs' .value
properties when new data arrives. Specifically, locate the ref declarations for
hydrationMismatches, lazyLoadHints, and htmlValidateReports, call
nuxtApp.provide('$hydrationMismatches', hydrationMismatches) (and similarly for
the other two) directly after those refs are created, and remove the provide
calls from the onDevtoolsClientConnected handler so the handler only updates the
refs' values.

Comment on lines +33 to +43
if (useHintsFeature('lazyLoad')) {
$fetch<ComponentLazyLoadData[]>(new URL(LAZY_LOAD_ROUTE, window.location.origin).href).then((data) => {
lazyLoadHints.value = data ?? []
})
}

// HTML validate: fetch from server
if (useHintsFeature('htmlValidate')) {
$fetch<HtmlValidateReport[]>(new URL(HTML_VALIDATE_ROUTE, window.location.origin).href).then((data) => {
htmlValidateReports.value = data ?? []
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Merge bootstrap results instead of replacing the live stores.

Line 35 and Line 42 overwrite the whole array. If a live RPC report arrives after the request starts but before it resolves, that newer item gets dropped when the stale response wins; the hydration bootstrap above already avoids this by merging and deduping.

🩹 One way to preserve live updates
   if (useHintsFeature('lazyLoad')) {
     $fetch<ComponentLazyLoadData[]>(new URL(LAZY_LOAD_ROUTE, window.location.origin).href).then((data) => {
-      lazyLoadHints.value = data ?? []
+      const merged = new Map<string, ComponentLazyLoadData>()
+      for (const entry of data ?? [])
+        merged.set(entry.id, entry)
+      for (const entry of lazyLoadHints.value)
+        merged.set(entry.id, entry)
+      lazyLoadHints.value = [...merged.values()]
     })
   }
 
   // HTML validate: fetch from server
   if (useHintsFeature('htmlValidate')) {
     $fetch<HtmlValidateReport[]>(new URL(HTML_VALIDATE_ROUTE, window.location.origin).href).then((data) => {
-      htmlValidateReports.value = data ?? []
+      const merged = new Map<string, HtmlValidateReport>()
+      for (const report of data ?? [])
+        merged.set(report.id, report)
+      for (const report of htmlValidateReports.value)
+        merged.set(report.id, report)
+      htmlValidateReports.value = [...merged.values()]
     })
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (useHintsFeature('lazyLoad')) {
$fetch<ComponentLazyLoadData[]>(new URL(LAZY_LOAD_ROUTE, window.location.origin).href).then((data) => {
lazyLoadHints.value = data ?? []
})
}
// HTML validate: fetch from server
if (useHintsFeature('htmlValidate')) {
$fetch<HtmlValidateReport[]>(new URL(HTML_VALIDATE_ROUTE, window.location.origin).href).then((data) => {
htmlValidateReports.value = data ?? []
})
if (useHintsFeature('lazyLoad')) {
$fetch<ComponentLazyLoadData[]>(new URL(LAZY_LOAD_ROUTE, window.location.origin).href).then((data) => {
const merged = new Map<string, ComponentLazyLoadData>()
for (const entry of data ?? [])
merged.set(entry.id, entry)
for (const entry of lazyLoadHints.value)
merged.set(entry.id, entry)
lazyLoadHints.value = [...merged.values()]
})
}
// HTML validate: fetch from server
if (useHintsFeature('htmlValidate')) {
$fetch<HtmlValidateReport[]>(new URL(HTML_VALIDATE_ROUTE, window.location.origin).href).then((data) => {
const merged = new Map<string, HtmlValidateReport>()
for (const report of data ?? [])
merged.set(report.id, report)
for (const report of htmlValidateReports.value)
merged.set(report.id, report)
htmlValidateReports.value = [...merged.values()]
})
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/app/app.vue` around lines 33 - 43, The fetch handlers for
lazyLoadHints and htmlValidateReports currently replace the live stores
(lazyLoadHints.value = data ?? [] and htmlValidateReports.value = data ?? []),
which can drop newer live RPC updates; instead merge the fetched array into the
existing store and dedupe using the same key/logic used by the bootstrap
hydration (i.e., take lazyLoadHints and htmlValidateReports, append the fetched
items (data ?? []), then run the same dedupe/merge routine you use elsewhere to
remove duplicates and preserve newer entries). Locate the assignments in the
useHintsFeature('lazyLoad') and useHintsFeature('htmlValidate') blocks and
replace the direct overwrite with a merge+dedupe into lazyLoadHints.value and
htmlValidateReports.value respectively.

Comment on lines +63 to +67
it('should not throw when RPC broadcast is not available', async () => {
globalThis.__nuxtHintsRpcBroadcast = undefined
expect(() =>
hooks.callHook('hints:hydration:mismatch', { id: '1', componentName: 'Test', fileLocation: '/test.vue', htmlPreHydration: '', htmlPostHydration: '' }),
).not.toThrow()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

In Vitest, does expect(() => promiseReturningFn()).not.toThrow()assert rejected promises, or shouldawait expect(promise).resolves/rejects be used instead?

💡 Result:

No, expect( => promiseReturningFn).not.toThrow does not correctly assert that a promise resolves (i.e., does not reject). It only checks that calling the function does not synchronously throw an error. Since promise-returning functions typically do not throw synchronously, this assertion will always pass even if the promise rejects, leading to false positives. Instead, use await expect(promiseReturningFn).resolves.not.toThrow (or equivalent assertions on the resolved value) to properly assert that the promise resolves without rejecting, and then check the resolved value does not throw if needed. For simply asserting the promise resolves (does not reject), await expect(promiseReturningFn).resolves is sufficient, as any further synchronous throw on the resolved value can be asserted separately. Official Vitest documentation recommends .resolves and .rejects specifically for promise assertions to unwrap the promise and handle async behavior correctly. The .toThrow matcher is designed for synchronous function calls that throw errors, as noted in docs and issues: ".rejects correctly unwraps the promise" while sync toThrow expects a function.

Citations:


🏁 Script executed:

cat -n test/unit/core/rpc-bridge.test.ts | sed -n '50,80p'

Repository: nuxt/hints

Length of output: 1045


🏁 Script executed:

# Find where hooks is defined or imported in the test file
rg -A 5 -B 5 "hooks\s*=" test/unit/core/rpc-bridge.test.ts | head -30

Repository: nuxt/hints

Length of output: 396


🏁 Script executed:

# Search for callHook implementation to confirm it's async
fd -e ts -e js | xargs rg -l "callHook" | head -10

Repository: nuxt/hints

Length of output: 403


🏁 Script executed:

# Search for createHooks implementation
rg -A 10 "export.*createHooks" | head -30

Repository: nuxt/hints

Length of output: 36


🏁 Script executed:

# Look for the callHook method definition
rg -B 5 -A 8 "callHook\s*\(" --type ts | grep -A 8 "callHook\s*\("

Repository: nuxt/hints

Length of output: 77


🏁 Script executed:

# Look at imports in the test file
head -20 test/unit/core/rpc-bridge.test.ts

Repository: nuxt/hints

Length of output: 805


🏁 Script executed:

# Check package.json for hook dependencies
rg "hooks|unhead" package.json

Repository: nuxt/hints

Length of output: 36


🏁 Script executed:

# Check if hookable is in package.json
cat package.json | rg -A 20 "dependencies|devDependencies"

Repository: nuxt/hints

Length of output: 1216


🏁 Script executed:

# Look at how callHook is actually called in rpc-bridge implementation
cat src/runtime/core/server/rpc-bridge.ts | head -100

Repository: nuxt/hints

Length of output: 1043


Use await expect(...).resolves to properly assert the async hook call.

Line 65-67 wraps hooks.callHook() in an arrow function. Since callHook() returns a Promise (as shown by the await pattern in lines 52-61), the current assertion only checks that calling the function doesn't synchronously throw. A rejected promise would not be caught, causing a false pass.

✅ Proposed fix
-    expect(() =>
-      hooks.callHook('hints:hydration:mismatch', { id: '1', componentName: 'Test', fileLocation: '/test.vue', htmlPreHydration: '', htmlPostHydration: '' }),
-    ).not.toThrow()
+    await expect(
+      hooks.callHook('hints:hydration:mismatch', { id: '1', componentName: 'Test', fileLocation: '/test.vue', htmlPreHydration: '', htmlPostHydration: '' }),
+    ).resolves.toBeUndefined()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/core/rpc-bridge.test.ts` around lines 63 - 67, The test currently
wraps hooks.callHook(...) in a synchronous arrow function which only checks for
sync throws; instead call the async hook and assert its resolution: replace the
inline function with await expect(hooks.callHook('hints:hydration:mismatch', {
id: '1', componentName: 'Test', fileLocation: '/test.vue', htmlPreHydration: '',
htmlPostHydration: '' })).resolves.toBeUndefined() (or .resolves.not.toThrow
equivalent) so any rejected Promise will fail the test; keep the
globalThis.__nuxtHintsRpcBroadcast = undefined setup and target the
hooks.callHook invocation to fix the assertion.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
client/app/app.vue (1)

70-74: Inconsistent validation: html-validate reports are not validated.

onLazyLoadReport (lines 56-66) validates incoming data with valibot.parse and gracefully handles malformed payloads. However, onHtmlValidateReport does not validate incoming data, making it inconsistent and potentially vulnerable to malformed RPC payloads causing runtime errors.

Consider adding similar validation. If no valibot schema exists for HtmlValidateReport, either create one or add basic structural checks.

♻️ Example defensive validation
     onHtmlValidateReport(report: HtmlValidateReport) {
+      if (typeof report?.id !== 'string' || typeof report?.path !== 'string') {
+        console.warn('[hints] Ignoring malformed html-validate report', report)
+        return
+      }
       if (!htmlValidateReports.value.some(existing => existing.id === report.id)) {
         htmlValidateReports.value = [...htmlValidateReports.value, report]
       }
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/app/app.vue` around lines 70 - 74, The onHtmlValidateReport handler
currently pushes incoming reports into htmlValidateReports without validating
them; add the same defensive validation used in onLazyLoadReport by creating or
reusing a valibot schema (e.g., HtmlValidateReportSchema) and calling
valibot.parse/validate inside onHtmlValidateReport, catch parse errors and
handle them gracefully (log or ignore malformed payload) before appending to
htmlValidateReports to prevent runtime errors from malformed RPC payloads.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/app/app.vue`:
- Around line 22-30: The code spreads client.host.nuxt.payload.__hints.hydration
without guarding for missing properties; update the block that runs when
useHintsFeature('hydration') is true to defensively read payload hints (e.g.,
const existing = client.host?.nuxt?.payload?.__hints?.hydration ?? []) and
initialize hydrationMismatches.value from that empty-safe array, then in the
$fetch.then merge new data into hydrationMismatches.value using the same safe
reference (and fall back to [] when data.mismatches is missing) so no undefined
property access occurs; look for useHintsFeature('hydration'),
hydrationMismatches, client.host.nuxt.payload.__hints.hydration, and
HYDRATION_ROUTE to locate the code to change.

---

Nitpick comments:
In `@client/app/app.vue`:
- Around line 70-74: The onHtmlValidateReport handler currently pushes incoming
reports into htmlValidateReports without validating them; add the same defensive
validation used in onLazyLoadReport by creating or reusing a valibot schema
(e.g., HtmlValidateReportSchema) and calling valibot.parse/validate inside
onHtmlValidateReport, catch parse errors and handle them gracefully (log or
ignore malformed payload) before appending to htmlValidateReports to prevent
runtime errors from malformed RPC payloads.
🪄 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

Run ID: 89eda054-4683-4865-9321-6148245cc9ca

📥 Commits

Reviewing files that changed from the base of the PR and between 1430770 and e60ebaa.

📒 Files selected for processing (3)
  • client/app/app.vue
  • src/devtools.ts
  • src/runtime/core/rpc-types.ts
✅ Files skipped from review due to trivial changes (1)
  • src/runtime/core/rpc-types.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/devtools.ts

Comment on lines +22 to +30
if (useHintsFeature('hydration')) {
hydrationMismatches.value = [...client.host.nuxt.payload.__hints.hydration]
$fetch<HydrationMismatchResponse>(new URL(HYDRATION_ROUTE, window.location.origin).href).then((data) => {
hydrationMismatches.value = [
...hydrationMismatches.value,
...data.mismatches.filter(m => !hydrationMismatches.value.some(existing => existing.id === m.id)),
]
})
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add defensive check for payload hydration data.

Line 23 spreads client.host.nuxt.payload.__hints.hydration without verifying the property exists. If __hints or hydration is undefined, this will throw. The useHintsFeature('hydration') check doesn't guarantee the payload data was populated.

🛡️ Proposed defensive access
   if (useHintsFeature('hydration')) {
-    hydrationMismatches.value = [...client.host.nuxt.payload.__hints.hydration]
+    hydrationMismatches.value = [...(client.host.nuxt.payload.__hints?.hydration ?? [])]
     $fetch<HydrationMismatchResponse>(new URL(HYDRATION_ROUTE, window.location.origin).href).then((data) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/app/app.vue` around lines 22 - 30, The code spreads
client.host.nuxt.payload.__hints.hydration without guarding for missing
properties; update the block that runs when useHintsFeature('hydration') is true
to defensively read payload hints (e.g., const existing =
client.host?.nuxt?.payload?.__hints?.hydration ?? []) and initialize
hydrationMismatches.value from that empty-safe array, then in the $fetch.then
merge new data into hydrationMismatches.value using the same safe reference (and
fall back to [] when data.mismatches is missing) so no undefined property access
occurs; look for useHintsFeature('hydration'), hydrationMismatches,
client.host.nuxt.payload.__hints.hydration, and HYDRATION_ROUTE to locate the
code to change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant