diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index ee98e68cd5..f523671ec9 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -572,7 +572,6 @@ export const PromptInput: Component = (props) => { const open = recent() const seen = new Set(open) 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 fileOptions: AtOption[] = paths .filter((path) => !seen.has(path)) diff --git a/packages/app/src/context/global-sync/bootstrap.ts b/packages/app/src/context/global-sync/bootstrap.ts index 47be3abcb3..c795ab471c 100644 --- a/packages/app/src/context/global-sync/bootstrap.ts +++ b/packages/app/src/context/global-sync/bootstrap.ts @@ -31,47 +31,6 @@ type GlobalStore = { reload: undefined | "pending" | "complete" } -function waitForPaint() { - return new Promise((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[]) { - return list.filter((item): item is PromiseRejectedResult => item.status === "rejected").map((item) => item.reason) -} - -function runAll(list: Array<() => Promise>) { - return Promise.allSettled(list.map((item) => item())) -} - -function showErrors(input: { - errors: unknown[] - title: string - translate: (key: string, vars?: Record) => 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: { globalSDK: OpencodeClient requestFailedTitle: string @@ -79,54 +38,45 @@ export async function bootstrapGlobal(input: { formatMoreCount: (count: number) => string setGlobalStore: SetStoreFunction }) { - const fast = [ - () => - retry(() => - input.globalSDK.path.get().then((x) => { - input.setGlobalStore("path", x.data!) - }), - ), - () => - retry(() => - input.globalSDK.global.config.get().then((x) => { - input.setGlobalStore("config", x.data!) - }), - ), - () => - retry(() => - input.globalSDK.provider.list().then((x) => { - input.setGlobalStore("provider", normalizeProviderList(x.data!)) - }), - ), + const tasks = [ + retry(() => + input.globalSDK.path.get().then((x) => { + input.setGlobalStore("path", x.data!) + }), + ), + retry(() => + input.globalSDK.global.config.get().then((x) => { + input.setGlobalStore("config", x.data!) + }), + ), + retry(() => + input.globalSDK.project.list().then((x) => { + const projects = (x.data ?? []) + .filter((p) => !!p?.id) + .filter((p) => !!p.worktree && !p.worktree.includes("opencode-test")) + .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 = [ - () => - retry(() => - input.globalSDK.project.list().then((x) => { - const projects = (x.data ?? []) - .filter((p) => !!p?.id) - .filter((p) => !!p.worktree && !p.worktree.includes("opencode-test")) - .slice() - .sort((a, b) => cmp(a.id, b.id)) - 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, - }) + const results = await Promise.allSettled(tasks) + const errors = results.filter((r): r is PromiseRejectedResult => r.status === "rejected").map((r) => r.reason) + if (errors.length) { + const message = formatServerError(errors[0], input.translate) + const more = errors.length > 1 ? input.formatMoreCount(errors.length - 1) : "" + showToast({ + variant: "error", + title: input.requestFailedTitle, + description: message + more, + }) + } input.setGlobalStore("ready", true) } @@ -169,113 +119,95 @@ export async function bootstrapDirectory(input: { } if (loading) input.setStore("status", "partial") - const fast = [ - () => - seededProject - ? Promise.resolve() - : retry(() => input.sdk.project.current()).then((x) => input.setStore("project", x.data!.id)), - () => retry(() => input.sdk.app.agents().then((x) => input.setStore("agent", x.data ?? []))), - () => retry(() => input.sdk.config.get().then((x) => input.setStore("config", x.data!))), - () => - retry(() => - input.sdk.path.get().then((x) => { - input.setStore("path", x.data!) - const next = projectID(x.data?.directory ?? input.directory, input.global.project) - if (next) input.setStore("project", next) - }), - ), - () => retry(() => input.sdk.session.status().then((x) => input.setStore("session_status", x.data!))), - () => - retry(() => - input.sdk.vcs.get().then((x) => { - const next = x.data ?? input.store.vcs - input.setStore("vcs", next) - if (next?.branch) input.vcsCache.setStore("value", next) - }), - ), - () => retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? []))), - () => - retry(() => - input.sdk.permission.list().then((x) => { - const grouped = groupBySession( - (x.data ?? []).filter((perm): perm is PermissionRequest => !!perm?.id && !!perm.sessionID), - ) - batch(() => { - for (const sessionID of Object.keys(input.store.permission)) { - if (grouped[sessionID]) continue - input.setStore("permission", sessionID, []) - } - for (const [sessionID, permissions] of Object.entries(grouped)) { - input.setStore( - "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(() => { - for (const sessionID of Object.keys(input.store.question)) { - if (grouped[sessionID]) continue - input.setStore("question", sessionID, []) - } - for (const [sessionID, questions] of Object.entries(grouped)) { - input.setStore( - "question", - sessionID, - reconcile( - questions.filter((q) => !!q?.id).sort((a, b) => cmp(a.id, b.id)), - { key: "id" }, - ), - ) - } - }) - }), - ), - ] + const results = await Promise.allSettled([ + seededProject + ? Promise.resolve() + : retry(() => input.sdk.project.current()).then((x) => input.setStore("project", x.data!.id)), + retry(() => + input.sdk.provider.list().then((x) => { + input.setStore("provider", normalizeProviderList(x.data!)) + }), + ), + retry(() => input.sdk.app.agents().then((x) => input.setStore("agent", x.data ?? []))), + retry(() => input.sdk.config.get().then((x) => input.setStore("config", x.data!))), + retry(() => + input.sdk.path.get().then((x) => { + input.setStore("path", x.data!) + const next = projectID(x.data?.directory ?? input.directory, input.global.project) + if (next) input.setStore("project", next) + }), + ), + retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? []))), + retry(() => input.sdk.session.status().then((x) => input.setStore("session_status", x.data!))), + 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!))), + retry(() => + input.sdk.vcs.get().then((x) => { + const next = x.data ?? input.store.vcs + input.setStore("vcs", next) + if (next?.branch) input.vcsCache.setStore("value", next) + }), + ), + retry(() => + input.sdk.permission.list().then((x) => { + const grouped = groupBySession( + (x.data ?? []).filter((perm): perm is PermissionRequest => !!perm?.id && !!perm.sessionID), + ) + batch(() => { + for (const sessionID of Object.keys(input.store.permission)) { + if (grouped[sessionID]) continue + input.setStore("permission", sessionID, []) + } + for (const [sessionID, permissions] of Object.entries(grouped)) { + input.setStore( + "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(() => { + for (const sessionID of Object.keys(input.store.question)) { + if (grouped[sessionID]) continue + input.setStore("question", sessionID, []) + } + for (const [sessionID, questions] of Object.entries(grouped)) { + input.setStore( + "question", + sessionID, + reconcile( + questions.filter((q) => !!q?.id).sort((a, b) => cmp(a.id, b.id)), + { key: "id" }, + ), + ) + } + }) + }), + ), + ]) - const slow = [ - () => - retry(() => - input.sdk.provider.list().then((x) => { - input.setStore("provider", normalizeProviderList(x.data!)) - }), - ), - () => 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 errors = results + .filter((item): item is PromiseRejectedResult => item.status === "rejected") + .map((item) => item.reason) + if (errors.length > 0) { + console.error("Failed to bootstrap instance", errors[0]) const project = getFilename(input.directory) showToast({ variant: "error", title: input.translate("toast.project.reloadFailed.title", { project }), - description: formatServerError(errs[0], input.translate), + description: formatServerError(errors[0], input.translate), }) + return } - await waitForPaint() - 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") + if (loading) input.setStore("status", "complete") } diff --git a/packages/app/src/context/settings.tsx b/packages/app/src/context/settings.tsx index eddd752eb4..247d36dd36 100644 --- a/packages/app/src/context/settings.tsx +++ b/packages/app/src/context/settings.tsx @@ -118,11 +118,8 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont createEffect(() => { if (typeof document === "undefined") return - const id = store.appearance?.font ?? defaultSettings.appearance.font - if (id !== defaultSettings.appearance.font) { - void loadFont().then((x) => x.ensureMonoFont(id)) - } - document.documentElement.style.setProperty("--font-family-mono", monoFontFamily(id)) + void loadFont().then((x) => x.ensureMonoFont(store.appearance?.font)) + document.documentElement.style.setProperty("--font-family-mono", monoFontFamily(store.appearance?.font)) }) return { diff --git a/packages/app/src/context/sync.tsx b/packages/app/src/context/sync.tsx index bbf4fc5ec4..66b889e2ad 100644 --- a/packages/app/src/context/sync.tsx +++ b/packages/app/src/context/sync.tsx @@ -180,8 +180,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ return globalSync.child(directory) } const absolute = (path: string) => (current()[0].path.directory + "/" + path).replace("//", "/") - const initialMessagePageSize = 80 - const historyMessagePageSize = 200 + const messagePageSize = 200 const inflight = new Map>() const inflightDiff = new Map>() const inflightTodo = new Map>() @@ -464,7 +463,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ const cached = store.message[sessionID] !== undefined && meta.limit[key] !== undefined if (cached && hasSession && !opts?.force) return - const limit = meta.limit[key] ?? initialMessagePageSize + const limit = meta.limit[key] ?? messagePageSize const sessionReq = hasSession && !opts?.force ? Promise.resolve() @@ -561,7 +560,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ const [, setStore] = globalSync.child(directory) touch(directory, setStore, sessionID) const key = keyFor(directory, sessionID) - const step = count ?? historyMessagePageSize + const step = count ?? messagePageSize if (meta.loading[key]) return if (meta.complete[key]) return const before = meta.cursor[key] diff --git a/packages/app/src/pages/home.tsx b/packages/app/src/pages/home.tsx index 4c795b9683..ba3a2b9427 100644 --- a/packages/app/src/pages/home.tsx +++ b/packages/app/src/pages/home.tsx @@ -113,14 +113,6 @@ export default function Home() { - -
-
{language.t("common.loading")}
- -
-
diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 2d3e31355a..7a3b476e8d 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1184,6 +1184,8 @@ export default function Page() { on( () => sdk.directory, () => { + void file.tree.list("") + const tab = activeFileTab() if (!tab) return const path = file.pathFromTab(tab) @@ -1638,9 +1640,6 @@ export default function Page() { sessionID: () => params.id, messagesReady, visibleUserMessages, - historyMore, - historyLoading, - loadMore: (sessionID) => sync.session.history.loadMore(sessionID), turnStart: historyWindow.turnStart, currentMessageId: () => store.messageId, pendingMessage: () => ui.pendingMessage, @@ -1712,7 +1711,7 @@ export default function Page() {
- + string | undefined messagesReady: () => boolean visibleUserMessages: () => UserMessage[] - historyMore: () => boolean - historyLoading: () => boolean - loadMore: (sessionID: string) => Promise turnStart: () => number currentMessageId: () => string | undefined pendingMessage: () => string | undefined @@ -184,21 +181,6 @@ export const useSessionHashScroll = (input: { 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(() => { if (typeof window !== "undefined" && "scrollRestoration" in window.history) { window.history.scrollRestoration = "manual" diff --git a/packages/app/vite.js b/packages/app/vite.js index f65a68a1cb..6b8fd61376 100644 --- a/packages/app/vite.js +++ b/packages/app/vite.js @@ -1,10 +1,7 @@ -import { readFileSync } from "node:fs" import solidPlugin from "vite-plugin-solid" import tailwindcss from "@tailwindcss/vite" import { fileURLToPath } from "url" -const theme = fileURLToPath(new URL("./public/oc-theme-preload.js", import.meta.url)) - /** * @type {import("vite").PluginOption} */ @@ -24,15 +21,6 @@ export default [ } }, }, - { - name: "opencode-desktop:theme-preload", - transformIndexHtml(html) { - return html.replace( - '', - ``, - ) - }, - }, tailwindcss(), solidPlugin(), ]