mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-07 01:08:58 +00:00
fix: lots of desktop stability, better e2e error logging (#18300)
This commit is contained in:
@@ -378,6 +378,7 @@ function createGlobalSync() {
|
||||
return globalStore.error
|
||||
},
|
||||
child: children.child,
|
||||
peek: children.peek,
|
||||
bootstrap,
|
||||
updateConfig,
|
||||
project: projectApi,
|
||||
|
||||
@@ -226,6 +226,15 @@ export function createChildStoreManager(input: {
|
||||
return childStore
|
||||
}
|
||||
|
||||
function peek(directory: string, options: ChildOptions = {}) {
|
||||
const childStore = ensureChild(directory)
|
||||
const shouldBootstrap = options.bootstrap ?? true
|
||||
if (shouldBootstrap && childStore[0].status === "loading") {
|
||||
input.onBootstrap(directory)
|
||||
}
|
||||
return childStore
|
||||
}
|
||||
|
||||
function projectMeta(directory: string, patch: ProjectMeta) {
|
||||
const [store, setStore] = ensureChild(directory)
|
||||
const cached = metaCache.get(directory)
|
||||
@@ -256,6 +265,7 @@ export function createChildStoreManager(input: {
|
||||
children,
|
||||
ensureChild,
|
||||
child,
|
||||
peek,
|
||||
projectMeta,
|
||||
projectIcon,
|
||||
mark,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user