[object Object]

← 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

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 →