Skip to content

Merge upstream/main (2026-05-28) - #24

Merged
philip-zhan merged 84 commits into
mainfrom
merge-upstream-2026-05-28
May 28, 2026
Merged

philip-zhan merged 84 commits into
mainfrom
merge-upstream-2026-05-28

Conversation

@philip-zhan

Copy link
Copy Markdown
Owner

Summary

Merges 83 upstream commits (manaflow-ai/cmux) into the fork. Debug build verified to compile (BUILD SUCCEEDED).

Conflict resolutions

  • History sidebar tab — followed upstream's removal (Remove History from right sidebar manaflow-ai/cmux#4785): stripped sidebar-History from the shortcut/focus/command-palette/tool-panel code, schema, and pbxproj; the merge deleted HistoryPanelView.swift and AppDelegate+HistoryPane.swift. Preserved the fork's Source Control tab and the separate menu-bar focus/closed-item history.
  • FilePreviewPanel — adopted upstream's content-sniffing for .ts files (TypeScript vs MPEG transport stream) over the fork's knownTextFile path.
  • project.pbxproj — union of upstream entries plus genuine fork-only entries (CodeViewer / SourceControl / CommandPalette file-search families). Upstream re-sorted whole sections; verified no dangling references and no lost source files.
  • vendor/bonsplit → 082bf66 — upstream pointer, which adds the forkConversation tab action that upstream's Workspace.swift now requires. ghostty unchanged at 176bd550f.

Notes

  • Several merge drops were silent (no conflict markers) where upstream deleted regions the fork built on — caught by the compile check.
  • Possible orphaned History localization keys remain in Resources/Localizable.xcstrings (unused, harmless); the file was left as auto-merged.

Test plan

  • CI green
  • Smoke-test right sidebar (Files/Find/Source Control/Vault/Feed/Dock tabs + shortcuts)
  • Verify Fork Conversation tab context action works
  • Verify .ts files open as text, not media

🤖 Generated with Claude Code

lawrencecchen and others added 30 commits May 26, 2026 03:25
…aflow-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>
…naflow-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 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>
* 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

* 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>
…nal-path-open

Open external path arguments without socket access
* Add secure cmux navigation links

* Localize cmux navigation link labels

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* 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

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

* 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>
…anaflow-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

* Align algorithmic complexity review hint

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
lawrencecchen and others added 28 commits May 27, 2026 14:54
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Add David Ondrej testimonial

* Move David Ondrej testimonial after DeepMind

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* test: cover broken process pipe reads

* fix: read process pipes without NSFileHandle exceptions

* fix: preserve partial process pipe output

* fix: log process pipe read failures

* fix: keep available pipe reads nonblocking

* fix: distinguish empty available pipe reads

* Avoid toggling pipe read descriptor flags
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Migrate macOS CI/CD runners from WarpBuild/Depot to Blacksmith

Switch all macOS jobs to blacksmith-6vcpu-macos-{15,26,latest}:
- ci.yml (tests, tests-build-and-lag, release-build, ui-regressions)
- build-ghosttykit, nightly, release, test-depot, perf-activation,
  tmux-corpus, ci-macos-compat
- test-e2e default → blacksmith-6vcpu-macos-latest; existing
  workflow_dispatch input keeps depot-macos-* as fallback options

Update tests/test_ci_self_hosted_guard.sh to assert Blacksmith runners
on the paid jobs from issue manaflow-ai#385.

* Diagnose GUI session on Blacksmith for Cmd-Tab perf bench

Cmd-Tab activation bench times out waiting for a visible CGWindow on Blacksmith macOS runners. perf-activation-session.py works because it talks to the debug socket, so the app process launches fine, but the Cmd-Tab bench uses CGWindowListCopyWindowInfo([.optionOnScreenOnly,...]) which only returns windows the WindowServer is rendering. Print enough runner state to decide whether to launchctl asuser the bench step or move it back to a Warp/Depot GUI-enabled runner.

* Run Cmd-Tab perf bench inside the console Aqua session

On Blacksmith macOS runners (and likely any future GitHub-image-based hosted runner), the runner job runs in launchd's system bootstrap even though it is uid 501. Apps launched via NSWorkspace.openApplication still attach to the WindowServer, but CGWindowListCopyWindowInfo([.optionOnScreenOnly,...]) from a system-bootstrap process does not see those windows in the console user's Aqua session, so the Cmd-Tab benchmark times out waiting for an initial visible window. Re-enter the console user's launchd domain with launchctl asuser before invoking the swift bench so visibility polling sees the real on-screen windows. perf-activation-session.py was unaffected because it talks to the debug socket and does not need WindowServer visibility.

* Harden Blacksmith CI runner config

* Harden macOS runner guard diagnostics

---------

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

* Localize Peter Steinberger subtitle

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Add Fork Conversation to tab right-click menu

Adds a `Fork Conversation` item to the bonsplit tab context menu. It appears
only when the tab is running an agent session that supports forking without
a probe (Claude, Codex). On click, opens a new sibling tab to the right of
the source with `claude --resume <id> --fork-session` (or the analogous
Codex form) as the startup input, so divergence is owned by the agent's
native fork support instead of any cmux-side history copy.

Companion to manaflow-ai/bonsplit#135 which adds the menu item plumbing.

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

* Lock down Fork Conversation isolation + handler guard

Addresses PR manaflow-ai#4888 review feedback:

- Annotate `liveAgentIndexCache` / `liveAgentIndexCacheLoadedAt` / their
  accessor with `@MainActor` so the implicit MainActor isolation from the
  enclosing `Workspace` class is explicit at the static-storage level. The
  provider closure already runs during SwiftUI body evaluation on the main
  actor, but the explicit annotation prevents a Swift 6 strict-concurrency
  violation when that mode is enabled (CodeRabbit critical / Greptile P1).

- Tighten the bonsplit `.forkConversation` handler to mirror the menu
  visibility gate exactly (`== .supportedWithoutProbe`) instead of the
  weaker `!= .unsupported`. Closes the gap where the action could let a
  `.requiresProbe` snapshot through if ever wired up outside the controlled
  menu (Greptile summary note).

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

* Add unit tests for Fork Conversation tab action

Covers the new Workspace API and the bonsplit context-menu wiring without
needing to drive the NSMenu UI (which XCUI can't reach reliably):

- forkAgentConversationToNewTab creates a sibling tab in the same pane,
  not a split pane, with the snapshot's `--fork-session` command as its
  initial input.
- The forked tab lands immediately to the right of its source even when
  there are other tabs further right.
- canForkAgentConversationFromPanel flips on once a restored Claude
  snapshot is associated with the panel.
- The bonsplit `.forkConversation` action dispatches end-to-end through
  splitTabBar(_:didRequestTabContextAction:for:inPane:) to the same
  forkAgentConversationToNewTab path, locking in the menu wiring.

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

* Fix Fork Conversation tests: use public TabID API

CI test target failed to compile because the tests reached into
`TabID.id` (which is `internal` in bonsplit), accessible only inside the
bonsplit module via `@testable`. Local `./scripts/reload.sh --tag` builds
the main app but not the test target, which is why the issue slipped
through locally.

Compare TabIDs directly (TabID is Hashable) instead of unwrapping the
underlying UUID.

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

* Background-refresh live agent index; add Codex parity test

Addresses two PR manaflow-ai#4888 review findings:

- Greptile P1: `RestorableAgentSessionIndex.load()` performs blocking
  `sysctl(KERN_PROCARGS2)` per hook record (for live-PID filtering),
  which is N×2 syscalls on `@MainActor` per right-click within the TTL
  window. Replace the synchronous main-actor load with an off-main
  `Task.detached(priority: .utility)` refresh, keyed on a 1s freshness
  window. The provider reads the cached value synchronously and tolerates
  a slightly-stale snapshot — claude's `--resume`/`--fork-session` still
  works against the persisted transcript even if the cmux-recorded PID
  has died, so showing the menu item briefly for a recently-terminated
  session is benign. First right-click after a fresh Claude launch may
  miss the item until the background refresh completes (~hundreds of
  ms); subsequent SwiftUI body re-renders pick up the new snapshot.

- CodeRabbit: add a Codex-parity test that drives the same context-menu
  dispatcher path with a Codex snapshot and asserts the sibling tab is
  created with the Codex `--fork-session` command. (The Claude-path test
  already asserted focus on the new tab.)

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

* Trigger immediate UI refresh after live-index load

Addresses PR manaflow-ai#4888 CodeRabbit follow-up: the previous static cache
returned nil on the first call after a stale-out and relied on a later
SwiftUI body refresh (hover/focus) to pick up the new index, which is
why the first right-click could miss the menu item.

Move the cache off the static slot and onto the Workspace instance as
a `@Published` property. When the background `Task.detached` completes
and writes the new index, `objectWillChange` fires on the workspace,
ContentView re-renders, and bonsplit's TabBarView re-evaluates its
context-menu state on the same frame — so "Fork Conversation" becomes
visible the moment the index lands, without a second right-click.

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

* Beep when Fork Conversation tab creation fails silently

Greptile P1 (PR manaflow-ai#4888): the bonsplit `.forkConversation` handler
discarded `forkAgentConversationToNewTab`'s return value with `_ = …`,
so if `newTerminalSurface` ever returned nil (e.g. layout reject,
workspace resource constraint) the click produced no visible result and
no audio cue, even though every other early-exit in this action branch
already calls `NSSound.beep()`. Match the existing convention.

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

* Use shared singleton for live agent index across workspaces

PR manaflow-ai#4888 CodeRabbit follow-up: the previous per-Workspace `@Published`
cache meant each open workspace performed its own background load of
`RestorableAgentSessionIndex.load()`, redundantly running the same JSON
+ per-PID work N times for N open workspaces. Replace with a single
`SharedLiveAgentIndex.shared` actor-isolated singleton that loads once
and serves every workspace; each Workspace subscribes to its
`objectWillChange` in its initializer and forwards it as its own so the
SwiftUI re-render path stays intact (ContentView re-renders → bonsplit's
TabBarView re-evaluates Fork Conversation visibility on the same frame).

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

---------

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: Lawrence Chen <lawrencecchen@users.noreply.github.com>
…naflow-ai#4919)

Adds four CLAUDE.md sections capturing engineering policies that are
currently informal: Package architecture (ergonomic, no cycles, broad
domain responsibilities, leaf-first extraction), File organization (one
major type per file), Documentation (Swift-DocC triple-slash on every
public symbol in Packages/), and Test framework (Swift Testing default
for new tests, XCUITest for UI tests only).

Adds .cursor/rules/main.mdc so Cursor CLI picks up the same CLAUDE.md
ruleset that Claude Code reads directly and Codex CLI reads through the
existing AGENTS.md -> CLAUDE.md symlink.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add cmd-click Markdown path regression coverage

* Open Markdown cmd-clicks in Markdown viewer by default

* Update Markdown routing subtitle translations

* Handle absolute Markdown path links with punctuation

* Fix command-click fallback path normalization

* Align Markdown routing settings subtitle

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
…manaflow-ai#4926)

This reverts commit 370c412.

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Improve macOS CI runner fallbacks

* Harden release-build fallback on hosted macOS

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Add TypeScript preview regression test

* Resolve textual ts files as source previews

* Fix ambiguous ts media sniffing

* Stabilize ts preview sniffing across UTType environments

* Tighten ts text preview sniffing

* Allow UTF-8 BOM in text preview sniff

* Preserve ANSI text preview sniffing

* Cover nul byte ts preview sniffing

* Handle m2ts preview sniffing

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Add browser keyboard hit-test regression

* Skip browser hit testing during key dispatch

* Share window input routing for hit tests

* Keep scroll out of tab pass-through routing

* Preserve no-event workspace drop hit testing

* Preserve appKit browser portal routing

* Drop unused input routing helpers

* Move window input routing context

* Remove unused routing context wrappers

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Add failing open-with menu tests

* Fix file preview open-with menu

* Address open-with review feedback

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Add cmux diff CodeView command

* Match Diffs CodeView styling

* Respect Ghostty themes in cmux diff

* Lower cmux diff default font size

* Stabilize cmux diff sticky headers

* Add git sources to cmux diff

* Polish cmux diff viewer controls

* Use Pierre file tree in diff viewer

* Address diff viewer closeout feedback

* Fix diff viewer warning budget

* Fix diff viewer last-turn closeout issues

* Address remaining diff viewer review feedback

* Bundle diff viewer assets

* Clean diff source error messages

* Fix diff viewer closeout gaps

* Honor diff viewer Ghostty font size

* Localize bundled diff viewer labels

* Gate nested diff baselines

* Disable color in git diff viewer sources

* Fix diff target selection and branch base

* Fix no-focus diff selection and asset races

* Fix diff viewer theme lookup after main merge

* Polish diff viewer source closeout

* feat: add diff viewer repo navigation

* fix: split browser panel view body

* fix: localize diff viewer command title

* fix: detach diff viewer palette launch

* fix: avoid new browser onChange warning

* fix: keep last-turn untracked deletions diffable

* fix: serve diff viewer from trusted app scheme

* fix: keep diff viewer scheme inside browser

* fix: sort diff viewer file tree input

* fix: close diff viewer review gaps

* fix: harden diff viewer closeout cases

* fix: compile raw git blob helper

* fix: keep diff viewer repo targets source-specific

* fix: open diff viewer when selected source is empty

* fix: avoid probing non-repo diff siblings

* fix: close diff viewer review findings

* test: avoid waiting for absent diff open

* fix: share diff viewer trust root

* fix: preserve empty last-turn source

* fix: allow diff viewer wasm highlighter

* perf: bound diff viewer deferred targets

* test: make diff viewer payload extraction robust

* fix: keep last-turn untracked snapshots out of git

* fix: retain command palette diff launcher

* fix: harden last-turn untracked snapshots

* fix: align diff viewer codeview lifecycle

* fix: match diffshub diff viewer scrolling

* fix: stream diff viewer patches

* fix: align diff viewer chrome styling

* fix: repair diff viewer scheme test

* fix: split diff viewer assets and stabilize large diffs

* fix: tighten diff viewer source switching

* test: assert diffhub patch sidecar content

* fix: improve diff viewer large patch rendering

* fix: address diff viewer review cleanup

* Speed up diff viewer highlighting

* Stabilize diff viewer sticky headers

* Fix diff viewer sidecar test assertions

* Tame WebKit sticky diff headers

* Keep diff viewer scheme in app when browser is disabled

* Clarify diff command help flags

* Secure diff viewer temp directory

* Sanitize diff viewer error copy

* Harden diff viewer collapse and snapshots

* Preserve last-turn baselines on duplicate hooks

* Keep duplicate prompt hooks idempotent

* Serialize diff viewer scheme task cancellation

* Avoid type erasure in browser panel body

* Serve diff viewer from loopback origin

* Keep diff viewer loopback URLs temporary

* Align diff viewer streaming with Diffshub

* Use event-driven diff viewer pending loads

* Bound diff viewer pending waits

* Fix streamed diff tree selection

* Retry deferred diff waits until ready

* Stream trusted remote diff viewer patches

* Test remote diff streaming manifest

* Route cmux diff before path fallback

* Stabilize diff viewer sticky headers

* Improve diff viewer streaming performance

* Keep diff viewer URLs out of browser history

* Fix diff viewer closeout review issues

* Fix quoted last-turn untracked diff paths

* Fix diff viewer restore and socket env leaks

* Prefetch trusted PR diffs for viewer performance

* Treat loopback alias diff URLs as temporary

* Bypass remote proxy for diff viewer panes

* test: cover unborn last-turn baselines

* fix: baseline unborn repos against empty tree

* fix: align diff viewer model with DiffHub

* fix: wait for deferred diff pages without polling

* fix: retry deferred diff wait on transient failure

* fix: avoid rebuilding diff tree entries

* fix: skip unchanged tree status refreshes

* fix: refresh changed diff tree stats

* fix: mount diff viewer with first batch

* Match DiffHub CodeView item stream

* Fix diff viewer review regressions

* Bound deferred diff viewer waits

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Expose FeedSidebar launch activation failures

* Run E2E xcodebuild in GUI bootstrap

---------

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

Guard E2E Depot runner identity
* Unmask right sidebar chrome UI tests

* Fix right sidebar chrome hit-target assertions

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Merges 83 upstream commits. Notable conflict resolutions:

- Follow upstream's removal of the right-sidebar History tab (manaflow-ai#4785):
  dropped sidebar History across the shortcut/focus/command-palette/tool-panel
  code, schema, and pbxproj, and deleted HistoryPanelView.swift and
  AppDelegate+HistoryPane.swift. Preserved the fork's Source Control tab and
  the separate menu-bar focus/closed-item history.
- FilePreviewPanel: adopt upstream's content-sniffing approach for .ts files
  (TypeScript vs MPEG transport stream) over the fork's knownTextFile path.
- project.pbxproj: union of upstream entries plus genuine fork-only entries
  (CodeViewer/SourceControl/CommandPalette file search families).
- vendor/bonsplit -> 082bf66 (adds the Fork Conversation tab action that
  upstream's Workspace.swift now requires).

Debug build verified to compile.
@philip-zhan
philip-zhan merged commit f0d4ef5 into main May 28, 2026
2 of 9 checks passed
@philip-zhan
philip-zhan deleted the merge-upstream-2026-05-28 branch May 28, 2026 22:42
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.

4 participants