Spaces:
Sleeping
Fix the Shape Settings table blink on re-renders
Browse filesThe 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>
|
@@ -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
|
| 688 |
-
//
|
| 689 |
-
//
|
| 690 |
entries.forEach(function (entry) {
|
| 691 |
-
if (!entry.fromLive) return;
|
| 692 |
entry.copies.forEach(function (copy) {
|
| 693 |
-
|
|
|
|
| 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(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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') {
|