From 2936ba6e3def8cf6fe7bb246c6a889e2013134e0 Mon Sep 17 00:00:00 2001 From: Devvyn <22340871+opticon454@users.noreply.github.com> Date: Wed, 16 Sep 2026 15:29:38 +0800 Subject: [PATCH] fix(custom-model): replace the native confirm() popup with an in-app modal The llama-swap "this will unload it for session X" warning used a native browser confirm() popup, which looks out of place next to the rest of the app's own modals. Adds #customModelSwapConfirmModal (index.html) with Cancel/Switch-anyway buttons, styled to match the app. _confirmModelSwap(message) shows it and returns a promise that resolves true/false the same way confirm() would; _resolveModelSwapConfirm(proceed) (wired to both buttons and the backdrop click) settles it. Both llama-swap conflict call sites (_quickStartWithCustomModelConfirm for the one-shot launch path, _runCustomModelEntryViaRestart for Claude's restart path) now await this instead of calling confirm() directly. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01RqZeHrRS6DYcGcGX2p9EwG --- src/web/public/index.html | 21 +++++++++++ src/web/public/session-ui.js | 30 ++++++++++++++- src/web/public/styles.css | 11 ++++++ test/custom-model-one-shot-launch.test.ts | 4 +- test/custom-model-run-menu-ui.test.ts | 45 +++++++++++++++++++---- 5 files changed, 99 insertions(+), 12 deletions(-) diff --git a/src/web/public/index.html b/src/web/public/index.html index b962b55d..9d2e0dc7 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -927,6 +927,27 @@ + + + + `, { url: 'http://localhost/', runScripts: 'dangerously' } ); @@ -515,8 +518,8 @@ describe('Custom Model Endpoint Profiles: llama-swap model-swap confirmation and return { win, app, applyBodies }; } - it('confirming the native window.confirm() re-sends the apply with confirmed:true', async () => { - const { win, app, applyBodies } = launchHarness([ + it('confirming the in-app swap-confirm modal re-sends the apply with confirmed:true', async () => { + const { app, applyBodies } = launchHarness([ { requiresConfirmation: true, currentlyLoadedModel: 'llama3', @@ -525,10 +528,10 @@ describe('Custom Model Endpoint Profiles: llama-swap model-swap confirmation and { customModel: { endpointId: 'llama-box' }, restarted: true, modelSwapInProgress: true }, ]); let confirmMessage: string | undefined; - win.confirm = ((msg: string) => { - confirmMessage = msg; + app._confirmModelSwap = async (message: string) => { + confirmMessage = message; return true; - }) as typeof win.confirm; + }; app._watchLlamaSwapLoading = async () => {}; // not under test here await app.runCustomModelEntry('claude', 'llama-box', 'qwen3'); @@ -542,11 +545,11 @@ describe('Custom Model Endpoint Profiles: llama-swap model-swap confirmation and ]); }); - it('cancelling window.confirm() keeps the native backend and never re-sends the apply', async () => { - const { win, app, applyBodies } = launchHarness([ + it('cancelling the in-app swap-confirm modal keeps the native backend and never re-sends the apply', async () => { + const { app, applyBodies } = launchHarness([ { requiresConfirmation: true, currentlyLoadedModel: 'llama3', affectedSessions: [{ id: 's2', name: 'w2' }] }, ]); - win.confirm = (() => false) as typeof win.confirm; + app._confirmModelSwap = async () => false; let toastMessage: string | undefined; app.showToast = (msg: string) => { toastMessage = msg; @@ -658,3 +661,29 @@ describe('Custom Model Endpoint Profiles: _watchLlamaSwapLoading polling', () => expect(toastCalls.at(-1)).toMatch(/ready/i); }); }); + +describe('Custom Model Endpoint Profiles: _confirmModelSwap (in-app modal, replaces a native confirm() popup)', () => { + it('shows the message, activates the modal, and resolves true when "Switch anyway" is clicked', async () => { + const { win, app } = bootApp({}); + const promise = app._confirmModelSwap('w2 is using llama3. Switch anyway?'); + + const modal = win.document.getElementById('customModelSwapConfirmModal')!; + expect(modal.classList.contains('active')).toBe(true); + expect(win.document.getElementById('customModelSwapConfirmMessage')!.textContent).toBe( + 'w2 is using llama3. Switch anyway?' + ); + + app._resolveModelSwapConfirm(true); + + expect(await promise).toBe(true); + expect(modal.classList.contains('active')).toBe(false); + }); + + it('resolves false when Cancel (or the backdrop) is clicked, without ever showing a browser confirm() popup', async () => { + const { win, app } = bootApp({}); + const promise = app._confirmModelSwap('w2 is using llama3. Switch anyway?'); + app._resolveModelSwapConfirm(false); + expect(await promise).toBe(false); + expect(win.document.getElementById('customModelSwapConfirmModal')!.classList.contains('active')).toBe(false); + }); +});