From 6596d86db6cf4a778d900c348f1c8ad0fa1c2eef Mon Sep 17 00:00:00 2001 From: Augustin Date: Fri, 24 Apr 2026 20:28:02 +0200 Subject: [PATCH] fix(terminal): detect shell tab visibility via MutationObserver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shell is always mounted inside a display:none parent when the app loads on a different tab. Added MutationObserver on the wrapper to detect when the shell tab becomes visible and initialize/fit all pending terminals at that moment. Removed attempt limit so retries continue until the tab is actually shown. 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush --- web/src/components/Shell.jsx | 41 +++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/web/src/components/Shell.jsx b/web/src/components/Shell.jsx index efa9521..2ae3c00 100644 --- a/web/src/components/Shell.jsx +++ b/web/src/components/Shell.jsx @@ -425,15 +425,36 @@ export default function Shell({ api }) { } }, []) + const initPendingTabs = useCallback(() => { + for (const tab of tabsRef.current._tabList || []) { + if (!tabsRef.current[tab.id]) { + const container = document.getElementById(`terminal-${tab.id}`) + if (container && container.offsetHeight > 0) { + initTerminal(tab.id, tab) + } + } + } + requestAnimationFrame(() => { + for (const tab of tabsRef.current._tabList || []) { + const entry = tabsRef.current[tab.id] + if (entry) entry.fitAddon.fit() + } + }) + }, [initTerminal]) + + useEffect(() => { + tabsRef.current._tabList = tabs + }, [tabs]) + useEffect(() => { let cancelled = false const pending = [] const tryInitTab = (tab, attempt) => { - if (cancelled || attempt > 30) return + if (cancelled) return const shellCol = document.querySelector('.shell-terminal-col') if (!shellCol || shellCol.offsetParent === null) { - pending.push(setTimeout(() => tryInitTab(tab, attempt + 1), 150)) + pending.push(setTimeout(() => tryInitTab(tab, attempt + 1), 200)) return } const container = document.getElementById(`terminal-${tab.id}`) @@ -461,11 +482,23 @@ export default function Shell({ api }) { } } + const wrapper = document.querySelector('.shell-layout')?.parentElement + let observer + if (wrapper) { + observer = new MutationObserver(() => { + if (!wrapper.classList.contains('tab-hidden') && wrapper.offsetParent !== null) { + initPendingTabs() + } + }) + observer.observe(wrapper, { attributes: true, attributeFilter: ['class'] }) + } + return () => { cancelled = true pending.forEach(clearTimeout) + observer?.disconnect() } - }, [tabs, initTerminal]) + }, [tabs, initTerminal, initPendingTabs]) useEffect(() => { const entry = tabsRef.current[activeTab] @@ -480,6 +513,8 @@ export default function Shell({ api }) { useEffect(() => { const iv = setInterval(() => { + const wrapper = document.querySelector('.shell-layout')?.parentElement + if (wrapper && wrapper.classList.contains('tab-hidden')) return const entry = tabsRef.current[activeTabRef.current] if (entry) { entry.fitAddon.fit()