diff --git a/docs/content/2.module/1.utils-kit.md b/docs/content/2.module/1.utils-kit.md index b7902c9f68..5b429b646f 100644 --- a/docs/content/2.module/1.utils-kit.md +++ b/docs/content/2.module/1.utils-kit.md @@ -219,6 +219,29 @@ to the [migration guide](/module/migration-v4). See [`extendServerRpc()`](/module/migration-v4#ndt_dep_0003) and [`startSubprocess()`](/module/migration-v4#ndt_dep_0004). +### `devtools:notify` hook + +Push a notification into the unified devframe **Messages** system — the same +system that powers the Vite DevTools **Messages** dock and its toast overlay. +The notification is forwarded to `ctx.messages`, so no direct kit access is +needed, and calls made before the kit connects are buffered and replayed once +it does: + +```ts +// server-side (module setup) +nuxt.callHook('devtools:notify', { + message: 'Something happened', + level: 'error', // 'info' | 'warn' | 'error' | 'success' | 'debug' (default 'info') + description: 'More details about what happened.', + notify: true, // also show a transient toast +}) +``` + +Omit `autoDelete` for a **persistent**, leveled entry that builds history in the +Messages dock; set `notify` + `autoDismiss` + `autoDelete` for an **ephemeral**, +toast-only notification. From the client, the same input is available on the +injected client as `client.devtools.notify(...)`. + ## `@nuxt/devtools-kit/iframe-client` To provide complex interactions for your module integrations, we recommend to host your own view and display it in devtools via iframe. diff --git a/packages/devtools-kit/src/_types/client-api.ts b/packages/devtools-kit/src/_types/client-api.ts index c260cfa7f1..36598976bd 100644 --- a/packages/devtools-kit/src/_types/client-api.ts +++ b/packages/devtools-kit/src/_types/client-api.ts @@ -7,6 +7,7 @@ import type { $Fetch } from 'ofetch' import type { BuiltinLanguage } from 'shiki' import type { Ref } from 'vue' import type { HookInfo, LoadingTimeMetric, PluginMetric } from './integrations' +import type { NuxtDevtoolsNotifyInput } from './notify' import type { ServerFunctions } from './rpc' import type { TimelineMetrics } from './timeline-metrics' @@ -119,6 +120,16 @@ export interface NuxtDevtoolsClient { renderMarkdown: (markdown: string) => string colorMode: string + /** + * Push a notification through the devframe Messages system. + * + * Routes to the shared Messages host, so it surfaces in the Vite DevTools + * **Messages** dock and/or as a transient toast (when `notify` is set — the + * default). Use this from custom tabs / in-client code to give feedback + * through the same notification system as the rest of DevTools. + */ + notify: (input: NuxtDevtoolsNotifyInput) => Promise + /** * The connected Vite DevTools RPC client, mirroring `nuxt.devtools.devtoolsKit` * on the server. Use it for full devframe-native access — register client RPC diff --git a/packages/devtools-kit/src/_types/hooks.ts b/packages/devtools-kit/src/_types/hooks.ts index a348a1feb5..e562875067 100644 --- a/packages/devtools-kit/src/_types/hooks.ts +++ b/packages/devtools-kit/src/_types/hooks.ts @@ -1,5 +1,6 @@ import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit' import type { ModuleCustomTab } from './custom-tabs' +import type { NuxtDevtoolsNotifyInput } from './notify' import type { NuxtDevtoolsInfo } from './server-ctx' import type { TerminalState } from './terminals' @@ -26,6 +27,21 @@ declare module '@nuxt/schema' { */ 'devtools:ready': (ctx: ViteDevToolsNodeContext) => void | Promise + /** + * Push a notification through the devframe Messages system. + * + * Forwarded to the connected `ctx.messages` host, so it surfaces in the + * Vite DevTools **Messages** dock (persistent, when leveled) and/or as a + * transient toast (when `notify` is set). Calls made before the kit connects + * are buffered and replayed once it does. + * + * @example + * ```ts + * nuxt.callHook('devtools:notify', { message: 'Build failed', level: 'error' }) + * ``` + */ + 'devtools:notify': (input: NuxtDevtoolsNotifyInput) => void + /** * Hooks to extend devtools tabs. */ diff --git a/packages/devtools-kit/src/_types/index.ts b/packages/devtools-kit/src/_types/index.ts index d54ed0fd98..3843b640c9 100644 --- a/packages/devtools-kit/src/_types/index.ts +++ b/packages/devtools-kit/src/_types/index.ts @@ -5,6 +5,7 @@ export * from './client-api' export * from './common' export * from './custom-tabs' export * from './integrations' +export * from './notify' export * from './options' export * from './rpc' export * from './server-ctx' diff --git a/packages/devtools-kit/src/_types/notify.ts b/packages/devtools-kit/src/_types/notify.ts new file mode 100644 index 0000000000..f245023576 --- /dev/null +++ b/packages/devtools-kit/src/_types/notify.ts @@ -0,0 +1,47 @@ +import type { DevToolsMessageFilePosition, DevToolsMessageLevel } from '@vitejs/devtools-kit' + +/** + * Severity level of a notification, mirroring devframe's message levels. + * + * Determines the color/icon of the entry in the Vite DevTools **Messages** dock + * and its toast. + */ +export type NuxtDevtoolsNotifyLevel = DevToolsMessageLevel + +/** + * A Nuxt-friendly subset of devframe's `DevframeMessageEntryInput`. + * + * This is the input accepted by the `devtools:notify` Nuxt hook, the `notify` + * RPC function and the injected client's `notify()` — all of which forward to + * the connected `ctx.messages` host so notifications flow through the single + * devframe Messages system (persistent dock list + toast overlay). + * + * Tiers are expressed through the flags below: + * - **Ephemeral** (toast-only feedback like "Copied!"): `notify: true` with an + * `autoDismiss` (toast lifetime) and `autoDelete` (entry lifetime) so it never + * builds up history in the Messages dock. + * - **Persistent** (server-originated, leveled): omit `autoDelete` so the entry + * is kept in the Messages dock list. + */ +export interface NuxtDevtoolsNotifyInput { + /** Short title / summary of the message. */ + message: string + /** Severity level. Defaults to `'info'`. */ + level?: NuxtDevtoolsNotifyLevel + /** Optional detailed description or explanation. */ + description?: string + /** Optional tags/labels for filtering in the Messages dock. */ + labels?: string[] + /** Optional grouping category (e.g. `'build'`, `'lint'`, `'runtime'`). */ + category?: string + /** Optional source file position (e.g. for a build/lint error). */ + filePosition?: DevToolsMessageFilePosition + /** Optional stack trace string. */ + stacktrace?: string + /** Whether this message should also appear as a transient toast. */ + notify?: boolean + /** Time in ms to auto-dismiss the toast (client-side). */ + autoDismiss?: number + /** Time in ms to auto-delete the entry from the persistent list (server-side). */ + autoDelete?: number +} diff --git a/packages/devtools-kit/src/_types/rpc.ts b/packages/devtools-kit/src/_types/rpc.ts index e52759a275..c5fafd8971 100644 --- a/packages/devtools-kit/src/_types/rpc.ts +++ b/packages/devtools-kit/src/_types/rpc.ts @@ -5,6 +5,7 @@ import type { ResolvedConfig } from 'vite' import type { AnalyzeBuildsInfo } from './analyze-build' import type { ModuleCustomTab } from './custom-tabs' import type { AssetEntry, AssetInfo, AutoImportsWithMetadata, ComponentRelationship, HookInfo, ImageMeta, NpmCommandOptions, NpmCommandType, PackageUpdateInfo, ScannedNitroTasks, ServerRouteInfo } from './integrations' +import type { NuxtDevtoolsNotifyInput } from './notify' import type { ModuleOptions, NuxtDevToolsOptions } from './options' import type { InstallModuleReturn, ServerDebugContext } from './server-ctx' import type { TerminalAction, TerminalInfo } from './terminals' @@ -63,6 +64,9 @@ export interface ServerFunctions { deleteStaticAsset: (filepath: string) => Promise renameStaticAsset: (oldPath: string, newPath: string) => Promise + // Notifications + notify: (input: NuxtDevtoolsNotifyInput) => Promise + // Actions telemetryEvent: (payload: object, immediate?: boolean) => void customTabAction: (name: string, action: number) => Promise diff --git a/packages/devtools-kit/src/_types/server-ctx.ts b/packages/devtools-kit/src/_types/server-ctx.ts index e26ec44332..e6ab6e57ef 100644 --- a/packages/devtools-kit/src/_types/server-ctx.ts +++ b/packages/devtools-kit/src/_types/server-ctx.ts @@ -1,6 +1,7 @@ import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit' import type { BirpcGroup } from 'birpc' import type { Nuxt, NuxtDebugModuleMutationRecord } from 'nuxt/schema' +import type { NuxtDevtoolsNotifyInput } from './notify' import type { ModuleOptions } from './options' import type { ClientFunctions, ServerFunctions } from './rpc' @@ -55,6 +56,16 @@ export interface NuxtDevtoolsServerContext { */ refresh: (event: keyof ServerFunctions) => void + /** + * Push a notification through the devframe Messages system (`ctx.messages`). + * + * The connected messages host surfaces it in the Vite DevTools **Messages** + * dock and/or as a toast. Calls made before the kit connects are buffered and + * replayed on connect. Used by the `devtools:notify` hook, the `notify` RPC + * function and the curated built-in notification sources. + */ + notify: (input: NuxtDevtoolsNotifyInput) => void + /** * @deprecated Use the Vite DevTools RPC registration instead: * `nuxt.devtools.rpc.register(defineRpcFunction(...))`. Kept working as a shim. diff --git a/packages/devtools-ui-kit/src/components/NNotification.vue b/packages/devtools-ui-kit/src/components/NNotification.vue index 95fda04a4f..8bc1c7095c 100644 --- a/packages/devtools-ui-kit/src/components/NNotification.vue +++ b/packages/devtools-ui-kit/src/components/NNotification.vue @@ -1,48 +1,13 @@ diff --git a/packages/devtools-ui-kit/src/composables/notification.ts b/packages/devtools-ui-kit/src/composables/notification.ts index f1b9712268..2b07e6d3b2 100644 --- a/packages/devtools-ui-kit/src/composables/notification.ts +++ b/packages/devtools-ui-kit/src/composables/notification.ts @@ -2,13 +2,44 @@ let _devtoolsUiShowNotification: typeof devtoolsUiShowNotification export type DevtoolsUiShowNotificationPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' -export function devtoolsUiShowNotification(data: { +/** + * Severity of an ephemeral notification. Mirrors devframe's message levels; the + * concrete implementation (provided by the Nuxt DevTools client) maps this onto + * a devframe message so the toast picks up the matching color/icon. + */ +export type DevtoolsUiShowNotificationLevel = 'info' | 'warn' | 'error' | 'success' | 'debug' + +export interface DevtoolsUiShowNotificationData { message: string icon?: string classes?: string + /** + * Severity of the notification. When omitted the implementation infers it + * from `classes`/`icon` (best-effort), defaulting to `info`. + */ + level?: DevtoolsUiShowNotificationLevel + /** + * Toast lifetime, in ms. + * + * @remarks Best-effort — the concrete implementation may forward this to the + * host toast, which ultimately owns placement and timing. + */ duration?: number + /** + * @deprecated Placement is owned by the Vite DevTools chrome toast; this is a + * no-op and kept only for backward compatibility. + */ position?: DevtoolsUiShowNotificationPosition -}) { +} + +/** + * Show an ephemeral toast notification. + * + * This is a thin proxy: the Nuxt DevTools client provides the concrete + * implementation via {@link devtoolsUiProvideNotificationFn} at startup, which + * routes the toast through the devframe Messages system. Fire-and-forget. + */ +export function devtoolsUiShowNotification(data: DevtoolsUiShowNotificationData) { _devtoolsUiShowNotification?.(data) } diff --git a/packages/devtools/client/app.vue b/packages/devtools/client/app.vue index be4c6eb440..867e4f9eef 100644 --- a/packages/devtools/client/app.vue +++ b/packages/devtools/client/app.vue @@ -143,7 +143,6 @@ registerCommands(() => [