Skip to content

feat(desktop): redesign v2-workspaces as a sortable table#3660

Merged
Kitenite merged 2 commits into
mainfrom
please-help-me-clean-up-workspaces-view-use-linear-inspration-and-make-it-less-ugly-also-overflow-an
Apr 22, 2026
Merged

feat(desktop): redesign v2-workspaces as a sortable table#3660
Kitenite merged 2 commits into
mainfrom
please-help-me-clean-up-workspaces-view-use-linear-inspration-and-make-it-less-ugly-also-overflow-an

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Apr 22, 2026

Summary

  • Rebuilds the v2-workspaces discovery view as a full-width, Linear-style table instead of stacked card rows.
  • Columns (Sidebar · Name · Host · Branch · Created) align across all projects, each column header is a sort toggle, and rows truncate properly on long names/branches/hosts with title tooltips.
  • Row actions (Add to sidebar / Remove from sidebar) became icon-only buttons revealed on hover/focus, reclaiming horizontal space.
  • Still grouped by project with a tinted header band; dropped the prior "In your sidebar" / "Other workspaces" split (sidebar status is now a leftmost dot column) and the device subgroup headers (host is now a column).
  • Header is tightened to one line, sticky column header stays pinned while scrolling, empty state retains the sortable header.

Test plan

  • Open the /v2-workspaces route and verify projects render with aligned columns.
  • Try a very long workspace name, a long branch name, and a long host name — each should truncate with a tooltip and not push other columns.
  • Click each column header — rows re-sort within each project; click again to flip direction; sort indicator (▲/▼) moves.
  • Resize window narrow → wide: Created hides first, then Branch, then Host, leaving Name + Sidebar + action at the smallest width.
  • Toggle a workspace's sidebar state with the hover icon button; the leftmost dot should light up/off.
  • Filter by device + search — counts update, empty state shows with working "Clear filters" button.
  • Keyboard: Tab onto a row, press Enter or Space to open; focus ring is visible.
  • Verify host offline badge shows a muted dot and offline tooltip.

Summary by cubic

Redesigned the v2-workspaces view as a dense, Linear-style sortable table for faster scanning and consistent alignment, replacing stacked rows and the sidebar/others split. Improves accessibility, responsiveness, sticky column header, and row actions while keeping project grouping.

  • New Features

    • Full-width table with columns: Sidebar · Name · Host · Branch · Created; columns align across all projects.
    • Click headers to sort within each project; defaults: Created (desc), Name (asc), Host (asc), Branch (asc), Sidebar (desc), with clear indicators and ARIA labels.
    • Responsive columns: Created → Branch → Host hide as the viewport narrows; long text truncates with tooltips.
    • Hover/focus row actions are icon-only; sidebar status shown as a left dot; offline hosts show a muted dot with a tooltip.
    • Project groups retain a tinted header and counts; compact header with search and device filter; empty state keeps the sortable header and “Clear filters”.
  • Bug Fixes

    • Sidebar sort now orders in-sidebar items first when descending.
    • Row keyboard handling ignores Enter/Space from focused action buttons.
    • Offline host cell uses the shared tooltip for consistent, keyboard-accessible status.

Written for commit 2e27732. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Clickable sortable column headers for workspace list (sort by sidebar, name, host, branch, or creation date)
    • Unified workspace list grouping with newest projects shown first
  • UI Improvements

    • Streamlined header row with compact search ("Search workspaces…") and fixed-width input
    • Grid-style workspace rows with inline host/device status (offline marker + tooltip)
    • Actions converted to icon-only buttons with tooltips for a cleaner layout

…able

Replace stacked card rows with a dense full-width table: Sidebar / Name /
Host / Branch / Created columns that align across projects, sortable
column headers, proper truncation on long workspace and host names, and
hover-revealed row actions.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 22, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d42b49e1-0cd1-476e-9b11-b77dbc6bc9ef

📥 Commits

Reviewing files that changed from the base of the PR and between cd53f0c and 2e27732.

📒 Files selected for processing (5)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/V2WorkspacesList.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/SortableHeader/SortableHeader.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/SortableHeader/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/V2WorkspaceRow.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/types.ts
✅ Files skipped from review due to trivial changes (3)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/types.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/SortableHeader/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/V2WorkspaceRow.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/V2WorkspacesList.tsx

📝 Walkthrough

Walkthrough

The V2 workspaces UI was refactored: header compacted into a single bordered row; workspaces data unified into a single array with client-side sorting and project grouping; rows moved to a grid-based list; a separate device-badge component was removed and its logic inlined.

Changes

Cohort / File(s) Summary
List & Sorting
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/V2WorkspacesList.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/constants.ts, apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/types.ts
Consolidates pinned/others into a single workspaces prop. Adds in-memory sorting (new SortField/SortDirection), project grouping by latestCreatedAt, deterministic workspace ordering (hostTypeRank, compareWorkspaces), and exports V2_WORKSPACES_ROW_GRID.
Sortable Header
.../components/SortableHeader/SortableHeader.tsx, .../SortableHeader/index.ts
New SortableHeader component and barrel export; computes active sort state, chevron icon, aria-label, and calls onSort(field).
Row & Row Layout
.../components/V2WorkspaceRow/V2WorkspaceRow.tsx
Replaces Superset Item/Badge layout with <li> + grid button structure. Removes showProjectName prop, inlines host icon selection (hostIconFor), adds treatAsOffline logic, keyboard handling tweaks, and icon-only ghost action buttons with tooltips.
Header UI
.../components/V2WorkspacesHeader/V2WorkspacesHeader.tsx
Header refactored from stacked column to single bordered row; title styling reduced; controls compacted and search container fixed to w-72 with placeholder changed.
Device Badge Removal
.../components/V2WorkspaceRow/components/V2WorkspaceDeviceBadge/V2WorkspaceDeviceBadge.tsx, .../index.ts
Deleted V2WorkspaceDeviceBadge component and its re-export; device/icon/offline logic migrated into row component.
Page Integration
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/page.tsx
Now passes workspaces={all} from useAccessibleV2Workspaces instead of separate pinned/others.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Header as V2WorkspacesHeader
    participant List as V2WorkspacesList
    participant Row as V2WorkspaceRow

    User->>Header: Type search / toggle device filter
    Header->>List: Provide search/filter params
    User->>Header: Click sortable column
    Header->>List: Update sortField/sortDirection
    List->>List: Sort workspaces (hostTypeRank, compareWorkspaces) & group by project
    List->>Row: Render each workspace in grid row
    Row->>Row: Determine host icon & offline state
    Row-->>User: Display workspace row with metadata and actions
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A twitch and a hop through rows now aligned,
Columns that sort and projects combined,
Badges folded in, icons inline,
A tidy little header, compact and fine,
I nibble a carrot and celebrate this design! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.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 'feat(desktop): redesign v2-workspaces as a sortable table' clearly and concisely describes the main change: converting the v2-workspaces view into a table format with sorting capability.
Description check ✅ Passed The PR description is comprehensive, covering the summary of changes, test plan with detailed steps, and additional context. All required template sections are present and well-filled, providing clear information about the redesign scope and validation approach.
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.

✏️ 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 please-help-me-clean-up-workspaces-view-use-linear-inspration-and-make-it-less-ugly-also-overflow-an

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.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 22, 2026

Greptile Summary

This PR replaces the v2-workspaces discovery view's stacked card layout with a full-width sortable table, bringing a Linear-style design with aligned columns (Sidebar · Name · Host · Branch · Created), responsive column hiding, per-column sort toggles, tinted project-group headers, and hover-only icon buttons for sidebar actions.

Key changes:

  • V2WorkspacesList: Adds client-side sorting (compareWorkspaces/groupByProject) with per-field default directions, a sticky SortableHeader row, and a unified workspaces prop (previously split into pinned/others).
  • V2WorkspaceRow: Migrated from <Item> card component to <li> + inner div[role=button], using the shared V2_WORKSPACES_ROW_GRID constant for column alignment; sidebar action buttons are now icon-only with <Tooltip> labels revealed on hover/focus.
  • constants.ts (new): Shared Tailwind grid-cols string ensures the sticky header and every data row are column-aligned across all breakpoints.
  • V2WorkspaceDeviceBadge (deleted): Host type icon and offline indicator are now rendered inline in the row.
  • V2WorkspacesHeader: Condensed to a single tight line; subtitle description removed; search bar narrowed to fixed w-72.
  • page.tsx: Consumes all from useAccessibleV2Workspaces instead of destructuring pinned/others.

One P1 issue found: the sidebar column's sort comparator and its default direction are mismatched, causing non-sidebar items to appear at the top when the Sidebar column is first clicked.

Confidence Score: 4/5

Safe to merge after fixing the sidebar sort-direction inversion; all other changes are clean UI improvements.

The overall redesign is well-structured — shared grid constant, responsive breakpoints, accessible keyboard navigation, and proper ARIA attributes are all handled correctly. One concrete P1 logic bug exists: the sidebar comparator formula (b - a) and its DEFAULT_DIRECTION_BY_FIELD 'desc' are mismatched, so the first click on the Sidebar column header surfaces non-sidebar items at the top. This is a noticeable UX inversion for a primary sorting affordance but does not affect data integrity or break navigation. The P2 offline tooltip regression is minor. Everything else looks solid.

V2WorkspacesList.tsx — sidebar sort direction logic around line 83 needs a one-line fix.

Important Files Changed

Filename Overview
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/V2WorkspacesList.tsx Core list rewrite — adds client-side sorting and responsive column header. Contains a P1 sort-direction inversion for the Sidebar column that causes non-sidebar items to appear first on first click.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/V2WorkspaceRow.tsx Row redesigned from card (Item) to table row (li + div[role=button]); icon-only sidebar buttons with Tooltip; offline status regressed from Tooltip to native title attribute.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesHeader/V2WorkspacesHeader.tsx Header condensed to single line; removes subtitle description; search bar narrowed to fixed w-72; device filter toggle preserved; clean, no issues.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/constants.ts New shared grid-template constant used by both column header and workspace rows to ensure column alignment. Clean approach.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/page.tsx Updated to consume all from the hook instead of pinned/others; trivial and correct.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/components/V2WorkspaceDeviceBadge/V2WorkspaceDeviceBadge.tsx Deleted — host badge functionality moved inline into V2WorkspaceRow. Offline tooltip was a Tooltip component; now only a title attribute (see row comment).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[page.tsx] -->|all workspaces| B[V2WorkspacesList]
    A -->|counts| C[V2WorkspacesHeader]

    B --> D{totalCount = 0?}
    D -->|yes| E[Empty state with sticky header]
    D -->|no| F[ScrollArea]

    F --> G[Sticky SortableHeader row]
    G -->|onSort callback| H[handleSort updates sortField and sortDirection]
    H --> I[useMemo projectGroups - filter then groupByProject then sort]
    I --> F

    F --> J[project group header band]
    J --> K[ul of V2WorkspaceRow]

    K --> L1[Sidebar dot indicator]
    K --> L2[Name truncated with title]
    K --> L3[Host icon and name - hidden below md]
    K --> L4[Branch mono - hidden below lg]
    K --> L5[Created time - hidden below xl]
    K --> L6[Add or Remove sidebar icon button with Tooltip]
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/V2WorkspacesList.tsx
Line: 83-84

Comment:
**Sidebar sort direction is inverted on first click**

The comparator `Number(b.isInSidebar) - Number(a.isInSidebar)` is already "descending" by construction — it returns a negative value when `a` is in the sidebar, meaning sidebar items sort first when `direction === "asc"`. However, the default direction for this field is `"desc"`, which negates the result and therefore puts **non-sidebar items first** the first time a user clicks the Sidebar column header.

Step through the math with `a.isInSidebar = true`, `b.isInSidebar = false`:
- `cmp = Number(false) - Number(true) = 0 - 1 = -1`
- `direction === "desc"``return -cmp = 1``b` sorts before `a` → non-sidebar first ✗

Either flip the default direction to `"asc"` for the sidebar field:

```suggestion
const DEFAULT_DIRECTION_BY_FIELD: Record<SortField, SortDirection> = {
	sidebar: "asc",
	name: "asc",
	host: "asc",
	branch: "asc",
	created: "desc",
};
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/V2WorkspaceRow.tsx
Line: 131-146

Comment:
**Offline tooltip regressed to native browser title**

The old `V2WorkspaceDeviceBadge` showed a styled `<Tooltip>` ("Host is offline") for offline remote hosts. The new code drops to a plain `title` attribute on the host `<span>`. The `title` text is only exposed on mouse hover (not keyboard focus) and renders as an unstyled OS tooltip, which doesn't match the rest of the UI — especially now that the sidebar-action buttons use the `<Tooltip>` component right next to it. Consider wrapping the offline host cell in a `<Tooltip>` similar to the button tooltips when `treatAsOffline` is true.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat(desktop): redesign v2-workspaces li..." | Re-trigger Greptile

Comment on lines +83 to +84
case "sidebar":
cmp = Number(b.isInSidebar) - Number(a.isInSidebar);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Sidebar sort direction is inverted on first click

The comparator Number(b.isInSidebar) - Number(a.isInSidebar) is already "descending" by construction — it returns a negative value when a is in the sidebar, meaning sidebar items sort first when direction === "asc". However, the default direction for this field is "desc", which negates the result and therefore puts non-sidebar items first the first time a user clicks the Sidebar column header.

Step through the math with a.isInSidebar = true, b.isInSidebar = false:

  • cmp = Number(false) - Number(true) = 0 - 1 = -1
  • direction === "desc"return -cmp = 1b sorts before a → non-sidebar first ✗

Either flip the default direction to "asc" for the sidebar field:

Suggested change
case "sidebar":
cmp = Number(b.isInSidebar) - Number(a.isInSidebar);
const DEFAULT_DIRECTION_BY_FIELD: Record<SortField, SortDirection> = {
sidebar: "asc",
name: "asc",
host: "asc",
branch: "asc",
created: "desc",
};
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/V2WorkspacesList.tsx
Line: 83-84

Comment:
**Sidebar sort direction is inverted on first click**

The comparator `Number(b.isInSidebar) - Number(a.isInSidebar)` is already "descending" by construction — it returns a negative value when `a` is in the sidebar, meaning sidebar items sort first when `direction === "asc"`. However, the default direction for this field is `"desc"`, which negates the result and therefore puts **non-sidebar items first** the first time a user clicks the Sidebar column header.

Step through the math with `a.isInSidebar = true`, `b.isInSidebar = false`:
- `cmp = Number(false) - Number(true) = 0 - 1 = -1`
- `direction === "desc"``return -cmp = 1``b` sorts before `a` → non-sidebar first ✗

Either flip the default direction to `"asc"` for the sidebar field:

```suggestion
const DEFAULT_DIRECTION_BY_FIELD: Record<SortField, SortDirection> = {
	sidebar: "asc",
	name: "asc",
	host: "asc",
	branch: "asc",
	created: "desc",
};
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +131 to +146
<span
className={cn(
"hidden min-w-0 items-center gap-1.5 text-xs text-muted-foreground md:flex",
treatAsOffline && "text-muted-foreground/60",
)}
title={`${workspace.hostName}${treatAsOffline ? " (offline)" : ""}`}
>
<HostIcon className="size-3 shrink-0" />
<span className="min-w-0 truncate">{workspace.hostName}</span>
{treatAsOffline ? (
<span
aria-hidden
className="inline-block size-1.5 shrink-0 rounded-full bg-muted-foreground/40"
/>
) : null}
</span>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Offline tooltip regressed to native browser title

The old V2WorkspaceDeviceBadge showed a styled <Tooltip> ("Host is offline") for offline remote hosts. The new code drops to a plain title attribute on the host <span>. The title text is only exposed on mouse hover (not keyboard focus) and renders as an unstyled OS tooltip, which doesn't match the rest of the UI — especially now that the sidebar-action buttons use the <Tooltip> component right next to it. Consider wrapping the offline host cell in a <Tooltip> similar to the button tooltips when treatAsOffline is true.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/V2WorkspaceRow.tsx
Line: 131-146

Comment:
**Offline tooltip regressed to native browser title**

The old `V2WorkspaceDeviceBadge` showed a styled `<Tooltip>` ("Host is offline") for offline remote hosts. The new code drops to a plain `title` attribute on the host `<span>`. The `title` text is only exposed on mouse hover (not keyboard focus) and renders as an unstyled OS tooltip, which doesn't match the rest of the UI — especially now that the sidebar-action buttons use the `<Tooltip>` component right next to it. Consider wrapping the offline host cell in a `<Tooltip>` similar to the button tooltips when `treatAsOffline` is true.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/V2WorkspaceRow.tsx (1)

82-90: ⚠️ Potential issue | 🟠 Major

Prevent nested button keyboard events from opening the row.

Keyboard events (Enter/Space) from focused Add/Remove buttons bubble to the parent row's onKeyDown handler, causing unwanted navigation. The nested buttons' click handlers use stopPropagation(), but this only prevents click event bubbling, not keyboard event bubbling. Ignore child-originated keyboard events before calling handleOpen.

🐛 Proposed fix
 const handleRowKeyDown = useCallback(
 	(event: React.KeyboardEvent<HTMLDivElement>) => {
+		if (event.target !== event.currentTarget) {
+			return;
+		}
 		if (event.key === "Enter" || event.key === " ") {
 			event.preventDefault();
 			handleOpen();
 		}
 	},
 	[handleOpen],
 );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/V2WorkspaceRow.tsx`
around lines 82 - 90, The row's onKeyDown handler (handleRowKeyDown) should
ignore keyboard events that originate from nested interactive elements so
Enter/Space on child Add/Remove buttons don't trigger handleOpen; modify
handleRowKeyDown to early-return when the event target is not the row itself
(e.g., if (event.target !== event.currentTarget) return;) or when (event.target
as HTMLElement).closest('button') is truthy, then only proceed to call
handleOpen for genuine row-level key presses.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/V2WorkspacesList.tsx`:
- Around line 150-205: Extract the SortableHeader component into its own
co-located component folder: create SortableHeader/SortableHeader.tsx containing
the SortableHeader function and its SortableHeaderProps interface, export it as
the default export, and add an index.ts barrel exporting default; then remove
the SortableHeader declaration from the V2WorkspacesList file and replace it
with an import of SortableHeader (e.g. import SortableHeader from
'./SortableHeader'), keeping all used symbols (LuChevronsUpDown, LuChevronUp,
LuChevronDown, cn, SortField, SortDirection) and prop usage identical so
behavior is unchanged.
- Around line 83-85: The sidebar comparator is inverted: replace the current
line cmp = Number(b.isInSidebar) - Number(a.isInSidebar); with cmp =
Number(a.isInSidebar) - Number(b.isInSidebar); (or an equivalent boolean
comparator that yields a.isInSidebar before b.isInSidebar) in the case "sidebar"
block inside V2WorkspacesList (and apply the same change to the other sidebar
comparator occurrence later in the file) so that descending sort places
workspaces that are in the sidebar first.

---

Outside diff comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/V2WorkspaceRow.tsx`:
- Around line 82-90: The row's onKeyDown handler (handleRowKeyDown) should
ignore keyboard events that originate from nested interactive elements so
Enter/Space on child Add/Remove buttons don't trigger handleOpen; modify
handleRowKeyDown to early-return when the event target is not the row itself
(e.g., if (event.target !== event.currentTarget) return;) or when (event.target
as HTMLElement).closest('button') is truthy, then only proceed to call
handleOpen for genuine row-level key presses.
🪄 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: b2085635-b061-453c-88d3-777d34a5cd08

📥 Commits

Reviewing files that changed from the base of the PR and between 57aa28c and cd53f0c.

📒 Files selected for processing (7)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesHeader/V2WorkspacesHeader.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/V2WorkspacesList.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/V2WorkspaceRow.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/components/V2WorkspaceDeviceBadge/V2WorkspaceDeviceBadge.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/components/V2WorkspaceDeviceBadge/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/constants.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/page.tsx
💤 Files with no reviewable changes (2)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/components/V2WorkspaceDeviceBadge/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/components/V2WorkspaceDeviceBadge/V2WorkspaceDeviceBadge.tsx

Comment on lines +150 to +205
interface SortableHeaderProps {
field: SortField;
label: string;
align?: "start" | "center";
className?: string;
sortField: SortField;
sortDirection: SortDirection;
onSort: (field: SortField) => void;
srOnlyLabel?: boolean;
}

export function V2WorkspacesList({ pinned, others }: V2WorkspacesListProps) {
function SortableHeader({
field,
label,
align = "start",
className,
sortField,
sortDirection,
onSort,
srOnlyLabel = false,
}: SortableHeaderProps) {
const isActive = sortField === field;
const Icon = !isActive
? LuChevronsUpDown
: sortDirection === "asc"
? LuChevronUp
: LuChevronDown;
const sortLabel = isActive
? sortDirection === "asc"
? "ascending"
: "descending"
: "not sorted";

return (
<button
type="button"
onClick={() => onSort(field)}
aria-label={`Sort by ${label}, currently ${sortLabel}`}
className={cn(
"group flex min-w-0 items-center gap-1 rounded outline-none transition-colors",
"hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/40",
align === "center" && "justify-center",
isActive && "text-foreground",
className,
)}
>
<span className={cn("truncate", srOnlyLabel && "sr-only")}>{label}</span>
<Icon
className={cn(
"size-3 shrink-0 transition-opacity",
isActive ? "opacity-100" : "opacity-0 group-hover:opacity-60",
)}
/>
</button>
);
}
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.

🛠️ Refactor suggestion | 🟠 Major

Move SortableHeader into its own component file.

This file now defines both SortableHeader and V2WorkspacesList; please split the header into a co-located SortableHeader/SortableHeader.tsx with an index.ts barrel. As per coding guidelines, "**/*.{tsx,ts}: Do not create multi-component files; use one component per file." Also: "**/*.tsx: Use one folder per component with structure: ComponentName/ComponentName.tsx + index.ts for barrel export."

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

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/V2WorkspacesList.tsx`
around lines 150 - 205, Extract the SortableHeader component into its own
co-located component folder: create SortableHeader/SortableHeader.tsx containing
the SortableHeader function and its SortableHeaderProps interface, export it as
the default export, and add an index.ts barrel exporting default; then remove
the SortableHeader declaration from the V2WorkspacesList file and replace it
with an import of SortableHeader (e.g. import SortableHeader from
'./SortableHeader'), keeping all used symbols (LuChevronsUpDown, LuChevronUp,
LuChevronDown, cn, SortField, SortDirection) and prop usage identical so
behavior is unchanged.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 7 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/V2WorkspacesList.tsx">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/V2WorkspacesList.tsx:84">
P2: Sidebar sort direction is inverted because the comparator is pre-reversed before the shared asc/desc inversion.</violation>
</file>

<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/V2WorkspaceRow.tsx">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/V2WorkspaceRow.tsx:136">
P2: The offline host indicator regressed from a styled `<Tooltip>` (as used by the adjacent sidebar buttons) to a plain `title` attribute. Native `title` tooltips don't appear on keyboard focus and look inconsistent with the rest of the row. Wrap the offline host cell in a `<Tooltip>` when `treatAsOffline` is true, matching the pattern already used for the action buttons.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

- Sidebar column sort direction was inverted (default "desc" put non-sidebar
  items first); use a normal boolean comparator so descending means "in
  sidebar first".
- Row onKeyDown was firing when Enter/Space bubbled from focused action
  buttons; ignore keyboard events originating from descendants.
- Offline host cell regressed to a native `title` tooltip; wrap it in the
  shared `<Tooltip>` so the indicator is keyboard-accessible and styled
  consistently with the action buttons.
- Extract SortableHeader into its own folder per repo conventions
  (one component per file) and move shared sort types into a types module.
@Kitenite Kitenite merged commit de7a42f into main Apr 22, 2026
7 checks passed
@Kitenite Kitenite deleted the please-help-me-clean-up-workspaces-view-use-linear-inspration-and-make-it-less-ugly-also-overflow-an branch April 22, 2026 22:40
@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