Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions packages/core/playground/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { A11yCheckerPlugin } from '../../../examples/plugin-a11y-checker/src/nod
import { GitUIPlugin } from '../../../examples/plugin-git-ui/src/node'
import { DevTools } from '../../core/src'
import { buildCSS } from '../../core/src/client/webcomponents/scripts/build-css'
import { hideDockWhenEmpty } from '../../core/src/node/plugins/auto-hide'
// eslint-disable-next-line ts/ban-ts-comment
// @ts-ignore ignore the type error
import { DevToolsRolldownUI } from '../../rolldown/src/node'
Expand All @@ -35,12 +36,27 @@ export default defineConfig({
plugins: [
VueRouter(),
Vue(),
createPluginFromDevframe(createTerminalsDevframe(), {
dock: { category: '~builtin' },
}),
createPluginFromDevframe(createMessagesDevframe(), {
dock: { category: '~builtin' },
}),
...(() => {
// Mirror the shipped `DevTools()` mounts (the playground runs with
// `builtinDevTools: false`, so it re-creates them by hand): terminals
// and messages auto-hide from the dock bar while empty.
const terminalsDevframe = createTerminalsDevframe()
const messagesDevframe = createMessagesDevframe()
return [
createPluginFromDevframe(terminalsDevframe, {
dock: { category: '~builtin' },
setup(ctx) {
hideDockWhenEmpty(ctx, terminalsDevframe.id, () => ctx.terminals.sessions.size === 0)
},
}),
createPluginFromDevframe(messagesDevframe, {
dock: { category: '~builtin' },
setup(ctx) {
hideDockWhenEmpty(ctx, messagesDevframe.id, () => ctx.messages.entries.size === 0)
},
}),
]
})(),
createPluginFromDevframe(createInspectDevframe(), {
dock: { category: '~builtin', icon: 'ph:stethoscope-duotone' },
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import type { SharedState } from 'devframe/utils/shared-state'
import type { DevToolsDocksUserSettings } from '../../state/dock-settings'
import { DEFAULT_STATE_USER_SETTINGS } from '@vitejs/devtools-kit/constants'
import { useConfirm } from '../../state/confirm'
import { sharedStateToRef } from '../../state/docks'

const props = defineProps<{
context: DocksContext
settingsStore: SharedState<DevToolsDocksUserSettings>
}>()

const settings = sharedStateToRef(props.settingsStore)
const confirm = useConfirm()

async function resetAllSettings() {
Expand Down Expand Up @@ -64,6 +66,26 @@ async function deauthorize() {

<template>
<div class="flex flex-col gap-6">
<!-- Show Devframe Inspector toggle -->
<label class="flex items-center gap-3 cursor-pointer group">
<button
class="w-10 h-6 rounded-full transition-colors relative shrink-0"
:class="settings.showDevframeInspector ? 'bg-lime' : 'bg-gray/30'"
@click="settingsStore.mutate((s) => { s.showDevframeInspector = !s.showDevframeInspector })"
>
<div
class="absolute top-1 w-4 h-4 rounded-full bg-white shadow transition-transform"
:class="settings.showDevframeInspector ? 'translate-x-5' : 'translate-x-1'"
/>
</button>
<div class="flex flex-col">
<span class="text-sm">Show Devframe Inspector</span>
<span class="text-xs op50">Reveal the experimental Devframe Inspector dock — the DevTools for the DevTools</span>
</div>
</label>

<div class="border-t border-base" />

<!-- Reset Shortcuts -->
<div class="flex items-start gap-4">
<div class="flex-1">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { DevToolsDockEntry } from '@vitejs/devtools-kit'
import { DEFAULT_STATE_USER_SETTINGS } from '@vitejs/devtools-kit/constants'
import { DEFAULT_STATE_USER_SETTINGS, DEVTOOLS_INSPECTOR_DOCK_ID } from '@vitejs/devtools-kit/constants'
import { describe, expect, it } from 'vitest'
import {
docksGroupByCategories,
Expand Down Expand Up @@ -92,6 +92,33 @@ describe('dock groups', () => {
})
})

describe('devframe inspector visibility (settings: showDevframeInspector)', () => {
const entries: DevToolsDockEntry[] = [
iframe('a'),
iframe(DEVTOOLS_INSPECTOR_DOCK_ID),
]

it('hides the inspector from the dock bar by default', () => {
const grouped = docksGroupByCategories(entries, settings)
const ids = grouped.flatMap(([, items]) => items.map(i => i.id))
expect(ids).toContain('a')
expect(ids).not.toContain(DEVTOOLS_INSPECTOR_DOCK_ID)
})

it('reveals the inspector when showDevframeInspector is enabled', () => {
const enabled = { ...settings, showDevframeInspector: true }
const grouped = docksGroupByCategories(entries, enabled)
const ids = grouped.flatMap(([, items]) => items.map(i => i.id))
expect(ids).toContain(DEVTOOLS_INSPECTOR_DOCK_ID)
})

it('still lists the inspector in the settings management view (includeHidden)', () => {
const grouped = docksGroupByCategories(entries, settings, { includeHidden: true })
const ids = grouped.flatMap(([, items]) => items.map(i => i.id))
expect(ids).toContain(DEVTOOLS_INSPECTOR_DOCK_ID)
})
})

describe('resolveCommandIcon (dock icon → command icon projection)', () => {
it('passes string icons through unchanged', () => {
expect(resolveCommandIcon('logos:nuxt-icon')).toBe('logos:nuxt-icon')
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/client/webcomponents/state/dock-settings.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { DevToolsDockEntriesGrouped, DevToolsDockEntry, DevToolsDocksUserSettings, DevToolsViewGroup } from '@vitejs/devtools-kit'
import type { Immutable } from 'devframe/utils/shared-state'
import type { WhenContext } from 'devframe/utils/when'
import { DEVTOOLS_INSPECTOR_DOCK_ID } from '@vitejs/devtools-kit/constants'
import { evaluateWhen } from 'devframe/utils/when'
import { DEFAULT_CATEGORIES_ORDER } from '../constants'

Expand Down Expand Up @@ -104,6 +105,11 @@ export function docksGroupByCategories(
continue
if (entry.when && !whenContext && entry.when === 'false' && !includeHidden)
continue
// The Devframe Inspector is hidden by default; it only joins the dock bar
// once opted into via Settings → Advanced. The settings management view
// (`includeHidden`) still lists it so users can discover the toggle.
if (!includeHidden && entry.id === DEVTOOLS_INSPECTOR_DOCK_ID && !settings.showDevframeInspector)
continue
if (!includeHidden && docksHidden.includes(entry.id))
continue

Expand Down
41 changes: 41 additions & 0 deletions packages/core/src/node/plugins/__tests__/auto-hide.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { KitNodeContext } from '@vitejs/devtools-kit/node'
import { describe, expect, it } from 'vitest'
import { hideDockWhenEmpty } from '../auto-hide'

interface FakeView { id: string, type: string, url: string, title: string, when?: string }

function fakeCtx(views: FakeView[]): { ctx: KitNodeContext, get: (id: string) => FakeView } {
const map = new Map(views.map(v => [v.id, v]))
const ctx = { docks: { views: map } } as unknown as KitNodeContext
return { ctx, get: id => map.get(id)! }
}

describe('hideDockWhenEmpty', () => {
it('resolves `when` live from the isEmpty callback', () => {
let empty = true
const { ctx, get } = fakeCtx([{ id: 'x', type: 'iframe', url: '/', title: 'X' }])

hideDockWhenEmpty(ctx, 'x', () => empty)

// Hidden while empty, visible once populated — re-read on every access.
expect(get('x').when).toBe('false')
empty = false
expect(get('x').when).toBeUndefined()
empty = true
expect(get('x').when).toBe('false')
})

it('attaches an enumerable `when` so it survives serialization', () => {
const { ctx, get } = fakeCtx([{ id: 'x', type: 'iframe', url: '/', title: 'X' }])
hideDockWhenEmpty(ctx, 'x', () => true)

// Enumerable getters are picked up by JSON.stringify (the shared-state wire
// format), so the resolved value reaches the client filter.
expect(JSON.parse(JSON.stringify(get('x'))).when).toBe('false')
})

it('is a no-op when the dock id is not registered', () => {
const { ctx } = fakeCtx([])
expect(() => hideDockWhenEmpty(ctx, 'missing', () => true)).not.toThrow()
})
})
34 changes: 34 additions & 0 deletions packages/core/src/node/plugins/auto-hide.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { KitNodeContext } from '@vitejs/devtools-kit/node'

/**
* Keep a dock entry out of the dock bar while its backing collection is empty.
*
* Mirrors the hub's built-in `~terminals` / `~messages` docks: attaches a live
* `when` getter to the registered entry that resolves to `'false'`
* (unconditionally hidden) while `isEmpty()` and `undefined` (visible)
* otherwise. The hub already re-serializes the dock shared state on every
* terminal / message change, so the getter is re-read at exactly the right
* moments — no explicit event subscription needed here.
*
* Call this from the `setup(ctx)` hook of the `createPluginFromDevframe` mount,
* after the auto-derived dock entry has been registered.
*
* TODO: once devframe/hub ships first-class support for a functional `when`
* (`when?: () => string | boolean | undefined`, resolved during dock
* serialization), this can become a plain `dock: { when: () => ... }` option
* on `createPluginFromDevframe` and the getter trick can be deleted.
*/
export function hideDockWhenEmpty(
ctx: KitNodeContext,
dockId: string,
isEmpty: () => boolean,
): void {
const view = ctx.docks.views.get(dockId)
if (!view)
return
Object.defineProperty(view, 'when', {
enumerable: true,
configurable: true,
get: () => (isEmpty() ? 'false' : undefined),
})
}
25 changes: 21 additions & 4 deletions packages/core/src/node/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import type { Plugin } from 'vite'
import { createInspectDevframe } from '@devframes/plugin-inspect'
import { createMessagesDevframe } from '@devframes/plugin-messages'
import { createTerminalsDevframe } from '@devframes/plugin-terminals'
import { DEVTOOLS_INSPECTOR_DOCK_ID } from '@vitejs/devtools-kit/constants'
import { createPluginFromDevframe } from '@vitejs/devtools-kit/node'
import { hideDockWhenEmpty } from './auto-hide'
import { DevToolsBuild } from './build'
import { DevToolsInjection } from './injection'
import { DevToolsServer } from './server'
Expand Down Expand Up @@ -58,17 +60,32 @@ export async function DevTools(options: DevToolsOptions = {}): Promise<Plugin[]>
// dock — rather than the `~viteplus` group (which collects integrations
// like Rolldown). The hub's own `~terminals` / `~messages` docks are
// suppressed via `builtinDocks` in `createDevToolsContext`.
plugins.push(createPluginFromDevframe(createTerminalsDevframe(), {
//
// The hub's built-in `~terminals` / `~messages` docks auto-hid themselves
// when empty; the plugin-mounted iframe docks that replaced them carry no
// such rule, so we restore it here — the dock is filtered out of the bar
// (`when: 'false'`) whenever there are no sessions / messages.
const terminalsDevframe = createTerminalsDevframe()
plugins.push(createPluginFromDevframe(terminalsDevframe, {
dock: { category: '~builtin' },
setup(ctx) {
hideDockWhenEmpty(ctx, terminalsDevframe.id, () => ctx.terminals.sessions.size === 0)
},
}))
plugins.push(createPluginFromDevframe(createMessagesDevframe(), {
const messagesDevframe = createMessagesDevframe()
plugins.push(createPluginFromDevframe(messagesDevframe, {
dock: { category: '~builtin' },
setup(ctx) {
hideDockWhenEmpty(ctx, messagesDevframe.id, () => ctx.messages.entries.size === 0)
},
}))

// Meta-introspection ("DevTools for the DevTools"), provided by the
// official devframe inspector plugin (replaces the former
// `@vitejs/devtools-self-inspect` package).
plugins.push(createPluginFromDevframe(createInspectDevframe(), {
// `@vitejs/devtools-self-inspect` package). Pinned to a stable id so the
// client can gate it behind the `showDevframeInspector` user setting;
// hidden by default (opt in via Settings → Advanced).
plugins.push(createPluginFromDevframe(createInspectDevframe({ id: DEVTOOLS_INSPECTOR_DOCK_ID }), {
dock: { category: '~builtin', icon: 'ph:stethoscope-duotone' },
}))
}
Expand Down
8 changes: 8 additions & 0 deletions packages/kit/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ export const DEVTOOLS_DOCK_IMPORTS_VIRTUAL_ID = '/__devtools-client-imports.js'
*/
export const DEVTOOLS_VITEPLUS_GROUP_ID = '~viteplus'

/**
* Dock id of the built-in Devframe Inspector (mounted from
* `@devframes/plugin-inspect`). Shared between the node side (which pins the
* mounted devframe to this id) and the client (which gates the dock behind the
* `showDevframeInspector` user setting).
*/
export const DEVTOOLS_INSPECTOR_DOCK_ID = 'devframes-plugin-inspect'

export const DEFAULT_CATEGORIES_ORDER: Record<string, number> = {
'default': 0,
'app': 100,
Expand Down
19 changes: 18 additions & 1 deletion packages/kit/src/types/settings.ts
Original file line number Diff line number Diff line change
@@ -1 +1,18 @@
export type { DevframeDocksUserSettings as DevToolsDocksUserSettings } from '@devframes/hub/types'
import type { DevframeDocksUserSettings } from '@devframes/hub/types'

/**
* Vite DevTools user settings — the hub's {@link DevframeDocksUserSettings}
* widened with Vite-flavored toggles that only Vite DevTools ships.
*
* The extra fields are optional so the hub's `DEFAULT_STATE_USER_SETTINGS()`
* (which knows nothing about them) stays assignable; an absent value reads as
* its documented default.
*/
export interface DevToolsDocksUserSettings extends DevframeDocksUserSettings {
/**
* Reveal the Devframe Inspector dock (the "DevTools for the DevTools"
* meta-introspection panel). Hidden by default — an absent value keeps the
* dock out of the dock bar until the user opts in from Settings → Advanced.
*/
showDevframeInspector?: boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
export declare const DEFAULT_CATEGORIES_ORDER: Record<string, number>;
export declare const DEVTOOLS_DIRNAME: string;
export declare const DEVTOOLS_DOCK_IMPORTS_VIRTUAL_ID: string;
export declare const DEVTOOLS_INSPECTOR_DOCK_ID: string;
export declare const DEVTOOLS_MOUNT_PATH: string;
export declare const DEVTOOLS_MOUNT_PATH_NO_TRAILING_SLASH: string;
export declare const DEVTOOLS_VITEPLUS_GROUP_ID: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
export var DEFAULT_CATEGORIES_ORDER /* const */
export var DEVTOOLS_DIRNAME /* const */
export var DEVTOOLS_DOCK_IMPORTS_VIRTUAL_ID /* const */
export var DEVTOOLS_INSPECTOR_DOCK_ID /* const */
export var DEVTOOLS_MOUNT_PATH /* const */
export var DEVTOOLS_MOUNT_PATH_NO_TRAILING_SLASH /* const */
export var DEVTOOLS_VITEPLUS_GROUP_ID /* const */
Expand Down
Loading