CyGuy8 Claude Fable 5 commited on
Commit
c11d374
·
1 Parent(s): f97d164

Fix the Shape Settings table blink on re-renders

Browse files

The ghost-hiding/group-tinting pass ran on a 50 ms debounce, so every
table write-back painted the raw duplicated, unstyled rows first and
snapped back a few frames later - a random quick blink on any event
chain that touches the table.

- Run the pass SYNCHRONOUSLY in the MutationObserver callback: observer
callbacks are microtasks that execute before the browser paints the
mutations that fired them, so ghosts are hidden and tints applied
pre-paint and a re-render can never flash. The debounced pass stays as
a trailing safety net.
- Decide row visibility once at the end of each pass instead of
blanket-unhiding every row first, so a mid-render pass whose live twin
is momentarily absent cannot repaint an already-hidden ghost.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +19 -7
app.py CHANGED
@@ -650,13 +650,15 @@ APP_HEAD = """
650
  var entries = [];
651
  Array.prototype.slice.call(container.querySelectorAll('table tbody tr')).forEach(function (tr) {
652
  var tds = tr.querySelectorAll('td');
653
- tr.style.display = '';
654
  for (var i = 0; i < tds.length; i++) {
655
  tds[i].style.background = '';
656
  if (i === 0 || i === PRESSURE_COL || i === PORT_COL) tds[i].style.boxShadow = '';
657
  if (i === VALVE_COL || i === PRESSURE_COL || i === PORT_COL) tds[i].removeAttribute('title');
658
  }
659
  tr.removeAttribute('title');
 
 
 
660
  if (tds.length < COLUMNS) return;
661
  var idx = cellNumber(tds[0]);
662
  if (idx === null) return;
@@ -684,13 +686,13 @@ APP_HEAD = """
684
  entry.copies.push({tr: tr, tds: tds, live: fromLive});
685
  });
686
  // Ghost pass: when a Shape number has a LIVE row, any stale copy of
687
- // it in the outer table is a render leftover — hide it entirely.
688
- // (Shape numbers with no live row are left alone: hiding on a guess
689
- // could blank a legitimately rendered table.)
690
  entries.forEach(function (entry) {
691
- if (!entry.fromLive) return;
692
  entry.copies.forEach(function (copy) {
693
- if (!copy.live) copy.tr.style.display = 'none';
 
694
  });
695
  });
696
  var byNozzle = {}, byValve = {}, byPort = {};
@@ -787,7 +789,17 @@ APP_HEAD = """
787
  // container node when it hydrates, which would orphan the observer.
788
  // Style writes are attribute mutations and the note rewrite is
789
  // guarded, so observing childList/characterData cannot loop.
790
- new MutationObserver(schedule).observe(document.body, {childList: true, subtree: true, characterData: true});
 
 
 
 
 
 
 
 
 
 
791
  schedule();
792
  }
793
  if (document.readyState === 'loading') {
 
650
  var entries = [];
651
  Array.prototype.slice.call(container.querySelectorAll('table tbody tr')).forEach(function (tr) {
652
  var tds = tr.querySelectorAll('td');
 
653
  for (var i = 0; i < tds.length; i++) {
654
  tds[i].style.background = '';
655
  if (i === 0 || i === PRESSURE_COL || i === PORT_COL) tds[i].style.boxShadow = '';
656
  if (i === VALVE_COL || i === PRESSURE_COL || i === PORT_COL) tds[i].removeAttribute('title');
657
  }
658
  tr.removeAttribute('title');
659
+ // Visibility is decided ONCE at the end of the pass (below) —
660
+ // never blanket-unhidden here: a mid-render pass whose live twin
661
+ // is momentarily absent must not repaint a hidden ghost.
662
  if (tds.length < COLUMNS) return;
663
  var idx = cellNumber(tds[0]);
664
  if (idx === null) return;
 
686
  entry.copies.push({tr: tr, tds: tds, live: fromLive});
687
  });
688
  // Ghost pass: when a Shape number has a LIVE row, any stale copy of
689
+ // it in the outer table is a render leftover — hide it; every other
690
+ // recognized row is explicitly shown (it may have been a ghost in a
691
+ // previous render). Unrecognized rows keep their current state.
692
  entries.forEach(function (entry) {
 
693
  entry.copies.forEach(function (copy) {
694
+ var hide = entry.fromLive && !copy.live;
695
+ copy.tr.style.display = hide ? 'none' : '';
696
  });
697
  });
698
  var byNozzle = {}, byValve = {}, byPort = {};
 
789
  // container node when it hydrates, which would orphan the observer.
790
  // Style writes are attribute mutations and the note rewrite is
791
  // guarded, so observing childList/characterData cannot loop.
792
+ new MutationObserver(function () {
793
+ // Refresh SYNCHRONOUSLY: observer callbacks are microtasks that
794
+ // run BEFORE the browser paints the mutations that fired them,
795
+ // so ghost rows are hidden and group tints applied pre-paint —
796
+ // a table re-render never visibly flashes duplicated/unstyled
797
+ // rows. (A delayed pass here made every table write-back blink
798
+ // for ~50 ms.) The trailing debounced pass still runs to catch
799
+ // whatever an in-flight render finishes after this batch.
800
+ refresh();
801
+ schedule();
802
+ }).observe(document.body, {childList: true, subtree: true, characterData: true});
803
  schedule();
804
  }
805
  if (document.readyState === 'loading') {