Skip to content

Refactor tabs to windows/panes model - #220

Merged
saddlepaddle merged 5 commits into
mainfrom
indigo-forest-68
Dec 2, 2025
Merged

saddlepaddle merged 5 commits into
mainfrom
indigo-forest-68

Conversation

@saddlepaddle

@saddlepaddle saddlepaddle commented Dec 1, 2025 •

Copy link
Copy Markdown
Collaborator

Summary

  • Refactors the tab system from a "tabs can be singles OR groups" model to a clean "windows always contain panes" model
  • Windows now always have ≥1 pane (invariant enforced at creation time)
  • Removes ungroup concepts entirely - panes can only be split/closed, not ungrouped
  • Simplifies sidebar to show only windows (no expandable children)
  • Drag-and-drop now only reorders windows in sidebar
  • Window names are now static at creation time (e.g., "Window 5" stays "Window 5" regardless of position)
  • Deletes ~3,200 lines of unused code (old tab grouping logic, tests, helpers)

Test plan

  • Create new windows - should appear at bottom of sidebar with correct numbering
  • Reorder windows via drag-and-drop - names should stay static
  • Split panes horizontally/vertically within a window
  • Close panes - closing last pane should close the window
  • Verify layout animations when adding/removing windows

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor

    • Switched from a tab-centered model to window/pane-centered UI — terminals now live in panes within windows; focus, lifecycle, and keyboard shortcuts updated accordingly.
    • Simplified rendering: group/tab views and legacy drop overlays removed.
  • New Features

    • Window-based sidebar with drag-and-drop reordering, inline rename, and context menu actions.
    • Mosaic-style pane splitting (horizontal/vertical), per-pane context actions, and pane-focused terminal behavior.
  • Chores

    • Removed legacy tab-focused DnD and grouping helpers and related tests.

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

@vercel

vercel Bot commented Dec 1, 2025 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
website Ready Ready Preview Comment Dec 2, 2025 0:35am

@coderabbitai

coderabbitai Bot commented Dec 1, 2025 •

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@saddlepaddle has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 13 minutes and 56 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between aa39e87 and 79556cc.

📒 Files selected for processing (4)
  • apps/desktop/src/renderer/stores/tabs/store.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/utils.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/utils/terminal-naming.test.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/utils/terminal-naming.ts (0 hunks)

Walkthrough

This PR replaces the tab-focused model with a window/pane model across store, UI, and terminal flows: tabs, group/layout helpers, and many tab-specific components/hooks/tests are removed; a WindowsStore (windows, panes, activeWindowIds, focusedPaneIds) plus WindowView/WindowPane UI are added; terminal/session logic is migrated from tabId to paneId.

Changes

Cohort / File(s) Summary
Core store & types (tabs → windows)
apps/desktop/src/renderer/stores/tabs/store.ts, apps/desktop/src/renderer/stores/tabs/types.ts, apps/desktop/src/renderer/stores/tabs/hooks.ts, apps/desktop/src/renderer/stores/tabs/index.ts
Replaced TabsStore with WindowsStore: new Window/Pane types, windows/panes state, history/focus primitives, actions (add/remove/rename/reorder windows & panes, split, focus, needsAttention), and removal of many tab-centric hooks/exports.
Utilities for mosaic & panes
apps/desktop/src/renderer/stores/tabs/utils.ts
Added window/pane helpers (ID generation, createWindowWithPane, extractPaneIdsFromLayout, removePaneFromLayout, cleanLayout, getFirstPaneId, getPaneIdsForWindow, etc.) replacing tab utilities.
Terminal & workspace creation
apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx, .../Terminal/helpers.ts
Migrated terminal lifecycle and TRPC calls to use paneId/paneName; createOrAttach/stream/write/detach subscribe and operate on paneId; focus listener API simplified to onFocus callback.
Content rendering: TabsContent → WindowView
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx, .../WindowView/index.tsx, .../WindowView/WindowPane.tsx
Replaced group/single tab views and DropOverlay with WindowView rendering mosaic of panes; WindowPane renders a MosaicWindow with Terminal and split/remove/focus handlers.
Sidebar / TopBar UI (tab list → window list)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx, .../WindowItem/index.tsx, .../WindowItem/WindowContextMenu.tsx, apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
Sidebar and top-bar components refactored to operate on windows/panes: new WindowItem (drag/reorder/rename/activate), WindowContextMenu; WorkspaceItem attention logic switched to pane-based needsAttention derived from window layouts.
Removed tab UI components, DnD hooks, and helpers
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/DropOverlay.tsx, .../GroupTabView/*, .../SingleTabView.tsx, .../useTabContentDrop.ts, .../Sidebar/TabsView/TabItem/*, .../UngroupDropZone/*, .../TabContextMenu.tsx
Deleted many tab-specific components, context menus, DnD hooks, rename hooks, and related UI pieces—removes group/tab mosaic UI paths.
Removed tab helper modules & tests
apps/desktop/src/renderer/stores/tabs/helpers/*, apps/desktop/src/renderer/stores/tabs/drag-logic.ts, apps/desktop/src/renderer/stores/tabs/drag-logic.test.ts, apps/desktop/src/renderer/stores/tabs/store.test.ts, .../*.test.ts
Deleted tab helper implementations (active-tab, group-operations, next-tab-finder, split-operations, tab-crud, tab-ordering, validation) and associated tests.
Hotkeys and top-level wiring
apps/desktop/src/renderer/screens/main/index.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
Hotkeys and workspace wiring updated to window/pane context: new splitPane* hooks, activeWindow/focusedPane derivation; NEW_TERMINAL creates window, CLOSE_TERMINAL removes pane, navigation moves between windows.
Agent listener updates
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
Replaced useTabsStore usage with useWindowsStore, mapping tabId→paneId and updating focus/attention flows to window/pane semantics.

Sequence Diagram(s)

sequenceDiagram
  participant UI as Renderer UI (WindowView / Terminal)
  participant Store as WindowsStore
  participant TRPC as Terminal TRPC (createOrAttach / stream / write / detach)

  UI->>Store: addWindow(workspaceId) → { windowId, paneId }
  Store-->>UI: { windowId, paneId }
  UI->>TRPC: createOrAttach(paneId, paneName, initialCommands?)
  TRPC-->>UI: session established
  UI->>TRPC: stream.subscribe(paneId)
  TRPC-->>UI: terminal output events
  UI->>TRPC: write(paneId, input)
  UI->>Store: setFocusedPane(windowId, paneId)  %% on focus
  Store-->>UI: focusedPaneIds updated
  UI->>TRPC: detach(paneId)  %% on unmount/close
  TRPC-->>UI: session ended
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

  • Files needing extra attention:
    • apps/desktop/src/renderer/stores/tabs/store.ts — state shape, persistence, history and focus invariants, API surface changes.
    • apps/desktop/src/renderer/stores/tabs/utils.ts — mosaic layout traversal, removal, and cleanup correctness.
    • Terminal component and TRPC interactions — subscription, createOrAttach, detach, and cleanup using paneId.
    • Sidebar drag/drop and WindowItem rename/activation — DnD index calculation and accessibility/keyboard handling.
    • Broad test removals — ensure equivalent coverage exists or add new tests for windows/panes.

Possibly related PRs

  • fix worktree setup #146 — modifies workspace creation flow and previously added setup-tab logic; directly related to useCreateWorkspace changes that now create windows/panes.
  • node pty processes #117 — updates terminal tRPC procedures (createOrAttach/stream/write/detach); relevant to Terminal migration to paneId.
  • refactor tabs store #110 — refactors tabs store and helpers; closely related to this large tabs→windows store migration.

Poem

🐰 I hopped through tabs and tunneled deep,

Windows sprung where old tabs used to sleep,
Panes in mosaics nest and play,
Focus hops and sessions stay,
A happy refactor — nibble, hop, repeat! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Refactor tabs to windows/panes model' clearly and concisely summarizes the primary architectural change in the changeset.
Description check ✅ Passed The PR description covers all required sections with substantial detail: summary of changes, test plan, and additional context about the refactoring scope and objectives.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (13)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts (1)

173-187: Consider removing unused parameters.

The parameters _windowId and _paneId are marked as unused with underscore prefixes but are still required by the function signature. If they're not needed for the focus listener logic, consider simplifying the signature to just (xterm: XTerm, onFocus: () => void).

If these are kept for API consistency with the caller (to match the shape expected elsewhere), that's acceptable—but worth a brief comment explaining why they're retained.

 export function setupFocusListener(
 	xterm: XTerm,
-	_windowId: string,
-	_paneId: string,
 	onFocus: () => void,
 ): (() => void) | null {
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx (1)

49-75: Consider memoizing or extracting the needsAttention computation.

The collectPaneIds function is redefined on every render, and the entire computation involving filter, flatMap, and some runs on each render cycle. This can be optimized using useMemo to avoid unnecessary recalculations when windows and panes haven't changed.

+import { useMemo, useState } from "react";
-import { useState } from "react";
...

+	// Extract pane IDs from a MosaicNode layout
+	const collectPaneIds = (node: unknown): string[] => {
+		if (typeof node === "string") return [node];
+		if (
+			node &&
+			typeof node === "object" &&
+			"first" in node &&
+			"second" in node
+		) {
+			const branch = node as { first: unknown; second: unknown };
+			return [...collectPaneIds(branch.first), ...collectPaneIds(branch.second)];
+		}
+		return [];
+	};
+
+	const needsAttention = useMemo(() => {
+		const workspaceWindows = windows.filter((w) => w.workspaceId === id);
+		const workspacePaneIds = new Set(
+			workspaceWindows.flatMap((w) => collectPaneIds(w.layout)),
+		);
+		return Object.values(panes)
+			.filter((p) => workspacePaneIds.has(p.id))
+			.some((p) => p.needsAttention);
+	}, [windows, panes, id]);

Alternatively, extract collectPaneIds to a shared utility in your store's utils.ts file for reuse elsewhere.

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

74-76: Prop naming inconsistency: tabId vs paneId.

The Terminal component still expects a tabId prop while this code passes the paneId. While this works because Terminal internally aliases tabId to paneId (as seen in Terminal.tsx lines 18-19), this creates a confusing naming mismatch in the API surface.

Consider renaming the Terminal prop in a follow-up to complete the migration:

// Future cleanup: rename tabId to paneId in TerminalProps
<Terminal paneId={paneId} workspaceId={workspaceId} />
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (1)

223-231: Effect dependency on terminalTheme causes full terminal re-initialization on theme changes.

Including terminalTheme in the main effect's dependencies (line 229) means the entire terminal instance is disposed and recreated when the theme changes. However, there's a separate effect (lines 234-239) that handles live theme updates via xterm.options.theme.

Consider removing terminalTheme from the main effect's dependencies since theme changes can be applied without re-initialization:

	}, [
		paneId,
		workspaceId,
		pane?.windowId,
		workspaceCwd,
		paneName,
-		terminalTheme,
		handleTerminalFocus,
	]);

This would prevent unnecessary terminal re-creation and preserve terminal state (scrollback, cursor position) when switching themes.

#!/bin/bash
# Verify that terminalTheme is only used in createTerminalInstance within this effect
rg -n "terminalTheme" apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (2)

15-21: Consider extracting shared drag constants.

DRAG_TYPE and DragItem are duplicated in both TabsView and WindowItem. Consider moving them to a shared location (e.g., a constants.ts or types.ts file within this folder) to maintain DRY principles.


95-100: Simplify the ref callback.

The type assertion for containerRef is awkward. Consider using a callback that handles both refs more cleanly.

 		<nav
-			ref={(node) => {
-				drop(node);
-				(containerRef as React.MutableRefObject<HTMLElement | null>).current =
-					node;
-			}}
+			ref={(node) => {
+				drop(node);
+				containerRef.current = node;
+			}}
 			className="flex flex-col h-full p-2"
 		>

And update the ref type on line 31:

-	const containerRef = useRef<HTMLElement>(null);
+	const containerRef = useRef<HTMLElement | null>(null);
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/index.tsx (3)

87-94: Consider cleanup for the setTimeout.

If the component unmounts before the timeout fires, inputRef.current?.focus() will attempt to operate on a potentially stale ref. While this is unlikely to cause issues in practice, adding cleanup would be more robust.

 	const startRename = () => {
 		setRenameValue(window.name || displayName);
 		setIsRenaming(true);
-		setTimeout(() => {
-			inputRef.current?.focus();
-			inputRef.current?.select();
-		}, 0);
+		// Use requestAnimationFrame for safer timing with React's rendering
+		requestAnimationFrame(() => {
+			inputRef.current?.focus();
+			inputRef.current?.select();
+		});
 	};

69-73: Consider memoizing the panes calculation.

Filtering panes on every render could be avoided with useMemo, especially if the panes object is large.

+	const windowPanes = useMemo(
+		() => Object.values(panes).filter((p) => p.windowId === window.id),
+		[panes, window.id],
+	);
-	// Check if any pane in this window needs attention
-	const windowPanes = Object.values(panes).filter(
-		(p) => p.windowId === window.id,
-	);
 	const needsAttention = windowPanes.some((p) => p.needsAttention);

Don't forget to add useMemo to the imports on line 2.


30-36: Verify the trpc query usage pattern.

Each WindowItem instance makes its own trpc.workspaces.getActive.useQuery() call. Since this data is likely shared across all window items, consider lifting this query to the parent TabsView and passing activeWorkspaceId as a prop to reduce redundant network requests.

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

41-47: Consider consistency in nullable types.

activeWindowIds uses string | null to represent no active window, but focusedPaneIds uses just string. If a window can exist without a focused pane (e.g., during initialization), consider using string | null for consistency:

 export interface WindowsState {
 	windows: Window[];
 	panes: Record<string, Pane>;
 	activeWindowIds: Record<string, string | null>; // workspaceId → windowId
-	focusedPaneIds: Record<string, string>; // windowId → paneId (last focused pane in each window)
+	focusedPaneIds: Record<string, string | null>; // windowId → paneId (last focused pane in each window)
 	windowHistoryStacks: Record<string, string[]>; // workspaceId → windowId[] (MRU history)
 }
apps/desktop/src/renderer/stores/tabs/utils.ts (2)

7-9: Potential ID collision in rapid succession.

Using Date.now() as part of the ID could lead to collisions if called multiple times within the same millisecond (e.g., during batch operations). Consider using a counter or UUID library for more robust uniqueness.

+let idCounter = 0;
+
 export const generateId = (prefix: string): string => {
-	return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
+	return `${prefix}-${Date.now()}-${++idCounter}-${Math.random().toString(36).substring(2, 11)}`;
 };

65-80: Improve efficiency of generateWindowName.

The current implementation has O(n²) complexity due to the while loop with includes(). For a large number of windows, this could be optimized using a Set:

 export const generateWindowName = (existingWindows: Window[]): string => {
-	const existingNumbers = existingWindows
+	const existingNumbers = new Set(existingWindows
 		.map((w) => {
 			const match = w.name.match(/^Window (\d+)$/);
 			return match ? Number.parseInt(match[1], 10) : 0;
 		})
-		.filter((n) => n > 0);
+		.filter((n) => n > 0));

 	// Find the next available number
 	let nextNumber = 1;
-	while (existingNumbers.includes(nextNumber)) {
+	while (existingNumbers.has(nextNumber)) {
 		nextNumber++;
 	}

 	return `Window ${nextNumber}`;
 };
apps/desktop/src/renderer/stores/tabs/store.ts (1)

298-326: Consider returning undefined or throwing on failure.

addPane returns an empty string "" when the window is not found (line 302). This could cause issues if callers don't check for empty string. Consider returning undefined or null to make the failure case more explicit.

-			addPane: (windowId) => {
+			addPane: (windowId): string | null => {
 				const state = get();
 				const window = state.windows.find((w) => w.id === windowId);
-				if (!window) return "";
+				if (!window) return null;

This would also require updating the WindowsStore interface in types.ts:

-	addPane: (windowId: string) => string;
+	addPane: (windowId: string) => string | null;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fac0d44 and 2c7a998.

📒 Files selected for processing (42)
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/DropOverlay.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView/GroupTabPane.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView/index.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (8 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.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/useTabContentDrop.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/TabContextMenu.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useGroupDrop.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useTabRename.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/UngroupDropZone/index.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/WindowContextMenu.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx (2 hunks)
  • apps/desktop/src/renderer/screens/main/index.tsx (3 hunks)
  • apps/desktop/src/renderer/stores/tabs/drag-logic.test.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/drag-logic.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/active-tab.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/group-operations.test.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/group-operations.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.test.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/split-operations.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/tab-crud.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/tab-ordering.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/validation.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/hooks.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/index.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/store.test.ts (0 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/useAgentHookListener.ts (2 hunks)
  • apps/desktop/src/renderer/stores/tabs/utils.ts (1 hunks)
💤 Files with no reviewable changes (25)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/DropOverlay.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx
  • apps/desktop/src/renderer/stores/tabs/drag-logic.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useTabRename.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView/GroupTabPane.tsx
  • apps/desktop/src/renderer/stores/tabs/helpers/validation.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/UngroupDropZone/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useTabContentDrop.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/tab-ordering.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/group-operations.test.ts
  • apps/desktop/src/renderer/stores/tabs/store.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/TabContextMenu.tsx
  • apps/desktop/src/renderer/stores/tabs/helpers/tab-crud.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/split-operations.ts
  • apps/desktop/src/renderer/stores/tabs/index.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/active-tab.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useGroupDrop.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/group-operations.ts
  • apps/desktop/src/renderer/stores/tabs/drag-logic.ts
🧰 Additional context used
📓 Path-based instructions (6)
apps/desktop/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/index.tsx
  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/WindowContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • apps/desktop/src/renderer/stores/tabs/store.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/stores/tabs/hooks.ts
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: Please use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/index.tsx
  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/WindowContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • apps/desktop/src/renderer/stores/tabs/store.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/stores/tabs/hooks.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid using any type - use explicit types instead for type safety
Use camelCase for variable and function names following existing codebase patterns
Keep diffs minimal with targeted edits only - avoid unnecessary changes when making modifications
Follow existing patterns and match the codebase style when writing new code

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/index.tsx
  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/WindowContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • apps/desktop/src/renderer/stores/tabs/store.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/stores/tabs/hooks.ts
**/components/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

**/components/**/*.tsx: Create one folder per component with structure: ComponentName/ComponentName.tsx + index.ts for barrel export
Co-locate tests next to the component file they test (e.g., ComponentName.test.tsx)
Co-locate dependencies (utils, hooks, constants, config, stories) next to the file using them
Use nested components/ subdirectory within a parent component only if a sub-component is used 2+ times within that parent; otherwise keep it in the parent's components/ folder
One component per file - avoid multi-component files

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/WindowContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
apps/desktop/src/renderer/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import Node.js modules (fs, path, os, net, etc.) in renderer process code - browser environment only

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/index.tsx
  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/WindowContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • apps/desktop/src/renderer/stores/tabs/store.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/stores/tabs/hooks.ts
apps/desktop/src/renderer/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Call IPC methods using window.ipcRenderer.invoke() with object parameters - TypeScript will infer the exact response type automatically

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/index.tsx
  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/WindowContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
🧠 Learnings (7)
📚 Learning: 2025-11-28T01:03:47.963Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.963Z
Learning: Applies to **/*.{ts,tsx} : Keep diffs minimal with targeted edits only - avoid unnecessary changes when making modifications

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
  • apps/desktop/src/renderer/screens/main/index.tsx
📚 Learning: 2025-11-28T01:03:47.963Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.963Z
Learning: Applies to apps/desktop/src/renderer/**/*.tsx : Call IPC methods using `window.ipcRenderer.invoke()` with object parameters - TypeScript will infer the exact response type automatically

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : Please use alias as defined in `tsconfig.json` when possible

Applied to files:

  • apps/desktop/src/renderer/screens/main/index.tsx
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary

Applied to files:

  • apps/desktop/src/renderer/screens/main/index.tsx
📚 Learning: 2025-11-28T01:03:47.963Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.963Z
Learning: Applies to apps/desktop/src/renderer/**/*.{ts,tsx} : Never import Node.js modules (fs, path, os, net, etc.) in renderer process code - browser environment only

Applied to files:

  • apps/desktop/src/renderer/screens/main/index.tsx
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.{ts,tsx,js,jsx} : For Electron interprocess communication, ALWAYS use tRPC as defined in `src/lib/trpc`

Applied to files:

  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
📚 Learning: 2025-11-28T01:03:47.963Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.963Z
Learning: Applies to packages/ui/src/**/*.tsx : Use shadcn/ui components and TailwindCSS v4 for all UI component styling in the shared UI package

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
🧬 Code graph analysis (13)
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx (2)
apps/desktop/src/renderer/stores/tabs/hooks.ts (2)
  • useWindows (4-4)
  • usePanes (5-5)
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/useWorkspaceRename.ts (1)
  • useWorkspaceRename (4-64)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (2)
apps/desktop/src/renderer/stores/tabs/hooks.ts (2)
  • usePanes (5-5)
  • useSetFocusedPane (29-30)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts (2)
  • setupFocusListener (173-187)
  • setupResizeHandlers (189-213)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx (2)
apps/desktop/src/renderer/stores/tabs/hooks.ts (1)
  • useWindows (4-4)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx (1)
  • WindowView (32-136)
apps/desktop/src/renderer/screens/main/index.tsx (1)
apps/desktop/src/renderer/stores/tabs/hooks.ts (4)
  • useSplitPaneVertical (37-38)
  • useSplitPaneHorizontal (39-40)
  • useActiveWindowIds (6-7)
  • useFocusedPaneIds (8-9)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/WindowContextMenu.tsx (1)
packages/ui/src/components/context-menu.tsx (5)
  • ContextMenu (241-241)
  • ContextMenuTrigger (242-242)
  • ContextMenuContent (243-243)
  • ContextMenuItem (244-244)
  • ContextMenuSeparator (248-248)
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts (1)
apps/desktop/src/renderer/stores/tabs/store.ts (1)
  • useWindowsStore (78-560)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (2)
apps/desktop/src/renderer/stores/tabs/hooks.ts (4)
  • useWindows (4-4)
  • useAddWindow (11-11)
  • useReorderWindowById (20-21)
  • useActiveWindowIds (6-7)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/index.tsx (1)
  • WindowItem (30-180)
apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts (2)
apps/desktop/src/renderer/stores/tabs/store.ts (1)
  • useWindowsStore (78-560)
apps/desktop/src/main/lib/terminal-manager.ts (1)
  • createOrAttach (41-174)
apps/desktop/src/renderer/stores/tabs/utils.ts (2)
apps/desktop/src/renderer/stores/tabs/types.ts (3)
  • Window (30-36)
  • PaneType (6-6)
  • Pane (12-24)
apps/desktop/src/renderer/stores/tabs/utils/terminal-naming.ts (1)
  • generateTerminalName (41-46)
apps/desktop/src/renderer/stores/tabs/store.ts (3)
apps/desktop/src/renderer/stores/tabs/utils.ts (5)
  • createWindowWithPane (86-108)
  • getPaneIdsForWindow (113-120)
  • extractPaneIdsFromLayout (22-27)
  • createPane (38-59)
  • isLastPaneInWindow (125-130)
apps/desktop/src/shared/types.ts (1)
  • MosaicNode (35-35)
apps/desktop/src/renderer/lib/electron-storage.ts (1)
  • electronStorage (20-20)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx (2)
apps/desktop/src/renderer/stores/tabs/hooks.ts (6)
  • useWindows (4-4)
  • useActiveWindowIds (6-7)
  • useFocusedPaneIds (8-9)
  • useAddWindow (11-11)
  • useSetActiveWindow (16-17)
  • useRemovePane (27-27)
apps/desktop/src/shared/hotkeys.ts (1)
  • HOTKEYS (28-151)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
apps/desktop/src/shared/types.ts (1)
  • MosaicNode (35-35)
apps/desktop/src/renderer/stores/tabs/hooks.ts (1)
apps/desktop/src/renderer/stores/tabs/store.ts (1)
  • useWindowsStore (78-560)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (31)
apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts (1)

27-35: Naming mismatch between frontend paneId and backend tabId.

The code correctly adapts to the new windows/panes model by passing paneId as tabId for backwards compatibility with the terminal manager. However, this creates a naming inconsistency that could cause confusion during future maintenance.

Consider adding a brief comment explaining this mapping, or plan to update the backend API (terminal-manager.ts) to use paneId terminology in a follow-up PR.

 			if (
 				Array.isArray(data.initialCommands) &&
 				data.initialCommands.length > 0
 			) {
 				const { paneId } = addWindow(data.workspace.id);
-				// Pre-create terminal session with initial commands
-				// Terminal component will attach to this session when it mounts
+				// Pre-create terminal session with initial commands
+				// Terminal component will attach to this session when it mounts
+				// Note: paneId is passed as tabId for backwards compatibility with terminal-manager
 				createOrAttach.mutate({
 					tabId: paneId,
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts (1)

26-35: LGTM!

The isAlreadyActive check correctly validates both workspace and focused pane to determine if attention is needed. The backwards compatibility comment on line 16-17 is helpful for maintainers understanding the tabId→paneId mapping.

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

1-33: LGTM!

Clean, focused component with proper typing and appropriate use of the context menu primitives. The destructive styling for "Close Window" is good UX practice.

The component is correctly co-located as a private sub-component within the WindowItem folder since it's used only once by WindowItem. No separate barrel export is needed.

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

56-67: Potential race condition between pane removal and layout update.

Calling removePane for each removed pane in a loop (lines 62-64) before updateWindowLayout (line 67) may cause state inconsistencies. The layout update happens after individual pane removals, but each removePane call might trigger its own layout recalculation in the store.

Consider batching the pane removals or ensuring the store handles this atomically:

-			// Remove panes that were removed via Mosaic UI
-			for (const removedId of removedPaneIds) {
-				removePane(removedId);
-			}
-
-			// Update the layout
-			updateWindowLayout(window.id, newLayout);
+			// Update the layout first, then remove orphaned panes
+			updateWindowLayout(window.id, newLayout);
+			
+			// Remove panes that were removed via Mosaic UI
+			for (const removedId of removedPaneIds) {
+				removePane(removedId);
+			}

Alternatively, verify that your store actions are designed to handle this sequence correctly.


72-110: Well-structured renderPane callback with proper defensive handling.

The fallback for missing panes (lines 77-83) is a good defensive practice that prevents crashes if the panes store becomes out of sync with the layout.


112-136: Clean implementation of the Mosaic layout with appropriate empty state handling.

The empty state message clearly communicates that the window will close when no panes remain, which aligns well with the PR's invariant that windows always have ≥1 pane.

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

1-35: Clean refactor to window-centric rendering.

The simplified logic correctly derives the active window from workspace context and renders the appropriate view. The implementation is concise and follows the minimal diff principle.

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

51-79: Well-structured pane component with proper event handling.

The component correctly:

  • Uses stopPropagation on close to prevent focus events
  • Wires context menu actions with the path parameter for split operations
  • Applies focused styling conditionally
apps/desktop/src/renderer/screens/main/index.tsx (2)

67-77: Verify split direction mapping is intentional.

The hotkey mapping appears swapped:

  • SPLIT_HORIZONTAL (Cmd+D) calls splitPaneVertical
  • SPLIT_VERTICAL (Cmd+Shift+D) calls splitPaneHorizontal

This could be intentional depending on naming convention: "horizontal split" might mean "create a horizontal divider" (which results in vertical stacking) vs "split horizontally" (side-by-side).

Please verify this matches the expected user experience described in the hotkey labels:

  • SPLIT_HORIZONTAL: "Split Terminal Window Horizontal"
  • SPLIT_VERTICAL: "Split Terminal Window Vertical"

43-55: Clean derivation of active window and focused pane state.

The chained lookups properly handle null cases and follow the expected data flow from workspace → window → pane.

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

26-31: Memoization correctly applied for filtered windows.

The useMemo correctly depends on activeWorkspaceId and allWindows, ensuring the filtered list only recomputes when necessary.


56-70: Window navigation logic is correct but doesn't wrap around.

The navigation stops at the first/last window rather than wrapping. This is a valid UX choice. If wrap-around is desired in the future, the boundary checks could be adjusted.


42-53: Terminal shortcuts correctly wired to window/pane operations.

The mapping makes sense from a user perspective:

  • "New Terminal" creates a new window (with its initial pane)
  • "Close Terminal" closes the focused pane (potentially closing the window if last pane)

The inline comment on line 49 helpfully documents the cascading close behavior.

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

17-22: Clear documentation of the prop aliasing during migration.

The comment on line 18 clearly explains the temporary aliasing approach. This is a pragmatic migration strategy that maintains API compatibility while transitioning internals.


84-89: Proper focus handling with defensive null check.

The callback correctly guards against undefined windowId before setting focused pane state.


196-202: The windowId parameter passed to setupFocusListener at line 199 is unused. The function signature declares it as _windowId (underscore prefix indicating intentional non-usage), and the implementation only uses xterm.textarea and the onFocus callback. The empty string fallback has no functional impact and doesn't mask any issues.

Likely an incorrect or invalid review comment.

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

103-149: LGTM!

The window rendering logic with framer-motion animations and drop indicators is well-structured. The conditional rendering of drop indicators before items and at the end of the list provides good visual feedback during drag operations.

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

119-179: LGTM!

The component structure is well-organized with proper keyboard accessibility (tabIndex, onKeyDown handlers), context menu integration, and visual feedback for drag states. The inline rename flow with blur/Enter/Escape handling is solid.

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

3-36: LGTM!

The type definitions are well-structured with clear JSDoc documentation. The separation of Pane and Window types, along with the use of discriminated properties for different pane types (terminal vs browser), follows good TypeScript patterns.


49-91: LGTM!

The WindowsStore interface provides a comprehensive API surface for window/pane management. The organization into window operations, pane operations, split operations, and query helpers is clean and intuitive.

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

38-59: LGTM!

The createPane function properly generates unique terminal names based on existing panes. The default type of "terminal" and the conditional naming logic are well-handled.


136-164: LGTM!

The removePaneFromLayout function correctly handles mosaic tree manipulation with proper null propagation and branch collapsing when one side becomes empty.

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

217-230: Splicing filtered arrays is safe but subtle.

The splice operations on lines 226-227 and 250-251 work correctly because they operate on filtered array copies, not the original state.windows. The final set() replaces the state with the reordered results. This is correct but the pattern could be clearer with explicit array manipulation.

Also applies to: 232-254


126-171: LGTM!

The removeWindow implementation properly handles:

  • Killing terminals for all panes in the window
  • Cleaning up pane state
  • Finding the next window to activate via history or position
  • Cleaning up focused pane tracking

The cascading cleanup logic is thorough.


18-76: LGTM!

The findNextWindow helper implements a sensible priority order: MRU history first, then positional (next/prev), then fallback to first available. This provides good UX for window navigation after closing.


430-525: LGTM!

The split operations (splitPaneVertical and splitPaneHorizontal) correctly use updateTree from react-mosaic-component for path-based splits and handle the simpler case of appending to the root layout. The focus is properly updated to the new pane.

apps/desktop/src/renderer/stores/tabs/hooks.ts (5)

1-9: Clean zustand hook exports for window/pane state.

The hooks follow idiomatic zustand patterns with simple selectors. This aligns with the coding guidelines preferring zustand for state management.


11-23: Window action hooks are correctly exported.

Action selectors return stable function references from the store, which is the recommended pattern for zustand actions.


25-34: Pane action hooks follow consistent patterns.

The pane-level operations (add, remove, rename, focus, attention tracking) are cleanly exposed as individual hooks, maintaining separation of concerns.


36-40: Split hooks align with the new pane-based model.

Vertical and horizontal pane splitting operations are correctly exposed, supporting the PR's goal of pane-level content management within windows.


42-50: Query helper hooks export imperative lookup functions.

These hooks return functions that perform lookups via get() internally. This is a valid pattern for use in event handlers or effects. Note that components using these won't automatically re-render when windows/panes change—they'll need to also subscribe to useWindows() or usePanes() if reactivity is required.

Comment on lines +89 to +92
// Clear drop index when not hovering
if (!isOver && dropIndex !== null) {
setDropIndex(null);
}

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

Avoid setting state during render.

Calling setDropIndex(null) synchronously during the render phase can cause React warnings and unexpected re-render loops. Move this side effect into a useEffect.

+	// Clear drop index when not hovering
+	React.useEffect(() => {
+		if (!isOver && dropIndex !== null) {
+			setDropIndex(null);
+		}
+	}, [isOver, dropIndex]);

-	// Clear drop index when not hovering
-	if (!isOver && dropIndex !== null) {
-		setDropIndex(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
// Clear drop index when not hovering
if (!isOver && dropIndex !== null) {
setDropIndex(null);
}
// Clear drop index when not hovering
React.useEffect(() => {
if (!isOver && dropIndex !== null) {
setDropIndex(null);
}
}, [isOver, dropIndex]);
🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
around lines 89-92: the component is calling setDropIndex(null) directly during
render when !isOver && dropIndex !== null, which produces React warnings; move
this state change into a useEffect that depends on isOver and dropIndex, and
inside the effect check the same condition and call setDropIndex(null) there so
the state update runs as a side effect after render rather than during render.

Comment on lines +554 to +563
name: "windows-storage",
storage: electronStorage,
},
),
{ name: "TabsStore" },
{ name: "WindowsStore" },
),
);

// Keep old export name for backwards compatibility during migration
export const useTabsStore = useWindowsStore;

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

🏁 Script executed:

rg -n "tabs-storage" --type=ts

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

rg -n "tabs-storage" --type=ts --type=js

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

fd -e ts -e tsx -e js -e jsx | xargs grep -l "migrate\|migration" | head -20

Repository: superset-sh/superset

Length of output: 146


🏁 Script executed:

rg -n "windows-storage" --type=ts -A 5 -B 5

Repository: superset-sh/superset

Length of output: 831


🏁 Script executed:

cat -n apps/desktop/src/renderer/stores/tabs/store.ts | head -100

Repository: superset-sh/superset

Length of output: 3590


🏁 Script executed:

cat -n apps/desktop/src/renderer/stores/tabs/store.ts | tail -100

Repository: superset-sh/superset

Length of output: 3299


🏁 Script executed:

cat -n apps/cli/src/lib/storage/adapter.ts

Repository: superset-sh/superset

Length of output: 1987


🏁 Script executed:

rg -n "migration|migrate" apps/desktop/src/renderer/stores --type=ts -B 3 -A 3

Repository: superset-sh/superset

Length of output: 440


🏁 Script executed:

rg -n "electronStorage" apps/desktop/src/renderer --type=ts -B 2 -A 5

Repository: superset-sh/superset

Length of output: 3584


🏁 Script executed:

fd -name "*electron*storage*" -type f

Repository: superset-sh/superset

Length of output: 294


🏁 Script executed:

cat -n apps/desktop/src/renderer/lib/electron-storage.ts

Repository: superset-sh/superset

Length of output: 883


Storage migration for old data is missing.

While the useTabsStore alias provides good backward compatibility for code imports, there is no migration logic to handle data persisted under the old storage key. Users upgrading will lose their window and pane state.

Add an onRehydrateStorage handler (similar to the pattern in apps/desktop/src/renderer/stores/theme/store.ts) to migrate persisted data from the previous storage key to "windows-storage", or document the data loss as an acceptable trade-off for this release.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (1)

47-87: Move setDropIndex out of render phase (use useEffect)

setDropIndex(null) is currently called during render when !isOver && dropIndex !== null (lines 89-92), which can trigger React warnings and unstable render loops. This is the same issue previously flagged; it should be handled as a side effect.

Here’s a minimal fix using useEffect:

-import { useMemo, useRef, useState } from "react";
+import { useEffect, useMemo, useRef, useState } from "react";
@@
-  const [{ isOver }, drop] = useDrop<DragItem, void, { isOver: boolean }>({
+  const [{ isOver }, drop] = useDrop<DragItem, void, { isOver: boolean }>({
@@
-    collect: (monitor) => ({
-      isOver: monitor.isOver(),
-    }),
-  });
-
-  // Clear drop index when not hovering
-  if (!isOver && dropIndex !== null) {
-    setDropIndex(null);
-  }
+    collect: (monitor) => ({
+      isOver: monitor.isOver(),
+    }),
+  });
+
+  // Clear drop index when not hovering
+  useEffect(() => {
+    if (!isOver && dropIndex !== null) {
+      setDropIndex(null);
+    }
+  }, [isOver, dropIndex]);

This preserves behavior while ensuring state updates happen after render.

Also applies to: 89-92

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

546-553: Storage migration for old data is missing.

This duplicates a concern from a previous review. The storage key changed from "tabs-storage" to "windows-storage", but there's no migration logic. Users upgrading will lose their existing window/pane state. Consider adding an onRehydrateStorage handler to migrate data from the old key, or explicitly document this as an acceptable breaking change for the release.

🧹 Nitpick comments (9)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/WindowContextMenu.tsx (1)

1-33: Consider component folder structure and test coverage.

As per coding guidelines, components should follow the structure ComponentName/ComponentName.tsx with an index.ts for barrel export, and tests should be co-located. Since this is a sub-component of WindowItem, consider:

  1. Moving to WindowContextMenu/WindowContextMenu.tsx with a WindowContextMenu/index.ts barrel export.
  2. Adding a co-located test file WindowContextMenu.test.tsx to verify the menu renders correctly and callbacks are invoked.

</review_comment_end>

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

30-31: Simplify containerRef typing and remove cast in ref callback

You can avoid the React.MutableRefObject cast and make the ref type-safe by including null in the ref type and assigning directly in the callback:

-import { useMemo, useRef, useState } from "react";
+import { useEffect, useMemo, useRef, useState } from "react";
@@
-const TabsView() {
+const TabsView() {
@@
-  const [dropIndex, setDropIndex] = useState<number | null>(null);
-  const containerRef = useRef<HTMLElement>(null);
+  const [dropIndex, setDropIndex] = useState<number | null>(null);
+  const containerRef = useRef<HTMLElement | null>(null);
@@
-    <nav
-      ref={(node) => {
-        drop(node);
-        (containerRef as React.MutableRefObject<HTMLElement | null>).current =
-          node;
-      }}
+    <nav
+      ref={(node) => {
+        drop(node);
+        containerRef.current = node;
+      }}

(Effect import is also needed for the next comment’s fix; if you apply that separately, drop the useEffect part here.)

Also applies to: 95-100


23-29: Window-centric filtering and rendering look consistent with store API

The switch from tabs to windows is wired cleanly: windows is filtered by activeWorkspaceId, addWindow is guarded on workspace presence, and activeWindowIds[activeWorkspaceId || ""] === window.id keeps the active state scoped per workspace. The framer-motion layout group and drop indicators integrate well with the new WindowItem component.

Optional: consider renaming TabsView to something window-oriented (e.g., WindowsSidebar) in a later cleanup to better reflect the new model, but this is not blocking.

Also applies to: 33-45, 103-149

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

50-76: Consider extracting collectPaneIds outside the component for clarity and reuse.

The collectPaneIds helper is re-created on every render inside the flatMap callback. While not a significant performance issue, extracting it as a standalone utility (or importing extractPaneIdsFromLayout from renderer/stores/tabs/utils which already exists per the relevant snippets) would improve readability and reduce duplication.

The utility extractPaneIdsFromLayout in renderer/stores/tabs/utils.ts appears to do the same thing. Consider using it:

-	const workspacePaneIds = new Set(
-		workspaceWindows.flatMap((w) => {
-			// Extract pane IDs from the layout (which is a MosaicNode<string>)
-			const collectPaneIds = (node: unknown): string[] => {
-				if (typeof node === "string") return [node];
-				if (
-					node &&
-					typeof node === "object" &&
-					"first" in node &&
-					"second" in node
-				) {
-					const branch = node as { first: unknown; second: unknown };
-					return [
-						...collectPaneIds(branch.first),
-						...collectPaneIds(branch.second),
-					];
-				}
-				return [];
-			};
-			return collectPaneIds(w.layout);
-		}),
-	);
+	const workspacePaneIds = new Set(
+		workspaceWindows.flatMap((w) => extractPaneIdsFromLayout(w.layout)),
+	);

You'd need to add the import:

import { extractPaneIdsFromLayout } from "renderer/stores/tabs/utils";
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx (1)

46-70: Consider batching pane removals to avoid multiple state updates.

When multiple panes are removed via the Mosaic UI (e.g., closing a split), calling removePane in a loop triggers multiple store updates. While React 18+ batches updates within event handlers, the store's removePane implementation (from the relevant snippet at store.ts) performs terminal cleanup and may trigger side effects per call.

Consider calling updateWindowLayout first and letting the store's updateWindowLayout implementation handle the pane cleanup (which it already does per lines 238-268 in store.ts), avoiding redundant removals.

If the store's updateWindowLayout already handles removed pane cleanup, you can simplify:

 	const handleLayoutChange = useCallback(
 		(newLayout: MosaicNode<string> | null) => {
 			if (!newLayout) {
 				// This shouldn't happen as we handle last pane removal in removePane
 				return;
 			}

-			const oldPaneIds = extractPaneIdsFromLayout(window.layout);
-			const newPaneIds = extractPaneIdsFromLayout(newLayout);
-
-			// Find removed panes (e.g., from Mosaic close button)
-			const removedPaneIds = oldPaneIds.filter(
-				(id) => !newPaneIds.includes(id),
-			);
-
-			// Remove panes that were removed via Mosaic UI
-			for (const removedId of removedPaneIds) {
-				removePane(removedId);
-			}
-
 			// Update the layout
 			updateWindowLayout(window.id, newLayout);
 		},
-		[window.id, window.layout, updateWindowLayout, removePane],
+		[window.id, updateWindowLayout],
 	);
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx (1)

68-77: Consider renaming tabId prop to paneId in Terminal for consistency.

The Terminal component still uses tabId as its prop name while receiving paneId. This works for backwards compatibility but creates naming confusion in the new window/pane model. Consider a follow-up task to rename the Terminal prop.

This is a follow-up cleanup item - the current code works correctly. When ready, update Terminal's interface:

// In Terminal component
interface TerminalProps {
  paneId: string;  // was: tabId
  workspaceId: string;
}
apps/desktop/src/renderer/stores/tabs/utils.ts (1)

7-9: ID generation is adequate but consider UUID for stronger uniqueness.

The current approach (Date.now() + random string) works for typical client-side usage. If IDs ever need to be globally unique across clients or persisted long-term, consider using crypto.randomUUID() which is available in modern browsers.

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

16-16: Function name still references "Tab" but operates on panes.

The import killTerminalForTab should ideally be renamed to killTerminalForPane to align with the new naming conventions. This is a minor inconsistency in the refactoring.


222-235: Array mutation occurs on a filtered copy, so it's safe but could be clearer.

The splice operations on lines 231-232 mutate workspaceWindows, which is a new array created by filter(). This is technically safe since filter() returns a shallow copy, but using immutable operations would make the intent clearer.

-				reorderWindows: (workspaceId, startIndex, endIndex) => {
-					const state = get();
-					const workspaceWindows = state.windows.filter(
-						(w) => w.workspaceId === workspaceId,
-					);
-					const otherWindows = state.windows.filter(
-						(w) => w.workspaceId !== workspaceId,
-					);
-
-					const [removed] = workspaceWindows.splice(startIndex, 1);
-					workspaceWindows.splice(endIndex, 0, removed);
-
-					set({ windows: [...otherWindows, ...workspaceWindows] });
-				},
+				reorderWindows: (workspaceId, startIndex, endIndex) => {
+					const state = get();
+					const workspaceWindows = state.windows.filter(
+						(w) => w.workspaceId === workspaceId,
+					);
+					const otherWindows = state.windows.filter(
+						(w) => w.workspaceId !== workspaceId,
+					);
+
+					const reordered = [...workspaceWindows];
+					const [removed] = reordered.splice(startIndex, 1);
+					reordered.splice(endIndex, 0, removed);
+
+					set({ windows: [...otherWindows, ...reordered] });
+				},
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c7a998 and 3072c96.

📒 Files selected for processing (42)
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/DropOverlay.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView/GroupTabPane.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView/index.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (8 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.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/useTabContentDrop.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/TabContextMenu.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useGroupDrop.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useTabRename.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/UngroupDropZone/index.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/WindowContextMenu.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx (2 hunks)
  • apps/desktop/src/renderer/screens/main/index.tsx (3 hunks)
  • apps/desktop/src/renderer/stores/tabs/drag-logic.test.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/drag-logic.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/active-tab.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/group-operations.test.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/group-operations.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.test.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/split-operations.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/tab-crud.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/tab-ordering.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/validation.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/hooks.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/index.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/store.test.ts (0 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/useAgentHookListener.ts (2 hunks)
  • apps/desktop/src/renderer/stores/tabs/utils.ts (1 hunks)
💤 Files with no reviewable changes (25)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/UngroupDropZone/index.tsx
  • apps/desktop/src/renderer/stores/tabs/store.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts
  • apps/desktop/src/renderer/stores/tabs/drag-logic.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useGroupDrop.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/validation.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useTabRename.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/active-tab.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/TabContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useTabContentDrop.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/tab-crud.ts
  • apps/desktop/src/renderer/stores/tabs/index.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/tab-ordering.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/split-operations.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/DropOverlay.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView/GroupTabPane.tsx
  • apps/desktop/src/renderer/stores/tabs/helpers/group-operations.test.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.test.ts
  • apps/desktop/src/renderer/stores/tabs/drag-logic.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/group-operations.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
  • apps/desktop/src/renderer/stores/tabs/hooks.ts
🧰 Additional context used
📓 Path-based instructions (6)
apps/desktop/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc

Files:

  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/WindowContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/stores/tabs/store.ts
  • apps/desktop/src/renderer/stores/tabs/utils.ts
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: Please use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary

Files:

  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/WindowContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/stores/tabs/store.ts
  • apps/desktop/src/renderer/stores/tabs/utils.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid using any type - use explicit types instead for type safety
Use camelCase for variable and function names following existing codebase patterns
Keep diffs minimal with targeted edits only - avoid unnecessary changes when making modifications
Follow existing patterns and match the codebase style when writing new code

Files:

  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/WindowContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/stores/tabs/store.ts
  • apps/desktop/src/renderer/stores/tabs/utils.ts
**/components/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

**/components/**/*.tsx: Create one folder per component with structure: ComponentName/ComponentName.tsx + index.ts for barrel export
Co-locate tests next to the component file they test (e.g., ComponentName.test.tsx)
Co-locate dependencies (utils, hooks, constants, config, stories) next to the file using them
Use nested components/ subdirectory within a parent component only if a sub-component is used 2+ times within that parent; otherwise keep it in the parent's components/ folder
One component per file - avoid multi-component files

Files:

  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/WindowContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
apps/desktop/src/renderer/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import Node.js modules (fs, path, os, net, etc.) in renderer process code - browser environment only

Files:

  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/WindowContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/stores/tabs/store.ts
  • apps/desktop/src/renderer/stores/tabs/utils.ts
apps/desktop/src/renderer/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Call IPC methods using window.ipcRenderer.invoke() with object parameters - TypeScript will infer the exact response type automatically

Files:

  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/WindowContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx
  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
🧠 Learnings (7)
📚 Learning: 2025-11-28T01:03:47.963Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.963Z
Learning: Applies to **/*.{ts,tsx} : Keep diffs minimal with targeted edits only - avoid unnecessary changes when making modifications

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
📚 Learning: 2025-11-28T01:03:47.963Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.963Z
Learning: Applies to packages/ui/src/**/*.tsx : Use shadcn/ui components and TailwindCSS v4 for all UI component styling in the shared UI package

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.{ts,tsx,js,jsx} : For Electron interprocess communication, ALWAYS use tRPC as defined in `src/lib/trpc`

Applied to files:

  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
📚 Learning: 2025-11-28T01:03:47.963Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.963Z
Learning: Applies to apps/desktop/src/renderer/**/*.tsx : Call IPC methods using `window.ipcRenderer.invoke()` with object parameters - TypeScript will infer the exact response type automatically

Applied to files:

  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
📚 Learning: 2025-11-28T01:03:47.963Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.963Z
Learning: Applies to apps/desktop/src/renderer/**/*.{ts,tsx} : Never import Node.js modules (fs, path, os, net, etc.) in renderer process code - browser environment only

Applied to files:

  • apps/desktop/src/renderer/screens/main/index.tsx
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : Please use alias as defined in `tsconfig.json` when possible

Applied to files:

  • apps/desktop/src/renderer/screens/main/index.tsx
🧬 Code graph analysis (9)
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx (2)
apps/desktop/src/renderer/stores/tabs/hooks.ts (2)
  • useWindows (4-4)
  • usePanes (5-5)
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/useWorkspaceRename.ts (1)
  • useWorkspaceRename (4-64)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (3)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/types.ts (1)
  • DragItem (1-4)
apps/desktop/src/renderer/stores/tabs/hooks.ts (4)
  • useWindows (4-4)
  • useAddWindow (11-11)
  • useReorderWindowById (20-21)
  • useActiveWindowIds (6-7)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/index.tsx (1)
  • WindowItem (30-174)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/WindowContextMenu.tsx (1)
packages/ui/src/components/context-menu.tsx (5)
  • ContextMenu (241-241)
  • ContextMenuTrigger (242-242)
  • ContextMenuContent (243-243)
  • ContextMenuItem (244-244)
  • ContextMenuSeparator (248-248)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx (4)
apps/desktop/src/renderer/stores/tabs/types.ts (2)
  • Window (30-36)
  • Pane (12-24)
apps/desktop/src/renderer/stores/tabs/utils.ts (2)
  • getPaneIdsForWindow (113-120)
  • extractPaneIdsFromLayout (22-27)
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/useAgentHookListener.ts (1)
apps/desktop/src/renderer/stores/tabs/store.ts (1)
  • useWindowsStore (78-554)
apps/desktop/src/renderer/screens/main/index.tsx (1)
apps/desktop/src/renderer/stores/tabs/hooks.ts (4)
  • useSplitPaneVertical (36-37)
  • useSplitPaneHorizontal (38-39)
  • useActiveWindowIds (6-7)
  • useFocusedPaneIds (8-9)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx (2)
apps/desktop/src/renderer/stores/tabs/hooks.ts (6)
  • useWindows (4-4)
  • useActiveWindowIds (6-7)
  • useFocusedPaneIds (8-9)
  • useAddWindow (11-11)
  • useSetActiveWindow (16-17)
  • useRemovePane (27-27)
apps/desktop/src/shared/hotkeys.ts (1)
  • HOTKEYS (28-151)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
apps/desktop/src/shared/types.ts (1)
  • MosaicNode (35-35)
apps/desktop/src/renderer/stores/tabs/utils.ts (2)
apps/desktop/src/renderer/stores/tabs/types.ts (3)
  • Window (30-36)
  • PaneType (6-6)
  • Pane (12-24)
apps/desktop/src/renderer/stores/tabs/utils/terminal-naming.ts (1)
  • generateTerminalName (41-46)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (17)
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts (2)

16-33: LGTM! Agent-complete handling correctly migrated to pane-centric model.

The logic properly looks up the pane, determines if it's already focused by checking both activeWindowIds and focusedPaneIds, and only sets attention when the pane isn't currently visible. The backwards compatibility comment for tabId → paneId is helpful.


34-56: LGTM! Focus-tab handling correctly chains workspace switch with window/pane focus.

The handler properly:

  1. Validates the pane exists before proceeding
  2. Switches workspace first via mutation
  3. Fetches fresh state in onSuccess to avoid stale closures
  4. Sets both active window and focused pane
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx (1)

30-49: LGTM! Clean handler implementations.

The handleFocus and handleClosePane handlers are properly implemented with appropriate event propagation handling.

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

68-78: Verify hotkey-to-split-function mapping is intentional.

The naming is potentially confusing:

  • SPLIT_HORIZONTAL ("Split Right") calls splitPaneVertical
  • SPLIT_VERTICAL ("Split Down") calls splitPaneHorizontal

Based on the store implementation (where splitPaneVertical uses direction: "row" for side-by-side splits and splitPaneHorizontal uses direction: "column" for stacked splits), this mapping appears correct. The function names refer to the orientation of the resulting panes, not the divider direction.

Please confirm this matches the intended UX:

  • Cmd+D ("Split Right") → creates a new pane to the right
  • Cmd+Shift+D ("Split Down") → creates a new pane below

53-56: LGTM! Correct derivation chain for active pane.

The logic properly chains: activeWorkspaceId → activeWindowId → focusedPaneId, with null checks at each step.

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

4-11: LGTM!

The migration from tab-based hooks to window/pane hooks is clean and follows the established import patterns. The alias paths are correctly using the renderer/stores barrel export as defined in the tsconfig.


26-39: LGTM!

The memoization of windows is appropriate since it involves filtering. The derived state for activeWindowId and focusedPaneId is correctly computed from the store state without unnecessary memoization.


56-70: Navigation wrapping behavior is worth confirming.

The prev/next terminal shortcuts stop at the first/last window rather than wrapping around. This is a valid UX choice, but verify this matches the intended behavior—some users expect circular navigation.

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

22-33: LGTM!

The recursive extraction handles both leaf nodes (strings) and branch nodes correctly. The spread operator approach is clean and readable.


65-80: LGTM!

The window naming logic correctly finds gaps in the sequence (e.g., if "Window 1" and "Window 3" exist, it will assign "Window 2" to the next window). This is a nice touch for maintaining predictable naming.


136-164: LGTM!

The removePaneFromLayout function correctly handles all edge cases: leaf removal, branch collapse when one child is removed, and preservation of structure when both children remain. The null propagation is clean.

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

3-24: LGTM!

The Pane interface cleanly separates terminal-specific properties (command, cwd) from browser/preview properties (url) while maintaining a common base. The JSDoc comments provide helpful context.


41-47: LGTM!

The state structure is well-designed:

  • windows as array maintains ordering for sidebar display
  • panes as Record<string, Pane> enables O(1) lookups
  • activeWindowIds and focusedPaneIds as maps allow per-workspace/per-window tracking
  • windowHistoryStacks supports MRU-based navigation

52-90: LGTM!

The action signatures are comprehensive and cover all CRUD operations plus specialized behaviors (splits, focus, attention flags). The query helpers return derived state appropriately.

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

89-124: LGTM!

The addWindow action correctly enforces the invariant that windows always have at least one pane by using createWindowWithPane. The history stack management properly prepends the current active window before switching.


333-378: LGTM!

The removePane action correctly implements the "last pane closes window" behavior. The defensive check on line 352-356 is a good safeguard even though isLastPaneInWindow should prevent reaching that state.


424-471: LGTM!

The split operations correctly handle both cases: splitting at a specific path (using updateTree) and splitting the root layout. The new pane is properly registered and focused after the split.

Comment on lines +27 to +29
<ContextMenuItem onSelect={onClose} className="text-destructive">
Close Window
</ContextMenuItem>

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for window state persistence/serialization
echo "=== Searching for window state persistence patterns ==="
rg -nP --type=ts -C3 'persist.*[Ww]indow|save.*[Ww]indow|serialize.*[Ww]indow|restore.*[Ww]indow' apps/desktop/src 2>/dev/null | head -50

echo -e "\n=== Searching for undo/history/revert mechanisms ==="
rg -nP --type=ts -C3 'undo|history|revert' apps/desktop/src 2>/dev/null | head -50

echo -e "\n=== Searching for closeWindow implementation ==="
rg -nP --type=ts 'closeWindow|close.*[Ww]indow' apps/desktop/src --max-count=20

echo -e "\n=== Checking WindowContextMenu usage and callback definition ==="
rg -nP --type=ts -B5 -A5 'WindowContextMenu' apps/desktop/src 2>/dev/null | head -60

Repository: superset-sh/superset

Length of output: 12598


🏁 Script executed:

#!/bin/bash
# Check the tRPC window.close implementation
echo "=== tRPC window.close implementation ==="
rg -nP --type=ts -B5 -A10 'window\.close' apps/desktop/src --max-count=20 | head -80

# Check handleRemoveWindow implementation
echo -e "\n=== handleRemoveWindow implementation in WindowItem ==="
rg -nP --type=ts -B3 -A8 'handleRemoveWindow' apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/index.tsx

# Check for confirmation dialogs in the entire codebase
echo -e "\n=== Searching for confirmation/alert dialogs related to window/close ==="
rg -nP --type=ts 'AlertDialog|confirm|ConfirmDialog' apps/desktop/src/renderer --max-count=15 | head -50

# Check if there's middleware or backend validation
echo -e "\n=== Checking for window close routes/handlers in main process ==="
fd -e ts -e tsx apps/desktop/src -type f -exec grep -l 'window-close\|window\.close' {} \; 2>/dev/null

Repository: superset-sh/superset

Length of output: 6051


Add confirmation dialog for destructive Close Window action.

The "Close Window" action in the context menu currently closes windows without confirmation. While closed windows cannot be recovered within a session (only via app restart), this behavior is inconsistent with how other destructive actions are handled—the codebase already implements DeleteWorkspaceDialog with confirmation for workspace deletion. Add an alert dialog to confirm window closure before executing removeWindow().

Comment on lines +49 to +50
const name =
type === "terminal" ? generateTerminalName(existingNames) : `${type} 1`;

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

Non-terminal pane naming doesn't account for existing panes of the same type.

For non-terminal types, the name is hardcoded to ${type} 1 regardless of how many panes of that type already exist. This will cause duplicate names when multiple browser/editor/preview panes are created.

-	const name =
-		type === "terminal" ? generateTerminalName(existingNames) : `${type} 1`;
+	let name: string;
+	if (type === "terminal") {
+		name = generateTerminalName(existingNames);
+	} else {
+		const existingTypeNames = Object.values(existingPanes)
+			.filter((pane) => pane.type === type)
+			.map((pane) => pane.name);
+		const count = existingTypeNames.length + 1;
+		name = `${type} ${count}`;
+	}
🤖 Prompt for AI Agents
In apps/desktop/src/renderer/stores/tabs/utils.ts around lines 49-50, the
non-terminal pane name is always set to `${type} 1`, causing duplicate names;
change the logic to compute the next available numeric suffix for that pane type
by scanning existingNames for entries that start with `${type}` (and parse
trailing numbers), determine the highest existing index, and set the new name to
`${type} ${highestIndex + 1}` so each new non-terminal pane of the same type
gets a unique incremented name.

- Refactor from "tabs can be singles OR groups" to "windows always contain panes" model
- Windows now always have ≥1 pane (invariant enforced at creation time)
- Remove ungroup concepts - panes can only be split/closed
- Simplify sidebar to show only windows (no expandable children)
- Drag-and-drop now only reorders windows in sidebar
- Window names are now static at creation time (e.g., "Window 5" stays "Window 5")
- Add validation in setActiveWindow and split operations
- Add efficient useWindowNeedsAttention selector hook
- Delete ~3,200 lines of unused code (old tab grouping logic, tests, helpers)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Delete hooks.ts containing ~20 one-liner wrapper hooks
- Update all consumers to use useWindowsStore selectors directly
- Simplify PaneType to just "terminal" (removed speculative unused types)
- Remove unused Pane properties (command, cwd, url)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
The test was incorrectly catching the "Directory should not exist"
error which has no .code property, causing confusing "Received: undefined"
failures instead of clear "directory still exists" messages.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (1)

84-87: State update during render persists.

This synchronous setDropIndex(null) call during render can cause React warnings and re-render loops. The previous review suggested moving this to a useEffect.

Apply this diff:

+import { useMemo, useRef, useState, useEffect } from "react";
-import { useMemo, useRef, useState } from "react";

// ... after useDrop hook ...

-	// Clear drop index when not hovering
-	if (!isOver && dropIndex !== null) {
-		setDropIndex(null);
-	}
+	// Clear drop index when not hovering
+	useEffect(() => {
+		if (!isOver && dropIndex !== null) {
+			setDropIndex(null);
+		}
+	}, [isOver, dropIndex]);
apps/desktop/src/renderer/stores/tabs/store.ts (1)

547-549: Storage migration for existing data is still missing.

The storage key changed from "tabs-storage" to "windows-storage", but there's no migration logic to handle existing persisted data. Users upgrading will lose their previous state.

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

49-50: Non-terminal pane naming still doesn't account for duplicates.

This is the same issue from the previous review. While PaneType currently only includes "terminal", the code structure anticipates other pane types (e.g., browser, editor, preview). When those types are added, hardcoding ${type} 1 will create duplicate names.

🧹 Nitpick comments (7)
apps/desktop/src/main/lib/terminal-manager.test.ts (1)

332-336: Existence check now swallows all fs errors, not just ENOENT

The simplified stat().then(...).catch(() => false) is concise, but it will treat any fs.stat failure (including unexpected ones like EACCES) as "directory does not exist", potentially masking real issues. Consider preserving the previous behavior by only mapping ENOENT to false and rethrowing other errors, e.g.:

-      const exists = await fs
-        .stat(historyDir)
-        .then(() => true)
-        .catch(() => false);
+      const exists = await fs
+        .stat(historyDir)
+        .then(() => true)
+        .catch((error: NodeJS.ErrnoException) => {
+          if (error.code !== "ENOENT") throw error;
+          return false;
+        });

This keeps the test readable while ensuring unexpected filesystem problems still fail the test, rather than silently passing. Based on learnings, this maintains repeatable, trustworthy tests.

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

50-76: Consider using the existing extractPaneIdsFromLayout utility.

The inline collectPaneIds function duplicates logic that already exists in renderer/stores/tabs/utils. This would reduce code duplication and ensure consistent behavior.

+import { extractPaneIdsFromLayout } from "renderer/stores/tabs/utils";
...
 	// Check if any pane in windows belonging to this workspace needs attention
 	const workspaceWindows = windows.filter((w) => w.workspaceId === id);
-	const workspacePaneIds = new Set(
-		workspaceWindows.flatMap((w) => {
-			// Extract pane IDs from the layout (which is a MosaicNode<string>)
-			const collectPaneIds = (node: unknown): string[] => {
-				if (typeof node === "string") return [node];
-				if (
-					node &&
-					typeof node === "object" &&
-					"first" in node &&
-					"second" in node
-				) {
-					const branch = node as { first: unknown; second: unknown };
-					return [
-						...collectPaneIds(branch.first),
-						...collectPaneIds(branch.second),
-					];
-				}
-				return [];
-			};
-			return collectPaneIds(w.layout);
-		}),
-	);
+	const workspacePaneIds = new Set(
+		workspaceWindows.flatMap((w) => extractPaneIdsFromLayout(w.layout)),
+	);

46-47: Minor: Consider combining store selectors to reduce subscriptions.

Two separate useWindowsStore calls create two subscriptions. If performance becomes a concern, you could combine them:

-	const windows = useWindowsStore((s) => s.windows);
-	const panes = useWindowsStore((s) => s.panes);
+	const { windows, panes } = useWindowsStore((s) => ({
+		windows: s.windows,
+		panes: s.panes,
+	}));

Note: This requires Zustand's shallow equality check to avoid unnecessary re-renders, or use useShallow from zustand/react/shallow.

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

62-65: Consider batching pane removals for efficiency.

Multiple removePane calls in a loop will trigger multiple state updates. While Zustand batches updates within the same tick, consider adding a removePanes(paneIds: string[]) batch method to the store if users frequently close multiple panes at once (e.g., via Mosaic drag operations that reorganize layouts).

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

90-95: Ref combination pattern works but could be cleaner.

The ref assignment combining drop and containerRef works correctly. Consider using a callback ref utility or useMergeRefs from a library for cleaner composition, though this is not blocking.


118-121: Consider early return if no workspace.

The fallback activeWorkspaceId || "" for the lookup works but could mask issues. Since handleAddWindow already guards for !activeWorkspaceId, consider adding an early return at the component level if there's no active workspace.

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

18-23: Props type should be updated to reflect pane terminology.

The aliasing const paneId = tabId works but the underlying TerminalProps type still uses tabId. Consider updating the interface to use paneId for consistency with the new model, even if it requires updating callers.

#!/bin/bash
# Check TerminalProps definition and usages
rg -n "interface TerminalProps" apps/desktop/src/renderer --type=ts -A 5
rg -n "tabId.*TerminalProps\|TerminalProps.*tabId" apps/desktop/src/renderer --type=ts
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3072c96 and aa39e87.

📒 Files selected for processing (43)
  • apps/desktop/src/main/lib/terminal-manager.test.ts (1 hunks)
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/DropOverlay.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView/GroupTabPane.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView/index.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (8 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.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/useTabContentDrop.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/TabContextMenu.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useGroupDrop.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useTabRename.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/UngroupDropZone/index.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/WindowContextMenu.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/index.tsx (3 hunks)
  • apps/desktop/src/renderer/stores/tabs/drag-logic.test.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/drag-logic.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/active-tab.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/group-operations.test.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/group-operations.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.test.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/split-operations.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/tab-crud.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/tab-ordering.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/validation.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/hooks.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/index.ts (0 hunks)
  • apps/desktop/src/renderer/stores/tabs/store.test.ts (0 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/useAgentHookListener.ts (2 hunks)
  • apps/desktop/src/renderer/stores/tabs/utils.ts (1 hunks)
💤 Files with no reviewable changes (26)
  • apps/desktop/src/renderer/stores/tabs/drag-logic.test.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/split-operations.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/validation.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/active-tab.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView/GroupTabPane.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useTabRename.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/group-operations.test.ts
  • apps/desktop/src/renderer/stores/tabs/store.test.ts
  • apps/desktop/src/renderer/stores/tabs/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/DropOverlay.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/TabContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/helpers/tab-crud.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.test.ts
  • apps/desktop/src/renderer/stores/tabs/drag-logic.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx
  • apps/desktop/src/renderer/stores/tabs/helpers/tab-ordering.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/group-operations.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/UngroupDropZone/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useTabContentDrop.ts
  • apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useGroupDrop.ts
  • apps/desktop/src/renderer/stores/tabs/hooks.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/WindowContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
🧰 Additional context used
📓 Path-based instructions (8)
apps/desktop/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc

Files:

  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/main/lib/terminal-manager.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/store.ts
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: Please use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary

Files:

  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/main/lib/terminal-manager.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/store.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid using any type - use explicit types instead for type safety
Use camelCase for variable and function names following existing codebase patterns
Keep diffs minimal with targeted edits only - avoid unnecessary changes when making modifications
Follow existing patterns and match the codebase style when writing new code

Files:

  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/main/lib/terminal-manager.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/store.ts
apps/desktop/src/renderer/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import Node.js modules (fs, path, os, net, etc.) in renderer process code - browser environment only

Files:

  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx
  • apps/desktop/src/renderer/stores/tabs/store.ts
apps/desktop/src/renderer/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Call IPC methods using window.ipcRenderer.invoke() with object parameters - TypeScript will infer the exact response type automatically

Files:

  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx
apps/desktop/**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.test.{ts,tsx,js,jsx}: Tests should have one assert per test
Tests should be readable
Tests should be fast
Tests should be independent
Tests should be repeatable

Files:

  • apps/desktop/src/main/lib/terminal-manager.test.ts
apps/desktop/src/main/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Node.js modules (fs, path, os, net, etc.) can be used in main process code only

Files:

  • apps/desktop/src/main/lib/terminal-manager.test.ts
**/components/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

**/components/**/*.tsx: Create one folder per component with structure: ComponentName/ComponentName.tsx + index.ts for barrel export
Co-locate tests next to the component file they test (e.g., ComponentName.test.tsx)
Co-locate dependencies (utils, hooks, constants, config, stories) next to the file using them
Use nested components/ subdirectory within a parent component only if a sub-component is used 2+ times within that parent; otherwise keep it in the parent's components/ folder
One component per file - avoid multi-component files

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx
🧠 Learnings (8)
📚 Learning: 2025-11-28T01:03:47.963Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.963Z
Learning: Applies to **/*.{ts,tsx} : Keep diffs minimal with targeted edits only - avoid unnecessary changes when making modifications

Applied to files:

  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/main/lib/terminal-manager.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.{ts,tsx,js,jsx} : For Electron interprocess communication, ALWAYS use tRPC as defined in `src/lib/trpc`

Applied to files:

  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
📚 Learning: 2025-11-28T01:03:47.963Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.963Z
Learning: Applies to apps/desktop/src/renderer/**/*.{ts,tsx} : Never import Node.js modules (fs, path, os, net, etc.) in renderer process code - browser environment only

Applied to files:

  • apps/desktop/src/renderer/screens/main/index.tsx
📚 Learning: 2025-11-28T01:03:47.963Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.963Z
Learning: Applies to apps/desktop/src/renderer/**/*.tsx : Call IPC methods using `window.ipcRenderer.invoke()` with object parameters - TypeScript will infer the exact response type automatically

Applied to files:

  • apps/desktop/src/renderer/screens/main/index.tsx
📚 Learning: 2025-11-28T01:03:47.963Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.963Z
Learning: Applies to apps/desktop/src/lib/**/*.ts : Never import Node.js modules in shared code like `src/lib/electron-router-dom.ts` - this code runs in both main and renderer processes

Applied to files:

  • apps/desktop/src/renderer/screens/main/index.tsx
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.test.{ts,tsx,js,jsx} : Tests should be repeatable

Applied to files:

  • apps/desktop/src/main/lib/terminal-manager.test.ts
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.test.{ts,tsx,js,jsx} : Tests should be readable

Applied to files:

  • apps/desktop/src/main/lib/terminal-manager.test.ts
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
🧬 Code graph analysis (5)
apps/desktop/src/renderer/screens/main/index.tsx (2)
apps/desktop/src/renderer/stores/tabs/store.ts (1)
  • useWindowsStore (78-554)
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts (1)
  • useAgentHookListener (9-59)
apps/desktop/src/renderer/stores/tabs/utils.ts (2)
apps/desktop/src/renderer/stores/tabs/types.ts (3)
  • Window (25-31)
  • PaneType (6-6)
  • Pane (12-19)
apps/desktop/src/renderer/stores/tabs/utils/terminal-naming.ts (1)
  • generateTerminalName (41-46)
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx (2)
apps/desktop/src/renderer/stores/tabs/store.ts (1)
  • useWindowsStore (78-554)
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/useWorkspaceRename.ts (1)
  • useWorkspaceRename (4-64)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx (5)
apps/desktop/src/renderer/stores/tabs/types.ts (2)
  • Window (25-31)
  • Pane (12-19)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)
  • getPaneIdsForWindow (113-120)
apps/desktop/src/shared/types.ts (1)
  • MosaicNode (35-35)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx (1)
  • WindowPane (30-80)
apps/desktop/src/renderer/lib/dnd.ts (1)
  • dragDropManager (5-5)
apps/desktop/src/renderer/stores/tabs/store.ts (2)
apps/desktop/src/renderer/stores/tabs/types.ts (2)
  • WindowsState (36-42)
  • WindowsStore (47-85)
apps/desktop/src/renderer/stores/tabs/utils.ts (5)
  • createWindowWithPane (86-108)
  • getPaneIdsForWindow (113-120)
  • extractPaneIdsFromLayout (22-27)
  • createPane (38-59)
  • isLastPaneInWindow (125-130)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (22)
apps/desktop/src/renderer/stores/tabs/types.ts (1)

1-85: Well-structured type definitions for the window/pane model.

The type hierarchy is clean and correctly models the invariant that windows always contain panes. The use of MosaicNode<string> for layouts with paneId leaves is appropriate.

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

13-56: Clean migration from tab-centric to pane-centric event handling.

The backwards compatibility comments (Lines 16-17, 35-36) are helpful for understanding the migration. The logic correctly translates tab events to the new window/pane model, and using getState() inside callbacks properly avoids stale closure issues.

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

188-200: Good simplification of the focus listener API.

Removing the unused window/pane ID parameters and delegating focus handling to a callback makes this function more reusable and aligns with the new pane-centric architecture where the caller manages focus state.

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

40-45: Auto-close window effect correctly addresses the stated invariant.

This effect properly enforces that windows with no panes are removed, which aligns with the PR objectives. The previous review comment about this was addressed.


118-128: Clean Mosaic integration.

The component correctly wires up the Mosaic layout with the store's layout state, handles layout changes, and delegates pane rendering to WindowPane. The use of a shared dragDropManager ensures consistent drag-and-drop behavior across the app.


78-84: This review comment is based on an incorrect assumption. The code already implements the suggested cleanup: cleanLayout() is called on line 38 to remove orphaned pane IDs from the layout before rendering, and the cleaned layout is passed to the Mosaic component on line 122. The fallback "Pane not found" handler on lines 78-84 is an appropriate defensive measure for rare edge cases (e.g., race conditions), not a primary concern.

Likely an incorrect or invalid review comment.

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

12-17: Good use of individual store selectors.

The granular selector pattern (one selector per state slice) optimizes re-renders by only subscribing to the specific state pieces needed. This follows zustand best practices.


41-46: Closing the focused pane is correctly implemented.

The guard if (focusedPaneId) properly prevents calling removePane when no pane is focused. The store's removePane handles the case where closing the last pane should close the window.

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

39-51: Store access and derived state look correct.

The chained derivation of activeWindowId → focusedPaneId follows the new window/pane model correctly. Guards for null values are appropriately placed.

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

85-91: Focus handler implementation is correct.

The handleTerminalFocus callback properly guards against undefined pane and correctly sets the focused pane when the terminal gains focus.


223-230: Effect will recreate terminal if pane is renamed.

Including paneName in the dependency array means renaming a pane will detach and recreate the terminal session. If pane renaming should preserve the terminal session, consider removing paneName from deps and only using it for the initial createOrAttach call.

Is recreating the terminal on pane rename the intended behavior? If panes can be renamed by users, this could be disruptive.

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

18-76: Well-structured window activation fallback logic.

The findNextWindow helper correctly prioritizes MRU history, then positional adjacency, then any remaining window. Edge cases are handled properly.


88-124: Window creation with history tracking is well implemented.

The addWindow operation correctly creates a window with an initial pane, manages the MRU history stack, and returns the created IDs for caller use.


126-171: Window removal properly cleans up all related state.

The removeWindow operation correctly kills terminals, removes panes, updates active window selection, and cleans up focus tracking and history in the right order.


222-259: Reorder operations correctly scope to workspace.

Both reorderWindows and reorderWindowById properly filter windows by workspace, perform the reorder, and merge back with windows from other workspaces.


333-378: Pane removal handles edge cases correctly.

The removePane operation correctly handles the last-pane-closes-window case, cleans up the terminal, updates the layout, and reassigns focus when needed.


424-519: Split operations correctly handle both path-based and direct splits.

The splitPaneVertical and splitPaneHorizontal operations properly validate inputs, create new panes, and update layouts using either updateTree for specific paths or direct wrapping for top-level splits.

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

7-9: LGTM! Clean utility implementations.

The ID generation, window display name retrieval, and recursive layout traversal logic are all correct and well-implemented.

Also applies to: 15-17, 22-33


65-80: LGTM! Window creation logic is solid.

Both generateWindowName and createWindowWithPane are well-implemented:

  • Window numbering correctly finds the next available number, handling gaps in the sequence
  • Atomic window+pane creation ensures the invariant that windows always have ≥1 pane
  • Workspace-scoped window naming aligns with the PR objectives

Also applies to: 86-108


113-120: LGTM! Pane query helpers are correct.

Both functions correctly identify panes belonging to a window and check for the last-pane condition, which is essential for window lifecycle management.

Also applies to: 125-130


136-164: LGTM! Mosaic removal logic is sound.

The recursive pane removal correctly handles all cases:

  • Leaf node removal
  • Branch collapsing when one side becomes empty
  • Tree restructuring when both branches remain valid

169-196: LGTM! Layout validation and traversal helpers are well-designed.

cleanLayout properly validates and prunes invalid pane references with a smart optimization (lines 187-189) that preserves object identity when no changes are needed—avoiding unnecessary React re-renders.

getFirstPaneId correctly traverses to the leftmost leaf for focus fallback.

Also applies to: 201-208

Comment on lines 60 to +73
useHotkeys(HOTKEYS.SPLIT_HORIZONTAL.keys, () => {
if (isWorkspaceView && activeWorkspaceId) {
splitTabVertical(activeWorkspaceId);
if (isWorkspaceView && activeWindowId && focusedPaneId) {
splitPaneVertical(activeWindowId, focusedPaneId);
}
}, [activeWorkspaceId, splitTabVertical, isWorkspaceView]);
}, [activeWindowId, focusedPaneId, splitPaneVertical, isWorkspaceView]);

useHotkeys(HOTKEYS.SPLIT_VERTICAL.keys, () => {
if (isWorkspaceView && activeWorkspaceId) {
splitTabHorizontal(activeWorkspaceId);
if (isWorkspaceView && activeWindowId && focusedPaneId) {
splitPaneHorizontal(activeWindowId, focusedPaneId);
}
}, [activeWorkspaceId, splitTabHorizontal, isWorkspaceView]);
}, [activeWindowId, focusedPaneId, splitPaneHorizontal, isWorkspaceView]);

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

🏁 Script executed:

# Check hotkey definitions
rg -n "SPLIT_HORIZONTAL|SPLIT_VERTICAL" apps/desktop/src --type ts --type tsx -B 2 -A 3

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

# Check the split pane implementations in the store
rg -n "splitPaneVertical|splitPaneHorizontal" apps/desktop/src/shared --type ts --type tsx -B 2 -A 5

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

# Check if these functions are defined in the windows store
fd -e ts -e tsx apps/desktop/src | xargs grep -l "splitPaneVertical\|splitPaneHorizontal" | head -5

Repository: superset-sh/superset

Length of output: 433


🏁 Script executed:

# Check hotkey definitions in hotkeys file
find apps/desktop/src -name "hotkeys*" -type f

Repository: superset-sh/superset

Length of output: 98


🏁 Script executed:

# Look for SPLIT_HORIZONTAL and SPLIT_VERTICAL definitions
rg "SPLIT_HORIZONTAL|SPLIT_VERTICAL" apps/desktop/src -g "*.ts" -g "*.tsx" -B 2 -A 3

Repository: superset-sh/superset

Length of output: 1782


🏁 Script executed:

# Check split pane implementations
rg "splitPaneVertical|splitPaneHorizontal" apps/desktop/src -g "*.ts" -g "*.tsx" -B 1 -A 5

Repository: superset-sh/superset

Length of output: 11608


Fix reversed split pane function calls in hotkey handlers.

The hotkey handlers are calling the wrong split functions. SPLIT_HORIZONTAL ("Split Right") calls splitPaneVertical, and SPLIT_VERTICAL ("Split Down") calls splitPaneHorizontal—this is backwards. In WindowPane.tsx, the context menu correctly maps onSplitHorizontal to splitPaneHorizontal and onSplitVertical to splitPaneVertical. Update lines 65 and 71 to call the semantically correct functions.

🤖 Prompt for AI Agents
In apps/desktop/src/renderer/screens/main/index.tsx around lines 63 to 73, the
hotkey handlers call the wrong split functions: the SPLIT_HORIZONTAL handler
currently calls splitPaneVertical and the SPLIT_VERTICAL handler calls
splitPaneHorizontal; change the SPLIT_HORIZONTAL handler (line ~65) to call
splitPaneHorizontal(activeWindowId, focusedPaneId) and change the SPLIT_VERTICAL
handler (line ~71) to call splitPaneVertical(activeWindowId, focusedPaneId) so
the hotkeys match the semantic actions used elsewhere.

Replace verbose inline import("react-mosaic-component").MosaicNode<string>
with proper import at top of file for cleaner type annotations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Remove numbered terminal naming (was globally numbered, not per-window)
- All terminals now simply named "Terminal"
- Delete unused terminal-naming.ts and its test

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@saddlepaddle
saddlepaddle merged commit 07a04b4 into main Dec 2, 2025
7 checks passed
@Kitenite Kitenite mentioned this pull request Dec 2, 2025
@Kitenite
Kitenite deleted the indigo-forest-68 branch December 2, 2025 02:25
@Kitenite
Kitenite restored the indigo-forest-68 branch December 2, 2025 02:42
@Kitenite Kitenite mentioned this pull request Dec 2, 2025
Kitenite added a commit that referenced this pull request Dec 2, 2025

This branch was successfully deployed

1 active deployment
Preview — 79556cc4 Deployed Dec 2, 2025 by vercel[bot]
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