fix(site): let OpenCode Lite take the keys after DevTools opens mid-load (#62301)

This commit is contained in:
Adam
2026-11-06 01:36:14 -06:00
committed by GitHub
Unverified
parent 76ff9a6ab2
commit 21eaa626a3
2 changed files with 28 additions and 6 deletions
+15 -5
View File
@@ -104,9 +104,19 @@ export function install() {
// One OpenCode Lite at a time. Quitting it brings the greeting back, and stops it from starting on
// its own in this browser (people who debug the site open DevTools all day) until opencode.try().
let lite: Promise<{ stop: () => void }> | undefined
const startLite = () => {
lite ??= textjs<{ start: (options: { onExit: () => void }) => Promise<{ stop: () => void }> }>("lite").then((app) =>
const startLite = (reason?: string) => {
if (lite) return lite
// Lite takes a moment to download, and the visitor may click back into the page from DevTools
// meanwhile. Its keyboard needs to know that focus left the page (see src/lite.tsx). A DevTools
// shortcut counts at once, since DevTools takes focus as it opens.
let away = reason === "shortcut" || !document.hasFocus()
const blur = () => (away = true)
window.addEventListener("blur", blur)
lite = textjs<{
start: (options: { away: () => boolean; onExit: () => void }) => Promise<{ stop: () => void }>
}>("lite").then((app) =>
app.start({
away: () => away,
onExit: () => {
lite = undefined
localStorage.setItem(liteQuitKey, "1")
@@ -121,7 +131,7 @@ export function install() {
},
}),
)
lite.catch(() => (lite = undefined))
lite.finally(() => window.removeEventListener("blur", blur)).catch(() => (lite = undefined))
return lite
}
@@ -287,10 +297,10 @@ export function install() {
// Settings › Other › Console API removes window.opencode when it's turned off, and Lite may have
// been started by hand and quit since this page loaded.
const intent = textjs.onConsoleIntent(
() =>
(e) =>
(window as { opencode?: unknown }).opencode === api &&
!localStorage.getItem(liteQuitKey) &&
void startLite().catch(greetOnce),
void startLite(e.reason).catch(greetOnce),
{ engageSelector: false },
)
if (!intent.triggered) greetOnce()
+13 -1
View File
@@ -14,14 +14,26 @@ import { createDemoClient } from "../.upstream/opencode-console/lib/demo"
// `new URL("@textjs/core/yoga.wasm", import.meta.url)`, so load it from next to the chunks.
declare const __TEXTJS_YOGA__: string
export async function start(options: { onExit?: () => void } = {}) {
export async function start(options: { away?: () => boolean; onExit?: () => void } = {}) {
await initYoga({ wasm: new URL(__TEXTJS_YOGA__, import.meta.url) })
// Text.js 1.6 counts a press as the click back from DevTools only within 300 ms of the window's
// focus event, so it misses a visitor who came back while Lite loaded: their clicks on the page,
// which already has focus, never reached Lite. Replay that focus for their next press, until
// Text.js ships createHostInput({ away }), which does this itself. Registered first, so it runs
// before createHostInput's own listener.
const replay = (e: MouseEvent) => {
if (e.button !== 0 || e.ctrlKey) return
document.removeEventListener("mousedown", replay, true)
window.dispatchEvent(new FocusEvent("focus"))
}
if (options.away?.() && document.hasFocus()) document.addEventListener("mousedown", replay, true)
// Keys reach the app only once the visitor clicks the page to come back from DevTools, and never
// while they type in one of its fields, so the site's own shortcuts keep working until then.
const input = createHostInput()
const stop = () => {
root.unmount()
input.dispose()
document.removeEventListener("mousedown", replay, true)
}
const exit = () => {
stop()