Repository navigation
feat(desktop): morph v2 sidebar inline agent/port sections into a compact activity strip - #5439
Conversation
…pact activity strip Replace the per-workspace "N ports" / "N agents" toggle sections (which cost up to ~6 sidebar lines per workspace) with a single fixed-height activity strip under each workspace title: - At rest: overlapping agent circles (icon + status dot) plus a tiny port-count pill. - On hover/focus (or while a chip menu is open): the circles morph into labeled agent pills and the count pill unfolds into the individual port pills with a slight stagger. Geometry (margins/max-widths) animates, so the strip expands and retracts instead of fading; 200ms in, 500ms out, reduced-motion respected. - Clicking the strip's empty area opens the workspace, same as the row. The expansion trigger is a new `details-expanded` Tailwind custom variant (item hover/focus-within, or an open menu inside the strip). The old collapse-state store, section toggle rows, and the two chip-row components are removed.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThis PR replaces the sidebar's toggle/store-driven ports and agents sections with an always-rendered details strip controlled by a new ChangesSidebar workspace details strip refactor
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
Ready to review this PR? Stage has broken it down into 7 individual chapters for you: Chapters generated by Stage for commit 3bbd4fc on Jul 3, 2026 5:40pm UTC. |
Greptile SummaryReplaces the experimental inline sidebar's per-workspace collapsible "N agents / N ports" toggle sections with a fixed-height activity strip that morphs between a compact facepile/count-pill cluster at rest and labeled interactive pills on hover using CSS geometry transitions and a new
Confidence Score: 4/5Safe to merge; changes are purely visual/UX with no data loss risk and the removed store only held ephemeral UI collapse state that is no longer needed. The redesign is well-executed: click-handling is safe (the strip renders outside the row's div[role=button] so no double-navigation), the details-expanded variant is correctly scoped, and dead code is cleanly removed. Two non-blocking concerns exist — the symmetric stagger delay on retract and the undocumented drop of the bulk-kill action for single-port workspaces — but neither breaks existing functionality. DashboardSidebarWorkspaceDetails.tsx warrants a second look around the stagger delay direction and the ports.length > 1 threshold for the Close all action.
|
| Filename | Overview |
|---|---|
| apps/desktop/src/renderer/globals.css | Adds @custom-variant details-expanded using :is() + :where() to trigger the strip's expanded state on workspace-item hover/focus or when a dropdown inside the strip is open; well-commented and correctly scoped. |
| apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/DashboardSidebarWorkspaceDetails.tsx | Core rewrite: replaces collapsible sections with a single animated strip. The stagger delay applies identically to expand and retract (no reverse), and the Close all ports action is now gated on ports.length > 1 rather than > 0, dropping coverage for single-port workspaces. |
| apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/components/DashboardSidebarWorkspaceAgentBadge/DashboardSidebarWorkspaceAgentBadge.tsx | Redesigned from a flat chip to a morphing circle-to-pill; icon is now always rendered with an absolute-positioned status dot overlay for non-idle agents, replacing the previous either-icon-or-indicator pattern. |
| apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx | Adds group/item class to the expanded content wrapper and threads handleClick down to the details strip so empty-area clicks navigate to the workspace; no double-invoke risk because children renders outside the inner div[role=button]. |
| apps/desktop/src/renderer/stores/workspace-details/store.ts | Deleted: the persisted collapsedSectionKeys store is no longer needed now that expand/collapse is driven by CSS hover state rather than explicit toggles. |
| apps/desktop/src/renderer/stores/inline-workspace-ports.ts | Comment-only update: removes the reference to DashboardSidebarWorkspacePortsRow and the workspace-details store from the migration guide, keeping it consistent with deleted files. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["group/item\n(DashboardSidebarWorkspaceItem div)"] -->|hover / focus-within| B["details-expanded variant\nactive"]
A -->|no hover| C["details-expanded variant\ninactive"]
B --> D["Agent badges\n-ml-1.5 open, label max-w-28\n200ms expand"]
B --> E["Port-count pill\nmax-w-0, opacity-0\n200ms collapse"]
B --> F["Port badge wrappers\nvisible, max-w-44\n200ms + 25ms stagger"]
B --> G["Close-all action\nvisible only when ports > 1\n200ms expand"]
C --> H["Agent badges\n-ml-1.5 overlap, label max-w-0\n500ms retract"]
C --> I["Port-count pill\nmax-w-14, opacity-100\n500ms expand"]
C --> J["Port badge wrappers\ninvisible, max-w-0\n500ms + stagger retract"]
K["group/details dropdown\n[data-state='open']"] -->|menu open| B
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A["group/item\n(DashboardSidebarWorkspaceItem div)"] -->|hover / focus-within| B["details-expanded variant\nactive"]
A -->|no hover| C["details-expanded variant\ninactive"]
B --> D["Agent badges\n-ml-1.5 open, label max-w-28\n200ms expand"]
B --> E["Port-count pill\nmax-w-0, opacity-0\n200ms collapse"]
B --> F["Port badge wrappers\nvisible, max-w-44\n200ms + 25ms stagger"]
B --> G["Close-all action\nvisible only when ports > 1\n200ms expand"]
C --> H["Agent badges\n-ml-1.5 overlap, label max-w-0\n500ms retract"]
C --> I["Port-count pill\nmax-w-14, opacity-100\n500ms expand"]
C --> J["Port badge wrappers\ninvisible, max-w-0\n500ms + stagger retract"]
K["group/details dropdown\n[data-state='open']"] -->|menu open| B
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/DashboardSidebarWorkspaceDetails.tsx:119-129
**Stagger delay is symmetric — last port lingers on retract**
The inline `transitionDelay` is the same value for both the expand and retract directions. On retract, port 0 starts collapsing immediately while port 7+ waits 200ms before its 500ms animation begins, so the strip takes up to 700ms to fully clear — noticeably longer than the 500ms base retract duration. Meanwhile the "Close all" action wrapper has no delay at all, so it disappears at t=500ms while later-indexed port pills are still mid-transition. If a symmetric stagger on retract was intentional, this is fine as-is; if the intent was stagger-on-expand only, each wrapper would need two separate delay values, or the delay could be conditionally applied via a CSS class.
### Issue 2 of 2
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/DashboardSidebarWorkspaceDetails.tsx:131-140
**"Close all ports" no longer accessible for a single open port**
Previously the bulk-kill action was included whenever `portCount > 0`; the new condition `ports.length > 1` means a workspace with exactly one forwarded port no longer gets the action in the expanded strip. This relies on `DashboardSidebarPortBadge` exposing a per-port kill control, which it does, but it's a silent reduction in discoverability. If the single-port case was intentionally excluded, a brief code comment explaining the `> 1` threshold would help future reviewers.
Reviews (1): Last reviewed commit: "feat(desktop): morph v2 sidebar inline a..." | Re-trigger Greptile
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/DashboardSidebarWorkspaceDetails.tsx (1)
7-9: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPrefer path aliases over deep relative imports.
Lines 4-6 in this same file already use the
renderer/...alias, but lines 7-9 use multi-level relative paths (../../../../providers/...,../../../DashboardSidebarPortsList/...).♻️ Suggested alias-based imports
-import { useDashboardSidebarWorkspacePorts } from "../../../../providers/DashboardSidebarPortsProvider"; -import { DashboardSidebarPortBadge } from "../../../DashboardSidebarPortsList/components/DashboardSidebarPortBadge"; -import { useDashboardSidebarPortKill } from "../../../DashboardSidebarPortsList/hooks/useDashboardSidebarPortKill"; +import { useDashboardSidebarWorkspacePorts } from "renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/providers/DashboardSidebarPortsProvider"; +import { DashboardSidebarPortBadge } from "renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarPortsList/components/DashboardSidebarPortBadge"; +import { useDashboardSidebarPortKill } from "renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarPortsList/hooks/useDashboardSidebarPortKill";As per path instructions, "When possible, use the path aliases defined in
tsconfig.jsoninstead of relative imports."🤖 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 `@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/DashboardSidebarWorkspaceDetails.tsx` around lines 7 - 9, The imports in DashboardSidebarWorkspaceDetails use deep relative paths instead of the existing renderer alias, so update the imports for useDashboardSidebarWorkspacePorts, DashboardSidebarPortBadge, and useDashboardSidebarPortKill to use the path aliases defined in tsconfig.json. Keep the same symbols and module targets, just replace the multi-level relative paths in DashboardSidebarWorkspaceDetails.tsx with alias-based imports for consistency.Source: Path instructions
🤖 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 `@apps/desktop/src/renderer/globals.css`:
- Around line 9-19: Stylelint is flagging the new Tailwind v4 `@custom-variant`
rule in globals.css even though the selector logic is correct. Update the
stylelint configuration to recognize or ignore `@custom-variant` as a valid
at-rule so the lint step passes, keeping the existing variant definition and its
workspace strip selectors unchanged.
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/DashboardSidebarWorkspaceDetails.tsx`:
- Around line 83-90: The strip click guard in DashboardSidebarWorkspaceDetails
should also ignore dropdown menu interactions, since DropdownMenuItem actions
can bubble into the row and trigger onClick(). Update the interactive-target
check in the row’s onClick handler to include menu items (for example, the
existing closest(...) guard should treat [role='menuitem'] as interactive) or
otherwise stop propagation from the dropdown menu content, using the
DashboardSidebarWorkspaceDetails component as the place to fix it.
---
Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/DashboardSidebarWorkspaceDetails.tsx`:
- Around line 7-9: The imports in DashboardSidebarWorkspaceDetails use deep
relative paths instead of the existing renderer alias, so update the imports for
useDashboardSidebarWorkspacePorts, DashboardSidebarPortBadge, and
useDashboardSidebarPortKill to use the path aliases defined in tsconfig.json.
Keep the same symbols and module targets, just replace the multi-level relative
paths in DashboardSidebarWorkspaceDetails.tsx with alias-based imports for
consistency.
🪄 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: b849da23-c7b6-4291-8853-98d5e09c7168
📒 Files selected for processing (17)
apps/desktop/src/renderer/globals.cssapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceAgentsRow/DashboardSidebarWorkspaceAgentsRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceAgentsRow/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/DashboardSidebarWorkspaceDetails.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/components/DashboardSidebarWorkspaceAgentBadge/DashboardSidebarWorkspaceAgentBadge.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/components/DashboardSidebarWorkspaceAgentBadge/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/components/DashboardSidebarWorkspaceDetailsToggle/DashboardSidebarWorkspaceDetailsToggle.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/components/DashboardSidebarWorkspaceDetailsToggle/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/hooks/useDashboardSidebarWorkspaceRunningAgents/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/hooks/useDashboardSidebarWorkspaceRunningAgents/useDashboardSidebarWorkspaceRunningAgents.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspacePortsRow/DashboardSidebarWorkspacePortsRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspacePortsRow/index.tsapps/desktop/src/renderer/stores/index.tsapps/desktop/src/renderer/stores/inline-workspace-ports.tsapps/desktop/src/renderer/stores/workspace-details/index.tsapps/desktop/src/renderer/stores/workspace-details/store.ts
💤 Files with no reviewable changes (9)
- apps/desktop/src/renderer/stores/workspace-details/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceAgentsRow/DashboardSidebarWorkspaceAgentsRow.tsx
- apps/desktop/src/renderer/stores/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceAgentsRow/index.ts
- apps/desktop/src/renderer/stores/workspace-details/store.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/components/DashboardSidebarWorkspaceDetailsToggle/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspacePortsRow/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspacePortsRow/DashboardSidebarWorkspacePortsRow.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/components/DashboardSidebarWorkspaceDetailsToggle/DashboardSidebarWorkspaceDetailsToggle.tsx
There was a problem hiding this comment.
3 issues found across 17 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
…only on expand - Radix dropdown items render in a portal, so their clicks bubble (React-wise) to the strip's onClick without any button in the DOM ancestry; selecting a port menu action also navigated to the workspace. Ignore targets outside the strip's DOM and role=menuitem targets. - Apply the port-pill stagger delay only in the expanded state (CSS var consumed under details-expanded), so retraction is one uniform motion instead of trailing up to ~200ms behind the close-all button.
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
Summary
prefers-reduced-motionrespected.Why / Context
With the experimental inline layout enabled, workspaces with a dev stack running showed two summary rows plus multiple wrapped rows of truncated chips each ("Notification…", "Caddy Elec…"), making the sidebar unscannable (~6 lines per workspace). The chips were also mostly redundant across workspaces — the at-rest signal is just "which agents, what status, how many ports". Detail is now one hover away instead of permanently expanded.
How It Works
DashboardSidebarWorkspaceDetailsis now a single scrolling row (fixed 22px) instead of two collapsible sections. Expansion is driven by a new Tailwind@custom-variant details-expanded(inglobals.css): active when the workspace item (group/item) is hovered or focus-within, or a dropdown inside the strip (group/details) is open — so the strip can't collapse under an open menu.DashboardSidebarWorkspaceAgentBadgeis one element in both states: an 18px facepile circle (preset icon +StatusIndicatordot) overlapping neighbors via negative margin at rest; on expand the margin opens and the label'smax-widthgrows, so the circle morphs into the pill. Clickable in both states (jumps to the agent's terminal).DashboardSidebarPortBadge, unchanged, shared with the bottom ports panel) sit in unfold wrappers whosemax-width/opacity/visibilityanimate from zero with a 25ms-per-item stagger (capped at 8), while the port-count pill collapses in the opposite direction. Collapsed content isvisibility: hidden, so it's not clickable/tabbable.DashboardSidebarWorkspacePortsRow,DashboardSidebarWorkspaceAgentsRow, and theworkspace-detailscollapse-state store (all dead with no toggles). The agent badge + running-agents hook moved underDashboardSidebarWorkspaceDetails/per co-location rules.Design reference: the spread-on-hover avatar stack pattern (shadcn.io Avatar Group / Animate UI Avatar Group). Implemented natively with CSS transitions instead of their framer-motion variants because our chips must morph into labeled, interactive pills and hold open while menus are up, which the CSS
:has-based variant handles without JS hover state.Manual QA Checklist
Validated live in the dev app over CDP (flags enabled in Settings → Experimental):
Testing
bun run typecheckbun run lintKnown Limitations
Summary by cubic
Replaced the inline sidebar’s per-workspace “N ports / N agents” sections with a single compact activity strip that expands on hover into labeled agent/port pills. This keeps each workspace to one line and makes the list easier to scan while keeping status and actions close.
New Features
details-expandedTailwind variant drives the expand state (group/itemhover/focus orgroup/details:has([data-state="open"])).Refactors
workspace-detailsstore; deletedDashboardSidebarWorkspaceAgentsRow,DashboardSidebarWorkspacePortsRow, and toggle component.DashboardSidebarWorkspaceDetails; agent badge now morphs from a facepile circle to a labeled pill; running-agents hook and badge co-located.group/itemto the workspace row andOverflowFadeContainerfor horizontal overflow; no dependency changes.Written for commit 3bbd4fc. Summary will update on new commits.
Summary by CodeRabbit
New Features
Bug Fixes