From 8595dae1a47bff819bb507d77d0dddf14c335757 Mon Sep 17 00:00:00 2001
From: Adam <2363879+adamdotdevin@users.noreply.github.com>
Date: Tue, 20 Jan 2026 10:00:59 -0600
Subject: [PATCH] fix(app): session loading loop
---
.../src/components/session/session-header.tsx | 30 ++-
packages/app/src/context/global-sync.tsx | 218 ++++++++++--------
packages/app/src/pages/layout.tsx | 24 +-
3 files changed, 166 insertions(+), 106 deletions(-)
diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx
index 7cded4bce..7214ac652 100644
--- a/packages/app/src/components/session/session-header.tsx
+++ b/packages/app/src/components/session/session-header.tsx
@@ -45,6 +45,8 @@ export function SessionHeader() {
const currentSession = createMemo(() => sync.data.session.find((s) => s.id === params.id))
const shareEnabled = createMemo(() => sync.data.config.share !== "disabled")
+ const showReview = createMemo(() => !!currentSession()?.summary?.files)
+ const showShare = createMemo(() => shareEnabled() && !!currentSession())
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const view = createMemo(() => layout.view(sessionKey()))
@@ -172,12 +174,14 @@ export function SessionHeader() {
{/* */}
{/* */}
-
-
)}
diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx
index 69a2b8ce2..6a2102640 100644
--- a/packages/app/src/context/global-sync.tsx
+++ b/packages/app/src/context/global-sync.tsx
@@ -88,6 +88,10 @@ type VcsCache = {
ready: Accessor
}
+type ChildOptions = {
+ bootstrap?: boolean
+}
+
function createGlobalSync() {
const globalSDK = useGlobalSDK()
const platform = usePlatform()
@@ -127,8 +131,10 @@ function createGlobalSync() {
})
const children: Record, SetStoreFunction]> = {}
+ const booting = new Map>()
+ const sessionLoads = new Map>()
- function child(directory: string) {
+ function ensureChild(directory: string) {
if (!directory) console.error("No directory provided")
if (!children[directory]) {
const cache = runWithOwner(owner, () =>
@@ -163,7 +169,6 @@ function createGlobalSync() {
message: {},
part: {},
})
- bootstrapInstance(directory)
}
runWithOwner(owner, init)
@@ -173,11 +178,23 @@ function createGlobalSync() {
return childStore
}
+ function child(directory: string, options: ChildOptions = {}) {
+ const childStore = ensureChild(directory)
+ const shouldBootstrap = options.bootstrap ?? true
+ if (shouldBootstrap && childStore[0].status === "loading") {
+ void bootstrapInstance(directory)
+ }
+ return childStore
+ }
+
async function loadSessions(directory: string) {
- const [store, setStore] = child(directory)
+ const pending = sessionLoads.get(directory)
+ if (pending) return pending
+
+ const [store, setStore] = child(directory, { bootstrap: false })
const limit = store.limit
- return globalSDK.client.session
+ const promise = globalSDK.client.session
.list({ directory, roots: true })
.then((x) => {
const nonArchived = (x.data ?? [])
@@ -208,13 +225,23 @@ function createGlobalSync() {
const project = getFilename(directory)
showToast({ title: `Failed to load sessions for ${project}`, description: err.message })
})
+
+ sessionLoads.set(directory, promise)
+ promise.finally(() => {
+ sessionLoads.delete(directory)
+ })
+ return promise
}
async function bootstrapInstance(directory: string) {
if (!directory) return
- const [store, setStore] = child(directory)
- const cache = vcsCache.get(directory)
- if (!cache) return
+ const pending = booting.get(directory)
+ if (pending) return pending
+
+ const promise = (async () => {
+ const [store, setStore] = ensureChild(directory)
+ const cache = vcsCache.get(directory)
+ if (!cache) return
const sdk = createOpencodeClient({
baseUrl: globalSDK.url,
fetch: platform.fetch,
@@ -250,98 +277,105 @@ function createGlobalSync() {
config: () => sdk.config.get().then((x) => setStore("config", x.data!)),
}
- try {
- await Promise.all(Object.values(blockingRequests).map((p) => retry(p)))
- } catch (err) {
- console.error("Failed to bootstrap instance", err)
- const project = getFilename(directory)
- const message = err instanceof Error ? err.message : String(err)
- showToast({ title: `Failed to reload ${project}`, description: message })
- setStore("status", "partial")
- return
- }
+ try {
+ await Promise.all(Object.values(blockingRequests).map((p) => retry(p)))
+ } catch (err) {
+ console.error("Failed to bootstrap instance", err)
+ const project = getFilename(directory)
+ const message = err instanceof Error ? err.message : String(err)
+ showToast({ title: `Failed to reload ${project}`, description: message })
+ setStore("status", "partial")
+ return
+ }
- if (store.status !== "complete") setStore("status", "partial")
+ if (store.status !== "complete") setStore("status", "partial")
- Promise.all([
- sdk.path.get().then((x) => setStore("path", x.data!)),
- sdk.command.list().then((x) => setStore("command", x.data ?? [])),
- sdk.session.status().then((x) => setStore("session_status", x.data!)),
- loadSessions(directory),
- sdk.mcp.status().then((x) => setStore("mcp", x.data!)),
- sdk.lsp.status().then((x) => setStore("lsp", x.data!)),
- sdk.vcs.get().then((x) => {
- const next = x.data ?? store.vcs
- setStore("vcs", next)
- if (next?.branch) cache.setStore("value", next)
- }),
- sdk.permission.list().then((x) => {
- const grouped: Record = {}
- for (const perm of x.data ?? []) {
- if (!perm?.id || !perm.sessionID) continue
- const existing = grouped[perm.sessionID]
- if (existing) {
- existing.push(perm)
- continue
+ Promise.all([
+ sdk.path.get().then((x) => setStore("path", x.data!)),
+ sdk.command.list().then((x) => setStore("command", x.data ?? [])),
+ sdk.session.status().then((x) => setStore("session_status", x.data!)),
+ loadSessions(directory),
+ sdk.mcp.status().then((x) => setStore("mcp", x.data!)),
+ sdk.lsp.status().then((x) => setStore("lsp", x.data!)),
+ sdk.vcs.get().then((x) => {
+ const next = x.data ?? store.vcs
+ setStore("vcs", next)
+ if (next?.branch) cache.setStore("value", next)
+ }),
+ sdk.permission.list().then((x) => {
+ const grouped: Record = {}
+ for (const perm of x.data ?? []) {
+ if (!perm?.id || !perm.sessionID) continue
+ const existing = grouped[perm.sessionID]
+ if (existing) {
+ existing.push(perm)
+ continue
+ }
+ grouped[perm.sessionID] = [perm]
}
- grouped[perm.sessionID] = [perm]
- }
- batch(() => {
- for (const sessionID of Object.keys(store.permission)) {
- if (grouped[sessionID]) continue
- setStore("permission", sessionID, [])
+ batch(() => {
+ for (const sessionID of Object.keys(store.permission)) {
+ if (grouped[sessionID]) continue
+ setStore("permission", sessionID, [])
+ }
+ for (const [sessionID, permissions] of Object.entries(grouped)) {
+ setStore(
+ "permission",
+ sessionID,
+ reconcile(
+ permissions
+ .filter((p) => !!p?.id)
+ .slice()
+ .sort((a, b) => a.id.localeCompare(b.id)),
+ { key: "id" },
+ ),
+ )
+ }
+ })
+ }),
+ sdk.question.list().then((x) => {
+ const grouped: Record = {}
+ for (const question of x.data ?? []) {
+ if (!question?.id || !question.sessionID) continue
+ const existing = grouped[question.sessionID]
+ if (existing) {
+ existing.push(question)
+ continue
+ }
+ grouped[question.sessionID] = [question]
}
- for (const [sessionID, permissions] of Object.entries(grouped)) {
- setStore(
- "permission",
- sessionID,
- reconcile(
- permissions
- .filter((p) => !!p?.id)
- .slice()
- .sort((a, b) => a.id.localeCompare(b.id)),
- { key: "id" },
- ),
- )
- }
- })
- }),
- sdk.question.list().then((x) => {
- const grouped: Record = {}
- for (const question of x.data ?? []) {
- if (!question?.id || !question.sessionID) continue
- const existing = grouped[question.sessionID]
- if (existing) {
- existing.push(question)
- continue
- }
- grouped[question.sessionID] = [question]
- }
- batch(() => {
- for (const sessionID of Object.keys(store.question)) {
- if (grouped[sessionID]) continue
- setStore("question", sessionID, [])
- }
- for (const [sessionID, questions] of Object.entries(grouped)) {
- setStore(
- "question",
- sessionID,
- reconcile(
- questions
- .filter((q) => !!q?.id)
- .slice()
- .sort((a, b) => a.id.localeCompare(b.id)),
- { key: "id" },
- ),
- )
- }
- })
- }),
- ]).then(() => {
- setStore("status", "complete")
+ batch(() => {
+ for (const sessionID of Object.keys(store.question)) {
+ if (grouped[sessionID]) continue
+ setStore("question", sessionID, [])
+ }
+ for (const [sessionID, questions] of Object.entries(grouped)) {
+ setStore(
+ "question",
+ sessionID,
+ reconcile(
+ questions
+ .filter((q) => !!q?.id)
+ .slice()
+ .sort((a, b) => a.id.localeCompare(b.id)),
+ { key: "id" },
+ ),
+ )
+ }
+ })
+ }),
+ ]).then(() => {
+ setStore("status", "complete")
+ })
+ })()
+
+ booting.set(directory, promise)
+ promise.finally(() => {
+ booting.delete(directory)
})
+ return promise
}
const unsub = globalSDK.event.listen((e) => {
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx
index 8ea21bd34..64fc52739 100644
--- a/packages/app/src/pages/layout.tsx
+++ b/packages/app/src/pages/layout.tsx
@@ -563,9 +563,13 @@ export default function Layout(props: ParentProps) {
if (!project) return [] as Session[]
if (workspaceSetting()) {
const dirs = workspaceIds(project)
+ const activeDir = params.dir ? base64Decode(params.dir) : ""
const result: Session[] = []
for (const dir of dirs) {
- const [dirStore] = globalSync.child(dir)
+ const expanded = store.workspaceExpanded[dir] ?? dir === project.worktree
+ const active = dir === activeDir
+ if (!expanded && !active) continue
+ const [dirStore] = globalSync.child(dir, { bootstrap: true })
const dirSessions = dirStore.session
.filter((session) => session.directory === dirStore.path.directory)
.filter((session) => !session.parentID && !session.time?.archived)
@@ -1238,8 +1242,12 @@ export default function Layout(props: ParentProps) {
if (!project) return
if (workspaceSetting()) {
+ const activeDir = params.dir ? base64Decode(params.dir) : ""
const dirs = [project.worktree, ...(project.sandboxes ?? [])]
for (const directory of dirs) {
+ const expanded = store.workspaceExpanded[directory] ?? directory === project.worktree
+ const active = directory === activeDir
+ if (!expanded && !active) continue
globalSync.project.loadSessions(directory)
}
return
@@ -1558,7 +1566,7 @@ export default function Layout(props: ParentProps) {
const SortableWorkspace = (props: { directory: string; project: LocalProject; mobile?: boolean }): JSX.Element => {
const sortable = createSortable(props.directory)
- const [workspaceStore, setWorkspaceStore] = globalSync.child(props.directory)
+ const [workspaceStore, setWorkspaceStore] = globalSync.child(props.directory, { bootstrap: false })
const [menuOpen, setMenuOpen] = createSignal(false)
const [pendingRename, setPendingRename] = createSignal(false)
const slug = createMemo(() => base64Encode(props.directory))
@@ -1569,12 +1577,17 @@ export default function Layout(props: ParentProps) {
.toSorted(sortSessions),
)
const local = createMemo(() => props.directory === props.project.worktree)
+ const active = createMemo(() => {
+ const current = params.dir ? base64Decode(params.dir) : ""
+ return current === props.directory
+ })
const workspaceValue = createMemo(() => {
const branch = workspaceStore.vcs?.branch
const name = branch ?? getFilename(props.directory)
return workspaceName(props.directory, props.project.id, branch) ?? name
})
- const open = createMemo(() => store.workspaceExpanded[props.directory] ?? true)
+ const open = createMemo(() => store.workspaceExpanded[props.directory] ?? local())
+ const boot = createMemo(() => open() || active())
const loading = createMemo(() => open() && workspaceStore.status !== "complete" && sessions().length === 0)
const hasMore = createMemo(() => local() && workspaceStore.sessionTotal > workspaceStore.session.length)
const loadMore = async () => {
@@ -1591,6 +1604,11 @@ export default function Layout(props: ParentProps) {
if (editorOpen(`workspace:${props.directory}`)) closeEditor()
}
+ createEffect(() => {
+ if (!boot()) return
+ globalSync.child(props.directory, { bootstrap: true })
+ })
+
const header = () => (