Skip to content

tabs interaction#111

Merged
Kitenite merged 8 commits intomainfrom
tabs-interaction
Nov 20, 2025
Merged

tabs interaction#111
Kitenite merged 8 commits intomainfrom
tabs-interaction

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Nov 20, 2025

Summary by CodeRabbit

  • New Features

    • In-list "New Terminal" button to quickly create a terminal/tab.
  • Improvements

    • Drag-and-drop: dropping onto tabs now consistently creates/upates grouped split layouts and updates active focus.
    • Smarter next-tab selection when closing tabs (uses workspace history, siblings, and top-level order).
    • Default single-tab title changed to "New Terminal".
  • Bug Fixes

    • Better group layout cleanup and stability during complex tab moves.
  • Refactoring

    • Internal tab management helpers reorganized for clarity.
  • UI

    • Adjusted drag behavior for top bar/workspace items and minor sidebar layout tweaks.

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 20, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Allows drops onto any existing tab (removes identity checks); expands drag-to-tab logic to create/reparent tabs and groups for many drop scenarios; extracts next-tab selection into findNextTab; refactors handleEmptyGroupRemoval signature to accept TabsState; adjusts tests and small UI/label behavior (no-drag wrappers, New Terminal button, default title).

Changes

Cohort / File(s) Summary
Drop validation
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useTabContentDrop.ts
Removed identity/equality checks in drop/canDrop; drop allowed whenever a target tab exists and invokes dragTabToTab.
Drag-to-tab logic & tests
apps/desktop/src/renderer/stores/tabs/drag-logic.ts, apps/desktop/src/renderer/stores/tabs/drag-logic.test.ts
Reworked handleDragTabToTab to handle self-drops, group creation/updates, reparenting, nested row/column splits and history updates; tests expanded/updated to assert layouts, parent relationships, and active-tab outcomes.
Next-tab finder & tests
apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.ts, apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.test.ts
Added findNextTab(state, tabId) implementing prioritized next-tab selection (history, group siblings, top-level ordering, fallback) with comprehensive tests.
Group operations & tab CRUD
apps/desktop/src/renderer/stores/tabs/helpers/group-operations.ts, apps/desktop/src/renderer/stores/tabs/helpers/tab-crud.ts
handleEmptyGroupRemoval signature changed to accept single TabsState; active-tab selection now favors fallbackActiveTabId then findNextTab; handleRemoveTab delegates to findNextTab; minor imports/usage updates.
Store tests robustness
apps/desktop/src/renderer/stores/tabs/store.test.ts
Relaxed test typings (casts) and added defensive checks for object/string layout shapes when locating newly created split children.
UI behavior & labels
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/index.tsx, apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeHeader.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx, apps/desktop/src/renderer/stores/tabs/utils.ts
Added/removed no-drag wrappers; ModeHeader returns early for "tabs" mode (renders spacer); TabsView adds a "New Terminal" ghost button and adjusts padding; default single-tab title changed to "New Terminal".

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant DropHook as useTabContentDrop
    participant DragLogic as handleDragTabToTab
    participant Store as TabsState

    User->>DropHook: Drop dragged tab onto target tab
    Note over DropHook: canDrop only verifies target exists
    DropHook->>DragLogic: call dragTabToTab(draggedId, targetId)

    alt target is same tab
        DragLogic->>Store: create new tab
        DragLogic->>Store: create/update group (row split 50/50)
        DragLogic->>Store: set group active
    else target is sibling / same group
        DragLogic->>Store: create new tab in group
        DragLogic->>Store: update group layout (split/nesting)
    else target is different group
        DragLogic->>Store: create new tab
        DragLogic->>Store: reparent tab, update group layouts
    end

    DragLogic-->>DropHook: updated TabsState
    DropHook-->>User: UI re-renders with new tabs/layout
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Files needing extra attention:
    • apps/desktop/src/renderer/stores/tabs/drag-logic.ts — high branching and state-mutation complexity.
    • apps/desktop/src/renderer/stores/tabs/helpers/group-operations.ts — signature change and active selection semantics.
    • apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.ts — priority logic correctness and edge cases.
  • Verify drag-logic.test.ts expectations match implemented layout shapes (string vs object layouts) and history stack updates.

Possibly related PRs

  • finish ui #106 — touches the same drop hook (useTabContentDrop) and canDrop/drop rules; likely closely related.
  • tabs mangement interaction #105 — overlaps on drag-logic and store helpers for tab drag-and-drop behavior.
  • refactor tabs store #110 — refactors tab store helpers (group removal/next-tab logic) and may interact with the new findNextTab and group-operations changes.

Poem

🐰 I hopped through tabs with whiskers bright,
Dropped onto self and split the night.
A new small pane in tidy rows,
FindNext leads where focus goes.
Hooray — the warren’s set just right! ✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description is largely incomplete and off-topic. It lists many unrelated PR numbers and features rather than explaining the actual changes in this PR, which focuses on tab interaction improvements and refactoring. Replace the list of unrelated PR references with a clear explanation of the actual changes: refactored tab drag logic, added findNextTab utility, improved tab removal handling, and UI updates to tab interactions.
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'tabs interaction' is vague and generic, using non-descriptive terms that don't convey meaningful information about the specific changes made to the codebase. Provide a more specific title that highlights the primary change, such as 'Improve tab drag-and-drop interactions and add findNextTab utility' or 'Refactor tab state management and drag logic'.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e77a98a and 3d6536c.

📒 Files selected for processing (1)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ModeCarousel/ModeContent.tsx (1 hunks)

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
apps/desktop/src/renderer/stores/tabs/store.test.ts (2)

18-31: Repeated as any casts in test setup could be localized

Using as const for the fixtures and then childTab as any (and similar for child1, child2, etc.) when setting state is fine for tests, but the pattern is a bit noisy and repeats in several places. Consider either:

  • Introducing a small typed helper to build Tab test objects, or
  • Narrowing the fixture type once (e.g., const childTab: Tab = { … }) so that the later setState calls don’t need as any.

Not urgent, but it would make the tests slightly cleaner and less brittle to type changes.

Also applies to: 59-79, 115-127, 157-169


221-230: Simplify layout checks by narrowing once before find

The newChild lookup currently repeats runtime guards inside the find predicate:

if (typeof groupTab.layout === "string" || !groupTab.layout) return;
const newChild = state.tabs.find(
  (t) =>
    typeof groupTab.layout !== "string" &&
    groupTab.layout &&
    "second" in groupTab.layout &&
    t.id === groupTab.layout.second &&
    t.type === TabType.Group,
);

Given the early return, those extra checks are always true at runtime. For clarity and to keep TypeScript happy without duplication, you could narrow once and capture the layout:

if (!groupTab.layout || typeof groupTab.layout === "string") return;
const { second } = groupTab.layout;

const newChild = state.tabs.find(
  (t) => t.id === second && t.type === TabType.Single,
);

Same pattern applies to the horizontal split test. This keeps the tests readable and removes redundant conditions.

Also applies to: 316-321

apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.test.ts (1)

1-245: Good coverage of next-tab behavior; consider adding history-stack cases

This suite nicely exercises the main flows in findNextTab (linear neighbors, grouped children, cross-workspace isolation, and the ultimate fallback). One thing you might add later is an explicit case where tabHistoryStacks[workspaceId] is populated, to lock in the “history wins first” behavior before group/index fallbacks.

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

258-289: Consider guarding against cross-workspace drags in handleDragTabToTab

The new same-group behaviors (child→child and child→group both creating new tabs in the group) are nicely aligned and go through addToParentGroup, with removeFromOldParent used only when moving between different parents. One hardening you might want:

  • Early in handleDragTabToTab, after finding draggedTab and targetTab, bail out if their workspaceIds differ:
if (draggedTab.workspaceId !== targetTab.workspaceId) {
  return state;
}

Right now the logic assumes UI-level constraints prevent cross-workspace drags; adding this guard would make the store more robust to future UI changes and avoid accidentally wiring a tab into a group in another workspace.

Also applies to: 327-352, 127-143

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e217fbb and 9646062.

📒 Files selected for processing (8)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useTabContentDrop.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/drag-logic.test.ts (5 hunks)
  • apps/desktop/src/renderer/stores/tabs/drag-logic.ts (3 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/group-operations.ts (7 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.test.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/tab-crud.ts (2 hunks)
  • apps/desktop/src/renderer/stores/tabs/store.test.ts (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.test.ts (3)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • TabsState (27-31)
apps/desktop/src/shared/types.ts (1)
  • TabType (23-30)
apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.ts (1)
  • findNextTab (11-48)
apps/desktop/src/renderer/stores/tabs/store.test.ts (2)
apps/desktop/src/renderer/stores/tabs/store.ts (1)
  • useTabsStore (34-138)
apps/desktop/src/shared/types.ts (1)
  • TabType (23-30)
apps/desktop/src/renderer/stores/tabs/helpers/tab-crud.ts (1)
apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.ts (1)
  • findNextTab (11-48)
apps/desktop/src/renderer/stores/tabs/drag-logic.test.ts (3)
apps/desktop/src/shared/types.ts (1)
  • TabType (23-30)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • Tab (25-25)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (1)
  • handleDragTabToTab (127-467)
apps/desktop/src/renderer/stores/tabs/drag-logic.ts (3)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)
  • createNewTab (10-36)
apps/desktop/src/shared/types.ts (1)
  • TabType (23-30)
apps/desktop/src/renderer/stores/tabs/types.ts (2)
  • TabGroup (20-23)
  • Tab (25-25)
apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.ts (1)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • TabsState (27-31)
apps/desktop/src/renderer/stores/tabs/helpers/group-operations.ts (3)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • TabsState (27-31)
apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.ts (1)
  • findNextTab (11-48)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)
  • getChildTabIds (6-8)
🔇 Additional comments (17)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/useTabContentDrop.ts (1)

15-22: Self-drop is now allowed; relies on store logic for safeguards

The updated drop and canDrop handlers now allow dropping a tab onto itself (any non-null tabToRender), which matches the new drag behavior. This is fine as long as handleDragTabToTab fully defines what self-drop does (group creation, etc.), which seems to be the case. No additional validation looks necessary at the DnD layer.

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

4-4: Delegating next-active-tab selection to findNextTab looks correct

Using findNextTab(state, id) inside handleRemoveTab is a good centralization of “what becomes active next.” Passing the original state (before removing the tab and before pruning the history stack) is important so the helper can see the closing tab and its history/layout when making the decision. The resulting nextTabId can legitimately be null, which aligns with the activeTabIds type.

Also applies to: 39-63

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

144-255: Self-drop behavior is consistent and preserves layout/parent invariants

The new Rule 1 handling for draggedTabId === targetTabId looks solid:

  • Child tab: creates a new sibling in the same group, updates layout via addToParentGroup, and keeps the group as active.
  • Group tab: adds a new child tab to the group and extends its layout, again keeping the group active.
  • Standalone single: promotes the tab into a new group plus a new sibling, inserts that group at the original tab’s top-level index, and rewires parentId/layout appropriately.

Using addToParentGroup in all three branches keeps the mosaic layout coherent, and re-partitioning into workspaceTabsUpdated vs otherTabsUpdated maintains the expected ordering of top-level vs child tabs. I don’t see any immediate correctness issues here.

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

2-2: LGTM: Import additions support the refactored logic.

The new imports for removeTabFromLayout and findNextTab are correctly added to support the state-based refactor and next-tab selection logic.

Also applies to: 6-7


9-60: Clean state-based refactor with improved next-tab selection.

The refactored signature accepting a single TabsState object is cleaner and more maintainable. The integration of findNextTab properly delegates next-tab selection to a dedicated utility.

The logic correctly handles the case where the active tab is being removed by using the fallback, then findNextTab, then defaulting to the first workspace tab.


62-72: LGTM: Minor formatting improvement.

The condensed ternary on line 69 maintains the same logic with improved readability.


96-126: LGTM: Correctly updated to use new state-based API.

The call to handleEmptyGroupRemoval correctly passes the current state object and properly handles the case where removing a child leaves an empty group.


128-218: LGTM: Correct state composition for nested call.

Line 169 correctly composes a new state object with updatedTabs before calling handleEmptyGroupRemoval. This ensures the next-tab finder sees the ungrouped tab as a valid candidate, and the fallback correctly activates the ungrouped tab when the parent group is removed.

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

98-142: LGTM: Comprehensive test for self-drop group creation.

The test thoroughly validates that dragging a tab onto itself creates a new group containing both the original tab and a newly created tab, with proper parent relationships and a 50/50 row layout. The assertions correctly verify the group becomes active.


144-192: LGTM: Well-structured test for child self-drop.

The test correctly validates that dragging a child tab onto itself creates a new tab within the parent group, updates the group layout to include both tabs, and maintains the group as active. Good coverage of parent-child relationships.


712-917: LGTM: Comprehensive coverage of same-group drag scenarios.

These three new tests thoroughly validate edge cases where tabs are dragged within the same group:

  1. Dragging a tab onto its own parent group creates a new sibling
  2. Dragging a group onto itself creates a new child within that group
  3. Dragging a child onto a sibling creates a new tab in the same group

All tests properly verify the nested layout structures, parent relationships, and active state management. The consistent layout patterns across these tests indicate predictable behavior.

apps/desktop/src/renderer/stores/tabs/helpers/next-tab-finder.ts (6)

11-48: LGTM: Well-structured priority-based next-tab selection.

The main function implements a clear four-level priority system for selecting the next tab. The early returns for edge cases (tab not found, no remaining tabs) are correctly handled, and the progressive fallback strategy is logical.


53-72: LGTM: Robust history-based selection.

The function correctly filters the closing tab from the history stack and validates that the selected tab still exists and belongs to the same workspace. The iteration through the history ensures stale entries don't cause issues.


77-105: LGTM: Position-based sibling selection.

The function correctly finds the next sibling within the same group using position-based logic (next first, then previous). This provides predictable behavior for users closing tabs within groups.


110-154: LGTM: Correct position-based top-level tab selection.

The function properly handles both grouped and top-level tabs:

  • For grouped tabs, it delegates to findNextRelativeToParentGroup to find tabs near the parent's position
  • For top-level tabs, it uses index arithmetic that correctly accounts for the removed tab when selecting the next or previous tab

The index calculations are subtle but correct: after filtering out the closing tab, remainingTabs[currentIndex] gives the next tab (indices shifted down), and remainingTabs[currentIndex - 1] gives the previous tab.


159-183: LGTM: Logical parent-relative positioning.

When closing a child tab with no siblings, finding the next tab relative to the parent group's position is intuitive and provides good UX. The function correctly handles edge cases where the parent is at the beginning or end of the workspace tabs.


188-211: LGTM: Sensible ultimate fallback with top-level preference.

The fallback strategy correctly prioritizes top-level tabs (more accessible to users) before falling back to any remaining tab in the workspace. This ensures a tab is always selected when possible while maintaining good UX.

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