Skip to content

Commit c161978

Browse files
authored
feat(tui): prefetch open session tabs after connect (#39589)
1 parent cb37a71 commit c161978

1 file changed

Lines changed: 45 additions & 1 deletion

File tree

packages/tui/src/context/session-tabs.tsx

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { createEffect, onCleanup } from "solid-js"
1+
import { createEffect, createMemo, onCleanup } from "solid-js"
22
import { isDeepEqual } from "remeda"
33
import { createSimpleContext } from "./helper"
4+
import { useClient } from "./client"
45
import { useData } from "./data"
56
import { useEvent } from "./event"
67
import { useRoute } from "./route"
@@ -31,10 +32,14 @@ type PersistedState = {
3132

3233
const empty = (): TabsState => ({ tabs: [], unread: {} })
3334

35+
// Deliberately after connect settles: the visible session's mount syncs win the first slots.
36+
const TAB_PREFETCH_DELAY = 300
37+
3438
export const { use: useSessionTabs, provider: SessionTabsProvider } = createSimpleContext({
3539
name: "SessionTabs",
3640
init: () => {
3741
const route = useRoute()
42+
const client = useClient()
3843
const data = useData()
3944
const event = useEvent()
4045
const config = useConfig().data
@@ -129,6 +134,45 @@ export const { use: useSessionTabs, provider: SessionTabsProvider } = createSimp
129134
})
130135
})
131136

137+
// Warm open tabs' session data so first switches render from cache instead of fetching inside
138+
// the switch gesture. Uses only existing sync methods (each dedupes internally), so reruns on
139+
// tab-set or connection changes are no-ops for already-warm sessions, and reconnects double as
140+
// a cache refresh after an SSE gap. The delay lets the current session's own mount syncs get
141+
// the first connection slots. The effect tracks only the id set: reorders, tab switches, and
142+
// title updates neither restart the timer nor an in-flight warm pass; the timer callback
143+
// itself runs untracked, where the current session is skipped.
144+
const openTabSessions = createMemo(() =>
145+
state()
146+
.tabs.map((tab) => tab.sessionID)
147+
.sort()
148+
.join("\n"),
149+
)
150+
createEffect(() => {
151+
if (!enabled()) return
152+
if (client.connection.status() !== "connected") return
153+
if (openTabSessions() === "") return
154+
let stale = false
155+
const timer = setTimeout(async () => {
156+
const sessions = state()
157+
.tabs.map((tab) => tab.sessionID)
158+
.filter((sessionID) => sessionID !== current())
159+
for (const sessionID of sessions) {
160+
if (stale) return
161+
await Promise.allSettled([
162+
data.session.sync(sessionID),
163+
data.session.message.sync(sessionID),
164+
data.session.pending.sync(sessionID),
165+
data.session.permission.sync(sessionID),
166+
data.session.form.sync(sessionID),
167+
])
168+
}
169+
}, TAB_PREFETCH_DELAY)
170+
onCleanup(() => {
171+
stale = true
172+
clearTimeout(timer)
173+
})
174+
})
175+
132176
onCleanup(event.on("session.execution.succeeded", (evt) => markUnread(evt.data.sessionID, "activity")))
133177
onCleanup(event.on("session.execution.interrupted", (evt) => markUnread(evt.data.sessionID, "activity")))
134178
onCleanup(event.on("session.execution.failed", (evt) => markUnread(evt.data.sessionID, "error")))

0 commit comments

Comments
 (0)