summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-01-06 08:18:17 -0600
committerAdam <[email protected]>2026-01-06 08:19:17 -0600
commitb88bcd49fdea0955f2efc8f09a3614c188d22107 (patch)
tree8ab04d7a9d2b892cd884eab5ab3bb5da9187191a /packages/app/src
parent3f463bc9168abd907be9ae582e161ff89c3a27c9 (diff)
downloadopencode-b88bcd49fdea0955f2efc8f09a3614c188d22107.tar.gz
opencode-b88bcd49fdea0955f2efc8f09a3614c188d22107.zip
fix(app): code splitting for web load perf gains
Diffstat (limited to 'packages/app/src')
-rw-r--r--packages/app/src/app.tsx21
-rw-r--r--packages/app/src/components/session/session-header.tsx7
-rw-r--r--packages/app/src/components/terminal.tsx9
-rw-r--r--packages/app/src/context/command.tsx13
-rw-r--r--packages/app/src/context/server.tsx62
5 files changed, 81 insertions, 31 deletions
diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx
index e41575e7a..a2f1aa401 100644
--- a/packages/app/src/app.tsx
+++ b/packages/app/src/app.tsx
@@ -1,5 +1,5 @@
import "@/index.css"
-import { ErrorBoundary, Show, type ParentProps } from "solid-js"
+import { ErrorBoundary, Show, Suspense, lazy, type ParentProps } from "solid-js"
import { Router, Route, Navigate } from "@solidjs/router"
import { MetaProvider } from "@solidjs/meta"
import { Font } from "@opencode-ai/ui/font"
@@ -21,12 +21,14 @@ import { NotificationProvider } from "@/context/notification"
import { DialogProvider } from "@opencode-ai/ui/context/dialog"
import { CommandProvider } from "@/context/command"
import Layout from "@/pages/layout"
-import Home from "@/pages/home"
import DirectoryLayout from "@/pages/directory-layout"
-import Session from "@/pages/session"
import { ErrorPage } from "./pages/error"
import { iife } from "@opencode-ai/util/iife"
+const Home = lazy(() => import("@/pages/home"))
+const Session = lazy(() => import("@/pages/session"))
+const Loading = () => <div class="size-full flex items-center justify-center text-text-weak">Loading...</div>
+
declare global {
interface Window {
__OPENCODE__?: { updaterEnabled?: boolean; port?: number }
@@ -81,7 +83,14 @@ export function App() {
</PermissionProvider>
)}
>
- <Route path="/" component={Home} />
+ <Route
+ path="/"
+ component={() => (
+ <Suspense fallback={<Loading />}>
+ <Home />
+ </Suspense>
+ )}
+ />
<Route path="/:dir" component={DirectoryLayout}>
<Route path="/" component={() => <Navigate href="session" />} />
<Route
@@ -91,7 +100,9 @@ export function App() {
<TerminalProvider>
<FileProvider>
<PromptProvider>
- <Session />
+ <Suspense fallback={<Loading />}>
+ <Session />
+ </Suspense>
</PromptProvider>
</FileProvider>
</TerminalProvider>
diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx
index e70e0790c..4958ad2c3 100644
--- a/packages/app/src/components/session/session-header.tsx
+++ b/packages/app/src/components/session/session-header.tsx
@@ -244,8 +244,13 @@ export function SessionHeader() {
}
return shareURL
},
+ { initialValue: "" },
+ )
+ return (
+ <Show when={url.latest}>
+ {(shareUrl) => <TextField value={shareUrl()} readOnly copyable class="w-72" />}
+ </Show>
)
- return <Show when={url()}>{(url) => <TextField value={url()} readOnly copyable class="w-72" />}</Show>
})}
</Popover>
</Show>
diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx
index a298e3f76..18c77653e 100644
--- a/packages/app/src/components/terminal.tsx
+++ b/packages/app/src/components/terminal.tsx
@@ -1,4 +1,4 @@
-import { Ghostty, Terminal as Term, FitAddon } from "ghostty-web"
+import type { Ghostty, Terminal as Term, FitAddon } from "ghostty-web"
import { ComponentProps, createEffect, createSignal, onCleanup, onMount, splitProps } from "solid-js"
import { useSDK } from "@/context/sdk"
import { SerializeAddon } from "@/addons/serialize"
@@ -106,14 +106,15 @@ export const Terminal = (props: TerminalProps) => {
}
onMount(async () => {
- ghostty = await Ghostty.load()
+ const mod = await import("ghostty-web")
+ ghostty = await mod.Ghostty.load()
const socket = new WebSocket(
sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`,
)
ws = socket
- const t = new Term({
+ const t = new mod.Terminal({
cursorBlink: true,
fontSize: 14,
fontFamily: "IBM Plex Mono, monospace",
@@ -142,7 +143,7 @@ export const Terminal = (props: TerminalProps) => {
return false
})
- fitAddon = new FitAddon()
+ fitAddon = new mod.FitAddon()
serializeAddon = new SerializeAddon()
t.loadAddon(serializeAddon)
t.loadAddon(fitAddon)
diff --git a/packages/app/src/context/command.tsx b/packages/app/src/context/command.tsx
index efd83bec8..7f88b74c8 100644
--- a/packages/app/src/context/command.tsx
+++ b/packages/app/src/context/command.tsx
@@ -177,8 +177,19 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
const dialog = useDialog()
const options = createMemo(() => {
- const all = registrations().flatMap((x) => x())
+ const seen = new Set<string>()
+ const all: CommandOption[] = []
+
+ for (const reg of registrations()) {
+ for (const opt of reg()) {
+ if (seen.has(opt.id)) continue
+ seen.add(opt.id)
+ all.push(opt)
+ }
+ }
+
const suggested = all.filter((x) => x.suggested && !x.disabled)
+
return [
...suggested.map((x) => ({
...x,
diff --git a/packages/app/src/context/server.tsx b/packages/app/src/context/server.tsx
index beb00be87..48e7e99cc 100644
--- a/packages/app/src/context/server.tsx
+++ b/packages/app/src/context/server.tsx
@@ -1,6 +1,6 @@
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
import { createSimpleContext } from "@opencode-ai/ui/context"
-import { batch, createEffect, createMemo, createResource, createSignal, onCleanup } from "solid-js"
+import { batch, createEffect, createMemo, createSignal, onCleanup } from "solid-js"
import { createStore } from "solid-js/store"
import { usePlatform } from "@/context/platform"
import { persisted } from "@/utils/persist"
@@ -91,27 +91,49 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext(
const isReady = createMemo(() => ready() && !!active())
- const [healthy, { refetch }] = createResource(
- () => active() || undefined,
- async (url) => {
- if (!url) return
-
- const sdk = createOpencodeClient({
- baseUrl: url,
- fetch: platform.fetch,
- signal: AbortSignal.timeout(3000),
- })
- return sdk.global
- .health()
- .then((x) => x.data?.healthy === true)
- .catch(() => false)
- },
- )
+ const [healthy, setHealthy] = createSignal<boolean | undefined>(undefined)
+
+ const check = (url: string) => {
+ const sdk = createOpencodeClient({
+ baseUrl: url,
+ fetch: platform.fetch,
+ signal: AbortSignal.timeout(3000),
+ })
+ return sdk.global
+ .health()
+ .then((x) => x.data?.healthy === true)
+ .catch(() => false)
+ }
createEffect(() => {
- if (!active()) return
- const interval = setInterval(() => refetch(), 10_000)
- onCleanup(() => clearInterval(interval))
+ const url = active()
+ if (!url) return
+
+ setHealthy(undefined)
+
+ let alive = true
+ let busy = false
+
+ const run = () => {
+ if (busy) return
+ busy = true
+ void check(url)
+ .then((next) => {
+ if (!alive) return
+ setHealthy(next)
+ })
+ .finally(() => {
+ busy = false
+ })
+ }
+
+ run()
+ const interval = setInterval(run, 10_000)
+
+ onCleanup(() => {
+ alive = false
+ clearInterval(interval)
+ })
})
const origin = createMemo(() => projectsKey(active()))