Skip to content

Tab closing lag when multiple tabs are open #3547

@devin-ai-integration

Description

@devin-ai-integration

Problem

When multiple tabs are opened in the desktop app, there is noticeable lag when closing tabs. This creates a poor user experience, especially when users have many tabs open and want to close several of them.

Investigation Findings

After analyzing the tab management code, I identified the following root causes:

1. Middleware Cascade Causing Multiple Re-renders (Critical - ~80% of the issue)

The tab store uses 3 stacked middleware layers: restoreMiddlewarelifecycleMiddlewarenavigationMiddleware

When close() is called:

  1. Initial set() in basic.ts:137-141 triggers ALL middleware
  2. restoreMiddleware detects closed tabs, calls another set() at restore.ts:82
  3. This second set() triggers the ENTIRE middleware chain again
  4. navigationMiddleware may call a third set() if flags changed (navigation.ts:263)

Impact: Closing 5 tabs results in ~15 state updates → 15 React re-renders

2. O(n²) Duplicate Computation (High)

Both restoreMiddleware and lifecycleMiddleware independently compute closed tabs using Array.filter + Array.some:

// restore.ts:72-75
const closedTabs = prevTabs.filter(
  (prevTab) => !nextTabs.some((nextTab) => nextTab.slotId === prevTab.slotId)
);

// lifecycle.ts:70-72
const closedTabs = prevTabs.filter(
  (prevTab) => !nextTabs.some((nextTab) => isSameTab(prevTab, nextTab))
);

This O(n²) computation happens on EVERY set() call, including cascading ones.

3. Heavy onClose Handlers (~10-13% of the issue)

Session tabs trigger triggerEnhancementOnClose which performs:

  • TinyBase index queries
  • JSON parsing of transcript words
  • AI task generation

These operations run synchronously in the middleware callback, blocking the UI thread.

4. Reorder.Group Animations (~2% of the issue)

The motion/react Reorder.Group component triggers layout animations on every tab array change, adding to the perceived lag.

Relevant Files

  • apps/desktop/src/store/zustand/tabs/lifecycle.ts
  • apps/desktop/src/store/zustand/tabs/restore.ts
  • apps/desktop/src/store/zustand/tabs/navigation.ts
  • apps/desktop/src/store/zustand/tabs/basic.ts
  • apps/desktop/src/components/main/body/sessions/index.tsx
  • apps/desktop/src/components/main/body/index.tsx

Recommended Fixes (Prioritized by Impact)

Priority 1: Batch Middleware State Updates (~87% improvement)

Combine all middleware into a single pass that:

  1. Computes closed tabs ONCE
  2. Applies all state changes (closedTabs, navigation flags) in a single set() call
  3. Calls lifecycle handlers AFTER all state is settled

Priority 2: Defer Heavy onClose Handlers (~10-13% improvement)

Use queueMicrotask or setTimeout to defer heavy operations:

closedTabs.forEach((tab) => {
  queueMicrotask(() => {
    nextState.onClose?.(tab);
  });
});

Priority 3: Optimize Tab Comparison (~3% improvement)

Use a Set for O(n) lookup instead of O(n²):

const nextTabSlotIds = new Set(nextTabs.map(t => t.slotId));
const closedTabs = prevTabs.filter(prevTab => !nextTabSlotIds.has(prevTab.slotId));

Priority 4: Reduce Reorder Animations (~2% improvement)

Reduce transition duration or disable animations during rapid close operations.

Acceptance Criteria

  • Tab closing feels responsive even with 10+ tabs open
  • No visible lag when closing multiple tabs in quick succession
  • Existing functionality (restore closed tabs, navigation history) continues to work
  • No data loss when closing session tabs

Labels

product/desktop, area/frontend, performance

Metadata

Metadata

Assignees

Type

Projects

Status

Backlog

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions