Skip to content

Custom sidebar: instant toggle remount and live resize repaint - #5864

Merged
azooz2003-bit merged 7 commits into
mainfrom
feat-sidebar-snappy
Jun 11, 2026
Merged

azooz2003-bit merged 7 commits into
mainfrom
feat-sidebar-snappy

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Jun 11, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes two latency issues in the out-of-process custom sidebar, found while dogfooding the interpreter work.

Toggle blank (~1s). RemoteCustomSidebarHost shut the render worker down on every unmount and respawned it on remount, paying process spawn + first interpret + render before anything appeared. The client is now owned by the window: @State on ContentView (per-window root), threaded down to the host as a Binding. It is still created lazily on the first custom-sidebar mount (never eagerly per init, per the #5372/#5382 re-land lesson), survives sidebar toggles and provider switches, and a remount adopts the cached remote context synchronously, so the sidebar reappears instantly. No shared/global state.

Resize lag (1-2s). The worker's geometry handler resized the offscreen window and hosting view but never republished rootView. With no display link in the never-ordered window, a frame change alone never re-rendered the SwiftUI content, so resizes showed stretched stale pixels until the next 1s scene tick repainted. apply(geometry:) now republishes the cached render (no re-interpretation) and pumps, following the coordinator's existing "mutate rootView, force layout, commit" rendering rule.

Worker lifecycle: the surface's window-close reaper shuts the worker down when the sidebar is mounted at close; if the window deallocates while the sidebar is hidden, the worker exits on pipe EOF (RunSidebarRenderWorker EOF path).

Dogfooding on tag snappy; not for merge until dogfood approval.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor

    • Sidebar render lifecycle is now owned by the window/root view, avoiding shutdown when the sidebar hides.
    • Sidebar reliably re-renders on window resizes using a cached display state to prevent visual glitches without re-running interpretation.
    • Rendering worker is retained across sidebar show/hide for smoother toggles and improved performance.
    • Workers can now be associated with a content source key to improve cache/substitution behavior.
  • Tests

    • Strengthened worker supervision test and made the suite run serialized to more deterministically validate recovery.

Park the warm RenderWorkerClient across unmounts (pool of one) instead
of shutting the worker down, so reopening the sidebar adopts the cached
remote context synchronously. Republish rootView on geometry changes in
the worker: without a display link in the never-ordered window, a frame
change alone never repainted until the next 1s scene tick.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 11, 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 Jun 11, 2026 5:33am
cmux-staging Ready Ready Preview, Comment Jun 11, 2026 5:33am

@coderabbitai

coderabbitai Bot commented Jun 11, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

RenderWorkerClient ownership moved to window/root-level state and passed as a Binding into RemoteCustomSidebarHost; RemoteCustomSidebarHost uses a .task(id: sourceKey) lifecycle; RenderWorkerCoordinator republished the SwiftUI rootView on resize; tests poll for context reset and recovery.

Changes

Client lifecycle ownership refactoring

Layer / File(s) Summary
RemoteCustomSidebarHost binding contract
Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RemoteCustomSidebarHost.swift
RemoteCustomSidebarHost replaces internal @State client ownership with an external @Binding, updates the public initializer to accept client: Binding<RenderWorkerClient?>, and moves lifecycle handling to .task(id: sourceKey) (removes .onDisappear shutdown).
Window-level client state and binding threading
Sources/ContentView.swift
ContentView imports CmuxSidebarInterpreterClient, adds a window-level @State optional sidebarRenderWorkerClient, exposes it as a @Binding, and threads $sidebarRenderWorkerClient into sidebar views and the custom-sidebar host.
Geometry resize root view republishing
Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RenderWorkerCoordinator.swift
RenderWorkerCoordinator adds displayedState, resets it on scene path change, updates it in refresh(), and reassigns hosting.rootView = currentContent(state: displayedState) in apply(_:) to republish SwiftUI content on resizes without re-running the interpreter.
RenderWorkerClient: sourceKey API
Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarInterpreterClient/RenderWorkerClient*.swift
RenderWorkerClient gains a public sourceKey property and initializer parameter; reexecingCurrentBinary gains an optional sourceKey parameter and passes it through when constructing the client.
Tests: context reset polling and recovery
Packages/CmuxSidebarInterpreterService/Tests/CmuxSidebarInterpreterClientTests/RenderWorkerClientTests.swift
discardsAHungWorkerAndRecovers now validates the initial .context, waits for client.contextCache.contextId to become nil via waitForContextReset, and then repeatedly ticks/inspects until a new context ID is observed; suite marked serialized.

Sequence Diagram(s)

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

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • manaflow-ai/cmux#5372: Overlaps with remote-render/interpreter client lifecycle and sidebar hosting changes.
  • manaflow-ai/cmux#5382: Also modifies RemoteCustomSidebarHost and related render-worker initialization and wiring.
  • manaflow-ai/cmux#5294: Related changes to render coordinator and geometry/resize handling.

Poem

🐰 I bound the client where the window can keep,
The sidebar frees its paws from shutdown’s leap,
On resize the rootView wakes and sings,
Tests wait and tick till fresh context springs,
A warm worker hums — steady renders to keep.


Important

Pre-merge checks failed

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

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

Check name Status Explanation Resolution
Cmux Expensive Synchronous Load ❌ Error PR adds synchronous file I/O to main actor: RenderWorkerCoordinator.apply(scene:) calls model.start() which synchronously loads/decodes sidebar files on main actor. Move CustomSidebarModel.reload() off main actor or wrap model.start() in Task.detached. Use background loader similar to SharedLiveAgentIndex pattern.
Cmux Swiftui State Layout ❌ Error RemoteCustomSidebarHost.swift violates the render-time state mutation rule: the .task(id: sourceKey) modifier writes to the @Binding client property, scheduling state mutation during body compu... Move client lifecycle (creation and shutdown) to explicit lifecycle callbacks or event handlers outside body computation, avoiding render-time state writes.
Cmux Source Artifacts ❌ Error Commit adds 30 artifact files: .claude/ (25 files), .agents/ (1), .greptile/ (3), .cursor/ (1), .coderabbit.yaml (1), violating source-control-artifacts.md rule for hidden scratch/tool directories. Remove .claude/, .agents/, .greptile/, .cursor/, and .coderabbit.yaml from the commit. Add patterns to .gitignore: .claude/, .agents/, .greptile/, .cursor/, .coderabbit.yaml
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description covers the Summary and Testing sections; however, the Demo Video, Review Trigger, and Checklist sections are missing or incomplete. Add a demo video URL, include the review trigger block with bot mentions, and complete the checklist items to match the template.
✅ Passed checks (16 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the two main changes: instant toggle remount (solving ~1s blank on unmount/remount) and live resize repaint (solving 1-2s resize lag).
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 RenderWorkerClient is a properly isolated actor; the new sourceKey property is correctly marked nonisolated; ContentView (@State holder) and RemoteCustomSidebarHost (View) are MainActor-implicit; a...
Cmux Swift Blocking Runtime ✅ Passed PR introduces no blocking runtime patterns in production Swift. Test-only waitForContextReset polling is deterministic scaffolding (allowed). Pre-existing Task.sleep in armAckWatchdog unchanged.
Cmux Cache Substitution Correctness ✅ Passed The displayedState cache in RenderWorkerCoordinator is a transient UI-only rendering hint used during geometry changes, not persisted. It has event-driven freshness (reset when file changes) and co...
Cmux No Hacky Sleeps ✅ Passed All changes in the PR are Swift code. The check applies only to TypeScript, JavaScript, shell, or non-Swift build/runtime scripts; Swift code is explicitly out of scope and covered by swift-blockin...
Cmux Algorithmic Complexity ✅ Passed PR uses tiny fixed-size collections (UI tap targets, view hierarchy) not scalable user data; new code only; test harnesses properly isolated from production code.
Cmux Swift Concurrency ✅ Passed PR improves Swift concurrency: replaces fire-and-forget Task pattern in .onDisappear with properly lifecycle-managed .task(id:) and direct await. No new DispatchQueue, Combine, or fire-and-forget p...
Cmux Swift @Concurrent ✅ Passed PR introduces no @concurrent annotation violations: no missing @concurrent on nonisolated async work, no invalid @concurrent on sync/actor functions, and proper async/await patterns throughout.
Cmux Swift File And Package Boundaries ✅ Passed All modified Swift files comply with swift-file-package-boundaries rules: RemoteCustomSidebarHost (75 lines, focused UI view), RenderWorkerCoordinator (385 lines, existing file with +12 focused fix...
Cmux Swift Logging ✅ Passed No logging violations found. RenderWorkerCoordinator uses FileHandle.standardError guarded by CMUX_RENDER_WORKER_DEBUG env variable check, matching allowed debug-only logging pattern.
Cmux User-Facing Error Privacy ✅ Passed No user-facing errors, alerts, or error messages were introduced that expose sensitive information. All changes are architectural improvements to client lifecycle and view rendering with only debug...
Cmux Full Internationalization ✅ Passed No user-facing text or localization catalog changes were added. All modifications are internal implementation (client lifecycle, rendering logic, parameters) or test code, which are exempt from int...
Cmux Architecture Rethink ✅ Passed The PR moves sidebar worker ownership from view-local to window-level (@State in ContentView, @Binding in views), with clear invariants: sourceKey matches mounted sidebar path, .task reacts to path...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed RemoteWorkerWindow is an existing hidden, never-ordered-in offscreen NSWindow that predates this PR. The PR's changes to RenderWorkerCoordinator only modify rendering and caching logic without affe...
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-sidebar-snappy

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 Jun 11, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two sidebar latency issues by lifting RenderWorkerClient ownership from the per-mount RemoteCustomSidebarHost to the window's ContentView (toggle blank fix) and by republishing the last-published SwiftUI state on every geometry change in RenderWorkerCoordinator (resize lag fix).

  • Toggle blank (~1s): @Binding replaces the old @State in RemoteCustomSidebarHost; .task(id: sourceKey) handles lazy first-create and source-switch cleanup, while the client survives sidebar hide/show across the window's lifetime. Worker lifecycle on window close and pipe-EOF are handled by the existing window-close reaper and the coordinator's EOF path respectively.
  • Resize lag (1–2s): displayedState tracks what is currently rendered in the offscreen NSHostingView; apply(geometry:) now re-sets hosting.rootView to that cached state and calls pump(), forcing a layout-and-commit without re-interpreting the sidebar source.
  • Tests: @Suite(.serialized) prevents cross-test state contamination; the hang test's fixed 700 ms sleep is replaced by an adaptive waitForContextReset polling helper.

Confidence Score: 5/5

Safe to merge after dogfood approval; the two latency fixes are well-scoped and the worker lifecycle is correctly handled across all paths (toggle, file switch, window close, window dealloc while hidden).

The binding-based ownership transfer is structurally clean: all assignments in the task body are synchronous before the only suspension point, so no partial-shutdown race is possible on cancellation. The displayedState mirror is kept in sync with every rootView assignment in refresh(), and the nil→.missing fallback on the first geometry replay before any scene is processed is harmless because the scene arrives in the same pipe burst and overwrites it before the host adopts the context. The single finding is a test-only nit about not checking task cancellation after a sleep in the polling helper.

No files require special attention; the test helper in RenderWorkerClientTests.swift has a minor cancellation-handling gap but it is test-only scaffolding.

Important Files Changed

Filename Overview
Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RemoteCustomSidebarHost.swift Client ownership moved from per-mount @State to window-scoped @binding; task(id: sourceKey) replaces onDisappear shutdown, enabling warm-worker toggles. Logic is sound: assignments before the only await prevent partial-shutdown races on task cancellation.
Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RenderWorkerCoordinator.swift Adds displayedState mirror to republish last-known SwiftUI state on geometry changes; correctly cleared on file switch and kept in sync by refresh(). The nil→.missing fallback on initial geometry replay is safe: the scene replay arrives immediately after and overwrites it before the host adopts the context.
Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarInterpreterClient/RenderWorkerClient.swift Adds nonisolated sourceKey property for cache/substitution identity; minimal additive change with no impact on actor isolation or worker lifecycle.
Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarInterpreterClient/RenderWorkerClient+ReexecSelf.swift Threads sourceKey through the re-exec factory; purely additive, correct.
Packages/CmuxSidebarInterpreterService/Tests/CmuxSidebarInterpreterClientTests/RenderWorkerClientTests.swift Serializes the suite and replaces the fixed-delay hang test with adaptive waitForContextReset polling. try? Task.sleep in the helper discards CancellationError without a subsequent isCancelled guard, inconsistent with the same pattern in RenderEventCollector.
Sources/ContentView.swift Adds @State sidebarRenderWorkerClient at per-window root and passes it as a binding down to VerticalTabsSidebar; invalidation only fires on first sidebar mount and file switches, so broad re-render churn is not a concern.

Sequence Diagram

sequenceDiagram
    participant CV as ContentView (@State client)
    participant Host as RemoteCustomSidebarHost (@Binding client)
    participant Client as RenderWorkerClient (actor)
    participant Worker as Render Worker (subprocess)
    participant Coord as RenderWorkerCoordinator (@MainActor)

    Note over CV,Host: First sidebar mount (client is nil)
    Host->>Host: task(id: sourceKey) fires
    Host->>Client: reexecingCurrentBinary(sourceKey:)
    CV-->>CV: "@State sidebarRenderWorkerClient set"

    Note over Client,Worker: Lazy worker spawn on first send
    Host->>Client: updateScene / resize
    Client->>Worker: launch() — replay lastGeometry + lastScene
    Worker->>Coord: handle(.resize) → apply(geometry:)
    Coord->>Coord: "hosting.rootView = currentContent(state: displayedState)"
    Coord->>Coord: pump()
    Worker->>Coord: handle(.scene) → refresh()
    Coord->>Coord: "displayedState = displayState"
    Coord->>Coord: "hosting.rootView = currentContent(state:)"
    Coord->>Coord: pump()
    Worker-->>Client: .context(contextId)
    Client-->>Host: subscribe() stream → .context

    Note over Host,CV: Sidebar hidden (toggle off)
    Host->>Host: "task cancelled — client stays in @Binding"
    Note over Client,Worker: Worker remains warm

    Note over Host,CV: Sidebar shown again (toggle on)
    Host->>Host: task(id: sourceKey) fires
    Host->>Host: "guard client?.sourceKey == sourceKey → return (no-op)"
    Host->>Client: subscribe() — gets cached contextId immediately

    Note over CV,Worker: Window close
    CV->>Client: shutdown() via window-close reaper
    Client->>Worker: terminate()
Loading

Reviews (5): Last reviewed commit: "Remove render worker recovery test race" | Re-trigger Greptile

Replace the global RenderWorkerClientPool with per-window ownership: the
client lives as @State on ContentView (the per-window root) and threads
down to RemoteCustomSidebarHost as a binding. Still created lazily on
the first custom-sidebar mount; survives sidebar toggles and provider
switches; reaped by the surface's window-close observer, with worker
pipe-EOF exit as the backstop when the window deallocates while hidden.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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: 2

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

Inline comments:
In
`@Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RemoteCustomSidebarHost.swift`:
- Around line 24-25: The cached RenderWorkerClient binding in
RemoteCustomSidebarHost (the `@Binding` private var client: RenderWorkerClient?)
is being reused across provider/fileURL changes and can serve stale snapshots;
update the logic to scope or key the cached client by the sidebar source (e.g.,
fileURL or provider identifier) or explicitly reset/invalidate the cached render
when the provider or fileURL changes: add a source key (or compare current
provider id/fileURL) and recreate or nil-out client and any cached remote
snapshot when that key differs (apply the same fix to other cached usages
referenced in the comment such as the other client bindings/usages), ensuring
any authoritative read replaces the cached state per
cache-substitution-correctness.md.

In `@Sources/ContentView.swift`:
- Around line 1086-1092: The PR text claiming sidebarRenderWorkerClient will
"linger until app exit" is inaccurate: clarify that RemoteSidebarSurfaceView
(not ContentView) wires window-close shutdown by observing
NSWindow.willCloseNotification in viewDidMoveToWindow and calling Task { await
client.shutdown() }, and when the sidebar is unmounted teardown() only removes
that observer (relying on pipe EOF on window dealloc to stop the worker); update
the PR objective/description to reflect this actual mechanism (referencing
sidebarRenderWorkerClient, RemoteSidebarSurfaceView, viewDidMoveToWindow,
teardown(), and shutdown()) unless you have a reproducible case where the window
fails to deallocate on close.
🪄 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: 589acdb3-e6d6-4f1a-8b3d-b66897ef9f8b

📥 Commits

Reviewing files that changed from the base of the PR and between cb3610d and fdbd6bc.

📒 Files selected for processing (2)
  • Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RemoteCustomSidebarHost.swift
  • Sources/ContentView.swift

Comment thread Sources/ContentView.swift Outdated

This branch was successfully deployed

1 active deployment
Preview – cmux — 3ddc1dfb Deployed Jun 11, 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