Hi Miguel,
We maintain a SwiftTerm fork for Threading, a terminal host for macOS and iOS. We recently reconciled the fork with v1.20.0 and sorted out which downstream changes look reusable.
Before we spend time rebasing them onto current main, would you be interested in any of the areas below? We would send each accepted area as a small PR with its tests. The full v1.20.0 comparison is available, but it is an inventory rather than a ready-to-merge PR. Current main has changed substantially since v1.20.0, especially around I/O, so we would adapt each patch to the current design.
1. LocalProcess lifecycle and final-output ordering
Our fork:
- reaps the exact child PID and keeps the instance unavailable for relaunch until reaping finishes;
- waits for both child exit and PTY EOF before sending the termination callback after a natural exit;
- prevents queued output from an old process generation from reaching a newly launched process;
- reaps children after
terminate() and deinit, rather than cancelling the exit monitor and leaving a zombie; and
- bounds queued main-thread output with high and low water marks so a fast producer eventually blocks on the PTY instead of growing memory without limit.
There are nine tests covering instant exits, rapid relaunch, concurrent processes, termination, deallocation, stale output, and sustained backpressure. The final-output part appears to address #370.
The new TerminalIOPipeline on main already handles backpressure differently. We would port the lifecycle and callback-ordering parts that still apply, not restore the old reader.
2. Incremental recent-buffer text
We added a bounded logical-text read for hosts that scan terminal output for paths, links, or transcript updates. It joins soft-wrapped rows, preserves hard line breaks, omits an incomplete oldest logical line, and never returns more than the requested UTF-8 budget.
The incremental form returns an absolute row cursor. A later read skips stable scrollback but always reads the visible screen again, since a full-screen application can repaint those rows in place. A stale cursor after a reset falls back to a complete bounded read.
The implementation and focused tests are in fdebb7b. For current main, this may fit better as a concurrency-safe TerminalView snapshot API than as the exact v1.20 Terminal method.
3. Avoid reflowing hidden scrollback during alternate-screen resize
While an alternate-screen application is active, our fork resizes the visible alternate buffer immediately but defers normal-buffer reflow until the terminal switches back. This avoids repeatedly reflowing a large, hidden scrollback during every live-resize tick. The normal buffer still ends up at the final grid size, with its saved cursor and tab stops adjusted when it becomes visible.
There is a regression test for the deferred reflow and return to the normal buffer.
4. Terminal protocol and mirrored-renderer state
We implemented DECSET/DECRST/DECRQM 2031 color-scheme reporting plus the host call that sends CSI ? 997 ; 1 n or CSI ? 997 ; 2 n after an appearance change.
Our remote renderer also needs the active mouse coordinate encoding and cursor visibility. In the v1.20 fork those became public read-only state. On current main, adding them to TerminalViewStateSnapshot would probably fit better.
5. macOS selection and wheel behavior
The macOS selection no longer disappears whenever process output arrives. It remains valid while rows are appended and is cleared only when its buffer coordinates can no longer be trusted, such as after scrollback trimming, a buffer switch, or a reflow.
Mouse-wheel reports also pass through a small token bucket. AppKit can produce large accelerated deltas, and forwarding every unit can flood a slow full-screen application with partial mouse sequences. Our fork treats a classic wheel event as one notch, bounds precise-wheel bursts, and offers a modifier route to local scrollback while mouse reporting is active.
These could be separate PRs if either behavior is useful.
6. iOS mouse and scroll routing
We tightened the boundary between terminal mouse reporting and local UIKit behavior:
- a tap can reach a mouse-tracking application even when the terminal did not already own keyboard focus;
- one-finger vertical drags can become bounded wheel reports while two fingers remain available for local scrollback;
allowMouseReporting applies consistently to tap, drag, motion, and wheel paths; and
- hosts can forward a tap or wheel distance through public methods when another view owns the gesture recognizer.
This may belong under the existing iOS design discussion in #99. We have focused tests for tap routing, mouse modes, wheel bounds, and local scrolling.
7. Embedding and appearance seams
We also carry smaller APIs that may or may not fit SwiftTerm's public surface:
- hooks that let a remote host own the terminal grid instead of applying frame-derived rows and columns;
- injectable pasteboard access, selected-text access, and one callback when a selection gesture settles;
- replacement of the macOS scroller without replacing SwiftTerm's target, action, and sizing behavior;
- a separate default bold foreground color and an optional transform for true-color backgrounds; and
- structured, rate-limited diagnostic events for parser, buffer, PTY, renderer, and contrast failures.
We would split these by concern. The diagnostics would also need to be reconciled with the newer diagnostics already on main.
We do not propose upstreaming Threading's .legacy scroller default. That is a host layout choice, and SwiftTerm's .overlay default is reasonable.
The v1.20 reconciliation passed 96 XCTest tests and 732 Swift Testing tests. The nine LocalProcess tests and the focused integration tests pass on the final fork.
Which of these, if any, would you like us to prepare first? Also, should proposed fixes target current main only, or would you want any of them on v1.x as well?
Hi Miguel,
We maintain a SwiftTerm fork for Threading, a terminal host for macOS and iOS. We recently reconciled the fork with v1.20.0 and sorted out which downstream changes look reusable.
Before we spend time rebasing them onto current
main, would you be interested in any of the areas below? We would send each accepted area as a small PR with its tests. The full v1.20.0 comparison is available, but it is an inventory rather than a ready-to-merge PR. Currentmainhas changed substantially since v1.20.0, especially around I/O, so we would adapt each patch to the current design.1.
LocalProcesslifecycle and final-output orderingOur fork:
terminate()anddeinit, rather than cancelling the exit monitor and leaving a zombie; andThere are nine tests covering instant exits, rapid relaunch, concurrent processes, termination, deallocation, stale output, and sustained backpressure. The final-output part appears to address #370.
The new
TerminalIOPipelineonmainalready handles backpressure differently. We would port the lifecycle and callback-ordering parts that still apply, not restore the old reader.2. Incremental recent-buffer text
We added a bounded logical-text read for hosts that scan terminal output for paths, links, or transcript updates. It joins soft-wrapped rows, preserves hard line breaks, omits an incomplete oldest logical line, and never returns more than the requested UTF-8 budget.
The incremental form returns an absolute row cursor. A later read skips stable scrollback but always reads the visible screen again, since a full-screen application can repaint those rows in place. A stale cursor after a reset falls back to a complete bounded read.
The implementation and focused tests are in fdebb7b. For current
main, this may fit better as a concurrency-safeTerminalViewsnapshot API than as the exact v1.20Terminalmethod.3. Avoid reflowing hidden scrollback during alternate-screen resize
While an alternate-screen application is active, our fork resizes the visible alternate buffer immediately but defers normal-buffer reflow until the terminal switches back. This avoids repeatedly reflowing a large, hidden scrollback during every live-resize tick. The normal buffer still ends up at the final grid size, with its saved cursor and tab stops adjusted when it becomes visible.
There is a regression test for the deferred reflow and return to the normal buffer.
4. Terminal protocol and mirrored-renderer state
We implemented DECSET/DECRST/DECRQM 2031 color-scheme reporting plus the host call that sends
CSI ? 997 ; 1 norCSI ? 997 ; 2 nafter an appearance change.Our remote renderer also needs the active mouse coordinate encoding and cursor visibility. In the v1.20 fork those became public read-only state. On current
main, adding them toTerminalViewStateSnapshotwould probably fit better.5. macOS selection and wheel behavior
The macOS selection no longer disappears whenever process output arrives. It remains valid while rows are appended and is cleared only when its buffer coordinates can no longer be trusted, such as after scrollback trimming, a buffer switch, or a reflow.
Mouse-wheel reports also pass through a small token bucket. AppKit can produce large accelerated deltas, and forwarding every unit can flood a slow full-screen application with partial mouse sequences. Our fork treats a classic wheel event as one notch, bounds precise-wheel bursts, and offers a modifier route to local scrollback while mouse reporting is active.
These could be separate PRs if either behavior is useful.
6. iOS mouse and scroll routing
We tightened the boundary between terminal mouse reporting and local UIKit behavior:
allowMouseReportingapplies consistently to tap, drag, motion, and wheel paths; andThis may belong under the existing iOS design discussion in #99. We have focused tests for tap routing, mouse modes, wheel bounds, and local scrolling.
7. Embedding and appearance seams
We also carry smaller APIs that may or may not fit SwiftTerm's public surface:
We would split these by concern. The diagnostics would also need to be reconciled with the newer diagnostics already on
main.We do not propose upstreaming Threading's
.legacyscroller default. That is a host layout choice, and SwiftTerm's.overlaydefault is reasonable.The v1.20 reconciliation passed 96 XCTest tests and 732 Swift Testing tests. The nine
LocalProcesstests and the focused integration tests pass on the final fork.Which of these, if any, would you like us to prepare first? Also, should proposed fixes target current
mainonly, or would you want any of them onv1.xas well?