Skip to content

Revert "Refactor tabs to windows/panes model"#240

Closed
Kitenite wants to merge 1 commit intomainfrom
revert-220-indigo-forest-68
Closed

Revert "Refactor tabs to windows/panes model"#240
Kitenite wants to merge 1 commit intomainfrom
revert-220-indigo-forest-68

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Dec 2, 2025

Reverts #220

Summary by CodeRabbit

Release Notes

  • New Features

    • Redesigned terminal management with tab-based interface replacing the previous window system
    • Added tab drag-and-drop reordering and grouping capabilities
    • Added tab splitting (vertical/horizontal) for side-by-side terminal layouts
    • Added tab context menus with rename, ungroup, and close options
    • Added "New Terminal" button in the sidebar for quick terminal creation
    • Added attention indicators for tabs requiring user action
  • UI Improvements

    • Simplified terminal navigation and organization
    • Enhanced tab state management with improved history tracking

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

@vercel
Copy link
Copy Markdown

vercel Bot commented Dec 2, 2025

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 2:47am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 2, 2025

Walkthrough

This pull request refactors the desktop app's terminal management architecture from a window/pane model to a tab-centric model. It introduces a new tab store, tab-based UI components, drag-and-drop logic for tabs, and group tab rendering while removing window/pane infrastructure throughout the codebase.

Changes

Cohort / File(s) Summary
Test Updates
apps/desktop/src/main/lib/terminal-manager.test.ts
Replaces promise chain .stat(...).then(...).catch(...) with try/catch pattern to verify directory non-existence via ENOENT error code.
Tab Store Types & Core
apps/desktop/src/renderer/stores/tabs/types.ts, store.ts, index.ts
Replaces Windows/Pane types with Tab-centric model (TabType enum, SingleTab, TabGroup, Tab union); rewires TabsStore API from window/pane operations (addWindow/removeWindow/etc.) to tab operations (addTab/removeTab/etc.); updates storage layer and re-exports modules.
Tab Store Hooks
apps/desktop/src/renderer/stores/tabs/hooks.ts
Exports 14 new hooks (useTabs, useActiveTabIds, useAddTab, useSetActiveTab, useRemoveTab, useRenameTab, useReorderTabs, useReorderTabById, useMarkTabAsUsed, useUngroupTab, useUngroupTabs, useSplitTabVertical, useSplitTabHorizontal, useSetNeedsAttention) delegating to useTabsStore.
Tab Drag-and-Drop Logic
apps/desktop/src/renderer/stores/tabs/drag-logic.ts, drag-logic.test.ts
Introduces removeTabFromLayout, cleanLayout, and handleDragTabToTab to manage tab grouping/ungrouping via drag operations; comprehensive test suite covering layout mutations, group creation, and edge cases.
Tab Content Drop Hook
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useTabContentDrop.ts
New hook wiring React DnD drop target for tab items; exposes isDropZone and attachDrop to detect and handle drag-over states.
Tab CRUD Helpers
apps/desktop/src/renderer/stores/tabs/helpers/tab-crud.ts
Exports handleAddTab, handleRemoveTab, handleRenameTab, handleMarkTabAsUsed for tab lifecycle management.
Tab Ordering Helpers
apps/desktop/src/renderer/stores/tabs/helpers/tab-ordering.ts
Introduces handleReorderTabs and handleReorderTabById to reorder tabs within workspaces.
Tab Split Operations
apps/desktop/src/renderer/stores/tabs/helpers/split-operations.ts
Exports handleSplitTabVertical and handleSplitTabHorizontal to convert single tabs into split groups or split existing groups.
Tab Group Operations
apps/desktop/src/renderer/stores/tabs/helpers/group-operations.ts, group-operations.test.ts
Exports handleUpdateTabGroupLayout, handleAddChildTabToGroup, handleRemoveChildTabFromGroup, handleUngroupTab, handleUngroupTabs; test suite covers group removal, active tab updates, and history stack management.
Tab Active State Helpers
apps/desktop/src/renderer/stores/tabs/helpers/active-tab.ts
Exports handleSetActiveTab, getTabsByWorkspace, getActiveTab, getLastActiveTabId for tab activation and workspace-scoped queries; maintains per-workspace history stacks.
Tab Validation Helper
apps/desktop/src/renderer/stores/tabs/helpers/validation.ts
Exports validateGroupLayouts to clean group layouts by removing invalid child tab references.
Tab Next-Finder Helpers
apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.ts, next-tab-finder.test.ts
Exports findNextTab to select next tab on close using history, sibling proximity, or workspace-scoped fallback; comprehensive test coverage for various scenarios.
Tab Utils
apps/desktop/src/renderer/stores/tabs/utils.ts, utils/terminal-naming.ts, utils/terminal-naming.test.ts
Replaces window/pane helpers with getChildTabIds and createNewTab; introduces terminal naming strategy with DefaultTerminalNamingStrategy and generateTerminalName for unique tab titles.
Tab Store Agent Listener
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
Updates from useWindowsStore to useTabsStore; refactors agent-complete and focus-tab logic to use tabId and activeTabIds.
Tab Store Tests
apps/desktop/src/renderer/stores/tabs/store.test.ts
Comprehensive test suite for tab removal, splitting, group interactions, active tab selection, and history management.
Tab Item Drag Hooks
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useDragTab.ts, useGroupDrop.ts
New hooks useDragTab and useGroupDrop integrating React DnD for tab dragging and group drop targeting.
Tab Rename Hook
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useTabRename.ts
New hook managing in-place tab renaming with state, input focus, and keyboard handling (Enter/Escape).
Tab Item Types
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/types.ts
Exports DragItem, TAB_DND_TYPE constant, and TabItemProps for tab drag-and-drop and component props typing.
Tab Item Component
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx
New component rendering individual tab items with active state, drag/drop, rename flow, context menu (actions: remove, rename, ungroup, move-out-of-group), group expansion, and child recursion.
Tab Context Menu
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/TabContextMenu.tsx
New component rendering context menu with type-specific options (Group: rename/ungroup; Single: rename/move-out/close).
Ungroup Drop Zone
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/UngroupDropZone/index.tsx
New component integrating React DnD useDrop for ungrouping tabs and reordering; computes drop indices via hover measurement.
Tab Sidebar View
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
Replaces WindowItem rendering with TabItem rendering; adds "New Terminal" button and UngroupDropZone wrapper; filters tabs by workspace and top-level (no parentId).
Drop Overlay Component
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/DropOverlay.tsx
New component rendering semi-transparent full-area overlay with centered message during drag operations.
Single Tab View
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx
New component rendering single tab content with split/close handlers via TabContentContextMenu.
Group Tab Pane
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView/GroupTabPane.tsx
New component rendering individual pane within a Mosaic-based group layout; manages focus, close button, and context menu for splits.
Group Tab View
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView/index.tsx
New component rendering group of tabs via Mosaic layout; handles onChange to detect added/removed tabs; renders GroupTabPane for each pane; manages layout cleaning and child tab extraction.
Tabs Content
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
Replaces WindowView rendering with conditional SingleTabView/GroupTabView based on TabType; integrates useTabContentDrop; updates active tab lookup to use useActiveTabIds and useTabs; adds DropOverlay.
Terminal Component
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
Refactors from pane-based identifiers (paneId) to tab-based (tabId); imports useSetActiveTab and useTabs; updates TRPC subscription, focus handling via setupFocusListener, and terminal lifecycle to use tab identifiers.
Terminal Helpers
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
Updates setupFocusListener signature from (xterm, onFocus) to (xterm, workspaceId, tabId, setActiveTab) for tab-centric focus management.
Workspace View
apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
Replaces useWindowsStore with useTabs/useActiveTabIds/useAddTab/useSetActiveTab/useRemoveTab; updates hotkey handlers for NEW_TERMINAL, CLOSE_TERMINAL, PREV_TERMINAL, NEXT_TERMINAL to use tab operations.
Main Screen
apps/desktop/src/renderer/screens/main/index.tsx
Replaces window/pane split hooks with useSplitTabVertical/useSplitTabHorizontal; updates hotkey handlers for SPLIT_HORIZONTAL/SPLIT_VERTICAL to call tab split operations.
Workspace Tabs
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx
Updates needsAttention derivation from window/pane layout parsing to direct tab filtering; replaces useWindowsStore with useTabs.
Workspace Creation
apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
Replaces addWindow with addTab; updates import from useWindowsStore to useTabsStore; passes tabId (formerly paneId) to terminal creation in onSuccess flow.
Removed Window Components
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/WindowItem/index.tsx, WindowContextMenu.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/index.tsx, WindowPane.tsx
Complete removal of WindowItem, WindowContextMenu, WindowView, and WindowPane components; eliminates window/pane drag-and-drop, rename, and layout rendering logic.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant UI as Tab UI
    participant Store as Tabs Store
    participant Helper as Split Helper
    participant Component as Terminal Component

    User->>UI: Click split button (vertical)
    UI->>Store: useSplitTabVertical(workspaceId, sourceTabId)
    activate Store
    Store->>Helper: handleSplitTabVertical()
    activate Helper
    Helper->>Helper: Find tab path in Mosaic layout
    Helper->>Helper: Create new tab via createNewTab()
    Helper->>Helper: Convert/update group layout
    Helper->>Helper: Set parentId relationships
    Helper-->>Store: Return updated tabs + layout
    deactivate Helper
    Store->>Store: Merge state: tabs, activeTabIds, group layouts
    Store-->>UI: Notify subscribers
    deactivate Store
    UI->>Component: Render GroupTabView with new layout
    Component->>Component: Extract child tab IDs from Mosaic
    Component->>Component: Render each child as GroupTabPane
    Component-->>User: Display split terminal panes
Loading
sequenceDiagram
    participant User as User
    participant DragUI as Tab Item (Dragged)
    participant DropUI as Tab Item (Target)
    participant DragLogic as Drag-to-Tab Logic
    participant Store as Tabs Store

    User->>DragUI: Drag tab A over tab B
    DragUI->>DropUI: useDragTab emits drag start (tabId: A)
    DropUI->>DropUI: useGroupDrop detects drop target (tabId: B)
    User->>DropUI: Release (drop)
    DropUI->>DragLogic: dragTabToTab(tabIdA, tabIdB)
    activate DragLogic
    alt Tab A != Tab B
        DragLogic->>DragLogic: Check if B is group, child, or single
        alt B is group
            DragLogic->>DragLogic: Add A to B's layout
        else B is single & A is single
            DragLogic->>DragLogic: Create new group with A and B
        else B is child
            DragLogic->>DragLogic: Redirect to parent group
        end
        DragLogic->>Store: Update tabs, layouts, parentId
    end
    deactivate DragLogic
    Store-->>DropUI: Subscribers notified
    DropUI-->>User: UI re-renders with new structure
Loading
sequenceDiagram
    participant User as User
    participant Sidebar as Tab Sidebar
    participant Store as Tabs Store
    participant ActiveTab as Active Tab Helper
    participant Terminal as Terminal Component

    User->>Sidebar: Click tab to activate
    Sidebar->>Store: setActiveTab(workspaceId, tabId)
    activate Store
    Store->>ActiveTab: handleSetActiveTab(state, workspaceId, tabId)
    activate ActiveTab
    ActiveTab->>ActiveTab: Update activeTabIds[workspaceId] = tabId
    ActiveTab->>ActiveTab: Push tabId to tabHistoryStacks[workspaceId]
    ActiveTab->>ActiveTab: Clear needsAttention flag
    ActiveTab-->>Store: Return updated state
    deactivate ActiveTab
    Store->>Store: Merge state changes
    Store-->>Sidebar: Subscribers notified
    deactivate Store
    Sidebar-->>Terminal: Tab state propagates to TabsContent
    Terminal->>Terminal: Re-render active tab's Terminal component
    Terminal->>User: Terminal gains focus / becomes visible
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Areas requiring extra attention:

  • Drag-and-drop logic (drag-logic.ts, handleDragTabToTab): Complex multi-path state mutations orchestrating tab grouping, ungrouping, parent-child relationships, and layout updates; comprehensive test coverage provided but logic density is high.
  • Tab store refactor (store.ts, types.ts): Complete architectural replacement of Windows/Panes model with Tabs model; all state shape, action signatures, and store methods changed; verify all action implementations correctly delegate to helper functions.
  • Active tab and history management (active-tab.ts, next-tab-finder.ts, store.test.ts): Complex prioritization for tab activation (history preference, sibling proximity, workspace fallback); history stack mutations must maintain consistency across splits/removes/ungrouping.
  • Split operations (split-operations.ts): Multi-scenario splitting logic (single tab → group, group child → split, etc.); path-finding and layout mutations interact with Mosaic library; verify all tab relationship updates.
  • Tab item component (TabItem/index.tsx): Extensive state and event handling (drag/drop, rename, context menu, group expansion, keyboard shortcuts); recursive child rendering; multiple interacting hooks (useDragTab, useGroupDrop, useTabRename, etc.).
  • Integration points: Verify all removed Window/Pane component references are properly replaced with Tab equivalents; check Terminal component's shift from paneId to tabId does not introduce regressions in TRPC subscriptions or terminal lifecycle.
  • Test coverage: Store tests, drag-logic tests, and helper tests are comprehensive; verify test scenarios match implementation edge cases (e.g., empty groups, last-tab-in-group removal, history fallback selection).

Possibly related PRs

Poem

🐰 Tabs are grouped, splits are squared,
Dragging, dropping—tabs repaired!
Windows gone, the panes farewell,
Mosaic layouts weave their spell.
History remembers all,
Each terminal answers the call!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% 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 accurately describes the main change: reverting a previous refactor that changed tabs to a windows/panes model.
Description check ✅ Passed The description is minimal but appropriate for a revert PR. It references the PR being reverted, though it lacks detail about why the revert is necessary.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch revert-220-indigo-forest-68

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🧹 Nitpick comments (18)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useGroupDrop.ts (1)

5-30: Align canDrop/isDragOver with actual drop predicate

Current logic prevents no-op drops on the same group via if (!didDrop && item.tabId !== groupId), but canDrop is always whatever react‑dnd’s default is, so isDragOver can still be true even when the drop would be ignored.

You could tighten this up by adding an explicit canDrop:

useDrop<DragItem, void, { isOver: boolean; canDrop: boolean }>({
  accept: TAB_DND_TYPE,
  canDrop: (item) => item.tabId !== groupId,
  drop: (item, monitor) => {
    const didDrop = monitor.didDrop();
    if (!didDrop && item.tabId !== groupId) {
      dragTabToTab(item.tabId, groupId);
    }
  },
  collect: (monitor) => ({
    isOver: monitor.isOver({ shallow: true }),
    canDrop: monitor.canDrop(),
  }),
});

This keeps hover styling in sync with what will actually drop.

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

1-13: Avoid duplicating DragItem / TAB_DND_TYPE across modules

The typings here look correct, but there’s an identical DragItem/TAB_DND_TYPE pair in ContentView/TabsContent/types.ts. Keeping these in sync across files is brittle.

Consider extracting them to a single shared module (e.g. a tabs-dnd-types.ts co‑located with other tab DnD helpers) and importing from there in both Sidebar and ContentView code.

apps/desktop/src/renderer/stores/tabs/helpers/active-tab.ts (1)

3-61: Active tab + history helpers look consistent

handleSetActiveTab correctly maintains per‑workspace activeTabIds, keeps a de‑duplicated LRU‑style tabHistoryStacks[workspaceId], and clears needsAttention only on the newly activated tab. The selectors (getTabsByWorkspace, getActiveTab, getLastActiveTabId) are straightforward and align with the TabsState shape.

If history growth ever becomes a concern, you could cap newHistoryStack to a max length (e.g. last 50 tabs), but that’s not strictly necessary right now.

apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts (1)

4-40: Workspace → terminal tab creation flow looks correct

Hooking into useTabsStore to call addTab(data.workspace.id) and then passing the resulting tabId into terminal.createOrAttach aligns with the tab-centric terminal model and keeps IPC on tRPC. The behavior described in the docstring (“Creates a terminal tab with setup commands if present”) matches the implementation.

If you end up standardizing on the renderer/stores/tabs barrel for imports elsewhere, you could switch the useTabsStore import to that path for consistency, but the current import is functionally fine.

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

1-42: Tab drag/drop wiring is solid

useDragTab cleanly sets up a TAB drag source and a matching drop target that forwards to dragTabToTab when dropping onto a different tab, with isDragging/isDragOver exposed for styling. The use of react-dnd generics and the store selector keeps things type-safe and aligned with the central tab drag logic.

If you ever need finer-grained hover behavior (e.g. avoiding nested drops), you could switch monitor.isOver() to monitor.isOver({ shallow: true }), but the current implementation is perfectly acceptable.

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

26-27: Consider memoizing getChildTabs to prevent unnecessary re-renders.

This function is recreated on every render and passed implicitly through closures. If TabItem uses this to derive child tabs, it may cause unnecessary re-renders. Consider using useCallback:

-	const getChildTabs = (parentId: string) =>
-		allTabs.filter((tab) => tab.parentId === parentId);
+	const getChildTabs = useCallback(
+		(parentId: string) => allTabs.filter((tab) => tab.parentId === parentId),
+		[allTabs],
+	);

Alternatively, since getChildTabs(tab.id) is called inline at line 65, you could compute child tabs within the map and pass them directly.

apps/desktop/src/renderer/stores/tabs/helpers/group-operations.test.ts (1)

7-54: Consider test data consistency: child3 is not in the group layout.

The test includes child3 in the tabs array but the layout only references child1 and child2. While this might work if handleRemoveChildTabFromGroup derives children from parentId rather than layout, this inconsistency could mask bugs or cause confusion.

Based on learnings, tests should be readable - ensuring test data accurately represents real scenarios improves clarity.

 				{
 					id: "group1",
 					title: "Group 1",
 					workspaceId: "workspace1",
 					type: TabType.Group,
 					layout: {
 						direction: "row",
 						first: "child1",
-						second: "child2",
+						second: {
+							direction: "row",
+							first: "child2",
+							second: "child3",
+							splitPercentage: 50,
+						},
 					},
 				},
apps/desktop/src/renderer/stores/tabs/store.test.ts (1)

244-274: Early return could silently pass the test if group is not found.

Lines 246, 260 use early returns that would cause the test to pass without actually verifying the assertions below them. Consider using explicit assertions instead.

 		// Find the group tab
 		const groupTab = state.tabs.find((t) => t.type === TabType.Group);
 		expect(groupTab).toBeDefined();
-		if (groupTab?.type !== TabType.Group) return;
+		expect(groupTab?.type).toBe(TabType.Group);
+		if (groupTab?.type !== TabType.Group) throw new Error("Expected group tab");

 		expect(groupTab.layout).toEqual({
 			direction: "row",
 			first: "tab-1",
 			second: expect.any(String),
 			splitPercentage: 50,
 		});

 		// Original tab should now be a child
 		const originalTab = state.tabs.find((t) => t.id === "tab-1");
 		expect(originalTab?.parentId).toBe(groupTab.id);

 		// New child should exist
-		if (typeof groupTab.layout === "string" || !groupTab.layout) return;
+		expect(typeof groupTab.layout).not.toBe("string");
+		expect(groupTab.layout).not.toBeNull();
+		if (typeof groupTab.layout === "string" || !groupTab.layout) throw new Error("Expected object layout");
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/UngroupDropZone/index.tsx (1)

45-72: Hover computation may cause performance issues with frequent state updates.

The setDropIndex call on every hover event can trigger re-renders for each mouse movement. Consider adding a check to only update when the index actually changes.

 		hover: (_item, monitor) => {
 			if (!containerRef.current) return;

 			const clientOffset = monitor.getClientOffset();
 			if (!clientOffset) return;

 			const containerRect = containerRef.current.getBoundingClientRect();
 			const hoverY = clientOffset.y - containerRect.top;

 			// Get all tab elements
 			const tabElements =
 				containerRef.current.querySelectorAll("[data-tab-item]");
 			let newDropIndex = 0;

 			for (let i = 0; i < tabElements.length; i++) {
 				const element = tabElements[i] as HTMLElement;
 				const rect = element.getBoundingClientRect();
 				const elementY = rect.top - containerRect.top;
 				const elementMiddle = elementY + rect.height / 2;

 				if (hoverY < elementMiddle) {
 					newDropIndex = i;
 					break;
 				}
 				newDropIndex = i + 1;
 			}

-			setDropIndex(newDropIndex);
+			// Only update state if the index changed to avoid unnecessary re-renders
+			setDropIndex((prev) => (prev !== newDropIndex ? newDropIndex : prev));
 		},
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView/index.tsx (1)

28-45: Consider using the existing getChildTabIds utility instead of duplicating traversal logic.

The extractTabIdsFromLayout function traverses the MosaicNode structure to extract tab IDs, but there's already a getChildTabIds utility in renderer/stores/tabs/utils.ts that retrieves child tab IDs by filtering the tabs array. However, these serve different purposes—extractTabIdsFromLayout extracts IDs from the layout tree structure, while getChildTabIds queries the store.

The current implementation is correct for detecting layout-based changes. Consider adding a brief comment explaining why this traversal is needed (to detect removed tabs from the layout structure, not just store state).

apps/desktop/src/renderer/stores/tabs/utils/terminal-naming.test.ts (1)

109-127: Test violates "one assert per test" guideline.

This test has multiple assertions checking sequential name generation. While the test logic is valid, it should be split into separate test cases per the coding guidelines. Based on learnings, tests should have one assert per test.

Consider splitting into separate tests:

-	it("should generate sequential names for multiple new terminals", () => {
-		const strategy = new DefaultTerminalNamingStrategy();
-		const existingNames: string[] = [];
-
-		const name1 = strategy.generateName(existingNames);
-		expect(name1).toBe("Terminal");
-		existingNames.push(name1);
-
-		const name2 = strategy.generateName(existingNames);
-		expect(name2).toBe("Terminal (1)");
-		existingNames.push(name2);
-
-		const name3 = strategy.generateName(existingNames);
-		expect(name3).toBe("Terminal (2)");
-		existingNames.push(name3);
-
-		expect(existingNames).toEqual(["Terminal", "Terminal (1)", "Terminal (2)"]);
-	});
+	it("should generate first terminal name", () => {
+		const strategy = new DefaultTerminalNamingStrategy();
+		expect(strategy.generateName([])).toBe("Terminal");
+	});
+
+	it("should generate second terminal name after first exists", () => {
+		const strategy = new DefaultTerminalNamingStrategy();
+		expect(strategy.generateName(["Terminal"])).toBe("Terminal (1)");
+	});
+
+	it("should generate third terminal name after first two exist", () => {
+		const strategy = new DefaultTerminalNamingStrategy();
+		expect(strategy.generateName(["Terminal", "Terminal (1)"])).toBe("Terminal (2)");
+	});
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (1)

17-20: Tab lookup on every render may cause performance overhead.

The useTabs() hook returns all tabs, and filtering with find() on every render could be inefficient if there are many tabs. Consider using a selector that returns only the needed tab:

-	const tabs = useTabs();
-	const tab = tabs.find((t) => t.id === tabId);
-	const tabTitle = tab?.title || "Terminal";
+	const tabTitle = useTabsStore(
+		(state) => state.tabs.find((t) => t.id === tabId)?.title || "Terminal",
+	);

This creates a more targeted selector that only re-renders when the specific tab's title changes.

apps/desktop/src/renderer/stores/tabs/helpers/tab-ordering.ts (1)

37-38: Redundant tab lookup.

The tabToMove lookup on line 37 is redundant since tab from line 25 is guaranteed to be in workspaceTabs (already verified it has no parentId at line 27).

-	const tabToMove = workspaceTabs.find((t) => t.id === tabId);
-	if (!tabToMove) return {};
-
-	const filteredTabs = workspaceTabs.filter((t) => t.id !== tabId);
-	filteredTabs.splice(targetIndex, 0, tabToMove);
+	const filteredTabs = workspaceTabs.filter((t) => t.id !== tabId);
+	filteredTabs.splice(targetIndex, 0, tab);
apps/desktop/src/renderer/stores/tabs/helpers/group-operations.ts (1)

193-230: Consider extracting duplicated repositioning logic.

The tab repositioning logic (lines 193-207 and 215-230) is duplicated between the empty-group and non-empty branches. This could be extracted into a helper function.

const repositionTabInWorkspace = (
  tabs: Tab[],
  tabId: string,
  workspaceId: string,
  targetIndex: number,
): Tab[] => {
  const workspaceTabs = tabs.filter(
    (t) => t.workspaceId === workspaceId && !t.parentId,
  );
  const otherTabs = tabs.filter(
    (t) => t.workspaceId !== workspaceId || t.parentId,
  );
  const tabToMove = workspaceTabs.find((t) => t.id === tabId);
  if (!tabToMove) return tabs;
  
  const filteredTabs = workspaceTabs.filter((t) => t.id !== tabId);
  filteredTabs.splice(targetIndex, 0, tabToMove);
  return [...otherTabs, ...filteredTabs];
};
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (1)

203-203: Inconsistent ID generation pattern.

Group IDs use Date.now() alone (line 203, 391), while createNewTab in utils.ts uses Date.now()-${random}. This inconsistency could lead to ID collisions in rapid operations or tests.

Consider using the same pattern as createNewTab:

-		const groupId = `tab-${Date.now()}-group`;
+		const groupId = `tab-${Date.now()}-${Math.random().toString(36).substring(2, 11)}-group`;

Or extract a shared generateId() utility.

apps/desktop/src/renderer/stores/tabs/helpers/split-operations.ts (1)

80-184: Consider consolidating vertical and horizontal split handlers.

handleSplitTabVertical and handleSplitTabHorizontal are nearly identical, differing only in the direction parameter ("row" vs "column"). This could be refactored into a single internal handler.

const handleSplitTab = (
  state: TabsState,
  workspaceId: string,
  direction: "row" | "column",
  sourceTabId?: string,
  path?: MosaicBranch[],
): Partial<TabsState> => {
  // ... shared logic
};

export const handleSplitTabVertical = (
  state: TabsState,
  workspaceId: string,
  sourceTabId?: string,
  path?: MosaicBranch[],
): Partial<TabsState> => handleSplitTab(state, workspaceId, "row", sourceTabId, path);

export const handleSplitTabHorizontal = (
  state: TabsState,
  workspaceId: string,
  sourceTabId?: string,
  path?: MosaicBranch[],
): Partial<TabsState> => handleSplitTab(state, workspaceId, "column", sourceTabId, path);
apps/desktop/src/renderer/stores/tabs/types.ts (1)

3-6: Naming conflict: TabType enum shadows shared type.

This TabType enum with values Single and Group conflicts with the TabType union type defined in apps/desktop/src/shared/types.ts (which includes "terminal", "editor", "browser", etc.). This creates ambiguity and potential import errors when both types are needed in the same file.

Consider renaming this enum to something more specific like TabKind or TabVariant to avoid confusion with the shared TabType.

-export enum TabType {
-	Single = "single",
-	Group = "group",
+export enum TabKind {
+	Single = "single",
+	Group = "group",
 }
apps/desktop/src/renderer/stores/tabs/store.ts (1)

139-146: Remove redundant state spread.

Zustand's set() performs a shallow merge by default, so spreading ...state is unnecessary and adds overhead.

 setNeedsAttention: (tabId, needsAttention) => {
-	set((state) => ({
-		...state,
-		tabs: state.tabs.map((tab) =>
-			tab.id === tabId ? { ...tab, needsAttention } : tab,
-		),
-	}));
+	set((state) => ({
+		tabs: state.tabs.map((tab) =>
+			tab.id === tabId ? { ...tab, needsAttention } : tab,
+		),
+	}));
 },

Comment on lines +41 to +43
const _handleFocus = () => {
setActiveTab(workspaceId, tabId);
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove unused focus handler.

The _handleFocus function is defined but never called or passed as a prop. This is dead code that should be removed to keep the codebase clean.

Apply this diff:

-	const _handleFocus = () => {
-		setActiveTab(workspaceId, tabId);
-	};
-
 	const handleCloseTab = (e: React.MouseEvent) => {
📝 Committable suggestion

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

Suggested change
const _handleFocus = () => {
setActiveTab(workspaceId, tabId);
};
const handleCloseTab = (e: React.MouseEvent) => {
🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupTabView/GroupTabPane.tsx
around lines 41-43, the _handleFocus arrow function is defined but never used;
remove the unused _handleFocus declaration and any related dead-code references
(imports or variables solely used by it) so the file has no dangling, unused
handler and compiles cleanly.

Comment on lines +44 to +46
{tabToRender.type === TabType.Single && (
<SingleTabView tab={tabToRender} isDropZone={isDropZone} />
)}
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
# First, let's find the SingleTabView file and check its props definition
fd "SingleTabView" apps/desktop/src/renderer --type f

# Check the file for interface definition
cat -n apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView/SingleTabView.tsx | head -60

# Search for isDropZone usage in SingleTabView
rg -n "isDropZone" apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView/

Repository: superset-sh/superset

Length of output: 481


🏁 Script executed:

#!/bin/bash
# Read the SingleTabView.tsx file
cat -n apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx | head -80

# Search for isDropZone in the file
rg -n "isDropZone" apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx

# Also check the props interface definition more carefully
rg -n "interface|type.*Props|export.*SingleTabView" apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx

Repository: superset-sh/superset

Length of output: 1719


Remove unused isDropZone prop from SingleTabView.

The isDropZone prop is defined in SingleTabViewProps but neither destructured in the function signature (line 16) nor used within the component. Remove this prop from the interface and stop passing it when calling SingleTabView in TabsContent/index.tsx (line 45).

interface SingleTabViewProps {
	tab: SingleTab;
}

export function SingleTabView({ tab }: SingleTabViewProps) {
	// ...
}

And in TabsContent/index.tsx:

<SingleTabView tab={tabToRender} />
🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
around lines 44-46 and the SingleTabView component file (where
SingleTabViewProps is defined), remove the unused isDropZone prop: delete it
from the SingleTabViewProps interface and the function signature (so the
interface only contains tab: SingleTab), and stop passing isDropZone when
rendering SingleTabView in TabsContent (change the JSX to pass only
tab={tabToRender}); ensure no other references to isDropZone remain and run TS
checks to update any affected imports/types.

Comment on lines +11 to +14
interface SingleTabViewProps {
tab: SingleTab;
isDropZone: boolean;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove unused isDropZone prop.

The isDropZone prop is defined in the interface (line 13) but is never destructured or used in the component implementation (line 16). This suggests it's either leftover from a previous implementation or intended for future use but currently serves no purpose.

Apply this diff:

 interface SingleTabViewProps {
 	tab: SingleTab;
-	isDropZone: boolean;
 }
 
-export function SingleTabView({ tab }: SingleTabViewProps) {
+export function SingleTabView({ tab }: SingleTabViewProps) {

Also applies to: 16-16

🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx
around lines 11-16, the interface defines an unused isDropZone prop and the
component signature also includes it on line 16; remove isDropZone from the
SingleTabViewProps interface and from the component props/signature, and update
any call sites that pass isDropZone to this component (remove the prop there as
well) so the prop is fully eliminated from type and usage.

Comment on lines +34 to +36
const _handleFocus = () => {
setActiveTab(tab.workspaceId, tab.id);
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove unused focus handler.

The _handleFocus function is defined but never called or used. This is dead code that should be removed.

Apply this diff:

 	const handleClosePane = () => {
 		removeTab(tab.id);
 	};
-
-	const _handleFocus = () => {
-		setActiveTab(tab.workspaceId, tab.id);
-	};
 
 	return (
📝 Committable suggestion

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

Suggested change
const _handleFocus = () => {
setActiveTab(tab.workspaceId, tab.id);
};
const handleClosePane = () => {
removeTab(tab.id);
};
return (
🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/SingleTabView.tsx
around lines 34-36, the _handleFocus function is defined but never used; remove
the entire _handleFocus function declaration (and any trailing blank line) to
eliminate dead code, and run a quick search in this file to ensure there are no
remaining references to _handleFocus to avoid breaking behavior.

terminalTheme,
handleTerminalFocus,
]);
}, [tabId, workspaceId, setActiveTab, workspaceCwd, tabTitle, terminalTheme]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd /repo && cat -n apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx | head -250 | tail -50

Repository: superset-sh/superset

Length of output: 119


🏁 Script executed:

git ls-files | grep -i terminal.tsx | grep -i renderer

Repository: superset-sh/superset

Length of output: 173


🏁 Script executed:

wc -l apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx

Repository: superset-sh/superset

Length of output: 177


🏁 Script executed:

cat -n apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx

Repository: superset-sh/superset

Length of output: 9359


🏁 Script executed:

rg -n "createOrAttach" apps/desktop/src --type ts --type tsx -B 2 -A 5 | head -80

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

rg -n "createOrAttach" apps/desktop/src -A 5 | head -100

Repository: superset-sh/superset

Length of output: 7145


🏁 Script executed:

sed -n '41,120p' apps/desktop/src/main/lib/terminal-manager.ts

Repository: superset-sh/superset

Length of output: 2093


🏁 Script executed:

sed -n '41,150p' apps/desktop/src/main/lib/terminal-manager.ts | tail -50

Repository: superset-sh/superset

Length of output: 1259


🏁 Script executed:

rg "SUPERSET_TAB_TITLE" apps/

Repository: superset-sh/superset

Length of output: 340


🏁 Script executed:

grep -n "tabTitle" apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx

Repository: superset-sh/superset

Length of output: 231


Remove tabTitle from effect dependencies to prevent terminal UI recreation on tab rename.

tabTitle is only used during initial terminal creation and restart operations. Including it in the dependency array causes the effect to rerun whenever the tab is renamed, destroying and recreating the xterm UI instance unnecessarily. Since tabTitle is accessed as part of the effect closure and the backend's createOrAttach is idempotent (reusing existing sessions by tabId), tabTitle should be removed from the dependencies.

🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
around line 219, the useEffect dependency array includes tabTitle which causes
the terminal UI to be destroyed and recreated when a tab is renamed; remove
tabTitle from the dependency array so the effect does not rerun on renames. If
you need the latest tabTitle for explicit restart/creation flows, read it
on-demand (e.g., from a ref or pass it into the restart handler) rather than
listing it as an effect dependency.

Comment on lines +162 to +170
<button
type="button"
tabIndex={-1}
onClick={handleRemoveTab}
className="cursor-pointer opacity-0 group-hover:opacity-100 ml-2 text-xs shrink-0"
>
<HiMiniXMark className="size-4" />
</button>
)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Close button missing accessible label.

The close button uses only an icon without accessible text. Screen reader users won't know the button's purpose.

 						<button
 							type="button"
 							tabIndex={-1}
 							onClick={handleRemoveTab}
 							className="cursor-pointer opacity-0 group-hover:opacity-100 ml-2 text-xs shrink-0"
+							aria-label={`Close ${tab.title}`}
 						>
 							<HiMiniXMark className="size-4" />
 						</button>
📝 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
<button
type="button"
tabIndex={-1}
onClick={handleRemoveTab}
className="cursor-pointer opacity-0 group-hover:opacity-100 ml-2 text-xs shrink-0"
>
<HiMiniXMark className="size-4" />
</button>
)}
<button
type="button"
tabIndex={-1}
onClick={handleRemoveTab}
className="cursor-pointer opacity-0 group-hover:opacity-100 ml-2 text-xs shrink-0"
aria-label={`Close ${tab.title}`}
>
<HiMiniXMark className="size-4" />
</button>
)}
🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx
around lines 162-170, the close button only renders an icon and lacks an
accessible label; add an accessibility label (e.g., aria-label="Close tab" or
aria-label={`Close ${tabTitle}`}), or include visually hidden text for screen
readers, and keep the button keyboard-focusable (remove tabIndex={-1} or set to
0) so screen reader and keyboard users can identify and activate the control.

Comment on lines +11 to +20
interface TabContextMenuProps {
children: ReactNode;
tabId: string;
tabType: TabType;
hasParent?: boolean;
onClose: () => void;
onRename: () => void;
onUngroup?: () => void;
onMoveOutOfGroup?: () => void;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove unused tabId prop.

The tabId prop is defined in the TabContextMenuProps interface (line 13) but is never used in the component implementation. It's not destructured from the props (line 22-30) and serves no purpose.

Apply this diff:

 interface TabContextMenuProps {
 	children: ReactNode;
-	tabId: string;
 	tabType: TabType;
 	hasParent?: boolean;
 	onClose: () => void;
 	onRename: () => void;
 	onUngroup?: () => void;
 	onMoveOutOfGroup?: () => void;
 }

Also applies to: 22-30

🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/TabContextMenu.tsx
around lines 11-20 (and corresponding prop usage at 22-30), remove the unused
tabId prop from the TabContextMenuProps interface and from the component's props
destructuring; update the component signature and any local references to no
longer expect tabId, and remove or stop passing tabId from any parent/call sites
to avoid type errors. Ensure the props interface only includes children,
tabType, hasParent, onClose, onRename, onUngroup, and onMoveOutOfGroup so the
component compiles without unused fields.

Comment on lines +17 to +20
// Sync rename value when tab title changes
useEffect(() => {
setRenameValue(tabTitle);
}, [tabTitle]);
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

Sync effect may overwrite user input during active rename.

If tabTitle changes externally while the user is actively renaming (e.g., due to a concurrent update), this effect will overwrite their in-progress input. Consider guarding against this:

 	// Sync rename value when tab title changes
 	useEffect(() => {
+		if (!isRenaming) {
 			setRenameValue(tabTitle);
+		}
 	}, [tabTitle]);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/useTabRename.ts
around lines 17-20, the effect that sets renameValue from tabTitle will
overwrite a user's in-progress rename when tabTitle changes externally; guard
the sync so it only updates renameValue when the user is not actively renaming
(e.g., add or use an isRenaming flag / input focus check, or only setRenameValue
when renameValue === previous tabTitle), and update the effect to check that
condition before calling setRenameValue so external title updates do not clobber
in-progress edits.

const group = state.tabs.find(
(t) => t.id === tabToSplit.parentId && t.type === TabType.Group,
);
if (!group || group.type !== TabType.Group || !group.layout) return state;
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

Inconsistent error return value.

This function returns state on error (line 196), while other handlers in this codebase return {} to indicate a no-op. Returning full state could mask issues and cause unexpected spread behavior.

-	if (!group || group.type !== TabType.Group || !group.layout) return state;
+	if (!group || group.type !== TabType.Group || !group.layout) return {};
📝 Committable suggestion

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

Suggested change
if (!group || group.type !== TabType.Group || !group.layout) return state;
if (!group || group.type !== TabType.Group || !group.layout) return {};
🤖 Prompt for AI Agents
In apps/desktop/src/renderer/stores/tabs/helpers/split-operations.ts around line
196, the error branch currently returns the full state (return state) which is
inconsistent with other handlers that return an empty object to indicate a
no-op; change the early-return to return {} instead of state and ensure the
function's callers expect an object (adjust typing if necessary) so error/no-op
behavior matches the codebase convention and avoids accidental state spread.

Comment on lines +14 to +15
const [removed] = workspaceTabs.splice(startIndex, 1);
workspaceTabs.splice(endIndex, 0, removed);
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

Missing bounds validation for startIndex.

If startIndex is out of bounds (e.g., negative or >= array length), splice returns an empty array, making removed undefined. This undefined value would then be inserted into the array at endIndex, potentially corrupting state.

Consider adding a guard:

+	if (startIndex < 0 || startIndex >= workspaceTabs.length) return {};
+
 	const [removed] = workspaceTabs.splice(startIndex, 1);
+	if (!removed) return {};
 	workspaceTabs.splice(endIndex, 0, removed);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/desktop/src/renderer/stores/tabs/helpers/tab-ordering.ts around lines
14-15, the code removes an element using workspaceTabs.splice(startIndex, 1)
without validating startIndex; if startIndex is out of bounds splice returns an
empty array and removed becomes undefined which may corrupt state when inserted.
Add a guard that verifies startIndex is an integer within [0,
workspaceTabs.length - 1] (and normalize negative indices if intended) before
performing the splice; if invalid, no-op or return the original array (or throw
a clear error) and only perform the two splice operations when startIndex is
valid. Ensure endIndex is also clamped into the valid range before inserting.

@Kitenite Kitenite closed this Dec 2, 2025
@Kitenite Kitenite deleted the revert-220-indigo-forest-68 branch December 3, 2025 16:48
@Kitenite Kitenite restored the revert-220-indigo-forest-68 branch December 8, 2025 17:27
@Kitenite Kitenite reopened this Dec 8, 2025
Kitenite added a commit that referenced this pull request Dec 8, 2025
This refactor renames all internal 'window' terminology to 'tab' to avoid
confusion with Electron windows. The changes include:

- Window interface → Tab interface
- WindowsState/WindowsStore → TabsState/TabsStore
- useWindowsStore → useTabsStore
- windowId → tabId (in Pane interface and throughout)
- windows array → tabs array
- activeWindowIds → activeTabIds
- windowHistoryStacks → tabHistoryStacks
- All window-related function names (addWindow → addTab, etc.)
- Storage keys and DevTools names updated
- Comments and documentation updated
- Drag type constant: WINDOW → TAB

All typechecks and tests pass. This aligns with the previous PR #240 which
renamed tabs to windows, and now we're reverting that to use tabs for
internal terminal containers to avoid confusion with Electron windows.
Kitenite added a commit that referenced this pull request Dec 8, 2025
* refactor: rename internal terminal windows to tabs

This refactor renames all internal 'window' terminology to 'tab' to avoid
confusion with Electron windows. The changes include:

- Window interface → Tab interface
- WindowsState/WindowsStore → TabsState/TabsStore
- useWindowsStore → useTabsStore
- windowId → tabId (in Pane interface and throughout)
- windows array → tabs array
- activeWindowIds → activeTabIds
- windowHistoryStacks → tabHistoryStacks
- All window-related function names (addWindow → addTab, etc.)
- Storage keys and DevTools names updated
- Comments and documentation updated
- Drag type constant: WINDOW → TAB

All typechecks and tests pass. This aligns with the previous PR #240 which
renamed tabs to windows, and now we're reverting that to use tabs for
internal terminal containers to avoid confusion with Electron windows.

* refactor: rename 'New Tab' button to 'New Terminal' and update tab names to 'Terminal N'

- Change button text from 'New Tab' to 'New Terminal'
- Update default tab names from 'Tab 1', 'Tab 2' to 'Terminal 1', 'Terminal 2'
- Update fallback display name from 'Tab' to 'Terminal'

* refactor: rename window references to tab in component props and variables

* refactor: rename WindowItem to TabItem and WindowContextMenu to TabContextMenu

* refactor: rename WindowView to TabView and WindowPane to TabPane
@Kitenite Kitenite closed this Dec 11, 2025
@Kitenite Kitenite deleted the revert-220-indigo-forest-68 branch December 12, 2025 02:06
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