From fee001829b873913548ed73cbd045c8c8dc26db9 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Tue, 28 Jul 2026 13:17:31 +0200 Subject: [PATCH] Pin missing browser list when saved detail width exceeds max --- tests/test_downloads_tab_workflow_route.mjs | 69 +++++++++++++++++++ web/css/resolver-main.css | 8 +++ web/resolver/views/missing_browser_methods.js | 13 ++-- 3 files changed, 86 insertions(+), 4 deletions(-) diff --git a/tests/test_downloads_tab_workflow_route.mjs b/tests/test_downloads_tab_workflow_route.mjs index 5d671a6..463ec6a 100644 --- a/tests/test_downloads_tab_workflow_route.mjs +++ b/tests/test_downloads_tab_workflow_route.mjs @@ -507,6 +507,75 @@ test('missing browser splitter preserves the table through the Type column', () ); }); +test('missing browser keeps the list edge pinned until saved detail width fits', () => { + const applyDetailWidth = eval( + `(${extractMethod(missingBrowserMethodsSource, 'applyMissingBrowserDetailWidth')})` + ); + const previousHTMLElement = globalThis.HTMLElement; + + class FakeHTMLElement {} + globalThis.HTMLElement = FakeHTMLElement; + + try { + const pinnedClasses = new Set(); + const browser = new FakeHTMLElement(); + const listPane = new FakeHTMLElement(); + const detailPane = new FakeHTMLElement(); + listPane.isConnected = true; + listPane.style = { flexBasis: '', flexGrow: '', flexShrink: '' }; + detailPane.style = { flexBasis: '', flexGrow: '', flexShrink: '' }; + browser.classList = { + contains(name) { + return pinnedClasses.has(name); + }, + toggle(name, enabled) { + if (enabled) pinnedClasses.add(name); + else pinnedClasses.delete(name); + }, + }; + browser.querySelector = selector => ( + selector === '.mr-missing-list-pane' ? listPane : detailPane + ); + detailPane.classList = { + contains(name) { + return name === 'mr-missing-detail-pane'; + }, + }; + detailPane.closest = () => browser; + + const context = { + _missingBrowserSplitListPane: listPane, + _missingBrowserLastDetailWidth: null, + }; + + applyDetailWidth.call(context, detailPane, 420, { + splitBounds: { min: 270, max: 300, available: 640 }, + }); + + assert.equal(pinnedClasses.has('is-list-pinned'), true); + assert.equal(detailPane.style.flexBasis, '420px'); + assert.equal(context._missingBrowserLastDetailWidth, 300); + + applyDetailWidth.call(context, detailPane, 420, { + splitBounds: { min: 300, max: 450, available: 790 }, + }); + + assert.equal(pinnedClasses.has('is-list-pinned'), false); + assert.equal(detailPane.style.flexBasis, '420px'); + } finally { + globalThis.HTMLElement = previousHTMLElement; + } + + assert.match( + resolverMainCssSource, + /\.mr-missing-browser\.is-list-pinned \.mr-missing-list-pane\s*\{[^}]*flex:\s*0 0 var\(--mr-missing-list-min-width\) !important;/s + ); + assert.match( + resolverMainCssSource, + /\.mr-missing-browser\.is-list-pinned \.mr-missing-detail-pane\s*\{[^}]*flex:\s*1 1 0 !important;/s + ); +}); + test('local and loaded model tooltips include preview image routes above full names', () => { const getModelPreviewTooltipAttrs = eval(`(${extractMethod(renderFormatMethodsSource, 'getModelPreviewTooltipAttrs')})`); const previousApi = globalThis.api; diff --git a/web/css/resolver-main.css b/web/css/resolver-main.css index b32df72..cdb1053 100644 --- a/web/css/resolver-main.css +++ b/web/css/resolver-main.css @@ -1121,6 +1121,14 @@ background: var(--mr-missing-bg); } +.mr-missing-browser.is-list-pinned .mr-missing-list-pane { + flex: 0 0 var(--mr-missing-list-min-width) !important; +} + +.mr-missing-browser.is-list-pinned .mr-missing-detail-pane { + flex: 1 1 0 !important; +} + .mr-missing-detail-pane .mr-card { margin: 0; padding: 18px; diff --git a/web/resolver/views/missing_browser_methods.js b/web/resolver/views/missing_browser_methods.js index 4eda5d9..1f16b81 100644 --- a/web/resolver/views/missing_browser_methods.js +++ b/web/resolver/views/missing_browser_methods.js @@ -1317,8 +1317,12 @@ export const missingBrowserMethods = { : Number.isFinite(measuredWidth) && measuredWidth > 0 ? this.getMissingBrowserSplitBoundsForWidth(measuredWidth) : this.getMissingBrowserSplitBounds(browser); - const nextWidth = Math.round(Math.max(bounds.min, Math.min(bounds.max, width))); - const detailValue = `${nextWidth}px`; + const requestedWidth = Number.isFinite(Number(width)) ? Number(width) : bounds.min; + const nextWidth = Math.round(Math.max(bounds.min, Math.min(bounds.max, requestedWidth))); + const shouldPinList = requestedWidth > bounds.max; + const wasListPinned = browser.classList.contains('is-list-pinned'); + browser.classList.toggle('is-list-pinned', shouldPinList); + const detailValue = `${Math.round(shouldPinList ? requestedWidth : nextWidth)}px`; const listValue = '0px'; if ( @@ -1330,7 +1334,7 @@ export const missingBrowserMethods = { && listPane.style.flexShrink === '1' )) ) { - return false; + return wasListPinned !== shouldPinList; } if (listPane instanceof HTMLElement) { @@ -1356,6 +1360,7 @@ export const missingBrowserMethods = { pane.style.removeProperty('flex-grow'); pane.style.removeProperty('flex-shrink'); }); + browser.classList.remove('is-list-pinned'); detailPane?.style?.removeProperty('--mr-missing-detail-track'); }, @@ -1414,7 +1419,7 @@ export const missingBrowserMethods = { const nextWidth = Math.round(Math.max(bounds.min, Math.min(bounds.max, width))); const detailPane = browser.querySelector('.mr-missing-detail-pane'); const target = detailPane instanceof HTMLElement ? detailPane : browser; - this.applyMissingBrowserDetailWidth(target, nextWidth, { + this.applyMissingBrowserDetailWidth(target, width, { browserWidth: Number.isFinite(Number(browserWidth)) && Number(browserWidth) > 0 ? Number(browserWidth) : null,