← back to Commercialrealestate
Fix valley-pools renderer crash (filter-modal infinite-loop) + TV.refresh typo
685e76b0947fa2d761b307eef6012f8029b1ee7e · 2026-08-19 13:05:52 -0700 · Steve
filter-modal.js: recount() wrote pc.textContent unconditionally, but its own
MutationObserver watches the rail subtree for childList changes — a redundant
textContent write still emits a mutation record, re-firing the observer →
recount() → write → OBSERVER → an infinite loop that OOM-crashes the renderer.
Only triggered on pages whose filters start with a value (valley-pools ships
pre-filled price/rate/down), which is why 34 other pages were fine. Fix: make
recount() write only on real change (idempotent) so the observer settles.
valley-pools.html: TV.refresh() -> TV.render() (three-view exposes render, not refresh).
Verified: valley-pools renders (all 148 cards) + map opens (148 markers/price tags,
tiles load); deals-flow (also uses filter-modal) unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M public/filter-modal.jsM public/valley-pools.html
Diff
commit 685e76b0947fa2d761b307eef6012f8029b1ee7e
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Aug 19 13:05:52 2026 -0700
Fix valley-pools renderer crash (filter-modal infinite-loop) + TV.refresh typo
filter-modal.js: recount() wrote pc.textContent unconditionally, but its own
MutationObserver watches the rail subtree for childList changes — a redundant
textContent write still emits a mutation record, re-firing the observer →
recount() → write → OBSERVER → an infinite loop that OOM-crashes the renderer.
Only triggered on pages whose filters start with a value (valley-pools ships
pre-filled price/rate/down), which is why 34 other pages were fine. Fix: make
recount() write only on real change (idempotent) so the observer settles.
valley-pools.html: TV.refresh() -> TV.render() (three-view exposes render, not refresh).
Verified: valley-pools renders (all 148 cards) + map opens (148 markers/price tags,
tiles load); deals-flow (also uses filter-modal) unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
public/filter-modal.js | 15 +++++++++++++--
public/valley-pools.html | 2 +-
2 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/public/filter-modal.js b/public/filter-modal.js
index 187c62e..bf9fd6b 100644
--- a/public/filter-modal.js
+++ b/public/filter-modal.js
@@ -109,11 +109,22 @@
function recount() {
var total = countActive(rail);
var badge = trig.querySelector('.fm-badge');
- badge.textContent = total; badge.hidden = total === 0;
+ // idempotent writes only: the MutationObserver below watches the rail's subtree for
+ // childList/class changes, and .fm-pcount lives INSIDE the rail — so a *redundant*
+ // textContent write (which still emits a childList mutation record even when the value
+ // is unchanged) would re-fire the observer → recount() → write → observer → an INFINITE
+ // loop that OOM-crashes the renderer on any page whose filters start with a value
+ // (e.g. valley-pools' pre-filled price/rate/down). Guarding every write so recount()
+ // mutates nothing when the counts are stable makes the observer settle after one pass.
+ var bt = String(total);
+ if (badge.textContent !== bt) badge.textContent = bt;
+ if (badge.hidden !== (total === 0)) badge.hidden = total === 0;
trig.classList.toggle('fm-has', total > 0);
secs.forEach(function (sec) {
var pc = sec.querySelector('.fm-pcount'); if (!pc) return;
- var c = countActive(sec); pc.textContent = c ? c : ''; pc.classList.toggle('on', c > 0);
+ var c = countActive(sec), want = c ? String(c) : '';
+ if (pc.textContent !== want) pc.textContent = want; // write only on real change → no self-trigger
+ pc.classList.toggle('on', c > 0); // toggle(force) is a no-op when already correct
});
}
function clearAll() {
diff --git a/public/valley-pools.html b/public/valley-pools.html
index eccfc19..fefeffe 100644
--- a/public/valley-pools.html
+++ b/public/valley-pools.html
@@ -300,7 +300,7 @@ function render(){
`<div class="stat value"><b>${nv}</b><span>🟢 value</span></div>`+
`<div class="stat cut"><b>${nc}</b><span>🟠 cut</span></div>`;
$('#count').textContent=rows.length+' of '+DATA.length;
- if(TV)TV.refresh();else{renderGrid(rows);renderTable(rows);}
+ if(TV)TV.render();else{renderGrid(rows);renderTable(rows);}
if(document.body.classList.contains('mapmode'))renderMap(rows);
}
← cf9fb57 auto-data-snapshot: 2026-08-19T12:54:53 (1 data files) — dat
·
back to Commercialrealestate
·
auto-data-snapshot: 2026-08-19T13:27:48 (2 data files) — dat f750029 →