Skip to content

tabs mangement interaction#105

Merged
Kitenite merged 21 commits intomainfrom
ui-rewrite
Nov 20, 2025
Merged

tabs mangement interaction#105
Kitenite merged 21 commits intomainfrom
ui-rewrite

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Nov 19, 2025

Summary by CodeRabbit

  • New Features

    • Workspace-centric UX: create, switch, reorder, and remove workspaces; Add Workspace action.
    • Workspace view with sidebar and content area: single-tab, grouped mosaic, and empty-tab screens.
    • "Changes" section with "Coming soon..." placeholder.
  • Enhancements

    • Richer drag-and-drop: grouping, ungrouping, reordering, and in-content drop targets.
    • Dark mosaic theme and visual refinements.
  • Refactor

    • App navigation/state moved from tab-centric to workspace-centric.
  • Removed

    • Legacy "Add Tab" UI element.
  • Tests

    • New unit tests covering tab/group drag-and-drop and layout logic.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 19, 2025

Walkthrough

Replace the flat tab model with a workspace-centered architecture: remove legacy TabView and single-file tabs store, add a Workspaces store and WorkspaceView, introduce per-workspace tab modules (types/store/drag-logic), DnD hooks and sidebar helpers, mosaic-based group rendering, many UI component renames, and numerous import/order-only edits.

Changes

Cohort / File(s) Summary
Stores: workspaces added, legacy flat tabs removed
apps/desktop/src/renderer/stores/workspaces.ts, apps/desktop/src/renderer/stores/index.ts, apps/desktop/src/renderer/stores/tabs.ts (deleted)
Add Workspace type and useWorkspacesStore; remove legacy single-file tabs.ts; export workspaces from stores index.
Modular tabs package (per-workspace)
apps/desktop/src/renderer/stores/tabs/*
.../index.ts, .../store.ts, .../types.ts, .../utils.ts, .../drag-logic.ts, .../drag-logic.test.ts
New per-workspace tab model (TabType Single/Group), Zustand tab store and helpers, pure drag-to-tab logic, utilities and unit tests.
TopBar: tabs → workspaces UI
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/*, apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/AddWorkspaceButton.tsx, apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/AddTabButton.tsx (deleted)
Replace Tabs with WorkspacesTabs, rename TabItem → WorkspaceItem, switch store usage to useWorkspacesStore, add AddWorkspaceButton, remove old AddTabButton.
Main view: TabView → WorkspaceView
apps/desktop/src/renderer/screens/main/components/TabView/* (deleted), apps/desktop/src/renderer/screens/main/components/WorkspaceView/*, apps/desktop/src/renderer/screens/main/index.tsx, apps/desktop/src/renderer/screens/main/index.ts
Delete TabView/CenterView; add WorkspaceView, wire into MainScreen, remove MainScreen re-export from index.
Workspace UI: content & sidebar split
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/*, .../Sidebar/*, .../NewWorkspaceView.tsx
Add ContentView (renders ChangesContent or TabsContent), ChangesContent, ChangesView; rename NewTabViewNewWorkspaceView; Sidebar selects between ChangesView and TabsView.
Tab content: mosaic + DnD
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/*
Add TabsContent orchestrator, EmptyTabView, SingleTabView, GroupTabView (react-mosaic integration), mosaic-theme.css, drag types, and useDropTabTarget hook.
Tab sidebar: items & DnD hooks
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/*
Add TabsView, sidebar TabItem with nested groups and keyboard access, TabItem types, useDragTab, useGroupDrop hooks, and UngroupDropZone drop helper.
Tab store helpers: pure drag logic
apps/desktop/src/renderer/stores/tabs/drag-logic.ts
Pure transformations for dragTabToTab (grouping, reparenting, layout cleaning) and layout helpers (cleanLayout, removeTabFromLayout).
Tests & styles
apps/desktop/src/renderer/stores/tabs/drag-logic.test.ts, .../GroupTabView.test.tsx, .../mosaic-theme.css
Add unit tests for drag/layout behavior and a dark mosaic CSS theme.
Deleted / renamed small components & exports
apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/AddTabButton.tsx (deleted), apps/desktop/src/renderer/screens/main/components/TabView/CenterView.tsx (deleted), apps/desktop/src/renderer/screens/main/index.ts (MainScreen re-export removed)
Remove obsolete components and adjust exports to workspace-centric names.
Many import/order-only edits
multiple files under apps/old-desktop/..., apps/website/..., packages/ui/src/components/*
Numerous import reorders, type/export ordering tweaks, and cosmetic edits with no behavior changes.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant WorkspaceView
    participant WorkspacesStore
    participant Sidebar
    participant ContentView
    participant TabsContent

    User->>WorkspaceView: mount
    WorkspaceView->>WorkspacesStore: read workspaces, activeWorkspaceId
    alt activeWorkspace?.isNew
        WorkspaceView->>WorkspaceView: render NewWorkspaceView
    else
        WorkspaceView->>Sidebar: render Sidebar
        WorkspaceView->>ContentView: render ContentView
        ContentView->>WorkspacesStore: read sidebar mode
        alt mode = Changes
            ContentView->>ContentView: render ChangesContent
        else
            ContentView->>TabsContent: render TabsContent
            TabsContent->>TabsContent: determine tabToRender
            alt no active tab
                TabsContent->>TabsContent: render EmptyTabView
            else tab.type = Single
                TabsContent->>TabsContent: render SingleTabView (drop target)
            else
                TabsContent->>TabsContent: render GroupTabView (mosaic)
            end
        end
    end
Loading
sequenceDiagram
    participant Dragger as User (drag)
    participant TabItem
    participant DnD as DragDropEngine
    participant TabsStore

    Dragger->>TabItem: start drag
    TabItem->>DnD: emit DragItem (tabId)
    Dragger->>TabItem: drop onto TabContent (active)
    TabContent->>DnD: drop -> canDrop/isOver true
    DnD->>TabsStore: dragTabToTab(draggedId, targetId)
    TabsStore->>TabsStore: handleDragTabToTab rules (attach/group/reorder)
    TabsStore-->>TabItem: state updated
    TabItem-->>Dragger: UI updates
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Points to prioritize during review:

  • stores/tabs/store.ts and stores/tabs/types.ts: per-workspace invariants (activeTabIds, tabHistoryStacks), API surface and edge cases.
  • stores/tabs/drag-logic.ts and its tests: correctness of grouping/reparenting rules and layout sanitization.
  • DnD wiring and hooks: useDragTab, useDropTabTarget, useGroupDrop, UngroupDropZone and their integration with UI components.
  • GroupTabView (mosaic) rendering and accessibility, plus mosaic-theme.css focus/outlines.
  • Removed files/exports (AddTabButton, CenterView, MainScreen re-export) — ensure no dangling imports remain.

Possibly related PRs

  • desktop app ui rewrite  #104 — directly overlaps on tab UI and tabs store modularization (drag/drop and tab types).
  • use ui state #103 — related store reorganization and renderer Zustand store changes (tabs ↔ workspaces).
  • workspace #71 — implements workspace feature changes touching workspaces store and workspace-centric UI.

Poem

🐰 I hopped through workspaces, nudged a tab with delight,

I nudged and I grouped till the panes felt just right,
I cleaned up layouts in the cool code-night,
New stores, new views — my tiny paws took flight,
A carrot, a plus, and a brighter UI light.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ⚠️ Warning The PR title 'tabs mangement interaction' is vague and does not clearly summarize the extensive changes described in the PR objectives, which include workspace management, storage orchestration, CLI commands, and a desktop UI rewrite. Revise the title to reflect the primary change. Consider: 'Refactor tabs and workspaces management with new store architecture' or 'Desktop UI rewrite: migrate tab management to workspace-based architecture'.
Description check ❓ Inconclusive The PR description lists multiple high-level feature/fix topics (add change types, orchestrator, CLI, persistence, desktop app, UI rewrite, package upgrades) but lacks detail on the primary objective, rationale, and impact specific to this changeset. Expand the description with a clear summary of the main architectural shift (tabs to workspaces), rationale for the rewrite, key components affected, and any breaking changes or migration notes needed for reviewers.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ui-rewrite

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 0

🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/AddTabButton.tsx (1)

13-13: Margin removal looks good; consider omitting the empty className prop.

The removal of top margin is appropriate for the layout adjustment. The button component will render correctly with an empty className.

For slightly cleaner code, you can omit the className prop entirely when it's empty:

 		<Button
 			variant="ghost"
 			size="icon"
 			onClick={addTab}
 			aria-label="Add new tab"
-			className=""
 		>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e695f3 and 120a516.

📒 Files selected for processing (2)
  • apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/ModeHeader.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/AddTabButton.tsx (1 hunks)
🔇 Additional comments (1)
apps/desktop/src/renderer/screens/main/components/TabView/Sidebar/ModeCarousel/ModeHeader.tsx (1)

11-11: LGTM! Typography improvement enhances readability.

The text size increase from text-xs to text-sm is appropriate for a header component and improves visual hierarchy.

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: 1

🧹 Nitpick comments (7)
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/AddWorkspaceButton.tsx (1)

1-18: Minor cleanup and store subscription optimization for AddWorkspaceButton

Behavior is fine, but you can avoid unnecessary re-renders and simplify props:

  • Select just the addWorkspace action so this button doesn’t re-render whenever other workspace state changes.
  • Drop the empty className.

For example:

-import { useWorkspacesStore } from "renderer/stores/workspaces";
+import { useWorkspacesStore } from "renderer/stores/workspaces";

 export function AddWorkspaceButton() {
-  const { addWorkspace } = useWorkspacesStore();
+  const addWorkspace = useWorkspacesStore((state) => state.addWorkspace);

   return (
     <Button
       variant="ghost"
       size="icon"
       onClick={addWorkspace}
       aria-label="Add new workspace"
-      className=""
     >
       <span className="text-lg">+</span>
     </Button>
   );
 }
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent.tsx (1)

1-18: ChangesContent placeholder layout is straightforward and fine

The centered placeholder with background/scroll behavior looks good as a stub. Since TabsContent uses the same structure, you could later extract a small shared “placeholder content” component that just takes title/description to avoid duplication, but it’s not urgent.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent.tsx (1)

1-18: TabsContent placeholder mirrors ChangesContent cleanly

This is a fine placeholder implementation. Given it’s structurally identical to ChangesContent aside from labels, you could later factor both through a tiny shared component that takes title and subtitle props, if you want to keep things DRY as more modes are added.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx (1)

6-25: Clarify lifecycle of isNew so workspaces don’t stay permanently in NewWorkspaceView

WorkspaceView sends any workspace with isNew === true into NewWorkspaceView indefinitely. From the provided code, it’s not obvious where markWorkspaceAsUsed is ever called, so a workspace created via addWorkspace might never transition to the normal Sidebar + ContentView layout.

If that’s not intentional, consider:

  • Calling markWorkspaceAsUsed when the user takes an action in NewWorkspaceView, or
  • Clearing isNew when a workspace is first activated.

Otherwise new workspaces may be stuck in the onboarding view.

apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/index.tsx (1)

7-61: Width/hover logic is solid; consider guarding against workspaces.length === 0

The dynamic width calculation, hover-aware separator opacity, and scroll fade effects all look good and should behave well as workspaces are added/removed/reordered.

One small robustness tweak: in updateWorkspaceWidth, you divide by workspaces.length. With the current store implementation you never end up with 0 workspaces, but if that invariant ever changes (or via devtools time-travel), availableWidth / 0 would yield Infinity before clamping. An early return like:

if (workspaces.length === 0) return;

would make this component safer against future changes without affecting current behavior.

Also applies to: 74-121

apps/desktop/src/renderer/stores/workspaces.ts (1)

21-89: Workspace store behavior is coherent; add a defensive check in removeWorkspace

The state shape and actions align well with the new workspace UX (default “Home”, ensuring at least one workspace, picking a sensible next active on close, etc.).

One edge case: in removeWorkspace, if an unknown id were ever passed, findIndex would return -1, so:

const closedIndex = state.workspaces.findIndex((workspace) => workspace.id === id);
const nextWorkspace = workspaces[closedIndex] || workspaces[closedIndex - 1];
return { workspaces, activeWorkspaceId: nextWorkspace.id };

would try to access nextWorkspace.id where nextWorkspace is undefined.

It can’t happen with the current UI, but adding a guard like:

if (id === state.activeWorkspaceId) {
  const closedIndex = state.workspaces.findIndex((workspace) => workspace.id === id);
  if (closedIndex === -1) {
    return { workspaces, activeWorkspaceId: workspaces[0]?.id ?? null };
  }
  // existing logic...
}

would make this reducer safer against future callers or devtools manipulation.

apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx (1)

5-30: Workspace store wiring is consistent; consider narrowing the selector for perf

The mapping to useWorkspacesStore actions (setActiveWorkspace, removeWorkspace, reorderWorkspaces) matches the store API and looks correct. One improvement: useWorkspacesStore() without a selector will re-render this component on any workspace state change; since this component only needs the actions, consider:

const { setActiveWorkspace, removeWorkspace, reorderWorkspaces } =
  useWorkspacesStore((state) => ({
    setActiveWorkspace: state.setActiveWorkspace,
    removeWorkspace: state.removeWorkspace,
    reorderWorkspaces: state.reorderWorkspaces,
  }));

to avoid unnecessary re-renders when the workspace list or active id changes elsewhere.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 120a516 and 6dc7c34.

📒 Files selected for processing (23)
  • apps/desktop/src/renderer/routes.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/TabView/CenterView.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/TabView/index.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/AddTabButton.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/AddWorkspaceButton.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx (5 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/index.tsx (4 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/NewWorkspaceView.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeContent.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeHeader.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/index.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/index.tsx (2 hunks)
  • apps/desktop/src/renderer/stores/index.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs.ts (0 hunks)
  • apps/desktop/src/renderer/stores/workspaces.ts (1 hunks)
💤 Files with no reviewable changes (6)
  • apps/desktop/src/renderer/screens/main/index.ts
  • apps/desktop/src/renderer/screens/main/components/TabView/CenterView.tsx
  • apps/desktop/src/renderer/stores/tabs.ts
  • apps/desktop/src/renderer/routes.tsx
  • apps/desktop/src/renderer/screens/main/components/TabView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/AddTabButton.tsx
🧰 Additional context used
🧬 Code graph analysis (8)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/index.tsx (3)
apps/desktop/src/renderer/stores/sidebar-state.ts (1)
  • useSidebarStore (17-37)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent.tsx (1)
  • ChangesContent (1-18)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent.tsx (1)
  • TabsContent (1-18)
apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx (1)
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/index.tsx (1)
  • WorkspacesTabs (11-123)
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/AddWorkspaceButton.tsx (1)
apps/desktop/src/renderer/stores/workspaces.ts (1)
  • useWorkspacesStore (27-90)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx (4)
apps/desktop/src/renderer/stores/workspaces.ts (1)
  • useWorkspacesStore (27-90)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/NewWorkspaceView.tsx (1)
  • NewWorkspaceView (1-50)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx (1)
  • Sidebar (8-55)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/index.tsx (1)
  • ContentView (5-13)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView.tsx (1)
  • ChangesView (1-7)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView.tsx (1)
  • TabsView (1-20)
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/index.tsx (3)
apps/desktop/src/renderer/stores/workspaces.ts (1)
  • useWorkspacesStore (27-90)
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx (1)
  • WorkspaceItem (19-103)
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/AddWorkspaceButton.tsx (1)
  • AddWorkspaceButton (4-18)
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx (2)
apps/desktop/src/renderer/stores/workspaces.ts (1)
  • useWorkspacesStore (27-90)
packages/ui/src/lib/utils.ts (1)
  • cn (4-6)
apps/desktop/src/renderer/screens/main/index.tsx (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx (1)
  • WorkspaceView (6-26)
🔇 Additional comments (12)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView.tsx (1)

1-7: Placeholder ChangesView looks fine for now

Stateless placeholder is simple and consistent; nothing blocking here. Just remember to replace this with the real changes UI when it’s ready.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/NewWorkspaceView.tsx (1)

1-7: Rename to NewWorkspaceView and updated heading are consistent

The component name and H1 text now align with the workspace terminology; no issues from this change.

apps/desktop/src/renderer/screens/main/index.tsx (1)

5-16: MainScreen now correctly renders WorkspaceView

Wiring WorkspaceView into the main layout in place of the old tab view is straightforward and keeps the existing DnD and frame structure intact. Looks good.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeContent.tsx (1)

2-2: Colocating SidebarMode types is fine

Updating the SidebarMode import to ./types keeps the typing local to this ModeCarousel group and doesn’t affect runtime behavior.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeHeader.tsx (1)

11-13: Mode header text size change is safe

Bumping the mode label to text-sm is a simple visual adjustment and doesn’t affect behavior.

apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx (1)

3-21: TopBar now correctly hosts WorkspacesTabs

Replacing the old Tabs with WorkspacesTabs keeps the existing top-bar structure intact while aligning with the new workspace model. The no-drag region and overflow handling around the tabs are preserved correctly.

apps/desktop/src/renderer/stores/index.ts (1)

7-8: Barrel export updated correctly to include workspaces

Re-exporting from "./workspaces" alongside "./sidebar-state" keeps store imports centralized and matches the new workspace-centric architecture; no issues here.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx (1)

4-6: Sidebar mode switching between TabsView and ChangesView looks correct

Using SidebarMode.Changes for the branch and defaulting to TabsView for other modes keeps sidebar content aligned with the store’s currentMode; I don’t see behavioral issues with this wiring.

Also applies to: 45-50

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/index.tsx (1)

1-13: ContentView’s mode-based switch is consistent with the sidebar

Returning ChangesContent only when currentMode === SidebarMode.Changes and TabsContent otherwise keeps the main content pane in sync with the sidebar mode; implementation is clear and adequate for the two current modes.

apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx (3)

31-50: Drag-and-drop configuration for workspace reordering looks sound

The DnD setup uses a dedicated WORKSPACE_TYPE, passes id and index in the drag item, and updates order via reorderWorkspaces(item.index, index) while mutating item.index to prevent redundant calls. This is a standard, correct pattern for index-based reordering with react-dnd.


57-83: Active workspace behavior and main button interactions are correct

The active-state styling, drag handle ref (drag(drop(node))), and onClick={() => setActiveWorkspace(id)} correctly align the visual selection with store state, while preserving hover callbacks. The opacity change on isDragging is a clear feedback mechanism and doesn’t interfere with click or drag behavior.


85-100: Close button interaction and accessibility are handled well

Stopping propagation before removeWorkspace(id) ensures closing doesn’t also activate the workspace, and the aria-label="Close workspace" provides a clear description for assistive tech. The hover/active opacity behavior tied to group-hover is a reasonable UX pattern for a secondary control.

Comment on lines +1 to +20
export function TabsView() {
return (
<nav className="space-y-2">
<div className="text-sm text-sidebar-foreground">
<p className="font-medium mb-2">Navigation</p>
<ul className="space-y-1">
<li className="px-3 py-2 rounded-md hover:bg-sidebar-accent cursor-pointer">
Dashboard
</li>
<li className="px-3 py-2 rounded-md hover:bg-sidebar-accent cursor-pointer">
Projects
</li>
<li className="px-3 py-2 rounded-md hover:bg-sidebar-accent cursor-pointer">
Settings
</li>
</ul>
</div>
</nav>
);
}
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.

⚠️ Potential issue | 🟡 Minor

List items are styled as interactive but not actually clickable or accessible

<li> elements with cursor-pointer and hover styles but no onClick or semantic role will confuse users and assistive tech. If this is more than a pure visual mock, consider making them real buttons/links:

- <li className="px-3 py-2 rounded-md hover:bg-sidebar-accent cursor-pointer">
-   Dashboard
- </li>
+ <li>
+   <button
+     type="button"
+     className="w-full text-left px-3 py-2 rounded-md hover:bg-sidebar-accent cursor-pointer"
+     // onClick={...}
+   >
+     Dashboard
+   </button>
+ </li>

Same pattern can apply to the other items.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function TabsView() {
return (
<nav className="space-y-2">
<div className="text-sm text-sidebar-foreground">
<p className="font-medium mb-2">Navigation</p>
<ul className="space-y-1">
<li className="px-3 py-2 rounded-md hover:bg-sidebar-accent cursor-pointer">
Dashboard
</li>
<li className="px-3 py-2 rounded-md hover:bg-sidebar-accent cursor-pointer">
Projects
</li>
<li className="px-3 py-2 rounded-md hover:bg-sidebar-accent cursor-pointer">
Settings
</li>
</ul>
</div>
</nav>
);
}
export function TabsView() {
return (
<nav className="space-y-2">
<div className="text-sm text-sidebar-foreground">
<p className="font-medium mb-2">Navigation</p>
<ul className="space-y-1">
<li>
<button
type="button"
className="w-full text-left px-3 py-2 rounded-md hover:bg-sidebar-accent cursor-pointer"
// onClick={...}
>
Dashboard
</button>
</li>
<li>
<button
type="button"
className="w-full text-left px-3 py-2 rounded-md hover:bg-sidebar-accent cursor-pointer"
// onClick={...}
>
Projects
</button>
</li>
<li>
<button
type="button"
className="w-full text-left px-3 py-2 rounded-md hover:bg-sidebar-accent cursor-pointer"
// onClick={...}
>
Settings
</button>
</li>
</ul>
</div>
</nav>
);
}
🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView.tsx
around lines 1 to 20, the <li> items are visually interactive (cursor-pointer,
hover styles) but lack semantic interactivity and keyboard/assistive support;
replace each purely-styled <li> with a focusable semantic control (preferably a
<button> or an <a> if it navigates) or wrap the content in a <button>, move the
interactive classes to that control, add an onClick handler (or href) for the
action, ensure keyboard activation (Enter/Space) is handled if using a
non-native element, include appropriate ARIA attributes/labels if needed, and
keep visible focus styles so the items are accessible to keyboard and assistive
technologies.

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: 5

Caution

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

⚠️ Outside diff range comments (3)
apps/old-desktop/src/renderer/screens/main/components/PlanView/PlanView.tsx (1)

178-224: Critical: Undefined variables passed to KanbanColumn components.

Multiple KanbanColumn components reference undefined variables onTabSelect and onTabCreated (lines 178-179, 189-190, 200-201, 212-213, 223-224). These variables are not defined in the component scope—the correct variables are handleTabSelect and handleTabCreated, which are destructured from useTabContext() on lines 18-19.

This will cause runtime errors when the child components attempt to invoke these undefined callbacks.

Apply this diff to fix all occurrences:

 					onTaskClick={setViewingTask}
 					statusColor="bg-neutral-500"
 					currentWorkspace={currentWorkspace}
 					selectedWorktreeId={selectedWorktreeId}
-					onTabSelect={onTabSelect}
-					onTabCreated={onTabCreated}
+					onTabSelect={handleTabSelect}
+					onTabCreated={handleTabCreated}
 					onUpdateTask={handleUpdateTask}
 				/>

Repeat this fix for all five KanbanColumn instances (lines 171-225).

apps/old-desktop/src/main/lib/desktop-stores.ts (1)

52-62: Error handling silently swallows initialization failures.

The catch block only logs errors without re-throwing or providing any failure indication to callers. If storage directory creation or migration fails, the application continues with potentially inconsistent state.

Consider one of these approaches:

 private async initializeAsync(): Promise<void> {
-  try {
-    // Ensure storage directories exist first
-    await ensureDesktopStorageDirs();
-
-    // Then run migration if needed
-    await this.runMigrationIfNeeded();
-  } catch (error) {
-    console.error("Failed to initialize Desktop stores:", error);
-  }
+  // Ensure storage directories exist first
+  await ensureDesktopStorageDirs();
+
+  // Then run migration if needed
+  await this.runMigrationIfNeeded();
 }

Or, if you need to handle errors gracefully:

 private async initializeAsync(): Promise<void> {
   try {
     // Ensure storage directories exist first
     await ensureDesktopStorageDirs();

     // Then run migration if needed
     await this.runMigrationIfNeeded();
   } catch (error) {
     console.error("Failed to initialize Desktop stores:", error);
+    throw new Error(`Desktop store initialization failed: ${error}`);
   }
 }
apps/old-desktop/src/renderer/screens/main/hooks/useTasks.ts (1)

171-216: Use a ref to guard modal-open state check after async operations

The if (isAddTaskModalOpen) check at line 195 captures state value at handler invocation time. After setIsCreatingWorktree(false) (line 188), the modal can be closed by the user, but the stale closure still sees the old true value. If the user closes the modal during the 50ms delay or before this check runs, the code will still call setSelectedWorktreeId and handleTabSelect despite the modal being closed—violating the "only if modal is still open" intent.

Track modal state in a ref (kept in sync via useEffect) and check that ref instead of the render-time state value, matching the pattern already used for isHandlingProgressRef.

♻️ Duplicate comments (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx (1)

26-57: tab.panes usage depends on fixing TabGroup shape

This component assumes TabGroup has a panes map keyed by the mosaic leaf IDs. Right now TabGroup in tabs/types.ts doesn’t declare panes, and its comment says layout uses tab IDs. Once you reconcile the TabGroup model there (see comment in apps/desktop/src/renderer/stores/tabs/types.ts), update this component to match the final shape.

apps/desktop/src/renderer/stores/tabs/drag-logic.ts (1)

40-115: Group creation/join paths rely on the incomplete TabGroup model

Both the “join existing group” and “create new group” rules construct TabGroup objects with a panes map but don’t set childTabIds, while TabGroup in tabs/types.ts currently doesn’t declare panes and does require childTabIds.

Once you fix the TabGroup shape in tabs/types.ts, make sure these branches:

  • Build layout using the agreed leaf IDs (pane or tab).
  • Populate all required fields on TabGroup (including any panes/child relationship fields you settle on).

Otherwise these branches will keep returning structurally inconsistent group tabs.

🧹 Nitpick comments (14)
apps/old-desktop/src/main/lib/storage/index.ts (1)

1-5: Export order tweak is fine and keeps the storage barrel coherent

Reordering to export ./types after ./orchestrators doesn’t change the public surface, and it aligns with the orchestrators index ordering. No issues from a correctness or API perspective assuming these modules are side‑effect free.

apps/old-desktop/src/main/lib/storage/orchestrators/index.ts (1)

1-4: Barrel export reordering and deduplication look correct

Moving change-orchestrator to the top and workspace-orchestrator to the end, while keeping environment/process in place, preserves the exported symbols and removes the duplicate change-orchestrator re-export. This is a safe, cleanliness-only change.

apps/old-desktop/src/renderer/screens/main/utils.ts (1)

2-2: Import cleanup is correct; consider promoting formatRelativeTime to a shared util.

The deduped, top-level import looks good and keeps this file tidy. If formatRelativeTime is (or becomes) widely used beyond AddTaskModal, consider moving it into a more generic shared/date‑utils module to avoid coupling main/utils to a specific layout component directory.

apps/old-desktop/src/main/lib/workspace-ipcs.ts (1)

15-15: Standardize worktreeManager import pattern.

The code uses both static import (line 15) and dynamic imports (lines 36, 242, 797, 870) for worktreeManager. This mixed pattern creates confusion about which approach to follow. Consider either:

  1. Use only the static import (line 15) throughout the file, or
  2. Remove the static import and use dynamic imports consistently everywhere

The static import is currently used directly at lines 487, 541, 580, 634, 676, and 738, while dynamic imports appear in IPC handlers at lines 36, 242, 797, and 870.

Also applies to: 36-36, 242-242, 797-797, 870-870

apps/old-desktop/src/main/lib/types/cli-types.ts (2)

17-25: Clarify whether AgentType and ProcessType have runtime values

You’re exporting AgentType and ProcessType twice with different forms:

  • As types: export type { AgentType, ProcessType } from "@superset/cli/types/process";
  • As values: export { AgentType, ProcessType } from "@superset/cli/types/process";

This is only valid if @superset/cli/types/process actually exposes runtime symbols named AgentType and ProcessType (e.g., enums or as const objects). If they are type-only (aliases/interfaces), the value export will fail under isolatedModules / emit.

If these are intended to be types only, consider removing the value re-export; if both type + runtime are intended, a short comment here explaining the dual export would help future readers.

If they are type-only, you can simplify to:

-export type {
-  Agent,
-  AgentType,
-  Process,
-  ProcessOrchestrator,
-  ProcessType,
-  Terminal,
-} from "@superset/cli/types/process";
-export { AgentType, ProcessType } from "@superset/cli/types/process";
+export type {
+  Agent,
+  AgentType,
+  Process,
+  ProcessOrchestrator,
+  ProcessType,
+  Terminal,
+} from "@superset/cli/types/process";

26-32: Same concern for WorkspaceType type vs value export

Here you similarly export WorkspaceType as both a type and a value:

  • export type { ... WorkspaceType } from "@superset/cli/types/workspace";
  • export { WorkspaceType } from "@superset/cli/types/workspace";

If WorkspaceType is type-only, the value export will cause issues; if it is an enum/constant, dual export is fine but a bit surprising in a “types” façade.

Also, you might not need both lines if consumers only ever use one of type or value forms. Consider either:

  • Keeping both but adding a brief comment that WorkspaceType is both a type and runtime enum; or
  • Dropping the value export if it isn’t actually used as a runtime symbol by the desktop app.

A possible simplification if WorkspaceType is type-only:

-export type {
-  LocalWorkspace,
-  Workspace,
-  WorkspaceOrchestrator,
-  WorkspaceType,
-} from "@superset/cli/types/workspace";
-export { WorkspaceType } from "@superset/cli/types/workspace";
+export type {
+  LocalWorkspace,
+  Workspace,
+  WorkspaceOrchestrator,
+  WorkspaceType,
+} from "@superset/cli/types/workspace";
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx (1)

13-13: Address the type assertion pattern.

The as unknown as React.Ref<HTMLDivElement> double type assertion is a code smell that typically indicates a type mismatch. This pattern bypasses TypeScript's type safety and could hide underlying type issues.

Consider addressing the type incompatibility properly. The useDrop hook from react-dnd returns a connector function that should work with ref callbacks. You might be able to fix this by:

  1. Using a ref callback pattern:
+	const setDropRef = (el: HTMLDivElement | null) => {
+		drop(el);
+	};
+
 	return (
 		<div
-			ref={drop as unknown as React.Ref<HTMLDivElement>}
+			ref={setDropRef}
  1. Or checking if the react-dnd types need to be updated to properly align with React's ref types.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (1)

93-104: Add a clarifying comment in TabItem explaining the single-level nesting design.

The one-level nesting constraint is intentional and well-documented in the shared types file as "cannot contain nested group tabs". However, the TabItem component doesn't explain why childTabs={[]} is hardcoded in the recursive call. Adding a brief comment linking to this architectural decision would improve code clarity for future maintainers:

// Intentional: Group tabs can only have direct children; nested groups are not supported
// See apps/desktop/src/shared/types.ts for the architectural constraint
<TabItem tab={childTab} isActive={isActive} childTabs={[]} />

This prevents future developers from mistakenly trying to pass actual childTabs to recursive children or questioning whether this is an oversight.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts (1)

1-14: Duplicate DragItem/TAB_DND_TYPE definitions should be centralized

DragItem and TAB_DND_TYPE are also defined in TabsContent/types.ts. Consider moving these to a shared DnD types module and importing from there to avoid future divergence.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx (1)

18-23: Avoid noisy console.log in layout change handler

handleLayoutChange logs on every mosaic change, which will get spammy in normal use. Once persistence is wired, drop the log or guard it behind a dev flag.

apps/desktop/src/renderer/stores/tabs/types.ts (1)

3-7: Consider renaming this TabType to avoid confusion with shared TabType

There’s already a TabType string union in apps/desktop/src/shared/types.ts. Introducing another TabType enum here increases the chance of importing the wrong one. Consider a more specific name (e.g. WorkspaceTabType) or re‑exporting the shared type instead if they are meant to represent the same concept.

apps/old-desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/WorktreeItemArborist.tsx (2)

153-209: Drag handler blocks moves within the same parent

handleMove returns early whenever sourceParentTabId === targetParentTabId, which prevents any drag that keeps a tab under the same parent (e.g., reordering siblings) from reaching the "tab-move" IPC call. If you intend to support intra-group reordering, this guard should also take the original vs target index into account, or be removed so the backend can decide whether a no-op move is needed.


257-279: Ungroup operation ignores IPC failures and can leave inconsistent state

In handleUngroupTab, each "tab-move" and the final "tab-delete" IPC call are awaited but their results are ignored, unlike handleMove/handleTabRemove/handleTabRename which check result.success and log errors. If any of these calls fails, you may end up with some children detached while the group still exists (or vice versa) without feedback to the user.

Consider mirroring the pattern used in handleMove:

  • Wrap the loop and delete in a try/catch.
  • Check result.success for each IPC call and bail or show an error dialog if something goes wrong, instead of always calling onReload().
apps/desktop/src/renderer/stores/tabs/store.ts (1)

41-99: Initial test tabs use a different group shape than dynamically created tabs

createInitialTabs constructs a group tab with childTabIds and a layout that refers directly to child tab IDs, but doesn’t define the same structure that createNewTab produces for group tabs (which includes its own internal layout and pane model, per utils.ts). That means group tabs seeded here may not behave the same way as ones created at runtime, which can lead to subtle bugs in any code that assumes a uniform Tab/group shape.

Consider either:

  • Building the example group via createNewTab and then adjusting it, or
  • Manually mirroring the fields that createNewTab sets for a group tab so both code paths produce the same structure.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6dc7c34 and 5cc8e7d.

📒 Files selected for processing (71)
  • apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/EmptyTabView.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/mosaic-theme.css (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/types.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useDropTabTarget.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeContent.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeNavigation.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/index.tsx (2 hunks)
  • apps/desktop/src/renderer/stores/index.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/drag-logic.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/index.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/store.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/types.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/utils.ts (1 hunks)
  • apps/old-desktop/src/main/index.ts (1 hunks)
  • apps/old-desktop/src/main/lib/desktop-stores.ts (1 hunks)
  • apps/old-desktop/src/main/lib/migration/migrator.ts (1 hunks)
  • apps/old-desktop/src/main/lib/storage/index.ts (1 hunks)
  • apps/old-desktop/src/main/lib/storage/lowdb-adapter.ts (1 hunks)
  • apps/old-desktop/src/main/lib/storage/orchestrators/index.ts (1 hunks)
  • apps/old-desktop/src/main/lib/types/cli-types.ts (1 hunks)
  • apps/old-desktop/src/main/lib/ui-store/index.ts (1 hunks)
  • apps/old-desktop/src/main/lib/ui-store/store.ts (1 hunks)
  • apps/old-desktop/src/main/lib/ui-store/types.ts (1 hunks)
  • apps/old-desktop/src/main/lib/workspace-composition/composer.ts (1 hunks)
  • apps/old-desktop/src/main/lib/workspace-composition/index.ts (1 hunks)
  • apps/old-desktop/src/main/lib/workspace-composition/types.ts (1 hunks)
  • apps/old-desktop/src/main/lib/workspace-ipcs.ts (1 hunks)
  • apps/old-desktop/src/renderer/contexts/AppProviders.tsx (1 hunks)
  • apps/old-desktop/src/renderer/contexts/TabContext.tsx (1 hunks)
  • apps/old-desktop/src/renderer/contexts/TaskContext.tsx (1 hunks)
  • apps/old-desktop/src/renderer/contexts/WorktreeOperationsContext.tsx (1 hunks)
  • apps/old-desktop/src/renderer/contexts/index.ts (1 hunks)
  • apps/old-desktop/src/renderer/screens/main/MainScreen.tsx (1 hunks)
  • apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/CreatingView.tsx (1 hunks)
  • apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/TaskList.tsx (1 hunks)
  • apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/index.ts (1 hunks)
  • apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/types.ts (1 hunks)
  • apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/useTaskForm.ts (1 hunks)
  • apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/utils.ts (1 hunks)
  • apps/old-desktop/src/renderer/screens/main/components/MainContent/TabContent.tsx (1 hunks)
  • apps/old-desktop/src/renderer/screens/main/components/MainContent/TabGroup.tsx (1 hunks)
  • apps/old-desktop/src/renderer/screens/main/components/MainContentArea/MainContentArea.tsx (1 hunks)
  • apps/old-desktop/src/renderer/screens/main/components/PlanView/PlanView.tsx (1 hunks)
  • apps/old-desktop/src/renderer/screens/main/components/Sidebar/Sidebar.tsx (1 hunks)
  • apps/old-desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/index.ts (1 hunks)
  • apps/old-desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/WorktreeItem.tsx (1 hunks)
  • apps/old-desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/WorktreeItemArborist.tsx (2 hunks)
  • apps/old-desktop/src/renderer/screens/main/hooks/useTasks.ts (1 hunks)
  • apps/old-desktop/src/renderer/screens/main/utils.ts (1 hunks)
  • apps/old-desktop/src/shared/ipc-channels/index.ts (1 hunks)
  • apps/old-desktop/src/shared/ipc-channels/ui.ts (1 hunks)
  • apps/website/src/app/components/HeroSection/HeroSection.tsx (1 hunks)
  • apps/website/src/app/components/HeroSection/components/HeroCanvas/components/LitBackground/LitBackground.tsx (1 hunks)
  • apps/website/src/app/page.tsx (1 hunks)
  • packages/ui/src/components/context-menu.tsx (1 hunks)
  • packages/ui/src/components/dialog.tsx (1 hunks)
  • packages/ui/src/components/dropdown-menu.tsx (1 hunks)
  • packages/ui/src/components/resizable.tsx (1 hunks)
  • packages/ui/src/components/select.tsx (1 hunks)
  • packages/ui/src/components/separator.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (21)
  • apps/old-desktop/src/main/lib/workspace-composition/types.ts
  • packages/ui/src/components/dropdown-menu.tsx
  • apps/old-desktop/src/renderer/contexts/WorktreeOperationsContext.tsx
  • apps/old-desktop/src/main/lib/migration/migrator.ts
  • apps/old-desktop/src/renderer/screens/main/components/MainContent/TabContent.tsx
  • packages/ui/src/components/context-menu.tsx
  • apps/old-desktop/src/renderer/screens/main/components/MainContent/TabGroup.tsx
  • apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/index.ts
  • apps/old-desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/index.ts
  • apps/old-desktop/src/renderer/screens/main/components/MainContentArea/MainContentArea.tsx
  • apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/types.ts
  • apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/utils.ts
  • apps/website/src/app/components/HeroSection/HeroSection.tsx
  • apps/old-desktop/src/main/lib/ui-store/store.ts
  • apps/old-desktop/src/shared/ipc-channels/index.ts
  • apps/old-desktop/src/renderer/contexts/TabContext.tsx
  • apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/TaskList.tsx
  • apps/old-desktop/src/main/index.ts
  • apps/old-desktop/src/shared/ipc-channels/ui.ts
  • packages/ui/src/components/dialog.tsx
  • apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/CreatingView.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/desktop/src/renderer/stores/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeContent.tsx
  • apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent.tsx
  • apps/desktop/src/renderer/screens/main/index.tsx
🧰 Additional context used
🧬 Code graph analysis (14)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/types.ts (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts (2)
  • DragItem (3-6)
  • TAB_DND_TYPE (8-8)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx (3)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • TabGroup (24-31)
apps/desktop/src/shared/types.ts (1)
  • MosaicNode (35-35)
apps/desktop/src/renderer/lib/dnd.ts (1)
  • dragDropManager (5-5)
apps/desktop/src/renderer/stores/tabs/utils.ts (2)
apps/desktop/src/shared/types.ts (1)
  • TabType (23-30)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • Tab (34-34)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (3)
apps/desktop/src/renderer/stores/workspaces.ts (1)
  • useWorkspacesStore (27-90)
apps/desktop/src/renderer/stores/tabs/store.ts (3)
  • useTabs (310-310)
  • useActiveTabIds (311-312)
  • useAddTab (315-315)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (1)
  • TabItem (13-107)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useDropTabTarget.ts (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts (2)
  • DragItem (3-6)
  • TAB_DND_TYPE (8-8)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/types.ts (2)
  • DragItem (1-4)
  • TAB_DND_TYPE (6-6)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (4)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts (1)
  • TabItemProps (10-14)
apps/desktop/src/renderer/stores/workspaces.ts (1)
  • useWorkspacesStore (27-90)
apps/desktop/src/renderer/stores/tabs/store.ts (2)
  • useRemoveTab (316-316)
  • useSetActiveTab (317-318)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts (1)
  • useDragTab (5-42)
apps/desktop/src/renderer/stores/tabs/types.ts (2)
apps/desktop/src/shared/types.ts (2)
  • TabType (23-30)
  • MosaicNode (35-35)
apps/old-desktop/src/renderer/screens/main/components/MainContent/TabGroup.tsx (1)
  • TabGroup (18-302)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx (6)
apps/desktop/src/renderer/stores/workspaces.ts (1)
  • useWorkspacesStore (27-90)
apps/desktop/src/renderer/stores/tabs/store.ts (2)
  • useTabs (310-310)
  • useActiveTabIds (311-312)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/EmptyTabView.tsx (1)
  • EmptyTabView (1-18)
apps/desktop/src/shared/types.ts (1)
  • TabType (23-30)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx (1)
  • SingleTabView (8-48)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx (1)
  • GroupTabView (17-82)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx (2)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • SingleTab (19-21)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useDropTabTarget.ts (1)
  • useDropTabTarget (6-34)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts (2)
apps/desktop/src/renderer/stores/tabs/store.ts (1)
  • useTabsStore (101-307)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts (2)
  • DragItem (3-6)
  • TAB_DND_TYPE (8-8)
apps/old-desktop/src/renderer/screens/main/components/PlanView/PlanView.tsx (2)
apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/types.ts (1)
  • Task (4-14)
packages/api/src/index.ts (1)
  • RouterOutputs (9-9)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (3)
apps/desktop/src/renderer/stores/tabs/types.ts (2)
  • Tab (34-34)
  • TabGroup (24-31)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)
  • createNewTab (3-40)
apps/desktop/src/shared/types.ts (1)
  • TabType (23-30)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/types.ts (2)
  • DragItem (1-4)
  • TAB_DND_TYPE (6-6)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • Tab (34-34)
apps/desktop/src/renderer/stores/tabs/store.ts (4)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • Tab (34-34)
apps/desktop/src/shared/types.ts (2)
  • TabType (23-30)
  • MosaicNode (35-35)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)
  • createNewTab (3-40)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (1)
  • handleDragTabToTab (10-118)
🔇 Additional comments (36)
apps/old-desktop/src/main/lib/storage/lowdb-adapter.ts (1)

6-6: Import reorder is fine, no behavioral impact

Reordering the named imports from "./config" is purely cosmetic here and does not affect runtime behavior. Looks good to keep as-is.

apps/old-desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/WorktreeItem.tsx (1)

21-21: Shared drag‑and‑drop manager wiring looks good

Importing dragDropManager from renderer/lib/dnd and passing it as dndManager to Tree keeps the DnD configuration centralized and consistent. As long as this is the same singleton used elsewhere, this is a clean, non‑behavioral change.

If you haven’t already, please double‑check that dragDropManager matches the expected react-arborist dndManager interface and is reused across all Tree instances, per their docs.

Also applies to: 1067-1072

packages/ui/src/components/select.tsx (1)

4-4: LGTM! Clean import reordering.

The React type import has been moved to follow the Radix UI import, improving consistency. This is a pure organizational change with no functional impact.

apps/old-desktop/src/main/lib/workspace-composition/index.ts (1)

1-2: LGTM!

The reordering of export declarations has no functional impact and aligns with the broader workspace-related refactoring in this PR.

packages/ui/src/components/resizable.tsx (1)

2-2: LGTM! Import reordering with no functional impact.

This is a cosmetic change that reorders the import statement without affecting component behavior or functionality.

apps/website/src/app/components/HeroSection/components/HeroCanvas/components/LitBackground/LitBackground.tsx (1)

10-10: LGTM! Clean import reorganization.

The import consolidation groups related config imports together and maintains a logical order. No functional changes to the component.

Also applies to: 16-17

apps/website/src/app/page.tsx (2)

4-5: LGTM!

The imports are correctly added and used in the render tree. The reordering improves organization without affecting functionality.

Also applies to: 10-10


20-21: LGTM!

The new sections are correctly integrated into the landing page. The onOpenWaitlist prop properly wires the features section to the existing waitlist modal state management.

packages/ui/src/components/separator.tsx (1)

2-10: Type‑only React import matches usage and keeps runtime output clean

React is used only for typing (React.ComponentProps), so switching to import type is correct and avoids emitting an unnecessary runtime binding. No behavior change, and this is consistent with modern TS/React best practices.

If any legacy tooling or older TS versions are still in use in this repo, please confirm they handle import type correctly; otherwise this change is fully safe.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView.tsx (1)

1-7: LGTM! Clean placeholder implementation.

The component is well-structured and appropriate for a feature under development. The centered layout and styling are correct.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeNavigation.tsx (1)

2-2: LGTM! Import consolidation.

The import reordering consolidates AnimatedBackground with other local imports, improving organization with no functional impact.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/index.tsx (1)

5-7: LGTM! Improved import grouping.

The reordering groups component imports together after hooks, creating a clearer logical structure with no functional changes.

apps/old-desktop/src/main/lib/workspace-ipcs.ts (1)

13-13: LGTM!

The windowManager import is correctly added and properly used in the IPC handlers at lines 213 and 224 for window-workspace association management.

apps/old-desktop/src/main/lib/types/cli-types.ts (1)

6-11: Change-related type re-exports look consistent

Re-exporting AgentSummary, Change, ChangeOrchestrator, and FileDiff from @superset/cli/types/change keeps this file as a clean façade over the CLI type surface and matches the stated intent in the header comment. No issues here.

apps/old-desktop/src/renderer/screens/main/components/PlanView/PlanView.tsx (2)

6-6: LGTM: Import reordering is stylistic.

The import order change has no functional impact and appears to be part of the broader codebase formatting cleanup mentioned in the PR objectives.


14-14: LGTM: Type alias change is stylistic.

Converting from interface to type for an empty, non-exported props definition has no functional impact.

apps/old-desktop/src/main/lib/desktop-stores.ts (2)

1-1: LGTM: Import additions support migration flow.

The addition of LegacyMigrator and DesktopChangeOrchestrator to the imports is consistent with the new migration orchestration introduced in this PR.

Also applies to: 5-5, 8-8


64-90: The review comment is incorrect; the code handles migration failure and version initialization properly.

The false parameter is well-defined as dryRun: boolean = false (migrator.ts:46), meaning normal migration proceeds—not an issue.

Version initialization is actually conditional on migration success: version writes in migrate() (migrator.ts:133–134) execute only on the success path before returning. If migration fails, the catch block (migrator.ts:147–154) returns success: false without writing versions. The subsequent version checks in desktop-stores.ts (lines 84–89) act as a safety net, writing versions only if not already set (conditional on read() === 0). This design correctly handles both successful migrations and failure scenarios.

Likely an incorrect or invalid review comment.

apps/old-desktop/src/renderer/screens/main/hooks/useTasks.ts (1)

2-5: Type-only imports align with usage

Switching TaskStatus, PendingWorktree, and UITask to type-only imports matches their usage here and avoids introducing runtime dependencies from these modules.

If you haven’t already, double-check that your TypeScript version in this repo supports import type (TS ≥ 3.8) via tsconfig.json / package.json to prevent build surprises.

apps/old-desktop/src/main/lib/ui-store/types.ts (1)

1-1: LGTM: Import reordering only.

This is purely a cosmetic change to import ordering with no functional impact.

apps/old-desktop/src/main/lib/ui-store/index.ts (1)

1-2: LGTM: Export reordering only.

This is purely a cosmetic change to export ordering with no functional impact.

apps/old-desktop/src/main/lib/workspace-composition/composer.ts (1)

1-11: LGTM: Import additions and reordering.

The added WorktreeUiMetadata import is correctly used throughout the file (e.g., line 191 return type). The import reordering has no functional impact.

apps/old-desktop/src/renderer/contexts/AppProviders.tsx (1)

3-9: LGTM: Import reordering only.

This is purely a cosmetic change to import ordering with no functional impact.

apps/old-desktop/src/renderer/screens/main/components/Layout/AddTaskModal/useTaskForm.ts (1)

1-4: LGTM: Import reordering only.

This is purely a cosmetic change to import ordering with no functional impact.

apps/old-desktop/src/renderer/contexts/TaskContext.tsx (1)

5-8: LGTM: Improved type imports.

Changing from value imports to type-only imports for PendingWorktree and UITask is a best practice that improves tree-shaking and clarifies that these are only used for type annotations.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/mosaic-theme.css (1)

1-65: LGTM: Well-structured mosaic theme.

The CSS is well-organized and follows good practices:

  • Uses CSS variables for theming consistency
  • Includes smooth transitions for better UX
  • Provides proper focus states for accessibility
  • Split dividers with hover/dragging states enhance the mosaic layout interaction
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/EmptyTabView.tsx (1)

1-18: LGTM: Clean empty state component.

This is a straightforward presentational component with proper centering and clear messaging. The implementation is clean and follows good practices.

apps/desktop/src/renderer/stores/tabs/index.ts (1)

1-4: LGTM!

Clean barrel export pattern for organizing tab-related modules.

apps/old-desktop/src/renderer/screens/main/components/Sidebar/Sidebar.tsx (1)

1-11: LGTM!

Import reordering with no functional changes to the component.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useDropTabTarget.ts (1)

6-34: LGTM!

The drop target logic is well-structured with appropriate guards to prevent dropping a tab onto itself. The hook correctly collects drag state and computes the drop zone status.

apps/old-desktop/src/renderer/screens/main/MainScreen.tsx (1)

1-12: LGTM!

Import reordering with no functional changes to the component.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts (1)

5-42: LGTM!

The drag-and-drop hook is well-implemented with both drag source and drop target functionality. The logic correctly prevents a tab from being dropped onto itself and properly collects drag state for visual feedback.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (1)

12-62: TabsView workspace/tab wiring looks correct

Filtering by workspaceId and !parentId, resolving activeTabId, and guarding addTab on activeWorkspaceId are all consistent and straightforward. LGTM.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx (1)

12-34: Active tab resolution and view selection look solid

The memoized lookup of the active tab per workspace and the type‑based switch between SingleTabView and GroupTabView are clear and correct.

apps/old-desktop/src/renderer/contexts/index.ts (1)

3-9: Context barrel changes are safe

Export set looks consistent (providers + hooks), and reordering named exports doesn’t affect consumers. No issues here.

apps/desktop/src/renderer/stores/tabs/store.ts (1)

185-211: Active-tab history handling looks solid

The setActiveTab implementation maintains a per-workspace history stack as an ordered, de-duplicated list, pushing the previous active tab to the front when switching. This matches the intended “last active tab” semantics for getLastActiveTabId and avoids duplicate entries.

Comment on lines +1 to +6
export interface DragItem {
type: "TAB";
tabId: string;
}

export const TAB_DND_TYPE = "TAB";
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

Consolidate duplicated type definitions.

The DragItem interface and TAB_DND_TYPE constant are duplicated in apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts. This violates the DRY principle and creates a maintenance burden where changes must be synchronized across both locations.

Consider consolidating these shared types into a common location, such as:

// apps/desktop/src/renderer/stores/tabs/types.ts or a shared types file
export interface DragItem {
	type: "TAB";
	tabId: string;
}

export const TAB_DND_TYPE = "TAB";

Then import from the single source in both locations.

🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/types.ts
lines 1-6, the DragItem interface and TAB_DND_TYPE constant are duplicated in
Sidebar/TabsView/TabItem/types.ts; extract these shared definitions into a
single central module (e.g. apps/desktop/src/renderer/stores/tabs/types.ts or a
shared types file), export DragItem and TAB_DND_TYPE from that new file, then
replace the local definitions in both TabsContent/types.ts and
Sidebar/TabsView/TabItem/types.ts with imports from the new central module.

Comment on lines +24 to +27
const workspaceId = draggedTab.workspaceId;
const historyStack = state.tabHistoryStacks[workspaceId] || [];

// Rule 1: Dragging into itself - create new tab
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.

⚠️ Potential issue | 🟠 Major

Guard against dragging tabs across different workspaces

workspaceId is taken from the dragged tab, but nothing enforces that targetTab.workspaceId === draggedTab.workspaceId. If a cross‑workspace drop ever slips through the UI, this will:

  • Remove targetTab from its original workspace.
  • Update activeTabIds and tabHistoryStacks only for the dragged tab’s workspace.

Add an explicit workspace check (and early‑return no‑op or a safer behavior) to keep workspace state consistent.

🤖 Prompt for AI Agents
In apps/desktop/src/renderer/stores/tabs/drag-logic.ts around lines 24 to 27,
the code derives workspaceId from the dragged tab but does not verify the
targetTab is in the same workspace; add an explicit check comparing
draggedTab.workspaceId and targetTab.workspaceId and if they differ perform an
early return (no-op) or handle with a safe behavior (e.g., deny the drop) to
avoid removing/updating tabs in the wrong workspace, ensuring activeTabIds and
tabHistoryStacks are only modified for the correct workspace.

Comment thread apps/desktop/src/renderer/stores/tabs/store.ts
Comment on lines +23 to +31
// Tab group - split view using react-mosaic
export interface TabGroup extends BaseTab {
type: TabType.Group;
// MosaicNode describes the layout structure (split direction and children)
// Now uses tab IDs instead of pane IDs
layout: MosaicNode<string>;
// Array of child tab IDs
childTabIds: string[];
}
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.

⚠️ Potential issue | 🔴 Critical

TabGroup type is inconsistent with all current usages

TabGroup only exposes layout and childTabIds, but:

  • GroupTabView relies on tab.panes[...].
  • createNewTab and handleDragTabToTab construct TabGroup objects with a panes map and never populate childTabIds.
  • Comments say layout “now uses tab IDs instead of pane IDs”, but the actual creators use pane IDs as mosaic leaves.

This mismatch will either break type‑checking or produce a misleading data model.

I’d strongly recommend:

  • Decide whether mosaic leaves represent pane IDs or tab IDs and update the comment and all creators accordingly.
  • Update TabGroup to match the real model (e.g. add a typed panes: Record<string, { title: string; /* maybe tabId? */ }> and remove or properly maintain childTabIds).
  • Fix all constructors (createNewTab, handleDragTabToTab) and consumers (GroupTabView, any others) to use the same shape.

Until this is aligned, Group tabs are effectively ill‑typed.

🤖 Prompt for AI Agents
In apps/desktop/src/renderer/stores/tabs/types.ts around lines 23–31, TabGroup's
shape (layout: MosaicNode<string>; childTabIds: string[]) does not match current
code which creates/uses a panes map and uses mosaic leaves as pane IDs; decide
which identity mosaic leaves represent (paneId or tabId), then update the
TabGroup interface to match that model (e.g., replace/augment childTabIds with
panes: Record<string, { title: string; tabId?: string }>, or keep childTabIds
and remove panes), ensure MosaicNode<string> leaf type is documented and matches
the chosen ID type, and update all creators (createNewTab, handleDragTabToTab)
to populate the new fields consistently and all consumers (GroupTabView, etc.)
to read the new shape; run TypeScript to fix remaining call sites and remove the
stale comment or update it to reflect the chosen representation.

Comment on lines +3 to +39
export const createNewTab = (
workspaceId: string,
type: TabType = TabType.Single,
): Tab => {
const id = `tab-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
const baseTab = {
id,
title: type === TabType.Single ? "New Tab" : "New Split View",
workspaceId,
isNew: true,
};

if (type === TabType.Single) {
return {
...baseTab,
type: TabType.Single,
};
}

// Create a default split view with two panes
const pane1 = `pane-${Date.now()}-1`;
const pane2 = `pane-${Date.now()}-2`;

return {
...baseTab,
type: TabType.Group,
layout: {
direction: "row",
first: pane1,
second: pane2,
splitPercentage: 50,
},
panes: {
[pane1]: { title: "Pane 1" },
[pane2]: { title: "Pane 2" },
},
};
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.

⚠️ Potential issue | 🔴 Critical

Group tab shape returned here doesn’t match TabGroup type

The Group branch builds { layout, panes } but doesn’t populate childTabIds, while TabGroup currently requires childTabIds and doesn’t declare panes. That will either fail TS type‑checking or make the Tab model internally inconsistent, especially together with GroupTabView and handleDragTabToTab which also expect tab.panes.

Align TabGroup and all creators/consumers on a single shape (e.g. add a typed panes map to TabGroup, decide whether mosaic leaves are pane IDs or tab IDs, and either remove or correctly populate childTabIds).

🤖 Prompt for AI Agents
In apps/desktop/src/renderer/stores/tabs/utils.ts around lines 3 to 39, the
Group branch returns an object with layout and panes but omits childTabIds while
the TabGroup type currently requires childTabIds and doesn't declare panes; fix
by aligning types and creator: update the TabGroup type to include a typed panes
map (Record<PaneId, Pane>) and make childTabIds optional OR required as an array
of the pane IDs, then modify createNewTab’s Group return to populate both panes
(with titles/metadata) and childTabIds = [pane1, pane2]; ensure consumers
(GroupTabView, handleDragTabToTab) use pane IDs consistently and adjust any
mosaic/leaf expectations to treat leaves as pane IDs not tab IDs.

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: 3

♻️ Duplicate comments (1)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (1)

24-26: Missing workspace validation can corrupt cross-workspace state.

The code derives workspaceId from draggedTab but never verifies that targetTab.workspaceId matches. If tabs from different workspaces are dragged together:

  • Rule 2: The target group's workspace won't have activeTabIds or tabHistoryStacks updated
  • Rule 3: The new group will be created in the dragged tab's workspace, orphaning the target tab from its original workspace

Add an explicit check at the start:

if (draggedTab.workspaceId !== targetTab.workspaceId) {
  return state; // Deny cross-workspace drops
}
🧹 Nitpick comments (2)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (1)

139-139: Document or handle group-to-single and group-to-group drag scenarios.

The current implementation only handles:

  • Single → Self (Rule 1)
  • Single → Group (Rule 2)
  • Single → Single (Rule 3)

Unhandled scenarios silently return the original state (no-op):

  • Group → Single
  • Group → Group

If these scenarios should be disallowed, consider adding explicit checks and logging/warnings. If they should be supported, implement the logic.

apps/desktop/src/renderer/stores/tabs/utils.ts (1)

3-7: Consider using a UUID library for ID generation.

The current ID generation using Date.now() and Math.random() is acceptable for UI purposes but has a small collision risk if multiple tabs are created in rapid succession. Consider using a standard UUID library (e.g., uuid or nanoid) for better collision resistance and entropy.

Example using nanoid:

+import { nanoid } from 'nanoid';
+
 export const createNewTab = (
 	workspaceId: string,
 	type: TabType = TabType.Single,
 ): Tab => {
-	const id = `tab-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
+	const id = `tab-${nanoid()}`;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5cc8e7d and f1330c8.

📒 Files selected for processing (4)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/drag-logic.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/types.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/utils.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
apps/desktop/src/renderer/stores/tabs/utils.ts (2)
apps/desktop/src/shared/types.ts (1)
  • TabType (23-30)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • Tab (35-35)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (3)
apps/desktop/src/renderer/stores/tabs/types.ts (2)
  • Tab (35-35)
  • TabGroup (24-32)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)
  • createNewTab (3-30)
apps/desktop/src/shared/types.ts (1)
  • TabType (23-30)
🔇 Additional comments (6)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (4)

1-8: LGTM!

The imports and interface definition are clear and well-structured.


10-22: LGTM!

Good defensive programming with the early return when tabs aren't found.


28-38: LGTM!

Rule 1 correctly creates a new tab when dragging onto itself and activates it.


41-81: parentId property verified—no issues found.

The parentId property is correctly defined in the BaseTab interface as an optional string field. The assignment in line 45 (parentId: targetTabId) is type-safe and valid. The code change is sound from a typing perspective.

apps/desktop/src/renderer/stores/tabs/utils.ts (2)

15-20: LGTM!

The Single tab creation logic is clean and correctly typed.


22-29: Implementation correctly matches TabGroup type definition.

The code returns a valid TabGroup structure with all required properties:

  • type: TabType.Group — matches interface requirement
  • layout: null — valid per MosaicNode<string> | null type
  • childTabIds: [] — valid per string[] type
  • All BaseTab properties provided via spread operator

The interface confirms no panes property exists, and the empty childTabIds array is the correct initial state for skeleton structures created before children are added via addChildTabToGroup. The previous review concern has been fully addressed.

Comment on lines +85 to +87
const groupId = `tab-${Date.now()}-group`;
const childTab1Id = `tab-${Date.now()}-child-1`;
const childTab2Id = `tab-${Date.now()}-child-2`;
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.

⚠️ Potential issue | 🟡 Minor

Strengthen ID generation to prevent collisions.

The ID generation relies solely on Date.now() without a random suffix. If multiple drag operations occur in quick succession (within the same millisecond), IDs could collide. The createNewTab utility (used in Rule 1) includes Math.random() for uniqueness.

Apply this diff to align with the pattern in createNewTab:

-		const groupId = `tab-${Date.now()}-group`;
-		const childTab1Id = `tab-${Date.now()}-child-1`;
-		const childTab2Id = `tab-${Date.now()}-child-2`;
+		const timestamp = Date.now();
+		const random = Math.random().toString(36).substring(2, 11);
+		const groupId = `tab-${timestamp}-${random}-group`;
+		const childTab1Id = `tab-${timestamp}-${random}-child-1`;
+		const childTab2Id = `tab-${timestamp}-${random}-child-2`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const groupId = `tab-${Date.now()}-group`;
const childTab1Id = `tab-${Date.now()}-child-1`;
const childTab2Id = `tab-${Date.now()}-child-2`;
const timestamp = Date.now();
const random = Math.random().toString(36).substring(2, 11);
const groupId = `tab-${timestamp}-${random}-group`;
const childTab1Id = `tab-${timestamp}-${random}-child-1`;
const childTab2Id = `tab-${timestamp}-${random}-child-2`;
🤖 Prompt for AI Agents
In apps/desktop/src/renderer/stores/tabs/drag-logic.ts around lines 85 to 87,
the group and child tab IDs use only Date.now(), which can collide if created
within the same millisecond; change the ID construction to include a randomness
suffix (e.g., append Math.random().toString(36).slice(2) or follow the
createNewTab pattern) so each ID becomes something like
`tab-${Date.now()}-${random}-group` / `tab-${Date.now()}-${random}-child-1` /
`tab-${Date.now()}-${random}-child-2`, ensuring unique IDs across rapid
sequential operations.

@@ -0,0 +1,30 @@
import { type Tab, type TabGroup, TabType } from "./types";
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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify TabType definition consistency across the codebase.

The code uses TabType.Single and TabType.Group (enum-style access), but shared/types.ts defines TabType as a string union: "terminal" | "editor" | "browser" | "preview" | "group" | "port" | "diff". This union doesn't include "single", and the usage pattern (dot notation) suggests an enum rather than string literals.

Ensure that the local ./types.ts definition is consistent with shared/types.ts, or clarify whether these are intentionally different types.


🏁 Script executed:

#!/bin/bash
# Description: Check TabType definitions and usage across the codebase

# Check local types.ts definition
echo "=== Local TabType definition ==="
cat apps/desktop/src/renderer/stores/tabs/types.ts | rg -A 5 "export.*TabType"

# Check shared types.ts definition
echo -e "\n=== Shared TabType definition ==="
cat apps/desktop/src/shared/types.ts | rg -A 5 "export.*TabType"

# Find all usages of TabType.Single and TabType.Group
echo -e "\n=== Usage of TabType.Single ==="
rg -n "TabType\.Single" apps/desktop/src/renderer/

echo -e "\n=== Usage of TabType.Group ==="
rg -n "TabType\.Group" apps/desktop/src/renderer/

Length of output: 2565


Critical type mismatch between local and shared TabType definitions.

Verification reveals a significant type inconsistency:

  • Local definition (apps/desktop/src/renderer/stores/tabs/types.ts): TabType is an enum with Single = "single" and Group = "group"
  • Shared definition (apps/desktop/src/shared/types.ts): TabType is a union type with values "terminal" | "editor" | "browser" | "preview" | "group" (no "single")

The renderer code creates tab objects with type: "single" (via TabType.Single), but this value is not part of the shared TabType union. This breaks type safety and will cause type errors when shared code encounters these values.

Action required: Align the shared TabType union to include "single", or refactor the local enum to match the shared definition exactly.

🤖 Prompt for AI Agents
In apps/desktop/src/renderer/stores/tabs/utils.ts around lines 1 to 1, the code
imports a local TabType enum that defines "single" and "group" but the shared
types declare TabType as a union that lacks "single", causing type mismatches
when renderer produces type: "single"; fix by making the definitions consistent
— either add "single" to the shared TabType union in apps/shared/types.ts or
change the local enum to exactly mirror the shared union (e.g., replace
TabType.Single usages with the appropriate shared union value such as "terminal"
or another matching literal), update all usages and imports to the unified type,
and run TypeScript checks to ensure no remaining mismatches.

Comment on lines +8 to +13
const baseTab = {
id,
title: type === TabType.Single ? "New Tab" : "New Split View",
workspaceId,
isNew: true,
};
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.

⚠️ Potential issue | 🟡 Minor

Title assignment assumes only two tab types.

Line 10 uses a ternary that assigns "New Split View" for any non-Single type. While currently only Single and Group types are handled in the function, this could lead to incorrect titles if additional tab types are added in the future.

Consider making the title assignment more explicit:

 	const baseTab = {
 		id,
-		title: type === TabType.Single ? "New Tab" : "New Split View",
+		title: type === TabType.Single ? "New Tab" : type === TabType.Group ? "New Split View" : "New Tab",
 		workspaceId,
 		isNew: true,
 	};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const baseTab = {
id,
title: type === TabType.Single ? "New Tab" : "New Split View",
workspaceId,
isNew: true,
};
const baseTab = {
id,
title: type === TabType.Single ? "New Tab" : type === TabType.Group ? "New Split View" : "New Tab",
workspaceId,
isNew: true,
};
🤖 Prompt for AI Agents
In apps/desktop/src/renderer/stores/tabs/utils.ts around lines 8 to 13, the
title assignment uses a ternary that assumes only Single vs non-Single types;
update it to map each TabType explicitly (e.g., switch or a lookup object) so
TabType.Single => "New Tab", TabType.Group => "New Split View", and provide a
safe default title for unknown/future types (e.g., "New Tab" or a localized
fallback). Ensure the implementation is exhaustive or includes a default branch
so adding new TabType values won't silently assign the wrong title.

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

♻️ Duplicate comments (4)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (2)

54-61: Guard against dragging tabs across different workspaces.

workspaceId is derived from draggedTab only, but the code never validates that targetTab.workspaceId matches. If a cross-workspace drop occurs, Rules 2, 3, and 4 will update activeTabIds and tabHistoryStacks only for the dragged tab's workspace, potentially removing the target tab from its original workspace and corrupting state.

Apply this diff to add workspace validation:

 	const workspaceId = draggedTab.workspaceId;
+	
+	// Prevent cross-workspace drags
+	if (draggedTab.workspaceId !== targetTab.workspaceId) {
+		return state;
+	}
+	
 	const historyStack = state.tabHistoryStacks[workspaceId] || [];

178-180: Strengthen ID generation to prevent collisions.

The ID generation uses only Date.now() without a random suffix. Multiple drag operations within the same millisecond will produce colliding IDs. The createNewTab utility adds Math.random() for uniqueness.

Apply this diff to align with createNewTab:

-		const groupId = `tab-${Date.now()}-group`;
-		const childTab1Id = `tab-${Date.now()}-child-1`;
-		const childTab2Id = `tab-${Date.now()}-child-2`;
+		const timestamp = Date.now();
+		const random = Math.random().toString(36).substring(2, 11);
+		const groupId = `tab-${timestamp}-${random}-group`;
+		const childTab1Id = `tab-${timestamp}-${random}-child-1`;
+		const childTab2Id = `tab-${timestamp}-${random}-child-2`;
apps/desktop/src/renderer/stores/tabs/store.ts (2)

228-246: Guard against undefined childTabIds in group tabs.

Line 234 spreads tab.childTabIds without checking if it's defined. Although createNewTab initializes childTabIds: [], there's no TypeScript guarantee that all TabGroup instances have this field populated, and future code paths could create groups without it.

Apply this diff to add defensive checks:

 			addChildTabToGroup: (groupId, childTabId) => {
 				set((state) => ({
 					tabs: state.tabs.map((tab) => {
 						if (tab.id === groupId && tab.type === TabType.Group) {
+							const childTabIds = tab.childTabIds ?? [];
+							// Prevent duplicate additions
+							if (childTabIds.includes(childTabId)) return tab;
 							return {
 								...tab,
-								childTabIds: [...tab.childTabIds, childTabId],
+								childTabIds: [...childTabIds, childTabId],
 							};
 						}
 						if (tab.id === childTabId) {
 							return {
 								...tab,
 								parentId: groupId,
 							};
 						}
 						return tab;
 					}),
 				}));
 			},

248-315: Guard against undefined childTabIds when removing children.

Line 255 calls group.childTabIds.filter(...) without verifying childTabIds is defined. While there's a type guard at lines 250-253, TypeScript doesn't guarantee the field exists at runtime.

Apply this diff to add defensive handling:

 				const group = state.tabs.find(
 					(tab) => tab.id === groupId && tab.type === TabType.Group,
 				);
 				if (!group || group.type !== TabType.Group) return state;

-				const updatedChildTabIds = group.childTabIds.filter(
+				const updatedChildTabIds = (group.childTabIds ?? []).filter(
 					(id) => id !== childTabId,
 				);
🧹 Nitpick comments (3)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/NewWorkspaceView.tsx (1)

11-46: Consider implementing button handlers in a follow-up.

The action buttons are currently non-functional placeholders. While this is fine for an initial UI implementation, consider adding onClick handlers or tracking this as follow-up work to make the new workspace onboarding fully interactive.

Would you like me to open an issue to track implementing these action handlers?

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx (1)

44-50: Consider explicit mode handling for better type safety.

The current implementation uses a fallback pattern where any mode that isn't SidebarMode.Changes renders TabsView. While this works given the two-mode constraint on line 11, a more explicit switch or exhaustive check would improve maintainability if additional modes are added in the future.

Consider this more explicit pattern:

 {(mode) => {
   if (mode === SidebarMode.Changes) {
     return <ChangesView />;
   }
-
-  return <TabsView />;
+  if (mode === SidebarMode.Tabs) {
+    return <TabsView />;
+  }
+  // Exhaustive check - TypeScript will error if new modes are added
+  const _exhaustive: never = mode;
+  return _exhaustive;
 }}
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx (1)

42-46: Add explicit type check for GroupTabView.

Line 46 implicitly assumes any non-Single tab is a Group, relying on the exhaustiveness of the Tab union. While currently safe, an explicit check improves clarity and guards against future type expansion.

Apply this diff to add explicit type checking:

 	if (tabToRender.type === TabType.Single) {
 		return <SingleTabView tab={tabToRender} />;
 	}

-	return <GroupTabView tab={tabToRender} />;
+	if (tabToRender.type === TabType.Group) {
+		return <GroupTabView tab={tabToRender} focusedChildId={focusedChildId} />;
+	}
+
+	// Exhaustive check: should never reach here
+	return <EmptyTabView />;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f1330c8 and 72374a0.

📒 Files selected for processing (14)
  • apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/mosaic-theme.css (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/NewWorkspaceView.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx (3 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/drag-logic.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/store.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/mosaic-theme.css
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx
🧰 Additional context used
🧬 Code graph analysis (7)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx (2)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • SingleTab (16-18)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useDropTabTarget.ts (1)
  • useDropTabTarget (6-34)
apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx (1)
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/index.tsx (1)
  • WorkspacesTabs (11-123)
apps/desktop/src/renderer/stores/tabs/types.ts (2)
apps/desktop/src/shared/types.ts (2)
  • TabType (23-30)
  • MosaicNode (35-35)
apps/old-desktop/src/renderer/screens/main/components/MainContent/TabGroup.tsx (1)
  • TabGroup (18-302)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx (3)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/types.ts (1)
  • SidebarMode (5-5)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView.tsx (1)
  • ChangesView (1-7)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (1)
  • TabsView (11-55)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (3)
apps/desktop/src/renderer/stores/tabs/types.ts (2)
  • Tab (26-26)
  • TabGroup (20-24)
apps/desktop/src/shared/types.ts (1)
  • TabType (23-30)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)
  • createNewTab (3-30)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx (6)
apps/desktop/src/renderer/stores/workspaces.ts (1)
  • useWorkspacesStore (27-90)
apps/desktop/src/renderer/stores/tabs/store.ts (2)
  • useTabs (340-340)
  • useActiveTabIds (341-342)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/EmptyTabView.tsx (1)
  • EmptyTabView (1-18)
apps/desktop/src/shared/types.ts (1)
  • TabType (23-30)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx (1)
  • SingleTabView (8-48)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx (1)
  • GroupTabView (19-82)
apps/desktop/src/renderer/stores/tabs/store.ts (4)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • Tab (26-26)
apps/desktop/src/shared/types.ts (2)
  • TabType (23-30)
  • MosaicNode (35-35)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)
  • createNewTab (3-30)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (1)
  • handleDragTabToTab (45-233)
🪛 GitHub Actions: CI
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx

[error] 1-1: Command failed: bun run typecheck (exit code 2)

apps/desktop/src/renderer/screens/main/components/WorkspaceView/NewWorkspaceView.tsx

[error] 1-1: Command failed: bun run typecheck (exit code 2)

apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx

[error] 1-1: Command failed: bun run typecheck (exit code 2)

apps/desktop/src/renderer/stores/tabs/types.ts

[error] 1-1: Command failed: bun run typecheck (exit code 2)

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx

[error] 1-1: Command failed: bun run typecheck (exit code 2)

apps/desktop/src/renderer/stores/tabs/drag-logic.ts

[error] 1-1: Command failed: bun run typecheck (exit code 2)

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx

[error] 1-1: Command failed: bun run typecheck (exit code 2)

apps/desktop/src/renderer/stores/tabs/store.ts

[error] 1-1: Command failed: bun run typecheck (exit code 2)

🔇 Additional comments (7)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/NewWorkspaceView.tsx (2)

3-3: Verify the removed background class is intentional.

The bg-background class was removed from the outer wrapper. Ensure this is intentional and doesn't cause visual issues when the workspace view inherits a different background.


1-1: Rename migration is complete; all imports properly updated.

Verification confirms:

  • No remaining references to the old NewTabView name in the codebase
  • NewWorkspaceView is correctly imported in apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx (line 3)
  • Component is properly exported and used as <NewWorkspaceView /> (line 15)

The import verification concern from the original review is resolved.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx (2)

23-23: LGTM!

The className update removes border and background classes as intended, keeping only the essential layout classes. The Tailwind CSS classes are valid and appropriate for the sidebar container.


3-3: Ignore this review comment — the import path is correct.

The analysis in this review is incorrect. The import on line 3 (import { SidebarMode } from "renderer/stores/sidebar-state";) is correct. SidebarMode is properly defined as an enum in apps/desktop/src/renderer/stores/sidebar-state.ts, not in ModeCarousel/types.ts. The original review conflated the current codebase structure (apps/desktop) with the legacy folder (apps/old-desktop), where ModeCarousel/types.ts exists as a type definition. The import path is accurate and usage throughout the codebase is correct.

Likely an incorrect or invalid review comment.

apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx (1)

1-27: Typecheck structure validates correctly; unable to confirm reported failures in sandbox environment.

The code structure is sound:

  • WorkspacesTabs component is properly defined in WorkspaceTabs/index.tsx with correct export: export function WorkspacesTabs()
  • Import path "./WorkspaceTabs" correctly resolves to the directory's index file
  • All referenced subcomponents (AddWorkspaceButton, WorkspaceItem) exist
  • TopBar imports and component usage are syntactically valid

However, I cannot execute the full typecheck pipeline in the sandbox environment (bun/turbo not available). If a typecheck failure is reported by your CI pipeline, it likely stems from:

  • Type issues in imported dependencies (useWorkspacesStore, trpc, or UI components)
  • Store or hook type definitions
  • Not from the TopBar or WorkspacesTabs structure itself

Verify the actual typecheck output on your system to identify the specific type error location.

apps/desktop/src/renderer/stores/tabs/types.ts (1)

3-6: Review comment is inaccurate—no actual TabType naming collision exists.

The codebase defines two separate TabType entities serving different architectural purposes: the local enum in renderer/stores/tabs/types.ts ("single" | "group") and the shared type in shared/types.ts (string union of terminal types). However, no file imports TabType from shared/types, and no file imports both definitions simultaneously. The types are never in scope together, making the claimed "ambiguity" and "typecheck failures" unfounded. These are distinct type systems for separate concerns with no actual conflict.

Likely an incorrect or invalid review comment.

apps/desktop/src/renderer/stores/tabs/store.ts (1)

1-350: Review comment is factually incorrect—the alleged root causes do not match the actual codebase.

The investigation reveals:

  1. TabType "naming collision": While two different TabType definitions exist (stores/tabs/types.ts enum vs shared/types.ts string union), they are never imported together in the same file. Zero cross-import conflicts found.

  2. Type assertion in SingleTabView.tsx line 13: The pattern ref={drop as unknown as React.Ref<HTMLDivElement>} is standard react-dnd practice for drop targets, not a problem.

  3. "Missing type guards" in TabsContent/index.tsx: Type guards are present via discriminated union: if (tabToRender.type === TabType.Single) { return <SingleTabView /> } return <GroupTabView /> correctly narrows the union type without explicit assertions.

The alleged root causes do not explain any actual typecheck failures in this codebase section.

Likely an incorrect or invalid review comment.

Comment on lines +27 to +33
if (activeTab.parentId) {
const parentGroup = allTabs.find((tab) => tab.id === activeTab.parentId);
return {
tabToRender: parentGroup || null,
focusedChildId: activeTabId,
};
}
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.

⚠️ Potential issue | 🟠 Major

Missing type guard when finding parent group.

Line 28 finds the parent group by ID but doesn't verify it's actually a TabGroup type. If parentId incorrectly points to a SingleTab, the GroupTabView will receive an invalid tab type, potentially causing runtime errors.

Apply this diff to add type validation:

 		if (activeTab.parentId) {
 			const parentGroup = allTabs.find((tab) => tab.id === activeTab.parentId);
+			if (parentGroup && parentGroup.type === TabType.Group) {
 			return {
 				tabToRender: parentGroup || null,
 				focusedChildId: activeTabId,
 			};
+			}
+			// Parent ID is invalid or not a group
+			return { tabToRender: null, focusedChildId: null };
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (activeTab.parentId) {
const parentGroup = allTabs.find((tab) => tab.id === activeTab.parentId);
return {
tabToRender: parentGroup || null,
focusedChildId: activeTabId,
};
}
if (activeTab.parentId) {
const parentGroup = allTabs.find((tab) => tab.id === activeTab.parentId);
if (parentGroup && parentGroup.type === TabType.Group) {
return {
tabToRender: parentGroup || null,
focusedChildId: activeTabId,
};
}
// Parent ID is invalid or not a group
return { tabToRender: null, focusedChildId: null };
}
🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
around lines 27 to 33, the code finds a parent tab by ID but doesn't verify it's
a TabGroup, risking passing a SingleTab to GroupTabView; modify the lookup to
perform a type guard (e.g., use an existing isTabGroup(tab) helper or check a
distinguishing property like tab.type === 'group' or presence of children) and
only set tabToRender to the parent if the guard confirms it's a TabGroup;
otherwise set tabToRender to null (and keep focusedChildId as before) so
GroupTabView never receives an invalid tab type.

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: 1

♻️ Duplicate comments (2)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (2)

168-172: Align group/child tab ID generation with createNewTab to avoid collisions.

Group and child tab IDs for Rule 4 are based solely on Date.now(), which can collide if multiple split‑group creations occur within the same millisecond. createNewTab in apps/desktop/src/renderer/stores/tabs/utils.ts already uses a timestamp + random suffix pattern for uniqueness.

Reuse that approach here:

-	if (targetTab.type === TabType.Single && draggedTab.type === TabType.Single) {
-		const groupId = `tab-${Date.now()}-group`;
-		const childTab1Id = `tab-${Date.now()}-child-1`;
-		const childTab2Id = `tab-${Date.now()}-child-2`;
+	if (targetTab.type === TabType.Single && draggedTab.type === TabType.Single) {
+		const timestamp = Date.now();
+		const random = Math.random().toString(36).substring(2, 11);
+		const groupId = `tab-${timestamp}-${random}-group`;
+		const childTab1Id = `tab-${timestamp}-${random}-child-1`;
+		const childTab2Id = `tab-${timestamp}-${random}-child-2`;

56-63: Guard against cross‑workspace drops before mutating per‑workspace state.

workspaceId is derived from draggedTab.workspaceId, but there is no check that targetTab.workspaceId matches. If a cross‑workspace drop slips through the UI, this will update activeTabIds and tabHistoryStacks for the dragged tab’s workspace while conceptually acting on a tab from another workspace.

Add an explicit workspace equality check and bail out on mismatch:

	const draggedTab = state.tabs.find((tab) => tab.id === draggedTabId);
	const targetTab = state.tabs.find((tab) => tab.id === targetTabId);

	if (!draggedTab || !targetTab) return state;
+
+	if (draggedTab.workspaceId !== targetTab.workspaceId) {
+		return state;
+	}
+
	const workspaceId = draggedTab.workspaceId;
	const historyStack = state.tabHistoryStacks[workspaceId] || [];
🧹 Nitpick comments (2)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (2)

64-78: Clarify behavior for self‑drops of non‑single tabs.

Rule 1 duplicates a tab when it’s dropped onto itself, but only blocks when draggedTab.parentId is truthy. If group tabs (or other non‑single tab types) can ever be drag sources/targets, dropping a group onto itself will create a new single tab via createNewTab, which may not be intended.

If group tabs are not meant to support this behavior, consider tightening the condition:

-	if (draggedTabId === targetTabId) {
+	if (draggedTabId === targetTabId && draggedTab.type === TabType.Single) {

Or explicitly document that self‑dropping a group creates a fresh single tab so future maintainers understand this behavior.


4-8: Reduce duplication between DragTabToTabResult and the state parameter type.

DragTabToTabResult and the inline state parameter type share the same shape (tabs, activeTabIds, tabHistoryStacks). To keep types in sync and ease future refactors, consider extracting a shared type alias and reusing it for both the param and return type.

For example:

type TabsSlice = {
	tabs: Tab[];
	activeTabIds: Record<string, string | null>;
	tabHistoryStacks: Record<string, string[]>;
};

export interface DragTabToTabResult extends TabsSlice {}

export const handleDragTabToTab = (
	draggedTabId: string,
	targetTabId: string,
	state: TabsSlice,
): DragTabToTabResult => { /* ... */ }

Also applies to: 47-55

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 72374a0 and df47bef.

📒 Files selected for processing (1)
  • apps/desktop/src/renderer/stores/tabs/drag-logic.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (2)
apps/desktop/src/renderer/stores/tabs/types.ts (2)
  • Tab (26-26)
  • TabGroup (20-24)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)
  • createNewTab (3-30)

Comment thread apps/desktop/src/renderer/stores/tabs/drag-logic.ts
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: 0

♻️ Duplicate comments (3)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (3)

10-24: Keep group layout in sync when removing a child tab

removeFromOldParent only filters childTabIds and leaves the group's layout untouched. After moving a tab between groups, the old group's layout can still reference the removed tab, causing inconsistent state / duplicate rendering.

Update this helper to also strip the tab ID from the group's layout, collapsing empty branches where needed:

 const removeFromOldParent = (
 	tabs: Tab[],
 	tabId: string,
 	oldParentId: string,
 ): Tab[] => {
-	return tabs.map((tab) => {
-		if (tab.id === oldParentId && tab.type === TabType.Group) {
-			return {
-				...tab,
-				childTabIds: tab.childTabIds.filter((id) => id !== tabId),
-			};
-		}
-		return tab;
-	});
+	const removeFromLayout = (
+		layout: TabGroup["layout"],
+		idToRemove: string,
+	): TabGroup["layout"] => {
+		if (!layout) return layout;
+		if (typeof layout === "string") {
+			return layout === idToRemove ? null : layout;
+		}
+
+		const first = removeFromLayout(layout.first, idToRemove);
+		const second = removeFromLayout(layout.second, idToRemove);
+
+		if (!first && !second) return null;
+		if (!first) return second;
+		if (!second) return first;
+
+		return {
+			...layout,
+			first,
+			second,
+		};
+	};
+
+	return tabs.map((tab) => {
+		if (tab.id === oldParentId && tab.type === TabType.Group) {
+			return {
+				...tab,
+				childTabIds: tab.childTabIds.filter((id) => id !== tabId),
+				layout: removeFromLayout(tab.layout, tabId),
+			};
+		}
+		return tab;
+	});
 };

56-63: Guard against dragging tabs across different workspaces

workspaceId is derived from the dragged tab and used to update activeTabIds and tabHistoryStacks, but there's no check that targetTab.workspaceId matches. If a cross‑workspace drop ever slips through, you'll remove/update tabs in one workspace while only adjusting metadata in another.

Add an explicit workspace equality check and no‑op early return:

 	const draggedTab = state.tabs.find((tab) => tab.id === draggedTabId);
 	const targetTab = state.tabs.find((tab) => tab.id === targetTabId);
 
 	if (!draggedTab || !targetTab) return state;
 
-	const workspaceId = draggedTab.workspaceId;
+	if (draggedTab.workspaceId !== targetTab.workspaceId) {
+		return state;
+	}
+
+	const workspaceId = draggedTab.workspaceId;

168-217: Fix Rule 4 reparenting (cleanup old parents) and harden group ID generation

Two issues in Rule 4:

  1. If either single tab already has a parentId, the old group's childTabIds/layout are never updated, so those groups still reference tabs that have been reparented into the new group.
  2. groupId uses only Date.now(), which can collide; elsewhere (createNewTab) you include a random suffix.

You can re-use removeFromOldParent for both tabs and strengthen ID generation in one pass:

 	// Rule 4: Dragging single tab into another single tab - creates new group container for split view
 	if (targetTab.type === TabType.Single && draggedTab.type === TabType.Single) {
-		const groupId = `tab-${Date.now()}-group`;
+		const timestamp = Date.now();
+		const random = Math.random().toString(36).substring(2, 11);
+		const groupId = `tab-${timestamp}-${random}-group`;
 
 		// Keep original tab IDs stable - just update their parentId
 		const updatedTargetTab: Tab = {
 			...targetTab,
 			parentId: groupId,
@@ -197,13 +210,30 @@
 			childTabIds: [targetTab.id, draggedTab.id], // Use original IDs
 		};
 
-		return {
-			...state,
-			tabs: [
-				...state.tabs.map((tab) => {
-					if (tab.id === targetTab.id) return updatedTargetTab;
-					if (tab.id === draggedTab.id) return updatedDraggedTab;
-					return tab;
-				}),
-				newGroupTab,
-			],
+		let updatedTabs = state.tabs.map((tab) => {
+			if (tab.id === targetTab.id) return updatedTargetTab;
+			if (tab.id === draggedTab.id) return updatedDraggedTab;
+			return tab;
+		});
+
+		if (targetTab.parentId) {
+			updatedTabs = removeFromOldParent(
+				updatedTabs,
+				targetTab.id,
+				targetTab.parentId,
+			);
+		}
+
+		if (draggedTab.parentId) {
+			updatedTabs = removeFromOldParent(
+				updatedTabs,
+				draggedTab.id,
+				draggedTab.parentId,
+			);
+		}
+
+		return {
+			...state,
+			tabs: [...updatedTabs, newGroupTab],
 			activeTabIds: {
 				...state.activeTabIds,
 				[workspaceId]: newGroupTab.id,
🧹 Nitpick comments (1)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (1)

64-78: Clarify self‑drop behavior vs. comment

The Rule 1 comment says "duplicates standalone tabs", but the implementation calls createNewTab, which creates a fresh tab (default title, isNew: true, etc.), not a semantic duplicate of draggedTab.

Either adjust the comment to describe the current behavior ("opens a new blank tab when dropping onto itself") or copy relevant properties from draggedTab into the new tab so it behaves like a true duplicate.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df47bef and f7b54ba.

📒 Files selected for processing (2)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/drag-logic.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (2)
apps/desktop/src/renderer/stores/tabs/types.ts (2)
  • Tab (26-26)
  • TabGroup (20-24)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)
  • createNewTab (3-30)
🔇 Additional comments (1)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (1)

47-221: Overall structure of drag rules looks good

The decomposition into explicit rules with small helpers, immutable updates of tabs/activeTabIds/tabHistoryStacks, and centralized handleDragTabToTab entrypoint is clean and easy to reason about. Once the edge cases noted in other comments are addressed, this should be a solid foundation for DnD behavior.

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: 1

♻️ Duplicate comments (3)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (2)

235-261: Harden group ID generation to avoid Date.now() collisions

groupId is built from Date.now() only, so extremely fast consecutive drags could collide. Since createNewTab already uses a timestamp + random suffix, it’s cheap to align with that pattern here.

-	if (targetTab.type === TabType.Single && draggedTab.type === TabType.Single) {
-		const groupId = `tab-${Date.now()}-group`;
+	if (targetTab.type === TabType.Single && draggedTab.type === TabType.Single) {
+		const groupId = `tab-${Date.now()}-${Math.random()
+			.toString(36)
+			.substring(2, 11)}-group`;

122-129: Guard against cross‑workspace drags in handleDragTabToTab

workspaceId is derived from the dragged tab, but there’s no check that targetTab.workspaceId matches. If a cross‑workspace drop slips through the UI, you can end up with a group and children spanning different workspaces while only updating activeTabIds/tabHistoryStacks for the dragged tab’s workspace.

Add an explicit workspace equality check and no‑op when they differ.

 	const draggedTab = state.tabs.find((tab) => tab.id === draggedTabId);
 	const targetTab = state.tabs.find((tab) => tab.id === targetTabId);
 
 	if (!draggedTab || !targetTab) return state;
+
+	// Prevent cross-workspace drags from corrupting tab/group state
+	if (draggedTab.workspaceId !== targetTab.workspaceId) {
+		return state;
+	}
apps/desktop/src/renderer/stores/tabs/utils.ts (1)

1-29: Clarify TabType usage and make title mapping future‑proof

This helper works, but there are two small maintainability nits:

  • There’s a local TabType in ./types and a different TabType string union in apps/desktop/src/shared/types.ts. Using the same name for distinct concepts is easy to confuse; consider renaming the local one (e.g., TabsStoreTabType) or explicitly documenting that it’s separate.
  • The title ternary treats “non‑Single” as “New Split View”. If you ever extend the local TabType, a small switch/lookup mapping per type (with a default) will be safer than the binary check.
🧹 Nitpick comments (2)
apps/desktop/src/renderer/stores/tabs/drag-logic.test.ts (1)

6-343: Solid coverage of drag rules; consider a cross‑workspace regression test

These tests exercise the main cleanLayout and drag scenarios well. Once you add a guard in handleDragTabToTab to no‑op when draggedTab.workspaceId !== targetTab.workspaceId, it would be worth adding a test that sets different workspaceIds for dragged/target tabs and asserts the state is unchanged.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (1)

7-48: Workspace-scoped tab list looks correct; child resolution can stay simple

The TabsView correctly scopes top-level tabs to the active workspace and disables “New Tab” when there’s no active workspace. The per-parent getChildTabs filter over allTabs is fine given expected tab counts; if this ever grows large, you could memoize a parent→children map from allTabs, but that’s not necessary now.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f7b54ba and a447c94.

📒 Files selected for processing (6)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/mosaic-theme.css (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/drag-logic.test.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/drag-logic.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/store.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/utils.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
apps/desktop/src/renderer/stores/tabs/drag-logic.test.ts (2)
apps/desktop/src/shared/types.ts (2)
  • MosaicNode (35-35)
  • TabType (23-30)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (2)
  • cleanLayout (48-70)
  • handleDragTabToTab (113-287)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (3)
apps/desktop/src/renderer/stores/tabs/types.ts (2)
  • Tab (26-26)
  • TabGroup (20-24)
apps/desktop/src/shared/types.ts (2)
  • MosaicNode (35-35)
  • TabType (23-30)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)
  • createNewTab (3-30)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (3)
apps/desktop/src/renderer/stores/workspaces.ts (1)
  • useWorkspacesStore (27-90)
apps/desktop/src/renderer/stores/tabs/store.ts (2)
  • useTabs (374-374)
  • useAddTab (378-378)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (1)
  • TabItem (14-114)
apps/desktop/src/renderer/stores/tabs/utils.ts (2)
apps/desktop/src/shared/types.ts (1)
  • TabType (23-30)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • Tab (26-26)
apps/desktop/src/renderer/stores/tabs/store.ts (4)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • Tab (26-26)
apps/desktop/src/shared/types.ts (2)
  • TabType (23-30)
  • MosaicNode (35-35)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (2)
  • cleanLayout (48-70)
  • handleDragTabToTab (113-287)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)
  • createNewTab (3-30)
🔇 Additional comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/mosaic-theme.css (1)

28-35: Nice :focus-visible treatment for mosaic content

The explicit focus outline and inset shadow on .mosaic-window-content give good keyboard focus affordance without relying on default browser styles.

Comment thread apps/desktop/src/renderer/stores/tabs/store.ts
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: 0

♻️ Duplicate comments (1)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)

10-36: Critical type mismatch between local and shared TabType definitions.

The code uses TabType.Single (which resolves to "single"), but the shared TabType union in apps/desktop/src/shared/types.ts doesn't include "single" as a valid value. This breaks type safety when tabs are passed to shared code.

As noted in previous reviews, align the type definitions:

  • Either add "single" to the shared TabType union, or
  • Map TabType.Single to an existing shared value like "terminal"

See the existing review comment for detailed remediation steps.

🧹 Nitpick comments (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx (1)

131-131: Consider making the theme configurable.

The "mosaic-theme-dark" class is hardcoded, which prevents supporting light themes or user preferences in the future.

Consider accepting a theme prop or deriving it from a theme context:

+	const theme = useTheme(); // or accept as prop
 	return (
 		<div className="w-full h-full mosaic-container">
 			<Mosaic<string>
 				renderTile={renderPane}
 				value={cleanedLayout}
 				onChange={handleLayoutChange}
-				className="mosaic-theme-dark"
+				className={`mosaic-theme-${theme}`}
 				dragAndDropManager={dragDropManager}
 			/>
 		</div>
 	);
apps/desktop/src/renderer/stores/tabs/store.ts (1)

255-274: Consider making addChildTabToGroup update the layout automatically.

The comment on line 267 notes that "caller is responsible for layout updates," which creates a fragile two-step API where callers must remember to call both addChildTabToGroup and updateTabGroupLayout. This could lead to inconsistent state if a caller forgets the second step.

Consider accepting an optional layout parameter or automatically adding the child to the layout:

-	addChildTabToGroup: (groupId, childTabId) => {
+	addChildTabToGroup: (groupId, childTabId, layoutPosition?: 'first' | 'second') => {
 		set((state) => {
 			const updatedTabs = state.tabs.map((tab) => {
 				if (tab.id === childTabId) {
 					return {
 						...tab,
 						parentId: groupId,
 					};
 				}
+				if (tab.id === groupId && tab.type === TabType.Group && layoutPosition) {
+					// Update layout to include the new child
+					const newLayout = layoutPosition === 'first' 
+						? { direction: 'row', first: childTabId, second: tab.layout, splitPercentage: 50 }
+						: { direction: 'row', first: tab.layout, second: childTabId, splitPercentage: 50 };
+					return { ...tab, layout: newLayout };
+				}
 				return tab;
 			});

-			// Note: This doesn't update layout - caller is responsible for layout updates
-			// This is typically used in conjunction with updateTabGroupLayout

 			return {
 				tabs: updatedTabs,
 			};
 		});
 	},

This would reduce the API surface and prevent inconsistent states.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a447c94 and 7d680c2.

📒 Files selected for processing (10)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.test.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/mosaic-theme.css (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/UngroupDropZone/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/drag-logic.test.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/drag-logic.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/store.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/types.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/utils.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/stores/tabs/drag-logic.ts
  • apps/desktop/src/renderer/stores/tabs/drag-logic.test.ts
🧰 Additional context used
🧬 Code graph analysis (6)
apps/desktop/src/renderer/stores/tabs/utils.ts (2)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • Tab (25-25)
apps/desktop/src/shared/types.ts (1)
  • TabType (23-30)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx (6)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • TabGroup (20-23)
apps/desktop/src/shared/types.ts (1)
  • MosaicNode (35-35)
apps/desktop/src/renderer/stores/tabs/store.ts (2)
  • useTabs (477-477)
  • useTabsStore (116-475)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)
  • getChildTabIds (6-8)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (1)
  • cleanLayout (48-70)
apps/desktop/src/renderer/lib/dnd.ts (1)
  • dragDropManager (5-5)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.test.tsx (4)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • Tab (25-25)
apps/desktop/src/shared/types.ts (1)
  • TabType (23-30)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)
  • getChildTabIds (6-8)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (1)
  • cleanLayout (48-70)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/UngroupDropZone/index.tsx (1)
apps/desktop/src/renderer/stores/tabs/store.ts (2)
  • useUngroupTab (488-488)
  • useTabs (477-477)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (4)
apps/desktop/src/renderer/stores/workspaces.ts (1)
  • useWorkspacesStore (27-90)
apps/desktop/src/renderer/stores/tabs/store.ts (2)
  • useTabs (477-477)
  • useAddTab (481-481)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/UngroupDropZone/index.tsx (1)
  • UngroupDropZone (16-94)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (1)
  • TabItem (14-114)
apps/desktop/src/renderer/stores/tabs/store.ts (4)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • Tab (25-25)
apps/desktop/src/shared/types.ts (2)
  • TabType (23-30)
  • MosaicNode (35-35)
apps/desktop/src/renderer/stores/tabs/utils.ts (2)
  • getChildTabIds (6-8)
  • createNewTab (10-36)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (3)
  • cleanLayout (48-70)
  • handleDragTabToTab (110-283)
  • removeTabFromLayout (15-43)
⏰ 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: Unit Test
🔇 Additional comments (7)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.test.tsx (1)

1-169: LGTM! Comprehensive test coverage for group tab logic.

The test suite thoroughly covers the critical scenarios for layout cleaning and child tab management, including edge cases like empty layouts and nested structures.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView.tsx (1)

45-135: LGTM! Well-structured mosaic group tab implementation.

The component correctly handles layout changes, tab removal, and empty states. The integration with react-mosaic-component and the drag-drop manager is clean.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/mosaic-theme.css (1)

1-48: LGTM! Well-structured dark theme with good accessibility.

The CSS uses custom properties effectively, includes appropriate transitions, and provides focus-visible states for accessibility.

apps/desktop/src/renderer/stores/tabs/utils.ts (1)

1-8: LGTM! Clean helper for deriving child tabs.

The getChildTabIds function correctly filters tabs by parentId and returns their IDs.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (1)

9-97: LGTM! Clean tabs view with drag-and-drop preview support.

The component correctly filters workspace tabs, integrates with the drag-drop system to show previews, and handles the empty workspace state appropriately.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/UngroupDropZone/index.tsx (1)

16-94: LGTM! Robust drag-and-drop zone implementation.

The component correctly handles drop events with proper defensive checks (monitor.didDrop(), draggedTab?.parentId), computes drop indices accurately during hover, and exposes the right drag state through the render prop.

apps/desktop/src/renderer/stores/tabs/store.ts (1)

116-475: LGTM! Comprehensive tab management store with proper state synchronization.

The store correctly manages tabs, groups, active states, and history stacks across workspaces. The complex operations like removeChildTabFromGroup and ungroupTab handle edge cases appropriately (empty groups, active tab updates, reordering).

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: 0

🧹 Nitpick comments (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (2)

52-53: Inconsistent drop indicator styling.

The drop line indicator uses bg-primary at Line 53 but bg-sidebar-accent at Line 62. This inconsistency could confuse users about where the tab will be dropped.

For consistency, use the same color for both indicators:

  {/* Drop line indicator before this tab */}
  {isDragOver && draggedTab && index === dropIndex && (
    <div className="absolute -top-0.5 left-0 right-0 h-0.5 bg-primary rounded-full z-10" />
  )}
  <div data-tab-item>
    <TabItem tab={tab} childTabs={getChildTabs(tab.id)} />
  </div>
</motion.div>
))}
{/* Drop line indicator at the end */}
{isDragOver && draggedTab && dropIndex >= tabs.length && (
- <div className="h-0.5 bg-sidebar-accent rounded-full" />
+ <div className="h-0.5 bg-primary rounded-full" />
)}

Also applies to: 61-62


64-72: Fix "New Tab" button layout.

The justify-between class on Line 67 will push the icon and text to opposite ends of the button, which is likely unintended for an icon-label button combination.

Apply this diff to group the icon and text together:

 <Button
   variant="ghost"
   onClick={handleAddTab}
-  className="w-full text-start group px-3 py-2 rounded-md cursor-pointer flex items-center justify-between"
+  className="w-full text-start group px-3 py-2 rounded-md cursor-pointer flex items-center gap-2"
   disabled={!activeWorkspaceId}
 >
   <HiMiniPlus className="size-4" />
   <span className="truncate flex-1">New Tab</span>
 </Button>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d680c2 and 4fb72cf.

📒 Files selected for processing (4)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/UngroupDropZone/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/store.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/UngroupDropZone/index.tsx
  • apps/desktop/src/renderer/stores/tabs/store.ts
🧰 Additional context used
🧬 Code graph analysis (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (5)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts (1)
  • TabItemProps (10-13)
apps/desktop/src/renderer/stores/workspaces.ts (1)
  • useWorkspacesStore (27-90)
apps/desktop/src/renderer/stores/tabs/store.ts (3)
  • useActiveTabIds (502-503)
  • useRemoveTab (506-506)
  • useSetActiveTab (507-508)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts (1)
  • useDragTab (5-42)
apps/desktop/src/shared/types.ts (1)
  • TabType (23-30)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (4)
apps/desktop/src/renderer/stores/workspaces.ts (1)
  • useWorkspacesStore (27-90)
apps/desktop/src/renderer/stores/tabs/store.ts (2)
  • useTabs (501-501)
  • useAddTab (505-505)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/UngroupDropZone/index.tsx (1)
  • UngroupDropZone (16-101)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (1)
  • TabItem (14-114)
🔇 Additional comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (1)

99-111: Verify if nested group tabs (groups within groups) should be supported.

Line 105 passes an empty array for childTabs, which means if a child tab is itself a group, its children won't be rendered. If nested groups are expected in your design, this will silently fail to display them.

If nested groups should be supported, apply this diff:

 {isGroupTab && hasChildren && isExpanded && (
   <div className="ml-4 mt-1 space-y-1">
     {childTabs.map((childTab) => {
       return (
         <div key={childTab.id} className="flex items-start gap-1">
           <div className="flex-1">
-            <TabItem tab={childTab} childTabs={[]} />
+            <TabItem tab={childTab} childTabs={getChildTabs(childTab.id)} />
           </div>
         </div>
       );
     })}
   </div>
 )}

Where getChildTabs is passed as a prop or imported from the parent component. Alternatively, if nested groups are not part of the design, consider adding a comment to clarify this limitation.

This was referenced Nov 20, 2025
This was referenced Nov 21, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Nov 21, 2025
5 tasks
This was referenced Nov 21, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Feb 7, 2026
5 tasks
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