<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/keeping-a-web-backed-ssh-terminal-alive-across-swiftui-navigation-svsgl6knu" -->

---
title: Keeping a Web-Backed SSH Terminal Alive Across SwiftUI...
description: Building a macOS SSH client that embeds xterm.js inside WKWebView surfaces a subtle problem: naive SwiftUI navigation destroys and rebuilds the terminal...
canonical: https://daily.dev/posts/keeping-a-web-backed-ssh-terminal-alive-across-swiftui-navigation-svsgl6knu
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Keeping a Web-Backed SSH Terminal Alive Across SwiftUI Navigation | daily.dev
og:description: Building a macOS SSH client that embeds xterm.js inside WKWebView surfaces a subtle problem: naive SwiftUI navigation destroys and rebuilds the terminal...
og:url: https://daily.dev/posts/keeping-a-web-backed-ssh-terminal-alive-across-swiftui-navigation-svsgl6knu
og:image: https://api.daily.dev/og/posts/SVsGl6Knu.png
og:image:alt: Keeping a Web-Backed SSH Terminal Alive Across SwiftUI Navigation
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Keeping a Web-Backed SSH Terminal Alive Across SwiftUI Navigation

**[Playful Programming](https://daily.dev/sources/playfulprogramming)** · 7 min read · 0 upvotes · 0 comments

## Summary

Building a macOS SSH client that embeds xterm.js inside WKWebView surfaces a subtle problem: naive SwiftUI navigation destroys and rebuilds the terminal subtree, losing scrollback, DOM state, and focus even though the underlying SSH connection stays alive. The proposed fix separates four distinct lifetimes—logical session, transport session, terminal renderer, and visible attachment—and keeps the WKWebView object alive but detached from the view hierarchy when the terminal section is hidden. Additional guidance covers handing off first responder status before detaching, freezing hidden geometry to avoid spurious PTY resizes, batching high-volume PTY output, and testing transition sequences rather than steady states. The author discloses working on Nexus Shell, a native macOS SSH workspace, and notes that OpenAI Codex assisted with editing.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://playfulprogramming.com/posts/keeping-a-web-backed-ssh-terminal-alive-across-swiftui-navigation>

## Questions this post answers

### Why does my SwiftUI app lose terminal scrollback and state when switching views with a WKWebView-based xterm.js terminal?

SwiftUI can destroy the WKWebView subtree when a conditional view changes, which tears down the WebContent process, the xterm.js DOM, and any scrollback stored only in the browser process. Alternate-screen programs like vim or top cannot be reconstructed from plain text, so the fix is to keep the WKWebView object alive and simply detach it from the view hierarchy instead of destroying it.

_Developers wiring WebKit views into SwiftUI navigation can find more lifecycle patterns like this on daily.dev._

### How do I prevent a hidden terminal view from resizing the remote PTY unexpectedly in a WebKit-based terminal app?

Freeze the inactive container at its last active width so a hidden xterm.js instance does not repeatedly run fit() and trigger PTY resizes as other UI animates. On reattachment, align the web view once, let the resize observer compute new rows and columns, and send a single meaningful PTY resize instead of a frame-by-frame storm of resize events.

_daily.dev surfaces practical fixes like this for developers debugging terminal or PTY resize glitches._

### How should keyboard focus be handled when detaching an active WKWebView-based terminal in a macOS app?

Before detaching, explicitly check whether the window's first responder is the web view or a descendant, then clear or move the first responder, remove the web view from its container, and disable terminal-only keyboard monitors. Otherwise stray keystrokes can be delivered to a hidden view and interpreted by the remote shell, since SwiftUI's appearance callback and AppKit's responder-chain update are not guaranteed to happen in the same frame.

_Teams building AppKit and SwiftUI hybrids track focus-handling gotchas like this via daily.dev._

## Similar posts on daily.dev

- [Make tmux the OS](https://daily.dev/posts/make-tmux-the-os-j0xjuqapl) · Mat Duggan · 1 upvotes · 0 comments
- [I treat my terminal less like a shell and more like a dashboard for everything I have running](https://daily.dev/posts/i-treat-my-terminal-less-like-a-shell-and-more-like-a-dashboard-for-everything-i-have-running-g9yxguc2v) · XDA Developers · 22 upvotes · 1 comments
- [Grid-layout terminal with an AI that drives your shells. - Clide](https://daily.dev/posts/grid-layout-terminal-with-an-ai-that-drives-your-shells---clide-wcj4uz0nl) · Product Hunt · 94 upvotes · 8 comments
- [Stop Making TUIs](https://daily.dev/posts/stop-making-tuis-m5mybfkns) · Lobsters · 1 upvotes · 0 comments

---

Tags: [#swiftui](https://daily.dev/tags/swiftui), [#ssh](https://daily.dev/tags/ssh)

[View this post on daily.dev](https://daily.dev/posts/keeping-a-web-backed-ssh-terminal-alive-across-swiftui-navigation-svsgl6knu)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Keeping a Web-Backed SSH Terminal Alive Across SwiftUI Navigation","url":"https://daily.dev/posts/keeping-a-web-backed-ssh-terminal-alive-across-swiftui-navigation-svsgl6knu","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/keeping-a-web-backed-ssh-terminal-alive-across-swiftui-navigation-svsgl6knu"},"datePublished":"2026-08-24T00:22:04.860Z","dateModified":"2026-08-24T00:22:29.301Z","description":"Building a macOS SSH client that embeds xterm.js inside WKWebView surfaces a subtle problem: naive SwiftUI navigation destroys and rebuilds the terminal...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/e1d1aca0d6ccd594d4f68ac95f1a32e2?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/e1d1aca0d6ccd594d4f68ac95f1a32e2?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Playful Programming","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Playful Programming","logo":"https://media.daily.dev/image/upload/s--WoGOmY81--/f_auto,q_auto/v1773697582/logos/playfulprogramming?_a=BAMAMiiu0","url":"https://daily.dev/sources/playfulprogramming"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/keeping-a-web-backed-ssh-terminal-alive-across-swiftui-navigation-svsgl6knu","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"swiftui,ssh","timeRequired":"PT7M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Playful Programming","item":"https://daily.dev/sources/playfulprogramming"},{"@type":"ListItem","position":3,"name":"Keeping a Web-Backed SSH Terminal Alive Across SwiftUI Navigation"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/keeping-a-web-backed-ssh-terminal-alive-across-swiftui-navigation-svsgl6knu#faq","mainEntity":[{"@type":"Question","name":"Why does my SwiftUI app lose terminal scrollback and state when switching views with a WKWebView-based xterm.js terminal?","acceptedAnswer":{"@type":"Answer","text":"SwiftUI can destroy the WKWebView subtree when a conditional view changes, which tears down the WebContent process, the xterm.js DOM, and any scrollback stored only in the browser process. Alternate-screen programs like vim or top cannot be reconstructed from plain text, so the fix is to keep the WKWebView object alive and simply detach it from the view hierarchy instead of destroying it. Developers wiring WebKit views into SwiftUI navigation can find more lifecycle patterns like this on daily.dev."}},{"@type":"Question","name":"How do I prevent a hidden terminal view from resizing the remote PTY unexpectedly in a WebKit-based terminal app?","acceptedAnswer":{"@type":"Answer","text":"Freeze the inactive container at its last active width so a hidden xterm.js instance does not repeatedly run fit() and trigger PTY resizes as other UI animates. On reattachment, align the web view once, let the resize observer compute new rows and columns, and send a single meaningful PTY resize instead of a frame-by-frame storm of resize events. daily.dev surfaces practical fixes like this for developers debugging terminal or PTY resize glitches."}},{"@type":"Question","name":"How should keyboard focus be handled when detaching an active WKWebView-based terminal in a macOS app?","acceptedAnswer":{"@type":"Answer","text":"Before detaching, explicitly check whether the window's first responder is the web view or a descendant, then clear or move the first responder, remove the web view from its container, and disable terminal-only keyboard monitors. Otherwise stray keystrokes can be delivered to a hidden view and interpreted by the remote shell, since SwiftUI's appearance callback and AppKit's responder-chain update are not guaranteed to happen in the same frame. Teams building AppKit and SwiftUI hybrids track focus-handling gotchas like this via daily.dev."}}]}
```

