mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-10 05:53:25 +00:00
fix(tui): load sidebar project names sooner (#40763)
This commit is contained in:
parent
c46f6ae112
commit
6f91bc7415
2 changed files with 54 additions and 16 deletions
|
|
@ -157,13 +157,9 @@ export const { use: useSessionTabs, provider: SessionTabsProvider } = createSimp
|
|||
})
|
||||
})
|
||||
|
||||
// Warm open tabs' session data so first switches render from cache instead of fetching inside
|
||||
// the switch gesture. Uses only existing sync methods (each dedupes internally), so reruns on
|
||||
// tab-set or connection changes are no-ops for already-warm sessions, and reconnects double as
|
||||
// a cache refresh after an SSE gap. The delay lets the current session's own mount syncs get
|
||||
// the first connection slots. The effect tracks only the id set: reorders, tab switches, and
|
||||
// title updates neither restart the timer nor an in-flight warm pass; the timer callback
|
||||
// itself runs untracked, where the current session is skipped.
|
||||
// Load lightweight session metadata concurrently so persisted tabs can resolve their project
|
||||
// labels immediately. Delay the heavier per-tab data so the visible session keeps the first
|
||||
// connection slots and switches still render from a warm cache.
|
||||
const openTabSessions = createMemo(() =>
|
||||
state()
|
||||
.tabs.map((tab) => tab.sessionID)
|
||||
|
|
@ -173,7 +169,9 @@ export const { use: useSessionTabs, provider: SessionTabsProvider } = createSimp
|
|||
createEffect(() => {
|
||||
if (!enabled()) return
|
||||
if (client.connection.status() !== "connected") return
|
||||
if (openTabSessions() === "") return
|
||||
const sessionIDs = openTabSessions()
|
||||
if (sessionIDs === "") return
|
||||
void Promise.allSettled(sessionIDs.split("\n").map((sessionID) => data.session.sync(sessionID)))
|
||||
let stale = false
|
||||
const timer = setTimeout(async () => {
|
||||
const sessions = state()
|
||||
|
|
@ -182,7 +180,6 @@ export const { use: useSessionTabs, provider: SessionTabsProvider } = createSimp
|
|||
for (const sessionID of sessions) {
|
||||
if (stale) return
|
||||
await Promise.allSettled([
|
||||
data.session.sync(sessionID),
|
||||
data.session.message.sync(sessionID),
|
||||
data.session.pending.sync(sessionID),
|
||||
data.session.permission.sync(sessionID),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { afterAll, expect, test } from "bun:test"
|
||||
import type { OpenCodeEvent } from "@opencode-ai/client"
|
||||
import { testRender } from "@opentui/solid"
|
||||
import { mkdtempSync, readdirSync, rmSync, watch } from "fs"
|
||||
import { mkdirSync, mkdtempSync, readdirSync, rmSync, watch } from "fs"
|
||||
import { tmpdir } from "os"
|
||||
import path from "path"
|
||||
import { ConfigProvider } from "../../src/config"
|
||||
|
|
@ -49,15 +49,33 @@ function stateDir(prefix: string) {
|
|||
return dir
|
||||
}
|
||||
|
||||
async function renderSessionTabs(initialSessionID: string, options?: { state?: string; title?: string }) {
|
||||
async function renderSessionTabs(
|
||||
initialSessionID: string,
|
||||
options?: { state?: string; title?: string; home?: boolean; persisted?: string[]; sessionGate?: Promise<void> },
|
||||
) {
|
||||
const state = options?.state ?? stateDir("opencode-session-tabs-")
|
||||
if (options?.persisted) {
|
||||
const file = path.join(state, "test", "tui", "tabs.json")
|
||||
mkdirSync(path.dirname(file), { recursive: true })
|
||||
await Bun.write(
|
||||
file,
|
||||
JSON.stringify({
|
||||
global: { tabs: options.persisted.map((sessionID) => ({ sessionID })), unread: {} },
|
||||
cwd: {},
|
||||
}),
|
||||
)
|
||||
}
|
||||
const events = createEventStream()
|
||||
const calls = createFetch((url) => {
|
||||
if (url.pathname !== `/api/session/${initialSessionID}`) return
|
||||
const sessions: string[] = []
|
||||
const calls = createFetch(async (url) => {
|
||||
const sessionID = url.pathname.match(/^\/api\/session\/([^/]+)$/)?.[1]
|
||||
if (!sessionID) return undefined
|
||||
sessions.push(sessionID)
|
||||
await options?.sessionGate
|
||||
return json({
|
||||
data: {
|
||||
id: initialSessionID,
|
||||
title: options?.title,
|
||||
id: sessionID,
|
||||
title: sessionID === initialSessionID ? options?.title : undefined,
|
||||
projectID: "project",
|
||||
location: { directory },
|
||||
cost: 0,
|
||||
|
|
@ -84,7 +102,9 @@ async function renderSessionTabs(initialSessionID: string, options?: { state?: s
|
|||
<TuiAppProvider value={{ name: "test", version: "test", channel: "test" }}>
|
||||
<StorageProvider>
|
||||
<ConfigProvider config={createTuiResolvedConfig({ tabs: { enabled: true } })}>
|
||||
<RouteProvider initialRoute={{ type: "session", sessionID: initialSessionID }}>
|
||||
<RouteProvider
|
||||
initialRoute={options?.home ? { type: "home" } : { type: "session", sessionID: initialSessionID }}
|
||||
>
|
||||
<ClientProvider api={createApi(calls.fetch)}>
|
||||
<DataProvider>
|
||||
<SessionTabsProvider>
|
||||
|
|
@ -104,6 +124,7 @@ async function renderSessionTabs(initialSessionID: string, options?: { state?: s
|
|||
tabs,
|
||||
route,
|
||||
data,
|
||||
sessions,
|
||||
state,
|
||||
emit: (event: OpenCodeEvent) => events.emit({ ...event, location: { directory } }),
|
||||
destroy() {
|
||||
|
|
@ -112,6 +133,26 @@ async function renderSessionTabs(initialSessionID: string, options?: { state?: s
|
|||
}
|
||||
}
|
||||
|
||||
test("loads persisted tab metadata concurrently on connect", async () => {
|
||||
let release!: () => void
|
||||
const sessionGate = new Promise<void>((resolve) => (release = resolve))
|
||||
const setup = await renderSessionTabs("first", {
|
||||
home: true,
|
||||
persisted: ["first", "second"],
|
||||
sessionGate,
|
||||
})
|
||||
|
||||
try {
|
||||
await wait(() => setup.sessions.length === 2)
|
||||
expect(setup.sessions.toSorted()).toEqual(["first", "second"])
|
||||
release()
|
||||
await wait(() => setup.data.session.get("first") !== undefined && setup.data.session.get("second") !== undefined)
|
||||
} finally {
|
||||
release()
|
||||
setup.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
test("stores session tabs globally by default", async () => {
|
||||
const setup = await renderSessionTabs("first")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue