fix(missing-browser): keep detail pane available and table-preserving across widths

Remove the brittle is-detail-collapsed responsive behavior from the missing
browser. Replace the timeout-based width handler with requestAnimationFrame
split restoration, clamp the list min width to 340px, and drop the
collapsed-state guards so the detail pane, compositor preview, and Type
column remain usable regardless of container width.
This commit is contained in:
Dariusz L
2026-07-28 13:08:26 +02:00
parent e6c6de1c6f
commit c0b7469b4d
4 changed files with 94 additions and 124 deletions
+74 -29
View File
@@ -329,16 +329,12 @@ test('missing browser splitter keeps a compositor preview between live layout fr
/\.mr-missing-browser\.is-resizing \.mr-missing-(?:list|detail)-pane/
);
assert.doesNotMatch(missingBrowserMethodsSource, /deferMissingBrowserSplitReleaseUi/);
assert.match(missingBrowserMethodsSource, /observedWidth\s*<\s*760/);
assert.match(missingBrowserMethodsSource, /observedWidth\s*<\s*700/);
assert.match(
missingBrowserMethodsSource,
/scheduleMissingBrowserExternalResizeRestore\(browser,\s*observedWidth\)/
);
assert.match(
resolverMainCssSource,
/\.mr-missing-browser\.is-detail-collapsed \.mr-missing-detail-pane\s*\{[^}]*display:\s*none/s
);
assert.doesNotMatch(missingBrowserMethodsSource, /is-detail-collapsed/);
assert.doesNotMatch(resolverMainCssSource, /is-detail-collapsed/);
assert.doesNotMatch(missingBrowserMethodsSource, /startMissingBrowserExternalResizeLock/);
assert.doesNotMatch(resolverMainCssSource, /mr-resolver-external-resizing/);
assert.doesNotMatch(
@@ -423,43 +419,92 @@ test('docked ComfyUI splitter coalesces live layout while its gutter stays respo
assert.doesNotMatch(dialogShellMethodsSource, /mr-resolver-external-resizing/);
});
test('missing browser detail collapse follows container width with hysteresis', () => {
test('missing browser detail remains available at narrow widths', () => {
assert.doesNotMatch(missingBrowserMethodsSource, /updateMissingBrowserDetailCollapse/);
assert.doesNotMatch(missingBrowserMethodsSource, /is-detail-collapsed/);
assert.doesNotMatch(resolverMainCssSource, /is-detail-collapsed/);
});
test('missing browser detail follows external splitter resizing every animation frame', () => {
const scheduleRestore = eval(
`(${extractMethod(missingBrowserMethodsSource, 'scheduleMissingBrowserExternalResizeRestore')})`
);
const previousHTMLElement = globalThis.HTMLElement;
const previousRequestAnimationFrame = globalThis.requestAnimationFrame;
const frames = [];
const restores = [];
class FakeHTMLElement {}
globalThis.HTMLElement = FakeHTMLElement;
globalThis.requestAnimationFrame = callback => {
frames.push(callback);
return frames.length;
};
try {
const classes = new Set();
const browser = new FakeHTMLElement();
browser.classList = {
contains(name) {
return classes.has(name);
},
toggle(name, enabled) {
if (enabled) classes.add(name);
else classes.delete(name);
},
};
browser.isConnected = true;
const context = {
_missingBrowserRestoreFrame: null,
cancelMissingBrowserExternalResizeRestore() {},
_missingBrowserExternalResizeFrame: null,
_pendingMissingBrowserExternalResizeBrowser: null,
_pendingMissingBrowserExternalResizeWidth: null,
_missingBrowserSplitDragging: false,
restoreMissingBrowserSplitWidth(target, options) {
restores.push([target, options]);
},
};
const updateDetailCollapse = eval(
`(${extractMethod(missingBrowserMethodsSource, 'updateMissingBrowserDetailCollapse')})`
);
assert.equal(updateDetailCollapse.call(context, browser, 690), true);
assert.equal(classes.has('is-detail-collapsed'), true);
scheduleRestore.call(context, browser, 620);
scheduleRestore.call(context, browser, 540);
assert.equal(updateDetailCollapse.call(context, browser, 730), false);
assert.equal(classes.has('is-detail-collapsed'), true);
assert.equal(frames.length, 1);
assert.equal(restores.length, 0);
assert.equal(updateDetailCollapse.call(context, browser, 770), true);
assert.equal(classes.has('is-detail-collapsed'), false);
frames.shift()();
assert.equal(restores.length, 1);
assert.equal(restores[0][0], browser);
assert.deepEqual(restores[0][1], {
browserWidth: 540,
useDefault: false,
});
} finally {
globalThis.HTMLElement = previousHTMLElement;
globalThis.requestAnimationFrame = previousRequestAnimationFrame;
}
assert.doesNotMatch(
extractMethod(missingBrowserMethodsSource, 'scheduleMissingBrowserExternalResizeRestore'),
/setTimeout|120/
);
});
test('missing browser splitter preserves the table through the Type column', () => {
const getSplitBounds = eval(
`(${extractMethod(missingBrowserMethodsSource, 'getMissingBrowserSplitBoundsForWidth')})`
);
const regularBounds = getSplitBounds(1000);
const narrowBounds = getSplitBounds(620);
assert.equal(regularBounds.available - regularBounds.max, 340);
assert.equal(narrowBounds.available - narrowBounds.max, 340);
assert.equal(narrowBounds.min, narrowBounds.max);
assert.match(
extractMethod(missingBrowserMethodsSource, 'getMissingBrowserSplitBoundsForWidth'),
/Math\.min\(340,\s*available\)/
);
assert.match(
resolverMainCssSource,
/\.mr-missing-browser\s*\{[^}]*--mr-missing-list-min-width:\s*340px;/s
);
assert.match(
resolverMainCssSource,
/\.mr-missing-list-pane\s*\{[^}]*min-width:\s*var\(--mr-missing-list-min-width\);/s
);
assert.doesNotMatch(
resolverMainCssSource,
/\.mr-missing-row-best,[\s\S]*?\.mr-missing-row-sources\s*\{[^}]*display:\s*none;/
);
});
test('local and loaded model tooltips include preview image routes above full names', () => {
+2 -48
View File
@@ -503,6 +503,7 @@
--mr-missing-line-strong: rgba(255, 255, 255, 0.13);
--mr-missing-radius: 6px;
--mr-missing-splitter-track: 10px;
--mr-missing-list-min-width: 340px;
--mr-missing-list-track: calc(100% - var(--mr-missing-splitter-track) - var(--mr-missing-detail-track, 42%));
position: relative;
display: flex;
@@ -527,6 +528,7 @@
.mr-missing-list-pane {
flex: 1 1 0;
min-width: var(--mr-missing-list-min-width);
display: grid;
grid-template-rows: auto minmax(0, 1fr);
border-right: 0;
@@ -1156,54 +1158,6 @@
box-shadow: none;
}
.mr-missing-browser.is-detail-collapsed .mr-missing-browser-splitter,
.mr-missing-browser.is-detail-collapsed .mr-missing-detail-pane {
display: none;
}
.mr-missing-browser.is-detail-collapsed .mr-missing-list-pane {
flex: 1 1 100% !important;
width: 100%;
border-right: 0;
}
@media (max-width: 760px) {
.mr-missing-list-head {
display: none;
}
#model-resolver-modal .mr-missing-list-row {
grid-template-columns: 24px 18px minmax(0, 1fr);
content-visibility: visible;
contain-intrinsic-size: none;
height: auto;
min-height: 64px;
overflow: visible;
gap: 7px;
}
.mr-missing-row-select {
grid-row: 1 / span 4;
align-self: start;
margin-top: 1px;
}
.mr-missing-row-index {
grid-row: 1 / span 4;
align-self: start;
margin-top: 1px;
text-align: center;
}
.mr-missing-row-model,
.mr-missing-row-type,
.mr-missing-row-best,
.mr-missing-row-match,
.mr-missing-row-sources {
grid-column: 3;
}
}
/* Card Styles */
.mr-card {
background: var(--mr-card-bg);
+1 -1
View File
@@ -73,7 +73,7 @@ export class ResolverManagerDialog extends ComfyDialog {
this.sidebarTabId = "comfyui-model-resolver";
this.sidebarOpenModeStorageKey = "model_resolver_sidebar_open_mode";
this.missingBrowserSplitStorageKey = "model_resolver_missing_browser_detail_w";
this._missingBrowserExternalResizeTimer = null;
this._missingBrowserExternalResizeFrame = null;
this._pendingMissingBrowserExternalResizeBrowser = null;
this._pendingMissingBrowserExternalResizeWidth = null;
this._missingBrowserObservedHostWidth = null;
+17 -46
View File
@@ -975,7 +975,7 @@ export const missingBrowserMethods = {
this.rememberMissingBrowserWidth(observedWidth);
if (!Number.isFinite(previousHostWidth) || previousHostWidth <= 0) {
this.updateMissingBrowserDetailCollapse(browser, observedWidth);
this.scheduleMissingBrowserSplitRestore(browser, observedWidth, { useDefault: false });
return;
}
if (Math.abs(previousHostWidth - observedWidth) < 0.5) return;
@@ -1000,10 +1000,7 @@ export const missingBrowserMethods = {
if (this._missingBrowserSplitDragging) return;
const measuredWidth = Number(browser.clientWidth || browser.getBoundingClientRect().width);
this.rememberMissingBrowserWidth(measuredWidth);
this.updateMissingBrowserDetailCollapse(browser, measuredWidth);
if (!this._missingBrowserSplitDragging && !browser.classList.contains('is-detail-collapsed')) {
this.scheduleMissingBrowserSplitRestore(browser, measuredWidth, { useDefault: false });
}
this.scheduleMissingBrowserSplitRestore(browser, measuredWidth, { useDefault: false });
});
}
},
@@ -1014,51 +1011,24 @@ export const missingBrowserMethods = {
this._missingBrowserPrewarmFrame = null;
},
updateMissingBrowserDetailCollapse(browser, width) {
if (!(browser instanceof HTMLElement)) return false;
const observedWidth = Number(width);
if (!Number.isFinite(observedWidth) || observedWidth <= 0) return false;
const wasCollapsed = browser.classList.contains('is-detail-collapsed');
const shouldCollapse = wasCollapsed
? observedWidth < 760
: observedWidth < 700;
if (shouldCollapse === wasCollapsed) return false;
browser.classList.toggle('is-detail-collapsed', shouldCollapse);
if (shouldCollapse) {
this.cancelMissingBrowserExternalResizeRestore();
if (this._missingBrowserRestoreFrame) {
cancelAnimationFrame(this._missingBrowserRestoreFrame);
this._missingBrowserRestoreFrame = null;
}
this._pendingMissingBrowserRestoreBrowser = null;
this._pendingMissingBrowserRestoreWidth = null;
this._pendingMissingBrowserRestoreUseDefault = false;
}
return true;
},
cancelMissingBrowserExternalResizeRestore() {
if (this._missingBrowserExternalResizeTimer) {
clearTimeout(this._missingBrowserExternalResizeTimer);
if (this._missingBrowserExternalResizeFrame) {
cancelAnimationFrame(this._missingBrowserExternalResizeFrame);
}
this._missingBrowserExternalResizeTimer = null;
this._missingBrowserExternalResizeFrame = null;
this._pendingMissingBrowserExternalResizeBrowser = null;
this._pendingMissingBrowserExternalResizeWidth = null;
},
scheduleMissingBrowserExternalResizeRestore(browser, browserWidth, delay = 120) {
scheduleMissingBrowserExternalResizeRestore(browser, browserWidth) {
if (!(browser instanceof HTMLElement)) return;
this._pendingMissingBrowserExternalResizeBrowser = browser;
this._pendingMissingBrowserExternalResizeWidth = Number(browserWidth) || null;
if (this._missingBrowserExternalResizeTimer) {
clearTimeout(this._missingBrowserExternalResizeTimer);
}
if (this._missingBrowserExternalResizeFrame) return;
this._missingBrowserExternalResizeTimer = setTimeout(() => {
this._missingBrowserExternalResizeTimer = null;
this._missingBrowserExternalResizeFrame = requestAnimationFrame(() => {
this._missingBrowserExternalResizeFrame = null;
const targetBrowser = this._pendingMissingBrowserExternalResizeBrowser;
const targetWidth = this._pendingMissingBrowserExternalResizeWidth;
this._pendingMissingBrowserExternalResizeBrowser = null;
@@ -1070,10 +1040,11 @@ export const missingBrowserMethods = {
) {
return;
}
this.updateMissingBrowserDetailCollapse(targetBrowser, targetWidth);
if (targetBrowser.classList.contains('is-detail-collapsed')) return;
this.scheduleMissingBrowserSplitRestore(targetBrowser, targetWidth, { useDefault: false });
}, Math.max(0, Number(delay) || 0));
this.restoreMissingBrowserSplitWidth(targetBrowser, {
browserWidth: targetWidth,
useDefault: false
});
});
},
getStoredMissingBrowserSplitWidth() {
@@ -1125,7 +1096,7 @@ export const missingBrowserMethods = {
getMissingBrowserSplitBoundsForWidth(width) {
const safeWidth = Math.max(1, Number(width) || 1);
const available = Math.max(1, safeWidth - 10);
const minListWidth = Math.min(360, Math.max(240, Math.floor(available * 0.28)));
const minListWidth = Math.min(340, available);
const max = Math.max(1, available - minListWidth);
const min = Math.min(max, Math.min(420, Math.max(300, Math.floor(available * 0.3))));
@@ -1142,7 +1113,7 @@ export const missingBrowserMethods = {
},
scheduleMissingBrowserSplitRestore(browser, browserWidth = null, { useDefault = false } = {}) {
if (!(browser instanceof HTMLElement) || browser.classList.contains('is-detail-collapsed')) return;
if (!(browser instanceof HTMLElement)) return;
this._pendingMissingBrowserRestoreBrowser = browser;
this._pendingMissingBrowserRestoreWidth = Number.isFinite(Number(browserWidth)) && Number(browserWidth) > 0
@@ -1182,7 +1153,7 @@ export const missingBrowserMethods = {
},
startMissingBrowserSplitDrag(event, browser, splitter = null, panes = {}) {
if (!(browser instanceof HTMLElement) || browser.classList.contains('is-detail-collapsed')) return;
if (!(browser instanceof HTMLElement)) return;
event.preventDefault();
event.stopPropagation?.();