From c035c9f82537b0815e5568ad2b386120d2679923 Mon Sep 17 00:00:00 2001 From: HONGJOONG SHIN Date: Sun, 26 Apr 2026 22:49:36 +0900 Subject: [PATCH] fix(app): sync sidebar sessions and title updates across project context --- .../app/src/components/prompt-input/submit.ts | 1 + packages/app/src/context/sync.tsx | 28 +++++++++++-------- packages/app/src/pages/layout.tsx | 26 +++++++++++++++-- packages/app/src/pages/layout/helpers.ts | 10 +++++-- packages/opencode/src/session/prompt.ts | 2 -- packages/opencode/src/session/session.ts | 7 ++--- 6 files changed, 51 insertions(+), 23 deletions(-) diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts index 05f0a3ed2cb3..28a2928abe2f 100644 --- a/packages/app/src/components/prompt-input/submit.ts +++ b/packages/app/src/components/prompt-input/submit.ts @@ -374,6 +374,7 @@ export function createPromptSubmit(input: PromptSubmitInput) { }) if (created) { seed(sessionDirectory, created) + void globalSync.project.loadSessions(sessionDirectory) session = created if (shouldAutoAccept) permission.enableAutoAccept(session.id, sessionDirectory) local.session.promote(sessionDirectory, session.id) diff --git a/packages/app/src/context/sync.tsx b/packages/app/src/context/sync.tsx index 34b597b6bb52..3aa1b5e04400 100644 --- a/packages/app/src/context/sync.tsx +++ b/packages/app/src/context/sync.tsx @@ -472,17 +472,23 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ if (!tracked(directory, sessionID)) return const data = session.data if (!data) return - setStore( - "session", - produce((draft) => { - const match = Binary.search(draft, sessionID, (s) => s.id) - if (match.found) { - draft[match.index] = data - return - } - draft.splice(match.index, 0, data) - }), - ) + const upsertSession = (setter: Setter) => + setter( + "session", + produce((draft) => { + const match = Binary.search(draft, sessionID, (s) => s.id) + if (match.found) { + draft[match.index] = data + return + } + draft.splice(match.index, 0, data) + }), + ) + upsertSession(setStore) + if (data.directory !== directory) { + const [, targetSetStore] = globalSync.child(data.directory) + upsertSession(targetSetStore) + } }) const messagesReq = diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index d9ce87a02e90..9d1f8314cfd8 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -566,7 +566,7 @@ export default function Layout(props: ParentProps) { const direct = projects.find((p) => workspaceKey(p.worktree) === key) if (direct) return direct - const [child] = globalSync.child(directory, { bootstrap: false }) + const [child] = globalSync.child(directory, { bootstrap: true }) const id = child.project if (!id) return @@ -1839,6 +1839,17 @@ export default function Layout(props: ParentProps) { ), ) + createEffect( + on( + () => [route().slug, params.id, currentDir()] as const, + ([slug, _id, dir]) => { + if (!slug || !dir) return + void globalSync.project.loadSessions(dir) + }, + { defer: true }, + ), + ) + function handleDragStart(event: unknown) { const id = getDraggableId(event) if (!id) return @@ -2329,6 +2340,17 @@ export default function Layout(props: ParentProps) { } const projects = () => layout.projects.list() + const panelProject = createMemo(() => { + const current = currentProject() + if (current) return current + const directory = currentDir() + if (directory) { + const root = projectRoot(directory) + const fromRoot = projects().find((project) => workspaceKey(project.worktree) === workspaceKey(root)) + if (fromRoot) return fromRoot + } + return projects()[0] + }) const projectOverlay = () => store.activeProject} /> const sidebarContent = (mobile?: boolean) => ( language.t("sidebar.help")} onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")} renderPanel={() => - mobile ? : + mobile ? : } /> ) diff --git a/packages/app/src/pages/layout/helpers.ts b/packages/app/src/pages/layout/helpers.ts index 4bc5254d959d..7f7594be9eea 100644 --- a/packages/app/src/pages/layout/helpers.ts +++ b/packages/app/src/pages/layout/helpers.ts @@ -3,6 +3,7 @@ import { type Session } from "@opencode-ai/sdk/v2/client" type SessionStore = { session?: Session[] + project?: string path: { directory: string } } @@ -28,11 +29,14 @@ function sortSessions(now: number) { } } -const isRootVisibleSession = (session: Session, directory: string) => - workspaceKey(session.directory) === workspaceKey(directory) && !session.parentID && !session.time?.archived +const isRootVisibleSession = (session: Session, store: SessionStore) => { + if (session.parentID || session.time?.archived) return false + if (store.project && session.projectID && store.project === session.projectID) return true + return workspaceKey(session.directory) === workspaceKey(store.path.directory) +} export const roots = (store: SessionStore) => - (store.session ?? []).filter((session) => isRootVisibleSession(session, store.path.directory)) + (store.session ?? []).filter((session) => isRootVisibleSession(session, store)) export const sortedRootSessions = (store: SessionStore, now: number) => roots(store).sort(sortSessions(now)) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 600eb42f795e..f1882dfcc4e4 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -170,8 +170,6 @@ export const layer = Layer.effect( m.info.role === "user" && !m.parts.every((p) => "synthetic" in p && p.synthetic) const idx = input.history.findIndex(real) if (idx === -1) return - if (input.history.filter(real).length !== 1) return - const context = input.history.slice(0, idx + 1) const firstUser = context[idx] if (!firstUser || firstUser.info.role !== "user") return diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index 6c67b8517e9f..91601bcc1329 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -750,11 +750,8 @@ export function* list(input?: { if (input?.workspaceID) { conditions.push(eq(SessionTable.workspace_id, input.workspaceID)) } - if (!Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) { - if (input?.directory) { - conditions.push(eq(SessionTable.directory, input.directory)) - } - } + // Keep session list stable across cwd changes within the same project. + // Project scoping is already enforced via project_id above. if (input?.roots) { conditions.push(isNull(SessionTable.parent_id)) }