Skip to content

Add active-pane terminal Find - #229

Open
wesm wants to merge 16 commits into
mainfrom
kenn-forge/issue-194-support-find-in-terminal
Open

Add active-pane terminal Find#229
wesm wants to merge 16 commits into
mainfrom
kenn-forge/issue-194-support-find-in-terminal

Conversation

@wesm

@wesm wesm commented Sep 2, 2026

Copy link
Copy Markdown
Member

Adds Ghostty-style Find for the active terminal pane without copying backend-owned history into Swift.

  • Uses libghostty search for standalone surfaces and exact-client, identity-fenced tmux copy-mode search on POSIX tmux 3.4+.
  • Preserves tmux version boundaries for counts and copy-mode command parsing; unsupported Herdr, Zellij, psmux, and older tmux versions keep Find unavailable instead of searching partial client output.
  • Adds Command-F, Command-G, Shift-Command-G, Shift-Command-F, and Command Palette controls with lifecycle cleanup and query-free diagnostics.
  • Documents the workflow in the Zensical guide with a verified synthetic acceptance screenshot.
  • Covers literal queries, no-match results, stale identities, client-bind readiness, app routing, lifecycle, and live libghostty/tmux behavior.

Closes #194

wesm and others added 9 commits September 2, 2026 08:48
Terminal users need Command-F to search the active pane's complete history, including output from before Ghosthub attached. A libghostty-only search cannot satisfy that contract for multiplexer sessions.

Keep search and viewport ownership in each backend. Start with an identity-fenced tmux implementation and disable unsupported backends instead of presenting incomplete results. Use a Ghostty-style Find bar only for query entry and controls; no pane history crosses into Swift.

Generated with Codex
Co-authored-by: Codex <noreply@openai.com>
tmux exposes exact match totals on newer releases, while copy mode and its viewport remain shared pane state. The Find design must reflect both contracts before implementation.

Use the existing tmux 3.4 client guard, show totals only when tmux reports a complete count, and treat multi-client visibility as a native limitation. Require nested tmux argument encoding and fixed query-free errors so search text cannot become command syntax or a diagnostic.

Generated with Codex
Co-authored-by: Codex <noreply@openai.com>
Find queries cross three tmux command parsers before they reach copy mode. Double-quoted or unquoted child commands can expand query text such as $HOME, so the design must require single-quoted encoding at every tmux depth.

Use search_present as the stable no-match signal and treat count fields only as optional detail. This keeps navigation and result text consistent when tmux renders missing counts as either empty or zero.

Generated with Codex
Co-authored-by: Codex <noreply@openai.com>
tmux 3.4 through 3.5a silently ignore a copy-mode search that uses the modern option terminator. A single command shape would make Find report false no-match results on supported releases.

Define the 3.6 parser boundary and keep direction changes under tmux control. Reuse the existing single-quote helper at every parser depth so the design does not add a duplicate encoder.

Generated with Codex
Co-authored-by: Codex <noreply@openai.com>
Active-pane Find crosses app shortcuts, per-surface state, libghostty callbacks, and exact-client tmux commands. The approved design needs reviewable steps that preserve backend-owned history and version-specific tmux behavior.

Define test-driven implementation slices and final gates. Clarify the tmux 3.5 tier as a numeric comparison so future patch-letter releases use the legacy command form.

Generated with Codex
Co-authored-by: Codex <noreply@openai.com>
Find must stay responsive while remote pane searches are still running. A per-surface controller now coalesces query edits, serializes backend work, and rejects results from stale searches.

Add the Ghostty-compatible application shortcuts at the same shared boundary. Later tmux and libghostty integrations can use one state and reservation contract without intercepting ordinary terminal input.

Generated with Codex
Co-authored-by: Codex <noreply@openai.com>
Pane history search needs the same exact-client protection as pane splitting. A shared guard now keeps later terminal operations tied to the attached server, session, client, and pane.

Use one numeric tmux version model for capability decisions. Patch-letter releases now follow their major and minor feature boundary without duplicate parsing rules.

Generated with Codex
Co-authored-by: Codex <noreply@openai.com>
Terminal users need to search complete pane history without Ghosthub copying or rebuilding backend-owned output. Add a Ghostty-style Find bar for ordinary libghostty surfaces and identity-fenced copy-mode search for POSIX tmux 3.4 and newer.

Keep unsupported multiplexers unavailable instead of searching only the client buffer. Preserve tmux's version-specific command syntax, pane-wide copy-mode behavior, and match-count limits while keeping queries out of logs and diagnostics.

Generated with Codex
Co-authored-by: Codex <noreply@openai.com>
New tmux clients can publish their exact TTY after the first identity probe,
so keep the initial binding retry bounded until the attachment is ready. Make
Find discoverable in the Command Palette and publish a deterministic guide
capture for the documented workflow. Remove the local planning artifacts
before the branch leaves the workspace.

Generated with Codex

Co-authored-by: Codex <noreply@openai.com>
@wesm
wesm deployed to sandbox-image-promotion-status September 2, 2026 14:00 — with GitHub Actions Active
@wesm
wesm deployed to sandbox-image-promotion-status September 2, 2026 14:00 — with GitHub Actions Active
@wesm
wesm deployed to sandbox-image-promotion-status September 2, 2026 14:01 — with GitHub Actions Active
@roborev-ci

roborev-ci Bot commented Sep 2, 2026

Copy link
Copy Markdown

roborev: Combined Review (e8d964c)

Verdict: Two medium-severity correctness issues require attention; no security vulnerabilities identified.

Medium

  • Sources/App/WorkspaceSceneModel.swift:388-390 — Standalone Find actions depend on terminal keyboard focus. Find-field focus and log-viewer sheets can make Cmd-G, Shift-Cmd-G, Hide Find Bar, and Find no-ops. Resolve the active Find controller independently of terminal responder focus.

  • Sources/TerminalSupport/TerminalFindController.swift:145-183 — Libghostty callbacks are not tied to a search generation. Delayed callbacks from earlier queries may overwrite current results or invalidate newer searches. Associate callbacks with an operation token or discard stale callbacks.


Reviewers: 2 done | Synthesis: codex, 12s | Total: 11m11s

Find commands must continue to address the visible terminal after focus moves
into the Find field or a log-viewer sheet. Delayed libghostty callbacks must
not let an older search replace the latest result.

The libghostty smoke test now waits until injected output is visible before it
takes the search snapshot. This removes the CI-only timing dependency.

Generated with Codex
Co-authored-by: Codex <noreply@openai.com>
@wesm
wesm deployed to sandbox-image-promotion-status September 2, 2026 16:33 — with GitHub Actions Active
@wesm
wesm deployed to sandbox-image-promotion-status September 2, 2026 16:33 — with GitHub Actions Active
@wesm
wesm deployed to sandbox-image-promotion-status September 2, 2026 16:34 — with GitHub Actions Active
@roborev-ci

roborev-ci Bot commented Sep 2, 2026

Copy link
Copy Markdown

roborev: Combined Review (9778758)

Verdict: High-severity token routing and Medium-severity tmux pane tracking issues remain.

High

  • Sources/Terminal/LibghosttyRuntime.swift:1011-1015 — Internally initiated searches register a controller operation token, but START_SEARCH replaces it with a new token. The controller rejects that token, and subsequent result callbacks use the wrong token, leaving standalone Find stuck without results. Reuse the pre-registered token for internal searches; create a new token only for genuinely external searches.

Medium

  • Sources/App/NativeTmuxSessionCoordinator.swift:1200-1225, Sources/App/TmuxPaneFinder.swift:162-190 — The Find target captures the tmux client’s pane ID only when the controller is installed. Switching panes or creating a split changes the active pane, causing Find operations to fail the exact-pane guard. Resolve the current client/pane identity before each Find operation while retaining the existing race guard.

Reviewers: 2 done | Synthesis: codex, 7s | Total: 16m23s

A tmux client can move to another pane after Ghosthub installs its Find
controller. Resolve the client's current pane before every Find operation,
while the existing exact-pane guard still rejects a later race.

Keep external libghostty search starts separate from registered app searches.
The pinned runtime updates internal searches without a START_SEARCH callback.

Generated with Codex
Co-authored-by: Codex <noreply@openai.com>
@wesm
wesm deployed to sandbox-image-promotion-status September 2, 2026 18:30 — with GitHub Actions Active
@wesm
wesm deployed to sandbox-image-promotion-status September 2, 2026 18:30 — with GitHub Actions Active
@wesm
wesm deployed to sandbox-image-promotion-status September 2, 2026 18:31 — with GitHub Actions Active
@roborev-ci

roborev-ci Bot commented Sep 2, 2026

Copy link
Copy Markdown

roborev: Combined Review (d018787)

Verdict: Two Medium-severity Find issues remain; no High or Critical findings were reported.

Medium

  • Focused log viewer loses Find prioritySources/App/WorkspaceSceneModel.swift:370-387
    When the log viewer is open over an active borrowed session, Find routes to the borrowed session controller instead of the focused log terminal. Prioritize the focused log-viewer controller.

  • External search operation can leave Find stuckSources/TerminalSupport/TerminalFindController.swift:173-181
    START_SEARCH registers a replacement operation, but backendDidOpen rejects it while another callback is active. Invalidate the previous operation and accept the new external operation atomically.


Reviewers: 2 done | Synthesis: codex, 6s | Total: 12m37s

A presented log viewer owns terminal focus even when a borrowed session stays
active behind it. Route Find to the log terminal so shortcuts affect the
visible work.

A new libghostty search must also replace any app-started callback wait. Move
the controller to a new generation so only the replacement can publish results.

Generated with Codex
Co-authored-by: Codex <noreply@openai.com>
@wesm
wesm deployed to sandbox-image-promotion-status September 2, 2026 19:22 — with GitHub Actions Active
@wesm
wesm deployed to sandbox-image-promotion-status September 2, 2026 19:22 — with GitHub Actions Active
@wesm
wesm deployed to sandbox-image-promotion-status September 2, 2026 19:22 — with GitHub Actions Active
@roborev-ci

roborev-ci Bot commented Sep 2, 2026

Copy link
Copy Markdown

roborev: Combined Review (72118a1)

Verdict: Three medium-severity issues require attention.

Medium

  • Sources/App/BorrowedTmuxSessionView.swift:260-268 — The Find bar and log viewer overlay depend on nested controller state, but the hosting view observes only surfaceView. Opening Find may update controller state without re-rendering the view. Observe the controller directly or forward its changes, and add a rendered-view test.

  • Sources/Terminal/LibghosttyRuntime.swift:1039-1065 — Delayed search callbacks from older queries can be associated with the current operation, allowing stale results to overwrite newer ones. Correlate callbacks with an upstream search generation or discard superseded callbacks.

  • Sources/App/NativeTmuxSessionCoordinator.swift:1203-1210, 1324-1337 — Find transport failures are presented as plain errors without invalidating failed remote SSH/tmux connections, leaving unusable connections in the pool. Preserve failure status and invalidate the connection before presenting the error.


Reviewers: 2 done | Synthesis: codex, 8s | Total: 13m17s

Find must remain visible when its field owns focus, and delayed libghostty
callbacks must not replace results for a newer query. Remote tmux failures
must also retire an unusable pooled connection before the error is shown.

Use libghostty's reset boundary to preserve callback ownership without
changing the pinned library. Keep tmux failure status and diagnostics internal
so the coordinator can invalidate the route without exposing query text.

Generated with Codex
Co-authored-by: Codex <noreply@openai.com>
@wesm
wesm deployed to sandbox-image-promotion-status September 2, 2026 22:44 — with GitHub Actions Active
@wesm
wesm deployed to sandbox-image-promotion-status September 2, 2026 22:44 — with GitHub Actions Active
@wesm
wesm deployed to sandbox-image-promotion-status September 2, 2026 22:44 — with GitHub Actions Active
@roborev-ci

roborev-ci Bot commented Sep 2, 2026

Copy link
Copy Markdown

roborev: Combined Review (0ca9e2d)

Verdict: One medium-severity UI reactivity issue was identified.

Medium

  • Sources/App/WorkspaceSceneModel.swift:5836-5844 — The standalone terminal Find overlay reads surface.terminalFindController directly without observing it, so opening Find may not trigger SwiftUI to render the search bar. Wrap the presentation in a view observing TerminalSurfaceView or observe the Find controller directly.

Reviewers: 2 done | Synthesis: codex, 7s | Total: 11m44s

Standalone log terminals must reveal Find when the controller opens after
SwiftUI builds the terminal view. Observe the controller at the shared overlay
boundary so menu shortcuts update the visible bar.

Wait for protected attachment establishment in the stateful removal test. A
slow runner can otherwise start removal while the attachment still owns its
mutation fence.

Generated with Codex
Co-authored-by: Codex <noreply@openai.com>
@wesm
wesm deployed to sandbox-image-promotion-status September 2, 2026 23:59 — with GitHub Actions Active
@wesm
wesm deployed to sandbox-image-promotion-status September 2, 2026 23:59 — with GitHub Actions Active
@wesm
wesm deployed to sandbox-image-promotion-status September 2, 2026 23:59 — with GitHub Actions Active
@roborev-ci

roborev-ci Bot commented Sep 3, 2026

Copy link
Copy Markdown

roborev: Combined Review (c4dc044)

Verdict: One medium-severity concurrency issue requires attention.

Medium

  • Sources/Terminal/LibghosttyFind.swift:59-107 — An upstream START_SEARCH can queue its token behind a pending Ghosthub search. Reset callbacks may target the stale token while the controller uses the external token, leaving the external search waiting indefinitely without results. External searches should supersede pending operations with explicit callback-generation handling.

Reviewers: 2 done | Synthesis: codex, 6s | Total: 14m43s

An upstream Find action must replace any Ghosthub search still waiting for
libghostty callbacks. Otherwise the reset boundary can promote the old token
and leave the visible external search waiting forever.

Keep the current token only for the in-flight reset pair, then route later
results to the external operation.

Generated with Codex
Co-authored-by: Codex <noreply@openai.com>
@wesm
wesm deployed to sandbox-image-promotion-status September 3, 2026 02:10 — with GitHub Actions Active
@wesm
wesm deployed to sandbox-image-promotion-status September 3, 2026 02:10 — with GitHub Actions Active
@wesm
wesm deployed to sandbox-image-promotion-status September 3, 2026 02:10 — with GitHub Actions Active
@roborev-ci

roborev-ci Bot commented Sep 3, 2026

Copy link
Copy Markdown

roborev: Combined Review (a1a6a02)

Verdict: Medium-severity concurrency, lifecycle, backend-routing, and callback-ordering issues remain.

Medium

  • Sources/TerminalSupport/TerminalFindController.swift:130-147 — Updating the query or closing Find does not cancel an in-flight backend operation, so a blocked tmux command can delay newer requests for up to 15 seconds. Track and cancel the active backend task when the query changes or Find closes.

  • Sources/App/NativeTmuxSessionCoordinator.swift:1265-1269, 1399-1403 — A stale Find transport failure can invalidate a newer SSH attachment using the same handle. Pass and verify the expected attachment ID before invalidating the connection.

  • Sources/Terminal/LibghosttyRuntime.swift:1006-1022START_SEARCH is handled for native tmux surfaces, potentially opening the embedded client-buffer search instead of tmux-history search. Track backend ownership and ignore or route these actions appropriately.

  • Sources/Terminal/LibghosttyFind.swift:94-110; Sources/TerminalSupport/TerminalFindController.swift:217-235 — The callback waiter resumes after the first callback even though the operation requires a total/selected callback pair. Replacement queries or navigation can misattribute delayed callbacks or leave the new operation waiting. Track explicit per-operation callback state and fence stale callbacks.

  • Sources/Terminal/LibghosttyFind.swift:193-198, 217-222 — Find totals and selection values are updated before operation-token validation, allowing stale and current callbacks to be combined into incorrect results. Store and publish callback state only after confirming the callback is current.


Reviewers: 2 done | Synthesis: codex, 9s | Total: 16m14s

A blocked pane search must not delay a replacement query or keep Find open.
Cancel superseded backend work and accept only the callback that completes the
current operation.

Keep native tmux surfaces out of libghostty search routing. Invalidate a lost
remote connection only when it still belongs to the attachment that failed.

Generated with Codex
Co-authored-by: Codex <noreply@openai.com>
@wesm
wesm deployed to sandbox-image-promotion-status September 3, 2026 02:59 — with GitHub Actions Active
@wesm
wesm deployed to sandbox-image-promotion-status September 3, 2026 02:59 — with GitHub Actions Active
@wesm
wesm deployed to sandbox-image-promotion-status September 3, 2026 03:00 — with GitHub Actions Active
@roborev-ci

roborev-ci Bot commented Sep 3, 2026

Copy link
Copy Markdown

roborev: Combined Review (be7e4ea)

Verdict: High-severity fallback build failure and two medium-severity Find behavior bugs remain.

High

  • Sources/App/NativeTmuxSessionCoordinator.swift:827 — The fallback TerminalSurfaceView in Sources/TerminalUnavailable lacks useExternalFindBackend(), causing the terminal-unavailable build to fail. Add a no-op stub or shared protocol implementation.

Medium

  • Sources/App/WorkspaceSceneModel+Selection.swift:112-116 — Closing Find via the menu, keyboard shortcut, or command palette does not restore focus to the terminal. Reuse the focus-restoration path used by the Find bar’s close button and Escape handling.

  • Sources/Terminal/LibghosttyFind.swift:50-51, 90-91 — Starting a replacement search resets the global receivedResetTotal flag while a prior reset pair is still incomplete, potentially advancing the wrong queued token and leaving the replacement search waiting indefinitely. Track reset-pair state per operation or preserve the in-flight boundary until its matching callback arrives.


Reviewers: 2 done | Synthesis: codex, 7s | Total: 10m2s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Support find in terminal

1 participant