diff options
| author | Luke Parker <[email protected]> | 2026-03-20 14:12:06 +1000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-03-20 00:12:06 -0400 |
| commit | d460614cd7ad9e047a2792139ea67e16caa82ea7 (patch) | |
| tree | ff415e8719c7b6edd73bc824da379308e4e62589 /packages/app/src/pages | |
| parent | 7866dbcfcc36a60d22ad466eddf54c54b21fabe3 (diff) | |
| download | opencode-d460614cd7ad9e047a2792139ea67e16caa82ea7.tar.gz opencode-d460614cd7ad9e047a2792139ea67e16caa82ea7.zip | |
fix: lots of desktop stability, better e2e error logging (#18300)
Diffstat (limited to 'packages/app/src/pages')
| -rw-r--r-- | packages/app/src/pages/error.tsx | 10 | ||||
| -rw-r--r-- | packages/app/src/pages/layout.tsx | 47 | ||||
| -rw-r--r-- | packages/app/src/pages/layout/helpers.ts | 4 | ||||
| -rw-r--r-- | packages/app/src/pages/layout/sidebar-project.tsx | 7 | ||||
| -rw-r--r-- | packages/app/src/pages/layout/sidebar-workspace.tsx | 4 |
5 files changed, 42 insertions, 30 deletions
diff --git a/packages/app/src/pages/error.tsx b/packages/app/src/pages/error.tsx index 11284b3d2..1cdc06116 100644 --- a/packages/app/src/pages/error.tsx +++ b/packages/app/src/pages/error.tsx @@ -1,11 +1,12 @@ import { TextField } from "@opencode-ai/ui/text-field" import { Logo } from "@opencode-ai/ui/logo" import { Button } from "@opencode-ai/ui/button" -import { Component, Show } from "solid-js" +import { Component, Show, onMount } from "solid-js" import { createStore } from "solid-js/store" import { usePlatform } from "@/context/platform" import { useLanguage } from "@/context/language" import { Icon } from "@opencode-ai/ui/icon" +import type { E2EWindow } from "@/testing/terminal" export type InitError = { name: string @@ -226,6 +227,13 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => { actionError: undefined as string | undefined, }) + onMount(() => { + const win = window as E2EWindow + if (!win.__opencode_e2e) return + const detail = formatError(props.error, language.t) + console.error(`[e2e:error-boundary] ${window.location.pathname}\n${detail}`) + }) + async function checkForUpdates() { if (!platform.checkUpdate) return setStore("checking", true) diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 52ac7c5f3..8e2248469 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -129,6 +129,16 @@ export default function Layout(props: ParentProps) { const theme = useTheme() const language = useLanguage() const initialDirectory = decode64(params.dir) + const route = createMemo(() => { + const slug = params.dir + if (!slug) return { slug, dir: "" } + const dir = decode64(slug) + if (!dir) return { slug, dir: "" } + return { + slug, + dir: globalSync.peek(dir, { bootstrap: false })[0].path.directory || dir, + } + }) const availableThemeEntries = createMemo(() => Object.entries(theme.themes())) const colorSchemeOrder: ColorScheme[] = ["system", "light", "dark"] const colorSchemeKey: Record<ColorScheme, "theme.scheme.system" | "theme.scheme.light" | "theme.scheme.dark"> = { @@ -137,7 +147,7 @@ export default function Layout(props: ParentProps) { dark: "theme.scheme.dark", } const colorSchemeLabel = (scheme: ColorScheme) => language.t(colorSchemeKey[scheme]) - const currentDir = createMemo(() => decode64(params.dir) ?? "") + const currentDir = createMemo(() => route().dir) const [state, setState] = createStore({ autoselect: !initialDirectory, @@ -484,8 +494,8 @@ export default function Layout(props: ParentProps) { } const currentSession = params.id - if (directory === currentDir() && props.sessionID === currentSession) return - if (directory === currentDir() && session?.parentID === currentSession) return + if (workspaceKey(directory) === workspaceKey(currentDir()) && props.sessionID === currentSession) return + if (workspaceKey(directory) === workspaceKey(currentDir()) && session?.parentID === currentSession) return dismissSessionAlert(sessionKey) @@ -620,7 +630,7 @@ export default function Layout(props: ParentProps) { const activeDir = currentDir() return workspaceIds(project).filter((directory) => { const expanded = store.workspaceExpanded[directory] ?? directory === project.worktree - const active = directory === activeDir + const active = workspaceKey(directory) === workspaceKey(activeDir) return expanded || active }) }) @@ -687,7 +697,7 @@ export default function Layout(props: ParentProps) { seen: lru, keep: sessionID, limit: PREFETCH_MAX_SESSIONS_PER_DIR, - preserve: directory === params.dir && params.id ? [params.id] : undefined, + preserve: params.id && workspaceKey(directory) === workspaceKey(currentDir()) ? [params.id] : undefined, }) } @@ -700,7 +710,7 @@ export default function Layout(props: ParentProps) { }) createEffect(() => { - params.dir + route() globalSDK.url prefetchToken.value += 1 @@ -1692,13 +1702,10 @@ export default function Layout(props: ParentProps) { createEffect( on( () => { - const dir = params.dir - const directory = dir ? decode64(dir) : undefined - const resolved = directory ? globalSync.child(directory, { bootstrap: false })[0].path.directory : "" - return [pageReady(), dir, params.id, currentProject()?.worktree, directory, resolved] as const + return [pageReady(), route().slug, params.id, currentProject()?.worktree, currentDir()] as const }, - ([ready, dir, id, root, directory, resolved]) => { - if (!ready || !dir || !directory) { + ([ready, slug, id, root, dir]) => { + if (!ready || !slug || !dir) { activeRoute.session = "" activeRoute.sessionProject = "" activeRoute.directory = "" @@ -1712,29 +1719,28 @@ export default function Layout(props: ParentProps) { return } - const next = resolved || directory - const session = `${dir}/${id}` + const session = `${slug}/${id}` if (!root) { activeRoute.session = session - activeRoute.directory = next + activeRoute.directory = dir activeRoute.sessionProject = "" return } if (server.projects.last() !== root) server.projects.touch(root) - const changed = session !== activeRoute.session || next !== activeRoute.directory + const changed = session !== activeRoute.session || dir !== activeRoute.directory if (changed) { activeRoute.session = session - activeRoute.directory = next - activeRoute.sessionProject = syncSessionRoute(next, id, root) + activeRoute.directory = dir + activeRoute.sessionProject = syncSessionRoute(dir, id, root) return } if (root === activeRoute.sessionProject) return - activeRoute.directory = next - activeRoute.sessionProject = rememberSessionRoute(next, id, root) + activeRoute.directory = dir + activeRoute.sessionProject = rememberSessionRoute(dir, id, root) }, ), ) @@ -1927,6 +1933,7 @@ export default function Layout(props: ParentProps) { const projectSidebarCtx: ProjectSidebarContext = { currentDir, + currentProject, sidebarOpened: () => layout.sidebar.opened(), sidebarHovering, hoverProject: () => state.hoverProject, diff --git a/packages/app/src/pages/layout/helpers.ts b/packages/app/src/pages/layout/helpers.ts index 209cff8a7..226098c1c 100644 --- a/packages/app/src/pages/layout/helpers.ts +++ b/packages/app/src/pages/layout/helpers.ts @@ -40,10 +40,10 @@ export const latestRootSession = (stores: SessionStore[], now: number) => stores.flatMap(roots).sort(sortSessions(now))[0] export function hasProjectPermissions<T>( - request: Record<string, T[] | undefined>, + request: Record<string, T[] | undefined> | undefined, include: (item: T) => boolean = () => true, ) { - return Object.values(request).some((list) => list?.some(include)) + return Object.values(request ?? {}).some((list) => list?.some(include)) } export const childMapByParent = (sessions: Session[] | undefined) => { diff --git a/packages/app/src/pages/layout/sidebar-project.tsx b/packages/app/src/pages/layout/sidebar-project.tsx index a26bc1831..252826456 100644 --- a/packages/app/src/pages/layout/sidebar-project.tsx +++ b/packages/app/src/pages/layout/sidebar-project.tsx @@ -15,6 +15,7 @@ import { childMapByParent, displayName, sortedRootSessions } from "./helpers" export type ProjectSidebarContext = { currentDir: Accessor<string> + currentProject: Accessor<LocalProject | undefined> sidebarOpened: Accessor<boolean> sidebarHovering: Accessor<boolean> hoverProject: Accessor<string | undefined> @@ -278,11 +279,7 @@ export const SortableProject = (props: { const globalSync = useGlobalSync() const language = useLanguage() const sortable = createSortable(props.project.worktree) - const selected = createMemo( - () => - props.project.worktree === props.ctx.currentDir() || - props.project.sandboxes?.includes(props.ctx.currentDir()) === true, - ) + const selected = createMemo(() => props.ctx.currentProject()?.worktree === props.project.worktree) const workspaces = createMemo(() => props.ctx.workspaceIds(props.project).slice(0, 2)) const workspaceEnabled = createMemo(() => props.ctx.workspacesEnabled(props.project)) const dirs = createMemo(() => props.ctx.workspaceIds(props.project)) diff --git a/packages/app/src/pages/layout/sidebar-workspace.tsx b/packages/app/src/pages/layout/sidebar-workspace.tsx index 127626feb..3bf00ea42 100644 --- a/packages/app/src/pages/layout/sidebar-workspace.tsx +++ b/packages/app/src/pages/layout/sidebar-workspace.tsx @@ -17,7 +17,7 @@ import { type LocalProject } from "@/context/layout" import { useGlobalSync } from "@/context/global-sync" import { useLanguage } from "@/context/language" import { NewSessionItem, SessionItem, SessionSkeleton } from "./sidebar-items" -import { childMapByParent, sortedRootSessions } from "./helpers" +import { childMapByParent, sortedRootSessions, workspaceKey } from "./helpers" type InlineEditorComponent = (props: { id: string @@ -323,7 +323,7 @@ export const SortableWorkspace = (props: { const sessions = createMemo(() => sortedRootSessions(workspaceStore, props.sortNow())) const children = createMemo(() => childMapByParent(workspaceStore.session)) const local = createMemo(() => props.directory === props.project.worktree) - const active = createMemo(() => props.ctx.currentDir() === props.directory) + const active = createMemo(() => workspaceKey(props.ctx.currentDir()) === workspaceKey(props.directory)) const workspaceValue = createMemo(() => { const branch = workspaceStore.vcs?.branch const name = branch ?? getFilename(props.directory) |
