perf(desktop): cut sidebar hover and polling render fan-out
Stacked follow-up to #6364 (sidebar pin drag-and-drop). Addresses the three pre-existing sidebar rendering costs identified while profiling that work.
Summary
- Hover-open PR refresh gets a cool-down (
pullRequestRefreshCooldown, unit-tested): opening a row's hover card no longer fires an unthrottled hostpullRequests.refreshByWorkspacesmutation + query invalidation per hover. - ~80 per-row query subscriptions collapse into one: new
DashboardSidebarWorkspaceStatusProviderholds a single sidebar-level subscription with per-row selectors (a row re-renders only when its entry changes); the hover provider is split so rows consume a stable actions context instead of churning state. - Per-row framer-motion wrappers replaced with CSS row-collapse: only the whole-project expand/collapse motion wrapper remains. Section collapse still animates; rows still hide while their section header is dragged.
Why / Context
CPU profiles during the #6364 work showed a plain mouse sweep across sidebar rows costing ~358ms of long tasks (worst 129ms) with shallowEqualObjects/useBaseQuery hot — hover-open side effects, per-row react-query subscriptions notified on every 10s PR poll, and ~80 motion components.
How It Works
- The cool-down is a module-level per-workspace timestamp map checked before firing the refresh; semantics of what refreshes are unchanged, only the rate.
DashboardSidebarWorkspaceStatusProvidersubscribes once and exposes selector hooks; poll ticks no longer run ~80 per-row subscription compare passes.- The hover provider now exports a state context and a separate stable actions context; the DnD provider and rows consume actions without re-rendering on hover-state changes.
Verification
Authored by an orchestrated worker agent; independently re-verified by the coordinator, then rebased onto post-#6364 main with one conflict reconciled (DashboardSidebarDndProvider: kept the actions-context split and the try/finally suppression release + fire-time open-timer re-check from 1aab78f3f — both suppression guards confirmed present after the merge).
bun run typecheck,bun run lint(exit 0, no post-lint diff), 92/92 tests across the sidebar + state + host-service-hook suites — run on the rebased branch.- CDP on the rebased branch (worker worktree stack, correct-instance verified): pin-by-drag works, and the drag-in-then-drag-out single gesture (the #6364 review finding) completes both transfers; zero React errors.
- Hover-sweep long tasks, same methodology as the baseline: 358ms / 4 tasks / 129ms worst → 178ms / 2 tasks / 90ms worst in my measurement window (the worker measured 0ms in a quieter window; the residual in mine coincides with the 10s PR-poll tick, which this PR reduces in fan-out but deliberately does not re-schedule).
Manual QA for reviewer (dev dataset couldn't exercise these)
- Agent status dots on sidebar rows render/update correctly with live running agents (status data now flows through the new provider)
- Section group collapse/expand animation looks right with real section groups (CSS transition replaced per-row framer-motion)
Risks / Rollback
- Risk: status/notification display regressions (every row's status now flows through one provider). Mitigated by preserved query semantics + suites; the two QA boxes above are the uncovered surface.
- Rollback: revert this single commit; no schema or persisted-state changes.
Summary by cubic
Reduce sidebar render fan-out on hover and polling to cut long-task time during a hover sweep by ~50% and limit re-renders to only affected rows.
- Performance
- Add a 45s cool-down for hover-open PR refreshes to avoid repeated
pullRequests.refreshByWorkspacescalls per hover (unit-tested). - Introduce
DashboardSidebarWorkspaceStatusProviderto replace ~80 per-row query subscriptions with one sidebar-level subscription + per-row selectors; also centralizes lifecycle event listeners and diff-stats invalidation. - Split hover provider into a stable actions context plus a per-row
isHoveredsubscription so a hover commit re-renders only the two involved rows. - Replace per-row framer-motion wrappers with a CSS grid-row/opacity transition; rows stay mounted but inert; section animations preserved.
- Add a 45s cool-down for hover-open PR refreshes to avoid repeated
Summary by CodeRabbit
- New Features
- Added consolidated workspace status indicators to the dashboard sidebar, including attention states, terminal activity, and diff statistics.
- Improved sidebar hover behavior and transitions for smoother interactions.
- Added workspace-specific unread and attention counts.
- Performance
- Limited pull-request refreshes to once every 45 seconds per workspace.
- Bug Fixes
- Improved sidebar status updates and terminal-seen handling across workspace rows.
- Tests
- Added coverage for pull-request refresh cooldown behavior.