From a8bc93cd87ce610a4ab4032ca0a701d54ef72555 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Tue, 13 Jan 2026 23:26:14 -0800 Subject: [PATCH 1/2] fix(desktop): add directory filter to session list endpoint, fix load more button logic --- packages/app/src/context/global-sync.tsx | 4 ++ packages/app/src/pages/layout.tsx | 2 +- packages/opencode/src/server/server.ts | 2 + .../opencode/test/server/session-list.test.ts | 39 +++++++++++++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 packages/opencode/test/server/session-list.test.ts diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index c11edd292d1..e48f1c5a6a2 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -38,6 +38,7 @@ type State = { config: Config path: Path session: Session[] + sessionTotal: number session_status: { [sessionID: string]: SessionStatus } @@ -98,6 +99,7 @@ function createGlobalSync() { agent: [], command: [], session: [], + sessionTotal: 0, session_status: {}, session_diff: {}, todo: {}, @@ -126,6 +128,8 @@ function createGlobalSync() { .filter((s) => !s.time?.archived) .slice() .sort((a, b) => a.id.localeCompare(b.id)) + // Store total session count before filtering (used for "load more" functionality) + setStore("sessionTotal", nonArchived.length) // Include up to the limit, plus any updated in the last 4 hours const sessions = nonArchived.filter((s, i) => { if (i < store.limit) return true diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index cffefd5634d..39f397ac466 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -944,7 +944,7 @@ export default function Layout(props: ParentProps) { .toSorted(sortSessions), ) const rootSessions = createMemo(() => sessions().filter((s) => !s.parentID)) - const hasMoreSessions = createMemo(() => store.session.length >= store.limit) + const hasMoreSessions = createMemo(() => store.sessionTotal > store.session.length) const loadMoreSessions = async () => { setProjectStore("limit", (limit) => limit + 5) await globalSync.project.loadSessions(props.project.worktree) diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 52457515b8e..19f3d7159fd 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -724,6 +724,7 @@ export namespace Server { validator( "query", z.object({ + directory: z.string().optional().meta({ description: "Filter sessions by project directory" }), start: z.coerce .number() .optional() @@ -737,6 +738,7 @@ export namespace Server { const term = query.search?.toLowerCase() const sessions: Session.Info[] = [] for await (const session of Session.list()) { + if (query.directory !== undefined && session.directory !== query.directory) continue if (query.start !== undefined && session.time.updated < query.start) continue if (term !== undefined && !session.title.toLowerCase().includes(term)) continue sessions.push(session) diff --git a/packages/opencode/test/server/session-list.test.ts b/packages/opencode/test/server/session-list.test.ts new file mode 100644 index 00000000000..623c16a8114 --- /dev/null +++ b/packages/opencode/test/server/session-list.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, test } from "bun:test" +import path from "path" +import { Instance } from "../../src/project/instance" +import { Server } from "../../src/server/server" +import { Session } from "../../src/session" +import { Log } from "../../src/util/log" + +const projectRoot = path.join(__dirname, "../..") +Log.init({ print: false }) + +describe("session.list", () => { + test("filters by directory", async () => { + await Instance.provide({ + directory: projectRoot, + fn: async () => { + const app = Server.App() + + const first = await Session.create({}) + + const otherDir = path.join(projectRoot, "..", "__session_list_other") + const second = await Instance.provide({ + directory: otherDir, + fn: async () => Session.create({}), + }) + + const response = await app.request(`/session?directory=${encodeURIComponent(projectRoot)}`) + expect(response.status).toBe(200) + + const body = (await response.json()) as unknown[] + const ids = body + .map((s) => (typeof s === "object" && s && "id" in s ? (s as { id: string }).id : undefined)) + .filter((x): x is string => typeof x === "string") + + expect(ids).toContain(first.id) + expect(ids).not.toContain(second.id) + }, + }) + }) +}) From a0b2961a4f4bd0dba1635acd42c88c17808e8115 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Wed, 14 Jan 2026 02:02:09 -0800 Subject: [PATCH 2/2] fix(desktop): fix sidebar session bug by using only root sessions when paging --- packages/app/src/context/global-sync.tsx | 12 +++++++----- packages/opencode/src/server/server.ts | 2 ++ packages/sdk/js/src/v2/gen/sdk.gen.ts | 2 ++ packages/sdk/js/src/v2/gen/types.gen.ts | 7 +++++++ 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index e48f1c5a6a2..ea0b90d5de7 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -119,8 +119,10 @@ function createGlobalSync() { async function loadSessions(directory: string) { const [store, setStore] = child(directory) - globalSDK.client.session - .list({ directory }) + const limit = store.limit + + return globalSDK.client.session + .list({ directory, roots: true }) .then((x) => { const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000 const nonArchived = (x.data ?? []) @@ -128,14 +130,14 @@ function createGlobalSync() { .filter((s) => !s.time?.archived) .slice() .sort((a, b) => a.id.localeCompare(b.id)) - // Store total session count before filtering (used for "load more" functionality) - setStore("sessionTotal", nonArchived.length) // Include up to the limit, plus any updated in the last 4 hours const sessions = nonArchived.filter((s, i) => { - if (i < store.limit) return true + if (i < limit) return true const updated = new Date(s.time?.updated ?? s.time?.created).getTime() return updated > fourHoursAgo }) + // Store total session count (used for "load more" pagination) + setStore("sessionTotal", nonArchived.length) setStore("session", reconcile(sessions, { key: "id" })) }) .catch((err) => { diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 19f3d7159fd..7015c818822 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -725,6 +725,7 @@ export namespace Server { "query", z.object({ directory: z.string().optional().meta({ description: "Filter sessions by project directory" }), + roots: z.coerce.boolean().optional().meta({ description: "Only return root sessions (no parentID)" }), start: z.coerce .number() .optional() @@ -739,6 +740,7 @@ export namespace Server { const sessions: Session.Info[] = [] for await (const session of Session.list()) { if (query.directory !== undefined && session.directory !== query.directory) continue + if (query.roots && session.parentID) continue if (query.start !== undefined && session.time.updated < query.start) continue if (term !== undefined && !session.title.toLowerCase().includes(term)) continue sessions.push(session) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index f83913ea5e1..697dac7eefe 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -781,6 +781,7 @@ export class Session extends HeyApiClient { public list( parameters?: { directory?: string + roots?: boolean start?: number search?: string limit?: number @@ -793,6 +794,7 @@ export class Session extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "roots" }, { in: "query", key: "start" }, { in: "query", key: "search" }, { in: "query", key: "limit" }, diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index acc29d9b43e..2bb3a600274 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -2589,7 +2589,14 @@ export type SessionListData = { body?: never path?: never query?: { + /** + * Filter sessions by project directory + */ directory?: string + /** + * Only return root sessions (no parentID) + */ + roots?: boolean /** * Filter sessions updated on or after this timestamp (milliseconds since epoch) */