feat(tui): add inbox tab layout

This commit is contained in:
Ryan Vogel 2026-08-01 10:40:26 -04:00
commit a01d24e908
15 changed files with 693 additions and 27 deletions

View file

@ -6,15 +6,42 @@ import {
moveSessionTab,
moveSessionTabHistory,
openSessionTab,
orderSessionTabs,
recordClosedSessionTab,
recordSessionTabHistory,
reopenSessionTab,
seedSessionTabMotion,
sessionInboxGroup,
sessionTabComplete,
sessionTabOverflowWidth,
} from "../../src/context/session-tabs-model"
describe("session tabs", () => {
test("orders running sessions first and completed sessions by recent update", () => {
const tabs = ["old", "running-old", "new", "running-new"].map((sessionID) => ({ sessionID }))
const state = {
old: { busy: false, updated: 10 },
"running-old": { busy: true, updated: 20 },
new: { busy: false, updated: 40 },
"running-new": { busy: true, updated: 30 },
}
expect(orderSessionTabs(tabs, (sessionID) => state[sessionID as keyof typeof state]).map((tab) => tab.sessionID)).toEqual([
"running-new",
"running-old",
"new",
"old",
])
})
test("groups inbox tabs by running state and local calendar day", () => {
const now = new Date(2026, 6, 31, 12).getTime()
expect(sessionInboxGroup(new Date(2026, 6, 20).getTime(), true, now)).toBe("running")
expect(sessionInboxGroup(new Date(2026, 6, 31, 1).getTime(), false, now)).toBe("today")
expect(sessionInboxGroup(new Date(2026, 6, 30, 1).getTime(), false, now)).toBe("yesterday")
expect(sessionInboxGroup(new Date(2026, 6, 29, 23).getTime(), false, now)).toBe("earlier")
})
test("moves a tab to a clamped index and returns the same tabs for no-ops", () => {
const tabs = ["a", "b", "c"].map((sessionID) => ({ sessionID }))
expect(moveSessionTab(tabs, "a", 2).map((tab) => tab.sessionID)).toEqual(["b", "c", "a"])

View file

@ -248,3 +248,33 @@ test("tracks a temporary new session tab across close and creation", async () =>
setup.destroy()
}
})
test("navigates the inbox without changing sessions and confirms done twice", async () => {
const setup = await renderSessionTabs("first")
try {
await wait(() => setup.tabs.current() === "first")
setup.route.navigate({ type: "session", sessionID: "second" })
await wait(() => setup.tabs.current() === "second" && setup.tabs.tabs().length === 2)
setup.route.navigate({ type: "session", sessionID: "first" })
await wait(() => setup.tabs.current() === "first")
expect(setup.tabs.navigation.focus()).toBe(true)
expect(setup.tabs.navigation.selected()).toBe("first")
setup.tabs.navigation.move(1)
expect(setup.tabs.navigation.selected()).toBe("second")
expect(setup.tabs.current()).toBe("first")
setup.tabs.navigation.done()
expect(setup.tabs.navigation.pendingDone()).toBe("second")
expect(setup.tabs.tabs().map((tab) => tab.sessionID)).toEqual(["first", "second"])
setup.tabs.navigation.done()
await wait(() => setup.tabs.tabs().length === 1)
expect(setup.tabs.tabs().map((tab) => tab.sessionID)).toEqual(["first"])
expect(setup.tabs.current()).toBe("first")
expect(setup.tabs.navigation.selected()).toBe("first")
} finally {
setup.destroy()
}
})