Skip to content

Commit 739ac31

Browse files
committed
refactor(app): keep the session index move self-contained
1 parent 516aa29 commit 739ac31

8 files changed

Lines changed: 20 additions & 44 deletions

File tree

‎packages/app/src/home/sessions/controller.tsx‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ export function createHomeSessionsController(home: HomeController) {
185185
}
186186

187187
// Through the shared query, so a fetch the sidebar already started is joined and the result lands in its cache.
188-
void queryClient.fetchQuery({ ...sessionIndexQuery(conn, ctx, queryClient), staleTime: 0 }).then(
188+
void queryClient.fetchQuery({ ...sessionIndexQuery(conn, ctx), staleTime: 0 }).then(
189189
() => dispatchLoad({ type: "fetch.succeeded", attempt, reducedMotion: prefersReducedMotion() }),
190190
(cause) =>
191191
dispatchLoad({
@@ -278,7 +278,9 @@ export function createHomeSessionsController(home: HomeController) {
278278
if (!ctx || !conn) return []
279279

280280
return retainSessions(
281-
ctx.data.session.apply(mergeSessionIndex([...fetchedSessions(), ...scopedSessions()], ctx.data.session.list())),
281+
ctx.data.session.apply(
282+
mergeSessionIndex([...fetchedSessions(), ...scopedSessions()], ctx.data.session.list()),
283+
),
282284
SESSION_LIST_LIMIT,
283285
Date.now(),
284286
)

‎packages/app/src/runtime/server/data.test.ts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ test("keeps a removed session hidden after its deletion event, for lists cached
1717

1818
const request = mutation.remove(session.id)
1919
expect(mutation.apply([session])).toEqual([])
20-
expect(mutation.apply([{ id: session.id }, { id: "ses_kept" }])).toEqual([{ id: "ses_kept" }])
2120
release.resolve()
2221
await request
2322
expect(mutation.apply([session])).toEqual([])
@@ -45,7 +44,6 @@ test("rolls back a failed removal", async () => {
4544
release.resolve()
4645
await expect(request).rejects.toThrow("offline")
4746
expect(mutation.apply([session])).toEqual([session])
48-
expect(mutation.apply([{ id: session.id }])).toEqual([{ id: session.id }])
4947
})
5048

5149
test("treats SessionNotFoundError on removal as already deleted", async () => {

‎packages/app/src/runtime/server/data.ts‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ export function createDesktopData(input: { data: Data; remove: (sessionID: strin
6161
...input.data.session,
6262
list: () => apply(input.data.session.list()),
6363
apply,
64-
// Lightweight projections use the same pending deletions as full session rows.
65-
filterRemoved: mutation.apply,
6664
remove: (sessionID: string) =>
6765
mutation.remove(sessionID).then(() => {
6866
setActivity(sessionID, undefined)
@@ -83,7 +81,7 @@ export function createSessionMutations(remove: (sessionID: string) => Promise<vo
8381
[...current.filter((mutation) => mutation.sessionID !== next.sessionID), next].slice(-REMOVED_SESSION_LIMIT)
8482

8583
return {
86-
apply<T extends { id: string }>(sessions: readonly T[]) {
84+
apply(sessions: readonly SessionInfo[]) {
8785
const removed = new Set(
8886
store.session.flatMap((mutation) => (mutation.type === "remove" ? [mutation.sessionID] : [])),
8987
)

‎packages/app/src/runtime/server/session-index.test.ts‎

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const history = (directory: string, count: number) =>
3939

4040
const ids = (sessions: SessionInfo[]) => sessions.map((item) => item.id)
4141

42-
describe("shared session index", () => {
42+
describe("Home session index", () => {
4343
test("loads one bounded page of the newest sessions", async () => {
4444
const all = history("/repo", SESSION_INDEX_PAGE_LIMIT + 1)
4545
const calls: Array<{ limit: number; order: "desc"; cursor?: string; parentID: null }> = []
@@ -52,8 +52,7 @@ describe("shared session index", () => {
5252

5353
expect(calls).toEqual([{ limit: SESSION_INDEX_PAGE_LIMIT, order: "desc", parentID: null }])
5454
// Newest rows in server order, plus the recent window beyond the limit.
55-
expect(ids(result.sessions)).toEqual(ids(all.slice(0, SESSION_INDEX_LIMIT + SESSION_RECENT_LIMIT)))
56-
expect(result.inventory.map((session) => session.id)).toEqual(ids(all.slice(0, SESSION_INDEX_PAGE_LIMIT)))
55+
expect(ids(result)).toEqual(ids(all.slice(0, SESSION_INDEX_LIMIT + SESSION_RECENT_LIMIT)))
5756
})
5857

5958
test("keeps each directory's newest sessions within the page", async () => {
@@ -69,8 +68,8 @@ describe("shared session index", () => {
6968
cursor: { next: "next" },
7069
}))
7170

72-
expect(ids(result.sessions.filter((item) => item.location.directory === "/quiet"))).toEqual(ids(quiet))
73-
expect(ids(result.sessions.filter((item) => item.location.directory === "/busy"))).toEqual(
71+
expect(ids(result.filter((item) => item.location.directory === "/quiet"))).toEqual(ids(quiet))
72+
expect(ids(result.filter((item) => item.location.directory === "/busy"))).toEqual(
7473
ids(busy.slice(0, SESSION_INDEX_LIMIT + SESSION_RECENT_LIMIT)),
7574
)
7675
})
@@ -85,8 +84,7 @@ describe("shared session index", () => {
8584
}))
8685

8786
const result = await loadSessionIndex(async () => ({ data: [...archived, ...live], cursor: {} }))
88-
expect(ids(result.sessions)).toEqual(ids(live))
89-
expect(result.inventory).toEqual(live.map(({ id, projectID, location }) => ({ id, projectID, location })))
87+
expect(ids(result)).toEqual(ids(live))
9088
})
9189

9290
test("keeps only visible roots", () => {
@@ -115,7 +113,7 @@ describe("Home session index parity with the complete index", () => {
115113
),
116114
).toSorted()
117115

118-
const loaded = async () => (await loadSessionIndex(async () => ({ data: complete, cursor: {} }))).sessions
116+
const loaded = () => loadSessionIndex(async () => ({ data: complete, cursor: {} }))
119117

120118
test("without local changes", async () => {
121119
const result = view(await loaded(), [])
@@ -153,7 +151,7 @@ describe("Home session index parity with the complete index", () => {
153151
}))
154152

155153
expect(hot.every((item) => item.time.updated > now - SESSION_RECENT_WINDOW)).toBe(true)
156-
const index = (await loadSessionIndex(async () => ({ data: hot, cursor: {} }))).sessions
154+
const index = await loadSessionIndex(async () => ({ data: hot, cursor: {} }))
157155
expect(index).toHaveLength(SESSION_INDEX_LIMIT + SESSION_RECENT_LIMIT)
158156
expect(view(index, [])).toEqual(view(hot, []))
159157
})

‎packages/app/src/runtime/server/session-index.ts‎

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,9 @@ export async function loadSessionIndex(
2929
) {
3030
const response = await list({ limit: SESSION_INDEX_PAGE_LIMIT, order: "desc", parentID: null }, { signal })
3131

32-
const sessions = parseSessionIndex(response.data)
33-
return {
34-
sessions: retainSessions(sessions, SESSION_INDEX_LIMIT, Date.now()),
35-
inventory: sessions.map((session) => ({
36-
id: session.id,
37-
projectID: session.projectID,
38-
location: session.location,
39-
})),
40-
}
32+
return retainSessions(parseSessionIndex(response.data), SESSION_INDEX_LIMIT, Date.now())
4133
}
4234

43-
export type SessionInventory = Awaited<ReturnType<typeof loadSessionIndex>>["inventory"]
44-
4535
// Keep this filter for locally known sessions merged into the fetched index.
4636
export function parseSessionIndex(sessions: SessionInfo[]) {
4737
return sessions.filter((session) => !session.parentID && session.time.archived === undefined)

‎packages/app/src/runtime/server/session-queries.ts‎

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,17 @@
11
import type { SessionInfo } from "@opencode/client/promise"
2-
import { queryOptions, skipToken, type QueryClient } from "@tanstack/solid-query"
2+
import { queryOptions, skipToken } from "@tanstack/solid-query"
33
import type { ServerConnection } from "@/runtime/server/registry"
44
import type { ServerCtx } from "@/runtime/server/runtime"
55
import { loadSessionIndex } from "./session-index"
66

77
// Keep the immutable result opaque so Solid Query does not recursively unwrap every session on mount.
88
const selectSessions = (sessions: SessionInfo[]) => () => sessions
99

10-
export function sessionIndexQuery(
11-
conn: ServerConnection.Any | undefined,
12-
ctx: ServerCtx | undefined,
13-
queryClient: QueryClient,
14-
) {
10+
export function sessionIndexQuery(conn: ServerConnection.Any | undefined, ctx: ServerCtx | undefined) {
1511
return queryOptions({
1612
queryKey: ["home-sessions", conn] as const,
1713
queryFn: ctx
18-
? async ({ signal }) => {
19-
const result = await loadSessionIndex((input, options) => ctx.sdk.api.session.list(input, options), signal)
20-
queryClient.setQueryData(["home-session-inventory", conn], result.inventory)
21-
return result.sessions
22-
}
14+
? ({ signal }) => loadSessionIndex((input, options) => ctx.sdk.api.session.list(input, options), signal)
2315
: skipToken,
2416
retry: false,
2517
staleTime: 30_000,

‎packages/app/src/shell/titlebar/recently-closed-tabs-menu.tsx‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createMemo, For, Show, type JSX } from "solid-js"
22
import { createStore } from "solid-js/store"
33
import { createResizeObserver } from "@solid-primitives/resize-observer"
4-
import { useQueries, useQueryClient } from "@tanstack/solid-query"
4+
import { useQueries } from "@tanstack/solid-query"
55
import { Icon } from "@opencode/ui/icon"
66
import { IconButton } from "@opencode/ui/icon-button"
77
import { Keybind } from "@opencode/ui/keybind"
@@ -30,7 +30,6 @@ export function RecentlyClosedTabsMenu(props: {
3030
const language = useLanguage()
3131
const tabs = useTabs()
3232
const global = useGlobal()
33-
const queryClient = useQueryClient()
3433
const [state, setState] = createStore({ query: "", open: false })
3534
let search: HTMLInputElement | undefined
3635

@@ -57,7 +56,7 @@ export function RecentlyClosedTabsMenu(props: {
5756
const ctx = global.serverCtx(ServerConnection.key(conn))
5857

5958
return {
60-
...sessionIndexQuery(conn, ctx, queryClient),
59+
...sessionIndexQuery(conn, ctx),
6160
enabled: state.open && recent().length < 10 && ctx?.sdk.connection.status() === "connected",
6261
}
6362
}),

‎packages/app/src/shell/titlebar/sidebar-organized.tsx‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Schema } from "effect"
22
import { createEffect, createMemo, Show, onCleanup, untrack, type Accessor, type JSX } from "solid-js"
33
import { Key } from "@solid-primitives/keyed"
44
import { createStore } from "solid-js/store"
5-
import { useQuery, useQueryClient } from "@tanstack/solid-query"
5+
import { useQuery } from "@tanstack/solid-query"
66
import { DragDropProvider, PointerSensor } from "@dnd-kit/solid"
77
import { isSortable, useSortable } from "@dnd-kit/solid/sortable"
88
import { Accessibility, Feedback, PointerActivationConstraints } from "@dnd-kit/dom"
@@ -228,9 +228,8 @@ function ProjectList(props: {
228228
untrack(() => props.onReorder(key, ids))
229229
})
230230

231-
const queryClient = useQueryClient()
232231
const sessions = useQuery(() => ({
233-
...sessionIndexQuery(connection(), ctx(), queryClient),
232+
...sessionIndexQuery(connection(), ctx()),
234233
enabled: ctx()?.sdk.connection.status() === "connected",
235234
}))
236235

0 commit comments

Comments
 (0)