diff options
| author | Adam <[email protected]> | 2026-03-12 11:31:52 -0500 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-03-12 11:31:52 -0500 |
| commit | 0e077f748352df6d44c811829baff3c26b3436ac (patch) | |
| tree | 1239dd7f492a528037c92ef695b31ef90b5c86cc /packages/app/src/pages/layout | |
| parent | 776e7a9c15f3e352c5abf0b0949a5d7b767adfa3 (diff) | |
| download | opencode-0e077f748352df6d44c811829baff3c26b3436ac.tar.gz opencode-0e077f748352df6d44c811829baff3c26b3436ac.zip | |
feat: session load perf (#17186)
Diffstat (limited to 'packages/app/src/pages/layout')
| -rw-r--r-- | packages/app/src/pages/layout/sidebar-items.tsx | 53 | ||||
| -rw-r--r-- | packages/app/src/pages/layout/sidebar-project.tsx | 12 | ||||
| -rw-r--r-- | packages/app/src/pages/layout/sidebar-workspace.tsx | 3 |
3 files changed, 52 insertions, 16 deletions
diff --git a/packages/app/src/pages/layout/sidebar-items.tsx b/packages/app/src/pages/layout/sidebar-items.tsx index 8dc03755e..b6c8fedb1 100644 --- a/packages/app/src/pages/layout/sidebar-items.tsx +++ b/packages/app/src/pages/layout/sidebar-items.tsx @@ -10,6 +10,7 @@ import { base64Encode } from "@opencode-ai/util/encode" import { getFilename } from "@opencode-ai/util/path" import { A, useNavigate, useParams } from "@solidjs/router" import { type Accessor, createMemo, For, type JSX, Match, onCleanup, Show, Switch } from "solid-js" +import { getSessionPrefetch, SESSION_PREFETCH_TTL } from "@/context/global-sync/session-prefetch" import { useGlobalSync } from "@/context/global-sync" import { useLanguage } from "@/context/language" import { getAvatarColors, type LocalProject, useLayout } from "@/context/layout" @@ -67,6 +68,8 @@ export const ProjectIcon = (props: { project: LocalProject; class?: string; noti export type SessionItemProps = { session: Session + list: Session[] + navList?: Accessor<Session[]> slug: string mobile?: boolean dense?: boolean @@ -95,18 +98,18 @@ const SessionRow = (props: { setHoverSession: (id: string | undefined) => void clearHoverProjectSoon: () => void sidebarOpened: Accessor<boolean> - prefetchSession: (session: Session, priority?: "high" | "low") => void - scheduleHoverPrefetch: () => void + warmHover: () => void + warmPress: () => void + warmFocus: () => void cancelHoverPrefetch: () => void }): JSX.Element => ( <A href={`/${props.slug}/session/${props.session.id}`} class={`flex items-center justify-between gap-3 min-w-0 text-left w-full focus:outline-none transition-[padding] ${props.mobile ? "pr-7" : ""} group-hover/session:pr-7 group-focus-within/session:pr-7 group-active/session:pr-7 ${props.dense ? "py-0.5" : "py-1"}`} - onPointerEnter={props.scheduleHoverPrefetch} + onPointerDown={props.warmPress} + onPointerEnter={props.warmHover} onPointerLeave={props.cancelHoverPrefetch} - onMouseEnter={props.scheduleHoverPrefetch} - onMouseLeave={props.cancelHoverPrefetch} - onFocus={() => props.prefetchSession(props.session, "high")} + onFocus={props.warmFocus} onClick={() => { props.setHoverSession(undefined) if (props.sidebarOpened()) return @@ -225,11 +228,37 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => { const hoverMessages = createMemo(() => sessionStore.message[props.session.id]?.filter((message): message is UserMessage => message.role === "user"), ) - const hoverReady = createMemo(() => sessionStore.message[props.session.id] !== undefined) + const hoverReady = createMemo(() => { + if (sessionStore.message[props.session.id] === undefined) return false + if (props.session.id === params.id) return true + const info = getSessionPrefetch(props.session.directory, props.session.id) + if (!info) return false + return Date.now() - info.at < SESSION_PREFETCH_TTL + }) const hoverAllowed = createMemo(() => !props.mobile && props.sidebarExpanded()) const hoverEnabled = createMemo(() => (props.popover ?? true) && hoverAllowed()) const isActive = createMemo(() => props.session.id === params.id) + const warm = (span: number, priority: "high" | "low") => { + const nav = props.navList?.() + const list = nav?.some((item) => item.id === props.session.id && item.directory === props.session.directory) + ? nav + : props.list + + props.prefetchSession(props.session, priority) + + const idx = list.findIndex((item) => item.id === props.session.id && item.directory === props.session.directory) + if (idx === -1) return + + for (let step = 1; step <= span; step++) { + const next = list[idx + step] + if (next) props.prefetchSession(next, step === 1 ? "high" : priority) + + const prev = list[idx - step] + if (prev) props.prefetchSession(prev, step === 1 ? "high" : priority) + } + } + const hoverPrefetch = { current: undefined as ReturnType<typeof setTimeout> | undefined, } @@ -239,11 +268,12 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => { hoverPrefetch.current = undefined } const scheduleHoverPrefetch = () => { + warm(1, "high") if (hoverPrefetch.current !== undefined) return hoverPrefetch.current = setTimeout(() => { hoverPrefetch.current = undefined - props.prefetchSession(props.session) - }, 200) + warm(2, "low") + }, 80) } onCleanup(cancelHoverPrefetch) @@ -267,8 +297,9 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => { setHoverSession={props.setHoverSession} clearHoverProjectSoon={props.clearHoverProjectSoon} sidebarOpened={layout.sidebar.opened} - prefetchSession={props.prefetchSession} - scheduleHoverPrefetch={scheduleHoverPrefetch} + warmHover={scheduleHoverPrefetch} + warmPress={() => warm(2, "high")} + warmFocus={() => warm(2, "high")} cancelHoverPrefetch={cancelHoverPrefetch} /> ) diff --git a/packages/app/src/pages/layout/sidebar-project.tsx b/packages/app/src/pages/layout/sidebar-project.tsx index 551090fd5..a26bc1831 100644 --- a/packages/app/src/pages/layout/sidebar-project.tsx +++ b/packages/app/src/pages/layout/sidebar-project.tsx @@ -30,7 +30,7 @@ export type ProjectSidebarContext = { workspacesEnabled: (project: LocalProject) => boolean workspaceIds: (project: LocalProject) => string[] workspaceLabel: (directory: string, branch?: string, projectId?: string) => string - sessionProps: Omit<SessionItemProps, "session" | "slug" | "children" | "mobile" | "dense" | "popover"> + sessionProps: Omit<SessionItemProps, "session" | "list" | "slug" | "children" | "mobile" | "dense" | "popover"> setHoverSession: (id: string | undefined) => void } @@ -204,11 +204,12 @@ const ProjectPreviewPanel = (props: { <Show when={props.workspaceEnabled()} fallback={ - <For each={props.projectSessions()}> + <For each={props.projectSessions().slice(0, 2)}> {(session) => ( <SessionItem {...props.ctx.sessionProps} session={session} + list={props.projectSessions()} slug={base64Encode(props.project.worktree)} dense mobile={props.mobile} @@ -231,11 +232,12 @@ const ProjectPreviewPanel = (props: { </div> <span class="truncate text-14-medium text-text-base">{props.label(directory)}</span> </div> - <For each={sessions()}> + <For each={sessions().slice(0, 2)}> {(session) => ( <SessionItem {...props.ctx.sessionProps} session={session} + list={sessions()} slug={base64Encode(directory)} dense mobile={props.mobile} @@ -317,11 +319,11 @@ export const SortableProject = (props: { } const projectStore = createMemo(() => globalSync.child(props.project.worktree, { bootstrap: false })[0]) - const projectSessions = createMemo(() => sortedRootSessions(projectStore(), props.sortNow()).slice(0, 2)) + const projectSessions = createMemo(() => sortedRootSessions(projectStore(), props.sortNow())) const projectChildren = createMemo(() => childMapByParent(projectStore().session)) const workspaceSessions = (directory: string) => { const [data] = globalSync.child(directory, { bootstrap: false }) - return sortedRootSessions(data, props.sortNow()).slice(0, 2) + return sortedRootSessions(data, props.sortNow()) } const workspaceChildren = (directory: string) => { const [data] = globalSync.child(directory, { bootstrap: false }) diff --git a/packages/app/src/pages/layout/sidebar-workspace.tsx b/packages/app/src/pages/layout/sidebar-workspace.tsx index 5eb5e71cd..48c63e547 100644 --- a/packages/app/src/pages/layout/sidebar-workspace.tsx +++ b/packages/app/src/pages/layout/sidebar-workspace.tsx @@ -32,6 +32,7 @@ type InlineEditorComponent = (props: { export type WorkspaceSidebarContext = { currentDir: Accessor<string> + navList: Accessor<Session[]> sidebarExpanded: Accessor<boolean> sidebarHovering: Accessor<boolean> nav: Accessor<HTMLElement | undefined> @@ -265,6 +266,8 @@ const WorkspaceSessionList = (props: { {(session) => ( <SessionItem session={session} + list={props.sessions()} + navList={props.ctx.navList} slug={props.slug()} mobile={props.mobile} popover={props.popover} |
