Skip to content

feat(desktop): make Pinned and Sessions sidebar sections collapsible like Projects - #6407

Merged
Kitenite merged 1 commit into
mainfrom
collapsible-pinned-sessio
Aug 13, 2026
Merged

Kitenite merged 1 commit into
mainfrom
collapsible-pinned-sessio

Conversation

@Kitenite

@Kitenite Kitenite commented Aug 13, 2026 •

Copy link
Copy Markdown
Member

Summary

The Pinned and Sessions sidebar sections now collapse exactly like the Projects header: click the header strip to toggle, with the chevron cue that stays visible while collapsed (per the GH #6009 convention) so hidden rows read as hidden rather than missing. Collapse state persists per section.

Refactor

  • New shared DashboardSidebarSectionHeader renders the top-level section header strip (label, chevron, right-aligned action slot); DashboardSidebarWorkspacesHeader (Projects), DashboardSidebarPinnedSection, and DashboardSidebarSessionsSection all use it now.
  • sidebar-workspaces-collapse store generalized to sidebar-sections-collapse holding collapsed: { pinned, sessions, workspaces }. The legacy localStorage key is kept with a v0→v1 persist migration, so an already-collapsed projects list carries over; persisted-key registry updated.

Behavior notes

  • Collapsed sections hide their rows (and unregister their sortables), matching collapsed project groups — drops don't target a collapsed section.
  • The empty-Pinned drop zone still renders mid-drag regardless of collapse, so the first drag-to-pin always has a landing target.
  • Sessions rows stay hidden when either its own collapse or the global Projects-list collapse is active; the Sessions "+" now stops propagation so it doesn't toggle the header.

Verification

Typecheck, lint, and persisted-key registry tests pass. Verified end-to-end over CDP against this worktree's dev app with trusted input: hover→chevron, collapse/expand each section, both-collapsed → location.reload() → still collapsed (store: {"collapsed":{"pinned":true,"sessions":true,"workspaces":false},"version":1}), console.error hooked throughout — zero entries. 27s screen recording captured.


Summary by cubic

Makes Pinned and Sessions sidebar sections collapsible with the same UX as Projects. Previously only Projects collapsed; now each section toggles via its header, shows the chevron while collapsed, and remembers its own state.

  • Review notes

    • Introduces DashboardSidebarSectionHeader and uses it for Projects, Pinned, and Sessions.
    • Replaces useSidebarWorkspacesCollapseStore with useSidebarSectionsCollapseStore consumers.
    • Collapsing unmounts rows and unregisters sortables; drops cannot target a collapsed section. The empty Pinned drop zone still renders mid-drag so the first pin always has a target. The Sessions “+” stops event propagation to avoid toggling; Sessions rows also hide when Projects is collapsed.
  • Persistence/migration

    • Generalizes persisted state to { collapsed: { pinned, sessions, workspaces } } under the legacy sidebar-workspaces-collapse key with a v0→v1 migration that maps isCollapsed to collapsed.workspaces.
    • Persisted-key registry updated; no migration action required.

Written for commit fd96fc3. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added collapsible sidebar sections for pinned items, sessions, and workspaces.
    • Added consistent section headers with chevrons, optional actions, and keyboard accessibility.
    • Sidebar collapse preferences are now saved between sessions.
    • Preserved drop zones and section actions when content is collapsed or hidden.

…like Projects

Extract the Projects header's collapse pattern into a shared
DashboardSidebarSectionHeader (clickable strip, chevron that stays
visible while collapsed) and generalize the persisted collapse store to
cover pinned/sessions/workspaces, migrating the legacy
sidebar-workspaces-collapse key to the new shape.
@coderabbitai

coderabbitai Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The dashboard sidebar now uses one persisted Zustand store for pinned, sessions, and workspace collapse state. A shared accessible section header handles toggling. Section components hide their rows when collapsed while preserving actions and drop zones.

Changes

Sidebar collapse behavior

Layer / File(s) Summary
Persisted section collapse store
apps/desktop/src/renderer/stores/sidebar-sections-collapse.ts, apps/desktop/src/renderer/lib/persisted-keys/persisted-key-registry.test-data.ts
Adds persisted pinned, sessions, and workspaces state with legacy workspace-state migration.
Reusable section header
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarSectionHeader/*
Adds a shared header with click and keyboard toggling, state-dependent chevrons, and optional actions.
Sidebar section integration
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/*
Updates dashboard, pinned, sessions, and workspaces sections to use shared collapse state and header behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Mergeability Score: 🟡 Moderate · up to fd96f

The sidebar changes currently risk blocking keyboard and screen-reader access to section actions and can incorrectly hide Pinned and Sessions content, including the drag-to-pin target, when Projects is collapsed. These issues should be fixed before merging.

Possibly related PRs

Suggested reviewers: avipeltz

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant DashboardSidebarSectionHeader
  participant useSidebarSectionsCollapseStore
  participant SidebarSection
  User->>DashboardSidebarSectionHeader: Click or press Enter/Space
  DashboardSidebarSectionHeader->>useSidebarSectionsCollapseStore: Toggle section
  useSidebarSectionsCollapseStore-->>SidebarSection: Updated collapsed state
  SidebarSection-->>User: Show or hide section rows
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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 clearly and concisely describes the primary change: making the Pinned and Sessions sidebar sections collapsible like Projects.
Description check ✅ Passed The description covers the changes, behavior, migration, and testing; it omits the template checklist, but the core required content is present.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch collapsible-pinned-sessio

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 3

🤖 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/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarSectionHeader/DashboardSidebarSectionHeader.tsx`:
- Around line 31-54: The wrapper in DashboardSidebarSectionHeader must not
provide button semantics around the action children. Render the collapse
interaction as its own native button containing the label and chevron, add
aria-expanded reflecting isCollapsed, and keep children in a separate sibling
action area so DashboardSidebarSessionsSection and
DashboardSidebarWorkspacesHeader buttons remain independently accessible.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx`:
- Around line 136-137: Keep the sidebar section states independent in
DashboardSidebar: use workspacesListCollapsed only to control the Projects
sortable content, while always rendering the Pinned section when the sidebar is
expanded and determining Sessions row visibility from its own collapse state
rather than collapsed.workspaces. Preserve the empty Pinned drop zone during
workspace dragging.

In `@apps/desktop/src/renderer/stores/sidebar-sections-collapse.ts`:
- Around line 25-43: The persisted key configuration for sidebar-workspace
collapse must document its fixed-size bound, deletion owner or removal path, and
behavior when the feature is removed. Update the metadata around the
`sidebar-workspaces-collapse` entry accordingly, and move the key to `DEAD_KEYS`
when removing the feature.
🪄 Autofix

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 Plus

Run ID: 3fb5a62a-7dd8-4046-9b04-30450eea5663

📥 Commits

Reviewing files that changed from the base of the PR and between 3d20b49 and fd96fc3.

📒 Files selected for processing (9)
  • apps/desktop/src/renderer/lib/persisted-keys/persisted-key-registry.test-data.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarPinnedSection/DashboardSidebarPinnedSection.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarSectionHeader/DashboardSidebarSectionHeader.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarSectionHeader/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarSessionsSection/DashboardSidebarSessionsSection.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/DashboardSidebarWorkspacesHeader.tsx
  • apps/desktop/src/renderer/stores/sidebar-sections-collapse.ts
  • apps/desktop/src/renderer/stores/sidebar-workspaces-collapse.ts
💤 Files with no reviewable changes (1)
  • apps/desktop/src/renderer/stores/sidebar-workspaces-collapse.ts

Comment on lines +31 to +54
// biome-ignore lint/a11y/useSemanticElements: can't be a native <button> — it may nest action buttons
<div
role="button"
tabIndex={0}
onClick={() => toggle(section)}
onKeyDown={(event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
toggle(section);
}
}}
className="group flex min-h-8 w-full shrink-0 items-center gap-1.5 py-1.5 pl-4 pr-2 text-[10px] font-semibold uppercase tracking-[0.075em] text-muted-foreground transition-colors"
>
<span className="min-w-0 truncate text-left">{label}</span>
<HiChevronRight
className={cn(
"size-3 shrink-0 text-muted-foreground transition-[opacity,transform] duration-150",
isCollapsed
? "opacity-100"
: "rotate-90 opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100",
)}
/>
<div className="min-w-0 flex-1" />
{children}

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use a separate collapse trigger.

Line 33 gives the wrapper button semantics. DashboardSidebarSessionsSection and DashboardSidebarWorkspacesHeader pass native buttons as children. Descendants of an ARIA button can become presentational to assistive technology, so users may not be able to access the section actions.

Render the collapse control as its own <button>. Keep the action area as a sibling. Set aria-expanded on the collapse button.

🤖 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/DashboardSidebarSectionHeader/DashboardSidebarSectionHeader.tsx`
around lines 31 - 54, The wrapper in DashboardSidebarSectionHeader must not
provide button semantics around the action children. Render the collapse
interaction as its own native button containing the label and chevron, add
aria-expanded reflecting isCollapsed, and keep children in a separate sibling
action area so DashboardSidebarSessionsSection and
DashboardSidebarWorkspacesHeader buttons remain independently accessible.

Comment on lines +136 to +137
const workspacesListCollapsed = useSidebarSectionsCollapseStore(
(s) => s.collapsed.workspaces,

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep the three section states independent.

After Line 136 changes this selector to the Projects-only flag, the conditions at Lines 257-267 still hide the Pinned section and Sessions rows when Projects is collapsed. This also removes the empty Pinned drop zone during a workspace drag.

Always render Pinned in the expanded sidebar. Do not derive Sessions row visibility from collapsed.workspaces. Use this flag only for the Projects sortable content.

🤖 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/DashboardSidebar.tsx`
around lines 136 - 137, Keep the sidebar section states independent in
DashboardSidebar: use workspacesListCollapsed only to control the Projects
sortable content, while always rendering the Pinned section when the sidebar is
expanded and determining Sessions row visibility from its own collapse state
rather than collapsed.workspaces. Preserve the empty Pinned drop zone during
workspace dragging.

Comment on lines +25 to +43
{
// Legacy key: v0 held only the workspaces-list flag as
// { isCollapsed }, before Pinned/Sessions became collapsible.
name: "sidebar-workspaces-collapse",
version: 1,
migrate: (persisted, version) => {
if (version === 0) {
const state = persisted as { isCollapsed?: boolean };
return {
collapsed: {
pinned: false,
sessions: false,
workspaces: state.isCollapsed ?? false,
},
};
}
return persisted as SidebarSectionsCollapseState;
},
},

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the persisted key lifecycle.

This change expands sidebar-workspaces-collapse to store three section flags. Document its fixed-size bound, deletion owner or path, and the removal behavior. On feature removal, move the key to DEAD_KEYS.

As per coding guidelines: “For every renderer localStorage key, document its bound, deletion owner or path, and behavior when the feature is removed.”

🤖 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/stores/sidebar-sections-collapse.ts` around lines
25 - 43, The persisted key configuration for sidebar-workspace collapse must
document its fixed-size bound, deletion owner or removal path, and behavior when
the feature is removed. Update the metadata around the
`sidebar-workspaces-collapse` entry accordingly, and move the key to `DEAD_KEYS`
when removing the feature.

Source: Coding guidelines

@Kitenite

Copy link
Copy Markdown
Member Author

Reviewed the CodeRabbit findings — proceeding without changes:

  1. Wrapper role=button semantics: pre-existing pattern extracted verbatim from the Projects header (DashboardSidebarWorkspacesHeader), where the original biome-ignore comment documents the trade-off (can't be a native <button> because it nests action buttons). Not a regression introduced here; a11y restructure of all three headers is follow-up material.
  2. Section-state independence: hiding Pinned/Sessions rows when the global Projects-list collapse is active is the pre-existing, intended behavior on main (workspacesListCollapsed gating predates this PR — only the selector was renamed). No workspace drag can originate while everything is hidden, so the drop-zone concern doesn't apply.
  3. Persisted key lifecycle: fixed-size singleton (three booleans), registered in the persisted-key registry; noted for DEAD_KEYS on feature removal.

@Kitenite
Kitenite merged commit 668b6bf into main Aug 13, 2026
12 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch

Thank you for your contribution! 🎉

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