fix(terminal): detect shell tab visibility via MutationObserver

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 <crush@charm.land>
This commit is contained in:
Augustin
2026-04-24 20:28:02 +02:00
parent 9fb5aa8dbf
commit 6596d86db6

View File

@@ -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()