Skip to content

Fix sidebar live refresh for branch and PR state - #2331

Merged
austinywang merged 2 commits into
mainfrom
issue-2329-sidebar-live-refresh
Mar 30, 2026
Merged

austinywang merged 2 commits into
mainfrom
issue-2329-sidebar-live-refresh

Conversation

@austinywang

@austinywang austinywang commented Mar 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • keep app-side git metadata polling active for tracked workspaces even when the current branch is main/master
  • add a faster selected-workspace refresh loop so branch checkout and PR state changes surface without unrelated sidebar redraws
  • add regression coverage for checkout-from-main and tracked-workspace polling behavior

Closes #2329

Testing

  • not run locally (per repo policy)

Summary by cubic

Fixes sidebar live refresh so branch checkouts and PR state update within ~5s, even on main/master (closes #2329). Adds a fast poll for the selected workspace and keeps git metadata polling active for all tracked workspaces.

  • Bug Fixes
    • Added a 5s selected-workspace Git metadata timer to surface branch/PR changes without full sidebar sweeps.
    • Removed skip logic for main/master; all tracked panels are now polled unless a probe is active.
    • Added regression tests for main/master inclusion and for updating after checking out a feature branch.

Written for commit 5ab3ee1. Summary will update on new commits.

Summary by CodeRabbit

  • Improvements

    • Optimized Git metadata polling for the currently selected workspace's focused panel with increased refresh frequency.
    • Simplified panel selection logic for Git metadata polling.
  • Tests

    • Updated unit tests to reflect new polling candidate panel behavior.
    • Added test coverage for periodic Git metadata refresh during workspace branch changes.

@vercel

vercel Bot commented Mar 30, 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 Mar 30, 2026 1:09am

@coderabbitai

coderabbitai Bot commented Mar 30, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This change introduces a dedicated, faster polling mechanism (5-second interval) for the currently selected workspace's focused panel Git metadata. It replaces branch-based polling guards with simpler candidate panel selection logic that always includes the focused panel, removing two helper methods that consulted branch information for polling decisions.

Changes

Cohort / File(s) Summary
Selected Workspace Polling
Sources/TabManager.swift
Added selectedWorkspaceGitMetadataPollInterval constant and selectedWorkspaceGitMetadataPollTimer property; new timer starts in init and is cancelled in deinit, periodically invoking refreshSelectedWorkspaceGitMetadata() to filter candidate panels and schedule Git metadata refresh with reason "selectedPeriodicPoll".
Simplified Candidate Selection
Sources/TabManager.swift
Removed branch-based polling guards (shouldPollTrackedWorkspaceGitMetadata helper and trackedWorkspaceGitBranch property); tracked-workspace candidate panel logic now excludes only active probe keys without consulting branch lookup/PR-skip conditions.
Updated Test Expectations
cmuxTests/TabManagerUnitTests.swift
Renamed and updated testTrackedWorkspaceGitMetadataPollCandidatesSkipMainAndMasterPanelsOnly to include focused main/master panels in candidate set; updated testTrackedWorkspaceGitMetadataPollCandidatesSkipFocusedFallbackOnMainOnly to include focused panel when workspace is on main; added testPeriodicWorkspaceGitMetadataRefreshUpdatesMainWorkspaceAfterCheckoutToFeatureBranch to verify branch updates propagate after Git checkout.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A timer ticks with focus keen,
Five seconds fast, the branch is seen,
No guards to skip, just polls the way,
The workspace fresh, come what may! 🌿✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix sidebar live refresh for branch and PR state' accurately summarizes the main change: enabling faster Git metadata polling so branch and PR state updates appear in the sidebar without delay.
Description check ✅ Passed The description covers the required template sections: Summary explains what changed and why, Testing notes the local testing approach per repo policy, and the auto-generated summary provides additional context.
Linked Issues check ✅ Passed The PR implements the key objectives from #2329: adds a 5s polling loop for the selected workspace to detect branch/PR changes quickly, removes skip logic for main/master branches to keep polling active, and includes regression tests.
Out of Scope Changes check ✅ Passed All changes align with the linked issue #2329: TabManager refactoring implements faster polling, test updates validate the new behavior, and no unrelated modifications are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-2329-sidebar-live-refresh

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.

@cubic-dev-ai cubic-dev-ai 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.

No issues found across 2 files

@greptile-apps

greptile-apps Bot commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two related bugs: (1) the background git-metadata poll was silently skipping workspaces on main/master branches, so a git checkout away from main would not be reflected in the sidebar; and (2) the 30s poll interval was too slow for branch/PR state changes to surface promptly. The fix removes the shouldPollTrackedWorkspaceGitMetadata/trackedWorkspaceGitBranch helpers that filtered out main/master panels, and introduces a dedicated 5-second selectedWorkspaceGitMetadataPollTimer for the focused panel only. The GitHub PR lookup guard in workspacePullRequestSnapshot is left intact, so extra overhead is just two fast local git commands per 5s tick.

Confidence Score: 5/5

Safe to merge; the only open item is a P2 suggestion to add a testing shim for the new fast-path function.

All findings are P2 style/coverage suggestions. The logic change is correct: removing the main/master filter is precisely the intended fix, the PR-lookup guard in workspacePullRequestSnapshot is left intact preventing GitHub API regressions, and the two-commit regression structure matches CLAUDE.md policy.

Sources/TabManager.swift — the new refreshSelectedWorkspaceGitMetadata function lacks a testing accessor, but its underlying logic is covered transitively by the existing tests.

Important Files Changed

Filename Overview
Sources/TabManager.swift Removes the main/master branch filter from polling candidates and adds a 5-second focused-panel refresh timer alongside the existing 30-second background sweep. Two private helpers are deleted and a new startSelectedWorkspaceGitMetadataPollTimer/refreshSelectedWorkspaceGitMetadata pair is added. Logic is correct; minor gap is the new fast-path function has no testing shim.
cmuxTests/TabManagerUnitTests.swift Updates two renamed/inverted assertion tests to expect all panels (including main/master) as candidates, and adds a new integration-style test that creates a real git repo, checks out to a feature branch, and verifies the background poll detects the change. Follows the two-commit regression-test policy from CLAUDE.md.

Sequence Diagram

sequenceDiagram
    participant ST as selectedWorkspaceGitMetadataPollTimer (5s)
    participant BT as workspaceGitMetadataPollTimer (30s)
    participant TM as TabManager (main thread)
    participant WS as Workspace (selected)
    participant Git as git process

    Note over ST,BT: Both timers fire on .global(qos: .utility), dispatch to main

    ST->>TM: refreshSelectedWorkspaceGitMetadata()
    TM->>TM: trackedWorkspaceGitMetadataPollCandidatePanelIds(selectedWorkspace)
    TM->>TM: guard candidatePanelIds.contains(focusedPanelId)
    TM->>TM: scheduleWorkspaceGitMetadataRefreshIfPossible(focusedPanelId)
    TM->>Git: git branch --show-current
    TM->>Git: git status --porcelain -uno
    Git-->>TM: branch / dirty state
    TM->>WS: updatePanelGitBranch(focusedPanelId, branch, isDirty)
    Note right of WS: also sets gitBranch when panelId == focusedPanelId

    BT->>TM: refreshTrackedWorkspaceGitMetadata()
    TM->>TM: iterate all workspace tabs
    TM->>TM: scheduleWorkspaceGitMetadataRefreshIfPossible (skipped if probe active)
Loading

Reviews (1): Last reviewed commit: "Refresh sidebar git metadata on active w..." | Re-trigger Greptile

Comment thread Sources/TabManager.swift
Comment on lines +960 to +978
private func refreshSelectedWorkspaceGitMetadata() {
guard let workspace = selectedWorkspace,
let focusedPanelId = workspace.focusedPanelId else {
return
}

let activeProbeKeys = Set(workspaceGitProbeGenerationByKey.keys)
let candidatePanelIds = trackedWorkspaceGitMetadataPollCandidatePanelIds(
in: workspace,
activeProbeKeys: activeProbeKeys
)
guard candidatePanelIds.contains(focusedPanelId) else { return }

scheduleWorkspaceGitMetadataRefreshIfPossible(
workspaceId: workspace.id,
panelId: focusedPanelId,
reason: "selectedPeriodicPoll"
)
}

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.

P2 No testing shim for the new fast-path function

refreshSelectedWorkspaceGitMetadata contains its own guard logic (specifically the candidatePanelIds.contains(focusedPanelId) check) that isn't exercised by any test. All new tests invoke refreshTrackedWorkspaceGitMetadataForTesting(), which goes through the background-sweep path.

The existing convention in the file is to expose a ForTesting wrapper for functions that need unit coverage. Consider adding:

func refreshSelectedWorkspaceGitMetadataForTesting() {
    refreshSelectedWorkspaceGitMetadata()
}

This would let a test verify that a workspace whose focused panel is not yet in panelGitBranches is correctly skipped by the fast path, and that a refresh is scheduled once the panel becomes a candidate.

@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

🧹 Nitpick comments (1)
cmuxTests/TabManagerUnitTests.swift (1)

499-504: Wait on both branch projections in the same predicate.

The test only blocks until workspace.panelGitBranches[panelId] flips, then immediately reads workspace.gitBranch. If those two updates land on adjacent async turns, this becomes unnecessarily timing-sensitive in CI.

♻️ Suggested test hardening
-        XCTAssertTrue(
-            waitForCondition {
-                workspace.panelGitBranches[panelId]?.branch == "feature/sidebar-live-refresh"
-            }
-        )
-        XCTAssertEqual(workspace.gitBranch?.branch, "feature/sidebar-live-refresh")
+        XCTAssertTrue(
+            waitForCondition {
+                workspace.panelGitBranches[panelId]?.branch == "feature/sidebar-live-refresh"
+                    && workspace.gitBranch?.branch == "feature/sidebar-live-refresh"
+            }
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/TabManagerUnitTests.swift` around lines 499 - 504, The test
currently waits only for workspace.panelGitBranches[panelId]?.branch to become
"feature/sidebar-live-refresh" then immediately asserts
workspace.gitBranch?.branch, which can race; update the waitForCondition
predicate (the closure passed to waitForCondition) to check both
workspace.panelGitBranches[panelId]?.branch and workspace.gitBranch?.branch
equal "feature/sidebar-live-refresh" in the same predicate so the test blocks
until both projections are updated (referencing waitForCondition,
workspace.panelGitBranches[panelId], workspace.gitBranch, and panelId).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/TabManager.swift`:
- Line 702: The selected-workspace loop currently driven by
selectedWorkspaceGitMetadataPollInterval (5s) runs the full metadata probe
including the gh pr list / gh pr checks calls; change this so the 5s loop only
performs local checks (branch name, working-tree dirty state) and move remote
PR/GitHub CLI calls into a separate, lower-frequency path. Concretely: in the
selected-workspace loop (the code that reads
selectedWorkspaceGitMetadataPollInterval) remove or short-circuit calls to the
functions that invoke the GitHub CLI (e.g., any
fetchPRMetadata/probeRemotePRs/performGitHubCliChecks helpers) and either (a)
add a separate timer/worker for remote probes with its own TTL/backoff, or (b)
gate remote calls with a lastPRCheckTime + TTL check so remote probes run far
less often. Ensure you reference selectedWorkspaceGitMetadataPollInterval and
the PR-check helper functions when applying the change so only local checkout
detection remains on the 5s interval.

---

Nitpick comments:
In `@cmuxTests/TabManagerUnitTests.swift`:
- Around line 499-504: The test currently waits only for
workspace.panelGitBranches[panelId]?.branch to become
"feature/sidebar-live-refresh" then immediately asserts
workspace.gitBranch?.branch, which can race; update the waitForCondition
predicate (the closure passed to waitForCondition) to check both
workspace.panelGitBranches[panelId]?.branch and workspace.gitBranch?.branch
equal "feature/sidebar-live-refresh" in the same predicate so the test blocks
until both projections are updated (referencing waitForCondition,
workspace.panelGitBranches[panelId], workspace.gitBranch, and panelId).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 482b51f5-d775-4980-b655-cdb44466d3ec

📥 Commits

Reviewing files that changed from the base of the PR and between e419fd9 and 5ab3ee1.

📒 Files selected for processing (2)
  • Sources/TabManager.swift
  • cmuxTests/TabManagerUnitTests.swift

Comment thread Sources/TabManager.swift
private static var nextPortOrdinal: Int = 0
private static let initialWorkspaceGitProbeDelays: [TimeInterval] = [0, 0.5, 1.5, 3.0, 6.0, 10.0]
private static let workspaceGitMetadataPollInterval: TimeInterval = 30
private static let selectedWorkspaceGitMetadataPollInterval: TimeInterval = 5

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="Sources/TabManager.swift"

printf '== New fast polling loop ==\n'
sed -n '700,1015p' "$file"

printf '\n== Full metadata probe path used by that loop ==\n'
sed -n '1582,1856p' "$file"

printf '\n== Worst-case selected-workspace gh call volume ==\n'
python - <<'PY'
selected_interval_seconds = 5
gh_calls_per_poll_when_pr_open = 2  # gh pr list + gh pr checks
polls_per_hour = 3600 // selected_interval_seconds
print({
    "selected_workspace_polls_per_hour": polls_per_hour,
    "worst_case_gh_calls_per_hour_per_window": polls_per_hour * gh_calls_per_poll_when_pr_open
})
PY

Repository: manaflow-ai/cmux

Length of output: 23150


Throttle PR/network probes separately from the new 5s branch poll.

The new selected-workspace loop runs the full metadata probe path, which includes gh pr list and gh pr checks calls on every 5-second cycle when a PR is open on a non-main branch. This drives ~1,440 GitHub CLI calls per hour per window—a significant reliability and performance cost for code that only needs fast local checkout detection.

Keep the 5-second loop local-only (branch/dirtiness) or add a separate PR TTL/backoff to decouple remote polling from the high-frequency timer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/TabManager.swift` at line 702, The selected-workspace loop currently
driven by selectedWorkspaceGitMetadataPollInterval (5s) runs the full metadata
probe including the gh pr list / gh pr checks calls; change this so the 5s loop
only performs local checks (branch name, working-tree dirty state) and move
remote PR/GitHub CLI calls into a separate, lower-frequency path. Concretely: in
the selected-workspace loop (the code that reads
selectedWorkspaceGitMetadataPollInterval) remove or short-circuit calls to the
functions that invoke the GitHub CLI (e.g., any
fetchPRMetadata/probeRemotePRs/performGitHubCliChecks helpers) and either (a)
add a separate timer/worker for remote probes with its own TTL/backoff, or (b)
gate remote calls with a lastPRCheckTime + TTL check so remote probes run far
less often. Ensure you reference selectedWorkspaceGitMetadataPollInterval and
the PR-check helper functions when applying the change so only local checkout
detection remains on the 5s interval.

@austinywang
austinywang merged commit 94cc865 into main Mar 30, 2026
20 checks passed
bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
* Add regression coverage for sidebar live refresh

* Refresh sidebar git metadata on active workspaces

This branch was successfully deployed

1 active deployment
Preview — 5ab3ee1b Deployed Mar 30, 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.

Sidebar does not live-refresh on git checkout, new PR, or agent-driven state changes

1 participant