diff --git a/packages/app/src/pages/home/home-sessions-controller.test.ts b/packages/app/src/pages/home/home-sessions-controller.test.ts new file mode 100644 index 000000000000..589732e4bbe0 --- /dev/null +++ b/packages/app/src/pages/home/home-sessions-controller.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, test } from "bun:test" +import type { SessionInfo } from "@opencode-ai/client/promise" +import type { LocalProject } from "@/context/layout" +import { buildHomeSessionRecords } from "./home-sessions-model" + +const session = (id: string, directory: string, projectID = "project") => + ({ + id, + projectID, + title: id, + time: { created: 1, updated: 1 }, + location: { directory }, + }) as SessionInfo + +const project = (worktree: string, input: Partial = {}) => + ({ worktree, expanded: false, ...input }) as LocalProject + +describe("Home session records", () => { + test("includes sessions from projects that were not added", () => { + const result = buildHomeSessionRecords({ + sessions: () => [session("unadded", "/repo/worktree")], + projectDirectories: () => undefined, + projects: () => [], + projectByID: () => new Map(), + projectMetadataByID: () => new Map([["project", project("/repo", { name: "Project name" })]]), + }) + + expect(result).toHaveLength(1) + expect(result[0]?.project.worktree).toBe("/repo/worktree") + expect(result[0]?.projectName).toBe("Project name") + }) + + test("keeps selected project scoping", () => { + const result = buildHomeSessionRecords({ + sessions: () => [session("selected", "/selected"), session("other", "/other", "other")], + projectDirectories: () => ["/selected"], + projects: () => [project("/selected")], + projectByID: () => new Map(), + projectMetadataByID: () => new Map(), + }) + + expect(result.map((item) => item.session.id)).toEqual(["selected"]) + }) +}) diff --git a/packages/app/src/pages/home/home-sessions-controller.tsx b/packages/app/src/pages/home/home-sessions-controller.tsx index 25ac2157174e..1e41b76e920a 100644 --- a/packages/app/src/pages/home/home-sessions-controller.tsx +++ b/packages/app/src/pages/home/home-sessions-controller.tsx @@ -14,12 +14,13 @@ import type { LocalProject } from "@/context/layout" import { useLanguage } from "@/context/language" import { ServerConnection } from "@/context/servers" import { sessionHasOpenTab, useTabs } from "@/context/tabs" -import { compareSessionTime, displayName, errorMessage, projectForSession } from "@/pages/layout/helpers" +import { errorMessage, projectForSession } from "@/pages/layout/helpers" import { useSessionTabAvatarState } from "@/pages/layout/project-avatar-state" import { pathKey } from "@/utils/path-key" import { showToast } from "@/utils/toast" import { archiveHomeSession } from "../home-session-archive" import type { HomeController } from "./home-controller" +import { buildHomeSessionRecords } from "./home-sessions-model" const HOME_SESSION_LIMIT = 64 export type HomeSessionRecord = { @@ -43,12 +44,20 @@ export function createHomeSessionsController(home: HomeController) { const language = useLanguage() const projectDirectories = createMemo(() => { const project = home.project.selected() - if (!project) return home.project.list().flatMap(directories) + if (!project) return return directories(project) }) const projectByID = createMemo( () => new Map(home.project.list().flatMap((project) => (project.id ? [[project.id, project] as const] : []))), ) + const projectMetadataByID = createMemo( + () => + new Map( + (home.server.focusedSync()?.data.project ?? []).map( + (project) => [project.id, { ...project, expanded: false }] as const, + ), + ), + ) const sessionLoad = useQuery(() => { const ctx = home.server.focusedContext() const conn = home.server.focused() @@ -84,6 +93,7 @@ export function createHomeSessionsController(home: HomeController) { projectDirectories, projects: home.project.list, projectByID, + projectMetadataByID, }), ) const records = createMemo(() => allRecords().slice(0, HOME_SESSION_LIMIT)) @@ -208,30 +218,6 @@ function directories(project: LocalProject) { return [project.worktree, ...(project.sandboxes ?? [])] } -function buildHomeSessionRecords(input: { - sessions: () => SessionInfo[] - projectDirectories: () => string[] - projects: () => LocalProject[] - projectByID: () => Map -}) { - const directories = new Set(input.projectDirectories().map(pathKey)) - const sessions = input.sessions().filter((session) => directories.has(pathKey(session.location.directory))) - return [...new Map(sessions.map((session) => [session.id, session] as const)).values()] - .sort(compareSessionTime) - .flatMap((session) => { - const directory = pathKey(session.location.directory) - const project = - input - .projects() - .find( - (item) => - pathKey(item.worktree) === directory || item.sandboxes?.some((sandbox) => pathKey(sandbox) === directory), - ) ?? projectForSession(session, input.projects(), input.projectByID()) - if (!project) return [] - return { session, project, projectName: displayName(project) } - }) -} - export function homeSessionSearchKey(record: HomeSessionRecord) { return `${pathKey(record.session.location.directory)}:${record.session.id}` } diff --git a/packages/app/src/pages/home/home-sessions-model.ts b/packages/app/src/pages/home/home-sessions-model.ts new file mode 100644 index 000000000000..551a1b46f18d --- /dev/null +++ b/packages/app/src/pages/home/home-sessions-model.ts @@ -0,0 +1,34 @@ +import type { SessionInfo } from "@opencode-ai/client/promise" +import type { LocalProject } from "@/context/layout" +import { compareSessionTime, displayName } from "@/pages/layout/helpers" +import { pathKey } from "@/utils/path-key" + +export function buildHomeSessionRecords(input: { + sessions: () => SessionInfo[] + projectDirectories: () => string[] | undefined + projects: () => LocalProject[] + projectByID: () => Map + projectMetadataByID: () => Map +}) { + const selected = input.projectDirectories() + const directories = selected ? new Set(selected.map(pathKey)) : undefined + const sessions = directories + ? input.sessions().filter((session) => directories.has(pathKey(session.location.directory))) + : input.sessions() + return [...new Map(sessions.map((session) => [session.id, session] as const)).values()] + .sort(compareSessionTime) + .map((session) => { + const directory = pathKey(session.location.directory) + const project = input + .projects() + .find( + (item) => + pathKey(item.worktree) === directory || item.sandboxes?.some((sandbox) => pathKey(sandbox) === directory), + ) ?? { + ...(input.projectByID().get(session.projectID) ?? input.projectMetadataByID().get(session.projectID)), + worktree: session.location.directory, + expanded: false, + } + return { session, project, projectName: displayName(project) } + }) +}