fix: preserve terminal buffer across tab switches and focus regain

Render all terminal sessions and hide inactive ones with display:none so
xterm instances are no longer unmounted/remounted when switching tabs.

Remove terminal.clear() on the connected status message: the server keeps
the session buffer, and clearing was erasing visible content when the tab
regained focus or reconnected.

- Keep TerminalComponent instances alive in both Desktop and Mobile views.
- Only refit/resize on reconnect instead of clearing.

Closes terminal redraw on focus regain.
This commit is contained in:
Developer
2026-06-15 13:11:08 +00:00
parent 3358af57c2
commit a6ad8268b9
4 changed files with 556 additions and 34 deletions
@@ -172,21 +172,25 @@ export const DesktopTerminalView: React.FC<Props> = ({
)}
<div className="terminal-page-content">
{error && <div className="terminal-error-banner">{error}</div>}
{sessions
.filter((session) => session.id === activeSessionId)
.map((session) => (
<div key={session.id} className="terminal-instance active">
<TerminalComponent
ref={terminalRefs.current[session.id]}
instanceId={instanceId}
sessionId={session.id}
onClose={() => onClose(session.id)}
isMobile={false}
showControls={!isFullscreen}
onTerminalReady={onTerminalReady}
/>
</div>
))}
{sessions.map((session) => (
<div
key={session.id}
className={`terminal-instance ${session.id === activeSessionId ? "active" : ""}`}
style={{
display: session.id === activeSessionId ? "flex" : "none",
}}
>
<TerminalComponent
ref={terminalRefs.current[session.id]}
instanceId={instanceId}
sessionId={session.id}
onClose={() => onClose(session.id)}
isMobile={false}
showControls={!isFullscreen}
onTerminalReady={onTerminalReady}
/>
</div>
))}
{sessions.length === 0 && !loading && (
<div className="terminal-empty-state">
<p>No terminal sessions. Press Alt+Shift+N to create one.</p>
@@ -115,23 +115,27 @@ export const MobileTerminalView: React.FC<Props> = ({
onClick={onToggleHeader}
>
{error && <div className="terminal-error-banner">{error}</div>}
{sessions
.filter((session) => session.id === activeSessionId)
.map((session) => (
<div key={session.id} className="terminal-instance active">
<TerminalComponent
ref={terminalRefs.current[session.id]}
instanceId={instanceId}
sessionId={session.id}
onClose={() => onClose(session.id)}
isMobile={true}
showControls={false}
activeModifier={activeModifier}
onModifierChange={onModifierChange}
onTerminalReady={onTerminalReady}
/>
</div>
))}
{sessions.map((session) => (
<div
key={session.id}
className={`terminal-instance ${session.id === activeSessionId ? "active" : ""}`}
style={{
display: session.id === activeSessionId ? "flex" : "none",
}}
>
<TerminalComponent
ref={terminalRefs.current[session.id]}
instanceId={instanceId}
sessionId={session.id}
onClose={() => onClose(session.id)}
isMobile={true}
showControls={false}
activeModifier={activeModifier}
onModifierChange={onModifierChange}
onTerminalReady={onTerminalReady}
/>
</div>
))}
{sessions.length === 0 && !loading && (
<div className="terminal-empty-state">
<p>No terminal sessions. Press Alt+Shift+N to create one.</p>
@@ -174,9 +174,11 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
if (msg.status === "connected") {
setStatus("connected");
setError(null);
// Clear terminal and refit after reset/reconnect
// Refit after reset/reconnect but do NOT clear the
// terminal: the server keeps the session buffer, and
// clearing would erase it when the user switches back
// to an already-connected session.
if (termRef.current) {
termRef.current.clear();
requestAnimationFrame(() => {
if (fitAddonRef.current && termRef.current) {
fitAddonRef.current.fit();