Skip to content

feat(devtools): project each DevTools tab as its own Vite DevTools dock entry#1035

Merged
antfu merged 11 commits into
nuxt:mainfrom
antfubot:feat/dock-per-tab-entries
Jul 24, 2026
Merged

feat(devtools): project each DevTools tab as its own Vite DevTools dock entry#1035
antfu merged 11 commits into
nuxt:mainfrom
antfubot:feat/dock-per-tab-entries

Conversation

@antfubot

@antfubot antfubot commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Intent

Surface every Nuxt DevTools tab as its own entry in the Vite DevTools dock, under a Nuxt group, instead of the single hub iframe that hosted the whole app with in-app tab switching.

Approach: shared-iframe soft navigation

Built on devframe's shared-iframe soft-navigation feature (devframe#128 / vitejs/devtools#464):

  • One anchor, one iframe. module-main.ts registers a single subTabs anchor iframe under the Nuxt group. It loads the client root once (/__nuxt_devtools__/client/?embed=1) and stays alive; tabs switch by client-side navigation within it — no per-tab iframe, no reload.
  • Client devframe:frame-nav shim (composables/frame-nav.ts). Inside the anchor, the client announces a tab manifest from useEnabledTabs() (so a tab's show() condition, hidden/pinned settings and experimental gating decide whether its dock appears), answers navigate with router.push, and reports navigated so the dock highlight follows in-app navigation. The dock materializes one member dock per reported tab (nuxt:devtools:<tab>), all sharing the one iframe. Settings is exposed as its own member.
  • Sub-categories in Nuxt's order. Each member carries its DevTools category (uncategorised → app), rendered as an in-group sub-category. The Nuxt group sets categoryOrder (vitejs/devtools#468) so sub-categories sort in Nuxt's own order — app, vue-devtools, analyze, server, modules, documentation, advanced — rather than the dock's shared default table.
  • Chromeless + full width. ?embed=1 puts the client in embedded mode (composables/embed.ts): app.vue hides the SideNav and split pane and lays the content out full-width. The Settings page also hides options that the dock now owns when embedded: the tab visibility/order panel, the color-mode toggle, the SideNav options, and close-on-outside-click.
  • Vue DevTools channel. view/client.ts binds the @vue/devtools-kit iframe channel to the shared anchor iframe, and retargets open/toggle/navigate at the Nuxt group.

Serving fix

The anchor loads /__nuxt_devtools__/client/?embed=1; the dev middleware now serves the base-rewritten SPA index for the root document ignoring the query string (previously a query made sirv serve a raw, un-rewritten index.html, breaking the base — the root cause of the earlier iframe/404 failures).

Dependencies

@vitejs/devtools + @vitejs/devtools-kit^0.4.6; @devframes/hub + devframe^0.7.12; @devframes/plugin-data-inspector + @devframes/plugin-code-server stay ^0.7.11.

Notes

Drops the interim machinery from earlier iterations (Node-side hardcoded built-in tab list, per-tab dock registrations, the /embed/* route). e2e specs updated for the shared-iframe model (anchor renders chromelessly; per-tab members registered via the shim). CI green: lint, build, typecheck, and the full e2e matrix pass.

This PR was created with the help of an agent.

…ck entry

Replace the single Nuxt DevTools hub iframe with one iframe dock entry per tab, all members of the Nuxt dock group, each opening a chromeless deep link to that tab.
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

DevTools now detects embedded mode from /embed paths and renders matched tab components without the standard split-pane layout or redirect middleware. A new integration registers built-in and custom tabs as individual iframe dock entries and synchronizes custom-tab refreshes. Runtime navigation and Vue DevTools iframe binding now follow the selected Nuxt dock entry. Workspace package versions were updated.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: each DevTools tab becomes its own dock entry.
Description check ✅ Passed The description is about the DevTools dock/tab integration changes and embedded client behavior.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

…dock tabs

Fix the per-tab dock 404 by riding the embed flag on each tab's existing
route (`?embed=1`) instead of a dedicated `/embed/*` route that the built
client doesn't contain. Upgrade @vitejs/devtools + @vitejs/devtools-kit to
0.4.4 and @devframes/hub + devframe to 0.7.10, and set each grouped entry's
`category` so it becomes an in-group sub-category of the Nuxt group.

Defers the postMessage shared-iframe soft-navigation (devframe#128).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
pnpm-workspace.yaml (1)

79-79: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Refresh the Vite compatibility rationale after this version bump.

The catalog now uses @vitejs/devtools ^0.4.4, but the adjacent override comment still refers to @vitejs/devtools@0.4.2 and its Vite 8.1 incompatibility. Update the rationale to reflect 0.4.4, or remove the Vite 8.0 pin if this release resolves the issue; verify against the package release notes and lockfile.

🤖 Prompt for 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.

In `@pnpm-workspace.yaml` at line 79, Update the adjacent override comment for
`@vitejs/devtools` to reference the catalog’s ^0.4.4 version and its current Vite
compatibility rationale; verify the package release notes and lockfile, and
remove the Vite 8.0 pin if 0.4.4 resolves the incompatibility.
🤖 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.

Nitpick comments:
In `@pnpm-workspace.yaml`:
- Line 79: Update the adjacent override comment for `@vitejs/devtools` to
reference the catalog’s ^0.4.4 version and its current Vite compatibility
rationale; verify the package release notes and lockfile, and remove the Vite
8.0 pin if 0.4.4 resolves the incompatibility.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 730e7c28-966c-4602-a075-3e6d370dfe3e

📥 Commits

Reviewing files that changed from the base of the PR and between 1658f91 and b0a8336.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • packages/devtools/client/composables/embed.ts
  • packages/devtools/src/integrations/dock-tabs.ts
  • pnpm-workspace.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/devtools/src/integrations/dock-tabs.ts

@socket-security

socket-security Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​vitejs/​devtools-kit@​0.4.6781007197100
Added@​vitejs/​devtools@​0.4.6771007497100
Added@​devframes/​plugin-code-server@​0.7.117610010095100
Added@​devframes/​plugin-data-inspector@​0.7.11781009493100

View full report

The client is an ssr:false SPA served by a single sirv middleware; the old
hub only ever hard-loaded the root `/`. Per-tab dock entries now also load
the root (`?embed=1&to=<route>`) and a global route middleware soft-navigates
to the target tab, instead of hard-loading a deep client URL. This keeps the
feature on the one serving path proven to work and sidesteps any deep-path
SPA-fallback concerns.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/devtools/src/integrations/dock-tabs.ts (1)

200-201: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Serialize custom-tab refreshes and return the sync promise.

syncCustomTabs() is asynchronous but launched fire-and-forget. Concurrent refreshes can apply stale snapshots out of order, causing an older refresh to hide entries from a newer one; failures also become unhandled rejections. Queue the refreshes or guard application with a generation token, and return the promise from the hook.

🤖 Prompt for 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.

In `@packages/devtools/src/integrations/dock-tabs.ts` around lines 200 - 201,
Update the devtools:customTabs:refresh hook to return syncCustomTabs() and
serialize successive refreshes (or use a generation guard) so stale snapshots
cannot overwrite newer results; ensure sync failures are propagated through the
returned promise instead of becoming unhandled rejections.
🧹 Nitpick comments (2)
packages/devtools/src/integrations/dock-tabs.ts (2)

127-130: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Use an explicit visible condition when re-showing tabs.

Removed tabs are hidden with when: 'false', but re-registration clears that state with when: undefined. Since update is a partial patch, verify that undefined actually removes the previous condition; otherwise a removed and re-added tab may remain hidden. Prefer when: 'true' or the dependency’s documented reset mechanism. (devtools.vite.dev)

Proposed fix
-      existing.update({ when: undefined, ...entry })
+      existing.update({ when: 'true', ...entry })

Also applies to: 187-190

🤖 Prompt for 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.

In `@packages/devtools/src/integrations/dock-tabs.ts` around lines 127 - 130,
Update the existing-tab re-registration path around existing.update so it
explicitly restores visibility instead of relying on an undefined partial-field
reset; set the documented visible condition (prefer when: 'true', or the
dependency’s supported reset mechanism) while preserving the metadata refresh.

Source: MCP tools


1-58: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Keep built-in tab metadata single-sourced.

This Node-side catalog can drift from the client’s routes and tab definitions: a future client tab addition or rename could silently produce a missing or broken dock entry. Share the catalog or add a parity test before this becomes the release path.

🤖 Prompt for 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.

In `@packages/devtools/src/integrations/dock-tabs.ts` around lines 1 - 58, Keep
BUILTIN_TABS synchronized with the client page definitions by introducing a
parity test or shared catalog source. Validate that every client tab has a
matching Node entry and that names and mirrored metadata such as title, icon,
order, and category remain consistent, so additions or renames cannot silently
produce invalid dock entries.
🤖 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.

Outside diff comments:
In `@packages/devtools/src/integrations/dock-tabs.ts`:
- Around line 200-201: Update the devtools:customTabs:refresh hook to return
syncCustomTabs() and serialize successive refreshes (or use a generation guard)
so stale snapshots cannot overwrite newer results; ensure sync failures are
propagated through the returned promise instead of becoming unhandled
rejections.

---

Nitpick comments:
In `@packages/devtools/src/integrations/dock-tabs.ts`:
- Around line 127-130: Update the existing-tab re-registration path around
existing.update so it explicitly restores visibility instead of relying on an
undefined partial-field reset; set the documented visible condition (prefer
when: 'true', or the dependency’s supported reset mechanism) while preserving
the metadata refresh.
- Around line 1-58: Keep BUILTIN_TABS synchronized with the client page
definitions by introducing a parity test or shared catalog source. Validate that
every client tab has a matching Node entry and that names and mirrored metadata
such as title, icon, order, and category remain consistent, so additions or
renames cannot silently produce invalid dock entries.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7ac1aaee-9287-44b4-8a2b-8bf33a060e91

📥 Commits

Reviewing files that changed from the base of the PR and between 52308aa and df7d312.

📒 Files selected for processing (2)
  • packages/devtools/client/middleware/route.global.ts
  • packages/devtools/src/integrations/dock-tabs.ts

Bring back the dedicated `/embed/<tab route>` sub-path for per-tab dock
iframes. The `/embed/[...path]` page resolves the target tab's own route
component and mounts it directly (like SplitScreen), so the tab renders
without the SideNav or split pane and takes the full iframe. app.vue drops
the split-pane wrapper while embedded; the route middleware skips its
first-visit/overview redirects for `/embed/*`.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/devtools/src/integrations/dock-tabs.ts (1)

194-196: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Serialize custom-tab refreshes before hiding tabs.

The refresh hook now triggers multiple async registrations (devtools:customTabs handlers and this dock integration), but syncCustomTabs() does not wait for those handlers before hiding disappeared entries. Queue syncCustomTabs() so previous custom-tab refreshes settle first, and avoid swallowing promise rejections in the queue.

🤖 Prompt for 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.

In `@packages/devtools/src/integrations/dock-tabs.ts` around lines 194 - 196,
Update the devtools:customTabs:refresh hook around syncCustomTabs() to serialize
refresh operations through an async queue, ensuring each prior custom-tab
registration settles before synchronization hides disappeared entries. Chain
each new refresh onto the previous promise and preserve promise rejections
rather than swallowing them.
🤖 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.

Outside diff comments:
In `@packages/devtools/src/integrations/dock-tabs.ts`:
- Around line 194-196: Update the devtools:customTabs:refresh hook around
syncCustomTabs() to serialize refresh operations through an async queue,
ensuring each prior custom-tab registration settles before synchronization hides
disappeared entries. Chain each new refresh onto the previous promise and
preserve promise rejections rather than swallowing them.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 27e339cb-5e94-4ad4-b56b-31275756252c

📥 Commits

Reviewing files that changed from the base of the PR and between df7d312 and 0d71a28.

📒 Files selected for processing (5)
  • packages/devtools/client/app.vue
  • packages/devtools/client/composables/embed.ts
  • packages/devtools/client/middleware/route.global.ts
  • packages/devtools/client/pages/embed/[...path].vue
  • packages/devtools/src/integrations/dock-tabs.ts

antfubot and others added 6 commits July 23, 2026 08:15
Replace the per-tab `/embed/` iframes (which deep-loaded client routes and
404'd) with the shared-iframe soft-navigation model from devframe#128 /
vitejs/devtools#464. Register a single `subTabs` anchor iframe under the
Nuxt group that loads the client root once; a client-side `devframe:frame-nav`
postMessage shim announces one member dock per tab (from useAllTabs, carrying
its category as an in-group sub-category) and soft-navigates within that one
iframe. No deep-path loads, no Node-side tab list.

Upgrades @vitejs/devtools + @vitejs/devtools-kit to ^0.4.5 and the devframe
family to ^0.7.11.
Drive the shared-iframe member docks from useEnabledTabs() so a tab's show()
condition, hidden/pinned settings and experimental gating decide whether its
dock appears (mirroring the old SideNav), re-announcing the manifest when that
set changes. Expose the Settings page as its own dock member. In embedded mode
the SideNav is hidden, so lay the content out full-width.
…hared-iframe

- module-main: serve the base-rewritten client index for the root document
  regardless of query, so the shared-frame anchor URL (`/?embed=1`) no longer
  gets a raw, un-rewritten `index.html` from sirv (broken base).
- e2e: update iframe/nuxt-group specs for the shared-iframe model — the anchor
  renders chromelessly (SideNav hidden) and surfaces per-tab member docks via
  the frame-nav shim, instead of showing the in-iframe SideNav.
- restore main's `unimport/auto-insert` eslint-disable comments that a merge in
  this branch had dropped (lint was failing on unrelated files).
… embedded settings

- Upgrade @vitejs/devtools + @vitejs/devtools-kit to ^0.4.6 and @devframes/hub
  + devframe to ^0.7.12.
- Set `categoryOrder` on the Nuxt dock group (vitejs/devtools#468) so the
  in-group sub-categories sort in Nuxt's own order (app, vue-devtools, analyze,
  server, modules, documentation, advanced) instead of the shared default table.
  Default uncategorised tabs to `app` in the frame-nav manifest to match.
- Hide settings that are redundant when embedded (owned by the Vite DevTools
  dock): the tab visibility/order panel, the color-mode toggle, the SideNav
  options, and the close-on-outside-click toggle.
@antfu
antfu merged commit f4fe278 into nuxt:main Jul 24, 2026
1 of 4 checks passed
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.

2 participants