feature (desktop): Add a workspaces page and add close project ability #599
feature (desktop): Add a workspaces page and add close project ability #599
Conversation
- Add WorkspacesListView component with time-grouped workspaces - Make WorkspaceSidebarHeader clickable to navigate to workspaces list - Show both open and closed workspaces in the list - Allow opening closed worktrees from the list view - Add context menu to project headers with Close Project option - Add Open in Finder and Project Settings to project context menu 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Resolve conflict in WorkspaceSidebarHeader by adapting clickable header functionality to new folder structure with NewWorkspaceButton. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When viewing the workspaces list page, clicking a workspace in the sidebar now properly navigates to that workspace's terminal view. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds a new Workspaces list view and UI flows: extends TRPC workspaces data with Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as WorkspacesListView
participant Store as App State
participant TRPC as TRPC Client
participant Server
User->>UI: Open workspaces list
UI->>Store: read isWorkspacesListOpen
UI->>TRPC: query getAllGrouped
TRPC->>Server: getAllGrouped
Server-->>TRPC: grouped workspaces + mainRepoPath
TRPC-->>UI: workspaces data
UI->>TRPC: lazy getWorktreesByProject (per project)
TRPC->>Server: getWorktreesByProject
Server-->>TRPC: worktrees
TRPC-->>UI: worktrees data
UI->>UI: build WorkspaceItem[], group, filter, sort
UI-->>User: render grouped list
User->>UI: Click workspace row
alt isOpen
UI->>TRPC: setActiveWorkspace
TRPC->>Server: setActiveWorkspace
Server-->>TRPC: success
else closed
UI->>TRPC: openWorktree
TRPC->>Server: openWorktree
Server-->>TRPC: success
end
UI->>Store: closeWorkspacesList()
Store-->>UI: isWorkspacesListOpen = false
UI-->>User: list closes, workspace active
sequenceDiagram
participant User
participant Header as ProjectHeader
participant Menu as ContextMenu
participant TRPC as TRPC Client
participant Store as App State
User->>Header: Click header trigger
Header->>Menu: open
User->>Menu: Select "Open in Finder"
Menu->>TRPC: openInFinder(mainRepoPath)
TRPC->>Server: openInFinder
Server-->>TRPC: success/error
TRPC-->>Menu: result (show toast on error)
User->>Menu: Select "Project Settings"
Menu->>Store: open settings for projectId
User->>Menu: Select "Close Project"
Menu->>TRPC: closeProject(projectId)
TRPC->>Server: closeProject
Server-->>TRPC: success + terminalWarning
TRPC-->>Menu: success (invalidate cache, show toast)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
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 |
There was a problem hiding this comment.
Actionable comments posted: 1
Fix all issues with AI Agents 🤖
In
@apps/desktop/src/renderer/screens/main/components/WorkspacesListView/utils.ts:
- Around line 5-23: The getRelativeTime function uses multiple hardcoded time
thresholds; extract those magic numbers into named constants defined at the top
of the module (e.g., MS_PER_SECOND, MS_PER_MINUTE, MS_PER_HOUR, MS_PER_DAY and
human thresholds like MINUTES_IN_HOUR, HOURS_IN_DAY, DAYS_IN_WEEK,
DAYS_IN_MONTH, DAYS_IN_YEAR or named threshold constants such as
JUST_NOW_MINUTES, WEEK_DAYS_THRESHOLD, MONTH_DAYS_THRESHOLD,
YEAR_DAYS_THRESHOLD) and then replace the literal numbers in getRelativeTime
with these constants to improve readability and maintainability; ensure the
constants are exported or file-scoped at module top and used in calculations for
minutes/hours/days and in the conditional comparisons inside getRelativeTime.
🧹 Nitpick comments (5)
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx (1)
27-41: Add accessibility attributes to the toggle button.The button is missing
aria-expandedto indicate whether the workspaces list is open or closed, which helps screen reader users understand the current state. Consider adding:🔎 Proposed accessibility enhancement
<button type="button" onClick={handleClick} + aria-expanded={isWorkspacesListOpen} + aria-label="Toggle workspaces list" className={cn( "flex items-center gap-2 px-2 py-1.5 w-full rounded-md transition-colors", isWorkspacesListOpen ? "text-foreground bg-accent" : "text-muted-foreground hover:text-foreground hover:bg-accent/50", )} >apps/desktop/src/renderer/screens/main/components/WorkspacesListView/types.ts (1)
1-18: Consider a discriminated union for open vs. closed workspaces.The current design uses nullable
workspaceIdandworktreeIdfields, which requires consumers to perform null checks and understand which field is valid based on theisOpenflag. A discriminated union would provide better type safety:type OpenWorkspace = { status: "open"; workspaceId: string; worktreeId: null; // ... other common fields }; type ClosedWorkspace = { status: "closed"; workspaceId: null; worktreeId: string; // ... other common fields }; export type WorkspaceItem = (OpenWorkspace | ClosedWorkspace) & { uniqueId: string; projectId: string; // ... other common fields };This approach eliminates the need for runtime null checks and makes the relationship between
isOpen,workspaceId, andworktreeIdexplicit at the type level.apps/desktop/src/renderer/screens/main/components/WorkspacesListView/utils.ts (1)
21-21: Document approximate month calculation.The month calculation using
days / 30is an approximation and may be slightly inaccurate. Consider adding a comment to document this intentional approximation, or use a more precise calendar-based calculation if accuracy is important.📝 Example with documentation
+ // Note: Month calculation is approximate (30 days) for simplicity if (days < 365) return `${Math.floor(days / 30)} months ago`;apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx (1)
34-41: Effective lazy-loading pattern to avoid N+1 queries.Using hover state to trigger data fetching is a smart optimization. The enabled flag properly guards against unnecessary queries.
💡 Minor refinement: avoid empty string fallback
Consider passing
undefineddirectly instead of defaulting to an empty string, which better communicates the absence of a value:- { workspaceId: workspace.workspaceId ?? "" }, + { workspaceId: workspace.workspaceId ?? undefined },This is safe because the query is disabled when
workspaceIdis falsy.apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx (1)
30-34: Consider backend optimization for worktree fetching.The current implementation fetches worktrees for each project independently using
useQueries, which results in N parallel requests. While this works for typical desktop usage with a moderate number of projects, a single backend endpoint that returns all worktrees across projects would reduce network overhead and improve performance.This isn't critical for initial release but worth considering for future optimization, especially if users typically have many projects open.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
apps/desktop/src/lib/trpc/routers/workspaces/workspaces.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/index.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/index.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/types.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/utils.tsapps/desktop/src/renderer/screens/main/index.tsxapps/desktop/src/renderer/stores/app-state.ts
🧰 Additional context used
📓 Path-based instructions (6)
apps/desktop/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)
apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined insrc/lib/trpc
Use alias as defined intsconfig.jsonwhen possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from@trpc/server/observableinstead of async generators, as the library explicitly checksisObservable(result)and throws an error otherwise
Files:
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/index.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/lib/trpc/routers/workspaces/workspaces.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/utils.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/types.tsapps/desktop/src/renderer/stores/app-state.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsxapps/desktop/src/renderer/screens/main/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use object parameters for functions with 2+ parameters instead of positional arguments
Functions with 2+ parameters should accept a single params object with named properties for self-documentation and extensibility
Use prefixed console logging with context pattern: [domain/operation] message
Extract magic numbers and hardcoded values to named constants at module top
Use lookup objects/maps instead of repeated if (type === ...) conditionals
Avoid usinganytype - maintain type safety in TypeScript code
Never swallow errors silently - at minimum log them with context
Import from concrete files directly when possible - avoid barrel file abuse that creates circular dependencies
Avoid deep nesting (4+ levels) - use early returns, extract functions, and invert conditions
Use named properties in options objects instead of boolean parameters to avoid boolean blindness
Files:
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/index.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/lib/trpc/routers/workspaces/workspaces.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/utils.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/types.tsapps/desktop/src/renderer/stores/app-state.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsxapps/desktop/src/renderer/screens/main/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx
apps/desktop/src/renderer/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Never import Node.js modules (fs, path, os, net) in renderer process or shared code - they are externalized for browser compatibility
Files:
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/index.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/utils.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/types.tsapps/desktop/src/renderer/stores/app-state.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsxapps/desktop/src/renderer/screens/main/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx
apps/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use Drizzle ORM for all database operations - never use raw SQL
Files:
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/index.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/lib/trpc/routers/workspaces/workspaces.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/utils.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/types.tsapps/desktop/src/renderer/stores/app-state.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsxapps/desktop/src/renderer/screens/main/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use Biome for formatting and linting - run at root level with
bun run lint:fixorbiome check --write
Files:
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/index.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/lib/trpc/routers/workspaces/workspaces.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/utils.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/types.tsapps/desktop/src/renderer/stores/app-state.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsxapps/desktop/src/renderer/screens/main/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx
**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
One component per file - do not create multi-component files
Files:
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsxapps/desktop/src/renderer/screens/main/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx
🧠 Learnings (4)
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/renderer/**/*.{ts,tsx} : Never import Node.js modules (fs, path, os, net) in renderer process or shared code - they are externalized for browser compatibility
Applied to files:
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/index.ts
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/lib/*.ts : Never import Node.js modules in shared code like electron-router-dom.ts - it runs in both main and renderer processes
Applied to files:
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/index.ts
📚 Learning: 2025-12-21T04:39:28.543Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-12-21T04:39:28.543Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
Applied to files:
apps/desktop/src/renderer/stores/app-state.ts
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/*/src/components/{ui,ai-elements,react-flow}/*.tsx : Use kebab-case single files for shadcn/ui components (e.g., button.tsx, base-node.tsx) in src/components/ui/, src/components/ai-elements, and src/components/react-flow/
Applied to files:
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx
🧬 Code graph analysis (3)
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx (1)
apps/desktop/src/renderer/stores/app-state.ts (1)
useCloseWorkspacesList(104-105)
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx (4)
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/types.ts (2)
WorkspaceItem(1-18)ProjectGroup(20-24)apps/desktop/src/renderer/stores/app-state.ts (1)
useCloseWorkspacesList(104-105)packages/ui/src/lib/utils.ts (1)
cn(4-6)apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx (1)
WorkspaceRow(23-144)
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx (2)
apps/desktop/src/renderer/stores/app-state.ts (3)
useCurrentView(86-86)useOpenWorkspacesList(102-103)useCloseWorkspacesList(104-105)packages/ui/src/lib/utils.ts (1)
cn(4-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build
🔇 Additional comments (20)
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/index.ts (1)
1-1: LGTM!Standard barrel export pattern for component organization.
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/index.ts (1)
1-1: LGTM!Standard barrel export pattern for component organization.
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx (1)
76-76: LGTM!The integration of
closeWorkspacesList()on workspace activation provides good UX—when a user clicks a workspace, the list closes and reveals the terminal view. The inline comment clearly explains the intent.Also applies to: 125-126
apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts (1)
829-829: No changes needed—mainRepoPathis guaranteed non-null by database schema.The
mainRepoPathfield is constrained with.notNull()in the projects table schema (packages/local-db/src/schema/schema.ts), ensuring it is always defined. The return type correctly declares it as requiredstring. No null handling is necessary.apps/desktop/src/renderer/screens/main/index.tsx (1)
33-33: LGTM! Clean integration of the new workspaces list view.The WorkspacesListView import and conditional rendering follow the existing pattern for settings and tasks views. The routing logic is consistent and straightforward.
Also applies to: 299-301
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx (1)
34-34: LGTM! Correct prop threading.The mainRepoPath prop is properly threaded from the groups data to ProjectSection, enabling project-specific actions in the UI.
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx (1)
31-31: LGTM! Proper prop threading to ProjectHeader.The mainRepoPath prop is correctly added to the interface, destructured, and passed to ProjectHeader along with projectId, enabling the context menu actions mentioned in the PR objectives.
Also applies to: 41-41, 72-74
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/types.ts (2)
20-24: LGTM! Clean project grouping type.The ProjectGroup interface properly models the hierarchical structure of projects containing workspaces.
26-26: LGTM! Simple and clear filter mode type.apps/desktop/src/renderer/screens/main/components/WorkspacesListView/utils.ts (2)
17-17: Fix singular/plural inconsistency.Line 17 uses
${days} days agowhich produces grammatically incorrect output for 1 day (e.g., "1 days ago" instead of "1 day ago"). However, the special case at line 16 handlesdays === 1with "yesterday", so this is actually not an issue.
28-31: LGTM! Simple date formatting utility.The formatDate function provides a clean, concise date format. The hardcoded "en-US" locale is acceptable for this use case.
apps/desktop/src/renderer/stores/app-state.ts (1)
4-4: LGTM! Clean zustand state management implementation.The workspaces-list view state is properly integrated following the established patterns for settings and tasks. The implementation is consistent with zustand best practices and avoids unnecessary effects.
Also applies to: 19-19, 28-29, 38-38, 73-79, 102-105
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx (2)
13-13: Good practice: magic number extracted to named constant.The GITHUB_STATUS_STALE_TIME constant improves readability and makes the cache duration easy to adjust.
59-69: Good accessibility and UX handling.The button properly uses semantic HTML, handles disabled state during loading, and provides appropriate ARIA attributes.
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx (2)
34-46: Well-structured mutation with comprehensive cache invalidation.The closeProject mutation properly invalidates all related queries and provides user feedback for both success (terminal warnings) and errors.
65-102: Clean context menu implementation with proper UX.The menu provides clear actions with appropriate icons, disables items during pending operations, and uses semantic destructive styling for the close action.
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx (4)
51-105: Well-structured data aggregation with proper memoization.The useMemo correctly combines open workspaces and closed worktrees into a unified list, with appropriate dependency tracking and defensive coding (optional chaining, null checks).
108-130: Clean filtering logic with good separation of concerns.Filter mode and search query are applied in a separate memoized computation, making the logic easy to understand and maintain.
133-163: Efficient grouping and sorting implementation.The project grouping logic properly sorts both within groups (active first, then by recency) and across groups (by most recent activity). Using Map ensures efficient lookups during construction.
268-278: Thoughtful empty state messaging.The empty state provides context-specific messages based on the current filter and search state, improving UX.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx (1)
34-41: Consider a more explicit pattern for the workspaceId parameter.The query correctly uses
enabledto prevent execution whenworkspaceIdis falsy. However, providing an empty string fallback (?? "") on line 35 is unclear since the parameter won't actually be used when invalid. For better code clarity, consider one of these approaches:
- Use
undefinedas the fallback if the tRPC procedure accepts optional parameters- Add a comment explaining why the fallback is safe
- Restructure to make the disabled state more explicit in the types
This doesn't affect functionality but improves code maintainability.
🔎 Optional refactor for clarity
- const { data: githubStatus } = trpc.workspaces.getGitHubStatus.useQuery( - { workspaceId: workspace.workspaceId ?? "" }, - { - enabled: - hasHovered && workspace.type === "worktree" && !!workspace.workspaceId, - staleTime: GITHUB_STATUS_STALE_TIME, - }, - ); + const { data: githubStatus } = trpc.workspaces.getGitHubStatus.useQuery( + { workspaceId: workspace.workspaceId ?? "" }, // Safe: query disabled when workspaceId is falsy + { + enabled: + hasHovered && workspace.type === "worktree" && !!workspace.workspaceId, + staleTime: GITHUB_STATUS_STALE_TIME, + }, + );Or if the tRPC procedure type allows:
- const { data: githubStatus } = trpc.workspaces.getGitHubStatus.useQuery( - { workspaceId: workspace.workspaceId ?? "" }, + const { data: githubStatus } = trpc.workspaces.getGitHubStatus.useQuery( + { workspaceId: workspace.workspaceId! },
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/utils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/desktop/src/renderer/screens/main/components/WorkspacesListView/utils.ts
🧰 Additional context used
📓 Path-based instructions (6)
apps/desktop/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)
apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined insrc/lib/trpc
Use alias as defined intsconfig.jsonwhen possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from@trpc/server/observableinstead of async generators, as the library explicitly checksisObservable(result)and throws an error otherwise
Files:
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use object parameters for functions with 2+ parameters instead of positional arguments
Functions with 2+ parameters should accept a single params object with named properties for self-documentation and extensibility
Use prefixed console logging with context pattern: [domain/operation] message
Extract magic numbers and hardcoded values to named constants at module top
Use lookup objects/maps instead of repeated if (type === ...) conditionals
Avoid usinganytype - maintain type safety in TypeScript code
Never swallow errors silently - at minimum log them with context
Import from concrete files directly when possible - avoid barrel file abuse that creates circular dependencies
Avoid deep nesting (4+ levels) - use early returns, extract functions, and invert conditions
Use named properties in options objects instead of boolean parameters to avoid boolean blindness
Files:
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
apps/desktop/src/renderer/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Never import Node.js modules (fs, path, os, net) in renderer process or shared code - they are externalized for browser compatibility
Files:
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
One component per file - do not create multi-component files
Files:
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
apps/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use Drizzle ORM for all database operations - never use raw SQL
Files:
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use Biome for formatting and linting - run at root level with
bun run lint:fixorbiome check --write
Files:
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build
🔇 Additional comments (4)
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx (4)
1-13: LGTM! Imports and constant definition follow best practices.The imports are appropriate for the renderer process, use path aliases correctly, and the stale time constant is properly extracted at the module level.
15-21: LGTM! Type definitions are clean and follow object parameter guidelines.The interface properly types all props without using
any, and the component signature follows the object parameters pattern for functions with multiple parameters.
30-31: LGTM! Component logic is well-structured.The hover-based lazy loading pattern with
useStateis appropriate for this component-local feature. The derived values and click handler are clear and correct.Also applies to: 43-56
58-146: LGTM! JSX structure and accessibility are well-implemented.The button element is properly structured with:
- Semantic HTML (
type="button")- Appropriate accessibility (disabled state, text content provides accessible name)
- Clear conditional rendering and styling
- Readable Tailwind classes with the
cnutility- Proper React 19 patterns
The hover-based UI transitions and conditional indicators provide good UX feedback.
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
Description
Related Issues
Type of Change
Testing
Screenshots (if applicable)
Additional Notes
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.