perf(desktop): cut sidebar hover and polling render fan-out

Merged
KI
3mo ago
+807-242

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 host pullRequests.refreshByWorkspaces mutation + query invalidation per hover.
  • ~80 per-row query subscriptions collapse into one: new DashboardSidebarWorkspaceStatusProvider holds 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.
  • DashboardSidebarWorkspaceStatusProvider subscribes 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.refreshByWorkspaces calls per hover (unit-tested).
    • Introduce DashboardSidebarWorkspaceStatusProvider to 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 isHovered subscription 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.

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.