Skip to content

Add compose bar with voice support - #23

Merged
h3nock merged 51 commits into
mainfrom
feature/voice-compose-bar
Jul 31, 2026
Merged

Add compose bar with voice support#23
h3nock merged 51 commits into
mainfrom
feature/voice-compose-bar

Conversation

@h3nock

@h3nock h3nock commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a compose bar to the terminal dock for writing input before sending it
to the terminal. Supports typed text, dictation, and attachments. Nothing
reaches the pane until Send.

  • Text is drafted in the bar and sent as one paste followed by Enter, both
    confirmed through tmux command completion.
  • Dictation uses on-device speech recognition with a live transcript and a
    waveform meter.
  • Photos, files, and pasted images are uploaded to the server over SFTP and
    their remote paths are inserted into the message.
  • The dock's attachment tray is replaced by the composer's own attachment
    handling; the dock gains a composer toggle.

How it works

The composer is a single app-scoped model that lives above the terminal
screens. The draft, attachments, dictation state, and any in-flight send
survive switching terminals, reconnects, and screen recreation — the
composer follows you rather than belonging to one terminal.

The send destination is captured once when Send is tapped, as a small value
holding the pane, the transfer service, and the paste/Enter operations. The
composer holds no terminal references and imports no terminal types, so the
whole submission path is covered by unit tests with a mock destination.

The bar observes the composer model directly and the screen subscribes only
to what it renders, so typing and dictation re-render just the bar.

Send behavior

  • The captured pane is the destination. Navigating somewhere else mid-send
    doesn't retarget the message.
  • If the paste is confirmed but Enter isn't, the app stops instead of
    retrying (a retry could double-submit) and the status names the
    destination window: Couldn’t finish sending. Check “remux: codex”.
  • Failures before the pane is touched keep the draft; tapping Send again
    targets whatever pane is visible then.
  • The composer stays open while a send is in flight so the progress and the
    outcome are visible.

Testing

  • Unit suites: submission pipeline against a mock destination, dictation
    controller against a mock backend.
  • Live UI harness against a real tmux 3.6b server over SSH: typing,
    dictation, and submit end to end, verified by reading a marker back out
    of the remote pane, with scripted cleanup of the sessions it creates.
  • Keyboard continuity and dictation UI tests on the simulator.
  • Manual testing on device: terminal switching with the composer open,
    dictation across switches, attachment uploads, sends into live CLI
    agents.

Screenshots

Screenshot 1 Screenshot 2

Summary by CodeRabbit

New Features

  • Added a composer for drafting and submitting terminal messages.
  • Added dictation with live transcription, audio-level feedback, cancellation, and restart support.
  • Added image and file attachments with preparation status, previews, retry, removal, and upload progress.
  • Improved keyboard handoff between terminal input and the composer.
  • Enhanced attachment previews with clearer navigation and close controls.

Bug Fixes

  • Improved paste and key-event delivery with completion feedback.
  • Improved attachment loading and failure handling.

Accessibility

  • Added microphone and speech-recognition permission descriptions.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 31, 2026

Copy link
Copy Markdown

Deploying getremux with  Cloudflare Pages  Cloudflare Pages

Latest commit: 97183c5
Status: ✅  Deploy successful!
Preview URL: https://5d4f3a5d.getremux.pages.dev
Branch Preview URL: https://feature-voice-compose-bar.getremux.pages.dev

View logs

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 57930762-f8cf-40f0-9350-a38b86be92b6

📥 Commits

Reviewing files that changed from the base of the PR and between 4ee187e and fb8f824.

📒 Files selected for processing (1)
  • RemuxApp/Sources/Tmux/TmuxPaneSurface.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • RemuxApp/Sources/Tmux/TmuxPaneSurface.swift

📝 Walkthrough

Walkthrough

The pull request adds a SwiftUI composer with attachment handling, dictation, keyboard ownership, completion-aware terminal submission, app integration, permission descriptions, deterministic UI-test transport data, updated GhosttyKit wiring, and unit and UI tests. It removes the legacy attachment tray and pending preview components.

Changes

Composer and terminal interaction

Layer / File(s) Summary
Composer state, attachments, and submission
RemuxApp/Sources/Ghostty/GhosttyComposeBar.swift, RemuxApp/Sources/Ghostty/GhosttyComposerModel.swift, RemuxApp/Sources/Ghostty/GhosttyPendingAttachment.swift, RemuxApp/Sources/Ghostty/GhosttyComposerSubmissionController.swift, RemuxApp/Sources/Ghostty/GhosttyAttachmentPreviewSheet.swift, RemuxAppTests/GhosttyComposerSubmissionControllerTests.swift
Adds draft editing, attachment preparation and transfer, preview and retry actions, submission state, and terminal delivery orchestration.
Dictation backends and lifecycle
RemuxApp/Sources/Ghostty/GhosttyComposerDictationController.swift, RemuxApp/Sources/Ghostty/GhosttyComposerAudioCaptureSession.swift, RemuxApp/Sources/Ghostty/GhosttySpeechAnalyzerComposerDictationBackend.swift, RemuxApp/Sources/Ghostty/GhosttyDebugComposerDictationBackend.swift, RemuxAppTests/GhosttyComposerDictationControllerTests.swift
Adds legacy, debug, and Speech Analyzer dictation backends with audio capture, transcript updates, cancellation, authorization, and lifecycle tests.
Tracked terminal delivery and keyboard ownership
RemuxApp/Sources/Ghostty/GhosttyTerminalInputCoordinator.swift, RemuxApp/Sources/Ghostty/GhosttyTerminalResponderView.swift, RemuxApp/Sources/Tmux/TmuxSessionController.swift, RemuxApp/Sources/Tmux/TmuxPaneSurface.swift, RemuxApp/Sources/Tmux/TmuxTerminalScreenAdapter.swift, RemuxAppTests/GhosttyTerminalInputCoordinatorTests.swift, RemuxAppTests/TmuxSessionControllerClientSizeTests.swift
Adds terminal and composer keyboard ownership plus exact and literal input APIs that resolve after tmux command completion.
Composer presentation and app integration
RemuxApp/Sources/Ghostty/GhosttySurfaceScreen.swift, RemuxApp/Sources/Ghostty/GhosttyKeyboardChrome.swift, RemuxApp/Sources/App/RootView.swift, RemuxApp/Sources/Ghostty/GhosttyTerminalResponderFocusPolicy.swift, RemuxAppUITests/RemuxAppUITests.swift
Injects the shared composer into terminal sessions, embeds it in keyboard chrome, transfers responder focus, stops dictation during lifecycle changes, and adds composer UI and performance tests.
Build, permission, and test configuration
Remux.xcodeproj/project.pbxproj, RemuxApp/Info.plist, project.yml, RemuxApp/Sources/App/RemuxAppDependencies.swift, scripts/fetch_ghosttykit.sh
Registers composer sources and tests, removes legacy attachment UI references, adds dictation permission descriptions, supplies deterministic UI-test transport data, and updates the GhosttyKit release checksum.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • h3nock/remux#20: Overlaps in app integration, input-focus handling, keyboard chrome, RootView, and GhosttySurfaceScreen.
  • h3nock/remux#21: Shares changes to GhosttyKeyboardChrome, GhosttySurfaceScreen, RootView, and project wiring.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant GhosttyComposeBar
  participant GhosttyComposerModel
  participant GhosttyComposerDictationController
  participant GhosttySurfaceScreen
  participant TmuxTerminalScreenAdapter
  participant TmuxSessionController

  User->>GhosttyComposeBar: Enter text, attachments, or dictation
  GhosttyComposeBar->>GhosttyComposerModel: Update composer state
  GhosttyComposerModel->>GhosttyComposerDictationController: Start or stop dictation
  GhosttyComposerDictationController-->>GhosttyComposerModel: Publish transcript and dictation state
  User->>GhosttyComposeBar: Submit draft
  GhosttyComposeBar->>GhosttyComposerModel: Submit
  GhosttyComposerModel->>GhosttySurfaceScreen: Build destination and submission
  GhosttySurfaceScreen->>TmuxTerminalScreenAdapter: Send paste and Enter
  TmuxTerminalScreenAdapter->>TmuxSessionController: Submit tracked tmux input
  TmuxSessionController-->>TmuxTerminalScreenAdapter: Return command completion status
  TmuxTerminalScreenAdapter-->>GhosttySurfaceScreen: Return delivery status
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.34% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main change: adding a compose bar with voice dictation support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/voice-compose-bar

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (8)
RemuxApp/Sources/Ghostty/GhosttyComposeBar.swift (3)

217-226: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share one submission-state computation.

GhosttyComposeBar.submissionState duplicates composerSubmissionState in RemuxApp/Sources/Ghostty/GhosttySurfaceScreen.swift (lines 684-693). Both expressions must stay identical, because the screen uses its copy for isInteractionLocked while the bar uses its copy for the Send button. A future change to one send-eligibility rule will silently desynchronize the dock lock from the button state.

Expose the computation once, for example as a computed property on GhosttyComposerModel or a static function on GhosttyComposeBarSubmissionState, and call it from both sites.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@RemuxApp/Sources/Ghostty/GhosttyComposeBar.swift` around lines 217 - 226,
Centralize the submission-state computation currently in
GhosttyComposeBar.submissionState and
GhosttySurfaceScreen.composerSubmissionState into one shared computed property
or static helper on GhosttyComposerModel or GhosttyComposeBarSubmissionState.
Update both call sites to use that shared implementation while preserving the
existing submitting, terminal-input, content, and attachment-readiness rules.

842-860: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Decode chip preview images once.

attachmentPreview calls UIImage(data:) on each body evaluation for every attachment chip. Draft keystrokes re-render this subtree, so each keystroke re-decodes every chip image on the main thread.

Store the decoded UIImage (or a SwiftUI Image) with the attachment preview payload, or cache it per attachment ID.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@RemuxApp/Sources/Ghostty/GhosttyComposeBar.swift` around lines 842 - 860,
Update attachmentPreview(_:) to avoid constructing UIImage(data:) during every
SwiftUI body evaluation. Cache the decoded image per attachment ID or store it
alongside the preview payload, then reuse that cached value when rendering image
attachments while preserving the existing loading and file preview branches.

655-672: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Avoid mutating the text view from sizeThatFits.

SwiftUI can call sizeThatFits multiple times with different proposals during layout, so setting textView.isScrollEnabled here can make the scroll flag inconsistent with the final applied height. Compute the clamped height in sizeThatFits and apply the scroll flag in updateUIView, using a stored measurement for the committed layout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@RemuxApp/Sources/Ghostty/GhosttyComposeBar.swift` around lines 655 - 672, The
sizeThatFits method currently mutates textView.isScrollEnabled during
measurement. Keep sizeThatFits limited to calculating and returning the clamped
height, store the measured-height or overflow state needed for the committed
layout, and update isScrollEnabled in updateUIView using that stored measurement
and the applied height.
RemuxApp/Sources/App/RemuxAppDependencies.swift (1)

328-341: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the coupling between this transcript and the tmux startup command order.

uiTestingTransportChunks() hardcodes command numbers 1 through 9 and a fixed reply order for the session, window, and pane queries. If the controller's startup sequence gains, removes, or reorders a command, this fixture no longer matches. The failure surfaces as a UI test that waits for terminal.input.ready until timeout, which is hard to diagnose.

Add a short comment that names the startup sequence this transcript mirrors, so a future change to that sequence points here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@RemuxApp/Sources/App/RemuxAppDependencies.swift` around lines 328 - 341, The
hardcoded transcript in uiTestingTransportChunks() must document its dependency
on the controller’s tmux startup command order. Add a short comment beside the
transcript identifying the startup sequence it mirrors, including the session,
window, and pane query ordering, so future changes direct maintainers to update
this fixture.
RemuxApp/Sources/Ghostty/GhosttyComposerAudioCaptureSession.swift (1)

160-207: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Confirm that start is never re-entered on a stopped session.

precondition(state == .idle) traps in release builds. A caller that reuses one GhosttyComposerAudioCaptureSession after cancel() or a completed drain crashes the app. Both current backends create a new session per run, so the contract holds today. Consider throwing GhosttyComposerDictationError instead of trapping, to keep a future reuse from being fatal.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@RemuxApp/Sources/Ghostty/GhosttyComposerAudioCaptureSession.swift` around
lines 160 - 207, Update start in GhosttyComposerAudioCaptureSession to replace
the fatal precondition on state == .idle with a thrown
GhosttyComposerDictationError for any non-idle state. Preserve normal
initialization for idle sessions and ensure reuse after cancel or completed
drain fails gracefully without trapping.
RemuxAppTests/GhosttyPendingAttachmentTests.swift (1)

223-231: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Strengthen the failed-preparation assertion.

pasteboardImagePlaceholder() has no payload, so transferSource is nil before the failure is applied. The test passes even if .failed does not suppress the transfer source. Build the attachment from a state that has a payload, then mark it .failed, so the assertion covers the failure gate.

♻️ Proposed test change
     func testFailedPreparationCannotProduceTransferSource() {
         let attachment = GhosttyPendingAttachment
-            .pasteboardImagePlaceholder()
+            .file(url: URL(fileURLWithPath: "/tmp/report.txt"))
             .updating(detail: "Couldn’t load", preparationState: .failed)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@RemuxAppTests/GhosttyPendingAttachmentTests.swift` around lines 223 - 231,
Update testFailedPreparationCannotProduceTransferSource to construct the
attachment from an initial state containing a valid transfer payload, then apply
preparationState: .failed. Keep the existing assertions, ensuring transferSource
would be non-nil before failure so its nil result verifies the failed-state
suppression.
RemuxApp/Sources/Ghostty/GhosttySpeechAnalyzerComposerDictationBackend.swift (1)

209-255: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Finish the input continuation on every abandoned start path.

If analyzer.start(inputSequence:) throws, or if the isDesired() guards fail after the analyzer starts, inputContinuation is never finished. The AsyncStream then stays open until the run object is released. The explicit inputContinuation.finish() in the capture-start catch shows the intent. Add the same call to the guard failures and to the outer catch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@RemuxApp/Sources/Ghostty/GhosttySpeechAnalyzerComposerDictationBackend.swift`
around lines 209 - 255, Ensure inputContinuation is finished on every abandoned
startup path in the surrounding analyzer-start flow: add
inputContinuation.finish() before returning from both activeRun/isDesired()
guard failures, and add it in the outer catch that handles
analyzer.start(inputSequence:) errors. Preserve the existing
captureSession-start catch cleanup.
RemuxApp/Sources/Ghostty/GhosttyComposerDictationController.swift (1)

721-731: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a deadline for the transcribing phase.

finish cancels startDeadlineTask and moves the controller to .transcribing. After that, no timer exists. If the backend never emits .completed or .recognitionFailed, the composer stays in .transcribing and the user cannot send the draft. The legacy backend reaches this state after recognitionTask.finish(), and the analyzer backend reaches it while awaiting finalizeAndFinishThroughEndOfInput(). Schedule a finalization deadline that fails the session with the existing "Dictation stopped unexpectedly" message.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@RemuxApp/Sources/Ghostty/GhosttyComposerDictationController.swift` around
lines 721 - 731, The finish flow in GhosttyComposerDictationController currently
enters .transcribing without a fallback deadline. Add a transcribing-phase
deadline in finish(afterTranscription:) that replaces the canceled
startDeadlineTask and fails the session using the existing “Dictation stopped
unexpectedly” handling if .completed or .recognitionFailed is not received;
preserve normal backend completion behavior and existing callback cleanup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Remux.xcodeproj/project.pbxproj`:
- Line 19: Gate GhosttySpeechAnalyzerComposerDictationBackend.swift with a
compile-time SDK/compiler availability condition so it is not compiled or
registered for targets whose SDK lacks SpeechAnalyzer. Preserve
GhosttyLegacyComposerDictationBackend as the fallback whenever the gated backend
is unavailable, while retaining the SpeechAnalyzer implementation for supported
SDKs.

In `@RemuxApp/Sources/Ghostty/GhosttyComposerModel.swift`:
- Around line 105-111: Replace the isolated deinit declaration in
GhosttyComposerModel with a deinitialization implementation compatible with the
project’s Swift 6.0 setting, or explicitly raise the required Swift/toolchain
version to 6.2+ consistently across the project if isolated deinit is required.
Preserve cancellation of submissionTask and attachmentPreparationJobs plus
GhosttyAttachmentStagingStore.cleanup(attachments).
- Around line 333-348: The performSubmission flow must bound both
destination.sendPaste(message) and destination.sendEnter() awaits with
deadlines. Handle timeout or failed completion by calling finishDelivery with
the appropriate non-submitted result, ensuring isSubmitting and submissionTask
are cleared so later submissions remain available; preserve the existing
successful paste, settlement-delay, and submit behavior.

In
`@RemuxApp/Sources/Ghostty/GhosttySpeechAnalyzerComposerDictationBackend.swift`:
- Around line 39-77: Serialize start, finish, cancel, and cancelAll through a
single ordered command stream instead of creating independent Tasks in each
entry point. Update the surrounding backend state flow so commands invoke the
actor in submission order, ensuring State.finish always observes the preceding
State.start and preserves the expected stop/result behavior.

In `@RemuxApp/Sources/Ghostty/GhosttySurfaceScreen.swift`:
- Around line 407-446: Update the bottom safe-area inset host in the
GhosttySurfaceScreen view to size from the measured expanded chrome height
rather than the fixed GhosttyKeyboardChromeSizing.baselineHeight. Preserve the
existing GhosttyKeyboardChrome layout, padding, and
GhosttyBottomChromeHeightPreferenceKey measurement while ensuring attachments
and expanded draft content increase the inset accordingly.
- Around line 150-170: Make the composer update publisher stable across body
evaluations instead of rebuilding it in the computed composerUpdates property.
Store the merged presented, submitting, and attachments publisher once in
view-owned state or an equivalent stable box, while preserving the existing
isSelected gating and dropFirst behavior; alternatively observe composer
directly with `@ObservedObject` and remove the manual composerRevision
passthrough.

In `@RemuxApp/Sources/Ghostty/GhosttyTerminalInputCoordinator.swift`:
- Around line 43-53: Update toggleKeyboard(owner:isOwnerAvailable:) to validate
ownership and transfer availability before calling showSystemKeyboard,
particularly when the requested owner is .composer and .terminal currently owns
the system keyboard. Prevent the shown transition from being projected unless
ownership can actually move to the requested owner, while preserving the
existing hide behavior for the current owner.

In `@RemuxApp/Sources/Tmux/TmuxPaneSurface.swift`:
- Around line 109-122: Protect trackedWrite access in performTrackedWrite and
the Ghostty write_cb path with MainActor-isolated storage or an equivalent
synchronization boundary, ensuring callbacks cannot concurrently read or clear
the slot while completing tracked input. Replace the precondition(trackedWrite
== nil) overlap crash by completing the new request with false and preserving
the existing trackedWrite until its original send finishes.

In `@RemuxApp/Sources/Tmux/TmuxSessionController.swift`:
- Around line 718-762: The tracked-input flow in sendTrackedInput needs a
timeout fallback. After storing each token in requestsByToken, schedule a
deadline that removes that token only if it is still pending and invokes its
completion with false; cancel or make the timeout harmless when
handleCommandCompletion or session-failure paths have already removed the entry,
preserving existing success and failure handling.

In `@RemuxAppUITests/RemuxAppUITests.swift`:
- Around line 1163-1194: Update the probe-delta calculations around the raw
samples used for dictation and typing so each UInt64 counter is validated as
nondecreasing before performing checked subtraction, reporting an XCTest failure
when a later sample is smaller rather than trapping. Apply this to evals,
barEvals, and passes while preserving the existing metric calculations, then
remove the ineffective typingEvals >= 0 assertion because the raw-sample
monotonicity assertion replaces it.

---

Nitpick comments:
In `@RemuxApp/Sources/App/RemuxAppDependencies.swift`:
- Around line 328-341: The hardcoded transcript in uiTestingTransportChunks()
must document its dependency on the controller’s tmux startup command order. Add
a short comment beside the transcript identifying the startup sequence it
mirrors, including the session, window, and pane query ordering, so future
changes direct maintainers to update this fixture.

In `@RemuxApp/Sources/Ghostty/GhosttyComposeBar.swift`:
- Around line 217-226: Centralize the submission-state computation currently in
GhosttyComposeBar.submissionState and
GhosttySurfaceScreen.composerSubmissionState into one shared computed property
or static helper on GhosttyComposerModel or GhosttyComposeBarSubmissionState.
Update both call sites to use that shared implementation while preserving the
existing submitting, terminal-input, content, and attachment-readiness rules.
- Around line 842-860: Update attachmentPreview(_:) to avoid constructing
UIImage(data:) during every SwiftUI body evaluation. Cache the decoded image per
attachment ID or store it alongside the preview payload, then reuse that cached
value when rendering image attachments while preserving the existing loading and
file preview branches.
- Around line 655-672: The sizeThatFits method currently mutates
textView.isScrollEnabled during measurement. Keep sizeThatFits limited to
calculating and returning the clamped height, store the measured-height or
overflow state needed for the committed layout, and update isScrollEnabled in
updateUIView using that stored measurement and the applied height.

In `@RemuxApp/Sources/Ghostty/GhosttyComposerAudioCaptureSession.swift`:
- Around line 160-207: Update start in GhosttyComposerAudioCaptureSession to
replace the fatal precondition on state == .idle with a thrown
GhosttyComposerDictationError for any non-idle state. Preserve normal
initialization for idle sessions and ensure reuse after cancel or completed
drain fails gracefully without trapping.

In `@RemuxApp/Sources/Ghostty/GhosttyComposerDictationController.swift`:
- Around line 721-731: The finish flow in GhosttyComposerDictationController
currently enters .transcribing without a fallback deadline. Add a
transcribing-phase deadline in finish(afterTranscription:) that replaces the
canceled startDeadlineTask and fails the session using the existing “Dictation
stopped unexpectedly” handling if .completed or .recognitionFailed is not
received; preserve normal backend completion behavior and existing callback
cleanup.

In
`@RemuxApp/Sources/Ghostty/GhosttySpeechAnalyzerComposerDictationBackend.swift`:
- Around line 209-255: Ensure inputContinuation is finished on every abandoned
startup path in the surrounding analyzer-start flow: add
inputContinuation.finish() before returning from both activeRun/isDesired()
guard failures, and add it in the outer catch that handles
analyzer.start(inputSequence:) errors. Preserve the existing
captureSession-start catch cleanup.

In `@RemuxAppTests/GhosttyPendingAttachmentTests.swift`:
- Around line 223-231: Update testFailedPreparationCannotProduceTransferSource
to construct the attachment from an initial state containing a valid transfer
payload, then apply preparationState: .failed. Keep the existing assertions,
ensuring transferSource would be non-nil before failure so its nil result
verifies the failed-state suppression.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bd476e0b-cb8d-4e56-bc5e-32c6719fa7d2

📥 Commits

Reviewing files that changed from the base of the PR and between 00c3fe3 and ebdff60.

📒 Files selected for processing (39)
  • Remux.xcodeproj/project.pbxproj
  • RemuxApp/Info.plist
  • RemuxApp/Sources/App/RemuxAppDependencies.swift
  • RemuxApp/Sources/App/RootView.swift
  • RemuxApp/Sources/Ghostty/GhosttyAttachmentNotice.swift
  • RemuxApp/Sources/Ghostty/GhosttyAttachmentPasteboardSnapshot.swift
  • RemuxApp/Sources/Ghostty/GhosttyAttachmentPreviewSheet.swift
  • RemuxApp/Sources/Ghostty/GhosttyAttachmentPreviewStyle.swift
  • RemuxApp/Sources/Ghostty/GhosttyAttachmentTransfer.swift
  • RemuxApp/Sources/Ghostty/GhosttyAttachmentTray.swift
  • RemuxApp/Sources/Ghostty/GhosttyComposeBar.swift
  • RemuxApp/Sources/Ghostty/GhosttyComposerAudioCaptureSession.swift
  • RemuxApp/Sources/Ghostty/GhosttyComposerDictationController.swift
  • RemuxApp/Sources/Ghostty/GhosttyComposerModel.swift
  • RemuxApp/Sources/Ghostty/GhosttyComposerSubmissionController.swift
  • RemuxApp/Sources/Ghostty/GhosttyDebugComposerDictationBackend.swift
  • RemuxApp/Sources/Ghostty/GhosttyKeyboardChrome.swift
  • RemuxApp/Sources/Ghostty/GhosttyManagedSurface.swift
  • RemuxApp/Sources/Ghostty/GhosttyPendingAttachment.swift
  • RemuxApp/Sources/Ghostty/GhosttyPendingAttachmentPreview.swift
  • RemuxApp/Sources/Ghostty/GhosttySpeechAnalyzerComposerDictationBackend.swift
  • RemuxApp/Sources/Ghostty/GhosttySurfaceScreen.swift
  • RemuxApp/Sources/Ghostty/GhosttySurfaceStatusOverlay.swift
  • RemuxApp/Sources/Ghostty/GhosttyTerminalInputCoordinator.swift
  • RemuxApp/Sources/Ghostty/GhosttyTerminalResponderFocusPolicy.swift
  • RemuxApp/Sources/Ghostty/GhosttyTerminalResponderView.swift
  • RemuxApp/Sources/Ghostty/GhosttyTerminalScreenModeling.swift
  • RemuxApp/Sources/Ghostty/GhosttyTerminalViewportCoordinator.swift
  • RemuxApp/Sources/Tmux/TmuxPaneSurface.swift
  • RemuxApp/Sources/Tmux/TmuxSessionController.swift
  • RemuxApp/Sources/Tmux/TmuxTerminalScreenAdapter.swift
  • RemuxAppTests/GhosttyComposerDictationControllerTests.swift
  • RemuxAppTests/GhosttyComposerSubmissionControllerTests.swift
  • RemuxAppTests/GhosttyPendingAttachmentTests.swift
  • RemuxAppTests/GhosttyTerminalInputCoordinatorTests.swift
  • RemuxAppTests/GhosttyTerminalResponderFocusPolicyTests.swift
  • RemuxAppTests/TmuxSessionControllerClientSizeTests.swift
  • RemuxAppUITests/RemuxAppUITests.swift
  • project.yml
💤 Files with no reviewable changes (2)
  • RemuxApp/Sources/Ghostty/GhosttyAttachmentTray.swift
  • RemuxApp/Sources/Ghostty/GhosttyPendingAttachmentPreview.swift

Comment thread Remux.xcodeproj/project.pbxproj
Comment thread RemuxApp/Sources/Ghostty/GhosttyComposerModel.swift
Comment on lines +333 to +348
let didPaste = await destination.sendPaste(message)
guard didPaste else {
finishDelivery(.pasteRejected, session: session, destination: destination)
return
}

do {
try await Task.sleep(for: Self.pasteSettlementDelay)
} catch {
finishDelivery(.pastedAwaitingSubmit, session: session, destination: destination)
return
}

let result: GhosttyComposerSubmissionController.DraftResult =
await destination.sendEnter() ? .submitted : .pastedAwaitingSubmit
finishDelivery(result, session: session, destination: destination)

@coderabbitai coderabbitai Bot Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether tracked terminal delivery guarantees completion on failure paths.
rg -n -C8 'sendTrackedInput|sendTrackedLiteralInput|sendComposerEnter|sendTerminalPaste' --glob '*.swift'

Repository: h3nock/remux

Length of output: 150


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Tracked files involving GhosttyComposerModel.swift:"
git ls-files | rg 'GhosttyComposerModel\.swift|Ghostty|Ghostty' || true

file="RemuxApp/Sources/Ghostty/GhosttyComposerModel.swift"
if [ -f "$file" ]; then
  echo
  echo "Outline:"
  ast-grep outline "$file" || true
  echo
  echo "Relevant section lines 220-370:"
  sed -n '220,370p' "$file" | nl -ba -v220
fi

echo
echo "Search for submission/isSubmitting/pasteSettlement/sendPaste/sendEnter/composer:"
rg -n -C4 'isSubmitting|submissionTask|performSubmission|sendPaste|sendEnter|pasteSettlementDelay|finishDelivery|sendComposerEnter|Send|submission' --glob '*.swift' RemuxApp || true

Repository: h3nock/remux

Length of output: 6617


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Relevant GhosttyComposerModel.swift lines:"
sed -n '1,420p' RemuxApp/Sources/Ghostty/GhosttyComposerModel.swift

echo
echo "GhosttyComposerSubmissionController.swift outline:"
ast-grep outline RemuxApp/Sources/Ghostty/GhosttyComposerSubmissionController.swift || true

echo
echo "GhosttyComposerSubmissionController.swift lines 1-260:"
sed -n '1,260p' RemuxApp/Sources/Ghostty/GhosttyComposerSubmissionController.swift

Repository: h3nock/remux

Length of output: 15845


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files containing GhosttyComposerSubmissionDestination:"
rg -n -C6 'GhosttyComposerSubmissionDestination|destination\.sendPaste|destination\.sendEnter|prepareTerminalInput|sendEnter|sendPaste|sendTerminalPaste|composer.*paste|sendComposer' --glob '*.swift' .

echo
echo "Find GhosttyTerminalResponderTextInputShim implementation around paste-enter paths:"
file="RemuxApp/Sources/Ghostty/GhosttyTerminalResponderTextInputShim.swift"
if [ -f "$file" ]; then
  sed -n '1,260p' "$file"
fi

Repository: h3nock/remux

Length of output: 50368


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "TmuxPaneSurface performInputAwaitingCommandCompletion:"
sed -n '260,340p' RemuxApp/Sources/Tmux/TmuxPaneSurface.swift

echo
echo "GhosttyManagedSurface sendKeyEventAwaitingCommandCompletion/sendKeyEvent:"
sed -n '80,180p' RemuxApp/Sources/Ghostty/GhosttyManagedSurface.swift

echo
echo "GhosttySurfaceScreen sendTerminalText/sendKeyEvent/sendTerminalPaste:"
sed -n '1488,1578p' RemuxApp/Sources/Ghostty/GhosttySurfaceScreen.swift

echo
echo "GhosttyTerminalScreenModeling async awaiting API body candidates:"
file="RemuxApp/Sources/Ghostty/GhosttyTerminalScreenModeling.swift"
sed -n '1,140p' "$file"

Repository: h3nock/remux

Length of output: 14633


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "TmuxPaneSurface lifecycle/callback completion paths:"
rg -n -C8 'lifecycle|CallbackBox|performTrackedWrite|prepareForPermanentRemoval|onRendererFailure|rendererDidFail|completion|resume' --glob '*.swift' RemuxApp/Sources/Tmux RemuxApp/Sources/Ghostty | head -n 240

echo
echo "Focused composer status and isSubmitting usages:"
rg -n -C4 'composerStatus|statusMessage|isSubmitting|Submit|sending|progress' --glob '*.swift' RemuxApp/Sources/Ghostty RemuxApp/Sources/Tmux | head -n 220

Repository: h3nock/remux

Length of output: 39860


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "TmuxPaneSurface lifecycle enum and performTrackedWrite implementation:"
rg -n -C10 'enum Lifecycle|Lifecycle\.|performTrackedWrite|withCheckedContinuation|prepareForPermanentRemoval|rendererDidFail|onRendererFailure|sendPasteAwaitingCommandCompletion|sendKeyEventAwaitingCommandCompletion' --glob '*.swift' RemuxApp/Sources/Tmux RemuxApp/Sources/Ghostty

Repository: h3nock/remux

Length of output: 50368


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "GhosttyKitControlSurface sendPaste/sendKeyEvent implementation:"
sed -n '190,230p' RemuxApp/Sources/Ghostty/GhosttyKitControlSurface.swift

echo
echo "Read-only verifier: composer pending-state transition logic"
python3 - <<'PY'
from pathlib import Path
import re

path = Path("RemuxApp/Sources/Ghostty/GhosttyComposerModel.swift")
src = path.read_text()

patterns = {
    "submit_guard_isSubmitting": re.search(r"func submit\(.*?\n\s*guard !isSubmitting\s*\) else\s*\{\s*\n\s*return\s*\n\s*\}", src, re.S),
    "isSubmitting_set_before_task": re.search(r"isSubmitting\s*=\s*true[\s\S]{0,800}submissionTask\s*=\s*Task", src),
    "finish_before_clears_state": re.search(r"submissionTask\s*=\s*nil[\s\S]{0,300}isSubmitting\s*=\s*false", src),
    "finish_delivery_clears_state": re.search(r"submissionTask\s*=\s*nil[\s\S]{0,600}isSubmitting\s*=\s*false", src),
    "await_send_paste": re.search(r"let\s+didPaste\s*=.*?destination\.sendPaste\(message\)", src, re.S),
    "await_send_enter": re.search(r"await\s+destination\.sendEnter\(\)", src),
}

for name, pat in patterns.items():
    print(f"{name}: {pat is not None}")

# Extract the two unchecked awaits and whether they are followed by immediate finishDelivery.
idx_paste = src.find("destination.sendPaste(message)")
idx_enter = src.find("destination.sendEnter()", idx_paste)
print("paste_await_index:", idx_paste)
print("enter_await_index:", idx_enter)
print("enter_is_last_direct_await_before_finish_delivery:", "finishDelivery(result" in src[idx_enter:])
PY

Repository: h3nock/remux

Length of output: 1787


Add a bound to the composer input awaits.

performSubmission awaits destination.sendPaste(message) and destination.sendEnter(). These paths wait on tracked tmux command completion, and an unbounded await can leave isSubmitting and submissionTask set indefinitely if the pane becomes unavailable or the completion does not fire. Use a deadline around each await and map expiry to finishDelivery, so normal future submissions remain available.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@RemuxApp/Sources/Ghostty/GhosttyComposerModel.swift` around lines 333 - 348,
The performSubmission flow must bound both destination.sendPaste(message) and
destination.sendEnter() awaits with deadlines. Handle timeout or failed
completion by calling finishDelivery with the appropriate non-submitted result,
ensuring isSubmitting and submissionTask are cleared so later submissions remain
available; preserve the existing successful paste, settlement-delay, and submit
behavior.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed the await is unbounded in the specific case where tmux stops replying while the transport stays open. Every realistic failure path does resolve it — disconnect and detach complete all outstanding tracked input — and reconnecting recovers today. Tracking a proper bound in #24, with one important amendment to the suggested fix: expiry has to resolve to the unconfirmed outcome rather than a plain failure, because a timed-out send may still land and "safe to retry" semantics would invite a double submission.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@h3nock, agreed. The finding remains valid, but #24 is the correct scope.

A timeout must produce an unconfirmed delivery outcome. It must keep the draft and avoid retry-safe failure semantics. A timed-out paste or Enter can still reach tmux after the deadline.

🐇


✏️ Learnings added
Learnt from: h3nock
Repo: h3nock/remux PR: 23
File: RemuxApp/Sources/Ghostty/GhosttyComposerModel.swift:333-348
Timestamp: 2026-07-31T12:51:39.768Z
Learning: In `RemuxApp/Sources/Ghostty/GhosttyComposerModel.swift`, tracked tmux input completion normally resolves on disconnect and detach. A remaining unbounded-await case exists when the tmux transport stays connected but tmux stops replying. A deadline for `destination.sendPaste(_:)` or `destination.sendEnter()` must resolve to an unconfirmed-delivery outcome, preserve the draft, and not imply that retrying is safe, because the timed-out input may still reach tmux. This follow-up is tracked in GitHub issue `#24`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

Comment thread RemuxApp/Sources/Ghostty/GhosttySurfaceScreen.swift
Comment thread RemuxApp/Sources/Ghostty/GhosttySurfaceScreen.swift
Comment thread RemuxApp/Sources/Ghostty/GhosttyTerminalInputCoordinator.swift
Comment thread RemuxApp/Sources/Tmux/TmuxPaneSurface.swift
Comment on lines +718 to +762
private func sendTrackedInput(
paneID: TmuxPaneID,
_ bytes: Data,
transport: TrackedInputTransport,
completion: @escaping @Sendable (Bool) -> Void
) -> Bool {
guard !bytes.isEmpty else { return false }
queue.async { [self, bytes] in
guard let client, outboundSink != nil, !shuttingDown else {
reportRequestFailure(.sendInput)
completion(false)
return
}
var token: UInt64 = 0
let result = bytes.withUnsafeBytes { buffer in
let pointer = buffer.bindMemory(to: UInt8.self).baseAddress
return switch transport {
case .exact:
ghostty_tmux_client_send_pane_input_tracked(
client,
paneID.rawValue,
pointer,
buffer.count,
&token
)
case .literal:
ghostty_tmux_client_send_pane_literal_input_tracked(
client,
paneID.rawValue,
pointer,
buffer.count,
&token
)
}
}
guard result == GHOSTTY_TMUX_RESULT_OK else {
reportImmediateFailure(result, request: .sendInput)
completion(false)
return
}
requestsByToken[token] = .trackedInput(completion)
_ = drainOutbound()
}
return true
}

@coderabbitai coderabbitai Bot Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Bound the tracked-input completion with a timeout.

A tracked input resolves only through handleCommandCompletion or one of the session-failure paths. If tmux accepts the command but never emits %begin/%end for that token while the transport stays open, the stored completion never runs.

The awaiting caller is the composer: GhosttyComposerModel.performSubmission awaits destination.sendPaste(message), keeps isSubmitting true, and GhosttySurfaceScreen locks the dock through isInteractionLocked. A single stalled response therefore leaves the composer and dock locked with no recovery other than disconnecting.

Add a deadline for each tracked token. On expiry, remove the entry and complete with false, so the composer reports the destination-aware failure and unlocks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@RemuxApp/Sources/Tmux/TmuxSessionController.swift` around lines 718 - 762,
The tracked-input flow in sendTrackedInput needs a timeout fallback. After
storing each token in requestsByToken, schedule a deadline that removes that
token only if it is still pending and invokes its completion with false; cancel
or make the timeout harmless when handleCommandCompletion or session-failure
paths have already removed the entry, preserving existing success and failure
handling.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed on the gap, with the same caveat as the sibling comment: all realistic failure paths already drain outstanding tracked input (failOutstandingTrackedInput on disconnect and detach), so this only bites when tmux stops replying on an otherwise healthy transport — and reconnecting recovers. Tracking a bounded version in #24; the deadline design there is Enter-leg-only so that expiry maps onto the existing unconfirmed outcome instead of falsely reporting a retry-safe failure.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@h3nock, agreed. The existing failure paths drain tracked input on disconnect, detach, shutdown, and client failure.

An Enter-leg-only deadline in #24 is the correct boundary. It can release the UI while preserving the unconfirmed outcome when tmux may have accepted the message. I will leave this finding open for the follow-up work.

🐇


✏️ Learnings added
Learnt from: h3nock
Repo: h3nock/remux PR: 23
File: RemuxApp/Sources/Tmux/TmuxSessionController.swift:718-762
Timestamp: 2026-07-31T12:51:26.107Z
Learning: In `RemuxApp/Sources/Tmux/TmuxSessionController.swift`, `failOutstandingTrackedInput()` already resolves pending tracked-input completions during disconnect, detach, shutdown, and client-failure paths. A future bounded deadline should apply only to the Enter leg and map expiry to an unconfirmed outcome, because tmux may have accepted the message and a retry may not be safe.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

Comment thread RemuxAppUITests/RemuxAppUITests.swift Outdated
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 31, 2026

Copy link
Copy Markdown

Deploying remux with  Cloudflare Pages  Cloudflare Pages

Latest commit: 97183c5
Status: ✅  Deploy successful!
Preview URL: https://253bd334.remux-agx.pages.dev
Branch Preview URL: https://feature-voice-compose-bar.remux-agx.pages.dev

View logs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/fetch_ghosttykit.sh`:
- Around line 8-9: Update scripts/fetch_ghosttykit.sh to persist a marker
containing release_tag and asset_sha256, compare that marker with the pinned
values before the existing early exit, and replace the installed
GhosttyKit.xcframework when they differ. Preserve the no-op behavior for
matching installations and the --force override, and ensure the marker is
updated after a successful installation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fc9d593f-f538-4a2d-a076-5df4a6c297cf

📥 Commits

Reviewing files that changed from the base of the PR and between ebdff60 and 4ee187e.

📒 Files selected for processing (1)
  • scripts/fetch_ghosttykit.sh

Comment thread scripts/fetch_ghosttykit.sh
@h3nock

h3nock commented Jul 31, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai We've replied to each of your review comments in their threads: two findings are fixed (50478f9, 97183c5), the tracked-write concern is addressed with a contract assertion (fb8f824) plus the analysis in that thread, the unbounded-send pair is tracked in #24, and the remaining threads contain rebuttals with the specific mechanisms. Please take a look at the thread replies and let us know if anything doesn't hold up.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="h3nock/remux"
pr_number="23"

echo "== Pull request review threads =="
gh api graphql \
  -F owner="${repo%%/*}" \
  -F name="${repo#*/}" \
  -F number="$pr_number" \
  -f query='
query($owner: String!, $name: String!, $number: Int!) {
  repository(owner: $owner, name: $name) {
    pullRequest(number: $number) {
      reviewThreads(first: 100) {
        nodes {
          id
          isResolved
          isOutdated
          path
          line
          comments(first: 30) {
            nodes {
              author { login }
              body
              createdAt
              url
              commit { oid }
            }
          }
        }
      }
    }
  }
}' \
  --jq '.data.repository.pullRequest.reviewThreads.nodes[]
    | select(any(.comments.nodes[]; .author.login == "coderabbitai"))
    | {
        id,
        isResolved,
        isOutdated,
        path,
        line,
        comments: [.comments.nodes[] | {author: .author.login, body, url, commit: .commit.oid}]
      }'

echo
echo "== Issue `#24` =="
gh issue view 24 --repo "$repo" --json number,title,state,body,url,author

echo
echo "== Cited commits =="
for sha in 50478f9 97183c5 fb8f824; do
  echo "--- $sha ---"
  gh api "repos/$repo/commits/$sha" \
    --jq '{sha: .sha, message: .commit.message, files: [.files[] | {filename, status, additions, deletions, patch}]}'
done

echo
echo "== Current relevant source outlines =="
for name in TmuxPaneSurface.swift TmuxSessionController.swift GhosttyComposerModel.swift; do
  file="$(fd -a -t f "^${name}$" . | head -n 1 || true)"
  if [ -n "$file" ]; then
    echo "--- $file ---"
    ast-grep outline "$file" --items all
  fi
done

Length of output: 115016


@h3nock, I verified the thread replies, commits, and #24. They hold up.

  • 50478f9 fixes the unsigned probe-counter subtraction. probeDelta reports a regression through XCTest instead of trapping.
  • 97183c5 records GhosttyKit provenance and reinstalls stale pinned releases. It preserves local builds unless --force is used.
  • fb8f824 makes the writeCallback presentation-thread contract explicit with a debug assertion. With the documented callback contract and serialized submission flow, the trackedWrite race and overlap concerns do not apply.
  • The controller state machine enforces the dictation ordering that the backend task scheduling alone does not guarantee.
  • The scoped composer observation and fixed viewport baseline are intentional. The cited mechanisms preserve the required performance and terminal-resize behavior.

The two remaining timeout findings are correctly tracked in #24. The issue has the right constraints: apply an initial deadline only to the Enter leg, preserve the unconfirmed-delivery outcome, and do not report a paste timeout as retry-safe failure.

I do not see any remaining objection that requires a change in this PR. 🐇

You are interacting with an AI system.

@h3nock
h3nock merged commit b3a3e5f into main Jul 31, 2026
5 checks passed
@h3nock h3nock mentioned this pull request Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant