Revert "fix(app): more startup efficiency (#18985)"

This reverts commit 98b3340cee.
pull/16065/merge
Adam 2026-03-24 18:36:25 -05:00
parent 50f6aa3763
commit cbe1337f24
No known key found for this signature in database
GPG Key ID: 9CB48779AF150E75
8 changed files with 128 additions and 240 deletions

View File

@ -572,7 +572,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const open = recent() const open = recent()
const seen = new Set(open) const seen = new Set(open)
const pinned: AtOption[] = open.map((path) => ({ type: "file", path, display: path, recent: true })) const pinned: AtOption[] = open.map((path) => ({ type: "file", path, display: path, recent: true }))
if (!query.trim()) return [...agents, ...pinned]
const paths = await files.searchFilesAndDirectories(query) const paths = await files.searchFilesAndDirectories(query)
const fileOptions: AtOption[] = paths const fileOptions: AtOption[] = paths
.filter((path) => !seen.has(path)) .filter((path) => !seen.has(path))

View File

@ -31,47 +31,6 @@ type GlobalStore = {
reload: undefined | "pending" | "complete" reload: undefined | "pending" | "complete"
} }
function waitForPaint() {
return new Promise<void>((resolve) => {
let done = false
const finish = () => {
if (done) return
done = true
resolve()
}
const timer = setTimeout(finish, 50)
if (typeof requestAnimationFrame !== "function") return
requestAnimationFrame(() => {
clearTimeout(timer)
finish()
})
})
}
function errors(list: PromiseSettledResult<unknown>[]) {
return list.filter((item): item is PromiseRejectedResult => item.status === "rejected").map((item) => item.reason)
}
function runAll(list: Array<() => Promise<unknown>>) {
return Promise.allSettled(list.map((item) => item()))
}
function showErrors(input: {
errors: unknown[]
title: string
translate: (key: string, vars?: Record<string, string | number>) => string
formatMoreCount: (count: number) => string
}) {
if (input.errors.length === 0) return
const message = formatServerError(input.errors[0], input.translate)
const more = input.errors.length > 1 ? input.formatMoreCount(input.errors.length - 1) : ""
showToast({
variant: "error",
title: input.title,
description: message + more,
})
}
export async function bootstrapGlobal(input: { export async function bootstrapGlobal(input: {
globalSDK: OpencodeClient globalSDK: OpencodeClient
requestFailedTitle: string requestFailedTitle: string
@ -79,54 +38,45 @@ export async function bootstrapGlobal(input: {
formatMoreCount: (count: number) => string formatMoreCount: (count: number) => string
setGlobalStore: SetStoreFunction<GlobalStore> setGlobalStore: SetStoreFunction<GlobalStore>
}) { }) {
const fast = [ const tasks = [
() => retry(() =>
retry(() => input.globalSDK.path.get().then((x) => {
input.globalSDK.path.get().then((x) => { input.setGlobalStore("path", x.data!)
input.setGlobalStore("path", x.data!) }),
}), ),
), retry(() =>
() => input.globalSDK.global.config.get().then((x) => {
retry(() => input.setGlobalStore("config", x.data!)
input.globalSDK.global.config.get().then((x) => { }),
input.setGlobalStore("config", x.data!) ),
}), retry(() =>
), input.globalSDK.project.list().then((x) => {
() => const projects = (x.data ?? [])
retry(() => .filter((p) => !!p?.id)
input.globalSDK.provider.list().then((x) => { .filter((p) => !!p.worktree && !p.worktree.includes("opencode-test"))
input.setGlobalStore("provider", normalizeProviderList(x.data!)) .slice()
}), .sort((a, b) => cmp(a.id, b.id))
), input.setGlobalStore("project", projects)
}),
),
retry(() =>
input.globalSDK.provider.list().then((x) => {
input.setGlobalStore("provider", normalizeProviderList(x.data!))
}),
),
] ]
const slow = [ const results = await Promise.allSettled(tasks)
() => const errors = results.filter((r): r is PromiseRejectedResult => r.status === "rejected").map((r) => r.reason)
retry(() => if (errors.length) {
input.globalSDK.project.list().then((x) => { const message = formatServerError(errors[0], input.translate)
const projects = (x.data ?? []) const more = errors.length > 1 ? input.formatMoreCount(errors.length - 1) : ""
.filter((p) => !!p?.id) showToast({
.filter((p) => !!p.worktree && !p.worktree.includes("opencode-test")) variant: "error",
.slice() title: input.requestFailedTitle,
.sort((a, b) => cmp(a.id, b.id)) description: message + more,
input.setGlobalStore("project", projects) })
}), }
),
]
showErrors({
errors: errors(await runAll(fast)),
title: input.requestFailedTitle,
translate: input.translate,
formatMoreCount: input.formatMoreCount,
})
await waitForPaint()
showErrors({
errors: errors(await runAll(slow)),
title: input.requestFailedTitle,
translate: input.translate,
formatMoreCount: input.formatMoreCount,
})
input.setGlobalStore("ready", true) input.setGlobalStore("ready", true)
} }
@ -169,113 +119,95 @@ export async function bootstrapDirectory(input: {
} }
if (loading) input.setStore("status", "partial") if (loading) input.setStore("status", "partial")
const fast = [ const results = await Promise.allSettled([
() => seededProject
seededProject ? Promise.resolve()
? Promise.resolve() : retry(() => input.sdk.project.current()).then((x) => input.setStore("project", x.data!.id)),
: retry(() => input.sdk.project.current()).then((x) => input.setStore("project", x.data!.id)), retry(() =>
() => retry(() => input.sdk.app.agents().then((x) => input.setStore("agent", x.data ?? []))), input.sdk.provider.list().then((x) => {
() => retry(() => input.sdk.config.get().then((x) => input.setStore("config", x.data!))), input.setStore("provider", normalizeProviderList(x.data!))
() => }),
retry(() => ),
input.sdk.path.get().then((x) => { retry(() => input.sdk.app.agents().then((x) => input.setStore("agent", x.data ?? []))),
input.setStore("path", x.data!) retry(() => input.sdk.config.get().then((x) => input.setStore("config", x.data!))),
const next = projectID(x.data?.directory ?? input.directory, input.global.project) retry(() =>
if (next) input.setStore("project", next) input.sdk.path.get().then((x) => {
}), input.setStore("path", x.data!)
), const next = projectID(x.data?.directory ?? input.directory, input.global.project)
() => retry(() => input.sdk.session.status().then((x) => input.setStore("session_status", x.data!))), if (next) input.setStore("project", next)
() => }),
retry(() => ),
input.sdk.vcs.get().then((x) => { retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? []))),
const next = x.data ?? input.store.vcs retry(() => input.sdk.session.status().then((x) => input.setStore("session_status", x.data!))),
input.setStore("vcs", next) input.loadSessions(input.directory),
if (next?.branch) input.vcsCache.setStore("value", next) retry(() => input.sdk.mcp.status().then((x) => input.setStore("mcp", x.data!))),
}), retry(() => input.sdk.lsp.status().then((x) => input.setStore("lsp", x.data!))),
), retry(() =>
() => retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? []))), input.sdk.vcs.get().then((x) => {
() => const next = x.data ?? input.store.vcs
retry(() => input.setStore("vcs", next)
input.sdk.permission.list().then((x) => { if (next?.branch) input.vcsCache.setStore("value", next)
const grouped = groupBySession( }),
(x.data ?? []).filter((perm): perm is PermissionRequest => !!perm?.id && !!perm.sessionID), ),
) retry(() =>
batch(() => { input.sdk.permission.list().then((x) => {
for (const sessionID of Object.keys(input.store.permission)) { const grouped = groupBySession(
if (grouped[sessionID]) continue (x.data ?? []).filter((perm): perm is PermissionRequest => !!perm?.id && !!perm.sessionID),
input.setStore("permission", sessionID, []) )
} batch(() => {
for (const [sessionID, permissions] of Object.entries(grouped)) { for (const sessionID of Object.keys(input.store.permission)) {
input.setStore( if (grouped[sessionID]) continue
"permission", input.setStore("permission", sessionID, [])
sessionID, }
reconcile( for (const [sessionID, permissions] of Object.entries(grouped)) {
permissions.filter((p) => !!p?.id).sort((a, b) => cmp(a.id, b.id)), input.setStore(
{ key: "id" }, "permission",
), sessionID,
) reconcile(
} permissions.filter((p) => !!p?.id).sort((a, b) => cmp(a.id, b.id)),
}) { key: "id" },
}), ),
), )
() => }
retry(() => })
input.sdk.question.list().then((x) => { }),
const grouped = groupBySession((x.data ?? []).filter((q): q is QuestionRequest => !!q?.id && !!q.sessionID)) ),
batch(() => { retry(() =>
for (const sessionID of Object.keys(input.store.question)) { input.sdk.question.list().then((x) => {
if (grouped[sessionID]) continue const grouped = groupBySession((x.data ?? []).filter((q): q is QuestionRequest => !!q?.id && !!q.sessionID))
input.setStore("question", sessionID, []) batch(() => {
} for (const sessionID of Object.keys(input.store.question)) {
for (const [sessionID, questions] of Object.entries(grouped)) { if (grouped[sessionID]) continue
input.setStore( input.setStore("question", sessionID, [])
"question", }
sessionID, for (const [sessionID, questions] of Object.entries(grouped)) {
reconcile( input.setStore(
questions.filter((q) => !!q?.id).sort((a, b) => cmp(a.id, b.id)), "question",
{ key: "id" }, sessionID,
), reconcile(
) questions.filter((q) => !!q?.id).sort((a, b) => cmp(a.id, b.id)),
} { key: "id" },
}) ),
}), )
), }
] })
}),
),
])
const slow = [ const errors = results
() => .filter((item): item is PromiseRejectedResult => item.status === "rejected")
retry(() => .map((item) => item.reason)
input.sdk.provider.list().then((x) => { if (errors.length > 0) {
input.setStore("provider", normalizeProviderList(x.data!)) console.error("Failed to bootstrap instance", errors[0])
}),
),
() => Promise.resolve(input.loadSessions(input.directory)),
() => retry(() => input.sdk.mcp.status().then((x) => input.setStore("mcp", x.data!))),
() => retry(() => input.sdk.lsp.status().then((x) => input.setStore("lsp", x.data!))),
]
const errs = errors(await runAll(fast))
if (errs.length > 0) {
console.error("Failed to bootstrap instance", errs[0])
const project = getFilename(input.directory) const project = getFilename(input.directory)
showToast({ showToast({
variant: "error", variant: "error",
title: input.translate("toast.project.reloadFailed.title", { project }), title: input.translate("toast.project.reloadFailed.title", { project }),
description: formatServerError(errs[0], input.translate), description: formatServerError(errors[0], input.translate),
}) })
return
} }
await waitForPaint() if (loading) input.setStore("status", "complete")
const slowErrs = errors(await runAll(slow))
if (slowErrs.length > 0) {
console.error("Failed to finish bootstrap instance", slowErrs[0])
const project = getFilename(input.directory)
showToast({
variant: "error",
title: input.translate("toast.project.reloadFailed.title", { project }),
description: formatServerError(slowErrs[0], input.translate),
})
}
if (loading && errs.length === 0 && slowErrs.length === 0) input.setStore("status", "complete")
} }

View File

@ -118,11 +118,8 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
createEffect(() => { createEffect(() => {
if (typeof document === "undefined") return if (typeof document === "undefined") return
const id = store.appearance?.font ?? defaultSettings.appearance.font void loadFont().then((x) => x.ensureMonoFont(store.appearance?.font))
if (id !== defaultSettings.appearance.font) { document.documentElement.style.setProperty("--font-family-mono", monoFontFamily(store.appearance?.font))
void loadFont().then((x) => x.ensureMonoFont(id))
}
document.documentElement.style.setProperty("--font-family-mono", monoFontFamily(id))
}) })
return { return {

View File

@ -180,8 +180,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
return globalSync.child(directory) return globalSync.child(directory)
} }
const absolute = (path: string) => (current()[0].path.directory + "/" + path).replace("//", "/") const absolute = (path: string) => (current()[0].path.directory + "/" + path).replace("//", "/")
const initialMessagePageSize = 80 const messagePageSize = 200
const historyMessagePageSize = 200
const inflight = new Map<string, Promise<void>>() const inflight = new Map<string, Promise<void>>()
const inflightDiff = new Map<string, Promise<void>>() const inflightDiff = new Map<string, Promise<void>>()
const inflightTodo = new Map<string, Promise<void>>() const inflightTodo = new Map<string, Promise<void>>()
@ -464,7 +463,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
const cached = store.message[sessionID] !== undefined && meta.limit[key] !== undefined const cached = store.message[sessionID] !== undefined && meta.limit[key] !== undefined
if (cached && hasSession && !opts?.force) return if (cached && hasSession && !opts?.force) return
const limit = meta.limit[key] ?? initialMessagePageSize const limit = meta.limit[key] ?? messagePageSize
const sessionReq = const sessionReq =
hasSession && !opts?.force hasSession && !opts?.force
? Promise.resolve() ? Promise.resolve()
@ -561,7 +560,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
const [, setStore] = globalSync.child(directory) const [, setStore] = globalSync.child(directory)
touch(directory, setStore, sessionID) touch(directory, setStore, sessionID)
const key = keyFor(directory, sessionID) const key = keyFor(directory, sessionID)
const step = count ?? historyMessagePageSize const step = count ?? messagePageSize
if (meta.loading[key]) return if (meta.loading[key]) return
if (meta.complete[key]) return if (meta.complete[key]) return
const before = meta.cursor[key] const before = meta.cursor[key]

View File

@ -113,14 +113,6 @@ export default function Home() {
</ul> </ul>
</div> </div>
</Match> </Match>
<Match when={!sync.ready}>
<div class="mt-30 mx-auto flex flex-col items-center gap-3">
<div class="text-12-regular text-text-weak">{language.t("common.loading")}</div>
<Button class="px-3" onClick={chooseProject}>
{language.t("command.project.open")}
</Button>
</div>
</Match>
<Match when={true}> <Match when={true}>
<div class="mt-30 mx-auto flex flex-col items-center gap-3"> <div class="mt-30 mx-auto flex flex-col items-center gap-3">
<Icon name="folder-add-left" size="large" /> <Icon name="folder-add-left" size="large" />

View File

@ -1184,6 +1184,8 @@ export default function Page() {
on( on(
() => sdk.directory, () => sdk.directory,
() => { () => {
void file.tree.list("")
const tab = activeFileTab() const tab = activeFileTab()
if (!tab) return if (!tab) return
const path = file.pathFromTab(tab) const path = file.pathFromTab(tab)
@ -1638,9 +1640,6 @@ export default function Page() {
sessionID: () => params.id, sessionID: () => params.id,
messagesReady, messagesReady,
visibleUserMessages, visibleUserMessages,
historyMore,
historyLoading,
loadMore: (sessionID) => sync.session.history.loadMore(sessionID),
turnStart: historyWindow.turnStart, turnStart: historyWindow.turnStart,
currentMessageId: () => store.messageId, currentMessageId: () => store.messageId,
pendingMessage: () => ui.pendingMessage, pendingMessage: () => ui.pendingMessage,
@ -1712,7 +1711,7 @@ export default function Page() {
<div class="flex-1 min-h-0 overflow-hidden"> <div class="flex-1 min-h-0 overflow-hidden">
<Switch> <Switch>
<Match when={params.id}> <Match when={params.id}>
<Show when={messagesReady()}> <Show when={lastUserMessage()}>
<MessageTimeline <MessageTimeline
mobileChanges={mobileChanges()} mobileChanges={mobileChanges()}
mobileFallback={reviewContent({ mobileFallback={reviewContent({

View File

@ -8,9 +8,6 @@ export const useSessionHashScroll = (input: {
sessionID: () => string | undefined sessionID: () => string | undefined
messagesReady: () => boolean messagesReady: () => boolean
visibleUserMessages: () => UserMessage[] visibleUserMessages: () => UserMessage[]
historyMore: () => boolean
historyLoading: () => boolean
loadMore: (sessionID: string) => Promise<void>
turnStart: () => number turnStart: () => number
currentMessageId: () => string | undefined currentMessageId: () => string | undefined
pendingMessage: () => string | undefined pendingMessage: () => string | undefined
@ -184,21 +181,6 @@ export const useSessionHashScroll = (input: {
queue(() => scrollToMessage(msg, "auto")) queue(() => scrollToMessage(msg, "auto"))
}) })
createEffect(() => {
const sessionID = input.sessionID()
if (!sessionID || !input.messagesReady()) return
visibleUserMessages()
let targetId = input.pendingMessage()
if (!targetId && !clearing) targetId = messageIdFromHash(location.hash)
if (!targetId) return
if (messageById().has(targetId)) return
if (!input.historyMore() || input.historyLoading()) return
void input.loadMore(sessionID)
})
onMount(() => { onMount(() => {
if (typeof window !== "undefined" && "scrollRestoration" in window.history) { if (typeof window !== "undefined" && "scrollRestoration" in window.history) {
window.history.scrollRestoration = "manual" window.history.scrollRestoration = "manual"

View File

@ -1,10 +1,7 @@
import { readFileSync } from "node:fs"
import solidPlugin from "vite-plugin-solid" import solidPlugin from "vite-plugin-solid"
import tailwindcss from "@tailwindcss/vite" import tailwindcss from "@tailwindcss/vite"
import { fileURLToPath } from "url" import { fileURLToPath } from "url"
const theme = fileURLToPath(new URL("./public/oc-theme-preload.js", import.meta.url))
/** /**
* @type {import("vite").PluginOption} * @type {import("vite").PluginOption}
*/ */
@ -24,15 +21,6 @@ export default [
} }
}, },
}, },
{
name: "opencode-desktop:theme-preload",
transformIndexHtml(html) {
return html.replace(
'<script id="oc-theme-preload-script" src="/oc-theme-preload.js"></script>',
`<script id="oc-theme-preload-script">${readFileSync(theme, "utf8")}</script>`,
)
},
},
tailwindcss(), tailwindcss(),
solidPlugin(), solidPlugin(),
] ]