From 2350f4dc609f615f06ef073fb5b94a110ea5e84d Mon Sep 17 00:00:00 2001 From: HexLab98 Date: Sun, 23 Aug 2026 22:49:57 +0700 Subject: [PATCH] fix(bot-mode): bound the Bots home re-front so the view stops strobing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-fronting the Bots home tab is a close followed by a re-open, which tears down and rebuilds the entire Bots view. `openBotsHomeWorkspace` took that path on EVERY passive reconcile that found the tab open but not holding its zone's active slot, with nothing bounding the retries. That condition is not always transient. `revealTreePane` returns early for a pane in `$hiddenTreePanes` without ever activating it, `isPaneVisible` is false for a minimized zone, and a pane the tree never adopted has no group to be active in. Pinned in any of those states, every signal that reaches a surface sync — sidebar visibility flips, focus churn, group changes — bought one more full remount, and the view visibly strobed. A passive reconcile now gets one attempt. The reveal has already granted or refused the active slot by the time `openWorkspace` returns, so the budget settles on that answer directly instead of waiting for a visibility notification that is not coming: a computed store stays silent when the value does not change. Retiring the tab starts a fresh budget, and an explicit gesture is never blocked. Giving up keeps the surface rather than closing it — a closed home drops the Bots tab through to the ownerless Sessions composer, which is the hole the home exists to plug. --- .../desktop/src/plugins/hermes-bots/plugin.js | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/apps/desktop/src/plugins/hermes-bots/plugin.js b/apps/desktop/src/plugins/hermes-bots/plugin.js index 64baa1c1e3..b0c1c33e02 100644 --- a/apps/desktop/src/plugins/hermes-bots/plugin.js +++ b/apps/desktop/src/plugins/hermes-bots/plugin.js @@ -316,6 +316,10 @@ const $botsHomeFronted = atom(false) let botsHomeClose = null let suppressBotsHomeReopen = false +// Latched while a re-front attempt has not yet been answered with visibility. +// Cleared the moment the home is actually fronted, and whenever the tab is +// retired — a fresh open starts the budget over. See openBotsHomeWorkspace. +let botsHomeRefrontTried = false function saveSelectedRosterBot(bot) { const key = botRosterKey(bot) @@ -13122,6 +13126,10 @@ function closeBotsHomeWorkspace() { const close = botsHomeClose botsHomeClose = null suppressBotsHomeReopen = true + // Retiring the tab ends the current attempt's budget: the next open is a + // new surface, and it gets its own re-front chance. The re-front path sets + // the latch again AFTER calling us, so this cannot erase its own attempt. + botsHomeRefrontTried = false try { close() @@ -13209,10 +13217,27 @@ function openBotsHomeWorkspace(explicit = false) { // and each of those either legitimately claims the center or cleared it. if (botsHomeClose) { if (botsHomeVisible()) { + botsHomeRefrontTried = false + + return true + } + + // A re-front is a close + re-open, so it REMOUNTS the whole Bots view. + // That is affordable once, against the backgrounded-tab case above. It is + // not affordable per signal: the shell does not always answer a reveal + // with the active slot (revealTreePane returns early for a pane in + // $hiddenTreePanes without activating it; a minimized zone and a pane the + // tree never adopted are never visible either). Pinned in one of those + // states the old code re-fronted on every passive pass — sidebar flips, + // focus churn and group changes all reach here — and the view strobed. + // One attempt proves whether this shell will front the tab; if it will + // not, keep the surface and wait for a signal that changes the answer. + if (botsHomeRefrontTried && !explicit) { return true } closeBotsHomeWorkspace() + botsHomeRefrontTried = true } try { @@ -13230,6 +13255,15 @@ function openBotsHomeWorkspace(explicit = false) { } }) + // The reveal has already either granted the tab its zone's active slot or + // refused it, so settle the re-front budget on that answer directly rather + // than waiting for a visibility notification to schedule another pass. A + // computed store stays silent when the value does not change, so on the + // shells that refuse, no such pass is coming. + if (botsHomeVisible()) { + botsHomeRefrontTried = false + } + return typeof botsHomeClose === 'function' } catch { botsHomeClose = null