Skip to content

Open forked conversations without palette action probe - #4852

Merged
lawrencecchen merged 16 commits into
mainfrom
fix-fork-conversation-lag
May 27, 2026
Merged

lawrencecchen merged 16 commits into
mainfrom
fix-fork-conversation-lag

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented May 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary:

  • Make command palette fork actions create the terminal split from the already verified focused conversation snapshot instead of awaiting a fresh session/process scan.
  • Reuse the palette forkability cache and fingerprint checks so stale probe-only snapshots are rejected synchronously.
  • Add unit coverage for immediate built-in forks, probe-verified OpenCode forks, and stale probe fingerprints.

Verification:

  • ./scripts/reload.sh --tag forklag
  • AWS M4 Pro: xcodebuild -scheme cmux-unit -only-testing:cmuxTests/CommandPaletteSearchEngineTests test, 56 tests passed

Note

Medium Risk
Changes which snapshot is used when forking terminals (local vs remote, cache vs fallback); mistakes could fork the wrong session, but behavior is guarded by fingerprints and covered by new unit tests.

Overview
Command palette fork conversation actions now run synchronously from a probe-verified snapshot cache instead of awaiting RestorableAgentSessionIndex and a post-probe focus check.

Immediate fork path: forkFocusedAgentConversation uses commandPaletteImmediateForkExecutionSnapshotSelection to pick a snapshot (verified cache vs fallback), updates per-panel fork cache fingerprints, and clears cache on failure via clearCommandPaletteForkableAgentCache. Fallback-only snapshots are not forkable or listed until probe state matches (panel key, remote/local context, fingerprint).

Palette UX: Fork command IDs dismiss the palette before the action runs (commandPaletteShouldDismissBeforeRun).

Availability refresh: Tracks whether the probe used a fallback (commandPaletteForkableAgentResultHadFallbackByPanelKey) and tightens when probe results are reused vs cleared; commandPalettePanelHasForkableAgent no longer treats unprobed fallback snapshots as forkable.

Tests: Expanded CommandPaletteSearchEngineTests for immediate selection, stale fingerprints, dismiss-before-run, and probe reuse/clear rules.

Reviewed by Cursor Bugbot for commit cfcc827. 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 forked conversations instantly from the command palette using a probe-verified cache. Fallback snapshots stay hidden and cannot execute until a prior probe verifies fingerprint and remote/local context; fork commands dismiss the palette before running to keep focus.

  • Bug Fixes
    • Execute forks via commandPaletteImmediateForkExecutionSnapshotSelection; prefer verified cached snapshots, allow probe-verified fallbacks, reject mismatches, and clear per-panel fork cache on failure.
    • Track and preserve actual fallback usage (commandPaletteForkableAgentResultHadFallbackByPanelKey) to decide cache reuse/clearing on palette reopen or panel changes; re-probe on context/fingerprint drift while keeping verified results visible.

Written for commit cfcc827. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features

    • Immediate, synchronous fork execution for focused conversations for faster, more responsive forks.
    • Certain command palette actions now dismiss the palette before running for smoother UX.
  • Bug Fixes

    • Per-panel cache eviction and failure signaling when snapshot or eligibility checks fail to prevent incorrect forks.
    • Better handling of fallback vs cached snapshot mismatches to avoid invalid executions.
  • Refactor

    • Improved fork-availability and cache-reuse logic with stricter fingerprint and remote/local context validation.
  • Tests

    • Added coverage for immediate snapshot selection, fingerprint verification, fallback behavior, probe-result reuse, and cache-clear conditions.

Review Change Stack

@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

vercel Bot commented May 27, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment May 27, 2026 9:12am
cmux-staging Building Building Preview May 27, 2026 9:12am

@coderabbitai

coderabbitai Bot commented May 27, 2026 •

Copy link
Copy Markdown

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

Fork snapshot selection now runs synchronously: availability helpers are module-visible, probe-result matching/reuse logic is centralized, focused-fork uses an immediate execution snapshot helper, per-panel caches and a fallback-origin flag are updated/evicted accordingly, and tests were added for immediate selection and probe caching.

Changes

Synchronous fork execution for agent conversations

Layer / File(s) Summary
Expose fork availability helpers
Sources/ContentView.swift
CommandPaletteForkSnapshotAvailability enum and commandPaletteSnapshotForkAvailability access changed from private to module-internal.
Add probe-match and reuse helpers
Sources/ContentView.swift
New module helpers compare cached probe results to expected snapshot fingerprints and remote/local context, and decide when cached probe results may be reused or must be cleared.
Per-panel state, refresh, and cached-state eviction
Sources/ContentView.swift
Added per-panel fallback-origin @State and refactored availability refresh and probe completion to reuse or clear per-panel cached probe state using the new helpers; clears fallback-origin when appropriate.
Add immediate fork snapshot computation
Sources/ContentView+ForkAgentConversation.swift
New commandPaletteImmediateForkExecutionSnapshot(...) computes a fork execution snapshot without an index snapshot, gates by availability, validates panel-key membership, remote-context, and fingerprint, and returns a verified cached or fallback snapshot.
Refactor fork-focused conversation to immediate execution
Sources/ContentView+ForkAgentConversation.swift
forkFocusedAgentConversation now validates focused terminal panel synchronously, derives ids/contexts, uses the immediate snapshot helper, updates or evicts per-panel caches/fingerprints/remote-context via a centralized clear helper, and performs the fork directly without async probes.
Run timing for command palette results
Sources/ContentView.swift
Certain fork-agent conversation commands now dismiss the palette before executing their action via commandPaletteShouldDismissBeforeRun(forCommandId:) and runCommandPaletteResult gating.
Test immediate fork snapshot behavior and probe reuse
cmuxTests/CommandPaletteSearchEngineTests.swift
Added tests covering immediate snapshot selection (fallback, probe-verified fallback, probe-verified cached preference, stale fingerprint rejection), probe-result reuse/matching/clear-before-probe; adjusted fallback availability tests and removed obsolete post-probe focus/remote-context test.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I nudge a snapshot, quick and spry,
No waiting probes to pass me by,
Fingerprints checked and caches neat,
Panels split clean — the tests all meet,
A tiny hop for code complete.


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift @Concurrent ❌ Error startCommandPaletteForkableAgentAvailabilityProbe creates Task calling I/O-heavy loadIncludingProcessDetectedSnapshots on MainActor without explicit actor hop, violating concurrent-annotation rule. Use Task.detached for I/O-heavy file loading before returning to MainActor with MainActor.run.
Cmux Architecture Rethink ❌ Error PR adds 5 new @State caches modified from independent paths (fork execution and async probe) without synchronization, creating representable bad states where fork reads during probe write. Add synchronization: check probe task state before fork uses cache, or move caching to single coordinated owner (actor/model).
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 (14 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: synchronous fork execution from the focused conversation snapshot without awaiting a palette probe, matching the primary refactor across the changeset.
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 Swift Actor Isolation ✅ Passed ContentView is SwiftUI View (MainActor), @State properties properly isolated, static helpers are pure functions. No actor isolation violations.
Cmux Swift Blocking Runtime ✅ Passed No blocking patterns (semaphores, sleeps, locks, DispatchQueue.main.sync, polling) introduced in production Swift code. All new functions are free of blocking synchronization.
Cmux No Hacky Sleeps ✅ Passed All PR changes are in Swift files. The rule explicitly excludes Swift (per runtime-no-hacky-sleeps.md scope), applying only to TypeScript, JavaScript, shell, and non-Swift build scripts.
Cmux Swift Concurrency ✅ Passed PR introduces synchronous fork snapshot helpers and cache logic with no new legacy async patterns: no DispatchQueue.global, new Combine state, completion handlers, or fire-and-forget Tasks.
Cmux Swift File And Package Boundaries ✅ Passed PR adds 168 lines to ContentView.swift, below the 250-line threshold; qualifies as focused bug fix with clear extraction path and independent test coverage.
Cmux Swift Logging ✅ Passed NSLog in ContentView.swift is DEBUG-guarded (allowed); print statements in CommandPaletteSearchEngineTests.swift are in tests (allowed). Main fork conversation changes have no logging.
Cmux User-Facing Error Privacy ✅ Passed PR introduces no user-facing error messages or sensitive data exposure. Changes are internal utility functions for fork snapshot cache management with error handling limited to NSSound.beep().
Cmux Full Internationalization ✅ Passed PR modifies fork agent logic but introduces no new user-facing strings. Existing fork agent strings are properly localized for all supported locales. Changes are internal refactoring only.
Cmux Swiftui State Layout ✅ Passed New fork-related state uses modern @State pattern with all mutations in lifecycle handlers (.onChange/.onAppear), not in body rendering; no ObservableObject/StateObject patterns introduced.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR refactors fork conversation flow and cache logic with no new NSWindow, NSPanel, NSWindowController, Window, or WindowGroup creation; allowed patterns only.
Description check ✅ Passed The pull request description is comprehensive and includes all required sections: a clear summary of changes and rationale, verification steps with test results, and a detailed explanation of the implementation with risk assessment.
✨ 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 fix-fork-conversation-lag

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

greptile-apps Bot commented May 27, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR converts the command palette fork-conversation flow from an async Task { @MainActor in } with a fresh RestorableAgentSessionIndex load on every invocation to a synchronous execution path that reads from a probe-verified snapshot cache. Palette fork commands now dismiss the palette before running the action, and a new commandPaletteForkableAgentResultHadFallbackByPanelKey dictionary tracks whether a cached result relied on a fallback snapshot, driving stricter re-probe decisions.

  • Synchronous fork execution uses commandPaletteImmediateForkExecutionSnapshotSelection to select a cached or fallback snapshot only when the cached probe fingerprint, panel key, and remote/local context all match; .requiresProbe (OpenCode) snapshots now correctly prefer the probe-verified live cached snapshot over the restored fallback, addressing the regression noted in the prior review.
  • Cache cleanup is now routed through a shared clearCommandPaletteForkableAgentCache helper called on all failure paths (selection miss, forkAgentWorkspaceLaunch nil, forkAgentConversation nil), fixing the orphaned-entry bugs flagged in earlier reviews.
  • Palette visibility for fork commands now requires a matching probe entry in commandPaletteForkableAgentSupportedPanelKeys; pure fallback snapshots without a prior probe are hidden until the async probe completes.

Confidence Score: 4/5

The logic is well-tested and the cache-orphan regressions from prior reviews are fixed, but the pre-dismiss / focus-restoration interaction for fork commands needs confirmation before merging.

The synchronous fork path and the new commandPaletteShouldDismissBeforeRun early-dismiss branch are the highest-risk changes. Fork commands have no commandPalettePostRunRestoreFocusIntent entry, so the early-dismiss branch always calls dismissCommandPalette(restoreFocus: false) immediately before forkFocusedAgentConversation reads focusedPanelContext. If AppKit's responder-chain update after palette dismissal is not synchronous, the guard in forkFocusedAgentConversation will silently fail with a beep on every keypress. The unit tests exercise the static cache helpers thoroughly but cannot cover this AppKit timing dependency at the integration level.

Sources/ContentView.swift — specifically the interaction between commandPaletteShouldDismissBeforeRun, the early-dismiss branch in runCommandPaletteCommand, and the focusedPanelContext read in forkFocusedAgentConversation

Important Files Changed

Filename Overview
Sources/ContentView+ForkAgentConversation.swift Core fork logic converted from async Task to synchronous execution using probe-verified cache; failure paths now uniformly call clearCommandPaletteForkableAgentCache; addresses prior orphaned-cache bugs; new snapshot selection correctly prefers live cached over fallback in .requiresProbe branch
Sources/ContentView.swift Adds commandPaletteForkableAgentResultHadFallbackByPanelKey as a sixth parallel cache dictionary; adds commandPaletteShouldDismissBeforeRun for pre-action palette dismissal; adjusts commandPalettePanelHasForkableAgent to require probe verification before showing fork commands for fallback snapshots
cmuxTests/CommandPaletteSearchEngineTests.swift Tests replaced and expanded to cover immediate fork selection, stale fingerprint rejection, probe-verified fallback reuse, and dismiss-before-run behavior; old post-probe context tests removed and replaced with new equivalents

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[runCommandPaletteCommand] --> B{command.dismissOnRun AND\ncommandPaletteShouldDismissBeforeRun?}
    B -- Yes --> C{postRunFocusTarget nil?}
    C -- No --> D[dismissCommandPalette restoreFocus:true]
    C -- Yes, fork commands have no RestoreFocusIntent --> E[dismissCommandPalette restoreFocus:false]
    D --> F[command.action]
    E --> F
    B -- No --> G[command.action then dismiss]
    F --> H[forkFocusedAgentConversation]
    H --> I{focusedPanelContext is terminal?}
    I -- No --> J[beep + return]
    I -- Yes --> K[commandPaletteImmediateForkExecutionSnapshotSelection]
    K --> L{fallbackSnapshot exists?}
    L -- Yes --> M{availability?}
    M -- unsupported --> N[return nil]
    M -- supportedWithoutProbe or requiresProbe --> O{probeResultMatches fingerprint + remote?}
    O -- No --> N
    O -- Yes --> P{verifiedCachedSnapshot exists?}
    P -- Yes --> Q[return cachedSnapshot usedFallback=false]
    P -- No --> R[return fallbackSnapshot usedFallback=true]
    L -- No --> S{verifiedCachedSnapshot no fingerprint check}
    S -- nil --> N
    S -- found --> T[return cachedSnapshot usedFallback=false]
    K -- nil selection --> U[clearCache + beep]
    K -- valid selection --> V[write cache dicts]
    V --> W{destination}
    W -- split --> X[forkAgentConversation]
    W -- newWorkspace --> Y[forkAgentWorkspaceLaunch]
    X -- nil --> Z[clearCache + beep]
    Y -- nil --> Z
    X -- non-nil --> AA[success]
    Y -- non-nil --> AB[addWorkspace + success]
Loading

Reviews (10): Last reviewed commit: "Preserve verified fork cache fallback fl..." | Re-trigger Greptile

Comment thread Sources/ContentView+ForkAgentConversation.swift Outdated
Comment thread Sources/ContentView+ForkAgentConversation.swift
coderabbitai[bot]
coderabbitai Bot previously requested changes May 27, 2026

@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

Caution

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

⚠️ Outside diff range comments (1)
Sources/ContentView+ForkAgentConversation.swift (1)

154-159: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't return an unverified cached snapshot for probe-only agents.

This branch verifies the fingerprint of fallbackSnapshot ?? cachedSnapshot, but then always prefers cachedSnapshot when it exists. If fallbackSnapshot is the thing that matched, a stale cached snapshot with a different sessionId can still win here, and SessionRestorableAgentSnapshot.forkCommand is derived from that sessionId. The new test in CommandPaletteSearchEngineTests already demonstrates this by expecting "live-opencode-session" to beat a verified "restored-opencode-session".

💡 Proposed fix
         case .requiresProbe:
             guard supportedPanelKeys.contains(panelKey),
                   supportedRemoteContextsByPanelKey[panelKey] == isRemoteTerminal else {
                 return nil
             }
             let expectedFingerprint = (fallbackSnapshot ?? cachedSnapshot).map(commandPaletteForkSnapshotFingerprint)
             guard let expectedFingerprint,
                   snapshotFingerprintsByPanelKey[panelKey] == expectedFingerprint else {
                 return nil
             }
-            return cachedSnapshot ?? snapshot
+            if let cachedSnapshot,
+               let fallbackSnapshot,
+               cachedSnapshot.forkCommand == fallbackSnapshot.forkCommand {
+                return cachedSnapshot
+            }
+            return snapshot
         }
🤖 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/ContentView`+ForkAgentConversation.swift around lines 154 - 159, The
code currently verifies the fingerprint of (fallbackSnapshot ?? cachedSnapshot)
but then unconditionally returns cachedSnapshot if present, which can return an
unverified/stale snapshot; update the return logic in the block that uses
commandPaletteForkSnapshotFingerprint, snapshotFingerprintsByPanelKey and
panelKey so that you return the actual snapshot that matched the verified
expectedFingerprint (e.g., compare expectedFingerprint to the fingerprint of
fallbackSnapshot and to the fingerprint of cachedSnapshot separately and return
fallbackSnapshot if it matched, otherwise return cachedSnapshot if it matched),
ensuring SessionRestorableAgentSnapshot.forkCommand comes from the verified
snapshot rather than always preferring cachedSnapshot.
🤖 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 `@Sources/ContentView.swift`:
- Around line 6036-6044: The code only clears per-panel caches when
probeResultMatches is false, leaving stale snapshot/support entries live when
panelChanged forces a reprobe; update the logic around the early return so that
cache eviction runs when either probeResultMatches is false OR panelChanged is
true (e.g., replace the current if !probeResultMatches { ... } with if
!probeResultMatches || panelChanged {
commandPaletteForkableAgentSupportedPanelKeys.remove(panelKey);
commandPaletteForkableAgentSnapshotsByPanelKey.removeValue(forKey: panelKey);
commandPaletteForkableAgentSnapshotFingerprintsByPanelKey.removeValue(forKey:
panelKey);
commandPaletteForkableAgentRemoteContextsByPanelKey.removeValue(forKey:
panelKey) }) so the synchronous fork path cannot consume stale data during a
forced reprobe.

---

Outside diff comments:
In `@Sources/ContentView`+ForkAgentConversation.swift:
- Around line 154-159: The code currently verifies the fingerprint of
(fallbackSnapshot ?? cachedSnapshot) but then unconditionally returns
cachedSnapshot if present, which can return an unverified/stale snapshot; update
the return logic in the block that uses commandPaletteForkSnapshotFingerprint,
snapshotFingerprintsByPanelKey and panelKey so that you return the actual
snapshot that matched the verified expectedFingerprint (e.g., compare
expectedFingerprint to the fingerprint of fallbackSnapshot and to the
fingerprint of cachedSnapshot separately and return fallbackSnapshot if it
matched, otherwise return cachedSnapshot if it matched), ensuring
SessionRestorableAgentSnapshot.forkCommand comes from the verified snapshot
rather than always preferring cachedSnapshot.
🪄 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: e7e2ca59-b577-4263-9145-aea962d592df

📥 Commits

Reviewing files that changed from the base of the PR and between 4319e28 and bd0d175.

📒 Files selected for processing (3)
  • Sources/ContentView+ForkAgentConversation.swift
  • Sources/ContentView.swift
  • cmuxTests/CommandPaletteSearchEngineTests.swift

Comment thread Sources/ContentView.swift
Comment thread Sources/ContentView+ForkAgentConversation.swift Outdated
Comment thread Sources/ContentView+ForkAgentConversation.swift

@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.

Caution

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

⚠️ Outside diff range comments (1)
Sources/ContentView+ForkAgentConversation.swift (1)

41-50: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject fallback-derived probe cache once the fallback snapshot is gone.

You're already tracking whether the cached probe result was anchored to a fallback fingerprint, but this helper never receives that flag. As a result, the no-fallback path still returns a cached .requiresProbe snapshot after fallbackSnapshot becomes nil, even when that cache entry was only validated against an older fallback fingerprint. That can fork a stale probe-only session after the restored snapshot is evicted.

Suggested fix
 let snapshot = Self.commandPaletteImmediateForkExecutionSnapshot(
     workspaceId: workspaceId,
     panelId: panelId,
     isRemoteTerminal: isRemoteContext,
     supportedPanelKeys: commandPaletteForkableAgentSupportedPanelKeys,
     supportedRemoteContextsByPanelKey: commandPaletteForkableAgentRemoteContextsByPanelKey,
     snapshotFingerprintsByPanelKey: commandPaletteForkableAgentSnapshotFingerprintsByPanelKey,
     fallbackSnapshot: fallbackSnapshot,
-    cachedSnapshot: commandPaletteForkableAgentSnapshotsByPanelKey[panelKey]
+    cachedSnapshot: commandPaletteForkableAgentSnapshotsByPanelKey[panelKey],
+    cachedResultHadFallback: commandPaletteForkableAgentResultHadFallbackByPanelKey[panelKey] == true
 )

 static func commandPaletteImmediateForkExecutionSnapshot(
     workspaceId: UUID,
     panelId: UUID,
     isRemoteTerminal: Bool,
     supportedPanelKeys: Set<String>,
     supportedRemoteContextsByPanelKey: [String: Bool],
     snapshotFingerprintsByPanelKey: [String: String],
     fallbackSnapshot: SessionRestorableAgentSnapshot?,
-    cachedSnapshot: SessionRestorableAgentSnapshot?
+    cachedSnapshot: SessionRestorableAgentSnapshot?,
+    cachedResultHadFallback: Bool
 ) -> SessionRestorableAgentSnapshot? {
     ...
     switch commandPaletteSnapshotForkAvailability(
         cachedSnapshot,
         isRemoteTerminal: isRemoteTerminal
     ) {
-    case .supportedWithoutProbe, .requiresProbe:
+    case .supportedWithoutProbe:
         return cachedSnapshot
+    case .requiresProbe:
+        return cachedResultHadFallback ? nil : cachedSnapshot
     case .unsupported:
         return nil
     }
 }

Also applies to: 69-69, 124-133, 182-190

🤖 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/ContentView`+ForkAgentConversation.swift around lines 41 - 50, The
helper Self.commandPaletteImmediateForkExecutionSnapshot is not being told
whether a cached probe result was anchored to a fallback, so when
fallbackSnapshot becomes nil we still accept cached `.requiresProbe` snapshots
that were validated only against an earlier fallback; update the call sites that
pass cachedSnapshot (places using commandPaletteForkableAgentSnapshotsByPanelKey
and commandPaletteForkableAgentSnapshotFingerprintsByPanelKey) to also pass a
boolean flag (e.g., cachedSnapshotAnchoredToFallback) derived from your existing
tracking of whether the cached probe was anchored to a fallback fingerprint, and
then modify commandPaletteImmediateForkExecutionSnapshot to reject and ignore a
cached snapshot if cachedSnapshotAnchoredToFallback is true while
fallbackSnapshot is nil; apply the same change to the other call sites noted
(the other invocations of commandPaletteImmediateForkExecutionSnapshot).
♻️ Duplicate comments (1)
Sources/ContentView.swift (1)

6060-6073: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Evict the cached probe state whenever panelChanged forces a reprobe.

Both branches reject reuse when panelChanged is true, but they only clear the per-panel probe cache on a fingerprint/context mismatch. That leaves the previous panel's supported/snapshot/fingerprint state live until the async probe completes, so the synchronous fork path added in this stack can still consume stale probe data during the forced reprobe.

♻️ Proposed fix
-                if !probeResultMatches {
+                if panelChanged || !probeResultMatches {
                     commandPaletteForkableAgentSupportedPanelKeys.remove(panelKey)
                     commandPaletteForkableAgentSnapshotsByPanelKey.removeValue(forKey: panelKey)
                     commandPaletteForkableAgentSnapshotFingerprintsByPanelKey.removeValue(forKey: panelKey)
                     commandPaletteForkableAgentRemoteContextsByPanelKey.removeValue(forKey: panelKey)
                     commandPaletteForkableAgentResultHadFallbackByPanelKey.removeValue(forKey: panelKey)
                 }
-        if !Self.commandPaletteForkableAgentProbeResultMatches(
+        let probeResultMatches = Self.commandPaletteForkableAgentProbeResultMatches(
             panelKey: panelKey,
             supportedPanelKeys: commandPaletteForkableAgentSupportedPanelKeys,
             supportedRemoteContextsByPanelKey: commandPaletteForkableAgentRemoteContextsByPanelKey,
             snapshotFingerprintsByPanelKey: commandPaletteForkableAgentSnapshotFingerprintsByPanelKey,
             expectedSnapshotFingerprint: nil,
             isRemoteTerminal: isRemoteTerminal
-        ) {
+        )
+        if panelChanged || !probeResultMatches {
             commandPaletteForkableAgentSupportedPanelKeys.remove(panelKey)
             commandPaletteForkableAgentSnapshotsByPanelKey.removeValue(forKey: panelKey)
             commandPaletteForkableAgentSnapshotFingerprintsByPanelKey.removeValue(forKey: panelKey)
             commandPaletteForkableAgentRemoteContextsByPanelKey.removeValue(forKey: panelKey)
             commandPaletteForkableAgentResultHadFallbackByPanelKey.removeValue(forKey: panelKey)
         }

Also applies to: 6098-6112

🤖 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/ContentView.swift` around lines 6060 - 6073, The code currently only
evicts per-panel probe caches when probeResultMatches is false, but a forced
reprobe (panelChanged == true) can leave stale data; update the logic around
probeResultMatches and panelChanged (the block that manipulates
commandPaletteForkableAgentSupportedPanelKeys,
commandPaletteForkableAgentSnapshotsByPanelKey,
commandPaletteForkableAgentSnapshotFingerprintsByPanelKey,
commandPaletteForkableAgentRemoteContextsByPanelKey, and
commandPaletteForkableAgentResultHadFallbackByPanelKey) so that you also
remove/clear those per-panel entries when panelChanged forces a reprobe (i.e.,
treat panelChanged as an eviction condition in the same places that currently
check !probeResultMatches), and make the same change in the other mirrored block
(the one around startCommandPaletteForkableAgentAvailabilityProbe at the later
lines).
🤖 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.

Outside diff comments:
In `@Sources/ContentView`+ForkAgentConversation.swift:
- Around line 41-50: The helper
Self.commandPaletteImmediateForkExecutionSnapshot is not being told whether a
cached probe result was anchored to a fallback, so when fallbackSnapshot becomes
nil we still accept cached `.requiresProbe` snapshots that were validated only
against an earlier fallback; update the call sites that pass cachedSnapshot
(places using commandPaletteForkableAgentSnapshotsByPanelKey and
commandPaletteForkableAgentSnapshotFingerprintsByPanelKey) to also pass a
boolean flag (e.g., cachedSnapshotAnchoredToFallback) derived from your existing
tracking of whether the cached probe was anchored to a fallback fingerprint, and
then modify commandPaletteImmediateForkExecutionSnapshot to reject and ignore a
cached snapshot if cachedSnapshotAnchoredToFallback is true while
fallbackSnapshot is nil; apply the same change to the other call sites noted
(the other invocations of commandPaletteImmediateForkExecutionSnapshot).

---

Duplicate comments:
In `@Sources/ContentView.swift`:
- Around line 6060-6073: The code currently only evicts per-panel probe caches
when probeResultMatches is false, but a forced reprobe (panelChanged == true)
can leave stale data; update the logic around probeResultMatches and
panelChanged (the block that manipulates
commandPaletteForkableAgentSupportedPanelKeys,
commandPaletteForkableAgentSnapshotsByPanelKey,
commandPaletteForkableAgentSnapshotFingerprintsByPanelKey,
commandPaletteForkableAgentRemoteContextsByPanelKey, and
commandPaletteForkableAgentResultHadFallbackByPanelKey) so that you also
remove/clear those per-panel entries when panelChanged forces a reprobe (i.e.,
treat panelChanged as an eviction condition in the same places that currently
check !probeResultMatches), and make the same change in the other mirrored block
(the one around startCommandPaletteForkableAgentAvailabilityProbe at the later
lines).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7595eb15-61e3-43cf-b0e1-0bd957e1350f

📥 Commits

Reviewing files that changed from the base of the PR and between bd0d175 and 88c7f93.

📒 Files selected for processing (3)
  • Sources/ContentView+ForkAgentConversation.swift
  • Sources/ContentView.swift
  • cmuxTests/CommandPaletteSearchEngineTests.swift

Comment thread Sources/ContentView+ForkAgentConversation.swift
Comment thread Sources/ContentView.swift
Comment thread Sources/ContentView+ForkAgentConversation.swift
coderabbitai[bot]
coderabbitai Bot previously requested changes May 27, 2026

@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

Caution

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

⚠️ Outside diff range comments (1)
Sources/ContentView+ForkAgentConversation.swift (1)

57-65: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Track actual fallback usage in commandPaletteForkableAgentResultHadFallbackByPanelKey.

Line 65 marks every successful fork as fallback-backed whenever a fallback snapshot exists, even when commandPaletteImmediateForkExecutionSnapshot returned the verified cached snapshot. That makes the next clear-before-probe path evict a live verified cache as if it were fallback-derived, which reintroduces the reopen lag this PR is trying to remove. Store whether the chosen snapshot actually came from fallbackSnapshot, not whether a fallback was merely present.

🤖 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/ContentView`+ForkAgentConversation.swift around lines 57 - 65, The
code currently sets
commandPaletteForkableAgentResultHadFallbackByPanelKey[panelKey] =
fallbackFingerprint != nil, which records only the presence of a fallback
instead of whether the chosen snapshot actually came from that fallback; change
this to record whether the selected snapshot is the fallback (e.g., set
commandPaletteForkableAgentResultHadFallbackByPanelKey[panelKey] = (snapshot ===
fallbackSnapshot) or, if snapshots are value-equal rather than reference-equal,
compare their fingerprints or an identifier), while leaving assignments to
commandPaletteForkableAgentSnapshotsByPanelKey,
commandPaletteForkableAgentSnapshotFingerprintsByPanelKey, and
commandPaletteForkableAgentRemoteContextsByPanelKey unchanged.
🤖 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/CommandPaletteSearchEngineTests.swift`:
- Around line 1167-1224: Update
testForkableAgentProbeResultClearBeforeProbeClearsFallbackBackedCache to
exercise the “verified cache beats fallback” path by invoking
ContentView.commandPaletteImmediateForkExecutionSnapshot to write a state that
contains both a cached snapshot and a fallback snapshot, assert that the helper
returns the cached snapshot (not the fallback), then call
ContentView.commandPaletteShouldClearForkableAgentProbeResultBeforeProbe with
the resulting state and verify the stored cachedResultHadFallback flag remains
false; reference commandPaletteImmediateForkExecutionSnapshot,
cachedResultHadFallback, and
commandPaletteShouldClearForkableAgentProbeResultBeforeProbe when adding this
new case so the test fails if reopening regresses.

---

Outside diff comments:
In `@Sources/ContentView`+ForkAgentConversation.swift:
- Around line 57-65: The code currently sets
commandPaletteForkableAgentResultHadFallbackByPanelKey[panelKey] =
fallbackFingerprint != nil, which records only the presence of a fallback
instead of whether the chosen snapshot actually came from that fallback; change
this to record whether the selected snapshot is the fallback (e.g., set
commandPaletteForkableAgentResultHadFallbackByPanelKey[panelKey] = (snapshot ===
fallbackSnapshot) or, if snapshots are value-equal rather than reference-equal,
compare their fingerprints or an identifier), while leaving assignments to
commandPaletteForkableAgentSnapshotsByPanelKey,
commandPaletteForkableAgentSnapshotFingerprintsByPanelKey, and
commandPaletteForkableAgentRemoteContextsByPanelKey unchanged.
🪄 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: 45cdd97c-4fea-4a53-85ae-6f39b5ddf3a3

📥 Commits

Reviewing files that changed from the base of the PR and between 88c7f93 and e6b451b.

📒 Files selected for processing (3)
  • Sources/ContentView+ForkAgentConversation.swift
  • Sources/ContentView.swift
  • cmuxTests/CommandPaletteSearchEngineTests.swift

Comment thread cmuxTests/CommandPaletteSearchEngineTests.swift
Comment thread Sources/ContentView.swift
Comment thread Sources/ContentView+ForkAgentConversation.swift

@cursor cursor 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.

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 c0ce2f0. Configure here.

Comment thread Sources/ContentView+ForkAgentConversation.swift
@lawrencecchen
lawrencecchen dismissed stale reviews from coderabbitai[bot] and coderabbitai[bot] May 27, 2026 08:49

Dismissed as stale. The requested cache selection fix was addressed in later commits, all review threads are resolved, latest CodeRabbit check passed/skipped, and Codex review is clean.

@lawrencecchen
lawrencecchen merged commit 53a533d into main May 27, 2026
27 of 29 checks passed
@lawrencecchen
lawrencecchen deleted the fix-fork-conversation-lag branch May 27, 2026 08:56
Vangor added a commit to Vangor/most that referenced this pull request May 27, 2026
* test: cover external dot path open

* fix: open external path arguments without socket access

* fix: preserve explicit socket path opens

* fix: bound launchservices open helper

* fix: avoid blocking primitive in open helper

* fix: format localized path open errors

* fix: complete path open localization

* fix: scrub socket env for external path opens

* fix: localize path open success output

* Add cmux.xcworkspace with Packages visible alongside the project (manaflow-ai#4834)

* Add cmux.xcworkspace with Packages visible alongside the project

Top-level navigator shows cmux.xcodeproj plus every Packages/* SPM
package as siblings, so local packages are editable in the same window
without nesting under the project node. Additive only: existing
-project build flows (reload.sh, CI) keep working unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Ignore xcshareddata under xcworkspaces

Xcode auto-creates xcshareddata inside .xcworkspace bundles for
WorkspaceSettings.xcsettings, IDEWorkspaceChecks.plist, and SwiftPM
configuration. None of those carry intentional shared state for cmux
today (build is driven by reload.sh and CI, no private SPM registry,
no custom file headers). Ignoring them prevents Xcode bookkeeping
churn in commits. Project-level xcshareddata (shared .xcscheme files)
is unaffected because the pattern only matches inside .xcworkspace
directories.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* fix: scrub legacy panel env for path opens

* fix: limit path open locale entries

* Redesign notifications popover: bigger, minimal, swipe to dismiss (manaflow-ai#4778)

* Redesign notifications popover: bigger, minimal, swipe to dismiss

- Larger frame (min 460x480, ideal 560x620, max 760x760) so more
  notifications are visible at once.
- Compact rows with a thin accent bar for unread, tighter typography,
  hover-revealed clear button. No more boxed cards per row, just
  dividers between rows.
- Drag a row left or right to dismiss; passing the threshold slides it
  out and removes the notification. Tap to open as before.
- Empty/loading/populated states share the same outer frame so the
  popover stops resizing when notifications come and go.

* Show jump-to-latest keyboard shortcut next to button label

* Stabilize hover on notification rows: dedicated background tracking layer

.onHover and .onTapGesture on the same SwiftUI node share AppKit's
mouse-tracking pipeline and arbitrate against each other, producing
flaky enter/exit events right after the popover opens and when
crossing rows fast. Move hover detection to a transparent background
layer using .onContinuousHover, scope the opacity animation to the
background only so it does not re-animate dragOffset, and bump the
hover opacity from 0.07 to 0.11 so it is visible against
.windowBackgroundColor in light mode.

* Use NSTrackingArea for notification row hover

SwiftUI's .onHover and .onContinuousHover still arbitrate with the
parent .onTapGesture in macOS popover content, leaving rows where
hover never fires (notably on first popover open and when crossing
between LazyVStack rows). Replace the SwiftUI hover modifier with an
NSViewRepresentable that installs an NSTrackingArea
(.mouseEnteredAndExited + .activeAlways + .inVisibleRect). The
tracking NSView returns nil from hitTest so clicks pass through to
the SwiftUI parent's tap gesture, and the view syncs current
inside/outside state on tracking-area install for the case where the
pointer is already inside the row when the popover opens.

* Notifications popover: keep timestamp visible, drop hover animation, add right-click menu

- Always show row timestamp (removed the hide-on-hover opacity).
- Removed the hover background fade animation so it's an instant flip.
- Right-click a row for Open / Mark as Read / Mark as Unread / Dismiss.
- Added TerminalNotificationStore.markUnread(id:) sibling to markRead(id:).
- Added en/ja localizations for the new menu strings.

* Notifications popover: drop top margin, always semibold title

* Make notifications popover resizable, taller default

Default size bumped to 560x760 (was 560x620 ideal). User can drag a
bottom-right resize handle to set their own size; the chosen width
and height persist via @AppStorage. Bounds are 420x320 .. 1000x1200.

* Notifications popover: invisible corner resize, drop swipe-to-dismiss

NSPopover has no native resize chrome and there's no first-class
SwiftUI resize API for it. The closest native-feeling thing is a
hit-only corner region that drives a state-driven .frame: no visual
button, just a resizeLeftRight cursor on hover (closest standard
NSCursor to a diagonal resize).

Also removed swipe-to-dismiss. The hover X button and the right-click
Dismiss menu item are the dismiss surfaces; swiping rows in an
NSPopover row list isn't a macOS convention.

* Notifications popover: fix glitchy resize via AppKit screen coords

SwiftUI's DragGesture reports translation in a local coordinate space
that is literally being resized under the cursor as the popover grows.
Each frame the gesture re-derives translation from a moving anchor,
which produces dimension oscillation (the popover jumps between two
sizes).

Replace the SwiftUI gesture with an NSViewRepresentable that tracks
NSEvent.mouseLocation in global screen coordinates. Screen coordinates
are stable regardless of popover resize, so deltas are monotonic.
Also disable implicit animation on the .frame width/height so the
popover follows the cursor 1:1.

* Notifications popover: use diagonal resize cursor on bottom-right corner

* Notifications popover: address review feedback

- unreadCount uses notificationMenuSnapshot.unreadCount instead of
  reducing the live list, so the badge stays consistent with the
  hasUnreadNotifications / hasNotifications guards that drive the
  Jump to Latest / Clear All disabled states.
- Hide the visual keyboard-shortcut chip from accessibility; the
  button already exposes the shortcut via .accessibilityValue, so
  VoiceOver no longer reads it twice.
- Add a row-level accessibility action to dismiss a notification,
  since the visible clear button is hover-only. Adds the localized
  string notifications.row.clear (en/ja).

* markUnread(id:): clear manual workspace unread to avoid double-count

When a user marks a read notification unread, the notification itself
now provides the workspace unread indicator. If the workspace also had
a manual unread flag set, the popover header, dock badge, and
workspace badge would double-count that workspace because the count
builders sum notification unread counts and workspace indicators.

Mirrors what markLatestNotificationAsOldestUnread already does in the
same file. Found by Codex review.

* Restore Button wrapper on notification rows for keyboard access

Replacing the original Button with .onTapGesture removed rows from
the SwiftUI key-view loop, so keyboard-only users could tab to the
header controls but not open a row. The original reason for dropping
Button was that SwiftUI's .onHover arbitrated with the row's primary
action — but hover is now driven entirely by an AppKit NSTrackingArea
(HoverTrackingRepresentable), independent of the row's activation
node. The Button wrapper is therefore safe again and restores
space/return activation in the key-view loop. Found by Codex review.

* Notifications popover: persist resize once on mouseUp, clamp drag baseline

- Live resize uses @State (liveWidth/liveHeight); @AppStorage is
  written exactly once when the user releases the mouse, instead of
  on every mouseDragged event. Each @AppStorage write was hitting
  UserDefaults and broadcasting UserDefaults.didChangeNotification to
  every app-wide observer.
- Drag baseline now comes from the clamped (currently displayed)
  size, not raw saved doubles, so a drag that starts with out-of-bounds
  stored values doesn't lose initial pointer travel re-entering the
  visible range.

* Notifications popover: clear button as ZStack sibling, not nested in row Button

Nesting clearButton inside the row's Button(action: onOpen) label
created the classic SwiftUI nested-button hit-test problem on macOS:
clicks on the inner X could be consumed by the outer row Button's
tap area instead of clearing the notification. Move clearButton out
of rowContent and into a ZStack(alignment: .trailing) at the body
level so it's a sibling of the row Button with an independent hit
target. Found by Codex review.

* Notifications popover: clamp to screen, hide hover-only X from focus

- Clamp the popover's frame against NSScreen.main?.visibleFrame minus
  an 80pt margin so the bottom-right resize handle stays reachable
  even when the saved size was captured on a larger display.
- Mark the hover-only clear button .accessibilityHidden(!isHovering)
  so Full Keyboard Access / VoiceOver doesn't focus an invisible
  dismiss control. Dismissal is still exposed via the row's
  accessibility action and the right-click menu. Found by Codex.

* CI: retry — GitHub Actions infra was returning 403/404 on codeload

* CI: retrigger now that GitHub Actions infra is back

* Mark-as-Read context menu: clear focused pane indicator too

A user-initiated 'Mark as Read' should mirror the full dismissal
intent, so the pane's focused-read indicator for that surface should
clear alongside flipping the notification's isRead flag. Otherwise
the unread count drops but the pane badge remains visible until the
user later interacts with the terminal. Found by Codex review.

* Notifications popover: clamp against host window's screen, fix hover sync race

- Clamp popover size against the screen of NSApp.keyWindow (the
  popover's anchor window), not NSScreen.main. On multi-monitor
  setups the popover may appear on a different display than 'main'.
- Remove the DispatchQueue.main.async hop in HoverTrackingNSView's
  updateTrackingAreas; the queued onChange(true) could land after
  mouseExited's synchronous onChange(false), leaving rows stuck in
  the hovered state. updateTrackingAreas already runs on the main
  thread, so the call is fine synchronously. Found by Cursor Bugbot.

* Mark-as-Read: only clear focused pane indicator for pane-scoped notifications

clearFocusedReadIndicator treats surfaceId == nil as 'clear any pane
indicator for this tab', so passing nil for a workspace-level
notification would wipe an unrelated pane's badge. Gate the call on
notification.surfaceId being non-nil. Found by Codex review.

* Notifications popover: add a11y label/hint to resize handle

The localized notifications.resize key existed but was never wired
to the actual control. Adds .accessibilityLabel/.accessibilityHint
so VoiceOver and Full Keyboard Access users can discover the resize
affordance. Found by Cursor Bugbot.

* Notifications popover: move accessibility identifier back onto the row Button

XCUITests query rows with app.buttons["NotificationPopoverRow.<id>"].
The previous pass put .accessibilityIdentifier on the combined outer
ZStack, exposing the row as a container rather than a button, which
breaks both XCUITest lookups and the button accessibility role for
assistive tech. Move identifier + primary action + clear action back
onto the inner Button. Found by Codex review.

* Notifications popover: snapshot list before LazyVStack; clear restored unread on markUnread

- ForEach now iterates an immutable snapshot captured outside the
  LazyVStack instead of reading notificationStore.notifications
  inside the row builder; the previous code reintroduced a store
  dependency below the list boundary, which CLAUDE.md flags as the
  same anti-pattern that caused the LazyLayoutViewCache spin-loop
  in the sessions panel (manaflow-ai#2586).
- markUnread(id:) now also clears the restored workspace unread
  indicator for the tab, so a session-restored unread hint plus a
  user-toggled unread notification no longer double-count in the
  header/dock/workspace badges. Found by Codex review.

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Remove History from right sidebar (manaflow-ai#4785)

* Remove History mode from right sidebar

Drops the History tab from the right sidebar mode picker along with its
keyboard shortcut, command palette entries, and the 'Open Full History'
menu button. Recently Closed/Focused submenus and Reopen Last Closed
still work from the menu bar.

* Drop tests for deleted HistoryDayGrouping helper

* Tolerate obsolete history mode and restore focus-history menu key

- SessionRightSidebarToolPanelSnapshot now decodes an unknown mode (e.g.
  legacy 'history') as nil instead of failing the entire snapshot, so
  upgraded users can still restore prior sessions.
- Restore the 'menu.history.showFullFocusHistory' key still referenced
  by AppDelegate+FocusHistoryContextMenu for non-sidebar focus history.

* Remove duplicate @mainactor attribute left by prior edit

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Launch restored agent sessions via startup commands (manaflow-ai#4777)

* Test agent restore startup command launch

* Launch restored agents with startup commands

* Tighten restored startup launch state

* Preserve remote startup for agent restore

* Relax remote startup restore assertion

* Fix startup restore lifecycle

* Remove unused restore launch flag

* Run startup resumes in login shell

* Tighten remote restore assertion

* Handle csh agent restore launchers

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Wrap workspace titles in sidebar (manaflow-ai#4848)

* Wrap workspace titles in sidebar

* Add workspace title wrap toggle

* Localize workspace title wrap setting

* Align wrapped workspace title accessories

* Reset workspace title wrap preference

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* test: cover bare relative external path open

* fix: open bare relative path arguments externally

* fix: preserve refresh surfaces command precedence

* fix: keep bare path command matching case-sensitive

* Add secure cmux navigation links (manaflow-ai#4857)

* Add secure cmux navigation links

* Localize cmux navigation link labels

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Fix matched sidebar terminal background (manaflow-ai#4780)

* test: cover matched sidebar background tint

* fix: match sidebar terminal background

* fix: remove dead sidebar overlay path

* test: cover matched sidebar boundary separators

* fix: keep matched sidebar separators visible

* test: cover matched sidebar chrome borders

* test: sample sidebar boundary resizers

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Fix JSONC comment skipper for CRLF line endings (manaflow-ai#4869)

* Fix JSONC // comment skipper for CRLF line endings

Swift treats \r\n as a single extended grapheme cluster, so the existing
source[index] != "\n" check inside the three // line-comment skippers
never matches a CRLF line ending. On a file with CRLF throughout, the
loop runs past the rest of the file looking for a standalone LF and
strips everything after the first //. preprocess() then produces
truncated JSON and loadCmuxSettingsRoot() / jsonObject() throw
"Unexpected end of file".

This was breaking the tmux corpus terminal-nightly job's
testSurfaceResumeApprovalWritesRecordsIntoCmuxJSON, which explicitly
exercises the CRLF path. Match any character whose first scalar is CR
or LF so we stop at CR, LF, or CRLF correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Allow workflow_dispatch to run tmux corpus terminal-nightly

So this workflow's macOS test job is reproducible on demand for branches
under review, not only on the nightly schedule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix JSONC editor indent detection for CRLF line endings

indentationBeforeLine walked back to find the start of the line
containing a given index by stopping at "\n" or "\r" characters, but on
CRLF input Swift treats "\r\n" as a single extended grapheme cluster
that equals neither. The loop then walked past every line break to the
start of the file and returned an empty indent, so newly inserted
properties (e.g. resumeCommands) ended up at the wrong indentation
level. Reuse the isLineTerminator helper that already handles CRLF
correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Open forked conversations without palette action probe (manaflow-ai#4852)

* Open forked conversations without palette action probe

* Fix fork probe snapshot selection

* Refresh fork probe cache per palette session

* Preserve verified fork probe visibility

* Preserve fork snapshot cache during live refresh

* Allow verified fallback fork execution

* Reprobe fork cache after fallback clears

* Preserve focus after instant fork commands

* Clear fork cache on execution failure

* Avoid stale fork cache on palette reopen

* Clear stale fallback fork cache before reprobe

* Preserve verified fork cache on palette reopen

* Require verified fork snapshot before fallback execution

* Track actual fork fallback usage

* Preserve verified fork cache fallback flag

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* test: cover bonsplit tab indicator drift

* fix: update bonsplit tab indicator handling

* Skip Cmd+Shift key forwarding test when Ghostty surface init fails (manaflow-ai#4871)

The tmux corpus terminal-nightly runner cannot initialize a
Metal-backed Ghostty surface; embedded_window logs
"error initializing surface err=error.OutOfMemory" for every surface
the suite creates, and ghostty_surface_new returns nil. The test then
fails XCTAssertTrue on performKeyEquivalentAfterMenuMiss because
ensureSurfaceReadyForInput cannot return a live surface, which looks
like a key-forwarding regression rather than the environment issue
that it is.

Skip the test (via XCTSkipUnless on TerminalSurface.hasLiveSurface)
when the surface fails to initialize, so CI surfaces the real problem
(Metal unavailable on this runner) without masking it as a key-bind
regression. The test still runs end-to-end on developer machines and
on runners with a logged-in GUI session.

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Add algorithmic complexity review rule (manaflow-ai#4866)

* Add algorithmic complexity review rule

* Align algorithmic complexity review hint

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Narrow AppDelegate CI quarantine (manaflow-ai#4874)

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Restore browser devtools config CI coverage

* Add prompt and rules deeplinks (manaflow-ai#4839)

* Add prompt and rules deeplinks

* Preserve encoded punctuation in text deeplinks

* Localize text deeplink dialogs

* Support Freestyle SSH link users

* Harden prompt and rules deeplinks

* Suppress welcome for text deeplinks

* Restore SSH deeplink trust gate

* Preserve plus signs in text deeplinks

* Respect no-focus for text deeplink target selection

* Use deterministic text deeplink target

* Start background text deeplink targets

* Report text deeplink send failures

* Handle mixed deeplink batches safely

* Gate mixed external deeplink batches

* Report failed external text deeplink sends

* Stop after handling external cmux links

* Allow multiline cmux text links

* Reject control characters in cmux text links

* Polish external cmux link handling

* Label sendTextWhenReady callbacks

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Make session index backfill linear (manaflow-ai#4868)

* Make session index backfill linear

* Stabilize session index backfill tie breaks

* Fix session index tie-break test setup

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Optimize batch workspace sidebar actions (manaflow-ai#4865)

* Optimize batch workspace sidebar actions

* Preserve workspace selection after drag reorder

* Harden workspace batch action coverage

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* test: harden bonsplit scroll indicator check

* Restore browser remote store CI coverage

* Make browser search providers configurable (manaflow-ai#4849)

* feat: make browser search providers configurable

* fix: keep parsing browser settings after invalid custom search config

* test: harden custom search config regression

* fix: handle custom search edge cases

* fix: validate custom search fallbacks

* fix: localize search engine labels

* fix: gate stale search suggestions

* fix: allow blank custom search names

* fix: add exa search provider

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Restore browser lifecycle CI coverage

* docs: add cmux ssh deep links (manaflow-ai#4833)

* docs: add cmux ssh deep links

* docs: add cmux deeplink fallback pages

* docs: add deeplink locale fallbacks

* docs: tighten deeplink fallback validation

* docs: mirror native ssh deeplink validation

* docs: localize deeplink messages

* docs: canonicalize rules deeplink alternates

* Allow Freestyle SSH deeplink users

* docs: validate text deeplink fallback params

* docs: ignore blank optional deeplink params

* docs: address deeplink review comments

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Restore file preview review CI coverage

* Pin Xcode 26 (objectVersion 60) and add pbxproj normalizer + CI guard (manaflow-ai#4836)

* Add deterministic normalizer for cmux.xcodeproj/project.pbxproj

scripts/normalize-pbxproj.py sorts the high-churn sections (PBXBuildFile,
PBXFileReference, and the files = (...) arrays inside Sources / Resources
/ Frameworks / CopyFiles build phases) into a deterministic order keyed
on the entry comment plus UUID. The Xcode build does not care about the
order of these flat dictionary sections; sorting them just kills the
nondeterministic diff noise Xcode generates on every UI touch.

Does not touch UUIDs, comments, or PBXGroup children = (...) arrays
(navigator order is intentional). Idempotent: a second run produces zero
diff.

Standalone in this commit so the diff is just the script. The next
commit applies the script and bumps objectVersion in one shot, so
the resulting churn is contained and never repeated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Pin objectVersion = 60 and normalize pbxproj

Bumps objectVersion from 56 to 60 (the format Xcode 16+ and Xcode 26
write by default) and runs scripts/normalize-pbxproj.py once to
establish the deterministic baseline. After this commit, future
diffs to project.pbxproj show only real changes, not Xcode's
nondeterministic section reordering.

One-time large diff. No semantic changes to targets, sources, build
phases, or settings: pure sort + version pin.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Add tracked pre-commit hook that normalizes pbxproj

scripts/git-hooks/pre-commit calls scripts/normalize-pbxproj.py on
cmux.xcodeproj/project.pbxproj when it is staged and re-stages the
result. scripts/install-git-hooks.sh points the clone at this directory
via `git config core.hooksPath scripts/git-hooks`, and scripts/setup.sh
auto-runs it so devs get the hook without a separate manual step.

After this, Xcode's nondeterministic reordering of build-file and
file-reference sections is canceled out at commit time. The CI guard
in the next commit enforces the rule for anyone who bypasses the hook
with --no-verify or who never ran setup.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Add CI guard for objectVersion pin and pbxproj normalization

scripts/check-pbxproj.sh asserts cmux.xcodeproj/project.pbxproj has
objectVersion = 60 (Xcode 26 default) and that the file is normalized
per scripts/normalize-pbxproj.py. Wired as a step in the
workflow-guard-tests job so every PR is gated.

This catches anyone who bypasses the pre-commit hook with --no-verify
or who never ran scripts/setup.sh. The error message points at the
exact fix path.

To bump the pin (e.g., when the team adopts a newer Xcode major), edit
EXPECTED_OBJECT_VERSION in this script and the matching line in
CLAUDE.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Add .xcode-version and document Xcode 26 pin in CLAUDE.md

.xcode-version records the major (26.0) for tooling that reads it
(xcodes CLI, some CI helpers).

CLAUDE.md gains an Xcode toolchain section explaining the pin, the
normalizer + pre-commit hook + CI guard mechanics, and the procedure
for bumping the pin in the future.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Read .xcode-version as the source of truth in check-pbxproj.sh

scripts/check-pbxproj.sh now reads .xcode-version and maps the Xcode
major to the expected objectVersion via a one-entry case statement.
Bumping the team's Xcode pin becomes a one-file edit (.xcode-version),
with a script update only required when Apple actually changes
objectVersion in a new Xcode major.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Address CodeRabbit findings on check-pbxproj.sh and pre-commit hook

scripts/check-pbxproj.sh now passes "$PBXPROJ" explicitly to
normalize-pbxproj.py instead of letting it default to a path relative
to the current working directory, so the guard works regardless of
where CI invokes it.

scripts/git-hooks/pre-commit refuses to run when the working-tree
pbxproj has unstaged changes. Previously the hook would normalize
the working-tree file and `git add` the result, which silently staged
any unstaged hunks the user had deliberately left out of the commit.
The hook now exits non-zero with a clear message telling the user to
either stage the whole file or stash the unstaged hunks first.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Address Greptile findings: misleading comment + bump-step docs

scripts/normalize-pbxproj.py: the comment said "preserve empty lines
exactly where they are" but the implementation collapses blanks to a
trailing group. Reworded the comment to match the actual behavior.

CLAUDE.md: the bump procedure now mentions opening cmux.xcodeproj in
the new Xcode so objectVersion gets rewritten automatically. Without
that step a developer following the docs alone would update only the
pin file and the script case, and the CI guard would fail on their
next commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Add scripts/cleanup-dev-builds.sh for safely reclaiming tagged DerivedData (manaflow-ai#4837)

* Add scripts/cleanup-dev-builds.sh

Removes tagged dev-build artifacts produced by scripts/reload.sh:
DerivedData/cmux-<tag>/ (multi-GB each), /tmp/cmux-<tag>/, the per-tag
debug socket and logs, the reload log, and the App Support cmuxd dev
socket. Defaults to dry-run; pass --apply to delete.

Safety rules always on:
  - Skip the tag of any running `cmux DEV <tag>` app
  - Skip the tag pointed at by /tmp/cmux-last-cli-path
  - Skip any tag tied to a live git worktree
Filters: --older-than DAYS, --keep TAG (repeatable).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Drop "worktree exists" safety rule in cleanup-dev-builds.sh

Existence of a git worktree with the same name is a weak signal of
active use, and HQ tends to accumulate worktrees long after the
work is done. The rule made cleanup over-protective for the typical
case (worktree still around from a merged or abandoned PR).

The remaining safety rules (skip running app, skip the tag pointed
at by /tmp/cmux-last-cli-path) plus --keep TAG and --older-than DAYS
cover what we actually want without false positives.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Add /cleanup-builds slash command

Wraps scripts/cleanup-dev-builds.sh with the standard preview ->
confirm -> apply flow. Sits alongside the existing .claude/commands
(pull, sync-branch, release, etc.) and enforces user confirmation
before --apply.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Address review feedback on cleanup-dev-builds

CodeRabbit + Greptile findings, all real:

- Active-tag extraction from /tmp/cmux-last-cli-path now uses a regex
  match on /cmux-<tag>/ anywhere in the path, not just a strict
  $DERIVED_DATA_ROOT/cmux- prefix. Also avoids the unquoted parameter
  expansion that could be sensitive to glob metacharacters in
  DERIVED_DATA_ROOT.
- discover_tags switched from find | xargs basename to a shell glob
  loop. Cleaner, works on macOS regardless of xargs flavor, handles
  the empty case naturally.
- --older-than no longer skips tags whose DerivedData was already
  deleted (age == -1 sentinel). Orphan sockets/logs for those tags
  now get cleaned instead of being silently retained.
- "freed" label reworded as "freed (estimated)" because the byte
  count is measured during planning, not after rm.
- .claude/commands/cleanup-builds.md: blank lines around fenced
  blocks (MD031).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Fix agent resume when saved cwd is deleted (manaflow-ai#4859)

* test: cover agent resume with deleted cwd

* fix: resume agents when saved cwd is gone

* fix: harden restored cwd guard

* fix: skip ghostty cwd for guarded restore commands

* fix: drop duplicate cwd args on restore

* fix: preserve shell args during cwd cleanup

* fix: preserve custom resume cwd arguments

* fix: preserve shell syntax during cwd cleanup

* chore: remove unused shell word helper

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Restore file preview text saving CI coverage

* Restore browser session history CI coverage

* chore(rename): swap bundle id, product name, config dir to most

Visual rename pass — upstream-merge friendly. Touches user-visible
surfaces only; keeps Swift identifiers, package dirs, env vars,
and on-disk xcodeproj/test target names so cmux upstream merges
remain conflict-free.

- pbxproj: PRODUCT_BUNDLE_IDENTIFIER com.cmuxterm.* → com.4etverg.most*
- pbxproj: PRODUCT_NAME cmux → most ("cmux DEV" → "most DEV")
- Entitlements: app group ids release/nightly → com.4etverg.most[.nightly]
- CLI config path: ~/.config/cmux/cmux.json → ~/.config/most/most.json
- One-shot legacy config migration in KeyboardShortcutSettingsFileStore
- README.md (English) user-visible refs flipped; upstream URLs preserved

Co-Authored-By: OpenAI Codex (gpt-5.4-mini) <noreply@openai.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(configuration): rename to most.json + document migration

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Co-authored-by: Abdulaziz Albahar <67667005+azooz2003-bit@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
Co-authored-by: Konstantin <your@email.ar>
Co-authored-by: OpenAI Codex (gpt-5.4-mini) <noreply@openai.com>
Vangor added a commit to Vangor/most that referenced this pull request May 30, 2026
* test: cover external dot path open

* fix: open external path arguments without socket access

* fix: preserve explicit socket path opens

* fix: bound launchservices open helper

* fix: avoid blocking primitive in open helper

* fix: format localized path open errors

* fix: complete path open localization

* fix: scrub socket env for external path opens

* fix: localize path open success output

* Add cmux.xcworkspace with Packages visible alongside the project (manaflow-ai#4834)

* Add cmux.xcworkspace with Packages visible alongside the project

Top-level navigator shows cmux.xcodeproj plus every Packages/* SPM
package as siblings, so local packages are editable in the same window
without nesting under the project node. Additive only: existing
-project build flows (reload.sh, CI) keep working unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Ignore xcshareddata under xcworkspaces

Xcode auto-creates xcshareddata inside .xcworkspace bundles for
WorkspaceSettings.xcsettings, IDEWorkspaceChecks.plist, and SwiftPM
configuration. None of those carry intentional shared state for cmux
today (build is driven by reload.sh and CI, no private SPM registry,
no custom file headers). Ignoring them prevents Xcode bookkeeping
churn in commits. Project-level xcshareddata (shared .xcscheme files)
is unaffected because the pattern only matches inside .xcworkspace
directories.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* fix: scrub legacy panel env for path opens

* fix: limit path open locale entries

* Redesign notifications popover: bigger, minimal, swipe to dismiss (manaflow-ai#4778)

* Redesign notifications popover: bigger, minimal, swipe to dismiss

- Larger frame (min 460x480, ideal 560x620, max 760x760) so more
  notifications are visible at once.
- Compact rows with a thin accent bar for unread, tighter typography,
  hover-revealed clear button. No more boxed cards per row, just
  dividers between rows.
- Drag a row left or right to dismiss; passing the threshold slides it
  out and removes the notification. Tap to open as before.
- Empty/loading/populated states share the same outer frame so the
  popover stops resizing when notifications come and go.

* Show jump-to-latest keyboard shortcut next to button label

* Stabilize hover on notification rows: dedicated background tracking layer

.onHover and .onTapGesture on the same SwiftUI node share AppKit's
mouse-tracking pipeline and arbitrate against each other, producing
flaky enter/exit events right after the popover opens and when
crossing rows fast. Move hover detection to a transparent background
layer using .onContinuousHover, scope the opacity animation to the
background only so it does not re-animate dragOffset, and bump the
hover opacity from 0.07 to 0.11 so it is visible against
.windowBackgroundColor in light mode.

* Use NSTrackingArea for notification row hover

SwiftUI's .onHover and .onContinuousHover still arbitrate with the
parent .onTapGesture in macOS popover content, leaving rows where
hover never fires (notably on first popover open and when crossing
between LazyVStack rows). Replace the SwiftUI hover modifier with an
NSViewRepresentable that installs an NSTrackingArea
(.mouseEnteredAndExited + .activeAlways + .inVisibleRect). The
tracking NSView returns nil from hitTest so clicks pass through to
the SwiftUI parent's tap gesture, and the view syncs current
inside/outside state on tracking-area install for the case where the
pointer is already inside the row when the popover opens.

* Notifications popover: keep timestamp visible, drop hover animation, add right-click menu

- Always show row timestamp (removed the hide-on-hover opacity).
- Removed the hover background fade animation so it's an instant flip.
- Right-click a row for Open / Mark as Read / Mark as Unread / Dismiss.
- Added TerminalNotificationStore.markUnread(id:) sibling to markRead(id:).
- Added en/ja localizations for the new menu strings.

* Notifications popover: drop top margin, always semibold title

* Make notifications popover resizable, taller default

Default size bumped to 560x760 (was 560x620 ideal). User can drag a
bottom-right resize handle to set their own size; the chosen width
and height persist via @AppStorage. Bounds are 420x320 .. 1000x1200.

* Notifications popover: invisible corner resize, drop swipe-to-dismiss

NSPopover has no native resize chrome and there's no first-class
SwiftUI resize API for it. The closest native-feeling thing is a
hit-only corner region that drives a state-driven .frame: no visual
button, just a resizeLeftRight cursor on hover (closest standard
NSCursor to a diagonal resize).

Also removed swipe-to-dismiss. The hover X button and the right-click
Dismiss menu item are the dismiss surfaces; swiping rows in an
NSPopover row list isn't a macOS convention.

* Notifications popover: fix glitchy resize via AppKit screen coords

SwiftUI's DragGesture reports translation in a local coordinate space
that is literally being resized under the cursor as the popover grows.
Each frame the gesture re-derives translation from a moving anchor,
which produces dimension oscillation (the popover jumps between two
sizes).

Replace the SwiftUI gesture with an NSViewRepresentable that tracks
NSEvent.mouseLocation in global screen coordinates. Screen coordinates
are stable regardless of popover resize, so deltas are monotonic.
Also disable implicit animation on the .frame width/height so the
popover follows the cursor 1:1.

* Notifications popover: use diagonal resize cursor on bottom-right corner

* Notifications popover: address review feedback

- unreadCount uses notificationMenuSnapshot.unreadCount instead of
  reducing the live list, so the badge stays consistent with the
  hasUnreadNotifications / hasNotifications guards that drive the
  Jump to Latest / Clear All disabled states.
- Hide the visual keyboard-shortcut chip from accessibility; the
  button already exposes the shortcut via .accessibilityValue, so
  VoiceOver no longer reads it twice.
- Add a row-level accessibility action to dismiss a notification,
  since the visible clear button is hover-only. Adds the localized
  string notifications.row.clear (en/ja).

* markUnread(id:): clear manual workspace unread to avoid double-count

When a user marks a read notification unread, the notification itself
now provides the workspace unread indicator. If the workspace also had
a manual unread flag set, the popover header, dock badge, and
workspace badge would double-count that workspace because the count
builders sum notification unread counts and workspace indicators.

Mirrors what markLatestNotificationAsOldestUnread already does in the
same file. Found by Codex review.

* Restore Button wrapper on notification rows for keyboard access

Replacing the original Button with .onTapGesture removed rows from
the SwiftUI key-view loop, so keyboard-only users could tab to the
header controls but not open a row. The original reason for dropping
Button was that SwiftUI's .onHover arbitrated with the row's primary
action — but hover is now driven entirely by an AppKit NSTrackingArea
(HoverTrackingRepresentable), independent of the row's activation
node. The Button wrapper is therefore safe again and restores
space/return activation in the key-view loop. Found by Codex review.

* Notifications popover: persist resize once on mouseUp, clamp drag baseline

- Live resize uses @State (liveWidth/liveHeight); @AppStorage is
  written exactly once when the user releases the mouse, instead of
  on every mouseDragged event. Each @AppStorage write was hitting
  UserDefaults and broadcasting UserDefaults.didChangeNotification to
  every app-wide observer.
- Drag baseline now comes from the clamped (currently displayed)
  size, not raw saved doubles, so a drag that starts with out-of-bounds
  stored values doesn't lose initial pointer travel re-entering the
  visible range.

* Notifications popover: clear button as ZStack sibling, not nested in row Button

Nesting clearButton inside the row's Button(action: onOpen) label
created the classic SwiftUI nested-button hit-test problem on macOS:
clicks on the inner X could be consumed by the outer row Button's
tap area instead of clearing the notification. Move clearButton out
of rowContent and into a ZStack(alignment: .trailing) at the body
level so it's a sibling of the row Button with an independent hit
target. Found by Codex review.

* Notifications popover: clamp to screen, hide hover-only X from focus

- Clamp the popover's frame against NSScreen.main?.visibleFrame minus
  an 80pt margin so the bottom-right resize handle stays reachable
  even when the saved size was captured on a larger display.
- Mark the hover-only clear button .accessibilityHidden(!isHovering)
  so Full Keyboard Access / VoiceOver doesn't focus an invisible
  dismiss control. Dismissal is still exposed via the row's
  accessibility action and the right-click menu. Found by Codex.

* CI: retry — GitHub Actions infra was returning 403/404 on codeload

* CI: retrigger now that GitHub Actions infra is back

* Mark-as-Read context menu: clear focused pane indicator too

A user-initiated 'Mark as Read' should mirror the full dismissal
intent, so the pane's focused-read indicator for that surface should
clear alongside flipping the notification's isRead flag. Otherwise
the unread count drops but the pane badge remains visible until the
user later interacts with the terminal. Found by Codex review.

* Notifications popover: clamp against host window's screen, fix hover sync race

- Clamp popover size against the screen of NSApp.keyWindow (the
  popover's anchor window), not NSScreen.main. On multi-monitor
  setups the popover may appear on a different display than 'main'.
- Remove the DispatchQueue.main.async hop in HoverTrackingNSView's
  updateTrackingAreas; the queued onChange(true) could land after
  mouseExited's synchronous onChange(false), leaving rows stuck in
  the hovered state. updateTrackingAreas already runs on the main
  thread, so the call is fine synchronously. Found by Cursor Bugbot.

* Mark-as-Read: only clear focused pane indicator for pane-scoped notifications

clearFocusedReadIndicator treats surfaceId == nil as 'clear any pane
indicator for this tab', so passing nil for a workspace-level
notification would wipe an unrelated pane's badge. Gate the call on
notification.surfaceId being non-nil. Found by Codex review.

* Notifications popover: add a11y label/hint to resize handle

The localized notifications.resize key existed but was never wired
to the actual control. Adds .accessibilityLabel/.accessibilityHint
so VoiceOver and Full Keyboard Access users can discover the resize
affordance. Found by Cursor Bugbot.

* Notifications popover: move accessibility identifier back onto the row Button

XCUITests query rows with app.buttons["NotificationPopoverRow.<id>"].
The previous pass put .accessibilityIdentifier on the combined outer
ZStack, exposing the row as a container rather than a button, which
breaks both XCUITest lookups and the button accessibility role for
assistive tech. Move identifier + primary action + clear action back
onto the inner Button. Found by Codex review.

* Notifications popover: snapshot list before LazyVStack; clear restored unread on markUnread

- ForEach now iterates an immutable snapshot captured outside the
  LazyVStack instead of reading notificationStore.notifications
  inside the row builder; the previous code reintroduced a store
  dependency below the list boundary, which CLAUDE.md flags as the
  same anti-pattern that caused the LazyLayoutViewCache spin-loop
  in the sessions panel (manaflow-ai#2586).
- markUnread(id:) now also clears the restored workspace unread
  indicator for the tab, so a session-restored unread hint plus a
  user-toggled unread notification no longer double-count in the
  header/dock/workspace badges. Found by Codex review.

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Remove History from right sidebar (manaflow-ai#4785)

* Remove History mode from right sidebar

Drops the History tab from the right sidebar mode picker along with its
keyboard shortcut, command palette entries, and the 'Open Full History'
menu button. Recently Closed/Focused submenus and Reopen Last Closed
still work from the menu bar.

* Drop tests for deleted HistoryDayGrouping helper

* Tolerate obsolete history mode and restore focus-history menu key

- SessionRightSidebarToolPanelSnapshot now decodes an unknown mode (e.g.
  legacy 'history') as nil instead of failing the entire snapshot, so
  upgraded users can still restore prior sessions.
- Restore the 'menu.history.showFullFocusHistory' key still referenced
  by AppDelegate+FocusHistoryContextMenu for non-sidebar focus history.

* Remove duplicate @mainactor attribute left by prior edit

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Launch restored agent sessions via startup commands (manaflow-ai#4777)

* Test agent restore startup command launch

* Launch restored agents with startup commands

* Tighten restored startup launch state

* Preserve remote startup for agent restore

* Relax remote startup restore assertion

* Fix startup restore lifecycle

* Remove unused restore launch flag

* Run startup resumes in login shell

* Tighten remote restore assertion

* Handle csh agent restore launchers

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Wrap workspace titles in sidebar (manaflow-ai#4848)

* Wrap workspace titles in sidebar

* Add workspace title wrap toggle

* Localize workspace title wrap setting

* Align wrapped workspace title accessories

* Reset workspace title wrap preference

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* test: cover bare relative external path open

* fix: open bare relative path arguments externally

* fix: preserve refresh surfaces command precedence

* fix: keep bare path command matching case-sensitive

* Add secure cmux navigation links (manaflow-ai#4857)

* Add secure cmux navigation links

* Localize cmux navigation link labels

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Fix matched sidebar terminal background (manaflow-ai#4780)

* test: cover matched sidebar background tint

* fix: match sidebar terminal background

* fix: remove dead sidebar overlay path

* test: cover matched sidebar boundary separators

* fix: keep matched sidebar separators visible

* test: cover matched sidebar chrome borders

* test: sample sidebar boundary resizers

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Fix JSONC comment skipper for CRLF line endings (manaflow-ai#4869)

* Fix JSONC // comment skipper for CRLF line endings

Swift treats \r\n as a single extended grapheme cluster, so the existing
source[index] != "\n" check inside the three // line-comment skippers
never matches a CRLF line ending. On a file with CRLF throughout, the
loop runs past the rest of the file looking for a standalone LF and
strips everything after the first //. preprocess() then produces
truncated JSON and loadCmuxSettingsRoot() / jsonObject() throw
"Unexpected end of file".

This was breaking the tmux corpus terminal-nightly job's
testSurfaceResumeApprovalWritesRecordsIntoCmuxJSON, which explicitly
exercises the CRLF path. Match any character whose first scalar is CR
or LF so we stop at CR, LF, or CRLF correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Allow workflow_dispatch to run tmux corpus terminal-nightly

So this workflow's macOS test job is reproducible on demand for branches
under review, not only on the nightly schedule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix JSONC editor indent detection for CRLF line endings

indentationBeforeLine walked back to find the start of the line
containing a given index by stopping at "\n" or "\r" characters, but on
CRLF input Swift treats "\r\n" as a single extended grapheme cluster
that equals neither. The loop then walked past every line break to the
start of the file and returned an empty indent, so newly inserted
properties (e.g. resumeCommands) ended up at the wrong indentation
level. Reuse the isLineTerminator helper that already handles CRLF
correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Open forked conversations without palette action probe (manaflow-ai#4852)

* Open forked conversations without palette action probe

* Fix fork probe snapshot selection

* Refresh fork probe cache per palette session

* Preserve verified fork probe visibility

* Preserve fork snapshot cache during live refresh

* Allow verified fallback fork execution

* Reprobe fork cache after fallback clears

* Preserve focus after instant fork commands

* Clear fork cache on execution failure

* Avoid stale fork cache on palette reopen

* Clear stale fallback fork cache before reprobe

* Preserve verified fork cache on palette reopen

* Require verified fork snapshot before fallback execution

* Track actual fork fallback usage

* Preserve verified fork cache fallback flag

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* test: cover bonsplit tab indicator drift

* fix: update bonsplit tab indicator handling

* Skip Cmd+Shift key forwarding test when Ghostty surface init fails (manaflow-ai#4871)

The tmux corpus terminal-nightly runner cannot initialize a
Metal-backed Ghostty surface; embedded_window logs
"error initializing surface err=error.OutOfMemory" for every surface
the suite creates, and ghostty_surface_new returns nil. The test then
fails XCTAssertTrue on performKeyEquivalentAfterMenuMiss because
ensureSurfaceReadyForInput cannot return a live surface, which looks
like a key-forwarding regression rather than the environment issue
that it is.

Skip the test (via XCTSkipUnless on TerminalSurface.hasLiveSurface)
when the surface fails to initialize, so CI surfaces the real problem
(Metal unavailable on this runner) without masking it as a key-bind
regression. The test still runs end-to-end on developer machines and
on runners with a logged-in GUI session.

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Add algorithmic complexity review rule (manaflow-ai#4866)

* Add algorithmic complexity review rule

* Align algorithmic complexity review hint

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Narrow AppDelegate CI quarantine (manaflow-ai#4874)

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Restore browser devtools config CI coverage

* Add prompt and rules deeplinks (manaflow-ai#4839)

* Add prompt and rules deeplinks

* Preserve encoded punctuation in text deeplinks

* Localize text deeplink dialogs

* Support Freestyle SSH link users

* Harden prompt and rules deeplinks

* Suppress welcome for text deeplinks

* Restore SSH deeplink trust gate

* Preserve plus signs in text deeplinks

* Respect no-focus for text deeplink target selection

* Use deterministic text deeplink target

* Start background text deeplink targets

* Report text deeplink send failures

* Handle mixed deeplink batches safely

* Gate mixed external deeplink batches

* Report failed external text deeplink sends

* Stop after handling external cmux links

* Allow multiline cmux text links

* Reject control characters in cmux text links

* Polish external cmux link handling

* Label sendTextWhenReady callbacks

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Make session index backfill linear (manaflow-ai#4868)

* Make session index backfill linear

* Stabilize session index backfill tie breaks

* Fix session index tie-break test setup

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Optimize batch workspace sidebar actions (manaflow-ai#4865)

* Optimize batch workspace sidebar actions

* Preserve workspace selection after drag reorder

* Harden workspace batch action coverage

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* test: harden bonsplit scroll indicator check

* Restore browser remote store CI coverage

* Make browser search providers configurable (manaflow-ai#4849)

* feat: make browser search providers configurable

* fix: keep parsing browser settings after invalid custom search config

* test: harden custom search config regression

* fix: handle custom search edge cases

* fix: validate custom search fallbacks

* fix: localize search engine labels

* fix: gate stale search suggestions

* fix: allow blank custom search names

* fix: add exa search provider

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Restore browser lifecycle CI coverage

* docs: add cmux ssh deep links (manaflow-ai#4833)

* docs: add cmux ssh deep links

* docs: add cmux deeplink fallback pages

* docs: add deeplink locale fallbacks

* docs: tighten deeplink fallback validation

* docs: mirror native ssh deeplink validation

* docs: localize deeplink messages

* docs: canonicalize rules deeplink alternates

* Allow Freestyle SSH deeplink users

* docs: validate text deeplink fallback params

* docs: ignore blank optional deeplink params

* docs: address deeplink review comments

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Restore file preview review CI coverage

* Pin Xcode 26 (objectVersion 60) and add pbxproj normalizer + CI guard (manaflow-ai#4836)

* Add deterministic normalizer for cmux.xcodeproj/project.pbxproj

scripts/normalize-pbxproj.py sorts the high-churn sections (PBXBuildFile,
PBXFileReference, and the files = (...) arrays inside Sources / Resources
/ Frameworks / CopyFiles build phases) into a deterministic order keyed
on the entry comment plus UUID. The Xcode build does not care about the
order of these flat dictionary sections; sorting them just kills the
nondeterministic diff noise Xcode generates on every UI touch.

Does not touch UUIDs, comments, or PBXGroup children = (...) arrays
(navigator order is intentional). Idempotent: a second run produces zero
diff.

Standalone in this commit so the diff is just the script. The next
commit applies the script and bumps objectVersion in one shot, so
the resulting churn is contained and never repeated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Pin objectVersion = 60 and normalize pbxproj

Bumps objectVersion from 56 to 60 (the format Xcode 16+ and Xcode 26
write by default) and runs scripts/normalize-pbxproj.py once to
establish the deterministic baseline. After this commit, future
diffs to project.pbxproj show only real changes, not Xcode's
nondeterministic section reordering.

One-time large diff. No semantic changes to targets, sources, build
phases, or settings: pure sort + version pin.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Add tracked pre-commit hook that normalizes pbxproj

scripts/git-hooks/pre-commit calls scripts/normalize-pbxproj.py on
cmux.xcodeproj/project.pbxproj when it is staged and re-stages the
result. scripts/install-git-hooks.sh points the clone at this directory
via `git config core.hooksPath scripts/git-hooks`, and scripts/setup.sh
auto-runs it so devs get the hook without a separate manual step.

After this, Xcode's nondeterministic reordering of build-file and
file-reference sections is canceled out at commit time. The CI guard
in the next commit enforces the rule for anyone who bypasses the hook
with --no-verify or who never ran setup.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Add CI guard for objectVersion pin and pbxproj normalization

scripts/check-pbxproj.sh asserts cmux.xcodeproj/project.pbxproj has
objectVersion = 60 (Xcode 26 default) and that the file is normalized
per scripts/normalize-pbxproj.py. Wired as a step in the
workflow-guard-tests job so every PR is gated.

This catches anyone who bypasses the pre-commit hook with --no-verify
or who never ran scripts/setup.sh. The error message points at the
exact fix path.

To bump the pin (e.g., when the team adopts a newer Xcode major), edit
EXPECTED_OBJECT_VERSION in this script and the matching line in
CLAUDE.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Add .xcode-version and document Xcode 26 pin in CLAUDE.md

.xcode-version records the major (26.0) for tooling that reads it
(xcodes CLI, some CI helpers).

CLAUDE.md gains an Xcode toolchain section explaining the pin, the
normalizer + pre-commit hook + CI guard mechanics, and the procedure
for bumping the pin in the future.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Read .xcode-version as the source of truth in check-pbxproj.sh

scripts/check-pbxproj.sh now reads .xcode-version and maps the Xcode
major to the expected objectVersion via a one-entry case statement.
Bumping the team's Xcode pin becomes a one-file edit (.xcode-version),
with a script update only required when Apple actually changes
objectVersion in a new Xcode major.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Address CodeRabbit findings on check-pbxproj.sh and pre-commit hook

scripts/check-pbxproj.sh now passes "$PBXPROJ" explicitly to
normalize-pbxproj.py instead of letting it default to a path relative
to the current working directory, so the guard works regardless of
where CI invokes it.

scripts/git-hooks/pre-commit refuses to run when the working-tree
pbxproj has unstaged changes. Previously the hook would normalize
the working-tree file and `git add` the result, which silently staged
any unstaged hunks the user had deliberately left out of the commit.
The hook now exits non-zero with a clear message telling the user to
either stage the whole file or stash the unstaged hunks first.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Address Greptile findings: misleading comment + bump-step docs

scripts/normalize-pbxproj.py: the comment said "preserve empty lines
exactly where they are" but the implementation collapses blanks to a
trailing group. Reworded the comment to match the actual behavior.

CLAUDE.md: the bump procedure now mentions opening cmux.xcodeproj in
the new Xcode so objectVersion gets rewritten automatically. Without
that step a developer following the docs alone would update only the
pin file and the script case, and the CI guard would fail on their
next commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Add scripts/cleanup-dev-builds.sh for safely reclaiming tagged DerivedData (manaflow-ai#4837)

* Add scripts/cleanup-dev-builds.sh

Removes tagged dev-build artifacts produced by scripts/reload.sh:
DerivedData/cmux-<tag>/ (multi-GB each), /tmp/cmux-<tag>/, the per-tag
debug socket and logs, the reload log, and the App Support cmuxd dev
socket. Defaults to dry-run; pass --apply to delete.

Safety rules always on:
  - Skip the tag of any running `cmux DEV <tag>` app
  - Skip the tag pointed at by /tmp/cmux-last-cli-path
  - Skip any tag tied to a live git worktree
Filters: --older-than DAYS, --keep TAG (repeatable).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Drop "worktree exists" safety rule in cleanup-dev-builds.sh

Existence of a git worktree with the same name is a weak signal of
active use, and HQ tends to accumulate worktrees long after the
work is done. The rule made cleanup over-protective for the typical
case (worktree still around from a merged or abandoned PR).

The remaining safety rules (skip running app, skip the tag pointed
at by /tmp/cmux-last-cli-path) plus --keep TAG and --older-than DAYS
cover what we actually want without false positives.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Add /cleanup-builds slash command

Wraps scripts/cleanup-dev-builds.sh with the standard preview ->
confirm -> apply flow. Sits alongside the existing .claude/commands
(pull, sync-branch, release, etc.) and enforces user confirmation
before --apply.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Address review feedback on cleanup-dev-builds

CodeRabbit + Greptile findings, all real:

- Active-tag extraction from /tmp/cmux-last-cli-path now uses a regex
  match on /cmux-<tag>/ anywhere in the path, not just a strict
  $DERIVED_DATA_ROOT/cmux- prefix. Also avoids the unquoted parameter
  expansion that could be sensitive to glob metacharacters in
  DERIVED_DATA_ROOT.
- discover_tags switched from find | xargs basename to a shell glob
  loop. Cleaner, works on macOS regardless of xargs flavor, handles
  the empty case naturally.
- --older-than no longer skips tags whose DerivedData was already
  deleted (age == -1 sentinel). Orphan sockets/logs for those tags
  now get cleaned instead of being silently retained.
- "freed" label reworded as "freed (estimated)" because the byte
  count is measured during planning, not after rm.
- .claude/commands/cleanup-builds.md: blank lines around fenced
  blocks (MD031).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Fix agent resume when saved cwd is deleted (manaflow-ai#4859)

* test: cover agent resume with deleted cwd

* fix: resume agents when saved cwd is gone

* fix: harden restored cwd guard

* fix: skip ghostty cwd for guarded restore commands

* fix: drop duplicate cwd args on restore

* fix: preserve shell args during cwd cleanup

* fix: preserve custom resume cwd arguments

* fix: preserve shell syntax during cwd cleanup

* chore: remove unused shell word helper

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Restore file preview text saving CI coverage

* Restore browser session history CI coverage

* chore(rename): swap bundle id, product name, config dir to most

Visual rename pass — upstream-merge friendly. Touches user-visible
surfaces only; keeps Swift identifiers, package dirs, env vars,
and on-disk xcodeproj/test target names so cmux upstream merges
remain conflict-free.

- pbxproj: PRODUCT_BUNDLE_IDENTIFIER com.cmuxterm.* → com.4etverg.most*
- pbxproj: PRODUCT_NAME cmux → most ("cmux DEV" → "most DEV")
- Entitlements: app group ids release/nightly → com.4etverg.most[.nightly]
- CLI config path: ~/.config/cmux/cmux.json → ~/.config/most/most.json
- One-shot legacy config migration in KeyboardShortcutSettingsFileStore
- README.md (English) user-visible refs flipped; upstream URLs preserved

Co-Authored-By: OpenAI Codex (gpt-5.4-mini) <noreply@openai.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(configuration): rename to most.json + document migration

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Co-authored-by: Abdulaziz Albahar <67667005+azooz2003-bit@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
Co-authored-by: Konstantin <your@email.ar>
Co-authored-by: OpenAI Codex (gpt-5.4-mini) <noreply@openai.com>

This branch was successfully deployed

1 active deployment
Preview – cmux — cfcc8270 Deployed May 27, 2026 by vercel[bot]
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