diff --git a/packages/app/e2e/settings/settings.spec.ts b/packages/app/e2e/settings/settings.spec.ts index 0602e95c7e..1b151b6066 100644 --- a/packages/app/e2e/settings/settings.spec.ts +++ b/packages/app/e2e/settings/settings.spec.ts @@ -159,7 +159,7 @@ test("typing a code font with spaces persists and updates CSS variable", async ( const dialog = await openSettings(page) const input = dialog.locator(settingsCodeFontSelector) await expect(input).toBeVisible() - await expect(input).toHaveAttribute("placeholder", "IBM Plex Mono") + await expect(input).toHaveAttribute("placeholder", "System Mono") const initialFontFamily = await page.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(), @@ -167,7 +167,7 @@ test("typing a code font with spaces persists and updates CSS variable", async ( const initialUIFamily = await page.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(), ) - expect(initialFontFamily).toContain("IBM Plex Mono") + expect(initialFontFamily).toContain("ui-monospace") const next = "Test Mono" @@ -185,7 +185,7 @@ test("typing a code font with spaces persists and updates CSS variable", async ( }) .toMatchObject({ appearance: { - font: next, + mono: next, }, }) @@ -206,7 +206,7 @@ test("typing a UI font with spaces persists and updates CSS variable", async ({ const dialog = await openSettings(page) const input = dialog.locator(settingsUIFontSelector) await expect(input).toBeVisible() - await expect(input).toHaveAttribute("placeholder", "Inter") + await expect(input).toHaveAttribute("placeholder", "System Sans") const initialFontFamily = await page.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(), @@ -214,7 +214,7 @@ test("typing a UI font with spaces persists and updates CSS variable", async ({ const initialCodeFamily = await page.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(), ) - expect(initialFontFamily).toContain("Inter") + expect(initialFontFamily).toContain("ui-sans-serif") const next = "Test Sans" @@ -232,7 +232,7 @@ test("typing a UI font with spaces persists and updates CSS variable", async ({ }) .toMatchObject({ appearance: { - uiFont: next, + sans: next, }, }) @@ -267,14 +267,14 @@ test("clearing the code font field restores the default placeholder and stack", }) .toMatchObject({ appearance: { - font: "Reset Mono", + mono: "Reset Mono", }, }) await input.clear() await input.press("Space") await expect(input).toHaveValue("") - await expect(input).toHaveAttribute("placeholder", "IBM Plex Mono") + await expect(input).toHaveAttribute("placeholder", "System Mono") await expect .poll(async () => { @@ -285,14 +285,14 @@ test("clearing the code font field restores the default placeholder and stack", }) .toMatchObject({ appearance: { - font: "", + mono: "", }, }) const fontFamily = await page.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(), ) - expect(fontFamily).toContain("IBM Plex Mono") + expect(fontFamily).toContain("ui-monospace") expect(fontFamily).not.toContain("Reset Mono") }) @@ -316,14 +316,14 @@ test("clearing the UI font field restores the default placeholder and stack", as }) .toMatchObject({ appearance: { - uiFont: "Reset Sans", + sans: "Reset Sans", }, }) await input.clear() await input.press("Space") await expect(input).toHaveValue("") - await expect(input).toHaveAttribute("placeholder", "Inter") + await expect(input).toHaveAttribute("placeholder", "System Sans") await expect .poll(async () => { @@ -334,14 +334,14 @@ test("clearing the UI font field restores the default placeholder and stack", as }) .toMatchObject({ appearance: { - uiFont: "", + sans: "", }, }) const fontFamily = await page.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(), ) - expect(fontFamily).toContain("Inter") + expect(fontFamily).toContain("ui-sans-serif") expect(fontFamily).not.toContain("Reset Sans") }) @@ -373,8 +373,8 @@ test("color scheme, code font, and UI font rehydrate after reload", async ({ pag return raw ? JSON.parse(raw) : null }, settingsKey) - const mono = initialSettings?.appearance?.font === "Reload Mono" ? "Reload Mono 2" : "Reload Mono" - const sans = initialSettings?.appearance?.uiFont === "Reload Sans" ? "Reload Sans 2" : "Reload Sans" + const mono = initialSettings?.appearance?.mono === "Reload Mono" ? "Reload Mono 2" : "Reload Mono" + const sans = initialSettings?.appearance?.sans === "Reload Sans" ? "Reload Sans 2" : "Reload Sans" await code.click() await code.clear() @@ -395,8 +395,8 @@ test("color scheme, code font, and UI font rehydrate after reload", async ({ pag }) .toMatchObject({ appearance: { - font: mono, - uiFont: sans, + mono, + sans, }, }) @@ -415,8 +415,8 @@ test("color scheme, code font, and UI font rehydrate after reload", async ({ pag expect(updatedMono).not.toBe(initialMono) expect(updatedSans).toContain(sans) expect(updatedSans).not.toBe(initialSans) - expect(updatedSettings?.appearance?.font).toBe(mono) - expect(updatedSettings?.appearance?.uiFont).toBe(sans) + expect(updatedSettings?.appearance?.mono).toBe(mono) + expect(updatedSettings?.appearance?.sans).toBe(sans) await closeDialog(page, dialog) await page.reload() @@ -432,8 +432,8 @@ test("color scheme, code font, and UI font rehydrate after reload", async ({ pag }) .toMatchObject({ appearance: { - font: mono, - uiFont: sans, + mono, + sans, }, }) @@ -468,8 +468,8 @@ test("color scheme, code font, and UI font rehydrate after reload", async ({ pag expect(rehydratedMono).not.toBe(initialMono) expect(rehydratedSans).toContain(sans) expect(rehydratedSans).not.toBe(initialSans) - expect(rehydratedSettings?.appearance?.font).toBe(mono) - expect(rehydratedSettings?.appearance?.uiFont).toBe(sans) + expect(rehydratedSettings?.appearance?.mono).toBe(mono) + expect(rehydratedSettings?.appearance?.sans).toBe(sans) }) test("toggling notification agent switch updates localStorage", async ({ page, gotoSession }) => { diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index a248ebb944..eb3c582d29 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -47,9 +47,14 @@ import { ErrorPage } from "./pages/error" import { useCheckServerHealth } from "./utils/server-health" const HomeRoute = lazy(() => import("@/pages/home")) -const Session = lazy(() => import("@/pages/session")) +const loadSession = () => import("@/pages/session") +const Session = lazy(loadSession) const Loading = () =>
+if (typeof location === "object" && /\/session(?:\/|$)/.test(location.pathname)) { + void loadSession() +} + const SessionRoute = () => ( @@ -278,7 +283,11 @@ export function AppInterface(props: { disableHealthCheck?: boolean }) { return ( - + diff --git a/packages/app/src/context/global-sdk.tsx b/packages/app/src/context/global-sdk.tsx index 60e9fd6d54..d240f9eeff 100644 --- a/packages/app/src/context/global-sdk.tsx +++ b/packages/app/src/context/global-sdk.tsx @@ -105,6 +105,8 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo const aborted = (error: unknown) => abortError.safeParse(error).success let attempt: AbortController | undefined + let run: Promise | undefined + let started = false const HEARTBEAT_TIMEOUT_MS = 15_000 let lastEventAt = Date.now() let heartbeat: ReturnType | undefined @@ -121,78 +123,93 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo heartbeat = undefined } - void (async () => { - while (!abort.signal.aborted) { - attempt = new AbortController() - lastEventAt = Date.now() - const onAbort = () => { - attempt?.abort() - } - abort.signal.addEventListener("abort", onAbort) - try { - const events = await eventSdk.global.event({ - signal: attempt.signal, - onSseError: (error) => { - if (aborted(error)) return - if (streamErrorLogged) return + const start = () => { + if (started) return run + started = true + run = (async () => { + while (!abort.signal.aborted && started) { + attempt = new AbortController() + lastEventAt = Date.now() + const onAbort = () => { + attempt?.abort() + } + abort.signal.addEventListener("abort", onAbort) + try { + const events = await eventSdk.global.event({ + signal: attempt.signal, + onSseError: (error) => { + if (aborted(error)) return + if (streamErrorLogged) return + streamErrorLogged = true + console.error("[global-sdk] event stream error", { + url: currentServer.http.url, + fetch: eventFetch ? "platform" : "webview", + error, + }) + }, + }) + let yielded = Date.now() + resetHeartbeat() + for await (const event of events.stream) { + resetHeartbeat() + streamErrorLogged = false + const directory = event.directory ?? "global" + const payload = event.payload + const k = key(directory, payload) + if (k) { + const i = coalesced.get(k) + if (i !== undefined) { + queue[i] = { directory, payload } + if (payload.type === "message.part.updated") { + const part = payload.properties.part + staleDeltas.add(deltaKey(directory, part.messageID, part.id)) + } + continue + } + coalesced.set(k, queue.length) + } + queue.push({ directory, payload }) + schedule() + + if (Date.now() - yielded < STREAM_YIELD_MS) continue + yielded = Date.now() + await wait(0) + } + } catch (error) { + if (!aborted(error) && !streamErrorLogged) { streamErrorLogged = true - console.error("[global-sdk] event stream error", { + console.error("[global-sdk] event stream failed", { url: currentServer.http.url, fetch: eventFetch ? "platform" : "webview", error, }) - }, - }) - let yielded = Date.now() - resetHeartbeat() - for await (const event of events.stream) { - resetHeartbeat() - streamErrorLogged = false - const directory = event.directory ?? "global" - const payload = event.payload - const k = key(directory, payload) - if (k) { - const i = coalesced.get(k) - if (i !== undefined) { - queue[i] = { directory, payload } - if (payload.type === "message.part.updated") { - const part = payload.properties.part - staleDeltas.add(deltaKey(directory, part.messageID, part.id)) - } - continue - } - coalesced.set(k, queue.length) } - queue.push({ directory, payload }) - schedule() + } finally { + abort.signal.removeEventListener("abort", onAbort) + attempt = undefined + clearHeartbeat() + } - if (Date.now() - yielded < STREAM_YIELD_MS) continue - yielded = Date.now() - await wait(0) - } - } catch (error) { - if (!aborted(error) && !streamErrorLogged) { - streamErrorLogged = true - console.error("[global-sdk] event stream failed", { - url: currentServer.http.url, - fetch: eventFetch ? "platform" : "webview", - error, - }) - } - } finally { - abort.signal.removeEventListener("abort", onAbort) - attempt = undefined - clearHeartbeat() + if (abort.signal.aborted || !started) return + await wait(RECONNECT_DELAY_MS) } + })().finally(() => { + run = undefined + flush() + }) + return run + } - if (abort.signal.aborted) return - await wait(RECONNECT_DELAY_MS) - } - })().finally(flush) + const stop = () => { + started = false + attempt?.abort() + clearHeartbeat() + } const onVisibility = () => { if (typeof document === "undefined") return if (document.visibilityState !== "visible") return + if (!started) return if (Date.now() - lastEventAt < HEARTBEAT_TIMEOUT_MS) return attempt?.abort() } @@ -204,6 +221,7 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo if (typeof document !== "undefined") { document.removeEventListener("visibilitychange", onVisibility) } + stop() abort.abort() flush() }) @@ -217,7 +235,11 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo return { url: currentServer.http.url, client: sdk, - event: emitter, + event: { + on: emitter.on.bind(emitter), + listen: emitter.listen.bind(emitter), + start, + }, createClient(opts: Omit[0], "server" | "fetch">) { const s = server.current if (!s) throw new Error(language.t("error.globalSDK.serverNotAvailable")) diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index 86ac9b45a0..0cf3570a8b 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -72,10 +72,16 @@ function createGlobalSync() { let projectWritten = false let bootedAt = 0 let bootingRoot = false + let eventFrame: number | undefined + let eventTimer: ReturnType | undefined onCleanup(() => { active = false }) + onCleanup(() => { + if (eventFrame !== undefined) cancelAnimationFrame(eventFrame) + if (eventTimer !== undefined) clearTimeout(eventTimer) + }) const cacheProjects = () => { setProjectCache( @@ -348,6 +354,20 @@ function createGlobalSync() { } onMount(() => { + if (typeof requestAnimationFrame === "function") { + eventFrame = requestAnimationFrame(() => { + eventFrame = undefined + eventTimer = setTimeout(() => { + eventTimer = undefined + globalSDK.event.start() + }, 0) + }) + } else { + eventTimer = setTimeout(() => { + eventTimer = undefined + globalSDK.event.start() + }, 0) + } void bootstrap() }) diff --git a/packages/app/src/context/global-sync/bootstrap.ts b/packages/app/src/context/global-sync/bootstrap.ts index 869f8b7eaa..cf104ad97f 100644 --- a/packages/app/src/context/global-sync/bootstrap.ts +++ b/packages/app/src/context/global-sync/bootstrap.ts @@ -43,8 +43,10 @@ function waitForPaint() { const timer = setTimeout(finish, 50) if (typeof requestAnimationFrame !== "function") return requestAnimationFrame(() => { - clearTimeout(timer) - finish() + setTimeout(() => { + clearTimeout(timer) + finish() + }, 0) }) }) } @@ -87,12 +89,6 @@ export async function bootstrapGlobal(input: { setGlobalStore: SetStoreFunction }) { const fast = [ - () => - retry(() => - input.globalSDK.path.get().then((x) => { - input.setGlobalStore("path", x.data!) - }), - ), () => retry(() => input.globalSDK.global.config.get().then((x) => { @@ -108,6 +104,12 @@ export async function bootstrapGlobal(input: { ] const slow = [ + () => + retry(() => + input.globalSDK.path.get().then((x) => { + input.setGlobalStore("path", x.data!) + }), + ), () => retry(() => input.globalSDK.project.list().then((x) => { @@ -221,12 +223,16 @@ export async function bootstrapDirectory(input: { if (loading) input.setStore("status", "partial") const fast = [ + () => retry(() => input.sdk.app.agents().then((x) => input.setStore("agent", normalizeAgentList(x.data)))), + () => retry(() => input.sdk.config.get().then((x) => input.setStore("config", x.data!))), + () => retry(() => input.sdk.session.status().then((x) => input.setStore("session_status", x.data!))), + ] + + const slow = [ () => 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", normalizeAgentList(x.data)))), - () => retry(() => input.sdk.config.get().then((x) => input.setStore("config", x.data!))), () => seededPath ? Promise.resolve() @@ -237,7 +243,6 @@ export async function bootstrapDirectory(input: { 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) => { @@ -299,9 +304,6 @@ export async function bootstrapDirectory(input: { ) }), ), - ] - - const slow = [ () => Promise.resolve(input.loadSessions(input.directory)), () => retry(() => diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx index 640d5e02eb..aafa4fb66c 100644 --- a/packages/app/src/context/layout.tsx +++ b/packages/app/src/context/layout.tsx @@ -544,12 +544,26 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( } }) + let sessionFrame: number | undefined + let sessionTimer: number | undefined + onMount(() => { - Promise.all( - server.projects.list().map((project) => { - return globalSync.project.loadSessions(project.worktree) - }), - ) + sessionFrame = requestAnimationFrame(() => { + sessionFrame = undefined + sessionTimer = window.setTimeout(() => { + sessionTimer = undefined + void Promise.all( + server.projects.list().map((project) => { + return globalSync.project.loadSessions(project.worktree) + }), + ) + }, 0) + }) + }) + + onCleanup(() => { + if (sessionFrame !== undefined) cancelAnimationFrame(sessionFrame) + if (sessionTimer !== undefined) window.clearTimeout(sessionTimer) }) return { diff --git a/packages/app/src/context/server.tsx b/packages/app/src/context/server.tsx index 1171ca9053..1204fba557 100644 --- a/packages/app/src/context/server.tsx +++ b/packages/app/src/context/server.tsx @@ -94,7 +94,11 @@ export namespace ServerConnection { export const { use: useServer, provider: ServerProvider } = createSimpleContext({ name: "Server", - init: (props: { defaultServer: ServerConnection.Key; servers?: Array }) => { + init: (props: { + defaultServer: ServerConnection.Key + disableHealthCheck?: boolean + servers?: Array + }) => { const checkServerHealth = useCheckServerHealth() const [store, setStore, _, ready] = persisted( @@ -202,6 +206,10 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext( const current_ = current() if (!current_) return + if (props.disableHealthCheck) { + setState("healthy", true) + return + } setState("healthy", undefined) onCleanup(startHealthPolling(current_)) }) diff --git a/packages/app/src/context/settings.tsx b/packages/app/src/context/settings.tsx index 4402855a6e..ae7768f71a 100644 --- a/packages/app/src/context/settings.tsx +++ b/packages/app/src/context/settings.tsx @@ -32,8 +32,8 @@ export interface Settings { } appearance: { fontSize: number - font: string - uiFont: string + mono: string + sans: string } keybinds: Record permissions: { @@ -43,20 +43,18 @@ export interface Settings { sounds: SoundSettings } -export const monoDefault = "IBM Plex Mono" -export const sansDefault = "Inter" +export const monoDefault = "System Mono" +export const sansDefault = "System Sans" const monoFallback = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace' const sansFallback = 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif' -const monoBase = `"${monoDefault}", "IBM Plex Mono Fallback", ${monoFallback}` -const sansBase = `"${sansDefault}", "Inter Fallback", ${sansFallback}` -const monoKey = "ibm-plex-mono" +const monoBase = monoFallback +const sansBase = sansFallback -function input(font: string | undefined, key?: string) { - if (!font || font === key || !font.trim()) return "" - return font +function input(font: string | undefined) { + return font ?? "" } function family(font: string) { @@ -64,14 +62,14 @@ function family(font: string) { return `"${font.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"` } -function stack(font: string | undefined, base: string, key?: string) { - const value = input(font, key).trim() +function stack(font: string | undefined, base: string) { + const value = font?.trim() ?? "" if (!value) return base return `${family(value)}, ${base}` } export function monoInput(font: string | undefined) { - return input(font, monoKey) + return input(font) } export function sansInput(font: string | undefined) { @@ -79,7 +77,7 @@ export function sansInput(font: string | undefined) { } export function monoFontFamily(font: string | undefined) { - return stack(font, monoBase, monoKey) + return stack(font, monoBase) } export function sansFontFamily(font: string | undefined) { @@ -100,8 +98,8 @@ const defaultSettings: Settings = { }, appearance: { fontSize: 14, - font: "", - uiFont: "", + mono: "", + sans: "", }, keybinds: {}, permissions: { @@ -134,8 +132,8 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont createEffect(() => { if (typeof document === "undefined") return const root = document.documentElement - root.style.setProperty("--font-family-mono", monoFontFamily(store.appearance?.font)) - root.style.setProperty("--font-family-sans", sansFontFamily(store.appearance?.uiFont)) + root.style.setProperty("--font-family-mono", monoFontFamily(store.appearance?.mono)) + root.style.setProperty("--font-family-sans", sansFontFamily(store.appearance?.sans)) }) return { @@ -189,13 +187,13 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont setFontSize(value: number) { setStore("appearance", "fontSize", value) }, - font: withFallback(() => store.appearance?.font, defaultSettings.appearance.font), + font: withFallback(() => store.appearance?.mono, defaultSettings.appearance.mono), setFont(value: string) { - setStore("appearance", "font", value.trim() ? value : "") + setStore("appearance", "mono", value.trim() ? value : "") }, - uiFont: withFallback(() => store.appearance?.uiFont, defaultSettings.appearance.uiFont), + uiFont: withFallback(() => store.appearance?.sans, defaultSettings.appearance.sans), setUIFont(value: string) { - setStore("appearance", "uiFont", value.trim() ? value : "") + setStore("appearance", "sans", value.trim() ? value : "") }, }, keybinds: { diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 11e6375b3b..917de35b1f 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -544,6 +544,8 @@ export default function Page() { let reviewFrame: number | undefined let refreshFrame: number | undefined let refreshTimer: number | undefined + let todoFrame: number | undefined + let todoTimer: number | undefined let diffFrame: number | undefined let diffTimer: number | undefined @@ -718,7 +720,6 @@ export default function Page() { if (!info) return true return Date.now() - info.at > SESSION_PREFETCH_TTL })() - const todos = untrack(() => sync.data.todo[id] !== undefined || globalSync.data.session_todo[id] !== undefined) untrack(() => { void sync.session.sync(id) }) @@ -730,13 +731,47 @@ export default function Page() { if (params.id !== id) return untrack(() => { if (stale) void sync.session.sync(id, { force: true }) - void sync.session.todo(id, todos ? { force: true } : undefined) }) }, 0) }) }), ) + createEffect( + on( + () => { + const id = params.id + return [ + sdk.directory, + id, + id ? (sync.data.session_status[id]?.type ?? "idle") : "idle", + id ? composer.blocked() : false, + ] as const + }, + ([dir, id, status, blocked]) => { + if (todoFrame !== undefined) cancelAnimationFrame(todoFrame) + if (todoTimer !== undefined) window.clearTimeout(todoTimer) + todoFrame = undefined + todoTimer = undefined + if (!id) return + if (status === "idle" && !blocked) return + const cached = untrack(() => sync.data.todo[id] !== undefined || globalSync.data.session_todo[id] !== undefined) + + todoFrame = requestAnimationFrame(() => { + todoFrame = undefined + todoTimer = window.setTimeout(() => { + todoTimer = undefined + if (sdk.directory !== dir || params.id !== id) return + untrack(() => { + void sync.session.todo(id, cached ? { force: true } : undefined) + }) + }, 0) + }) + }, + { defer: true }, + ), + ) + createEffect( on( () => visibleUserMessages().at(-1)?.id, @@ -1658,6 +1693,8 @@ export default function Page() { if (reviewFrame !== undefined) cancelAnimationFrame(reviewFrame) if (refreshFrame !== undefined) cancelAnimationFrame(refreshFrame) if (refreshTimer !== undefined) window.clearTimeout(refreshTimer) + if (todoFrame !== undefined) cancelAnimationFrame(todoFrame) + if (todoTimer !== undefined) window.clearTimeout(todoTimer) if (diffFrame !== undefined) cancelAnimationFrame(diffFrame) if (diffTimer !== undefined) window.clearTimeout(diffTimer) if (scrollStateFrame !== undefined) cancelAnimationFrame(scrollStateFrame) diff --git a/packages/ui/src/assets/fonts/BlexMonoNerdFontMono-Bold.woff2 b/packages/ui/src/assets/fonts/BlexMonoNerdFontMono-Bold.woff2 deleted file mode 100644 index b441202d1c..0000000000 Binary files a/packages/ui/src/assets/fonts/BlexMonoNerdFontMono-Bold.woff2 and /dev/null differ diff --git a/packages/ui/src/assets/fonts/BlexMonoNerdFontMono-Medium.woff2 b/packages/ui/src/assets/fonts/BlexMonoNerdFontMono-Medium.woff2 deleted file mode 100644 index d726b57c5c..0000000000 Binary files a/packages/ui/src/assets/fonts/BlexMonoNerdFontMono-Medium.woff2 and /dev/null differ diff --git a/packages/ui/src/assets/fonts/BlexMonoNerdFontMono-Regular.woff2 b/packages/ui/src/assets/fonts/BlexMonoNerdFontMono-Regular.woff2 deleted file mode 100644 index 8c8a38b91b..0000000000 Binary files a/packages/ui/src/assets/fonts/BlexMonoNerdFontMono-Regular.woff2 and /dev/null differ diff --git a/packages/ui/src/assets/fonts/ibm-plex-mono-bold.woff2 b/packages/ui/src/assets/fonts/ibm-plex-mono-bold.woff2 deleted file mode 120000 index f31cff001f..0000000000 --- a/packages/ui/src/assets/fonts/ibm-plex-mono-bold.woff2 +++ /dev/null @@ -1 +0,0 @@ -BlexMonoNerdFontMono-Bold.woff2 \ No newline at end of file diff --git a/packages/ui/src/assets/fonts/ibm-plex-mono-medium.woff2 b/packages/ui/src/assets/fonts/ibm-plex-mono-medium.woff2 deleted file mode 120000 index 50487e3c28..0000000000 --- a/packages/ui/src/assets/fonts/ibm-plex-mono-medium.woff2 +++ /dev/null @@ -1 +0,0 @@ -BlexMonoNerdFontMono-Medium.woff2 \ No newline at end of file diff --git a/packages/ui/src/assets/fonts/ibm-plex-mono.woff2 b/packages/ui/src/assets/fonts/ibm-plex-mono.woff2 deleted file mode 120000 index b47b298530..0000000000 --- a/packages/ui/src/assets/fonts/ibm-plex-mono.woff2 +++ /dev/null @@ -1 +0,0 @@ -BlexMonoNerdFontMono-Regular.woff2 \ No newline at end of file diff --git a/packages/ui/src/assets/fonts/inter.woff2 b/packages/ui/src/assets/fonts/inter.woff2 deleted file mode 100644 index b61bb0d0a5..0000000000 Binary files a/packages/ui/src/assets/fonts/inter.woff2 and /dev/null differ diff --git a/packages/ui/src/components/font.stories.tsx b/packages/ui/src/components/font.stories.tsx index 153a2c8dc9..f4e90bde09 100644 --- a/packages/ui/src/components/font.stories.tsx +++ b/packages/ui/src/components/font.stories.tsx @@ -2,24 +2,24 @@ import * as mod from "./font" const docs = `### Overview -Loads OpenCode typography assets and mono nerd fonts. +Uses native system font stacks for sans and mono typography. -Render once at the app root or Storybook preview. +Optional compatibility component. Existing roots can keep rendering it, but it does nothing. ### API - No props. ### Variants and states -- Fonts include sans and multiple mono families. +- No variants. ### Behavior -- Injects @font-face rules and preload links into the document head. +- Compatibility wrapper only. No font assets are injected or preloaded. ### Accessibility - Not applicable. ### Theming/tokens -- Provides font families used by theme tokens. +- Theme tokens come from CSS variables, not this component. ` diff --git a/packages/ui/src/components/font.tsx b/packages/ui/src/components/font.tsx index e1a508f16a..f89dfafe1d 100644 --- a/packages/ui/src/components/font.tsx +++ b/packages/ui/src/components/font.tsx @@ -1,63 +1 @@ -import { Link, Style } from "@solidjs/meta" -import { Show } from "solid-js" -import inter from "../assets/fonts/inter.woff2" -import ibmPlexMonoBold from "../assets/fonts/ibm-plex-mono-bold.woff2" -import ibmPlexMonoMedium from "../assets/fonts/ibm-plex-mono-medium.woff2" -import ibmPlexMonoRegular from "../assets/fonts/ibm-plex-mono.woff2" - -export const Font = () => { - return ( - <> - - - - - - - ) -} +export const Font = () => null diff --git a/packages/ui/src/styles/theme.css b/packages/ui/src/styles/theme.css index 021f959e4c..751036598d 100644 --- a/packages/ui/src/styles/theme.css +++ b/packages/ui/src/styles/theme.css @@ -1,8 +1,9 @@ :root { - --font-family-sans: "Inter", "Inter Fallback"; - --font-family-sans--font-feature-settings: "ss03" 1; - --font-family-mono: "IBM Plex Mono", "IBM Plex Mono Fallback"; - --font-family-mono--font-feature-settings: "ss01" 1; + --font-family-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --font-family-sans--font-feature-settings: normal; + --font-family-mono: + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --font-family-mono--font-feature-settings: normal; --font-size-small: 13px; --font-size-base: 14px;