diff --git a/eslint.config.mjs b/eslint.config.mjs index 691e7275d0..043a8932a8 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,7 +1,16 @@ +import { existsSync } from 'node:fs' +import { fileURLToPath } from 'node:url' import antfu from '@antfu/eslint-config' import { extend } from 'eslint-flat-config-utils' -export default antfu( +// The client Nuxt app's generated flat config only exists once the app has been +// prepared (`pnpm dev:prepare`/`pnpm build`). Skip it gracefully when it hasn't +// been generated yet (e.g. a fresh `pnpm install && pnpm lint` in CI) so linting +// doesn't hard-crash on the missing import. +const clientConfigUrl = new URL('./packages/devtools/client/.nuxt/eslint.config.mjs', import.meta.url) +const hasClientConfig = existsSync(fileURLToPath(clientConfigUrl)) + +const config = antfu( { formatters: true, unocss: true, @@ -22,12 +31,37 @@ export default antfu( }, }, ) - .append( + .removeRules( + 'vue/no-multiple-template-root', + ) + +// `FlatConfigComposer.append()` mutates the composer in place (and returns it), +// so the export stays a `const`. +if (hasClientConfig) { + config.append( extend( - import('./packages/devtools/client/.nuxt/eslint.config.mjs').then(mod => mod.default()), + import(clientConfigUrl.href).then(mod => mod.default()), 'packages/devtools/client', ), ) - .removeRules( - 'vue/no-multiple-template-root', +} +else { + // Before the client app is prepared, its generated flat config (which wires up + // `eslint-plugin-unimport`) is absent, so the `unimport/auto-insert` rule the + // client's inline `eslint-disable` directives reference is undefined. Register + // the plugin so those directives resolve and linting doesn't error. + config.append( + import('eslint-plugin-unimport').then(mod => ({ + name: 'nuxt-devtools/client/unimport-fallback', + files: ['packages/devtools/client/**/*.{ts,vue}'], + plugins: { unimport: mod.default ?? mod }, + rules: { 'unimport/auto-insert': 'off' }, + // The rule is off in this fallback, so the client's inline disable + // directives read as "unused"; don't report (or auto-fix away) them — + // they are needed once the client app is prepared and the rule is active. + linterOptions: { reportUnusedDisableDirectives: 'off' }, + })), ) +} + +export default config diff --git a/packages/devtools-kit/src/_types/options.ts b/packages/devtools-kit/src/_types/options.ts index 4508b5ea1d..f56884ff6b 100644 --- a/packages/devtools-kit/src/_types/options.ts +++ b/packages/devtools-kit/src/_types/options.ts @@ -235,13 +235,10 @@ export interface NuxtDevToolsOptions { componentsView: 'list' | 'graph' hiddenTabCategories: string[] hiddenTabs: string[] - interactionCloseOnOutsideClick: boolean pinnedTabs: string[] scale: number showExperimentalFeatures: boolean showHelpButtons: boolean - sidebarExpanded: boolean - sidebarScrollable: boolean } serverRoutes: { selectedRoute: ServerRouteInfo | null diff --git a/packages/devtools/client/app.vue b/packages/devtools/client/app.vue index a446a113c9..76da88ba85 100644 --- a/packages/devtools/client/app.vue +++ b/packages/devtools/client/app.vue @@ -1,15 +1,12 @@ - - diff --git a/packages/devtools/client/components/SideNav.vue b/packages/devtools/client/components/SideNav.vue deleted file mode 100644 index dba28afb15..0000000000 --- a/packages/devtools/client/components/SideNav.vue +++ /dev/null @@ -1,170 +0,0 @@ - - - diff --git a/packages/devtools/client/components/SideNavItem.vue b/packages/devtools/client/components/SideNavItem.vue deleted file mode 100644 index d4a8594c87..0000000000 --- a/packages/devtools/client/components/SideNavItem.vue +++ /dev/null @@ -1,81 +0,0 @@ - - - diff --git a/packages/devtools/client/components/SplitScreen.vue b/packages/devtools/client/components/SplitScreen.vue deleted file mode 100644 index f43a9c0cd1..0000000000 --- a/packages/devtools/client/components/SplitScreen.vue +++ /dev/null @@ -1,112 +0,0 @@ - - - diff --git a/packages/devtools/client/components/TabsGrid.vue b/packages/devtools/client/components/TabsGrid.vue deleted file mode 100644 index 31eee8b154..0000000000 --- a/packages/devtools/client/components/TabsGrid.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/packages/devtools/client/components/TimelineView.vue b/packages/devtools/client/components/TimelineView.vue index 9b09f04330..1c4c296760 100644 --- a/packages/devtools/client/components/TimelineView.vue +++ b/packages/devtools/client/components/TimelineView.vue @@ -101,7 +101,6 @@ function toggleView() { :model-value="!!selected" auto-close transition="bottom" - left="#nuxt-devtools-side-nav" @close="selected = undefined" >
diff --git a/packages/devtools/client/composables/embed.ts b/packages/devtools/client/composables/embed.ts deleted file mode 100644 index f307e9d46d..0000000000 --- a/packages/devtools/client/composables/embed.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ref } from 'vue' - -const STORAGE_KEY = 'nuxt-devtools-embedded' - -/** - * "Embedded" mode: the client is loaded as the shared-frame **anchor** inside a - * Vite DevTools dock iframe (`?embed=1`). In this mode the app shell — SideNav - * and split pane — is hidden so the iframe shows only the current tab, and the - * `devframe:frame-nav` shim drives tab navigation from the dock instead. - * - * The flag is latched into `sessionStorage` (isolated per iframe) so it - * survives soft navigation that drops the query and any reloads. - */ -function detect(): boolean { - if (typeof window === 'undefined') - return false - try { - if (window.sessionStorage.getItem(STORAGE_KEY) === '1') - return true - } - catch {} - const embedded = new URLSearchParams(window.location.search).get('embed') === '1' - if (embedded) { - try { - window.sessionStorage.setItem(STORAGE_KEY, '1') - } - catch {} - } - return embedded -} - -export const isEmbedded = ref(detect()) diff --git a/packages/devtools/client/composables/frame-nav.ts b/packages/devtools/client/composables/frame-nav.ts index b6b3969d10..cbc224afcc 100644 --- a/packages/devtools/client/composables/frame-nav.ts +++ b/packages/devtools/client/composables/frame-nav.ts @@ -10,10 +10,9 @@ import { useEnabledTabs } from '~/composables/state-tabs' // iframe/reload is needed. The shim is transport-only: it takes no hub/RPC // dependency, just `postMessage`. // -// The announced set comes from `useEnabledTabs()` — the same conditional list -// the SideNav used — so a tab's `show()` condition, hidden/pinned settings and -// experimental gating decide whether its dock appears, and the manifest is -// re-announced whenever that set changes. +// The announced set comes from `useEnabledTabs()` — so a tab's `show()` +// condition, hidden/pinned settings and experimental gating decide whether its +// dock appears, and the manifest is re-announced whenever that set changes. const CHANNEL = 'devframe:frame-nav' const VERSION = 1 // Must match the anchor's `frameId` registered in `module-main.ts`. @@ -78,8 +77,8 @@ export function setupFrameNav(): void { title: tab.title ?? tab.name, icon: normalizeIcon(tab.icon), defaultOrder: 'defaultOrder' in tab ? tab.defaultOrder : undefined, - // Mirror `getCategorizedTabs`: an uncategorised tab belongs to `app`, so - // the `Nuxt` group's `categoryOrder` weights apply to it. + // An uncategorised tab belongs to `app`, so the `Nuxt` group's + // `categoryOrder` weights apply to it. category: tab.category || 'app', navTarget: { path: tabPath(tab) }, })), diff --git a/packages/devtools/client/composables/state-tabs.ts b/packages/devtools/client/composables/state-tabs.ts index aff769ff20..3d5c63be08 100644 --- a/packages/devtools/client/composables/state-tabs.ts +++ b/packages/devtools/client/composables/state-tabs.ts @@ -1,7 +1,6 @@ -import type { ComputedRef, MaybeRef } from 'vue' -import type { CategorizedTabs, ModuleBuiltinTab, ModuleCustomTab, RouteInfo, TabCategory } from '../../src/types' +import type { ModuleBuiltinTab, ModuleCustomTab, RouteInfo, TabCategory } from '../../src/types' import { objectPick } from '@antfu/utils' -import { computed, toValue, unref } from 'vue' +import { computed, toValue } from 'vue' import { useRouter } from '#app/composables/router' import { useClientRouter } from './client' import { useCustomTabs, useServerPages } from './state' @@ -64,35 +63,6 @@ function getCategorizedRecord(): Record): ComputedRef { - const { - pinnedTabs, - } = useDevToolsOptions('ui') - - return computed(() => { - const categories = getCategorizedRecord() - for (const tab of unref(tabs)) { - let category = (tab.category || 'app') - if (pinnedTabs.value.includes(tab.name)) - category = 'pinned' - if (!categories[category]) - console.warn(`Unknown tab category: ${category}`) - else - categories[category].push(tab) - } - - for (const key of Object.keys(categories)) { - if (categories[key as TabCategory].length === 0) - delete categories[key as TabCategory] - } - - if (categories.pinned?.length) - categories.pinned.sort((a, b) => pinnedTabs.value.indexOf(a.name) - pinnedTabs.value.indexOf(b.name)) - - return Object.entries(categories) as CategorizedTabs - }) -} - export function useEnabledTabs() { const tabs = useAllTabs() const settings = useDevToolsOptions('ui') diff --git a/packages/devtools/client/composables/storage.ts b/packages/devtools/client/composables/storage.ts index 9b335d7e77..745f8b9a28 100644 --- a/packages/devtools/client/composables/storage.ts +++ b/packages/devtools/client/composables/storage.ts @@ -1,14 +1,5 @@ import type { DevToolsFrameState } from '~~/../src/types' -import { useLocalStorage, useWindowSize } from '@vueuse/core' -import { computed } from 'vue' - -export const isFirstVisit = useLocalStorage('nuxt-devtools-first-visit', true) - -const windowSize = useWindowSize() - -export const splitScreenAvailable = computed(() => windowSize.width.value > 1080) -export const splitScreenEnabled = useLocalStorage('nuxt-devtools-split-screen', false) -export const splitScreenView = useLocalStorage('nuxt-devtools-split-screen-view', 'overview') +import { useLocalStorage } from '@vueuse/core' const devToolsFrameState = useLocalStorage('nuxt-devtools-frame-state', {} as any, { listenToStorageChanges: true }) const devToolsPanelsState = useLocalStorage>('nuxt-devtools-panels-state', {} as any, { listenToStorageChanges: false }) diff --git a/packages/devtools/client/middleware/route.global.ts b/packages/devtools/client/middleware/route.global.ts index a57a187a1e..998ea6767d 100644 --- a/packages/devtools/client/middleware/route.global.ts +++ b/packages/devtools/client/middleware/route.global.ts @@ -1,21 +1,8 @@ import { defineNuxtRouteMiddleware, navigateTo } from '#imports' -import { isEmbedded } from '~/composables/embed' -import { isFirstVisit } from '~/composables/storage' export default defineNuxtRouteMiddleware((to) => { - // Embedded anchor: skip the first-visit welcome; land on a real tab so the + // The client is always the shared-frame anchor: land on a real tab so the // frame-nav shim has something to report as the current view. - if (isEmbedded.value) { - if (to.path === '/') - return navigateTo('/modules/overview') - return - } - - if (isFirstVisit.value) { - if (to.path !== '/') - return navigateTo('/') - } - else if (to.path === '/') { + if (to.path === '/') return navigateTo('/modules/overview') - } }) diff --git a/packages/devtools/client/nuxt.config.ts b/packages/devtools/client/nuxt.config.ts index 874e646d81..5284d8bba3 100644 --- a/packages/devtools/client/nuxt.config.ts +++ b/packages/devtools/client/nuxt.config.ts @@ -17,7 +17,6 @@ const packageBundles = { export default defineNuxtConfig({ modules: [ - '@nuxt/test-utils/module', '~/modules/markdown', DevToolsUiKit, DevTools, diff --git a/packages/devtools/client/pages/index.vue b/packages/devtools/client/pages/index.vue deleted file mode 100644 index e5ebd6f490..0000000000 --- a/packages/devtools/client/pages/index.vue +++ /dev/null @@ -1,49 +0,0 @@ - - - diff --git a/packages/devtools/client/pages/modules/overview.vue b/packages/devtools/client/pages/modules/overview.vue index a6278352f1..a9b07467e7 100644 --- a/packages/devtools/client/pages/modules/overview.vue +++ b/packages/devtools/client/pages/modules/overview.vue @@ -1,13 +1,11 @@