fix(terminal): init all tabs on load, fix excessive zoom
All checks were successful
Beta Release / beta (push) Successful in 46s

Use visibility:hidden instead of display:none for inactive terminal tabs
so xterm containers retain their dimensions. This allows all terminals
to initialize independently and prevents fitAddon from miscalculating
cell sizes on zero-height containers.

💘 Generated with Crush

Assisted-by: GLM-5.1 via Crush <crush@charm.land>
This commit is contained in:
Augustin
2026-04-24 20:13:21 +02:00
parent 3cf701b002
commit 13e937a11b
2 changed files with 35 additions and 27 deletions

View File

@@ -399,10 +399,7 @@ export default function Shell({ api }) {
}) })
const onResize = () => { const onResize = () => {
const el = document.getElementById(`terminal-${tabId}`) fitAddon.fit()
if (el && el.style.display !== 'none') {
fitAddon.fit()
}
} }
const resizeObserver = new ResizeObserver(onResize) const resizeObserver = new ResizeObserver(onResize)
@@ -429,27 +426,23 @@ export default function Shell({ api }) {
}, []) }, [])
useEffect(() => { useEffect(() => {
const tab = tabs.find(t => t.id === activeTab)
if (!tab) return
let cancelled = false let cancelled = false
const pending = [] const pending = []
const tryInit = (attempt) => { const tryInitTab = (tab, attempt) => {
if (cancelled || attempt > 20) return if (cancelled || attempt > 30) return
const shellCol = document.querySelector('.shell-terminal-col') const shellCol = document.querySelector('.shell-terminal-col')
if (!shellCol || shellCol.offsetParent === null) { if (!shellCol || shellCol.offsetParent === null) {
pending.push(setTimeout(() => tryInit(attempt + 1), 150)) pending.push(setTimeout(() => tryInitTab(tab, attempt + 1), 150))
return return
} }
const container = document.getElementById(`terminal-${tab.id}`) const container = document.getElementById(`terminal-${tab.id}`)
if (!container) { if (!container) {
pending.push(setTimeout(() => tryInit(attempt + 1), 100)) pending.push(setTimeout(() => tryInitTab(tab, attempt + 1), 100))
return return
} }
if (activeTabRef.current !== tab.id) return if (container.offsetHeight === 0) {
if (container.offsetHeight === 0 || container.style.display === 'none') { pending.push(setTimeout(() => tryInitTab(tab, attempt + 1), 100))
pending.push(setTimeout(() => tryInit(attempt + 1), 100))
return return
} }
if (!tabsRef.current[tab.id]) { if (!tabsRef.current[tab.id]) {
@@ -457,29 +450,39 @@ export default function Shell({ api }) {
} }
requestAnimationFrame(() => { requestAnimationFrame(() => {
if (cancelled) return if (cancelled) return
if (activeTabRef.current !== tab.id) return
const entry = tabsRef.current[tab.id] const entry = tabsRef.current[tab.id]
if (entry) entry.fitAddon.fit() if (entry) entry.fitAddon.fit()
}) })
} }
tryInit(0) for (const tab of tabs) {
if (!tabsRef.current[tab.id]) {
tryInitTab(tab, 0)
}
}
return () => { return () => {
cancelled = true cancelled = true
pending.forEach(clearTimeout) pending.forEach(clearTimeout)
} }
}, [activeTab, initTerminal]) }, [tabs, initTerminal])
useEffect(() => {
const entry = tabsRef.current[activeTab]
if (entry) {
requestAnimationFrame(() => {
if (activeTabRef.current === activeTab) {
entry.fitAddon.fit()
}
})
}
}, [activeTab])
useEffect(() => { useEffect(() => {
const iv = setInterval(() => { const iv = setInterval(() => {
for (const tab of tabs) { const entry = tabsRef.current[activeTabRef.current]
const entry = tabsRef.current[tab.id] if (entry) {
if (entry) { entry.fitAddon.fit()
const el = document.getElementById(`terminal-${tab.id}`)
if (el && el.style.display !== 'none') {
entry.fitAddon.fit()
}
}
} }
}, 2000) }, 2000)
return () => clearInterval(iv) return () => clearInterval(iv)
@@ -844,8 +847,7 @@ export default function Shell({ api }) {
<div <div
key={tab.id} key={tab.id}
id={`terminal-${tab.id}`} id={`terminal-${tab.id}`}
className="shell-xterm-instance" className={`shell-xterm-instance${activeTab === tab.id ? ' active' : ''}`}
style={{ display: activeTab === tab.id ? 'block' : 'none' }}
/> />
))} ))}
</div> </div>

View File

@@ -386,6 +386,12 @@ input::placeholder { color: var(--text-disabled); }
.shell-xterm-instance { .shell-xterm-instance {
position: absolute; position: absolute;
inset: 0; inset: 0;
visibility: hidden;
pointer-events: none;
}
.shell-xterm-instance.active {
visibility: visible;
pointer-events: auto;
} }
.shell-xterm-instance .xterm { height: 100%; } .shell-xterm-instance .xterm { height: 100%; }