The Phase-1 terminal transplant is derived from h3nock/remux commit
b3a3e5f5dfa4759ab189e203b9a03749e821540c, inspected from /tmp/remux-scout.
MoriRemoteTerminal is a separate iOS 17 static framework target
(MACH_O_TYPE = staticlib). It links only Mori's
../Frameworks/GhosttyKit.xcframework; it has no package dependency and
exports the public MoriRemoteTerminalSession facade used by the production
SSH shell. The app target imports MoriRemoteTerminal, never GhosttyKit:
the static terminal archive is the sole native Ghostty owner. This is not a
permanent binary-distribution mechanism; the static archive supplies a module
boundary without embedding a second terminal dylib.
The framework preserves upstream file and directory names for the terminal core:
Tmux/: identity, viewport, control protocol/link, session controller, terminal session, pane surface, screen adapter, and runtime trace.Ghostty/: runtime/control and managed surfaces, pane/local viewport and scroll physics, key/mouse/scroll mappings, responder/text-input/focus/input coordination, modifier state, keyboard visibility/trackpad, compact keypad, system-keyboard chrome, topology projection, andGhosttyTerminalCoreView(the minimal upstream-derived composition root).Domain/TerminalSettings.swift: terminal appearance only.
The paired MoriRemoteTerminalTests target owns the deterministic transport
fixture and ports the matching upstream tests
for controller/session/link/adapter teardown, scrolling and viewport state,
responder and keyboard input, modifier state, and local text selection.
No files from remux account/profile repositories, SSH services/transports,
live forwarding, pane-preview/selection sheets, full composer/voice, generic
file attachments, or shortcut marketplace/editor are linked into
MoriRemoteTerminal. Image input is the deliberate exception: the terminal
module owns photo/clipboard staging and preview UI, while a narrow
MoriRemoteTerminalImageUploader delegates authenticated SFTP upload through
Mori's app-owned SSHRootAccess, the same concrete root recipe used by session
discovery and terminal control. Each operation still owns its exact TOFU retry.
TmuxControlTransport remains a terminal-internal protocol-only seam. The app
crosses it only through MoriRemoteTerminalTransport, whose byte lifecycle
closures are adapted by SSHTmuxControlTransport.asTerminalTransport().
Image upload is a separate typed operation and cannot execute arbitrary tmux or
shell commands; it atomically uploads below ~/.cache/mori/attachments and
inserts a shell-escaped path without pressing Enter. The deterministic
transport remains solely a terminal-core test fixture.
| Area | Change | Why |
|---|---|---|
TmuxControlTransport.swift |
Protocol-only; removes SFTP/live-forward refinements. | Keeps the core independent of SSH/Citadel and forwarding. |
TmuxSessionController.swift |
Native client starts with initial_columns = initial_rows = 0; pane hydration derives dimensions from the authoritative tmux topology (window/pane grid), and exposes only fixed correlated agent-metadata query. |
Prevents an implicit startup refresh-client -C or phone viewport dimensions from resizing the shared tmux client while keeping arbitrary tmux execution out of the app boundary. |
App/MoriRemoteTerminalFacade.swift |
Public deep facade directly owns GhosttyKitRuntime, TmuxTerminalSession, and TmuxTerminalScreenAdapter; it exposes state/topology, fixed metadata results, labeled shared mutations, presentation lifecycle, type-erased SSH byte lifecycle closures, and one typed image-uploader closure. |
App code retains Citadel/trust/persistence/SFTP without importing GhosttyKit or terminal controller/surface types; the one-caller TmuxScreenModel wrapper and one-adapter modeling protocols were removed. |
GhosttyTerminalDisconnectReasonClassifier.swift |
Transport-agnostic classifier. | No Phase-1 dependency on NIO, SSH errors, or host-trust types. |
| Runtime status types | Local terminal-only TerminalRuntimeState / disconnect vocabulary. |
Avoids importing remux connection/account domain objects. |
GhosttySingleViewportView.swift |
Subtractive adaptation of upstream's viewport. It retains local text-selection long press/update/end, selection handles and endpoint drag, selection-geometry recovery, copy edit-menu, surface tap/focus, horizontal window swipe, and mouse routing. | Preview candidate resolution/action is removed; copy remains. Mori's app-owned Navigator is the only window/pane browser. |
TmuxPaneSurface.swift + GhosttyPublishedFrameObserver.swift |
Adds a post-publication interaction-state refresh when Ghostty replaces the renderer layer contents. | The pinned upstream callback polls scrollbar state immediately after terminalChanged, before the renderer necessarily applies new output. Without the completed-frame refresh, UIKit can retain an undersized local scroll document and stop above the true bottom. |
GhosttyKeyboardChrome.swift + GhosttyKeypadSheet.swift + GhosttyImageAttachmentSheet.swift |
Keeps remux's trailing keyboard placement in a slim four-icon input accessory, combines terminal shortcuts in one categorized keypad, and exposes remux-derived photo/clipboard image staging from that panel. App-owned and image-picker modal presentation suspends the hidden terminal responder. | Stable controls avoid localized-label drift; responder suspension protects text fields and system pickers; confirmed images upload through the typed facade and insert only an escaped path. Full composer/voice and shortcut-store domains remain excluded. |
GhosttyTerminalCompositionState.swift + GhosttyTerminalCoreView.swift |
Small upstream-derived composition root over TmuxTerminalScreenAdapter; consumes keyboard notifications, visibility projection, viewport holds, responder callbacks, delayed prefix flush, viewport, text selection, cursor-trackpad HUD, and chrome. |
Host/session/window/pane navigation belongs to Mori's app boundary, where server discovery and metadata already live. |
| Upstream pane preview and selection sheets | Removed after the final app-owned searchable Navigator replaced them. Renderer-frame publication remains only for safe surface presentation and post-frame scroll-state refresh; no pixel copy/cache or temporary picker-grid resize remains. | Two navigation concepts created dead UI and substantial IOSurface lifecycle machinery with no production caller. |
| Upstream composer command support | Removed tracked input completion and pane-current-directory queries with the excluded composer/submission path. Ordinary terminal input retains local routing through the sole controller queue; the fixed agent-metadata query is its only app-domain query. | The copied completion and directory-query machinery had no Mori production or test caller after composer removal. |
| Terminal presentation projection | Retains only the active surface identity, window count, and input availability consumed by Mori's single viewport. Removed readiness snapshots, status overlays, disconnect projections, and command-failure presentation with no UI consumer. | App-owned workspace chrome already presents connection state; duplicating an unrendered terminal status model added state without leverage. |
GhosttyRuntimeTrace.swift |
Retains gated diagnostics, performance timing, and tmux-viewport logging. Producerless flow/latency stores and their no-op call sites are removed. | Mori does not import remux's flow-start or marker-registration producers; preserving only consumers made every flow event unreachable. |
| iOS 17 | Terminal framework deployment target remains 17.0. |
The closed source slice uses no required iOS 18 API. |
The following table records the pinned upstream tests reviewed for each production area and the local equivalent. “Adapted” means the assertion remains but was made deterministic or detached from excluded remux domains.
| Production area | Pinned remux tests reviewed | MoriRemoteTerminalTests | Untranslated gap |
|---|---|---|---|
| tmux client/session/link/adapter | TmuxSessionControllerClientSizeTests.swift, TmuxTerminalScreenAdapterTests.swift, TmuxTerminalSessionShutdownDrainTests.swift |
MoriTmuxNativeStartupIsolationTests.swift, MoriTmuxIsolationTests.swift, facade state/error tests, plus matching session/link/adapter tests |
Native startup harness retains GhosttyKitRuntime, proves zero-grid startup emits version/list-windows but no refresh-client; facade failures complete fixed metadata queries; SSH transport integration remains excluded. |
| responder, text input and paste | GhosttyTerminalResponderViewTests.swift, GhosttyTerminalInputCoordinatorTests.swift |
Same filenames, plus marked-text composition coverage | Simulator-global text pasteboard integration is injected; IME marked text remains local and commits once through insert/replace/unmark. |
| keyboard visibility and viewport continuity | GhosttyKeyboardVisibilityProjectionTests.swift |
GhosttyKeyboardVisibilityProjectionTests.swift, GhosttyTerminalViewportCoordinatorTests.swift, GhosttyTerminalCompositionStateTests.swift |
No device keyboard-animation screenshot test. |
| delayed tmux prefix input | GhosttyTerminalInputCoordinatorTests.swift |
GhosttyTerminalInputCoordinatorTests.swift, GhosttyTerminalPrefixFlushLifecycleTests.swift |
Scheduler wall-clock timing is not asserted; token fencing and flush routing are deterministic. |
| local terminal selection/copy/gesture | GhosttyKitControlSurfaceTests.swift, GhosttySurfaceMouseEventTests.swift, GhosttySurfaceScrollGestureTests.swift |
Same filenames | No end-to-end UIKit edit-menu presentation test; selection geometry, text decoding, mouse/tap and gesture reducers are deterministic. Preview-menu assertion is excluded with preview. |
| keyboard chrome | GhosttyKeyboardChromeModeTests.swift |
GhosttyKeyboardChromeModeTests.swift, GhosttyKeyboardChromeActionsTests.swift |
SwiftUI pixel/snapshot tests are not imported. |
| composition root | GhosttySurfaceScreen.swift (production call graph reviewed) |
GhosttyTerminalCoreViewTests.swift, GhosttyTerminalCompositionStateTests.swift, GhosttyImageAttachmentTests.swift, app-side ImageInputTests.swift |
Full composer/voice, generic files, shortcut UI and account actions remain excluded; image path formatting and atomic upload ordering are deterministic. |
Mori builds one untracked Frameworks/GhosttyKit.xcframework from the pinned
remux Ghostty source. It includes iOS arm64 device/simulator slices and exposes
the ghostty_tmux_client_* ABI used by the upstream controller. See
ghosttykit-lock.json and scripts/verify-ghosttykit.sh for the artifact
provenance and ABI checks.