Skip to content

Open diff viewer pane immediately#5047

Open
lawrencecchen wants to merge 1 commit into
mainfrom
task-diff-viewer-404-instant-pane
Open

Open diff viewer pane immediately#5047
lawrencecchen wants to merge 1 commit into
mainfrom
task-diff-viewer-404-instant-pane

Conversation

@lawrencecchen
Copy link
Copy Markdown
Contributor

@lawrencecchen lawrencecchen commented May 31, 2026

Summary

  • Open git diff viewer panes with a tiny cmux-diff-viewer:// pending page before starting git or the HTTP asset server.
  • Defer git/source/asset work until after browser.open_split returns, then replace the pending page with the completed HTTP viewer.
  • Add an event-driven app-scheme wait endpoint so replacement waits do not block the shared stream operation, and cover the no-git-before-open path.

Testing

  • swiftc -parse CLI/cmux_open.swift
  • swiftc -parse Sources/Panels/BrowserPanel.swift
  • swiftc -parse cmuxTests/CMUXOpenCommandTests.swift
  • git diff --check
  • /Users/lawrence/fun/cmuxterm-hq/skills/codex-review/scripts/codex-review --parallel-tests "swiftc -parse CLI/cmux_open.swift && swiftc -parse Sources/Panels/BrowserPanel.swift && swiftc -parse cmuxTests/CMUXOpenCommandTests.swift && git diff --check"
  • ./scripts/reload.sh --tag task-diff-viewer-404-instant-pane --swift-frontend-workaround
  • Tagged CLI slow-git socket repro: browser.open_split in 35 ms, cmux-diff-viewer scheme, 2 initial files, no git before open, process exited 0.

Notes

  • Codex review found no correctness issues after the event-driven wait fix. cmux-policy still notes the existing XCTest-based non-UI test file preference for Swift Testing.

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


Note

Medium Risk
Touches WKWebView custom scheme handling, long-lived wait requests, and CSP framing for localhost; behavior is covered by tests but spans CLI and browser security boundaries.

Overview
Git diff panes now open on a minimal cmux-diff-viewer:// pending page (toolbar + spinner) before git, the local HTTP asset server, or full diff HTML run. DiffViewerURLMapper gains an app-scheme transport alongside HTTP; deferred completion rebuilds URLs against 127.0.0.1 and swaps the opening page to an embedded localhost viewer instead of an immediate meta-redirect.

CmuxDiffViewerURLSchemeHandler serves /__cmux_diff_viewer_wait/..., blocking on disk until the pending marker clears (file watcher + 120s timeout), and relaxes CSP frame-src for localhost. CLI --cwd uses a resolved path without forcing git rev-parse up front. Tests assert open uses the app scheme, two registered files, and no git before browser.open_split.

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


Summary by cubic

Open the diff viewer pane instantly with a lightweight cmux-diff-viewer:// pending page, then embed the full HTTP viewer once git and assets are ready. This removes the initial blank delay and keeps the UI responsive even when git is slow.

  • New Features
    • Open via cmux-diff-viewer:// with a minimal pending HTML page; no git, assets, or HTTP server work before the pane appears.
    • Add an event-driven wait path /__cmux_diff_viewer_wait/... that streams the replacement after the pending marker clears; 120s timeout and non-blocking.
    • Swap to the completed viewer by embedding an iframe or replacing the document; update CSP with frame-src and child-src for http://127.0.0.1:*.
    • Extend URL mapping to support app-scheme and HTTP transports, use --cwd for repo root to avoid upfront git, and update tests for app-scheme open, two registered files, and “no git before open.”

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

Review in cubic

Summary by CodeRabbit

  • New Features

    • Diff viewer now opens a lightweight "pending" page immediately and uses client polling to swap in the fully rendered viewer when ready.
    • Supports two URL modes for viewer content (HTTP and app-scheme) to improve how viewers are opened.
  • Bug Fixes / Reliability

    • Improved handling of deferred completion, error timeouts and recovery so failed completions show an error status.
    • Smoother in-browser replacement of pending-to-complete viewer without full reload.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 31, 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 8:20am
cmux-staging Building Building Preview, Comment May 31, 2026 8:20am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR splits diff viewer HTML into an immediate opening page (app-scheme) and a completion page (HTTP), adds client polling that requests a scheme-handler wait URL, implements a replacement-wait watcher that streams replacement HTML when ready (or times out), and updates tests and URL resolution to exercise the new flows.

Changes

Diff Viewer URL Transport and Opening Pipeline

Layer / File(s) Summary
Transport enum and URL mapper updates
CLI/cmux_open.swift
Transport enum with HTTP and app-scheme modes added to DiffViewerURLMapper; viewerURL(for:) now conditionally builds full HTTP paths with origin or app-scheme URLs using token as host.
Opening vs completion HTML target factories
CLI/cmux_open.swift
Split HTML generation into opening target (app-scheme, no origin) and completion target (HTTP with server origin); writeGitDiffViewerHTMLSet routes non-lastTurn sources through opening factory.
Opening status HTML generation with polling JS
CLI/cmux_open.swift
New writeDiffViewerOpeningStatusHTML helper generates pending-only opening HTML with an empty patch sidecar and client-side JS that polls /__cmux_diff_viewer_wait + location.pathname and upgrades the document by parsing replacement HTML and restarting inert scripts.
Opening page completion closure and upgrade
CLI/cmux_open.swift
Completion closure now renders the completion HTML set, embeds the selected completed viewer into the existing opening page on success, and writes an error status on failure; repoRoot is resolved directly from --cwd when provided.

Browser Panel Replacement-Wait Scheme Handler

Layer / File(s) Summary
Replacement-wait constants, state tracking, and routing
Sources/Panels/BrowserPanel.swift
Adds wait-path prefix, pending marker, and timeout constants; SchemeTaskState extended with replacement-wait finished flag and watcher/timer references; webView(_:start:) routes wait-path requests into the replacement-wait flow.
File watcher routing and streaming refactor
Sources/Panels/BrowserPanel.swift
Introduces registeredReplacementWaitFile to validate wait-path URLs and find the underlying registered HTML file; refactors startStreamingFile to accept waitForReplacement flag and route to the replacement-wait implementation.
Replacement-wait polling and timeout handling
Sources/Panels/BrowserPanel.swift
Implements watcher-based waiting with DispatchSourceFileSystemObject, rechecks for pending marker removal on events, completes exactly once and cancels watcher/timer, streams updated file content, or fails the scheme task with NSURLErrorTimedOut on deadline. Also updates diff-viewer CSP to allow frame-src/child-src http://127.0.0.1:*.

Test Updates for Pending Viewer Flow

Layer / File(s) Summary
Git invocation sentinel and test state tracking
cmuxTests/CMUXOpenCommandTests.swift
Adds gitInvokedURL sentinel file and modifies fake git to touch it; introduces AsyncValueBox holders to capture opened viewer scheme, diff_viewer_files count, and git-invoked sentinel presence at open time.
Mock server handler and viewer URL assertions
cmuxTests/CMUXOpenCommandTests.swift
Mock browser.open_split handler records opened scheme and diff_viewer_files count, checks sentinel at open time, resolves viewer HTML using diffViewerHTMLFileURLFromOpenParams for cmux-diff-viewer opens, refines runDiffCLIAndReadHTML assertions for http vs cmux-diff-viewer semantics, and prefers embedded-URL markers during HTML resolution.

Sequence Diagram(s)

sequenceDiagram
  participant ComponentA
  participant ComponentB
  ComponentA->>ComponentB: observable interaction
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • manaflow-ai/cmux#5016: Both PRs implement the pending diff-viewer open-and-upgrade flow using the /__cmux_diff_viewer_wait replacement/polling mechanism.
  • manaflow-ai/cmux#4451: Both PRs touch CLI/cmux_open.swift's runDiffCommand and diff viewer opening logic; changes here modify URL transport and HTML generation used by that flow.

Poem

🐰 A rabbit’s nibble on upgrades:

I open a page with a patient hop,
Polling the path until markers stop,
Watchers whisper when files are done,
Scripts restart — the new viewer’s begun!


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (8 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Actor Isolation ❌ Error SchemeTaskState is marked @unchecked Sendable without a clear safety explanation comment required by swift-actor-isolation.md rules. Add a comment explaining @unchecked Sendable safety for SchemeTaskState (e.g., "// Safely shared across threads: mutable state guarded by condition/external locks").
Cmux Swift Blocking Runtime ❌ Error PR introduces blocking semaphores and NSCondition.wait() in production code, violating swift-blocking-runtime rules in both CLI/cmux_open.swift and Sources/Panels/BrowserPanel.swift. Replace DispatchSemaphore.wait() with async/await. Replace NSCondition.wait() with actor-based synchronization. Eliminate polling with timeouts.
Cmux Algorithmic Complexity ❌ Error Line 2852 in BrowserPanel: full-file load in watcher event handler violates file-watcher complexity rule; CLI correctly limits to 8192 bytes. Use FileHandle.read(upToCount: 8192), support CMUX_DIFF_VIEWER_WAIT_TIMEOUT_SECONDS environment variable with bounds [0.05, 600].
Cmux Swift Concurrency ❌ Error startWaitingForReplacement() uses DispatchSource.makeFileSystemObjectSource + makeTimerSource for file-watching with timeout—ordinary async work that should use async/await. Replace DispatchSource file watching with async/await: create a Task polling with Task.sleep until the pending marker disappears or timeout, then call enqueueStreamingFile.
Cmux Swift @Concurrent ❌ Error File I/O-heavy diffViewerReplacementIsPending reads entire file from event handler on streamQueue without proper resource limits or concurrency boundary. Match CLI behavior: use FileHandle to read only first 8192 bytes, make timeout configurable via CMUX_DIFF_VIEWER_WAIT_TIMEOUT_SECONDS environment variable.
Cmux Swift Logging ❌ Error PR contains NSLog in BrowserPanel.swift (app code, not Sources/Providers/*), violating swift-logging.md rules. Also uses error.localizedDescription instead of String(describing:). Replace NSLog statements with os.log in BrowserPanel.swift; change diffViewerErrorMessage to use String(describing: error) for better error preservation.
Cmux User-Facing Error Privacy ❌ Error CLI/cmux_open.swift exposes error.localizedDescription to users: line 3783 (diffViewerErrorMessage) and line 4089 (startDiffViewerHTTPServer), violating privacy rules for raw upstream messages. Use String(describing: error) instead of error.localizedDescription in both locations per review feedback to prevent exposing Foundation error details.
Cmux Architecture Rethink ❌ Error Dual state observers (Swift watcher + JS polling) with split lifecycle, different timeouts (hardcoded 120s vs env var), different read strategies (full file vs 8KB), no documented invariant. Make BrowserPanel timeout configurable via CMUX_DIFF_VIEWER_WAIT_TIMEOUT_SECONDS and read only 8192 bytes to align with HTTP path.
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.
✅ Passed checks (9 passed)
Check name Status Explanation
Title check ✅ Passed The title "Open diff viewer pane immediately" directly reflects the main objective of the PR, which is to open git diff viewer panes immediately with a pending page before git/assets are ready.
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 PR contains Swift files (out of scope) and test-only shell scaffolding. Shell sleeps in CMUXOpenCommandTests are deterministic test fixtures, which are explicitly allowed.
Cmux Swift File And Package Boundaries ✅ Passed BrowserPanel adds 203 lines under the 250-line threshold for 800+ line files. CLI file is untracked. Focused feature with no new mixed responsibilities.
Cmux Full Internationalization ✅ Passed All new user-facing Swift text properly uses CMUXDiffViewerLocalization.string() API. No unlocalized user-facing strings, web message changes, or catalog additions found in this PR.
Cmux Swiftui State Layout ✅ Passed No SwiftUI state violations found. Existing ObservableObject classes remain unchanged; no new @Published/@Observable/@StateObject additions or render-time state mutations detected.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Three new windows added: backgroundPreloadWindow (ignored bootstrap window), ImportWizardPanel (modal), and progress window (sheet). All comply with rule's allowed exemptions.
Description check ✅ Passed The PR description provides clear technical context and comprehensive testing details, though it lacks a formal demo video link and some checklist items are incomplete or missing.
✨ 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-diff-viewer-404-instant-pane

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.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 31, 2026

Greptile Summary

This PR changes the git diff viewer open flow to show a lightweight cmux-diff-viewer:// pending page immediately—before git or the HTTP asset server has run—and then swaps in the full HTTP-backed viewer once deferred work completes. A new /__cmux_diff_viewer_wait/… endpoint in CmuxDiffViewerURLSchemeHandler watches the pending HTML file for the removal of its data-cmux-diff-pending marker using a DispatchSource watcher and 120 s timeout, non-blocking on the shared stream queue.

  • DiffViewerURLMapper gains an appScheme transport so opening URLs use the custom scheme while the finished viewer stays on http://127.0.0.1/…; makeDiffViewerGitOpeningHTMLSetTarget and makeDiffViewerGitCompletionHTMLSetTarget split the previously single-step target into opening and completion phases.
  • writeDiffViewerOpeningStatusHTML generates a spinner/toolbar pending page with inline JS that fetches the wait endpoint and then either embeds the complete viewer in an iframe or replaces the whole document via DOMParser; writeDiffViewerEmbeddedHTML writes the post-completion iframe wrapper.
  • Tests add assertions that the browser opens with the cmux-diff-viewer scheme, exactly two registered files, no git invocation before open, and that the pending marker is eventually replaced by an embedded viewer marker.

Confidence Score: 4/5

Safe to merge with the previously noted blocking I/O and repoRoot defects acknowledged; no new correctness regressions introduced by this PR's changes.

The event-driven wait mechanism in BrowserPanel.swift is well-synchronized (replacementWaitFinished flag, NSCondition drain, DispatchSource cancel on every exit path), and the two-phase opening/completion target split in cmux_open.swift is structurally sound. The previously flagged blocking I/O calls in startWaitingForReplacement and the raw-cwd repoRoot passed to the error-state writeDiffViewerStatusHTML both remain open; neither is introduced fresh by this PR but neither has been fixed. No new defects were found beyond those already on record.

Sources/Panels/BrowserPanel.swift (startWaitingForReplacement blocking I/O — previous thread); CLI/cmux_open.swift (context.repoRoot in error catch — previous outside-diff comment)

Important Files Changed

Filename Overview
CLI/cmux_open.swift Adds appScheme transport to DiffViewerURLMapper, splits opening vs completion HTML set targets, generates a full pending page (inline CSS/JS spinner, DOMParser-based replacement) and a post-completion embedded-iframe wrapper. The error catch path passes context.repoRoot (raw cwd, not the git root) to writeDiffViewerStatusHTML — unchanged defect carried forward from the repoRoot deferral at line 845.
Sources/Panels/BrowserPanel.swift Adds event-driven /__cmux_diff_viewer_wait endpoint backed by DispatchSource file-system watcher and timer; synchronization via replacementWaitFinished flag and NSCondition prevents double-completion and ensures the fd is closed on every path. The initial pending check and fd open in startWaitingForReplacement run on the main thread (flagged in an earlier review thread).
cmuxTests/CMUXOpenCommandTests.swift Extends the deferred diff-viewer test to assert cmux-diff-viewer scheme at open time, two registered files, no git invocation before open, and embedded-viewer marker after completion; helper methods updated from redirect URL to embed-URL parsing.

Sequence Diagram

sequenceDiagram
    participant CLI as cmux_open CLI
    participant App as cmux App (main thread)
    participant SH as CmuxDiffViewerURLSchemeHandler
    participant SQ as streamQueue
    participant Browser as WKWebView

    CLI->>CLI: writeDiffViewerOpeningStatusHTML()
    CLI->>App: open_split(cmux-diff-viewer://token/opening.html)
    App->>SH: webView(_:start:) for opening.html
    SH->>SQ: enqueueStreamingFile(opening.html)
    SQ-->>Browser: HTTP/200 pending HTML
    Note over Browser: Shows spinner toolbar

    par Deferred work
        CLI->>CLI: makeDiffViewerGitCompletionHTMLSetTarget()
        CLI->>CLI: writeCompleteGitDiffViewerHTMLSet()
        CLI->>CLI: writeDiffViewerEmbeddedHTML() overwrites opening.html
    and JS polling
        Browser->>SH: fetch(cmux-diff-viewer://token/__cmux_diff_viewer_wait/opening.html)
        SH->>SH: startWaitingForReplacement() DispatchSource watches file
        SH-->>SQ: file replaced, enqueueStreamingFile(updated opening.html)
        SQ-->>Browser: HTTP/200 embedded HTML with data-cmux-diff-embed-url
        Browser->>Browser: showEmbeddedViewer() or replaceDocumentWith()
        Browser->>Browser: iframe loads http://127.0.0.1/token/diff.html
    end
Loading

Reviews (2): Last reviewed commit: "fix: open diff viewer pane immediately" | Re-trigger Greptile

Comment on lines +2795 to +2851
private func startWaitingForReplacement(
_ file: RegisteredFile,
requestURL: URL,
urlSchemeTask: WKURLSchemeTask,
taskID: ObjectIdentifier
) {
guard isSchemeTaskActive(taskID) else { return }
guard diffViewerReplacementIsPending(file.fileURL) else {
enqueueStreamingFile(file, requestURL: requestURL, urlSchemeTask: urlSchemeTask, taskID: taskID)
return
}

let fd = open(file.fileURL.path, O_EVTONLY)
guard fd >= 0 else {
failSchemeTask(taskID, urlSchemeTask: urlSchemeTask, errorCode: NSURLErrorFileDoesNotExist)
return
}

let watcher = DispatchSource.makeFileSystemObjectSource(
fileDescriptor: fd,
eventMask: [.write, .extend, .attrib, .delete, .rename],
queue: streamQueue
)
let timeout = DispatchSource.makeTimerSource(queue: streamQueue)

watcher.setEventHandler { [weak self, weak watcher, weak timeout] in
guard let self else { return }
guard !self.diffViewerReplacementIsPending(file.fileURL) else { return }
guard self.finishReplacementWait(taskID, watcher: watcher, timeout: timeout) else { return }
self.enqueueStreamingFile(file, requestURL: requestURL, urlSchemeTask: urlSchemeTask, taskID: taskID)
}
watcher.setCancelHandler {
close(fd)
}
timeout.setEventHandler { [weak self, weak watcher, weak timeout] in
guard let self else { return }
guard self.finishReplacementWait(taskID, watcher: watcher, timeout: timeout) else { return }
self.failSchemeTask(taskID, urlSchemeTask: urlSchemeTask, errorCode: NSURLErrorTimedOut)
}
timeout.schedule(deadline: .now() + Self.replacementWaitTimeout)

guard storeReplacementWaitSources(taskID, watcher: watcher, timeout: timeout) else {
watcher.cancel()
watcher.resume()
timeout.cancel()
timeout.resume()
return
}
watcher.resume()
timeout.resume()

guard !diffViewerReplacementIsPending(file.fileURL) else { return }
guard finishReplacementWait(taskID, watcher: watcher, timeout: timeout) else { return }
enqueueStreamingFile(file, requestURL: requestURL, urlSchemeTask: urlSchemeTask, taskID: taskID)
}

private func diffViewerReplacementIsPending(_ fileURL: URL) -> Bool {
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.

P1 Blocking file I/O and syscalls on the main thread

webView(_:start:) is called on the main thread, so startWaitingForReplacement runs there too. It calls diffViewerReplacementIsPending (which does a full Data(contentsOf:) read) once before setting up the watcher, then again after watcher.resume() to close the TOCTOU window. It also calls open(file.fileURL.path, O_EVTONLY) between them — another blocking syscall on the main thread. For the small HTML files involved this is typically sub-millisecond, but calling blocking I/O APIs directly from a WKURLSchemeHandler delegate is prohibited by the cmux-swift-blocking-runtime rule. The pattern already used by enqueueStreamingFile — dispatching to streamQueue.async before doing any I/O — should be applied here too: dispatch to streamQueue first, then do the pending check, open, and watcher setup from that queue.

Rule Used: Flag new blocking or timing-based synchronization ... (source)

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

🤖 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 3140-3155: The error formatting currently falls back to
error.localizedDescription which loses detail for NSError/CocoaError; update the
fallback in diffViewerErrorMessage to use String(describing: error) (or ensure
callers pass String(describing: error)) so the full error is preserved, and
update the related call sites around writeDiffViewerStatusHTML /
diffViewerErrorMessage in CLI/cmux_open.swift (including the similar branch at
lines ~3164-3178) to pass or display the String(describing: error) output rather
than relying on localizedDescription.

In `@Sources/Panels/BrowserPanel.swift`:
- Around line 2505-2507: The app-scheme wait path currently hardcodes
replacementWaitTimeout = 120 and reads the whole file on each recheck which
causes inconsistent timeouts vs the HTTP side and unnecessary I/O; update
BrowserPanel to read the wait timeout from the same environment/config key used
by CLI (CMUX_DIFF_VIEWER_WAIT_TIMEOUT_SECONDS) instead of hardcoding 120
(replace usage of replacementWaitTimeout), and change the pending-marker check
(pendingReplacementMarker and replacementWaitPrefix code paths) to only read and
inspect the first 8192 bytes of the file when detecting the pending marker to
match the HTTP contract and avoid full-file reads on each filesystem event.
🪄 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: b817320e-51f3-4820-a970-ca38969940b8

📥 Commits

Reviewing files that changed from the base of the PR and between febec6a and c7e3ecf.

📒 Files selected for processing (3)
  • CLI/cmux_open.swift
  • Sources/Panels/BrowserPanel.swift
  • cmuxTests/CMUXOpenCommandTests.swift

Comment thread CLI/cmux_open.swift
Comment on lines +3140 to 3155
try? writeDiffViewerStatusHTML(
to: openingFileURL,
title: title,
targetURL: completed.url
sourceLabel: sourceLabel,
message: diffViewerErrorMessage(error),
isError: true,
pollForReplacement: false,
layout: layout,
appearance: appearance,
sourceOptions: [],
repoOptions: [],
baseOptions: [],
repoRoot: context.repoRoot,
branchBaseRef: selectedSource == .branch ? context.branchBaseRef : nil
)
throw error
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use String(describing:) before expanding this error path.

These new failure branches route more pending-pane errors through diffViewerErrorMessage(error), but that helper still falls back to error.localizedDescription. That will collapse many NSError/CocoaError failures into generic text in the new UI. Please switch the helper fallback to String(describing: error) before using it here.

Based on learnings, use String(describing: error) instead of error.localizedDescription when formatting errors in the cmux Swift CLI (CLI/**/*.swift) so the real cause is preserved.

Also applies to: 3164-3178

🤖 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 3140 - 3155, The error formatting currently
falls back to error.localizedDescription which loses detail for
NSError/CocoaError; update the fallback in diffViewerErrorMessage to use
String(describing: error) (or ensure callers pass String(describing: error)) so
the full error is preserved, and update the related call sites around
writeDiffViewerStatusHTML / diffViewerErrorMessage in CLI/cmux_open.swift
(including the similar branch at lines ~3164-3178) to pass or display the
String(describing: error) output rather than relying on localizedDescription.

Comment on lines +2505 to +2507
private static let replacementWaitPrefix = "/__cmux_diff_viewer_wait/"
private static let pendingReplacementMarker = Data("data-cmux-diff-pending=\"true\"".utf8)
private static let replacementWaitTimeout: TimeInterval = 120
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the app-scheme wait path aligned with the HTTP wait contract.

CLI/cmux_open.swift already makes this timeout configurable via CMUX_DIFF_VIEWER_WAIT_TIMEOUT_SECONDS and only checks the first 8192 bytes for the pending marker. Hardcoding 120 here and loading the whole file on each recheck means the same diff-viewer flow can time out differently by transport and does unnecessary full-file I/O on every filesystem event.

Suggested fix
-    private static let replacementWaitTimeout: TimeInterval = 120
+    private static func replacementWaitTimeout() -> TimeInterval {
+        let defaultTimeout: TimeInterval = 120
+        let key = "CMUX_DIFF_VIEWER_WAIT_TIMEOUT_SECONDS"
+        guard let raw = ProcessInfo.processInfo.environment[key]?.trimmingCharacters(in: .whitespacesAndNewlines),
+              let value = TimeInterval(raw),
+              value.isFinite else {
+            return defaultTimeout
+        }
+        return min(max(value, 0.05), 600)
+    }
...
-        timeout.schedule(deadline: .now() + Self.replacementWaitTimeout)
+        timeout.schedule(deadline: .now() + Self.replacementWaitTimeout())
...
     private func diffViewerReplacementIsPending(_ fileURL: URL) -> Bool {
-        guard let data = try? Data(contentsOf: fileURL) else {
+        guard let handle = try? FileHandle(forReadingFrom: fileURL) else {
             return true
         }
-        return data.range(of: Self.pendingReplacementMarker) != nil
+        defer { try? handle.close() }
+        guard let data = try? handle.read(upToCount: 8192),
+              !data.isEmpty else {
+            return true
+        }
+        return data.range(of: Self.pendingReplacementMarker) != nil
     }

Also applies to: 2834-2834, 2851-2855

🤖 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 `@Sources/Panels/BrowserPanel.swift` around lines 2505 - 2507, The app-scheme
wait path currently hardcodes replacementWaitTimeout = 120 and reads the whole
file on each recheck which causes inconsistent timeouts vs the HTTP side and
unnecessary I/O; update BrowserPanel to read the wait timeout from the same
environment/config key used by CLI (CMUX_DIFF_VIEWER_WAIT_TIMEOUT_SECONDS)
instead of hardcoding 120 (replace usage of replacementWaitTimeout), and change
the pending-marker check (pendingReplacementMarker and replacementWaitPrefix
code paths) to only read and inspect the first 8192 bytes of the file when
detecting the pending marker to match the HTTP contract and avoid full-file
reads on each filesystem event.

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