Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions src/contexts/tab-context.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2786,4 +2786,137 @@ describe("TabProvider tab groups", () => {
expect(isReparentUnmount(store(), "conv-1-codex-1", home)).toBe(false)
})
})

// Tile Display shows every tab in a group as its own pane at the same time,
// so consuming one of those panes for a preview would remove a session the
// mode exists to display. A tiled group hands the preview a slot of its own;
// an untiled group keeps the single preview slot it has always had.
describe("preview opens in a tiled group", () => {
/** `[id, isPinned]` for each tab the group holds, in strip order. */
const paneState = (groupId: string) =>
store()
.rawTabs.filter((tab) => groupOfId(tab.id) === groupId)
.map((tab) => [tab.id, tab.isPinned])

it("gives the preview its own pane instead of replacing one", async () => {
await renderWithTabs([tabItem(1, 1, true)])
const home = leaves()[0]

act(() => {
store().openTab(1, 2, "codex", false, "Second")
})
act(() => {
store().toggleGroupTile(home)
})
act(() => {
store().openTab(1, 3, "codex", false, "Third")
})
act(() => {
store().openTab(1, 4, "codex", false, "Fourth")
})

expect(paneState(home)).toEqual([
["conv-1-codex-1", true],
["conv-1-codex-2", false],
["conv-1-codex-3", false],
["conv-1-codex-4", false],
])
expect(store().activeTabId).toBe("conv-1-codex-4")
})

it("still replaces the preview when the group is not tiled", async () => {
await renderWithTabs([tabItem(1, 1, true)])
const home = leaves()[0]

act(() => {
store().openTab(1, 2, "codex", false, "Second")
})
act(() => {
store().openTab(1, 3, "codex", false, "Third")
})

expect(paneState(home)).toEqual([
["conv-1-codex-1", true],
["conv-1-codex-3", false],
])
expect(store().activeTabId).toBe("conv-1-codex-3")
})

it("replaces in the focused untiled group while a sibling group is tiled", async () => {
await renderWithTabs([tabItem(1, 1, true), tabItem(1, 2)])
const home = leaves()[0]

act(() => {
store().splitTab("conv-1-codex-2", "right", { move: true })
})
const g1 = newLeafBeside(home)
act(() => {
store().toggleGroupTile(g1)
store().switchTab("conv-1-codex-1")
})
act(() => {
store().openTab(1, 3, "codex", false, "Third")
})
act(() => {
store().openTab(1, 4, "codex", false, "Fourth")
})

expect(paneState(home)).toEqual([
["conv-1-codex-1", true],
["conv-1-codex-4", false],
])
expect(paneState(g1)).toEqual([["conv-1-codex-2", true]])
})

it("focuses a conversation the tiled group already shows", async () => {
await renderWithTabs([tabItem(1, 1, true)])
const home = leaves()[0]

act(() => {
store().toggleGroupTile(home)
})
act(() => {
store().openTab(1, 2, "codex", false, "Second")
})
act(() => {
store().switchTab("conv-1-codex-1")
})
act(() => {
store().openTab(1, 2, "codex", false, "Second")
})

expect(paneState(home)).toEqual([
["conv-1-codex-1", true],
["conv-1-codex-2", false],
])
expect(store().activeTabId).toBe("conv-1-codex-2")
})

it("keeps every pane when pinned and preview opens interleave", async () => {
await renderWithTabs([tabItem(1, 1, true), tabItem(1, 2)])
const home = leaves()[0]

act(() => {
store().toggleGroupTile(home)
})
act(() => {
store().openTab(1, 3, "codex", false, "Third")
})
act(() => {
store().openTab(1, 4, "codex", true, "Fourth")
})
act(() => {
store().openTab(1, 5, "codex", false, "Fifth")
})

expect(paneState(home)).toEqual([
["conv-1-codex-1", true],
["conv-1-codex-2", true],
["conv-1-codex-3", false],
["conv-1-codex-4", true],
["conv-1-codex-5", false],
])
expect(store().activeTabId).toBe("conv-1-codex-5")
})
})
})
22 changes: 14 additions & 8 deletions src/stores/tab-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,9 @@ export interface TabStoreState {
opts?: {
/** rawTabs slot for a tab that needs a NEW slot (clamped); omitted =
* append. Reopening a closed tab passes the slot it was closed from. A
* conversation that is already open is focused where it is, and a
* preview still replaces the group's current preview in place. */
* conversation that is already open is focused where it is. A preview
* replaces the group's existing preview in place for untiled groups; in
* tiled groups it takes a slot of its own (so no visible pane is lost). */
index?: number
}
) => void
Expand Down Expand Up @@ -1178,12 +1179,17 @@ export const useTabStore = create<TabStoreState>()((set, get) => ({

// Preview replacement stays within the focused group — a preview parked in
// another group is left alone (the new tab gets a slot of its own instead).
const previewIndex = prevState.rawTabs.findIndex(
(t) =>
!t.isPinned &&
groupOfTab(prevState.groupOf, prevState.groupLayout, t.id) ===
targetGroup
)
// A TILED group shows every one of its tabs as a pane at the same time, so
// replacing one there would silently remove a pane the mode exists to show.
// Tiled groups therefore give the new tab its own slot, like a pinned open.
const previewIndex = prevState.tileByGroup[targetGroup]
? -1
: prevState.rawTabs.findIndex(
(t) =>
!t.isPinned &&
groupOfTab(prevState.groupOf, prevState.groupLayout, t.id) ===
targetGroup
)
if (previewIndex >= 0) {
const updated = [...prevState.rawTabs]
const replacedPreviewTabId = updated[previewIndex].id
Expand Down
Loading