Skip to content

Open diff viewer before rendering large git diffs#5016

Merged
lawrencecchen merged 14 commits into
mainfrom
task-cmux-open-diff-nonblocking-loading
May 31, 2026
Merged

Open diff viewer before rendering large git diffs#5016
lawrencecchen merged 14 commits into
mainfrom
task-cmux-open-diff-nonblocking-loading

Conversation

@lawrencecchen
Copy link
Copy Markdown
Contributor

@lawrencecchen lawrencecchen commented May 30, 2026

Summary

  • Opens selected git diff viewer pages as pending HTML before generating large git diff output.
  • Replaces the pending page after diff rendering completes and keeps source/repo/base navigation deferred.
  • Adds regression coverage that blocks fake git diff and verifies browser.open_split happens first.

Testing

  • git diff --check
  • ./scripts/reload.sh --tag diffnb
  • Not run locally: Xcode tests are prohibited on the user Mac by repo policy.

Note

Medium Risk
Large change to CLI diff HTML lifecycle and blocking completion semantics; behavior is covered by new integration tests but touches browser open timing and deferred error/fallback paths.

Overview
Git diff opens in the browser before heavy git diff work finishes: the CLI serves a full pending viewer page (toolbar, spinner, poll-for-replace), then redirects to the real diff when generation completes. .lastTurn stays synchronous; other sources defer the selected page and background-fill alternate source/repo/base pages.

Deferred completion is centralized (completeDeferred, per-page fallbacks, manifest refresh on cache miss) so CLI/JSON output and browser.open_split report the final path, URL, title, and source after the selected diff is ready.

Adds diff-viewer-only shortcuts (j/k, gg, /, etc.) in the embedded HTML, cmux.json (bare first strokes), settings UI, schema, docs, and strings; marks them as browser-content so they do not steal global chord routing.

New tests cover opening while git is blocked, redirect resolution, shortcut payload from config, and preserving the unstaged-empty error when fallbacks fail.

Reviewed by Cursor Bugbot for commit f042011. Bugbot is set up for automated code reviews on this repo. Configure here.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag @codesmith with what you need. Autofix is disabled.


Summary by cubic

Open the diff viewer instantly with a full UI and pending state while large git diffs render in the background, then auto-redirect to the final page when ready. The selected source completes first, .lastTurn remains synchronous, and CLI/JSON now report the finalized viewer path/URL/title/source.

  • New Features

    • Open the full viewer (toolbar + viewer) with a pending spinner; in-page poll/replace upgrades to the real diff; reduced-motion aware.
    • Add configurable diff viewer shortcuts (J/K, gg/G, /) as browser-content with bare first strokes; scoped to diff viewer in cmux.json/schema, recorder enforces modifiers elsewhere; docs and localized “Loading diff: %@” added.
  • Bug Fixes

    • Preserve deferred failure semantics and per-page fallbacks so the selected page’s error stays correct; fix nil-coalescing crash in deferred fallback.
    • Refresh HTTP manifest on per-path cache miss so replacement pages load without restart; tests follow the deferred redirect and verify final viewer HTML/patch.
    • Fix deferred completion reporting so CLI and JSON outputs return finalized viewer details.

Written for commit f042011. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Diff viewer supports per-page source fallbacks and deferred rendering with placeholder pages.
    • Viewer shows a pending status with spinner (reduced-motion aware), polls for replacement, and opens immediately then updates when ready.
    • Added navigation keyboard shortcuts (j/k, g/G, /) and pending-indicator before redirects.
  • Localization

    • Added localized "Loading diff: %@" string for the viewer.
  • Bug Fixes

    • Tightened server-side manifest caching to validate entries by request path.
    • Preserve empty-unstaged error state when fallback probing selects unstaged.
  • Tests

    • Integration tests added for pending viewer behavior and empty-unstaged error preservation; harness resolves redirected viewer HTML.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 30, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment May 31, 2026 1:34am
cmux-staging Building Building Preview, Comment May 31, 2026 1:34am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Defers selected-page diff HTML when appropriate, emits a pending/status viewer (spinner + reduced-motion support), records per-page fallback candidates, completes deferred pages later by retrying alternate sources when allowed, tightens manifest caching, and adds integration tests and localization.

Changes

Deferred Diff Viewer with Source Fallback

Layer / File(s) Summary
Contracts and result shape
CLI/cmux_open.swift
Adds DiffViewerWriteResult.completeDeferred, per-page deferred metadata (allowsSourceFallback, sourceFallbacks), and DiffViewerGitHTMLSetTarget.
HTML set routing and opening (pending) path
CLI/cmux_open.swift
Split writeGitDiffViewerHTMLSet into opening/pending and completion paths; add writeOpeningGitDiffViewerHTMLSet that returns a completeDeferred closure; remove legacy pending writer and add allowlist merge helper.
Pending pages and per-page fallback metadata
CLI/cmux_open.swift
When deferring selected source, write status/pending HTML for the selected page, compute sourceFallbacks and allowsSourceFallback, and generate pending pages for other sources/repo/base candidates.
Completion, retries, and placeholder input
CLI/cmux_open.swift, CLI/cmux_open.swift (runDiffCommand)
Refactor completion into completeDeferredDiffViewer and per-page helpers that retry fallback sources on EmptyDiffSourceError when permitted; resolve selectedInput only when available and substitute a synthetic DiffInput placeholder when missing; ensure runDiffCommand finalizes deferred pages before emitting output.
Status HTML/CSS/JS and payload changes
CLI/cmux_open.swift
Add writeDiffViewerStatusHTML/writeDiffViewerRedirectHTML, extend writeDiffViewerHTML with statusMessage/statusIsError/pollForReplacement producing pendingReplacement, add status-only CSS and spinner (reduced-motion aware), and JS helpers for waiting and swapping replacement documents.
Selector pending state and keyboard shortcuts
CLI/cmux_open.swift
Emit pending loading status before navigating on source/repo/base selector changes; add keyboard shortcuts (j/k, g/G, /) with typing-target detection.
Manifest cache, allowlist merge, and resolver tests
CLI/cmux_open.swift, cmuxTests/CMUXOpenCommandTests.swift
DiffViewerHTTPManifestCache.file(...) now validates cached token entries for exact requestPath before refreshing; add manifest-redirect resolution helpers and integration tests validating pending viewer behavior and selected-source fallback semantics.
Localization
Resources/Localizable.xcstrings
Add diffViewer.loadingDiffTarget translated across supported locales.
Integration tests
cmuxTests/CMUXOpenCommandTests.swift
Add tests verifying pending viewer before git diff completes, that empty selected-source errors persist when fallback probe fails, and helpers resolving redirected viewer HTML via HTTP manifest.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant CMUXServer
  participant GitProcess
  participant ManifestCache
  Browser->>CMUXServer: GET /diff viewer
  CMUXServer->>Browser: 200 pending HTML (status/polling, data-cmux-diff-pending)
  CMUXServer->>GitProcess: spawn git diff (async)
  GitProcess-->>CMUXServer: diff output (when ready) or EmptyDiffSourceError
  CMUXServer->>ManifestCache: update token manifest / write final HTML & patch
  Browser->>CMUXServer: poll /__cmux_diff_viewer_wait (if pendingReplacement)
  CMUXServer->>Browser: signal replacement → client swaps to final HTML
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 I deferred a diff to buy some time,
A gentle spinner twirled in rhyme,
Fallbacks waited, probes ran true,
Then final patches came into view.


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (4 errors, 1 warning, 4 inconclusive)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error PR introduces blocking DispatchSemaphore waits in startup and polling loops with 120s timeout in HTTP handlers, violating swift-blocking-runtime.md prohibitions on polling and blocking waits. Replace semaphore waits with async/await or callbacks; replace 120s polling loop with event-based notification system.
Cmux Swift Concurrency ❌ Error PR introduces new completion-handler closure (completeDeferred) in internal code; should use async/await per swift-concurrency-modernization.md guidelines. Refactor completeDeferred closure to async/await; extract deferred work into async functions and await them instead of storing callback.
Cmux Swift File And Package Boundaries ❌ Error 806 lines added to cmux_open.swift (7970 total, >800 limit). Exceeds 250-line addition threshold for oversized files. Mixes UI, networking, persistence, git subprocess, and parsing responsibilities. Extract HTTP server and manifest handling into SwiftPM package, reducing file by >200 lines and enabling testable components independent of app lifecycle.
Cmux Architecture Rethink ❌ Error Introduces polling-based deferred rendering and file-system-mediated split UI lifecycle to work around browser opening before git diff completes, leaving representable bad state. Restructure to ensure diff rendering completion owns UI state as single source of truth, rather than split opener/HTTP-server coordination via file system polling.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Cmux Swift Actor Isolation ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
Cmux Algorithmic Complexity ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
Cmux User-Facing Error Privacy ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
Description check ❓ Inconclusive PR description includes summary, testing details, and regression test coverage, but lacks demo video and incomplete checklist. Add a demo video showing the pending viewer opening before git diff completes, and complete the PR checklist items to confirm testing and review status.
✅ Passed checks (9 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: opening the diff viewer before rendering large git diffs, which is the core objective of this PR.
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.
Cmux No Hacky Sleeps ✅ Passed Only new setTimeout is 700ms for vim-style keyboard shortcut (gg), a legitimate user-visible presentation timing, not a race-condition workaround per allowed cases.
Cmux Swift @Concurrent ✅ Passed No @concurrent annotation violations found. PR adds no Swift async functions, no missing @concurrent on nonisolated work, and correctly uses background queues for I/O.
Cmux Swift Logging ✅ Passed Print statements in CLI/cmux_open.swift are user-facing CLI output. No debugPrint, dump, NSLog, file logging, or secret exposure violations found.
Cmux Full Internationalization ✅ Passed All user-facing Swift text uses CMUXDiffViewerLocalization.string() with catalog entries. The key diffViewer.loadingDiffTarget has translations for all 20 locales. Test file has no violations.
Cmux Swiftui State Layout ✅ Passed No SwiftUI changes found. PR modifies CLI code (HTML/JS generation), test code, and localization strings. No @State, @Published, @Observable, or other SwiftUI state patterns present in changed files.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR modifies only CLI HTML generation and tests; no Cocoa window/panel/controller objects or SwiftUI Window/WindowGroup declarations are created or changed.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task-cmux-open-diff-nonblocking-loading

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 and usage tips.

Comment thread CLI/cmux_open.swift
Comment thread CLI/cmux_open.swift
Comment thread CLI/cmux_open.swift
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 30, 2026

Greptile Summary

This PR makes the diff viewer open immediately as a pending HTML page with a full toolbar and spinner before large git diffs finish computing, then replaces the page via an in-process long-poll redirect when the real diff is ready. .lastTurn sources remain synchronous; all other sources use the new deferred open path.

  • Deferred open path: writeOpeningGitDiffViewerHTMLSet writes a pending HTML file and returns a browser.open_split URL immediately; a completeDeferred closure then writes the full diff set, resolves per-page source fallbacks, and redirects the opening page to the final viewer URL. CLI/JSON output now reports the finalized path/URL/title/source after completion.
  • Viewer shortcuts: Five new browser-content shortcuts (j/k, G, gg, /) are added to the diff viewer, configurable via cmux.json, with dedicated isBrowserContentShortcut and allowsBareFirstStroke flags that exclude them from app-wide shortcut routing and allow bare-key registration.
  • HTTP manifest refresh: The manifest cache now refreshes on per-path cache miss so replacement pages load without restarting the server; new integration tests cover pending-before-open and empty-unstaged error preservation.

Confidence Score: 5/5

  • The deferred open path is well-covered by new integration tests that verify the pending-before-open ordering and empty-unstaged error preservation. The manifest refresh-on-miss fix and exit-code behavior are both exercised by the test suite. No new blocking primitives are introduced in production code.
  • The core deferred open logic — pending HTML, long-poll wait, redirect, fallback handling — is tested end-to-end including the timing ordering. The two observations found are narrow style/fragility items that don't affect runtime correctness of the existing implementation.
  • Sources/KeyboardShortcutSettings.swift (the duplicate isBrowserContentShortcut / allowsBareFirstStroke switch lists are a future maintenance trap) and Resources/Localizable.xcstrings (shortcut label translations should match the richer set carried by other shortcut entries).

Important Files Changed

Filename Overview
CLI/cmux_open.swift Largest change: adds writeOpeningGitDiffViewerHTMLSet, writeCompleteGitDiffViewerHTMLSet, completeDeferredDiffViewer, and completeDeferredDiffViewerSource* family; also adds manifest refresh-on-miss, writeDiffViewerStatusHTML/Redirect, DiffViewerShortcut parsing, and JavaScript keyboard shortcut setup. Net ~1100 lines to an already very large file.
Sources/KeyboardShortcutSettings.swift Adds 5 new diffViewer shortcut actions with isBrowserContentShortcut and allowsBareFirstStroke properties; extends parseConfig to allow bare first strokes. Both new properties have identical implementations.
Sources/KeyboardShortcutContext.swift Adds browserPanel context for the 5 new diffViewer actions; clean and consistent with existing pattern.
Sources/App/ShortcutBareStartRouting.swift Correctly excludes isBrowserContentShortcut actions from the app-wide bare-start key cache so bare keys (j/k/g//) don't steal terminal input.
Sources/AppDelegate.swift Excludes isBrowserContentShortcut actions from chord interception and from shouldForwardBrowserSurfaceShortcutToTerminal; straightforward additions.
Sources/KeyboardShortcutRecorder.swift Adds firstStrokeRequiresModifier flag to KeyboardShortcutRecorder/NSButton to conditionally allow bare keys when recording; threaded through correctly.
Sources/KeyboardShortcutSettingsFileStore.swift Passes allowsBareFirstStroke through to StoredShortcut.parseConfig so bare-key bindings from cmux.json are accepted for diffViewer actions.
cmuxTests/CMUXOpenCommandTests.swift Adds testDiffCommandOpensPendingViewerBeforeGitDiffCompletes (uses fake git with sleep 0.05 polling for test synchronization) and testDiffCommandKeepsSelectedEmptyErrorWhenFallbackProbeFails; test-only scaffolding, acceptable under the no-hacky-sleeps pass criteria.
Resources/Localizable.xcstrings Adds diffViewer.loadingDiffTarget (Japanese translated; English used for all other 19 locales, matching existing diffViewer.loadingDiff pattern) and 5 shortcut label keys (Japanese translated; English for other locales, which differs from the richer translations other shortcut labels carry).
web/data/cmux-shortcuts.ts Adds diff-viewer shortcut category with 5 shortcuts; en/ja LocalizedText matches existing pattern for other shortcut entries.
web/data/cmux.schema.json Schema correctly adds diffViewerShortcutBinding definitions and enumerates the 5 new action IDs in shortcuts.bindings.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI (main thread)
    participant HTTP as HTTP Server (bg thread)
    participant Browser as Browser

    CLI->>CLI: "writeOpeningGitDiffViewerHTMLSet()<br/>writes pending HTML → openingFileURL"
    CLI->>CLI: "writeDiffViewerHTTPManifest()<br/>(manifest: openingFileURL only)"
    CLI->>Browser: browser.open_split(openingURL)
    Browser->>HTTP: GET openingURL → pending spinner HTML
    Browser->>HTTP: GET /__cmux_diff_viewer_wait/[opening-path]
    HTTP-->>HTTP: "waitForDiffViewerHTTPReplacement()<br/>watches openingFileURL via kqueue/fsevent"

    CLI->>CLI: completeDeferredDiffViewer() → completeDeferred()
    CLI->>CLI: "writeCompleteGitDiffViewerHTMLSet()<br/>runs git diff, writes selectedFileURL<br/>updates manifest (all files)"
    CLI->>CLI: "completeDeferredDiffViewerSelectedSource()<br/>writes actual diff to selectedFileURL"
    CLI->>CLI: "writeDiffViewerRedirectHTML()<br/>replaces openingFileURL with redirect → selectedURL"

    Note over HTTP: fsevent fires on openingFileURL
    HTTP->>HTTP: file no longer pending → serve redirect HTML
    HTTP-->>Browser: 200 redirect HTML (data-cmux-diff-redirect)
    Browser->>Browser: "replaceDocumentWith(redirectHTML)<br/>follows redirect to selectedURL"
    Browser->>HTTP: GET selectedURL
    HTTP->>HTTP: cache miss → refresh manifest
    HTTP-->>Browser: 200 final diff HTML

    CLI->>CLI: "completeDeferredDiffViewerSources()<br/>fills remaining source/repo/base pages"
    CLI->>CLI: "print("OK surface=... pane=... path=...")"
Loading

Reviews (7): Last reviewed commit: "Scope diff viewer bare shortcut schema" | Re-trigger Greptile

Comment thread CLI/cmux_open.swift
Comment thread CLI/cmux_open.swift Outdated
Comment on lines 3191 to 3192
let message = diffViewerErrorMessage(error)
try? writePendingDiffViewerHTML(to: page.url, title: page.source.title, message: message, pollForReplacement: false)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 The error handler uses page.source.title (the default title for the source), but the DiffViewerDeferredSourcePage carries titleOverride for exactly this purpose. For the newly-deferred selected-source page, titleOverride is the user-supplied --title argument. Ignoring it means that if diff computation fails the error page displays the generic source name (e.g. "Unstaged changes") instead of the title the user specified.

Suggested change
let message = diffViewerErrorMessage(error)
try? writePendingDiffViewerHTML(to: page.url, title: page.source.title, message: message, pollForReplacement: false)
let message = diffViewerErrorMessage(error)
try? writePendingDiffViewerHTML(to: page.url, title: page.titleOverride ?? page.source.title, message: message, pollForReplacement: false)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread CLI/cmux_open.swift Outdated
Comment on lines +3224 to +3228
return
} catch {
continue
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Fallback errors are silently swallowed with a misleading final error

The bare catch { continue } swallows any error thrown by writeDeferredDiffViewerSource during fallback iteration — including non-EmptyDiffSourceError failures such as disk-full, git process crashes, or HTML write failures. If every fallback fails for a non-empty-diff reason, the loop falls through and re-throws the original EmptyDiffSourceError from the primary source. The displayed error message will say the selected source had no diff, not that a write or git command failed on the fallback that was actually attempted.

coderabbitai[bot]
coderabbitai Bot previously requested changes May 30, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
CLI/cmux_open.swift (1)

2885-2921: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Flip the deferral predicate so the fallback path is reachable.

Line 2885 currently makes .lastTurn the only source that stays synchronous. In the only branch that loads selectedInput, Line 2905 therefore always sees .lastTurn, so the fallback code below is dead and --last-turn still blocks before browser.open_split.

Suggested fix
-        let shouldDeferSelectedSource = requestedSource != .lastTurn
+        let shouldDeferSelectedSource = requestedSource == .lastTurn
🤖 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 `@CLI/cmux_open.swift` around lines 2885 - 2921, The deferral predicate is
inverted so .lastTurn remains synchronous and the fallback path after
nonEmptyGitDiffInput is never reached; change the initialization of
shouldDeferSelectedSource from "requestedSource != .lastTurn" to
"requestedSource == .lastTurn" so that .lastTurn is deferred. This will allow
the existing branch that attempts to eagerly load selectedInput (the code around
sourceContext(for:), nonEmptyGitDiffInput(source:context:), selectedInput and
the fallback selection loop over DiffSource.allCases) to operate as intended and
make the fallback reachable when the initially requested source is empty.
🤖 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 `@CLI/cmux_open.swift`:
- Around line 3211-3229: The loop currently swallows any error from
writeDeferredDiffViewerSource by using a blanket `catch { continue }`; change it
to only continue for EmptyDiffSourceError and rethrow other errors so real
failures aren't hidden — e.g., inside the loop replace the generic `catch` with
`catch let err as EmptyDiffSourceError { continue } catch { throw err }` while
keeping the surrounding logic that iterates DiffSource.allCases and checks
page.sourceFallbacks when page.allowsSourceFallback.

In `@cmuxTests/CMUXOpenCommandTests.swift`:
- Around line 1460-1463: The test currently only asserts the browser open
happened before the fake git diff completed; change it to assert the open
happened before git diff started by capturing whether diffStartedURL exists
inside the socket handler and recording that state when the open request
arrives; specifically, in the socket handler that sets diffStartedURL, add a
boolean flag (or record diffStartedURL presence) accessible to the test, and in
the open request callback that sets openedURLBox/openedHTMLURLBox/pendingHTMLBox
(and fulfills openHandled), assert that the captured flag shows diff had NOT
started (diffStartedURL was nil) at that moment so the browser.open request
occurred before git diff began. Ensure this pattern is applied similarly to the
other two locations referenced around lines 1478-1483 and 1512-1515.

---

Outside diff comments:
In `@CLI/cmux_open.swift`:
- Around line 2885-2921: The deferral predicate is inverted so .lastTurn remains
synchronous and the fallback path after nonEmptyGitDiffInput is never reached;
change the initialization of shouldDeferSelectedSource from "requestedSource !=
.lastTurn" to "requestedSource == .lastTurn" so that .lastTurn is deferred. This
will allow the existing branch that attempts to eagerly load selectedInput (the
code around sourceContext(for:), nonEmptyGitDiffInput(source:context:),
selectedInput and the fallback selection loop over DiffSource.allCases) to
operate as intended and make the fallback reachable when the initially requested
source is empty.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d77b34ad-b745-46c8-a36f-ed5ed4afa50c

📥 Commits

Reviewing files that changed from the base of the PR and between 5dac49f and 9a25118.

📒 Files selected for processing (2)
  • CLI/cmux_open.swift
  • cmuxTests/CMUXOpenCommandTests.swift

Comment thread CLI/cmux_open.swift
Comment thread cmuxTests/CMUXOpenCommandTests.swift
coderabbitai[bot]
coderabbitai Bot previously requested changes May 30, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
CLI/cmux_open.swift (1)

792-800: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Emit JSON after deferred completion has finalized the selected page.

For deferred sources, viewer.input and viewer.title are placeholders until completion runs. If completion falls back from the requested source to another non-empty source, the JSON here can report the wrong source/title even though the rendered viewer shows different content.

Also applies to: 3352-3364

🤖 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 `@CLI/cmux_open.swift` around lines 792 - 800, The JSON is emitted before
deferred completion runs, so fields like viewer.input.sourceLabel and
viewer.title can be stale; call completeDeferredDiffViewer(viewer) and wait for
it to finalize the selected page before building the response payload and
calling print(jsonString(formatIDs(..., mode: idFormat))); in other words, move
the completeDeferredDiffViewer(viewer) invocation to precede creating/setting
response["source"] and response["title"] (and do the same change in the
analogous block around the other occurrence at lines ~3352-3364).
🤖 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 `@CLI/cmux_open.swift`:
- Around line 2944-2959: The code builds a localized message by concatenating
CMUXDiffViewerLocalization.string("diffViewer.loadingDiff", ...) with
selectedSource.menuLabel which breaks grammar in other locales; update the
message construction to use a single localized interpolated string (e.g., use
String(localized: or CMUXDiffViewerLocalization.string with a format
placeholder) that accepts the dynamic label) and pass that localized, fully
formatted sentence into writeDiffViewerStatusHTML (keep same parameters like
selectedFileURL, title, sourceLabel, layout, appearance, branchBaseRef). Apply
the same change to the other similar concatenations in the file (the blocks
referenced near the other occurrences) so all dynamic labels are inserted via a
single localization key with interpolation rather than by string concatenation.

In `@cmuxTests/CMUXOpenCommandTests.swift`:
- Around line 1522-1525: The assertion that checks absence of a main element is
too narrow — update the check on pendingHTMLBox (the
pendingHTMLBox.get()?.contains(...) call) to look for "<main" (so it catches
"<main>" and "<main ...>") or, if you intend to assert a specific container is
excluded, assert that specific container id/class instead; modify the
XCTAssertFalse that currently uses contains("<main>") to use contains("<main")
(or replace with an assertion targeting the full-layout container you want
excluded).

---

Outside diff comments:
In `@CLI/cmux_open.swift`:
- Around line 792-800: The JSON is emitted before deferred completion runs, so
fields like viewer.input.sourceLabel and viewer.title can be stale; call
completeDeferredDiffViewer(viewer) and wait for it to finalize the selected page
before building the response payload and calling print(jsonString(formatIDs(...,
mode: idFormat))); in other words, move the completeDeferredDiffViewer(viewer)
invocation to precede creating/setting response["source"] and response["title"]
(and do the same change in the analogous block around the other occurrence at
lines ~3352-3364).
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4b1b0679-ce7f-4d8c-9c68-6bbd445e4140

📥 Commits

Reviewing files that changed from the base of the PR and between 9a25118 and 08c11fd.

📒 Files selected for processing (2)
  • CLI/cmux_open.swift
  • cmuxTests/CMUXOpenCommandTests.swift

Comment thread CLI/cmux_open.swift Outdated
Comment thread CLI/cmux_open.swift
Comment thread cmuxTests/CMUXOpenCommandTests.swift Outdated
Comment thread CLI/cmux_open.swift
Comment thread CLI/cmux_open.swift Outdated
Comment thread CLI/cmux_open.swift
coderabbitai[bot]
coderabbitai Bot previously requested changes May 31, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
CLI/cmux_open.swift (1)

3458-3475: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Rethrow the fallback write failure, not the outer empty-source error.

This looks reintroduced. Line 3474 still throws the outer EmptyDiffSourceError, so a real failure from writeDeferredDiffViewerSource(...) gets masked and the user sees the wrong cause.

Suggested fix
-                } catch {
-                    throw error
+                } catch let fallbackError {
+                    throw fallbackError
                 }
🤖 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 `@CLI/cmux_open.swift` around lines 3458 - 3475, The catch around
writeDeferredDiffViewerSource is rethrowing the outer EmptyDiffSourceError
variable (named error) instead of the actual failure from the fallback attempt,
masking the real cause; update the inner do-catch so the general catch captures
the fallback error (e.g., catch let fallbackError) and rethrows that
fallbackError (or just rethrow the caught error) instead of throwing the outer
"error" value from the surrounding catch; references: EmptyDiffSourceError,
writeDeferredDiffViewerSource(...), page.sourceFallbacks, DiffSource.allCases.
🤖 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 `@CLI/cmux_open.swift`:
- Around line 803-806: The success message is printed before deferred rendering
completes, which can misreport success if completeDeferredDiffViewer(viewer)
throws; call completeDeferredDiffViewer(viewer) and wait for it to succeed (or
wrap it in a do/catch) before printing the OK line, i.e. move the print("OK
surface=... pane=... path=...") to after a successful return from
completeDeferredDiffViewer(viewer) (or only print in the do block after
awaiting/try-ing completeDeferredDiffViewer), referencing formatHandle(payload,
kind: "surface"/"pane", idFormat: idFormat) and the viewer variable.

---

Duplicate comments:
In `@CLI/cmux_open.swift`:
- Around line 3458-3475: The catch around writeDeferredDiffViewerSource is
rethrowing the outer EmptyDiffSourceError variable (named error) instead of the
actual failure from the fallback attempt, masking the real cause; update the
inner do-catch so the general catch captures the fallback error (e.g., catch let
fallbackError) and rethrows that fallbackError (or just rethrow the caught
error) instead of throwing the outer "error" value from the surrounding catch;
references: EmptyDiffSourceError, writeDeferredDiffViewerSource(...),
page.sourceFallbacks, DiffSource.allCases.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: aad673cc-db62-4cb1-bbea-d027f3c2b098

📥 Commits

Reviewing files that changed from the base of the PR and between 08c11fd and ba7f25d.

📒 Files selected for processing (3)
  • CLI/cmux_open.swift
  • Resources/Localizable.xcstrings
  • cmuxTests/CMUXOpenCommandTests.swift
💤 Files with no reviewable changes (1)
  • Resources/Localizable.xcstrings

Comment thread CLI/cmux_open.swift Outdated
Comment thread CLI/cmux_open.swift Outdated
Comment thread CLI/cmux_open.swift
coderabbitai[bot]
coderabbitai Bot previously requested changes May 31, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
CLI/cmux_open.swift (1)

3553-3555: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve real fallback failures instead of continuing on all errors.

Line 3553 currently swallows every fallback failure, which can hide real write/render errors and incorrectly fall through as an empty-source outcome.

Suggested fix
-                } catch {
-                    continue
+                } catch is EmptyDiffSourceError {
+                    continue
+                } catch let fallbackError {
+                    throw fallbackError
                 }
🤖 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 `@CLI/cmux_open.swift` around lines 3553 - 3555, The current catch block after
the fallback write/render attempt unconditionally swallows all errors with
"catch { continue }"; change it to capture the error (e.g., "catch let error")
and only continue for explicit, expected recoverable cases (such as the
empty-source/fallback-not-available condition), otherwise propagate or surface
the error (rethrow, return failure, or log and break) so real write/render
failures are not hidden; locate the catch directly following the fallback
write/render call in CLI/cmux_open.swift and replace the unconditional continue
with conditional handling that preserves and forwards unexpected errors.
🤖 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 `@cmuxTests/CMUXOpenCommandTests.swift`:
- Around line 635-639: The test reads the file returned by
diffViewerHTMLFileURL(for:from:) but that may be an intermediate redirect page;
instead detect if the returned HTML is a redirect (e.g., contains a meta-refresh
or a redirect anchor), extract the redirect target href, resolve the real viewer
URL (call diffViewerHTMLFileURL(for: redirectTarget, from:
stagedFallback.params) or reuse diffViewerOptionURL to get the actual viewer
path) and then read the final HTML before asserting its contents; update the
assertions to run against the resolved final unstagedHTML rather than the
redirect shell.

---

Duplicate comments:
In `@CLI/cmux_open.swift`:
- Around line 3553-3555: The current catch block after the fallback write/render
attempt unconditionally swallows all errors with "catch { continue }"; change it
to capture the error (e.g., "catch let error") and only continue for explicit,
expected recoverable cases (such as the empty-source/fallback-not-available
condition), otherwise propagate or surface the error (rethrow, return failure,
or log and break) so real write/render failures are not hidden; locate the catch
directly following the fallback write/render call in CLI/cmux_open.swift and
replace the unconditional continue with conditional handling that preserves and
forwards unexpected errors.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2c5269f7-5f6c-4165-9e5b-4d3f441ea2c4

📥 Commits

Reviewing files that changed from the base of the PR and between ba7f25d and 4e4fa3e.

📒 Files selected for processing (2)
  • CLI/cmux_open.swift
  • cmuxTests/CMUXOpenCommandTests.swift

Comment thread cmuxTests/CMUXOpenCommandTests.swift
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1baaba8. Configure here.

Comment thread CLI/cmux_open.swift
@lawrencecchen lawrencecchen dismissed stale reviews from coderabbitai[bot], coderabbitai[bot], coderabbitai[bot], and coderabbitai[bot] May 31, 2026 02:02

Stale CodeRabbit review on an older commit. The actionable findings were addressed in later commits, review threads are resolved or outdated, and the current CodeRabbit check is passing/skipped on head f042011.

@lawrencecchen lawrencecchen merged commit 6237787 into main May 31, 2026
19 checks passed
@lawrencecchen lawrencecchen deleted the task-cmux-open-diff-nonblocking-loading branch May 31, 2026 02:02
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