Repository navigation
π dashboard: agents section refresh shifts scroll position - #12209
Conversation
renderAgents rebuilt the grid with innerHTML on every status tick. The
.doing terminal tails of cards in rows above the viewport grew/shrank
with new log lines, pushing the rows the reader was looking at; the
section-level scroll anchor in render() couldn't see it, Chrome's own
scroll anchor was destroyed with the nodes, and Safari has no scroll
anchoring at all.
- Patch cards in place (keyed by data-agent) instead of innerHTML.
- Anchor render() scroll restore on the first visible agent card.
- Follow .doing tails via element scrollTop only when already at bottom.
- focus({ preventScroll: true }) on re-focus.
Closes #12208
Signed-off-by: Andrew Anderson <andy@clubanderson.com>
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
Open PR overlap checkBase branch: Conflicts are checked with every PR merged onto the current tip of
|
Signed-off-by: Andrew Anderson <andy@clubanderson.com>
|
merged by Hive trusted-author auto-merge: author @clubanderson holds role owner and GitHub permission admin on repo; CI green at e8f9e3d. |
|
Thank you for your contribution! Your PR has been merged. We'd love to hear how your experience was: share feedback |
β¦ stable on errors (#12518) The spoke dashboard jumped every SSE tick while scrolled. renderCost rebuilt #cost-panel via innerHTML with its sub-tables expanded, forced layout in applyCostCollapse β replaySparklinesIn, and only then re-collapsed them; Chrome scroll anchoring moved scrollY by the transient delta and rode the 200ms max-height transition back. renderDebugSection and renderAuditTable used the same innerHTML-then-recollapse pattern. - Render collapsed state into markup (sectionCardShell/sectionCardHeader collapsed option, cost sub-table collapsed classes) and patch sections in place with morphInnerHtml (morphDomNode from #12209) so no transient height change occurs; re-apply sub-collapse before the panel collapse. - Derive /api/pane polling from the live agent list: skip disabled/stopped agents and park an agent after a 404 until its state changes, removing the /api/pane/{guide,quality} 404 stream. - Tests for candidate derivation, in-place patching and collapsed markup. Closes #12512 Signed-off-by: Andy Anderson <andy@clubanderson.com>
Closes #12208
Root cause
renderAgents()rebuilt the entire agents grid withel.innerHTML = htmlwhenever any byte changed β i.e. on every SSE status tick. Two consequences moved the content under the reader's viewport:.doingterminal-tail panes change height (min-height: 5.6em; max-height: 30em, content-sized). Each refresh addsliveSummarylines (or removes the pane when the summary is empty), so cards in the rows above the viewport grow/shrink and push the visible rows down/up. The existingcaptureScrollAnchor/restoreScrollAnchorinrender()only anchors on the section's top edge, which doesn't move once you've scrolled into the section, so it corrected nothing.Additionally
prev.focus()ran withoutpreventScroll, so a focused kick-prompt input was scrolled into view on every refresh.Reproduced with Playwright WebKit + Chromium against a locally served
index.html(9-card grid, scrolled so row 3 is first visible, refresh adds lines to the.doingpanes in rows 1β2): the visible card moved by ~97px withwindow.scrollYunchanged.Fix
morphAgentGrid/morphDomNode, keyed bydata-agent): attributes and text nodes are patched, nodes are reordered/inserted/removed, form controls swapped only when their markup changed, JS-set inlinestyle(resize-handle heights) preserved. Card nodes,.agent-card-details/.doingscrollTop, focus and typed input survive a refresh, and the browser's scroll anchor stays valid. Verified with an oracle that the live DOM after morphing across 8 varied payloads (reorder, add/remove cards, status/class/text changes) serialises identically to a cold render.captureScrollAnchorrecords the first agent card still in view (by key) andrestoreScrollAnchorcorrects by that card's delta. This is what fixes Safari..doingauto-follow uses elementscrollToponly, and only when the reader was already at the bottom.focus({ preventScroll: true }).After the fix the same repro keeps the visible card within <1px in both engines across alternating grow/shrink refreshes.
Tests
agents_refresh_scroll_jump_test.go: structural pins onrenderAgents/captureScrollAnchor/restoreScrollAnchor, plus a node-executed behavioural test ofmorphDomNodeagainst a minimal DOM stand-in (node identity kept, text/attrs patched, inline style preserved, form controls swapped only on change).No new inline
style=attributes (style ratchet unaffected). Other sections unchanged.