fix(log-viewer): mark a row the renderer hands back without rebuilding it - #981
Merged
Merged
Conversation
…g it The mark reached a row two ways: a sweep of what was attached when it moved, and the row formatter as a row is built. Neither reaches a row built earlier, detached when scrolled out, and only then named: the renderer re-attaches such a row without running the formatter. A MutationObserver on the row-holding element sweeps again as rows arrive, so a scroll and a sort are one case, and a table rebuilt in place is watched again rather than going quiet.
lcottercertinia
approved these changes
Sep 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📝 PR Overview
The inspector's row mark reached a row two ways: a sweep of the rows attached when the mark moved, and the row formatter as a row is built. Neither reaches a row that was built earlier, detached when it scrolled out of view, and only then named by a mark. The renderer re-attaches such a row without re-running the formatter, so it comes back with no mark. Scroll to it, or sort with it just off screen, and the highlight is simply missing.
The fix watches the arrival: a
MutationObserverforchildListon the element the renderer attaches rows to. A row entering the table is a child mutation, so a scroll and a structural render are one case, and a table destroyed and rebuilt into the same container is watched again rather than going quiet.🛠️ Changes made
watchRenders(host)— one observer per marked table, established on the first non-empty mark. The callback re-sweeps only while a mark is set, and touches nothing but a class, so it cannot report itself back.CallStackDetaildoes it on every event change. Keying on the container would leave the watch pointing at a destroyed table, and the old observer is disconnected when a new one takes over.🧩 Type of change (check all applicable)
📷 Screenshots / gifs / video [optional]
N/A. What changes is whether a row carries its highlight when the grid hands it back.
🔗 Related Issues
Completes the row mark from #975 and #980.
✅ Tests added?
Two cases: a row named while detached, and a table rebuilt into the same container. Each guard was proven by reverting the code it covers — no watch fails both; watching the spacer's
stylerather than the arrival fails both; keying so a rebuild is skipped fails the second.📚 Docs updated?
No entry: this corrects the unreleased Inspector, so it belongs to its existing entry. Ticked to record that it was considered.
Anything else we need to know? [optional]
Why not the signals that look more natural. Two were tried and rejected with evidence:
Cost. The observer fires when rows enter or leave, which is exactly when work is due, and only while a mark is set. A sweep reads what the table has attached — the viewport plus at most `OVERSCAN_MAX` rows each side — never the row count, and the renderer's idle prewarm builds cells with `inFragment: true` so it does not widen that. The callback runs in a microtask after the render, and touches only `classList`, so it forces no layout.
Test plan.
Known unrelated failure locally. `lana/src/services/tests/servicesRuntime.test.ts` cannot resolve `effect` in a worktree not installed since #951. It passes in CI.
Follow-up, not in this PR. The renderer already knows which rows it attached: `allAttached` is built in `_attachRanges`. Dispatching that would make relighting O(rows attached) rather than O(rows rendered), and passing the `Tabulator` to `mark()` instead of its element — every one of the eight owners already holds it — would add disposal on `tableDestroyed` and let `Find` drop its own scroll listener onto the same event.