|
1 | | -import { createEffect, onCleanup } from "solid-js" |
| 1 | +import { createEffect, createMemo, onCleanup } from "solid-js" |
2 | 2 | import { isDeepEqual } from "remeda" |
3 | 3 | import { createSimpleContext } from "./helper" |
| 4 | +import { useClient } from "./client" |
4 | 5 | import { useData } from "./data" |
5 | 6 | import { useEvent } from "./event" |
6 | 7 | import { useRoute } from "./route" |
@@ -31,10 +32,14 @@ type PersistedState = { |
31 | 32 |
|
32 | 33 | const empty = (): TabsState => ({ tabs: [], unread: {} }) |
33 | 34 |
|
| 35 | +// Deliberately after connect settles: the visible session's mount syncs win the first slots. |
| 36 | +const TAB_PREFETCH_DELAY = 300 |
| 37 | + |
34 | 38 | export const { use: useSessionTabs, provider: SessionTabsProvider } = createSimpleContext({ |
35 | 39 | name: "SessionTabs", |
36 | 40 | init: () => { |
37 | 41 | const route = useRoute() |
| 42 | + const client = useClient() |
38 | 43 | const data = useData() |
39 | 44 | const event = useEvent() |
40 | 45 | const config = useConfig().data |
@@ -129,6 +134,45 @@ export const { use: useSessionTabs, provider: SessionTabsProvider } = createSimp |
129 | 134 | }) |
130 | 135 | }) |
131 | 136 |
|
| 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 | + |
132 | 176 | onCleanup(event.on("session.execution.succeeded", (evt) => markUnread(evt.data.sessionID, "activity"))) |
133 | 177 | onCleanup(event.on("session.execution.interrupted", (evt) => markUnread(evt.data.sessionID, "activity"))) |
134 | 178 | onCleanup(event.on("session.execution.failed", (evt) => markUnread(evt.data.sessionID, "error"))) |
|
0 commit comments