From f1a98e34b3541edcc0b149a4ad154220769a645e Mon Sep 17 00:00:00 2001 From: Qing <44231502+byemaxx@users.noreply.github.com> Date: Tue, 6 Jan 2026 19:00:08 -0500 Subject: [PATCH 01/13] Fix matrix layout and export cropping --- css/comparison.css | 65 ++++++++++++++++-------- js/renderers/comparison-renderer.js | 76 +++++++++++++++++++++++++---- js/utils/export-tools.js | 54 +++++++++++++++++--- 3 files changed, 157 insertions(+), 38 deletions(-) diff --git a/css/comparison.css b/css/comparison.css index 38d8c18..4fb334c 100644 --- a/css/comparison.css +++ b/css/comparison.css @@ -5,19 +5,35 @@ */ /* ========== 比较矩阵容器 ========== */ +/* Wrapper to handle full-width layout */ +.comparison-matrix-wrapper { + width: calc(100% + 2 * var(--spacing-xl)); + margin: 0 calc(0px - var(--spacing-xl)); + display: flex; + flex-direction: column; + /* Default: Center the matrix when it fits in the screen */ + align-items: center; +} + +/* When the matrix overflows the screen, JS adds this class to switch alignment */ +.comparison-matrix-wrapper.align-left { + align-items: flex-start; +} + .comparison-matrix-container { - /* Allow the matrix to scroll horizontally on smaller screens while - still centering the content when space permits. */ - width: 100%; - max-width: 100%; - margin: var(--spacing-lg) auto; + /* Container fits its content */ + width: fit-content; + max-width: none; + /* Margin for vertical spacing only; horizontal alignment handled by wrapper */ + margin: var(--spacing-lg) 0; + padding: clamp(var(--spacing-lg), 3vw, var(--spacing-xl)); background: var(--bg-light); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); border: 1px solid var(--border-color); - overflow-x: auto; - overscroll-behavior-x: contain; + box-sizing: border-box; + overflow-x: visible; } .matrix-title { @@ -30,14 +46,16 @@ background: var(--bg-white); border-radius: var(--radius-md); border: 1px solid var(--border-color); + /* Ensure title limits width to container */ + max-width: 100%; } /* ========== 矩阵网格 ========== */ .comparison-matrix-grid { display: grid; gap: var(--spacing-sm); - /* remove large vertical margins that can cause visual overflow; center horizontally */ - margin: 0 auto; + /* Margin no longer needed for centering, handled by wrapper flex */ + margin: 0; background: var(--bg-white); padding: var(--spacing-md); border-radius: var(--radius-lg); @@ -103,7 +121,7 @@ } /* Hide the corner spacer cell so it doesn’t clash with theme backgrounds */ -.comparison-matrix-grid > .matrix-cell.empty-cell:first-child { +.comparison-matrix-grid>.matrix-cell.empty-cell:first-child { background: transparent; border: none; box-shadow: none; @@ -143,7 +161,8 @@ .matrix-col-label { height: var(--matrix-label-size, 40px); line-height: var(--matrix-label-size, 40px); - writing-mode: horizontal-tb; /* keep column labels horizontal */ + writing-mode: horizontal-tb; + /* keep column labels horizontal */ } /* Row labels: make them match the column label size (width) and display @@ -341,8 +360,13 @@ } @keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } + from { + opacity: 0; + } + + to { + opacity: 1; + } } .group-modal-content { @@ -364,6 +388,7 @@ transform: translateY(-50px); opacity: 0; } + to { transform: translateY(0); opacity: 1; @@ -503,11 +528,11 @@ .comparison-matrix-grid { gap: var(--spacing-sm); } - + .matrix-cell { min-height: 150px; } - + .matrix-row-label, .matrix-col-label { font-size: var(--font-xs); @@ -519,26 +544,26 @@ .comparison-matrix-container { padding: 10px; } - + .matrix-title { font-size: var(--font-xl); } - + .comparison-matrix-grid { gap: var(--spacing-xs); padding: 10px; } - + .matrix-cell { min-height: 120px; } - + .group-modal-content { width: 95%; margin: 10% auto; padding: var(--spacing-xl); } - + .group-modal-header { font-size: var(--font-2xl); } diff --git a/js/renderers/comparison-renderer.js b/js/renderers/comparison-renderer.js index 32ef21f..f120cf0 100644 --- a/js/renderers/comparison-renderer.js +++ b/js/renderers/comparison-renderer.js @@ -338,9 +338,9 @@ if (pSort && pSort !== 'none') { root.sort((a, b) => { if (pSort === 'name') return d3.ascending(a.data.name, b.data.name); - const valA = (typeof a._agg === 'number') ? a._agg : (a.value || 0); - const valB = (typeof b._agg === 'number') ? b._agg : (b.value || 0); - return pSort === 'value-asc' ? valA - valB : valB - valA; + const valA = (typeof a._agg === 'number') ? a._agg : (a.value || 0); + const valB = (typeof b._agg === 'number') ? b._agg : (b.value || 0); + return pSort === 'value-asc' ? valA - valB : valB - valA; }); } @@ -1254,16 +1254,18 @@ const treatments = [...new Set(comparisons.flatMap(c => [c.treatment_1, c.treatment_2]))]; const n = treatments.length; + + // Create a wrapper to handle the breakout layout (negative margins) and centering + const matrixWrapper = document.createElement('div'); + matrixWrapper.className = 'comparison-matrix-wrapper'; + const matrixContainer = document.createElement('div'); matrixContainer.className = 'comparison-matrix-container'; matrixContainer.id = 'comparison-matrix'; // Make the matrix container size to its inner grid so exports capture full width. try { - // set inline width to max-content to ensure render/export code sees correct size - matrixContainer.style.width = 'max-content'; - matrixContainer.style.maxWidth = 'none'; // keep centering via margin (CSS also sets this) - matrixContainer.style.margin = 'var(--spacing-lg) auto'; + matrixContainer.style.margin = 'var(--spacing-lg) 0'; } catch (_) { } const title = document.createElement('div'); @@ -1271,6 +1273,9 @@ title.textContent = `Treatment Comparison Matrix (${n} groups, ${comparisons.length} comparisons)`; matrixContainer.appendChild(title); + // Append container to wrapper + matrixWrapper.appendChild(matrixContainer); + const matrixGrid = document.createElement('div'); matrixGrid.className = 'comparison-matrix-grid'; matrixGrid.id = 'comparison-matrix-grid'; @@ -1296,7 +1301,7 @@ label.style.zIndex = '1'; matrixGrid.appendChild(label); } - + const cornerCell = createEmptyCell(); cornerCell.style.pointerEvents = 'none'; cornerCell.style.zIndex = '0'; @@ -1327,7 +1332,7 @@ matrixGrid.appendChild(createEmptyCell()); } } - + // Append row label at the end of the row matrixGrid.appendChild(rowLabel); } @@ -1340,7 +1345,58 @@ matrixContainer.appendChild(legend); } - vizContainer.appendChild(matrixContainer); + vizContainer.appendChild(matrixWrapper); + + // Adaptive Alignment Logic: + // Center the matrix when it fits in the screen (default). + // Align left (flex-start) when it overflows to avoid left-side clipping. + const resizeObserver = new ResizeObserver(() => { + if (!matrixContainer || !matrixWrapper) return; + const containerWidth = matrixContainer.offsetWidth; + // IMPORTANT: use the actual width of the visualization area (right pane), + // not window.innerWidth. Otherwise, when the sidebar is visible (or DevTools + // shrinks the viewport), we may incorrectly keep centering and the left side + // gets hidden behind the sidebar due to z-index stacking. + let availableWidth = 0; + try { + availableWidth = (vizContainer && vizContainer.getBoundingClientRect) + ? vizContainer.getBoundingClientRect().width + : 0; + } catch (_) { availableWidth = 0; } + if (!availableWidth || !Number.isFinite(availableWidth)) { + try { + availableWidth = matrixWrapper.parentElement + ? matrixWrapper.parentElement.getBoundingClientRect().width + : 0; + } catch (_) { availableWidth = 0; } + } + if (!availableWidth || !Number.isFinite(availableWidth)) { + availableWidth = window.innerWidth; + } + + // Alignment policy: + // - Sidebar visible: prefer left alignment when the matrix is narrow to avoid + // a large empty band on the left (esp. when users shrink panel width). + // - Sidebar collapsed: centering is usually preferable; only left-align when + // overflowing. + const appBodyEl = document.querySelector('.app-body'); + const sidebarCollapsed = !!(appBodyEl && appBodyEl.classList && appBodyEl.classList.contains('sidebar-collapsed')); + + const fillRatio = availableWidth > 0 ? (containerWidth / availableWidth) : 1; + const shouldAlignLeft = sidebarCollapsed + ? (containerWidth > availableWidth) + : ((containerWidth > availableWidth) || (fillRatio < 0.92)); + + if (shouldAlignLeft) { + matrixWrapper.classList.add('align-left'); + } else { + matrixWrapper.classList.remove('align-left'); + } + }); + resizeObserver.observe(matrixContainer); + // Also observe body to react to window resize (indirectly via reflow) + resizeObserver.observe(document.body); + if (typeof window.requestLayoutPanelContextSync === 'function') { try { window.requestLayoutPanelContextSync(); } catch (_) { } } diff --git a/js/utils/export-tools.js b/js/utils/export-tools.js index c0c3c51..91abe65 100644 --- a/js/utils/export-tools.js +++ b/js/utils/export-tools.js @@ -340,13 +340,26 @@ } catch (_) {} const rect = el.getBoundingClientRect(); - const baseWidth = Math.max(rect.width || 0, el.scrollWidth || 0, el.offsetWidth || 0, 1); + // Width strategy: + // - default: include rect.width so exports preserve on-screen container sizing + // - shrink-to-content: prefer intrinsic content width to avoid exporting large + // empty margins (common for centered layouts like the comparison matrix) + let baseWidth; + if (opts && opts.widthStrategy === 'shrink-to-content') { + baseWidth = Math.max(el.scrollWidth || 0, el.offsetWidth || 0, 1); + if (!baseWidth || !Number.isFinite(baseWidth)) baseWidth = Math.max(rect.width || 0, 1); + } else { + baseWidth = Math.max(rect.width || 0, el.scrollWidth || 0, el.offsetWidth || 0, 1); + } const clone = el.cloneNode(true); + const shrinkToContent = !!(opts && opts.widthStrategy === 'shrink-to-content'); clone.style.margin = '0'; clone.style.boxSizing = 'border-box'; clone.style.overflow = 'visible'; - clone.style.width = baseWidth + 'px'; + // Default snapshot keeps the on-screen container width; for shrink-to-content + // (matrix exports) we let the element wrap to its intrinsic content width. + clone.style.width = shrinkToContent ? 'fit-content' : (baseWidth + 'px'); clone.style.maxWidth = 'none'; clone.style.maxHeight = 'none'; @@ -375,8 +388,9 @@ wrapper.style.margin = '0'; wrapper.style.padding = '0'; wrapper.style.boxSizing = 'border-box'; - wrapper.style.display = 'block'; - wrapper.style.width = baseWidth + 'px'; + // Use inline-block when shrink-wrapping so bounding rect reflects content. + wrapper.style.display = shrinkToContent ? 'inline-block' : 'block'; + wrapper.style.width = shrinkToContent ? 'fit-content' : (baseWidth + 'px'); wrapper.style.maxWidth = 'none'; wrapper.style.overflow = 'visible'; @@ -426,8 +440,14 @@ wrapper.offsetHeight; const wrapperRect = wrapper.getBoundingClientRect(); - const contentWidth = Math.max(wrapper.scrollWidth || 0, wrapperRect.width || 0, baseWidth, 1); - const contentHeight = Math.max(wrapper.scrollHeight || 0, wrapperRect.height || 0, 1); + // For shrink-to-content: rely on measured bounding box (scrollWidth is at + // least clientWidth and can retain empty space if the host is wide). + const contentWidth = shrinkToContent + ? Math.max(wrapperRect.width || 0, 1) + : Math.max(wrapper.scrollWidth || 0, wrapperRect.width || 0, baseWidth, 1); + const contentHeight = shrinkToContent + ? Math.max(wrapperRect.height || 0, 1) + : Math.max(wrapper.scrollHeight || 0, wrapperRect.height || 0, 1); const width = Math.max(1, Math.round(contentWidth)); const height = Math.max(1, Math.round(contentHeight + 4)); @@ -539,7 +559,24 @@ function buildVizContainerSnapshot() { const vizContainer = (typeof document !== 'undefined') ? document.getElementById('viz-container') : null; if (!vizContainer) return null; - return buildSnapshot(vizContainer, { + + // In matrix mode, exporting the entire viz-container often includes large + // empty margins (because the matrix wrapper may be centered within a wide + // container). Prefer exporting the matrix content container directly. + let exportEl = vizContainer; + let widthStrategy = undefined; + try { + let mode = null; + if (typeof window !== 'undefined' && window.visualizationMode) mode = window.visualizationMode; + else if (typeof visualizationMode !== 'undefined') mode = visualizationMode; + if (mode === 'matrix') { + const matrix = document.getElementById('comparison-matrix'); + if (matrix) exportEl = matrix; + widthStrategy = 'shrink-to-content'; + } + } catch (_) { /* ignore */ } + + return buildSnapshot(exportEl, { removeSelectors: [ '.panel-actions', '.tree-panel-header .btn-back', @@ -549,7 +586,8 @@ '.modal-actions', '[data-export-exclude="1"]' ], - matchBackgroundFrom: vizContainer + matchBackgroundFrom: exportEl, + widthStrategy }); } From aa6362dbd4a973a4504dc6b1f0706f941898a75a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 00:15:27 +0000 Subject: [PATCH 02/13] Initial plan From 8cff17051f765d7169f785a8795e123872d4de27 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 00:15:33 +0000 Subject: [PATCH 03/13] Initial plan From 1adad3198438cc09ddec87a1fa71fbb30345433d Mon Sep 17 00:00:00 2001 From: Qing <44231502+byemaxx@users.noreply.github.com> Date: Tue, 6 Jan 2026 19:15:41 -0500 Subject: [PATCH 04/13] Update js/utils/export-tools.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- js/utils/export-tools.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/utils/export-tools.js b/js/utils/export-tools.js index 91abe65..2626ae7 100644 --- a/js/utils/export-tools.js +++ b/js/utils/export-tools.js @@ -564,7 +564,7 @@ // empty margins (because the matrix wrapper may be centered within a wide // container). Prefer exporting the matrix content container directly. let exportEl = vizContainer; - let widthStrategy = undefined; + let widthStrategy; try { let mode = null; if (typeof window !== 'undefined' && window.visualizationMode) mode = window.visualizationMode; From 1ece4b00b272230e84df876f53f11d2199420a31 Mon Sep 17 00:00:00 2001 From: Qing <44231502+byemaxx@users.noreply.github.com> Date: Tue, 6 Jan 2026 19:15:58 -0500 Subject: [PATCH 05/13] Update js/renderers/comparison-renderer.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- js/renderers/comparison-renderer.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/js/renderers/comparison-renderer.js b/js/renderers/comparison-renderer.js index f120cf0..423a634 100644 --- a/js/renderers/comparison-renderer.js +++ b/js/renderers/comparison-renderer.js @@ -1394,8 +1394,6 @@ } }); resizeObserver.observe(matrixContainer); - // Also observe body to react to window resize (indirectly via reflow) - resizeObserver.observe(document.body); if (typeof window.requestLayoutPanelContextSync === 'function') { try { window.requestLayoutPanelContextSync(); } catch (_) { } From 97127b8d13c97049b1d7c329bcc2fe330b254fd5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 00:16:00 +0000 Subject: [PATCH 06/13] Initial plan From 3f882acbfee5b2513356dffd1bd18906e7d22829 Mon Sep 17 00:00:00 2001 From: Qing <44231502+byemaxx@users.noreply.github.com> Date: Tue, 6 Jan 2026 19:16:09 -0500 Subject: [PATCH 07/13] Update css/comparison.css Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- css/comparison.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/css/comparison.css b/css/comparison.css index 4fb334c..f2db854 100644 --- a/css/comparison.css +++ b/css/comparison.css @@ -121,7 +121,7 @@ } /* Hide the corner spacer cell so it doesn’t clash with theme backgrounds */ -.comparison-matrix-grid>.matrix-cell.empty-cell:first-child { +.comparison-matrix-grid > .matrix-cell.empty-cell:first-child { background: transparent; border: none; box-shadow: none; From 2263d40481a9b0ac976057bba32d25af62387cd8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 00:16:27 +0000 Subject: [PATCH 08/13] Initial plan From 57ba9920f88e93c2486897a798176a1361ebc12e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 00:17:39 +0000 Subject: [PATCH 09/13] Refactor: extract shrinkToContent condition to avoid duplication Co-authored-by: byemaxx <44231502+byemaxx@users.noreply.github.com> --- js/utils/export-tools.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/utils/export-tools.js b/js/utils/export-tools.js index 91abe65..a1e113e 100644 --- a/js/utils/export-tools.js +++ b/js/utils/export-tools.js @@ -344,8 +344,9 @@ // - default: include rect.width so exports preserve on-screen container sizing // - shrink-to-content: prefer intrinsic content width to avoid exporting large // empty margins (common for centered layouts like the comparison matrix) + const shrinkToContent = !!(opts && opts.widthStrategy === 'shrink-to-content'); let baseWidth; - if (opts && opts.widthStrategy === 'shrink-to-content') { + if (shrinkToContent) { baseWidth = Math.max(el.scrollWidth || 0, el.offsetWidth || 0, 1); if (!baseWidth || !Number.isFinite(baseWidth)) baseWidth = Math.max(rect.width || 0, 1); } else { @@ -353,7 +354,6 @@ } const clone = el.cloneNode(true); - const shrinkToContent = !!(opts && opts.widthStrategy === 'shrink-to-content'); clone.style.margin = '0'; clone.style.boxSizing = 'border-box'; clone.style.overflow = 'visible'; From 90676248cbe1776d219b7bde06b8b155dcc64a74 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 00:18:20 +0000 Subject: [PATCH 10/13] Fix baseWidth fallback logic to properly check rect.width Co-authored-by: byemaxx <44231502+byemaxx@users.noreply.github.com> --- js/utils/export-tools.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/js/utils/export-tools.js b/js/utils/export-tools.js index 91abe65..4e1b7dc 100644 --- a/js/utils/export-tools.js +++ b/js/utils/export-tools.js @@ -346,8 +346,14 @@ // empty margins (common for centered layouts like the comparison matrix) let baseWidth; if (opts && opts.widthStrategy === 'shrink-to-content') { - baseWidth = Math.max(el.scrollWidth || 0, el.offsetWidth || 0, 1); - if (!baseWidth || !Number.isFinite(baseWidth)) baseWidth = Math.max(rect.width || 0, 1); + // Prefer intrinsic content width (scrollWidth/offsetWidth) over bounding rect + baseWidth = Math.max(el.scrollWidth || 0, el.offsetWidth || 0); + // If both intrinsic measurements are 0 or invalid, try rect.width + if (!baseWidth || !Number.isFinite(baseWidth)) { + baseWidth = rect.width || 0; + } + // Absolute minimum fallback to prevent 0-width exports + baseWidth = Math.max(baseWidth, 1); } else { baseWidth = Math.max(rect.width || 0, el.scrollWidth || 0, el.offsetWidth || 0, 1); } From 0a6816793310851266f92020c296b6e4d54b1176 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 00:18:41 +0000 Subject: [PATCH 11/13] Extract magic number 0.92 to MATRIX_FILL_RATIO_THRESHOLD constant Co-authored-by: byemaxx <44231502+byemaxx@users.noreply.github.com> --- js/renderers/comparison-renderer.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/js/renderers/comparison-renderer.js b/js/renderers/comparison-renderer.js index 423a634..4ff7116 100644 --- a/js/renderers/comparison-renderer.js +++ b/js/renderers/comparison-renderer.js @@ -15,6 +15,12 @@ const VALID_LAYOUTS = new Set(['radial', 'tree', 'packing']); const PACK_EPSILON = 1e-6; + + // Matrix alignment threshold: when sidebar is visible and the matrix fills less than + // this percentage of available width, left-align it to avoid a large empty band on the left. + // Value of 0.92 (92%) provides a good balance - matrices that are reasonably wide stay + // centered, while narrow matrices align left to improve visual appearance. + const MATRIX_FILL_RATIO_THRESHOLD = 0.92; function resolveReverseColorsFlag() { if (typeof colorSchemeReversed !== 'undefined') return !!colorSchemeReversed; @@ -1385,7 +1391,7 @@ const fillRatio = availableWidth > 0 ? (containerWidth / availableWidth) : 1; const shouldAlignLeft = sidebarCollapsed ? (containerWidth > availableWidth) - : ((containerWidth > availableWidth) || (fillRatio < 0.92)); + : ((containerWidth > availableWidth) || (fillRatio < MATRIX_FILL_RATIO_THRESHOLD)); if (shouldAlignLeft) { matrixWrapper.classList.add('align-left'); From 422c29f4ff10deb69dfea71750ba7b0c964ce055 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 00:20:59 +0000 Subject: [PATCH 12/13] Add ResizeObserver cleanup to prevent memory leaks Co-authored-by: byemaxx <44231502+byemaxx@users.noreply.github.com> --- js/renderers/comparison-renderer.js | 17 +++++++++++++++++ js/state/comparison-renderer-store.js | 19 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/js/renderers/comparison-renderer.js b/js/renderers/comparison-renderer.js index 423a634..d966cc4 100644 --- a/js/renderers/comparison-renderer.js +++ b/js/renderers/comparison-renderer.js @@ -476,6 +476,13 @@ } if (!vizContainer) return; + + // Clean up any existing ResizeObserver before clearing content + const cleanupStore = resolveComparisonRendererStore(); + if (cleanupStore && typeof cleanupStore.disconnectResizeObserver === 'function') { + try { cleanupStore.disconnectResizeObserver(); } catch (_) { } + } + vizContainer.innerHTML = ''; // 包裹面板与顶部栏(仅普通/内联模式使用) @@ -1394,6 +1401,9 @@ } }); resizeObserver.observe(matrixContainer); + + // Store the observer reference for cleanup when view changes + matrixStore.setResizeObserver(resizeObserver); if (typeof window.requestLayoutPanelContextSync === 'function') { try { window.requestLayoutPanelContextSync(); } catch (_) { } @@ -1538,6 +1548,13 @@ : document.getElementById('viz-container'); if (!vizContainer) return; + + // Clean up any existing ResizeObserver before clearing content + const cleanupStore = resolveComparisonRendererStore(); + if (cleanupStore && typeof cleanupStore.disconnectResizeObserver === 'function') { + try { cleanupStore.disconnectResizeObserver(); } catch (_) { } + } + vizContainer.innerHTML = ''; // Remember current focused item so redraws in matrix mode keep this view diff --git a/js/state/comparison-renderer-store.js b/js/state/comparison-renderer-store.js index 5726605..33cfe8e 100644 --- a/js/state/comparison-renderer-store.js +++ b/js/state/comparison-renderer-store.js @@ -7,6 +7,7 @@ let svg = null; let zoom = null; let stats = null; + let resizeObserver = null; return { getSvg() { @@ -25,12 +26,30 @@ svg = null; zoom = null; stats = null; + this.disconnectResizeObserver(); }, getStats() { return stats; }, setStats(value) { stats = value || null; + }, + getResizeObserver() { + return resizeObserver; + }, + setResizeObserver(value) { + this.disconnectResizeObserver(); + resizeObserver = value || null; + }, + disconnectResizeObserver() { + if (resizeObserver) { + try { + resizeObserver.disconnect(); + } catch (e) { + console.warn('Failed to disconnect ResizeObserver:', e); + } + resizeObserver = null; + } } }; })(); From e8edaedcae252a905da2f7ef473a344e22e2dcd8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 00:31:57 +0000 Subject: [PATCH 13/13] Initial plan