fix(app): persist home project selection (#33935)

This commit is contained in:
Brendan Allan
2026-06-25 17:00:19 +00:00
committed by GitHub
parent e7c59b17a8
commit 10e4b87d25
3 changed files with 31 additions and 22 deletions
+11 -1
View File
@@ -1,4 +1,4 @@
import { createStore, produce } from "solid-js/store" import { createStore, produce, reconcile } from "solid-js/store"
import { batch, createEffect, createMemo, onCleanup, onMount, type Accessor } from "solid-js" import { batch, createEffect, createMemo, onCleanup, onMount, type Accessor } from "solid-js"
import { useLocation } from "@solidjs/router" import { useLocation } from "@solidjs/router"
import { createSimpleContext } from "@opencode-ai/ui/context" import { createSimpleContext } from "@opencode-ai/ui/context"
@@ -73,6 +73,7 @@ type TabHandoff = {
} }
export type LocalProject = Partial<Project> & { worktree: string; expanded: boolean } export type LocalProject = Partial<Project> & { worktree: string; expanded: boolean }
export type HomeProjectSelection = { server: ServerConnection.Key; directory?: string }
export type ReviewDiffStyle = "unified" | "split" export type ReviewDiffStyle = "unified" | "split"
@@ -290,6 +291,9 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
handoff: { handoff: {
tabs: undefined as TabHandoff | undefined, tabs: undefined as TabHandoff | undefined,
}, },
home: {
selection: { server: server.key } as HomeProjectSelection,
},
}), }),
) )
@@ -579,6 +583,12 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
return { return {
route, route,
ready, ready,
home: {
selection: createMemo(() => store.home.selection),
setSelection(selection: HomeProjectSelection) {
setStore("home", "selection", reconcile(selection))
},
},
handoff: { handoff: {
tabs: createMemo(() => store.handoff?.tabs), tabs: createMemo(() => store.handoff?.tabs),
setTabs(dir: string, id: string) { setTabs(dir: string, id: string) {
+19 -19
View File
@@ -1,6 +1,5 @@
import type { Session } from "@opencode-ai/sdk/v2/client" import type { Session } from "@opencode-ai/sdk/v2/client"
import { import {
batch,
createEffect, createEffect,
createMemo, createMemo,
createRoot, createRoot,
@@ -26,7 +25,12 @@ import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2" import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
import { getProjectAvatarVariant, useLayout, type LocalProject } from "@/context/layout" import {
getProjectAvatarVariant,
useLayout,
type HomeProjectSelection,
type LocalProject,
} from "@/context/layout"
import { useNavigate } from "@solidjs/router" import { useNavigate } from "@solidjs/router"
import { base64Encode } from "@opencode-ai/core/util/encode" import { base64Encode } from "@opencode-ai/core/util/encode"
import { Icon } from "@opencode-ai/ui/icon" import { Icon } from "@opencode-ai/ui/icon"
@@ -47,7 +51,6 @@ import {
errorMessage, errorMessage,
getProjectAvatarSource, getProjectAvatarSource,
homeProjectDirectories, homeProjectDirectories,
type HomeProjectSelection,
projectForSession, projectForSession,
sortedRootSessions, sortedRootSessions,
toggleHomeProjectSelection, toggleHomeProjectSelection,
@@ -149,12 +152,12 @@ export function NewHome() {
let focusSessionSearch: (() => void) | undefined let focusSessionSearch: (() => void) | undefined
const [state, setState] = createStore({ const [state, setState] = createStore({
search: "", search: "",
selection: { server: server.key } as HomeProjectSelection,
searchFocused: false, searchFocused: false,
}) })
const selection = layout.home.selection
const focusedServer = createMemo( const focusedServer = createMemo(
() => global.servers.list().find((conn) => ServerConnection.key(conn) === state.selection.server) ?? server.current, () => global.servers.list().find((conn) => ServerConnection.key(conn) === selection().server) ?? server.current,
) )
const focusedServerCtx = createMemo(() => { const focusedServerCtx = createMemo(() => {
const conn = focusedServer() const conn = focusedServer()
@@ -163,7 +166,7 @@ export function NewHome() {
}) })
const focusedSync = () => focusedServerCtx()?.sync ?? sync() const focusedSync = () => focusedServerCtx()?.sync ?? sync()
const projects = createMemo(() => focusedServerCtx()?.projects.list() ?? layout.projects.list()) const projects = createMemo(() => focusedServerCtx()?.projects.list() ?? layout.projects.list())
const selectedProject = createMemo(() => projects().find((project) => project.worktree === state.selection.directory)) const selectedProject = createMemo(() => projects().find((project) => project.worktree === selection().directory))
const newSessionProject = createMemo( const newSessionProject = createMemo(
() => () =>
selectedProject() ?? selectedProject() ??
@@ -191,7 +194,7 @@ export function NewHome() {
return language.t("home.sessions.search.placeholder") return language.t("home.sessions.search.placeholder")
}) })
const sessionLoad = useQuery(() => ({ const sessionLoad = useQuery(() => ({
queryKey: ["home", "sessions", state.selection.server, ...projectDirectories()] as const, queryKey: ["home", "sessions", selection().server, ...projectDirectories()] as const,
queryFn: async () => { queryFn: async () => {
await Promise.all( await Promise.all(
projectDirectories().map((directory) => projectDirectories().map((directory) =>
@@ -257,10 +260,7 @@ export function NewHome() {
}) })
function setSelection(next: HomeProjectSelection) { function setSelection(next: HomeProjectSelection) {
batch(() => { layout.home.setSelection(next)
if (state.selection.server !== next.server) setState("selection", "server", next.server)
if (state.selection.directory !== next.directory) setState("selection", "directory", next.directory)
})
} }
function closeSearch() { function closeSearch() {
@@ -285,7 +285,7 @@ export function NewHome() {
createEffect(() => { createEffect(() => {
const list = global.servers.list() const list = global.servers.list()
if (list.some((conn) => ServerConnection.key(conn) === state.selection.server)) return if (list.some((conn) => ServerConnection.key(conn) === selection().server)) return
const conn = list.find((conn) => ServerConnection.key(conn) === server.key) ?? list[0] const conn = list.find((conn) => ServerConnection.key(conn) === server.key) ?? list[0]
if (conn) setSelection({ server: ServerConnection.key(conn) }) if (conn) setSelection({ server: ServerConnection.key(conn) })
}) })
@@ -310,7 +310,7 @@ export function NewHome() {
.some((project) => project.worktree === directory) .some((project) => project.worktree === directory)
) )
return return
setSelection(toggleHomeProjectSelection(state.selection, key, directory)) setSelection(toggleHomeProjectSelection(selection(), key, directory))
} }
function addProjects(conn: ServerConnection.Any, directories: string[]) { function addProjects(conn: ServerConnection.Any, directories: string[]) {
@@ -418,7 +418,7 @@ export function NewHome() {
<div class="mx-auto grid h-full w-full max-w-[1080px] grid-rows-[auto_minmax(0,1fr)_auto] gap-4 px-3 pb-3 lg:grid-cols-[280px_minmax(0,720px)] lg:grid-rows-1 lg:gap-8 lg:px-6 lg:pb-16"> <div class="mx-auto grid h-full w-full max-w-[1080px] grid-rows-[auto_minmax(0,1fr)_auto] gap-4 px-3 pb-3 lg:grid-cols-[280px_minmax(0,720px)] lg:grid-rows-1 lg:gap-8 lg:px-6 lg:pb-16">
<HomeProjectColumn <HomeProjectColumn
projects={projects()} projects={projects()}
selected={state.selection} selected={selection()}
focusServer={focusServer} focusServer={focusServer}
selectProject={selectProject} selectProject={selectProject}
openNewSession={openProjectNewSession} openNewSession={openProjectNewSession}
@@ -426,7 +426,7 @@ export function NewHome() {
editProject={editProject} editProject={editProject}
closeProject={(conn, directory) => { closeProject={(conn, directory) => {
const next = closeHomeProject( const next = closeHomeProject(
state.selection, selection(),
ServerConnection.key(conn), ServerConnection.key(conn),
global.ensureServerCtx(conn).projects, global.ensureServerCtx(conn).projects,
directory, directory,
@@ -451,8 +451,8 @@ export function NewHome() {
loading={sessionLoad.isLoading} loading={sessionLoad.isLoading}
results={searchResults()} results={searchResults()}
showProjectName={!selectedProject()} showProjectName={!selectedProject()}
server={state.selection.server} server={selection().server}
activeServer={state.selection.server === server.key} activeServer={selection().server === server.key}
noResultsLabel={language.t("home.sessions.search.noResults", { query: search() })} noResultsLabel={language.t("home.sessions.search.noResults", { query: search() })}
bindFocus={(focus) => { bindFocus={(focus) => {
focusSessionSearch = focus focusSessionSearch = focus
@@ -489,8 +489,8 @@ export function NewHome() {
<HomeSessionRow <HomeSessionRow
record={record} record={record}
showProjectName={!selectedProject()} showProjectName={!selectedProject()}
server={state.selection.server} server={selection().server}
activeServer={state.selection.server === server.key} activeServer={selection().server === server.key}
openSession={openSession} openSession={openSession}
archiveSession={archiveSession} archiveSession={archiveSession}
/> />
+1 -2
View File
@@ -2,6 +2,7 @@ import { getFilename } from "@opencode-ai/core/util/path"
import { type Session } from "@opencode-ai/sdk/v2/client" import { type Session } from "@opencode-ai/sdk/v2/client"
import { pathKey } from "@/utils/path-key" import { pathKey } from "@/utils/path-key"
import type { ServerConnection } from "@/context/server" import type { ServerConnection } from "@/context/server"
import type { HomeProjectSelection } from "@/context/layout"
type SessionStore = { type SessionStore = {
session?: Session[] session?: Session[]
@@ -56,8 +57,6 @@ export const childSessionOnPath = (sessions: Session[] | undefined, rootID: stri
export const displayName = (project: { name?: string; worktree: string }) => export const displayName = (project: { name?: string; worktree: string }) =>
project.name || getFilename(project.worktree) || project.worktree project.name || getFilename(project.worktree) || project.worktree
export type HomeProjectSelection = { server: ServerConnection.Key; directory?: string }
export function toggleHomeProjectSelection( export function toggleHomeProjectSelection(
current: HomeProjectSelection | undefined, current: HomeProjectSelection | undefined,
server: ServerConnection.Key, server: ServerConnection.Key,