- M0: libghostty SSH 终端(渲染/输入/连接)+ 白屏修复(OutputGate) + 会话状态机/自动重连 - M1: tsnet 用户态组网 + SSH-over-tsnet(fd 桥),shell 级真机验证;R5(Go+gvisor+C+++Swift 同进程) retire - M1.5: tmux -CC 原生 tab(MVP) - 结构: packages/(TXCore·TXTransport), apps/TerminalX, vendor/(libghostty-spm/libssh2/mbedtls/tsnet-bridge), artifacts/ - 文档: CLAUDE.md + docs/HANDOFF.md(新会话入口) - 环境: 认证代理→依赖 vendor 本地化;Go 在 ~/.local/go;仅模拟器/未签名 - 待续: M2 mosh, tmux 多 pane, M4 安全(host key/SE) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
10 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
SPM package wrapping Ghostty terminal emulator C library for Apple platforms (macOS 13+, iOS 15+, Mac Catalyst 15+). Four library products:
- GhosttyKit — minimal re-export of the libghostty C API (
@_exported import libghostty) - GhosttyTerminal — Swift wrapper: native views, SwiftUI integration, input handling, display link, host-managed I/O
- GhosttyTheme — 485 terminal color themes from iTerm2-Color-Schemes (MIT License, depends on GhosttyTerminal)
- ShellCraftKit — sandboxed shell emulation framework (depends on GhosttyTerminal)
Binary target: pre-built libghostty XCFramework. Dependency: MSDisplayLink ^2.1.0.
Build & Test Commands
# Build the SPM package
swift build
# Run tests
swift test
# Multi-destination build verification (macOS, iOS, iOS Simulator, Mac Catalyst)
./Script/test.sh
# Build full XCFramework from Ghostty source (requires zig)
./build.sh
./build.sh --platforms macos,ios --source /path/to/ghostty --skip-tests
# Generate Package.swift for release
./Script/build-manifest.sh
# Regenerate GhosttyTheme Swift files from iTerm2-Color-Schemes
./Script/generate-themes.sh
Architecture
GhosttyKit (C API re-export)
└─ libghostty.a (Zig → static lib) + ghostty.h
GhosttyTerminal (Swift wrapper, ~40 files)
├─ Configuration/ Config structs, themes, color schemes, ghostty.conf rendering
├─ Controller/ TerminalController — app lifecycle, config, surface creation
├─ InMemory/ Sandbox-safe I/O backend (no PTY), C callback bridge
├─ Metrics/ Grid size, viewport dimensions, input/scroll modifiers
├─ Platform/AppKit/ macOS NSView: input, IME, key events
├─ Platform/UIKit/ iOS UIView: UITextInput, keyboard, touch/gesture, IME, input accessory bar
├─ State/ ObservableObject TerminalViewState (SwiftUI state container)
├─ Surface/ Metal rendering bridge, display link, surface lifecycle
└─ View/ SwiftUI TerminalSurfaceView + platform representables
GhosttyTheme (485 terminal color themes)
├─ GhosttyThemeDefinition — theme data model (name, colors, palette)
├─ GhosttyThemeCatalog — static catalog, search, lookup by name
├─ +TerminalConfiguration — bridge to TerminalConfiguration/TerminalTheme, isDark helper
└─ Themes/ — auto-generated Swift files (A-Z) from iTerm2-Color-Schemes
ShellCraftKit (~5 files)
├─ Definition/ ShellDefinition, SandboxShell, ShellCommand protocol
└─ Session/ ShellSession + Bridge + Engine
Key types: TerminalViewState (ObservableObject, SwiftUI entry point), TerminalSurfaceView (SwiftUI view), TerminalView (platform typealias: UITerminalView / AppTerminalView), TerminalController, InMemoryTerminalSession, GhosttyThemeDefinition, GhosttyThemeCatalog.
Platform Branching
Use #if canImport(UIKit) FIRST, then #else #if canImport(AppKit) — Catalyst imports both UIKit and AppKit.
Host-Managed I/O
All example apps run in App Sandbox. Use GHOSTTY_SURFACE_IO_BACKEND_HOST_MANAGED for non-PTY I/O. Never disable sandbox or spawn subprocesses.
iOS Input Architecture (UITextInput)
UITerminalView conforms to UITextInput (which includes UIKeyInput) to receive both software keyboard and hardware keyboard input on iOS/Catalyst. The input chain:
- Hardware keys →
pressesBegan/pressesEndedin+Keyboard.swift→ buildsghostty_input_key_s→surface.sendKeyEvent(). SetshardwareKeyHandled = trueto suppress the duplicateinsertText/deleteBackwardthat UIKit would otherwise deliver. - Software keyboard → UIKit calls
insertText(_:)/deleteBackward()via UIKeyInput. Guarded byhardwareKeyHandledflag to avoid double-processing hardware key presses. - Input accessory bar (iOS only, excludes Catalyst) →
TerminalInputAccessoryViewprovides a toolbar above the software keyboard with Esc, Tab, arrow keys, modifier keys (Ctrl/Alt/Cmd), symbol keys, and Paste. Modifier keys support sticky states: tap to arm (consumed after next key), double-tap to lock (persists until toggled off). Sticky modifier state is tracked byTerminalStickyModifierState. Actions are dispatched viaUITerminalView+InputAccessory.swift. Button colors are configurable viaTerminalInputAccessoryStyle(regular/active background and foreground), exposed asUITerminalView.inputAccessoryStyle. - IME / marked text →
setMarkedText/unmarkTextdelegate toTerminalTextInputHandler, which callssurface.preedit()for inline composition preview. Committed text goes throughinsertText. Sticky modifiers are respected during IME composition. - Text positioning →
TerminalTextPosition/TerminalTextRange(UITextPosition/UITextRange subclasses) provide minimal cursor geometry.caretRect/firstRectusesurface.imePoint()for IME candidate window placement.
Files in Platform/UIKit/:
UITerminalView.swift— main view,canBecomeFirstResponder, coordinator setupUITerminalView+UITextInput.swift— full UITextInput conformance (UIKeyInput, marked text, positions, geometry)UITerminalView+Keyboard.swift— hardware key handling via UIPress, modifier translationUITerminalView+InputAccessory.swift— input accessory bar integration, key actions, sticky modifier dispatchUITerminalView+Interaction.swift— touch scrolling, momentum scroll via CADisplayLink, Catalyst pointer/mouseUITerminalView+Lifecycle.swift— display scale, sublayer frames, focus, color schemeTerminalInputAccessoryView.swift— input accessory bar UIView (blur background, scrollable button layout)TerminalInputAccessoryStyle.swift— configurable button colors for the accessory bar (regular/active background and foreground)TerminalInputBarKey.swift— enum defining accessory bar key types (esc, tab, arrows, symbols, paste)TerminalStickyModifierState.swift— modifier key state machine (inactive/armed/locked, double-tap locking)TerminalTextInputHandler@UIKit.swift— IME state machine (marked text, preedit bridge, sticky modifier support)TerminalTextPosition.swift— TerminalTextPosition / TerminalTextRange subclasses
The macOS equivalent uses NSTextInputClient in AppTerminalView+NSTextInputClient.swift with a parallel TerminalTextInputHandler@AppKit.swift.
iOS Long-Press Text Selection
Long-press ≥0.5s on UITerminalView (single-finger, iOS only — Catalyst excluded) triggers TerminalSurfaceTextSelectionRequestDelegate.terminalDidRequestTextSelection(_:). The host receives a TerminalTextSelectionRequest (viewport text snapshot + UTF-16 NSRange? for pre-selection + source point) and is expected to present a host UI (e.g. UITextView sheet). Word detection uses ghostty_surface_quicklook_word (Apple-only); TerminalSelectionAnchor.resolveRange maps the result to an NSRange via NSString UTF-16 calculations. Same-row duplicate occurrences are disambiguated by pointX / cellWidthPoints; callers must convert cellPixels / displayScale → points so ghostty's tl_px_x/y host-point units match. Prefix CJK full-width characters can shift cell-vs-UTF-16 columns and degrade disambiguation (ASCII-only correct, best-effort otherwise). The recognizer is gated by gestureRecognizerShouldBegin to stay inactive when no host has opted in. MVP supports only the inMemory backend.
In iPhone UI tests, synthesize ordinary terminal taps as explicitly short presses and verify hasKeyboardFocus before typeText; a loaded hosted runner can stretch tap() long enough for the selection recognizer to present its sheet. Keep the ordinary XCTest tap and typing path on iPad, where short presses do not reliably publish keyboard focus through accessibility.
Manifest Sync
When changing SwiftPM products, targets, or test dependencies, update all three together:
Package.swift— production manifest (remote XCFramework URL + checksum)Package.local.swift— local development (path-based binary target)Package.swift.template— CI template with__DOWNLOAD_URL__/__CHECKSUM__placeholders
Release Versioning
- Bare semantic-version tags are GhosttyKit Swift package versions, independent of Ghostty upstream versions.
Ghostty.refpins release builds to one immutable upstream commit; update it in a reviewed change instead of inferring an upstream ref from the package version.storage.<package-version>owns the XCFramework asset referenced by the matching package tag.- arm64e release slices must contain real ARM64E Mach-O members and adapt pointer-authenticated callback, Block, Objective-C IMP, and system callback boundaries; never satisfy the architecture by changing only a fat-archive label.
Swift Code Style
- 4-space indentation, opening brace on same line
- PascalCase types, camelCase properties/methods
- PascalCase files for types,
+for extensions (e.g.,AppTerminalView+Input.swift) - ObservableObject/@Published for SwiftUI state that must support iOS 15 / Mac Catalyst 15
- Swift concurrency: async/await, Task, actor, @MainActor
- Early returns, guard statements, single responsibility per type/extension
- Value types over reference types, composition over inheritance
- Dependency injection over singletons
- Avoid protocol-oriented design unless necessary
- Split files frequently — keep files small and focused (~40-100 lines typical)
- Don't extract methods unnecessarily — avoid premature abstraction
Shell Script Style
- Shebang:
#!/bin/zsh, failure handling:set -euo pipefail - Output:
[+]success,[-]failure, lowercase messages - Minimal comments, no color output, assume tools available
- Don't add if-checks when pipefail handles failures
GhosttyKit Design Requirements
Wrapper Design
- GhosttyTerminal must expose all functionality from
ghostty.h - Clean Swift APIs mapping to C API: config, app lifecycle, surfaces, input, clipboard, inspector, splits, mouse, IME, text selection
- Proper Swift patterns: enums for C enums, structs for C structs, closures for callbacks
Example App Requirements
- Apps run in App Sandbox — must NOT spawn subprocesses (non-negotiable)
- Use mock terminal IO with real GhosttyTerminal surface/view layer
- Use host-managed I/O backend, never disable sandbox for PTY workarounds
- Keep echo terminal as self-contained module separate from GhosttyKit integration