From 609305538adcac4adcdf80c580d9b3566f70ee97 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 10 Sep 2026 15:16:47 +0200 Subject: [PATCH] fix: drop the dead admin jump links; stop a test flake at its source Admin links - The [data-admin-jump] click handler had been pasted inside discoverModels(), so it only registered once someone pressed Search in AI Model Management, and re-registered on every later search. Normally nothing intercepted the click, so href="#" did what it says: jump to the top and leave "#" in the URL. - This is an app, so the pointers are plain text naming the sections rather than links, and the handler is gone. Test flake - admin-clinical-assistant-wiring failed about 1 run in 4 with "Unable to deserialize cloned data due to invalid or unsupported version": node:test reads a test file's results back over the child's stdout, and app.js's own console.log landed inside a serialized frame. - The page's console is now forwarded to stderr (jsdom 29: forwardTo). Verified: child stdout clean, 0 failures in 32 stress runs (was 6 in 24), and three full-suite runs at 668/668. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU --- public/components/admin.html | 2 +- public/js/admin.js | 13 ------------- test/admin-clinical-assistant-wiring.test.js | 13 +++++++++++-- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/public/components/admin.html b/public/components/admin.html index 74c5190d..15e526fe 100644 --- a/public/components/admin.html +++ b/public/components/admin.html @@ -296,7 +296,7 @@ Model availability

Models users may select in the Clinical Assistant. Leave a list empty to keep only the configured model.

-

To make a new model appear here, add it from the gateway: chat models in AI Model Management above, image models in Image Generation below — search, press + Add, and it shows up in this list.

+

To make a new model appear here, add it from the gateway: chat models under AI Model Management above, image models under Image Generation below — search there, press + Add, and it shows up in this list.

Chat models
diff --git a/public/js/admin.js b/public/js/admin.js index c3b5bb9a..f50a883d 100644 --- a/public/js/admin.js +++ b/public/js/admin.js @@ -909,19 +909,6 @@ initImageSettings(); container.innerHTML = '

Querying provider API...

'; if (hint) hint.style.display = 'none'; - // The Clinical Assistant lists models but cannot add them; adding happens in - // AI Model Management, one section up. Without this the two sections look - // unrelated and there is no visible way to get a new model into the list. - document.addEventListener('click', function(event) { - var jump = event.target.closest && event.target.closest('[data-admin-jump]'); - if (!jump) return; - event.preventDefault(); - var target = document.getElementById(jump.getAttribute('data-admin-jump')); - if (!target) return; - target.scrollIntoView({ behavior: 'smooth', block: 'center' }); - target.focus({ preventScroll: true }); - }); - fetch('/api/admin/config/models/discover?q=' + encodeURIComponent(search), { headers: getAuthHeaders() }) .then(function(r) { return r.json(); }) diff --git a/test/admin-clinical-assistant-wiring.test.js b/test/admin-clinical-assistant-wiring.test.js index 0bb41fff..42f6081e 100644 --- a/test/admin-clinical-assistant-wiring.test.js +++ b/test/admin-clinical-assistant-wiring.test.js @@ -3,10 +3,19 @@ const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const { pathToFileURL } = require('node:url'); -const { JSDOM } = require('jsdom'); +const { JSDOM, VirtualConsole } = require('jsdom'); +const { Console } = require('node:console'); const root = path.join(__dirname, '..'); const read = file => fs.readFileSync(path.join(root, file), 'utf8'); const tick = () => new Promise(resolve => setImmediate(resolve)); +// The page's console goes to stderr, never stdout. node:test reads each test +// file's results back over stdout as serialized frames; app.js's own log line +// ("✅ App.js loaded") written there could land inside a frame and fail the +// whole file with "Unable to deserialize cloned data" — about 1 run in 4. +function pageConsole() { + // jsdom 29 renamed sendTo() to forwardTo(). + return new VirtualConsole().forwardTo(new Console({ stdout: process.stderr, stderr: process.stderr })); +} function browserGlobals(t, dom, fetch, toasts) { const values = { window: dom.window, document: dom.window.document, fetch, getAuthHeaders: () => ({ 'X-Test': 'synthetic' }), showToast: (...args) => toasts.push(args) }; @@ -20,7 +29,7 @@ function browserGlobals(t, dom, fetch, toasts) { } test('native admin initializer preserves lazy navigation, assistant actions and read-only ENV budget metadata', async t => { - const dom = new JSDOM('
', { runScripts: 'outside-only', url: 'https://app.example' }); + const dom = new JSDOM('
', { runScripts: 'outside-only', url: 'https://app.example', virtualConsole: pageConsole() }); const calls = []; const toasts = []; const fetch = async (url, options = {}) => { calls.push({ url, options });