fix(app): more startup efficiency (#19454)
parent
82fc493520
commit
f736116967
|
|
@ -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 }) => {
|
||||
|
|
|
|||
|
|
@ -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 = () => <div class="size-full" />
|
||||
|
||||
if (typeof location === "object" && /\/session(?:\/|$)/.test(location.pathname)) {
|
||||
void loadSession()
|
||||
}
|
||||
|
||||
const SessionRoute = () => (
|
||||
<SessionProviders>
|
||||
<Session />
|
||||
|
|
@ -278,7 +283,11 @@ export function AppInterface(props: {
|
|||
disableHealthCheck?: boolean
|
||||
}) {
|
||||
return (
|
||||
<ServerProvider defaultServer={props.defaultServer} servers={props.servers}>
|
||||
<ServerProvider
|
||||
defaultServer={props.defaultServer}
|
||||
disableHealthCheck={props.disableHealthCheck}
|
||||
servers={props.servers}
|
||||
>
|
||||
<ConnectionGate disableHealthCheck={props.disableHealthCheck}>
|
||||
<ServerKey>
|
||||
<GlobalSDKProvider>
|
||||
|
|
|
|||
|
|
@ -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<void> | undefined
|
||||
let started = false
|
||||
const HEARTBEAT_TIMEOUT_MS = 15_000
|
||||
let lastEventAt = Date.now()
|
||||
let heartbeat: ReturnType<typeof setTimeout> | undefined
|
||||
|
|
@ -121,8 +123,11 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
|||
heartbeat = undefined
|
||||
}
|
||||
|
||||
void (async () => {
|
||||
while (!abort.signal.aborted) {
|
||||
const start = () => {
|
||||
if (started) return run
|
||||
started = true
|
||||
run = (async () => {
|
||||
while (!abort.signal.aborted && started) {
|
||||
attempt = new AbortController()
|
||||
lastEventAt = Date.now()
|
||||
const onAbort = () => {
|
||||
|
|
@ -185,14 +190,26 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
|||
clearHeartbeat()
|
||||
}
|
||||
|
||||
if (abort.signal.aborted) return
|
||||
if (abort.signal.aborted || !started) return
|
||||
await wait(RECONNECT_DELAY_MS)
|
||||
}
|
||||
})().finally(flush)
|
||||
})().finally(() => {
|
||||
run = undefined
|
||||
flush()
|
||||
})
|
||||
return run
|
||||
}
|
||||
|
||||
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<Parameters<typeof createSdkForServer>[0], "server" | "fetch">) {
|
||||
const s = server.current
|
||||
if (!s) throw new Error(language.t("error.globalSDK.serverNotAvailable"))
|
||||
|
|
|
|||
|
|
@ -72,10 +72,16 @@ function createGlobalSync() {
|
|||
let projectWritten = false
|
||||
let bootedAt = 0
|
||||
let bootingRoot = false
|
||||
let eventFrame: number | undefined
|
||||
let eventTimer: ReturnType<typeof setTimeout> | 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()
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -43,8 +43,10 @@ function waitForPaint() {
|
|||
const timer = setTimeout(finish, 50)
|
||||
if (typeof requestAnimationFrame !== "function") return
|
||||
requestAnimationFrame(() => {
|
||||
setTimeout(() => {
|
||||
clearTimeout(timer)
|
||||
finish()
|
||||
}, 0)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -87,12 +89,6 @@ export async function bootstrapGlobal(input: {
|
|||
setGlobalStore: SetStoreFunction<GlobalStore>
|
||||
}) {
|
||||
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(() =>
|
||||
|
|
|
|||
|
|
@ -544,12 +544,26 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||
}
|
||||
})
|
||||
|
||||
let sessionFrame: number | undefined
|
||||
let sessionTimer: number | undefined
|
||||
|
||||
onMount(() => {
|
||||
Promise.all(
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -94,7 +94,11 @@ export namespace ServerConnection {
|
|||
|
||||
export const { use: useServer, provider: ServerProvider } = createSimpleContext({
|
||||
name: "Server",
|
||||
init: (props: { defaultServer: ServerConnection.Key; servers?: Array<ServerConnection.Any> }) => {
|
||||
init: (props: {
|
||||
defaultServer: ServerConnection.Key
|
||||
disableHealthCheck?: boolean
|
||||
servers?: Array<ServerConnection.Any>
|
||||
}) => {
|
||||
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_))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ export interface Settings {
|
|||
}
|
||||
appearance: {
|
||||
fontSize: number
|
||||
font: string
|
||||
uiFont: string
|
||||
mono: string
|
||||
sans: string
|
||||
}
|
||||
keybinds: Record<string, string>
|
||||
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: {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
BlexMonoNerdFontMono-Bold.woff2
|
||||
|
|
@ -1 +0,0 @@
|
|||
BlexMonoNerdFontMono-Medium.woff2
|
||||
|
|
@ -1 +0,0 @@
|
|||
BlexMonoNerdFontMono-Regular.woff2
|
||||
Binary file not shown.
|
|
@ -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.
|
||||
|
||||
`
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<>
|
||||
<Style>{`
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
src: url("${inter}") format("woff2-variations");
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter Fallback";
|
||||
src: local("Arial");
|
||||
size-adjust: 100%;
|
||||
ascent-override: 97%;
|
||||
descent-override: 25%;
|
||||
line-gap-override: 1%;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "IBM Plex Mono";
|
||||
src: url("${ibmPlexMonoRegular}") format("woff2");
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "IBM Plex Mono";
|
||||
src: url("${ibmPlexMonoMedium}") format("woff2");
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "IBM Plex Mono";
|
||||
src: url("${ibmPlexMonoBold}") format("woff2");
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "IBM Plex Mono Fallback";
|
||||
src: local("Courier New");
|
||||
size-adjust: 100%;
|
||||
ascent-override: 97%;
|
||||
descent-override: 25%;
|
||||
line-gap-override: 1%;
|
||||
}
|
||||
`}</Style>
|
||||
<Show when={typeof location === "undefined" || location.protocol !== "file:"}>
|
||||
<Link rel="preload" href={inter} as="font" type="font/woff2" crossorigin="anonymous" />
|
||||
<Link rel="preload" href={ibmPlexMonoRegular} as="font" type="font/woff2" crossorigin="anonymous" />
|
||||
</Show>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export const Font = () => null
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue