From 2dd04e0482b0f8accdd0c334751cbf7383b3c75e Mon Sep 17 00:00:00 2001 From: Grace Kind Date: Sun, 6 Sep 2026 00:53:18 -0500 Subject: [PATCH] Suppress performance hints for third-party plugins in overlay --- package.json | 2 +- src/js/errorLogs.js | 11 +++- src/js/plugins/pluginSlotDispatcher.js | 10 +++- tests/unit/specs/errorLogs.test.js | 57 +++++++++++++++++++ .../plugins/pluginSlotDispatcher.test.js | 14 +++++ 5 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 tests/unit/specs/errorLogs.test.js diff --git a/package.json b/package.json index 893a2e74..8f11a959 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "impro", - "version": "0.20.57", + "version": "0.20.58", "type": "module", "scripts": { "start": "rm -rf \"${BUILD_DIR:-build}\" && NODE_ENV=development eleventy --serve", diff --git a/src/js/errorLogs.js b/src/js/errorLogs.js index 42d5d799..fd955878 100644 --- a/src/js/errorLogs.js +++ b/src/js/errorLogs.js @@ -11,6 +11,12 @@ const LEVEL_STYLES = { }, }; +const SUPPRESSED_TAGS = ["[plugins:perf]"]; + +function isSuppressed(message) { + return SUPPRESSED_TAGS.some((tag) => message.startsWith(tag)); +} + function formatArg(arg) { if (typeof arg === "string") { return arg; @@ -112,7 +118,10 @@ export function enableErrorLogs() { const consoleWarn = console.warn; console.warn = (...args) => { - showMessage(args.map(formatArg).join(" "), "warn"); + const message = args.map(formatArg).join(" "); + if (!isSuppressed(message)) { + showMessage(message, "warn"); + } consoleWarn(...args); }; diff --git a/src/js/plugins/pluginSlotDispatcher.js b/src/js/plugins/pluginSlotDispatcher.js index 376343b8..2f3081a5 100644 --- a/src/js/plugins/pluginSlotDispatcher.js +++ b/src/js/plugins/pluginSlotDispatcher.js @@ -164,6 +164,14 @@ function invocationAdvice(cacheKey) { return `Its cacheKey (${cacheKey.join(", ")}) may be too specific to share results.`; } +// Hints for local dev plugins get their own tag so the dev error overlay can +// show them while suppressing hints about third-party plugins +function perfTag(pluginId) { + return pluginId.endsWith("__LOCAL") + ? "[plugins:perf:local]" + : "[plugins:perf]"; +} + const INVOCATION_WINDOW_MS = 5000; const TOTAL_INVOCATION_LIMIT = 100; @@ -183,7 +191,7 @@ export class SlotInvocationMonitor { const seconds = INVOCATION_WINDOW_MS / 1000; const advice = invocationAdvice(registration.cacheKey); console.warn( - `[plugins] "${registration.pluginId}" slot "${name}" ran ${exceeded.total} times in ${seconds}s across ${exceeded.distinct} contexts. ${advice}`, + `${perfTag(registration.pluginId)} "${registration.pluginId}" slot "${name}" ran ${exceeded.total} times in ${seconds}s across ${exceeded.distinct} contexts. ${advice}`, ); } } diff --git a/tests/unit/specs/errorLogs.test.js b/tests/unit/specs/errorLogs.test.js new file mode 100644 index 00000000..a3836643 --- /dev/null +++ b/tests/unit/specs/errorLogs.test.js @@ -0,0 +1,57 @@ +import { describe, it, beforeEach, afterEach } from "node:test"; +import assert from "node:assert/strict"; +import { enableErrorLogs } from "/js/errorLogs.js"; + +describe("errorLogs overlay", () => { + const originalWarn = console.warn; + const originalError = console.error; + let overlay; + + function entries() { + return [...overlay.querySelectorAll("[data-log-level]")]; + } + + beforeEach(() => { + console.warn = () => {}; + console.error = () => {}; + enableErrorLogs(); + overlay = document.getElementById("error-log"); + }); + + afterEach(() => { + overlay.remove(); + console.warn = originalWarn; + console.error = originalError; + }); + + it("shows warnings in the overlay", () => { + console.warn("[plugins] something odd"); + assert.deepEqual(entries().length, 1); + assert(entries()[0].textContent.includes("[plugins] something odd")); + assert.deepEqual(overlay.style.display, "block"); + }); + + it("suppresses warnings carrying an advisory tag", () => { + console.warn('[plugins:perf] "some-plugin" slot "x" ran 100 times'); + assert.deepEqual(entries(), []); + assert.deepEqual(overlay.style.display, "none"); + }); + + it("shows hints tagged for local dev plugins", () => { + console.warn( + '[plugins:perf:local] "my-plugin__LOCAL" slot "x" ran 100 times', + ); + assert.deepEqual(entries().length, 1); + }); + + it("only matches the tag at the start of the message", () => { + console.warn("prefix [plugins:perf] not a hint"); + assert.deepEqual(entries().length, 1); + }); + + it("still shows errors", () => { + console.error("[plugins:perf] errors are never suppressed"); + assert.deepEqual(entries().length, 1); + assert.deepEqual(entries()[0].dataset.logLevel, "error"); + }); +}); diff --git a/tests/unit/specs/plugins/pluginSlotDispatcher.test.js b/tests/unit/specs/plugins/pluginSlotDispatcher.test.js index 21dae029..60bc94eb 100644 --- a/tests/unit/specs/plugins/pluginSlotDispatcher.test.js +++ b/tests/unit/specs/plugins/pluginSlotDispatcher.test.js @@ -621,10 +621,24 @@ describe("PluginSlotDispatcher - invocation monitor", () => { ), ); assert.deepEqual(warnings.length, 1); + assert(warnings[0].startsWith("[plugins:perf]")); assert(warnings[0].includes("100 times in 5s across 100 contexts")); assert(warnings[0].includes("Declare a cacheKey")); }); + it("tags hints about local dev plugins separately", async () => { + const { registration } = register(makeDispatcher(), { + pluginId: "my-plugin__LOCAL", + }); + await Promise.all( + Array.from({ length: 100 }, (_, index) => + registration.request({ did: `did:${index}` }), + ), + ); + assert.deepEqual(warnings.length, 1); + assert(warnings[0].startsWith('[plugins:perf:local] "my-plugin__LOCAL"')); + }); + it("points at an over-specific cacheKey when one is already declared", async () => { const { registration } = register(makeDispatcher(), { cacheKey: ["uri"] }); await Promise.all( -- 2.51.2