Repository navigation
fix(desktop): bot faces stop repainting in a hidden Bots tab - #128219
kshitijk4poor wants to merge 3 commits into
Conversation
After the Bots sidebar tab is visited once, its pane stays mounted as a
keep-alive layer hidden with `visibility: hidden`. The shared face clock
only asked an IntersectionObserver which faces were visible, and a hidden
layer keeps its box, so every roster face still counted as intersecting
and was repainted at 15 fps for the rest of the session.
The clock now paints only faces that are intersecting AND pass
`checkVisibility({ visibilityProperty: true })`, and parks when none do.
Re-activating the tab re-renders its BotFaces, which already wakes the
clock. The shown set is computed at the start of each draw (and on
observer callbacks), so the check never forces a mid-frame style recalc.
Re-activating the pane only woke the parked clock because every face happens to re-render on the tab switch; a memoised ancestor would leave its face frozen. The pane-visibility listener now wakes the clock directly. Visibility is also re-checked at the 1Hz rescan instead of on every painted frame: it only changes on a tab switch.
…nder Faces also live in chat tabs (the empty bot chat), group chats and the routines pane, all inside keep-alive tab stacks. Revealing such a tab changes neither the face's intersection nor, under a memoised host, its render, so a clock parked on hidden faces would leave them frozen. While faces intersect but are all hidden, recheck once a second instead of parking for good: one document scan per second instead of main's 15 fps repaint of every hidden face.
29d4d77 to
d3ffe30
Compare
The premise holds and the direction is right — I verified both. The inactive keep-alive tab is hidden with Worth adding before merge: two of the three behaviours this change introduces have no test that goes red when they're removed. Your claims, re-verified. The new test fails on main ( The gap that matters most. Mutating That option is load-bearing. Per MDN, bare Second gap. Deleting the new Not a defect report; the behaviour looks correct. I'd take a follow-up PR for the two assertions as a courtesy rather than a merge blocker. |
Outcome
After the Bots sidebar tab has been opened once, its bot faces stop repainting while the tab is hidden. Before this, every roster face kept animating at 15 fps for the rest of the session behind the Sessions tab.
Measured on isolated dev instances with four bot profiles, re-run on the final head after the rebase onto
7239625ae1. Main and this branch ran back-to-back, twice (A B A B). Each window comes after one visit to Bots and a return to Sessions, 3 windows per run:The remaining ~36 rAF callbacks per 30 s come from the once-a-second recheck that notices a hidden face being shown again (see below). Each recheck is one frame and one document scan.
Before the Bots tab has been opened, both branches run 0 rAF callbacks. With the tab visible, both run 600 per 10 s, and revisiting the tab resumes at 600 per 10 s, so the faces animate exactly as before.
Load average was about 20–30 from other work. The counts don't depend on load; CPU compares fairly because the runs interleave.
Why
Visiting a sidebar tab keeps its pane mounted as a keep-alive layer hidden with
visibility: hidden(#69750,30f6fc81e2, 2026-07-22). The shared face clock decided what to paint from an IntersectionObserver alone. That gate came fromba2fb191c6by @digitalbase, landed via @teknium1's #88543 on 2026-08-17. Avisibility: hiddenlayer keeps its box, so the observer kept reporting every roster face as intersecting, and the clock never parked.What changed
plugins/hermes-bots/avatar.tsx:checkVisibility({ visibilityProperty: true }). It parks when none do.as () => booleancast and its TODO:idleis now a plain boolean.plugins/hermes-bots/plugin.tsx: showing the Bots pane wakes the clock. Before, it only woke because every face happened to re-render on the tab switch.avatar.face-clock.test.ts: one test. A face that intersects but sits in a hidden pane parks the clock, and revealing it with no re-render resumes painting within a second (fake timers). It fails on main, and it fails with the recheck removed.Verification
npx vitest run src/plugins/hermes-bots: 93 files, 754 tests pass. On this loaded machine,bot-delete.test.tsandhidden-bots.test.tsoverrun their 60 s cold-importbeforeAllin a full-directory run. Main fails them the same way in the same run, and both pass with the budget raised; they take about 95 s on each branch.avatar.tsxand passes with this change.