Skip to content

Commit ba8f3c9

Browse files
authored
fix: make opening tabs switch to existing ones if possible (#3687)
1 parent e847341 commit ba8f3c9

File tree

5 files changed

+26
-73
lines changed

5 files changed

+26
-73
lines changed

apps/desktop/src/components/main/body/sessions/index.tsx

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,7 @@ import { useStartListening } from "../../../../hooks/useStartListening";
1818
import { useSTTConnection } from "../../../../hooks/useSTTConnection";
1919
import { useTitleGeneration } from "../../../../hooks/useTitleGeneration";
2020
import * as main from "../../../../store/tinybase/store/main";
21-
import {
22-
rowIdfromTab,
23-
type Tab,
24-
useTabs,
25-
} from "../../../../store/zustand/tabs";
21+
import { type Tab, useTabs } from "../../../../store/zustand/tabs";
2622
import { StandardTabWrapper } from "../index";
2723
import { type TabItem, TabItemBase } from "../shared";
2824
import { CaretPositionProvider } from "./caret-position-context";
@@ -52,12 +48,7 @@ export const TabItemNote: TabItem<Extract<Tab, { type: "sessions" }>> = ({
5248
pendingCloseConfirmationTab,
5349
setPendingCloseConfirmationTab,
5450
}) => {
55-
const title = main.UI.useCell(
56-
"sessions",
57-
rowIdfromTab(tab),
58-
"title",
59-
main.STORE_ID,
60-
);
51+
const title = main.UI.useCell("sessions", tab.id, "title", main.STORE_ID);
6152
const sessionMode = useListener((state) => state.getSessionMode(tab.id));
6253
const stop = useListener((state) => state.stop);
6354
const isEnhancing = useIsSessionEnhancing(tab.id);

apps/desktop/src/store/zustand/tabs/basic.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ describe("Basic Tab Actions", () => {
4141
});
4242
});
4343

44-
test("openCurrent replaces active tab and closes all duplicates", () => {
44+
test("openCurrent switches to existing tab instead of replacing active", () => {
4545
const session1 = createSessionTab({ active: false });
4646
const session2 = createSessionTab({ active: false });
4747
const session3 = createSessionTab({ active: false });
@@ -56,13 +56,13 @@ describe("Basic Tab Actions", () => {
5656
useTabs.getState().openCurrent(duplicateOfSession1);
5757

5858
expect(useTabs.getState()).toMatchTabsInOrder([
59-
{ id: session2.id, active: false },
6059
{ id: session1.id, active: true },
60+
{ id: session2.id, active: false },
61+
{ id: session3.id, active: false },
6162
]);
62-
expect(useTabs.getState()).toHaveLastHistoryEntry({ id: session1.id });
63-
expect(useTabs.getState()).toHaveHistoryLength(2);
63+
expect(useTabs.getState()).toHaveHistoryLength(1);
6464
expect(useTabs.getState()).toHaveNavigationState({
65-
canGoBack: true,
65+
canGoBack: false,
6666
canGoNext: false,
6767
});
6868
});

apps/desktop/src/store/zustand/tabs/basic.ts

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ export const createBasicSlice = <
5555
listenerStore.getState().live.status === "finalizing");
5656

5757
if (currentActiveTab?.pinned || isCurrentTabListening) {
58-
set(openTab(tabs, tab, history, false));
59-
} else {
6058
set(openTab(tabs, tab, history, true));
59+
} else {
60+
set(openTab(tabs, tab, history, false));
6161
}
6262

6363
if (tab.type === "sessions") {
@@ -71,7 +71,7 @@ export const createBasicSlice = <
7171
},
7272
openNew: (tab) => {
7373
const { tabs, history, addRecentlyOpened } = get();
74-
set(openTab(tabs, tab, history, false));
74+
set(openTab(tabs, tab, history, true));
7575

7676
if (tab.type === "sessions") {
7777
addRecentlyOpened(tab.id);
@@ -229,10 +229,6 @@ export const createBasicSlice = <
229229
},
230230
});
231231

232-
const removeDuplicates = (tabs: Tab[], newTab: Tab): Tab[] => {
233-
return tabs.filter((t) => !isSameTab(t, newTab));
234-
};
235-
236232
const setActiveFlags = (tabs: Tab[], activeTab: Tab): Tab[] => {
237233
return tabs.map((t) => ({ ...t, active: isSameTab(t, activeTab) }));
238234
};
@@ -258,7 +254,7 @@ const openTab = <T extends BasicState & NavigationState>(
258254
tabs: Tab[],
259255
newTab: TabInput,
260256
history: Map<string, TabHistory>,
261-
replaceActive: boolean,
257+
forceNewTab: boolean,
262258
): Partial<T> => {
263259
const tabWithDefaults: Tab = {
264260
...getDefaultState(newTab),
@@ -272,7 +268,13 @@ const openTab = <T extends BasicState & NavigationState>(
272268
const existingTab = tabs.find((t) => isSameTab(t, tabWithDefaults));
273269
const isNewTab = !existingTab;
274270

275-
if (replaceActive) {
271+
if (!isNewTab) {
272+
nextTabs = setActiveFlags(tabs, existingTab!);
273+
const currentTab = { ...existingTab!, active: true };
274+
return { tabs: nextTabs, currentTab, history } as Partial<T>;
275+
}
276+
277+
if (!forceNewTab) {
276278
const existingActiveIdx = tabs.findIndex((t) => t.active);
277279
const currentActiveTab = tabs[existingActiveIdx];
278280

@@ -283,32 +285,20 @@ const openTab = <T extends BasicState & NavigationState>(
283285
slotId: currentActiveTab.slotId,
284286
};
285287

286-
nextTabs = tabs
287-
.map((t, idx) => {
288-
if (idx === existingActiveIdx) {
289-
return activeTab;
290-
}
291-
if (isSameTab(t, tabWithDefaults)) {
292-
return null;
293-
}
294-
return { ...t, active: false };
295-
})
296-
.filter((t): t is Tab => t !== null);
288+
nextTabs = tabs.map((t, idx) => {
289+
if (idx === existingActiveIdx) {
290+
return activeTab;
291+
}
292+
return { ...t, active: false };
293+
});
297294
} else {
298295
activeTab = { ...tabWithDefaults, active: true, slotId: id() };
299-
const withoutDuplicates = removeDuplicates(tabs, tabWithDefaults);
300-
const deactivated = deactivateAll(withoutDuplicates);
296+
const deactivated = deactivateAll(tabs);
301297
nextTabs = [...deactivated, activeTab];
302298
}
303299

304300
return updateWithHistory(nextTabs, activeTab, history);
305301
} else {
306-
if (!isNewTab) {
307-
nextTabs = setActiveFlags(tabs, existingTab!);
308-
const currentTab = { ...existingTab!, active: true };
309-
return { tabs: nextTabs, currentTab, history } as Partial<T>;
310-
}
311-
312302
activeTab = { ...tabWithDefaults, active: true, slotId: id() };
313303
const deactivated = deactivateAll(tabs);
314304
nextTabs = [...deactivated, activeTab];

apps/desktop/src/store/zustand/tabs/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import {
3434
import { createStateUpdaterSlice, type StateBasicActions } from "./state";
3535

3636
export type { SettingsState, SettingsTab, Tab, TabInput } from "./schema";
37-
export { isSameTab, rowIdfromTab, uniqueIdfromTab } from "./schema";
37+
export { isSameTab, uniqueIdfromTab } from "./schema";
3838
export { restorePinnedTabsToStore, restoreRecentlyOpenedToStore };
3939

4040
type State = BasicState &

apps/desktop/src/store/zustand/tabs/schema.ts

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -195,34 +195,6 @@ export const getDefaultState = (tab: TabInput): Tab => {
195195
}
196196
};
197197

198-
export const rowIdfromTab = (tab: Tab): string => {
199-
switch (tab.type) {
200-
case "sessions":
201-
return tab.id;
202-
case "humans":
203-
return tab.id;
204-
case "organizations":
205-
return tab.id;
206-
case "contacts":
207-
case "templates":
208-
case "prompts":
209-
case "chat_shortcuts":
210-
case "extensions":
211-
case "empty":
212-
case "extension":
213-
case "calendar":
214-
case "changelog":
215-
case "settings":
216-
case "ai":
217-
throw new Error("invalid_resource");
218-
case "folders":
219-
if (!tab.id) {
220-
throw new Error("invalid_resource");
221-
}
222-
return tab.id;
223-
}
224-
};
225-
226198
export const uniqueIdfromTab = (tab: Tab): string => {
227199
switch (tab.type) {
228200
case "sessions":

0 commit comments

Comments
 (0)