Skip to content

Commit 6bfe9c7

Browse files
authored
feat(devtools): unify notifications on the devframe Messages system (#1025)
1 parent 6079f1b commit 6bfe9c7

17 files changed

Lines changed: 380 additions & 47 deletions

File tree

‎docs/content/2.module/1.utils-kit.md‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,29 @@ to the [migration guide](/module/migration-v4). See
219219
[`extendServerRpc()`](/module/migration-v4#ndt_dep_0003) and
220220
[`startSubprocess()`](/module/migration-v4#ndt_dep_0004).
221221

222+
### `devtools:notify` hook
223+
224+
Push a notification into the unified devframe **Messages** system — the same
225+
system that powers the Vite DevTools **Messages** dock and its toast overlay.
226+
The notification is forwarded to `ctx.messages`, so no direct kit access is
227+
needed, and calls made before the kit connects are buffered and replayed once
228+
it does:
229+
230+
```ts
231+
// server-side (module setup)
232+
nuxt.callHook('devtools:notify', {
233+
message: 'Something happened',
234+
level: 'error', // 'info' | 'warn' | 'error' | 'success' | 'debug' (default 'info')
235+
description: 'More details about what happened.',
236+
notify: true, // also show a transient toast
237+
})
238+
```
239+
240+
Omit `autoDelete` for a **persistent**, leveled entry that builds history in the
241+
Messages dock; set `notify` + `autoDismiss` + `autoDelete` for an **ephemeral**,
242+
toast-only notification. From the client, the same input is available on the
243+
injected client as `client.devtools.notify(...)`.
244+
222245
## `@nuxt/devtools-kit/iframe-client`
223246

224247
To provide complex interactions for your module integrations, we recommend to host your own view and display it in devtools via iframe.

‎packages/devtools-kit/src/_types/client-api.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { $Fetch } from 'ofetch'
77
import type { BuiltinLanguage } from 'shiki'
88
import type { Ref } from 'vue'
99
import type { HookInfo, LoadingTimeMetric, PluginMetric } from './integrations'
10+
import type { NuxtDevtoolsNotifyInput } from './notify'
1011
import type { ServerFunctions } from './rpc'
1112
import type { TimelineMetrics } from './timeline-metrics'
1213

@@ -119,6 +120,16 @@ export interface NuxtDevtoolsClient {
119120
renderMarkdown: (markdown: string) => string
120121
colorMode: string
121122

123+
/**
124+
* Push a notification through the devframe Messages system.
125+
*
126+
* Routes to the shared Messages host, so it surfaces in the Vite DevTools
127+
* **Messages** dock and/or as a transient toast (when `notify` is set — the
128+
* default). Use this from custom tabs / in-client code to give feedback
129+
* through the same notification system as the rest of DevTools.
130+
*/
131+
notify: (input: NuxtDevtoolsNotifyInput) => Promise<void>
132+
122133
/**
123134
* The connected Vite DevTools RPC client, mirroring `nuxt.devtools.devtoolsKit`
124135
* on the server. Use it for full devframe-native access — register client RPC

‎packages/devtools-kit/src/_types/hooks.ts‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit'
22
import type { ModuleCustomTab } from './custom-tabs'
3+
import type { NuxtDevtoolsNotifyInput } from './notify'
34
import type { NuxtDevtoolsInfo } from './server-ctx'
45
import type { TerminalState } from './terminals'
56

@@ -26,6 +27,21 @@ declare module '@nuxt/schema' {
2627
*/
2728
'devtools:ready': (ctx: ViteDevToolsNodeContext) => void | Promise<void>
2829

30+
/**
31+
* Push a notification through the devframe Messages system.
32+
*
33+
* Forwarded to the connected `ctx.messages` host, so it surfaces in the
34+
* Vite DevTools **Messages** dock (persistent, when leveled) and/or as a
35+
* transient toast (when `notify` is set). Calls made before the kit connects
36+
* are buffered and replayed once it does.
37+
*
38+
* @example
39+
* ```ts
40+
* nuxt.callHook('devtools:notify', { message: 'Build failed', level: 'error' })
41+
* ```
42+
*/
43+
'devtools:notify': (input: NuxtDevtoolsNotifyInput) => void
44+
2945
/**
3046
* Hooks to extend devtools tabs.
3147
*/

‎packages/devtools-kit/src/_types/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export * from './client-api'
55
export * from './common'
66
export * from './custom-tabs'
77
export * from './integrations'
8+
export * from './notify'
89
export * from './options'
910
export * from './rpc'
1011
export * from './server-ctx'
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { DevToolsMessageFilePosition, DevToolsMessageLevel } from '@vitejs/devtools-kit'
2+
3+
/**
4+
* Severity level of a notification, mirroring devframe's message levels.
5+
*
6+
* Determines the color/icon of the entry in the Vite DevTools **Messages** dock
7+
* and its toast.
8+
*/
9+
export type NuxtDevtoolsNotifyLevel = DevToolsMessageLevel
10+
11+
/**
12+
* A Nuxt-friendly subset of devframe's `DevframeMessageEntryInput`.
13+
*
14+
* This is the input accepted by the `devtools:notify` Nuxt hook, the `notify`
15+
* RPC function and the injected client's `notify()` — all of which forward to
16+
* the connected `ctx.messages` host so notifications flow through the single
17+
* devframe Messages system (persistent dock list + toast overlay).
18+
*
19+
* Tiers are expressed through the flags below:
20+
* - **Ephemeral** (toast-only feedback like "Copied!"): `notify: true` with an
21+
* `autoDismiss` (toast lifetime) and `autoDelete` (entry lifetime) so it never
22+
* builds up history in the Messages dock.
23+
* - **Persistent** (server-originated, leveled): omit `autoDelete` so the entry
24+
* is kept in the Messages dock list.
25+
*/
26+
export interface NuxtDevtoolsNotifyInput {
27+
/** Short title / summary of the message. */
28+
message: string
29+
/** Severity level. Defaults to `'info'`. */
30+
level?: NuxtDevtoolsNotifyLevel
31+
/** Optional detailed description or explanation. */
32+
description?: string
33+
/** Optional tags/labels for filtering in the Messages dock. */
34+
labels?: string[]
35+
/** Optional grouping category (e.g. `'build'`, `'lint'`, `'runtime'`). */
36+
category?: string
37+
/** Optional source file position (e.g. for a build/lint error). */
38+
filePosition?: DevToolsMessageFilePosition
39+
/** Optional stack trace string. */
40+
stacktrace?: string
41+
/** Whether this message should also appear as a transient toast. */
42+
notify?: boolean
43+
/** Time in ms to auto-dismiss the toast (client-side). */
44+
autoDismiss?: number
45+
/** Time in ms to auto-delete the entry from the persistent list (server-side). */
46+
autoDelete?: number
47+
}

‎packages/devtools-kit/src/_types/rpc.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { ResolvedConfig } from 'vite'
55
import type { AnalyzeBuildsInfo } from './analyze-build'
66
import type { ModuleCustomTab } from './custom-tabs'
77
import type { AssetEntry, AssetInfo, AutoImportsWithMetadata, ComponentRelationship, HookInfo, ImageMeta, NpmCommandOptions, NpmCommandType, PackageUpdateInfo, ScannedNitroTasks, ServerRouteInfo } from './integrations'
8+
import type { NuxtDevtoolsNotifyInput } from './notify'
89
import type { ModuleOptions, NuxtDevToolsOptions } from './options'
910
import type { InstallModuleReturn, ServerDebugContext } from './server-ctx'
1011
import type { TerminalAction, TerminalInfo } from './terminals'
@@ -63,6 +64,9 @@ export interface ServerFunctions {
6364
deleteStaticAsset: (filepath: string) => Promise<void>
6465
renameStaticAsset: (oldPath: string, newPath: string) => Promise<void>
6566

67+
// Notifications
68+
notify: (input: NuxtDevtoolsNotifyInput) => Promise<void>
69+
6670
// Actions
6771
telemetryEvent: (payload: object, immediate?: boolean) => void
6872
customTabAction: (name: string, action: number) => Promise<boolean>

‎packages/devtools-kit/src/_types/server-ctx.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit'
22
import type { BirpcGroup } from 'birpc'
33
import type { Nuxt, NuxtDebugModuleMutationRecord } from 'nuxt/schema'
4+
import type { NuxtDevtoolsNotifyInput } from './notify'
45
import type { ModuleOptions } from './options'
56
import type { ClientFunctions, ServerFunctions } from './rpc'
67

@@ -55,6 +56,16 @@ export interface NuxtDevtoolsServerContext {
5556
*/
5657
refresh: (event: keyof ServerFunctions) => void
5758

59+
/**
60+
* Push a notification through the devframe Messages system (`ctx.messages`).
61+
*
62+
* The connected messages host surfaces it in the Vite DevTools **Messages**
63+
* dock and/or as a toast. Calls made before the kit connects are buffered and
64+
* replayed on connect. Used by the `devtools:notify` hook, the `notify` RPC
65+
* function and the curated built-in notification sources.
66+
*/
67+
notify: (input: NuxtDevtoolsNotifyInput) => void
68+
5869
/**
5970
* @deprecated Use the Vite DevTools RPC registration instead:
6071
* `nuxt.devtools.rpc.register(defineRpcFunction(...))`. Kept working as a shim.
Lines changed: 8 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,13 @@
11
<script setup lang='ts'>
2-
import type { DevtoolsUiShowNotificationPosition } from '../composables/notification'
3-
4-
import { ref } from 'vue'
5-
import { devtoolsUiProvideNotificationFn } from '../composables/notification'
6-
7-
const show = ref(false)
8-
const icon = ref<string>()
9-
const text = ref<string>()
10-
const classes = ref<string>()
11-
const position = ref<DevtoolsUiShowNotificationPosition>('top-center')
12-
13-
devtoolsUiProvideNotificationFn((data) => {
14-
text.value = data.message
15-
icon.value = data.icon
16-
classes.value = data.classes ?? 'text-primary border-primary'
17-
icon.value = data.icon
18-
show.value = true
19-
position.value = data.position ?? 'top-center'
20-
setTimeout(() => {
21-
show.value = false
22-
}, data.duration ?? 1500)
23-
})
2+
/**
3+
* @deprecated The bespoke Nuxt DevTools toast has been retired. Notifications
4+
* now flow through the devframe Messages system (Vite DevTools' built-in
5+
* Messages dock + toast overlay). Use `devtoolsUiShowNotification(...)`, which
6+
* is re-pointed onto that system — this component is a no-op shim kept only so
7+
* existing `<NNotification />` mounts don't break, and renders nothing.
8+
*/
249
</script>
2510

2611
<template>
27-
<div
28-
fixed left-0 right-0 z-999 text-center
29-
:class="[
30-
{ 'pointer-events-none overflow-hidden': !show },
31-
{ 'top-0': position.startsWith('top') },
32-
{ 'bottom-0': position.startsWith('bottom') },
33-
]"
34-
>
35-
<div flex :style="{ justifyContent: position.includes('right') ? 'right' : position.includes('left') ? 'left' : 'center' }">
36-
<div
37-
border="~ base"
38-
flex="~ inline gap2"
39-
m-3 inline-block items-center rounded bg-base px-4 py-1 transition-all duration-300
40-
:style="show ? {} : { transform: `translateY(${position.startsWith('top') ? '-' : ''}300%)` }"
41-
:class="[show ? 'shadow' : 'shadow-none', classes]"
42-
>
43-
<div v-if="icon" :class="icon" />
44-
<div>{{ text }}</div>
45-
</div>
46-
</div>
47-
</div>
12+
<div hidden />
4813
</template>

‎packages/devtools-ui-kit/src/composables/notification.ts‎

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,44 @@ let _devtoolsUiShowNotification: typeof devtoolsUiShowNotification
22

33
export type DevtoolsUiShowNotificationPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
44

5-
export function devtoolsUiShowNotification(data: {
5+
/**
6+
* Severity of an ephemeral notification. Mirrors devframe's message levels; the
7+
* concrete implementation (provided by the Nuxt DevTools client) maps this onto
8+
* a devframe message so the toast picks up the matching color/icon.
9+
*/
10+
export type DevtoolsUiShowNotificationLevel = 'info' | 'warn' | 'error' | 'success' | 'debug'
11+
12+
export interface DevtoolsUiShowNotificationData {
613
message: string
714
icon?: string
815
classes?: string
16+
/**
17+
* Severity of the notification. When omitted the implementation infers it
18+
* from `classes`/`icon` (best-effort), defaulting to `info`.
19+
*/
20+
level?: DevtoolsUiShowNotificationLevel
21+
/**
22+
* Toast lifetime, in ms.
23+
*
24+
* @remarks Best-effort — the concrete implementation may forward this to the
25+
* host toast, which ultimately owns placement and timing.
26+
*/
927
duration?: number
28+
/**
29+
* @deprecated Placement is owned by the Vite DevTools chrome toast; this is a
30+
* no-op and kept only for backward compatibility.
31+
*/
1032
position?: DevtoolsUiShowNotificationPosition
11-
}) {
33+
}
34+
35+
/**
36+
* Show an ephemeral toast notification.
37+
*
38+
* This is a thin proxy: the Nuxt DevTools client provides the concrete
39+
* implementation via {@link devtoolsUiProvideNotificationFn} at startup, which
40+
* routes the toast through the devframe Messages system. Fire-and-forget.
41+
*/
42+
export function devtoolsUiShowNotification(data: DevtoolsUiShowNotificationData) {
1243
_devtoolsUiShowNotification?.(data)
1344
}
1445

‎packages/devtools/client/app.vue‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,6 @@ registerCommands(() => [
143143
<template>
144144
<div fixed inset-0 h-screen w-screen font-sans>
145145
<NuxtLoadingIndicator />
146-
<NNotification />
147146
<NLoading v-if="waiting">
148147
Connecting....
149148
</NLoading>

0 commit comments

Comments
 (0)