From fa77ba896c7537c73f16497c028d359bc7b56b40 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 10 Nov 2026 23:51:20 +0000 Subject: [PATCH 1/2] chore(deps): update my-bad to 1.2.0 --- packages/nuxt-cli/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/nuxt-cli/package.json b/packages/nuxt-cli/package.json index 1c06f4322..62565078a 100644 --- a/packages/nuxt-cli/package.json +++ b/packages/nuxt-cli/package.json @@ -75,7 +75,7 @@ "exsolve": "^1.2.0", "fuzzysort": "^4.0.2", "get-port-please": "^3.2.0", - "my-bad": "^1.0.1", + "my-bad": "^1.2.0", "obug": "^3.0.0", "package-manager-detector": "^1.9.0", "pathe": "^2.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 100d2ea41..d77446579 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -400,8 +400,8 @@ importers: specifier: ^3.2.0 version: 3.2.0 my-bad: - specifier: ^1.0.1 - version: 1.0.1(vite@8.3.5) + specifier: ^1.2.0 + version: 1.2.0(vite@8.3.5) obug: specifier: ^3.0.0 version: 3.0.0 @@ -4575,8 +4575,8 @@ packages: vite: optional: true - my-bad@1.0.1: - resolution: {integrity: sha512-/1XWvawUM0EYtIOXE+3w09bbf3qeU9OcHTdJgJ0BTjbabW+C3PRY0CZl1SonAzpgYnic2WXc2rJKIQfDugPwyw==} + my-bad@1.2.0: + resolution: {integrity: sha512-y7Fh95PwxGlod59un5ASP1xgbG2YzpmRQ2twCHCUZT2mJXF4m+2dNwpbbkUXbiGq2CZu1A5D4xDVXhIB4yK+mw==} engines: {node: '>=22.12.0'} peerDependencies: vite: '>=6' @@ -11268,7 +11268,7 @@ snapshots: optionalDependencies: vite: 8.3.5(@types/node@24.19.2)(@vitejs/devtools@0.7.6)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.1) - my-bad@1.0.1(vite@8.3.5): + my-bad@1.2.0(vite@8.3.5): dependencies: clickable-path: 0.1.1 errx: 0.2.2 From 2ec4fa986022a95dfa9c424fc56dac797fad9319 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 10 Nov 2026 23:51:20 +0000 Subject: [PATCH 2/2] feat(dev): scope error clears to the recovering request --- packages/nuxt-cli/src/dev/error-channel.ts | 18 +++++- .../nuxt-cli/test/unit/error-channel.spec.ts | 55 +++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/packages/nuxt-cli/src/dev/error-channel.ts b/packages/nuxt-cli/src/dev/error-channel.ts index d46d004e1..fd7b4276c 100644 --- a/packages/nuxt-cli/src/dev/error-channel.ts +++ b/packages/nuxt-cli/src/dev/error-channel.ts @@ -26,7 +26,7 @@ export const ERROR_BROADCAST_CHANNEL = 'nuxt:dev:error' /** Reports the app forwards; anything else on the wire is ignored. */ export type DevErrorMessage = | { type: 'nuxt:dev:error:report', report: ErrorReport, requestId?: string, request?: string } - | { type: 'nuxt:dev:error:clear', id?: string } + | { type: 'nuxt:dev:error:clear', id?: string, requestId?: string, request?: string } | { type: 'nuxt:dev:error:warning', report: ErrorReport } | { type: 'nuxt:dev:error:warning:clear', id?: string } | { type: 'nuxt:dev:error:log', entry: LogEntry } @@ -230,10 +230,14 @@ export function isDevErrorMessage(message: unknown): message is DevErrorMessage const { source } = message as { source?: unknown } return source === undefined || typeof source === 'string' } + if (type === 'nuxt:dev:error:clear') { + const { requestId } = message as { requestId?: unknown } + return requestId === undefined || typeof requestId === 'string' + } if (type === 'nuxt:dev:error:devtools') { return typeof (message as { registered?: unknown }).registered === 'boolean' } - return type === 'nuxt:dev:error:report' || type === 'nuxt:dev:error:clear' || type === 'nuxt:dev:error:warning' || type === 'nuxt:dev:error:warning:clear' + return type === 'nuxt:dev:error:report' || type === 'nuxt:dev:error:warning' || type === 'nuxt:dev:error:warning:clear' } const LOG_LEVELS = new Set(['trace', 'debug', 'info', 'log', 'warn', 'error', 'fatal'] satisfies LogLevel[]) @@ -397,7 +401,15 @@ export function openErrorBridge(handlers: ErrorBridgeHandlers = {}, options: Err break } case 'nuxt:dev:error:clear': { - instance.clearError(message.id) + if (message.requestId !== undefined || message.request !== undefined) { + instance.clearError(message.id, { + ...(message.requestId !== undefined ? { requestId: message.requestId } : {}), + ...(message.request !== undefined ? { request: message.request } : {}), + }) + } + else { + instance.clearError(message.id) + } handlers.onClear?.(message.id) break } diff --git a/packages/nuxt-cli/test/unit/error-channel.spec.ts b/packages/nuxt-cli/test/unit/error-channel.spec.ts index 27c910490..27b93292f 100644 --- a/packages/nuxt-cli/test/unit/error-channel.spec.ts +++ b/packages/nuxt-cli/test/unit/error-channel.spec.ts @@ -142,6 +142,10 @@ describe('the forwarding protocol', () => { expect(isDevErrorMessage({ type: 'nuxt:dev:error:sync' })).toBe(false) expect(isDevErrorMessage({ type: 'nuxt:dev:error:report', report: {}, request: 'GET /ok' })).toBe(true) expect(isDevErrorMessage({ type: 'nuxt:dev:error:report', report: {}, request: 7 })).toBe(false) + expect(isDevErrorMessage({ type: 'nuxt:dev:error:clear', requestId: 'page-a' })).toBe(true) + expect(isDevErrorMessage({ type: 'nuxt:dev:error:clear', request: 'GET /ok' })).toBe(true) + expect(isDevErrorMessage({ type: 'nuxt:dev:error:clear', requestId: 7 })).toBe(false) + expect(isDevErrorMessage({ type: 'nuxt:dev:error:clear', request: 7 })).toBe(false) expect(isDevErrorMessage(undefined)).toBe(false) }) @@ -420,6 +424,57 @@ describe('the CLI-owned error channel', () => { expect(setError).toHaveBeenNthCalledWith(2, buildReport, undefined, undefined) }) + it('should retire an error only for the request the app names when clearing', async () => { + const instance = await useErrorChannel() + const report = compileReport('/app/app.vue', 3, 1) + const close = openErrorBridge() + + const app = new BroadcastChannel(ERROR_BROADCAST_CHANNEL) + const pageA = createResponse() + const pageB = createResponse() + await instance.handler(request(`${DEFAULT_ERROR_CHANNEL}/events?requestId=page-a`), pageA.res) + await instance.handler(request(`${DEFAULT_ERROR_CHANNEL}/events?requestId=page-b`), pageB.res) + + app.postMessage({ type: 'nuxt:dev:error:report', report, requestId: 'page-a' }) + app.postMessage({ type: 'nuxt:dev:error:report', report, requestId: 'page-b' }) + await vi.waitUntil(() => pageA.chunks.join('').includes('event: error:set') && pageB.chunks.join('').includes('event: error:set')) + + app.postMessage({ type: 'nuxt:dev:error:clear', id: report.id, requestId: 'page-a' }) + await vi.waitUntil(() => pageA.chunks.join('').includes('event: error:clear')) + close() + app.close() + + expect(pageA.chunks.join('')).toContain('event: error:clear') + expect(pageB.chunks.join('')).not.toContain('event: error:clear') + expect(pageB.chunks.join('')).toContain(report.id) + expect(instance.current?.id).toBe(report.id) + }) + + it('should retire an error for every request when cleared without a scope', async () => { + const instance = await useErrorChannel() + const report = compileReport('/app/app.vue', 3, 1) + const close = openErrorBridge() + + const app = new BroadcastChannel(ERROR_BROADCAST_CHANNEL) + const pageA = createResponse() + const pageB = createResponse() + await instance.handler(request(`${DEFAULT_ERROR_CHANNEL}/events?requestId=page-a`), pageA.res) + await instance.handler(request(`${DEFAULT_ERROR_CHANNEL}/events?requestId=page-b`), pageB.res) + + app.postMessage({ type: 'nuxt:dev:error:report', report, requestId: 'page-a' }) + app.postMessage({ type: 'nuxt:dev:error:report', report, requestId: 'page-b' }) + await vi.waitUntil(() => pageA.chunks.join('').includes('event: error:set') && pageB.chunks.join('').includes('event: error:set')) + + app.postMessage({ type: 'nuxt:dev:error:clear', id: report.id }) + await vi.waitUntil(() => pageA.chunks.join('').includes('event: error:clear') && pageB.chunks.join('').includes('event: error:clear')) + close() + app.close() + + expect(pageA.chunks.join('')).toContain('event: error:clear') + expect(pageB.chunks.join('')).toContain('event: error:clear') + expect(instance.current).toBeUndefined() + }) + it('should publish a log entry the app forwards, without telling the supervisor', async () => { createServer() const instance = await useErrorChannel()