Skip to content

Custom sidebars: in-process renderer by default, Settings section, worker resize pump fix - #5867

Merged
azooz2003-bit merged 17 commits into
mainfrom
feat-sidebar-inprocess-flag
Jun 11, 2026
Merged

azooz2003-bit merged 17 commits into
mainfrom
feat-sidebar-inprocess-flag

Conversation

@azooz2003-bit

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

Copy link
Copy Markdown
Collaborator

One consolidated PR for the sidebar vibe-coding program's runtime work (per owner direction), based on the merged live-eval engine foundation #5866.

In-process rendering is now the default for custom sidebars: CustomSidebarSurface mounts CustomSidebarView (real SwiftUI, native hover/focus/keyboard, same-frame resize) and the remote out-of-process worker stays one flip away as the containment lane for untrusted sources. The choice is the customSidebars.renderer setting (inProcess default, remote opt-in), live without restart.

Also in this PR: a Custom Sidebars section in Settings (enable toggle + renderer picker, search wired, en+ja localized), cmux settings open custom-sidebars CLI target, customSidebars.beta.enabled now defaults on, an evaluation node budget that keeps last-good output on pathological sources, and the worker display-link pump fix from #5898 (geometry republish + dirtiness-gated pump; measured resize repaint p50 422ms to 0.51ms).

The throwaway textField stub from the spike was reverted; real bindings come from the live-eval engine. Verified on tag inproc: typing/focus/hover in-process, same-frame resize, Settings section renders and navigates, codex computer-use sweep + differential on PR comments.

Design and evidence: cmuxterm-hq plans/sidebar-vibe-coding/ (DESIGN.md, REPORT.md, spikes/).

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Custom Sidebars feature is now enabled by default.
    • Added renderer selection in settings: choose between in-process (full input support) and remote/crash-isolated rendering modes.
    • Implemented evaluation budget protection to prevent pathological content from freezing the app; last good render is retained if budget exceeded.
    • New "Custom Sidebars" settings section for enabling and configuring renderer mode.
  • Documentation

    • Updated custom sidebars documentation with renderer options and configuration details.

azooz2003-bit and others added 5 commits June 10, 2026 21:17
New catalog section customSidebars with a JSON-backed renderer key
(~/.config/cmux/cmux.json: { "customSidebars": { "renderer":
"inProcess" } }), defaulting to the crash-isolated remote worker.
Unknown raw values fall back to the safe default; covered by store-level
behavior tests.

Part of the sidebar vibe-coding architecture program (spike 2:
in-process mount + containment + renderer flag).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RecursionBudget grows a second axis: total produced RenderNodes (default
3000, an order of magnitude above a rich real sidebar). ForEach loops and
ViewBuilder walks early-out once tripped, and the top-level evaluate
returns nil for a truncated walk so the host's existing last-good-sticky
publish keeps the previous render up instead of flashing a partial tree.

A pathological ForEach(0..<100_000) now trips in milliseconds instead of
handing SwiftUI a 100k-node tree that freezes the host. Covered by
behavior tests at the interpreter level (nil + speed bound + no false
trip at 400 rows) and at the publish level (CustomSidebarModel keeps the
last good render when a saved edit trips the budget).

Part of the sidebar vibe-coding architecture program (spike 2).

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

CustomSidebarSurface is the single mount seam: it renders the selected
custom sidebar through the out-of-process worker by default and mounts
the previously never-used in-process CustomSidebarView when
customSidebars.renderer is inProcess, switching live when the setting
changes. In-process gains native input (hover, focus, keyboard) and
same-frame resize for trusted local files; remote stays the containment
lane and the default.

ContentView reads the flag with @LiveSetting per main-window convention;
the surface itself stays settings-agnostic so the package needs no
settings dependency and tests can drive both branches. Documented in
docs/custom-sidebars.md.

Part of the sidebar vibe-coding architecture program (spike 2). This is
the mount the live-eval engine (tier 1) lands on.

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

Lowers TextField("placeholder", text: $name) to a .textField node and
renders it in-process as a real SwiftUI TextField backed by view-local
@State. The static IR has no binding concept, so the typed value never
round-trips into the interpreter environment; this exists purely to
demonstrate that the in-process mount delivers native focus/caret/typing
where the remote worker cannot. The real binding story is the live-eval
engine (spike 1 of the program).

Accepts the SwiftViewInterpreter.swift length-budget growth (669 -> 695)
for the spike.

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

Adds CMUX_RENDER_WORKER_DEBUG-gated timing logs to the sidebar render
worker: geometry application, rootView republish, and pump commits now
log CACurrentMediaTime timestamps and pump duration. No behavior change.

This makes the resize repaint lag measurable: with only this commit, a
resize message logs "geometry applied" and a pump, but the visible
repaint (the next "rootView republished" + pump) only appears when the
next scene tick arrives, up to a full second later.

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 10:05pm
cmux-staging Building Building Preview, Comment Jun 11, 2026 10:05pm

@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

Adds configurable custom sidebar renderer mode (in-process vs remote), integrates renderer selection into settings UI/navigation/search, strengthens interpreter containment with node-count budgeting, and provides end-to-end wiring with tests, remote-worker pump/hosting plumbing, docs, and localization.

Changes

Custom Sidebar Renderer Configuration and Containment

Layer / File(s) Summary
Renderer mode type, settings catalog, and persistence tests
Packages/CmuxSettings/Sources/CmuxSettings/Values/CustomSidebarRendererMode.swift, Packages/CmuxSettings/Sources/CmuxSettings/Keys/CustomSidebarsCatalogSection.swift, Packages/CmuxSettings/Sources/CmuxSettings/Keys/SettingCatalog.swift, Packages/CmuxSettings/Sources/CmuxSettings/Keys/BetaFeaturesCatalogSection.swift, Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Values/CustomSidebarRendererMode+Display.swift, Packages/CmuxSettings/Tests/CmuxSettingsTests/CustomSidebarRendererSettingTests.swift
Defines CustomSidebarRendererMode enum (remote/inProcess), exposes customSidebars.renderer JSON key with default .inProcess, turns the custom-sidebars beta flag on by default, provides UI display helpers, and tests defaults/decoding/round-trip persistence.
Settings UI section, navigation targets, and search integration
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/CustomSidebarsSection.swift, Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSectionID.swift, Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Scene/SettingsWindowScene.swift, Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift, Sources/SettingsNavigation.swift, Sources/SettingsSearchAliases.swift, CLI/CMUXCLI+DocsSettings.swift, Packages/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift
Adds CustomSidebarsSection SwiftUI settings card with enable toggle and renderer picker, wires it into settings window and navigation/search/anchor index, updates curated entries and CLI target aliases, and adjusts test contracts for anchoring.
Node-count budgeting in interpreter and render retention
Packages/CmuxSwiftRender/Sources/CmuxSwiftRender/RecursionBudget.swift, Packages/CmuxSwiftRender/Sources/CmuxSwiftRender/SwiftViewInterpreter.swift, Packages/CmuxSwiftRender/Tests/CmuxSwiftRenderTests/EvaluationNodeBudgetTests.swift, Packages/CmuxSwiftRenderUI/Tests/CmuxSwiftRenderUITests/CustomSidebarLastGoodTests.swift
Adds node-count accounting to RecursionBudget, enforces node-limit cutoffs across SwiftViewInterpreter (evaluate, evalView, evalItems, evalForEach, evalFor), records node production, and validates containment and last-good-render retention with targeted tests.
Rendering surface, app-level mounting, and documentation
Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/CustomSidebarSurface.swift, Sources/ContentView.swift, docs/custom-sidebars.md
Introduces CustomSidebarSurface to switch between in-process and remote mount points, threads a live customSidebars.renderer setting into ContentView to select the renderer, and updates docs to describe renderer choices, hot-reload behavior, and evaluation-budget handling.
Remote worker display pump, hosting, coordinator, and tests
Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RenderPumpGate.swift, Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RemoteWorkerDisplayPump.swift, Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RemoteWorkerHostingView.swift, Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RemoteWorkerWindow.swift, Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RenderWorkerCoordinator.swift, Packages/CmuxSidebarInterpreterService/Tests/CmuxSidebarRemoteRenderTests/RenderPumpGateTests.swift, Packages/CmuxSidebarInterpreterService/Package.swift
Implements a gate-driven display pump (CADisplayLink) to coalesce invalidations, bridges hosting invalidation signals to the coordinator, exposes window invalidation callbacks, wires the display pump into the coordinator with reasoned pump logging, and adds RenderPumpGate unit tests plus a new test target.
UI string localization
Resources/Localizable.xcstrings
Adds English/Japanese strings for custom sidebars UI: renderer labels/descriptions, enable toggle and subtitles, explanatory note, renderer label, section title, and search aliases.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • manaflow-ai/cmux#5382: Related earlier work introducing the remote renderer host and wiring that this PR switches between.
  • manaflow-ai/cmux#5864: Overlaps in custom-sidebar remote render mounting and client-binding lifecycle changes.
  • manaflow-ai/cmux#5275: Related changes to interpreter budgeting and env.budget wiring.

Poem

🐰 I nibble keys and toggle dots,

In-process hops or remote dot plots.
Budgets trim unruly trees,
Last-good keeps the calm with ease.
Settings shine — pick how it plots.


Important

Pre-merge checks failed

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

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

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error RemoteWorkerDisplayPump.swift (non-test) newly adds a CADisplayLink tick loop to drive rendering/pump, which is timer-based synchronization per swift-blocking-runtime rules. Replace the CADisplayLink-driven tick/polling with a real callback/signal-based pump trigger (e.g., render-need-display invalidation notifications/actor message) instead of timer/tick scheduling.
Cmux Swift Concurrency ❌ Error RenderWorkerCoordinator.swift introduces a fire-and-forget Task { @mainactor ... } in the model onChange handler (~line 201) without storing/canceling, which the concurrency rules flag. Replace the fire-and-forget Task with a lifecycle-managed Task (store/cancel before recreating) or ensure the callback executes on MainActor and call refresh()/observe() directly.
Cmux Full Internationalization ❌ Error Resources/Localizable.xcstrings supports 20 locales, but PR’s new/used keys (e.g., settings.customSidebars., customSidebarRenderer.) only have en/ja translations, missing the other 18 locales. Update Resources/Localizable.xcstrings to add translated entries for all 20 locale codes for the touched custom-sidebars keys (settings.customSidebars., settings.betaFeatures.customSidebars., settings.section.customSidebars, settings.s...
Cmux Source Artifacts ❌ Error git diff main..HEAD includes M ghostty with a subproject commit change (dependency/submodule update), which is disallowed by the source-control-artifacts rule. Revert the ghostty submodule update from the PR diff (or provide explicit, deliberate justification and approved dependency-update handling) before merging.
Docstring Coverage ⚠️ Warning Docstring coverage is 32.56% 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 PR description comprehensively covers the main changes, objectives, and verification, but lacks explicit sections matching the template structure (Summary, Testing, Demo Video, Checklist). Restructure the description to match the template: add 'Summary' and 'Testing' sections with explicit subsections, provide a demo video link if available, and check off the completion checklist items.
✅ Passed checks (15 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the three main changes: in-process renderer as default, Settings section addition, and worker resize pump fix.
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 Checked PR Swift changes for actor-isolation issues: new value types/enums aren’t @MainActor, no file-scoped Logger constants found, and the only @unchecked Sendable is LargeStackResultBox with an...
Cmux Expensive Synchronous Load ✅ Passed PR adds CustomSidebarsSection and settings models that use async-only patterns: @LiveSetting and value models seed from defaults without I/O, observe via AsyncStream, and persist via Task blocks. N...
Cmux Cache Substitution Correctness ✅ Passed Settings cache correctness looks handled: JSONConfigStore uses cacheValid+file-change invalidation and snapshotValue re-reads disk; last-good render is transient UI with documented graceful degrada...
Cmux No Hacky Sleeps ✅ Passed PR #5867 only changes .swift/.md/.xcstrings/.tsv files per GitHub files tab; there are no TS/JS/shell/build-runtime scripts where hacky sleeps/timers would apply.
Cmux Algorithmic Complexity ✅ Passed Reviewed algorithmic-complexity rules and production Swift changes: node budget adds explicit nodeLimit=3000 with early-outs; modified interpreter loops avoid new sort/filter patterns in hot paths.
Cmux Swift @Concurrent ✅ Passed Scanned Swift files referenced in the PR summary for @concurrent/nonisolated async; none found. The new CustomSidebarSurface TimelineView mount code contains no await/Task, so no @concurren...
Cmux Swift File And Package Boundaries ✅ Passed New production Swift files in this PR are small (≤103 lines each); none exceed the 400-line threshold. Large existing files were only lightly touched (no >250-line growth), keeping responsibilities...
Cmux Swift Logging ✅ Passed In the PR #5867 diff view, searches found no print(, debugPrint(, dump(, NSLog, or Logger strings, so no Swift logging-rule violations detected.
Cmux User-Facing Error Privacy ✅ Passed Reviewed PR diff text added for custom sidebars settings/UI; no user-facing error/alert/command-output/API recovery copy includes upstream vendor/provider names, flags, secrets, tokens, or raw upst...
Cmux Swiftui State Layout ✅ Passed Checked SwiftUI files (CustomSidebarSurface, CustomSidebarsSection, RenderNodeView, TextFieldNodeView): no new ObservableObject/@published, no GeometryReader measurement, lazy/list subtrees don’t c...
Cmux Architecture Rethink ✅ Passed Remote sidebar/worker architecture adds a display-link state machine (RenderPumpGate + CADisplayLink) with only platform bridging (NotificationCenter screen-change + invalidation callbacks); no asy...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed SettingsWindowScene keeps a SwiftUI Window id "cmux.settings" and Sources/cmuxApp.swift registers "cmux.settings" in cmuxAuxiliaryWindowIdentifiers; no standalone window/close-shortcut violations d...
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-sidebar-inprocess-flag

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 completes the custom-sidebars runtime by making the in-process renderer (CustomSidebarSurface) the default mount point, shipping the Custom Sidebars Settings section with en+ja localization, defaulting customSidebars.beta.enabled to true, and landing the display-link pump fix that drops resize repaint from p50 422 ms to 0.51 ms.

  • In-process renderer default: VerticalTabsSidebar now mounts CustomSidebarSurface instead of RemoteCustomSidebarHost directly; the surface switches between CustomSidebarView (native SwiftUI, full input) and RemoteCustomSidebarHost (out-of-process, click-only) via a rendersInProcess bool derived from the new customSidebars.renderer JSON setting (default .inProcess).
  • Evaluation node budget: RecursionBudget gains a nodeLimit (default 3,000) counter; SwiftViewInterpreter.evaluate returns nil when the budget trips, keeping last-good render up instead of flashing a truncated tree or building a multi-thousand-node tree that freezes SwiftUI.
  • Display-link pump fix: RemoteWorkerDisplayPump + RenderPumpGate replace the implicit reliance on the 1 s scene tick; invalidations from RemoteWorkerHostingView and RemoteWorkerWindow.viewsNeedDisplay arm a CADisplayLink that idles when clean; RenderWorkerCoordinator.pump() now calls pumpCompleted() so the gate parks after every explicit commit.

Confidence Score: 5/5

Safe to merge — the in-process renderer path, node budget, display pump, and Settings section are all well-isolated and covered by targeted tests.

The three main changes (in-process renderer default, node budget, display-link pump) each have direct unit tests that exercise the failure paths. Actor isolation is correct throughout: the coordinator and pump are @mainactor, RenderPumpGate is a plain value type, and the NotificationCenter callback uses MainActor.assumeIsolated appropriately. All new user-facing strings have both en and ja entries. No blocking primitives, no sleep-based synchronization, no raw print/NSLog in new production paths. The one acknowledged rough edge (one-tick @LiveSetting lag on initial mount) was already reviewed and noted in comments.

No files require special attention.

Important Files Changed

Filename Overview
Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/CustomSidebarSurface.swift New single mount seam switching between in-process CustomSidebarView and RemoteCustomSidebarHost via a boolean prop; settings-agnostic and directly testable.
Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RenderWorkerCoordinator.swift Wires RemoteWorkerDisplayPump to hosting-view and window invalidation signals; adds displayedState to fix drag-resize last-good flicker; pump() calls pumpCompleted() to idle the gate.
Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RemoteWorkerDisplayPump.swift New display-link pump: arms on invalidation, pauses on first clean tick, rebuilds on screen-change notification; correct @mainactor isolation and proper cleanup.
Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RenderPumpGate.swift Pure value-type gate for the display pump: well-tested markDirty/tickAction/pumpCompleted state machine.
Packages/CmuxSwiftRender/Sources/CmuxSwiftRender/RecursionBudget.swift Extends budget from recursion depth to total produced nodes; recordNode()/nodesExceeded threaded through evalItems, evalForEach, evalForIn correctly.
Packages/CmuxSwiftRender/Sources/CmuxSwiftRender/SwiftViewInterpreter.swift Adds recordNode() calls and budget early-exits; evaluate() returns nil on budget trip to preserve last-good render.
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/CustomSidebarsSection.swift New Custom Sidebars settings section; renderer picker disabled when feature off; follows existing DefaultsValueModel/JSONValueModel @State pattern.
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Values/CustomSidebarRendererMode+Display.swift uiCases lists .remote before .inProcess despite .inProcess being the default; all strings use String(localized:defaultValue:).
Resources/Localizable.xcstrings 9 new string keys with both en and ja translations, extractionState: manual.
Sources/ContentView.swift Replaces RemoteCustomSidebarHost with CustomSidebarSurface; @LiveSetting lag on first tick acknowledged in comments.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    LV["@LiveSetting\ncustomSidebars.renderer"] --> CSS
    CSS["CustomSidebarSurface\nrendersInProcess: Bool"]
    CSS -->|true| IPR["CustomSidebarView\nnative SwiftUI in-process\nhover · focus · keyboard"]
    CSS -->|false| RCH["RemoteCustomSidebarHost\nout-of-process worker\nclicks only"]
    RCH --> RWC["RenderWorkerCoordinator\n@MainActor"]
    RWC --> PUMP["RemoteWorkerDisplayPump\nCADisplayLink gated"]
    PUMP --> GATE["RenderPumpGate\nmarkDirty / tickAction / pumpCompleted"]
    RWC --> RHVIEW["RemoteWorkerHostingView\nneedsLayout/needsDisplay → noteInvalidation"]
    RWC --> RWWIN["RemoteWorkerWindow\nviewsNeedDisplay → noteInvalidation"]
    RHVIEW --> PUMP
    RWWIN --> PUMP
    SVI["SwiftViewInterpreter.evaluate"] -->|nil if nodesExceeded| LG["last-good sticky render"]
    SVI -->|node tree| RWC
    subgraph Budget
        RB["RecursionBudget\ndepth ≤ 400 · nodes ≤ 3000"]
    end
    SVI --> RB
Loading

Reviews (5): Last reviewed commit: "Accept file-length budget growth for ren..." | Re-trigger Greptile

Comment thread Sources/ContentView.swift
Comment on lines 11398 to 11419
// Periodic tick so the custom sidebar re-renders live (clock,
// countdowns, and refreshed workspace/data context), mirroring the
// default sidebar's TimelineView. No banned timers involved.
// Fully out-of-process: the render worker interprets AND renders
// the file; this view only hosts the worker's remote layer and
// forwards input, so no file-derived view code runs in the host.
// The surface mounts the out-of-process worker by default (no
// file-derived view code runs in the host); the
// `customSidebars.renderer` setting switches it to the in-process
// renderer for trusted local files (native hover/focus/keyboard,
// same-frame resize). The @LiveSetting's initial value lags one
// store round-trip on remount, so an `inProcess` choice can mount
// the worker for one tick before flipping; harmless (the host
// shuts the short-lived client down on unmount).
TimelineView(.periodic(from: .now, by: 1)) { timeline in
// No .id(customSidebarURL): the worker swaps files in place on
// the next scene message, so remounting the surface would only
// flash the previous sidebar's pixels during the switch.
RemoteCustomSidebarHost(
CustomSidebarSurface(
fileURL: customSidebarURL,
dataContext: customSidebarDataContext(now: timeline.date),
dispatch: makeCmuxSidebarActionDispatch(),
contentInsets: CustomSidebarContentInsets(
top: SidebarWorkspaceScrollInsets.workspaceList.top,
bottom: SidebarWorkspaceScrollInsets.workspaceList.bottom
)
),
rendersInProcess: customSidebarRenderer == .inProcess
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 @LiveSetting initial-value lag unconditionally spawns a remote worker for every inProcess launch

When customSidebars.renderer is inProcess, @LiveSetting delivers the stored value asynchronously after one store round-trip. For that first render tick customSidebarRenderer holds the default (.remote), so rendersInProcess: false is passed to CustomSidebarSurface, mounting RemoteCustomSidebarHost and spawning the out-of-process worker. When the @LiveSetting update arrives, VerticalTabsSidebar.body re-renders, unmounts the host, and mounts CustomSidebarView instead.

The in-code comment calls this "harmless," but the PR description describes it differently: "an inProcess run can briefly spawn a worker at launch which then lingers idle until the next renderer flip." Whether the worker terminates on unmount or truly lingers is unclear from the diff alone, but either way a process is unconditionally spawned on every mount when the user has set inProcess. The candidate fix (synchronous catalog read at init, mirroring CmuxExtensionSidebarSelection.customSidebarsEnabled) is identified but not implemented.

@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

🤖 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/CmuxSwiftRender/Sources/CmuxSwiftRender/SwiftViewInterpreter.swift`:
- Around line 191-202: Remove the SPIKE TextField lowering implemented in
SwiftViewInterpreter (the entire case "TextField" branch that builds a
RenderNode(kind: .textField, ...)) because it creates a non-shipping editable
path; either delete this branch and any references to RenderNode.kind
.textField, or wrap it behind a clearly non-shipping feature flag (e.g., guard
enableTextFieldSpike else { fallthrough/handle as before }) so production builds
never use it; ensure any helper usage like stringArgument(...) and bindingSource
mapping is also removed or behind the same gate to avoid leaving a
dead/inconsistent stub.
🪄 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: 4cd7bb68-ac9f-4755-8201-c0d5a9e7caa7

📥 Commits

Reviewing files that changed from the base of the PR and between 0254e79 and c24ada3.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (15)
  • Packages/CmuxSettings/Sources/CmuxSettings/Keys/CustomSidebarsCatalogSection.swift
  • Packages/CmuxSettings/Sources/CmuxSettings/Keys/SettingCatalog.swift
  • Packages/CmuxSettings/Sources/CmuxSettings/Values/CustomSidebarRendererMode.swift
  • Packages/CmuxSettings/Tests/CmuxSettingsTests/CustomSidebarRendererSettingTests.swift
  • Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/CustomSidebarSurface.swift
  • Packages/CmuxSwiftRender/Sources/CmuxSwiftRender/RecursionBudget.swift
  • Packages/CmuxSwiftRender/Sources/CmuxSwiftRender/RenderNode.swift
  • Packages/CmuxSwiftRender/Sources/CmuxSwiftRender/SwiftViewInterpreter.swift
  • Packages/CmuxSwiftRender/Tests/CmuxSwiftRenderTests/EvaluationNodeBudgetTests.swift
  • Packages/CmuxSwiftRender/Tests/CmuxSwiftRenderTests/TextFieldStubTests.swift
  • Packages/CmuxSwiftRenderUI/Sources/CmuxSwiftRenderUI/Rendering/RenderNodeView.swift
  • Packages/CmuxSwiftRenderUI/Sources/CmuxSwiftRenderUI/Rendering/TextFieldNodeView.swift
  • Packages/CmuxSwiftRenderUI/Tests/CmuxSwiftRenderUITests/CustomSidebarLastGoodTests.swift
  • Sources/ContentView.swift
  • docs/custom-sidebars.md

Comment thread Packages/CmuxSwiftRender/Sources/CmuxSwiftRender/SwiftViewInterpreter.swift Outdated
azooz2003-bit and others added 2 commits June 11, 2026 09:58
Fixes the 1-3 s drag-resize repaint lag in remotely rendered custom
sidebars. Two changes in the worker (CmuxSidebarRemoteRender):

1. The geometry handler now republishes rootView before its pump.
   Resizing the hosting view only marks AppKit layout dirty; SwiftUI's
   own render update waits for display-cycle work that never runs in
   the never-ordered window, so the old pump committed a stale tree and
   the visible repaint rode the host's next 1 s scene tick.

2. A display-refresh-driven pump (NSScreen.displayLink, macOS 14+, the
   non-deprecated CVDisplayLink replacement) commits invalidations that
   arrive between host messages. RemoteWorkerHostingView and
   RemoteWorkerWindow forward needsLayout/needsDisplay/viewsNeedDisplay
   flips into a RenderPumpGate; dirtiness resumes the paused link, each
   tick pumps at most once, and the first clean tick re-pauses it, so
   an idle worker has zero periodic wakeups (no timers, no polling).

Geometry republishes reuse the cached interpretation and the displayed
state, so a resize during a broken on-disk save keeps the last-good
sticky render instead of flipping to an error state.

Behavior tests cover the gate: arm/coalesce/pump/pause transitions and
commit absorption of invalidations raised during a pump.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A pump's own layout work re-marks the hosting view dirty mid-commit,
which resumed the link for one throwaway clean tick after every pump.
Pausing directly in pumpCompleted() makes the idle worker truly
wakeup-free between host messages.

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

Copy link
Copy Markdown
Collaborator Author

Automated UI sweep result (codex computer-use against the tagged inproc build, with differential):

PASS: TextField gets a focus ring and accepts live typing in-process. PASS: drag-resize of the sidebar repaints same-frame with no stale-pixel lag. PASS: vibe-demo renders cleanly (no blank flashes or color glitches).

Confirmed gap: .help() tooltips on sidebar rows never appear in-process. Differential rules out a synthetic-input artifact: under the same synthetic hover, the native + titlebar button shows its New workspace tooltip while sidebar rows show nothing. Likely mechanism: the 1s TimelineView re-evaluation churns view identity under the cursor, resetting the tooltip rest timer before it fires. Worth checking against the live-eval engine from #5866, whose per-box invalidation avoids whole-tree re-render. Hover row-highlight was also absent but the demo content declares none, so that part is expected.

🤖 Generated with Claude Code

…picker

New settings section between Sidebar and Beta Features. The toggle binds
to the existing betaFeatures.customSidebars defaults key (same gate as
the Beta Features row); the picker binds to the customSidebars.renderer
JSON key (remote | inProcess) through JSONValueModel and is disabled
while custom sidebars are off. Search: section keywords, two curated
entries, and the row-anchor contract lists updated. 10 new localized
strings (en + ja). CmuxSettingsUI tests 30/30 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
azooz2003-bit and others added 5 commits June 11, 2026 14:13
App-side SettingsNavigationTarget gains the customSidebars case (title,
symbol, search text, alias, two setting entries, customSidebars.renderer
path anchor) so settings search and socket navigation reach the new
section, and the CLI accepts 'cmux settings open custom-sidebars'.
Verified on the tagged inproc build: the command opens Settings scrolled
to the section with the toggle and renderer picker rendered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
customSidebars.beta.enabled now defaults true. The catalog default is
the single source of truth (readers fall back to key.defaultValue), so
no other code changes. Users who toggled it off keep their stored
false. Docs updated to describe the off switch instead of opt-in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
customSidebars.renderer now defaults to inProcess: native input (hover,
focus, keyboard) and same-frame resize out of the box. The remote
worker stays one settings flip away as the containment lane for
untrusted sources; an explicit "remote" in cmux.json is honored
unchanged. Renderer tests updated to the new default (72 green), docs
and DocC flipped to match.

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

# Conflicts:
#	Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RenderWorkerCoordinator.swift
azooz2003-bit and others added 3 commits June 11, 2026 14:44
…s-flag

# Conflicts:
#	Sources/ContentView.swift
#	docs/custom-sidebars.md
Threads the client binding from main's instant-remount change (PR 5864)
through the surface seam, and defaults rendersInProcess to true to match
the new renderer default.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@azooz2003-bit azooz2003-bit changed the title Spike: in-process custom-sidebar mount behind customSidebars.renderer flag Custom sidebars: in-process renderer by default, Settings section, worker resize pump fix Jun 11, 2026
SwiftViewInterpreter 669->683 (node-budget containment), SettingsNavigation
589->599 (customSidebars nav case + search entries), SettingsWindowScene
523->531 (section mount), ContentView 19248->19256 (renderer flag wiring).

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: 1

Caution

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

⚠️ Outside diff range comments (1)
Packages/CmuxSwiftRender/Sources/CmuxSwiftRender/SwiftViewInterpreter.swift (1)

419-426: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stop Reorderable iteration immediately once node budget is exceeded.

At Line 419, evalReorderable keeps iterating all items and appending ids even after env.budget.nodesExceeded is true. That bypasses the new fast-cutoff behavior added elsewhere (evalForEach/evalFor) and can still do large O(n) work before evaluate discards the tree.

🔧 Suggested fix
 for item in items {
+    if env.budget.nodesExceeded { break }
     let scope = env.makeChild()
     if let paramName { scope.define(paramName, item) }
     scope.define("$0", item)
     let rowNodes = evalItems(closure.statements, scope)
+    if env.budget.nodesExceeded { break }
     rows.append(rowNodes.count == 1 ? rowNodes[0] : RenderNode(kind: .vstack, children: rowNodes))
     ids.append(item.member(idField)?.displayString ?? "")
 }

As per coding guidelines, for Swift production paths over scalable user data, nested/full rescans and unbounded work in hot paths should be flagged and cut off with bounded behavior.

🤖 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 `@Packages/CmuxSwiftRender/Sources/CmuxSwiftRender/SwiftViewInterpreter.swift`
around lines 419 - 426, In evalReorderable, stop iterating items immediately
when env.budget.nodesExceeded becomes true (matching the fast-cutoff behavior in
evalForEach/evalFor): inside the for item in items loop (in
SwiftViewInterpreter.swift) check env.budget.nodesExceeded at the top and break
out before doing any work (scope creation, evalItems, appending rows or ids);
also ensure you do not append to ids after the cutoff (i.e., only append ids if
nodesExceeded is false). This ensures evalItems/rows and ids growth are bounded
and mirrors the early-return behavior used elsewhere.

Source: Coding guidelines

🤖 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/RemoteWorkerDisplayPump.swift`:
- Around line 48-51: noteInvalidation() currently only calls resumeLink() on a
clean→dirty transition so if resumeLink() returns early (no screen) isDirty
remains true and future invalidations never retry; update noteInvalidation() to
still call resumeLink() when gate.markDirty() returns false but link == nil
(i.e. if link is nil, attempt to resume/rebuild the link regardless of dirty
transition). Also apply the same principle to the other places mentioned: where
resumeLink() or rebuildLinkIfNeeded() bail out based on isDirty or link (refer
to resumeLink(), rebuildLinkIfNeeded(), gate.markDirty()/isDirty and the link
property) so ensure they retry link creation when link == nil even if isDirty is
already set.

---

Outside diff comments:
In `@Packages/CmuxSwiftRender/Sources/CmuxSwiftRender/SwiftViewInterpreter.swift`:
- Around line 419-426: In evalReorderable, stop iterating items immediately when
env.budget.nodesExceeded becomes true (matching the fast-cutoff behavior in
evalForEach/evalFor): inside the for item in items loop (in
SwiftViewInterpreter.swift) check env.budget.nodesExceeded at the top and break
out before doing any work (scope creation, evalItems, appending rows or ids);
also ensure you do not append to ids after the cutoff (i.e., only append ids if
nodesExceeded is false). This ensures evalItems/rows and ids growth are bounded
and mirrors the early-return behavior used elsewhere.
🪄 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: a0d53df3-3735-46a7-99ec-d4ec896506f1

📥 Commits

Reviewing files that changed from the base of the PR and between 24eb42a and 8560725.

📒 Files selected for processing (13)
  • Packages/CmuxSidebarInterpreterService/Package.swift
  • Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/CustomSidebarSurface.swift
  • Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RemoteWorkerDisplayPump.swift
  • Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RemoteWorkerHostingView.swift
  • Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RemoteWorkerWindow.swift
  • Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RenderPumpGate.swift
  • Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RenderWorkerCoordinator.swift
  • Packages/CmuxSidebarInterpreterService/Tests/CmuxSidebarRemoteRenderTests/RenderPumpGateTests.swift
  • Packages/CmuxSwiftRender/Sources/CmuxSwiftRender/RecursionBudget.swift
  • Packages/CmuxSwiftRender/Sources/CmuxSwiftRender/SwiftViewInterpreter.swift
  • Resources/Localizable.xcstrings
  • Sources/ContentView.swift
  • docs/custom-sidebars.md
💤 Files with no reviewable changes (1)
  • Resources/Localizable.xcstrings

Comment on lines +48 to +51
func noteInvalidation() {
guard gate.markDirty() else { return }
resumeLink()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Retry link creation when dirty and link is nil to avoid a stalled pump.

At Line 49, noteInvalidation() only calls resumeLink() on a clean→dirty transition. If resumeLink() returned early at Line 87 (no screen available), isDirty stays true and subsequent invalidations never retry link creation; rebuildLinkIfNeeded() at Line 96 also bails when link == nil.

🔧 Suggested fix
 func noteInvalidation() {
-    guard gate.markDirty() else { return }
-    resumeLink()
+    let mustResume = gate.markDirty()
+    if mustResume || link == nil {
+        resumeLink()
+    }
 }
@@
 private func rebuildLinkIfNeeded() {
-    guard link != nil else { return }
-    link?.invalidate()
-    link = nil
+    link?.invalidate()
+    link = nil
     if gate.isDirty {
         resumeLink()
     }
 }

Also applies to: 83-93, 95-101

🤖 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
`@Packages/CmuxSidebarInterpreterService/Sources/CmuxSidebarRemoteRender/RemoteWorkerDisplayPump.swift`
around lines 48 - 51, noteInvalidation() currently only calls resumeLink() on a
clean→dirty transition so if resumeLink() returns early (no screen) isDirty
remains true and future invalidations never retry; update noteInvalidation() to
still call resumeLink() when gate.markDirty() returns false but link == nil
(i.e. if link is nil, attempt to resume/rebuild the link regardless of dirty
transition). Also apply the same principle to the other places mentioned: where
resumeLink() or rebuildLinkIfNeeded() bail out based on isDirty or link (refer
to resumeLink(), rebuildLinkIfNeeded(), gate.markDirty()/isDirty and the link
property) so ensure they retry link creation when link == nil even if isDirty is
already set.

@azooz2003-bit
azooz2003-bit merged commit ccde75d into main Jun 11, 2026
22 checks passed
@austinywang austinywang mentioned this pull request Jun 12, 2026
hhsw2015 pushed a commit to hhsw2015/cmux that referenced this pull request Jun 12, 2026
…browser CLI, pairing QR, iOS

PRs included:
- manaflow-ai#5816 ControlCommandCoordinator extraction (package coordinator skeleton; fork keeps legacy v2* dispatchers)
- manaflow-ai#5859 sidebar perf
- manaflow-ai#5857 RendererRealization (added as SurfaceHibernation adapter)
- manaflow-ai#5867 in-process custom sidebars
- manaflow-ai#5778 browser CLI / system-proxy bypass
- manaflow-ai#5872 minimal pairing QR
- iOS pairing/manual-entry stack
- 30+ hot fixes

Fork-side adjustments:
- Skip 21 TerminalController+Control* extension files (PR manaflow-ai#5816 architecture refactor not adopted)
- Add Sources/App/RendererRealizationSettingsAdapter.swift to bridge new RendererRealizationSettings to fork's existing SurfaceHibernationSettings
- Restore v2SurfaceDragToSplit shim removed by upstream
- Add SettingsNavigationTarget.customSidebars case
- Stub ghostty_surface_set_renderer_realized callsites pending GhosttyKit rebuild (zig 0.15.2 required, host has 0.16.0)
- Update ghostty submodule to 44b2baa81 (cherry-pick the 3 renderer commits onto fork's manaflow-ai#5128 link-fix pointer)
- Keep fork's CMUXSessionDaemon module pbxproj refs and SurfaceHibernation settings

This branch was successfully deployed

1 active deployment
Preview – cmux — 36e53e76 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