This commit is contained in:
LukeParkerDev
2026-04-28 08:02:30 +10:00
parent ed4a41f1e0
commit 81e907febe
7 changed files with 40 additions and 126 deletions

View File

@@ -59,7 +59,6 @@ interface ServerFormProps {
placeholder: string
busy: boolean
error: string
status: boolean | undefined
onChange: (value: string) => void
onNameChange: (value: string) => void
onUsernameChange: (value: string) => void
@@ -80,38 +79,6 @@ function isWslSidecar(conn: ServerConnection.Any): conn is ServerConnection.Side
return conn.type === "sidecar" && conn.variant === "wsl"
}
function useServerPreview() {
const checkServerHealth = useCheckServerHealth()
const looksComplete = (value: string) => {
const normalized = normalizeServerUrl(value)
if (!normalized) return false
const host = normalized.replace(/^https?:\/\//, "").split("/")[0]
if (!host) return false
if (host.includes("localhost") || host.startsWith("127.0.0.1")) return true
return host.includes(".") || host.includes(":")
}
const previewStatus = async (
value: string,
username: string,
password: string,
setStatus: (value: boolean | undefined) => void,
) => {
setStatus(undefined)
if (!looksComplete(value)) return
const normalized = normalizeServerUrl(value)
if (!normalized) return
const http: ServerConnection.HttpBase = { url: normalized }
if (username) http.username = username
if (password) http.password = password
const result = await checkServerHealth(http)
setStatus(result.healthy)
}
return { previewStatus }
}
function ServerForm(props: ServerFormProps) {
const language = useLanguage()
const keyDown = (event: KeyboardEvent) => {
@@ -184,7 +151,6 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) {
const language = useLanguage()
const { defaultKey, canDefault, setDefault } = useDefaultServer()
const wslServers = useWslServers()
const { previewStatus } = useServerPreview()
const checkServerHealth = useCheckServerHealth()
let disposed = false
onCleanup(() => {
@@ -199,7 +165,6 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) {
password: "",
error: "",
showForm: false,
status: undefined as boolean | undefined,
},
addWsl: {
showWizard: props.initialView === "add-wsl",
@@ -212,7 +177,6 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) {
username: "",
password: "",
error: "",
status: undefined as boolean | undefined,
},
})
@@ -224,7 +188,6 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) {
password: "",
error: "",
showForm: false,
status: undefined,
})
}
const resetEdit = () => {
@@ -235,7 +198,6 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) {
username: "",
password: "",
error: "",
status: undefined,
})
}
@@ -417,10 +379,6 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) {
return wslState()?.opencodeChecks[conn.distro] ?? null
}
const displayVersion = (conn: ServerConnection.Any) => {
return wslCheck(conn)?.version ?? undefined
}
async function select(conn: ServerConnection.Any, persist?: boolean) {
if (!isSelectable(conn)) return
if (!persist && health(ServerConnection.key(conn))?.healthy === false) return
@@ -470,9 +428,6 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) {
const handleAddChange = (value: string) => {
if (addMutation.isPending) return
setStore("addServer", { url: value, error: "" })
void previewStatus(value, store.addServer.username, store.addServer.password, (next) =>
setStore("addServer", { status: next }),
)
}
const handleAddNameChange = (value: string) => {
@@ -483,25 +438,16 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) {
const handleAddUsernameChange = (value: string) => {
if (addMutation.isPending) return
setStore("addServer", { username: value, error: "" })
void previewStatus(store.addServer.url, value, store.addServer.password, (next) =>
setStore("addServer", { status: next }),
)
}
const handleAddPasswordChange = (value: string) => {
if (addMutation.isPending) return
setStore("addServer", { password: value, error: "" })
void previewStatus(store.addServer.url, store.addServer.username, value, (next) =>
setStore("addServer", { status: next }),
)
}
const handleEditChange = (value: string) => {
if (editMutation.isPending) return
setStore("editServer", { value, error: "" })
void previewStatus(value, store.editServer.username, store.editServer.password, (next) =>
setStore("editServer", { status: next }),
)
}
const handleEditNameChange = (value: string) => {
@@ -512,17 +458,11 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) {
const handleEditUsernameChange = (value: string) => {
if (editMutation.isPending) return
setStore("editServer", { username: value, error: "" })
void previewStatus(store.editServer.value, value, store.editServer.password, (next) =>
setStore("editServer", { status: next }),
)
}
const handleEditPasswordChange = (value: string) => {
if (editMutation.isPending) return
setStore("editServer", { password: value, error: "" })
void previewStatus(store.editServer.value, store.editServer.username, value, (next) =>
setStore("editServer", { status: next }),
)
}
const mode = createMemo<"list" | "add-wsl" | "add" | "edit">(() => {
@@ -554,7 +494,6 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) {
username: DEFAULT_USERNAME,
password: "",
error: "",
status: undefined,
})
}
@@ -568,7 +507,6 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) {
username: conn.http.username ?? "",
password: conn.http.password ?? "",
error: "",
status: health(ServerConnection.key(conn))?.healthy,
})
}
@@ -674,7 +612,6 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) {
placeholder={language.t("dialog.server.add.placeholder")}
busy={formBusy()}
error={isAddMode() ? store.addServer.error : store.editServer.error}
status={isAddMode() ? store.addServer.status : store.editServer.status}
onChange={isAddMode() ? handleAddChange : handleEditChange}
onNameChange={isAddMode() ? handleAddNameChange : handleEditNameChange}
onUsernameChange={isAddMode() ? handleAddUsernameChange : handleEditUsernameChange}
@@ -735,7 +672,7 @@ export function DialogSelectServer(props: DialogSelectServerProps = {}) {
conn={i}
dimmed={blocked()}
status={health(key)}
version={displayVersion(i)}
version={wslCheck(i)?.version ?? undefined}
class="flex items-center gap-3 min-w-0 flex-1"
badge={
<Show when={defaultKey() === ServerConnection.key(i)}>

View File

@@ -51,14 +51,13 @@ export function DialogWslServer(props: DialogWslServerProps = {}) {
disposed = true
})
const busy = createMemo(() => !!current()?.job || store.adding)
const selectedDistro = () => store.selectedDistro
const selectedProbe = createMemo(() => {
const distro = selectedDistro()
const distro = store.selectedDistro
if (!distro) return null
return current()?.distroProbes[distro] ?? null
})
const selectedInstalled = createMemo(() => {
const distro = selectedDistro()
const distro = store.selectedDistro
if (!distro) return null
return (current()?.installed ?? []).find((item) => item.name === distro) ?? null
})
@@ -68,7 +67,7 @@ export function DialogWslServer(props: DialogWslServerProps = {}) {
const visibleOnlineDistros = createMemo(() => (current()?.online ?? []).filter((item) => !isHiddenDistro(item.name)))
const defaultInstalledDistro = createMemo(() => visibleInstalledDistros().find((item) => item.isDefault) ?? null)
const opencodeCheck = createMemo(() => {
const distro = selectedDistro()
const distro = store.selectedDistro
if (!distro) return null
return current()?.opencodeChecks[distro] ?? null
})
@@ -80,7 +79,7 @@ export function DialogWslServer(props: DialogWslServerProps = {}) {
})
const distroUnavailableMessage = createMemo(() => {
const probe = distroWarningProbe()
const distro = selectedDistro()
const distro = store.selectedDistro
if (!probe || probe.canExecute || !distro) return null
if (!selectedInstalled()) return `${distro} is not installed yet.`
return `Open ${distro} once to finish setup.`
@@ -91,10 +90,6 @@ export function DialogWslServer(props: DialogWslServerProps = {}) {
if (probe.hasBash && probe.hasCurl) return null
return probe
})
const opencodeMismatchCheck = createMemo(() => {
const check = opencodeCheck()
return check?.matchesDesktop === false ? check : null
})
const existingServerDistros = createMemo(() => new Set((current()?.servers ?? []).map((item) => item.config.distro)))
const addableInstalledDistros = createMemo(() => {
return visibleInstalledDistros().filter((item) => !existingServerDistros().has(item.name))
@@ -121,7 +116,7 @@ export function DialogWslServer(props: DialogWslServerProps = {}) {
const wslReady = createMemo(() => !!current()?.runtime?.available && !current()?.pendingRestart)
const distroReady = createMemo(() => {
const probe = selectedProbe()
if (!probe || !selectedDistro()) return false
if (!probe || !store.selectedDistro) return false
if (selectedInstalled()?.version === 1) return false
return probe.canExecute && probe.hasBash && probe.hasCurl
})
@@ -185,7 +180,7 @@ export function DialogWslServer(props: DialogWslServerProps = {}) {
if (!state.installed.length && !state.online.length) {
return { key: "distros", run: () => refreshDistrosMutation.mutateAsync() }
}
const distro = selectedDistro()
const distro = store.selectedDistro
if (distro && !state.distroProbes[distro]) {
return { key: `probe-distro:${distro}`, run: () => probeDistroMutation.mutateAsync(distro) }
}
@@ -220,7 +215,7 @@ export function DialogWslServer(props: DialogWslServerProps = {}) {
const state = current()
const distro = defaultInstalledDistro()
if (!state || !distro || busy()) return
if (selectedDistro()) return
if (store.selectedDistro) return
if (existingServerDistros().has(distro.name)) return
setStore("selectedDistro", distro.name)
})
@@ -246,7 +241,7 @@ export function DialogWslServer(props: DialogWslServerProps = {}) {
const distroMessage = createMemo(() => {
const state = current()
if (!state) return "Checking distros..."
const distro = selectedDistro()
const distro = store.selectedDistro
if (state.job?.kind === "install-distro") return `Installing ${state.job.distro}...`
if (state.job?.kind === "probe-distro") return `Checking ${state.job.distro}...`
if (state.job?.kind === "distros") return "Listing distros..."
@@ -259,7 +254,7 @@ export function DialogWslServer(props: DialogWslServerProps = {}) {
const opencodeMessage = createMemo(() => {
const state = current()
if (!state) return "Checking OpenCode..."
const distro = selectedDistro()
const distro = store.selectedDistro
if (state.job?.kind === "probe-opencode" || state.job?.kind === "install-opencode") {
return distro ? `Checking OpenCode in ${distro}...` : "Checking OpenCode..."
}
@@ -292,7 +287,7 @@ export function DialogWslServer(props: DialogWslServerProps = {}) {
}
const runSelectedDistro = (action: (distro: string) => Promise<unknown>) => {
const distro = selectedDistro()
const distro = store.selectedDistro
if (!distro) return
void run(() => action(distro))
}
@@ -303,7 +298,7 @@ export function DialogWslServer(props: DialogWslServerProps = {}) {
}
const finish = async () => {
const distro = selectedDistro()
const distro = store.selectedDistro
if (!distro) return
setStore("adding", true)
try {
@@ -402,7 +397,7 @@ export function DialogWslServer(props: DialogWslServerProps = {}) {
<div class="rounded-md bg-surface-base p-4 flex flex-col gap-3">
<div class="flex items-center justify-between gap-3">
<div class="text-14-medium text-text-strong">Choose a distro</div>
<Show when={selectedDistro()}>
<Show when={store.selectedDistro}>
<Button
variant="ghost"
size="small"
@@ -433,7 +428,7 @@ export function DialogWslServer(props: DialogWslServerProps = {}) {
<button
type="button"
class="rounded-md border border-border-weak-base px-3 py-2 text-left transition-colors"
classList={{ "bg-surface-raised-base": selectedDistro() === item.name }}
classList={{ "bg-surface-raised-base": store.selectedDistro === item.name }}
onClick={() => selectDistro(item.name)}
>
<div class="text-13-medium text-text-strong">{item.name}</div>
@@ -543,7 +538,7 @@ export function DialogWslServer(props: DialogWslServerProps = {}) {
<Button
variant="ghost"
size="large"
disabled={busy() || !selectedDistro()}
disabled={busy() || !store.selectedDistro}
onClick={() => runSelectedDistro((distro) => probeDistroMutation.mutateAsync(distro))}
>
Refresh
@@ -554,7 +549,7 @@ export function DialogWslServer(props: DialogWslServerProps = {}) {
<Button
variant="secondary"
size="large"
disabled={busy() || !selectedDistro() || !distroReady()}
disabled={busy() || !store.selectedDistro || !distroReady()}
onClick={() => setStore("step", "opencode")}
>
Next
@@ -568,7 +563,7 @@ export function DialogWslServer(props: DialogWslServerProps = {}) {
<div class="flex items-center justify-between gap-3">
<div class="text-14-medium text-text-strong">OpenCode</div>
<div class="flex items-center gap-2">
<Show when={selectedDistro()}>
<Show when={store.selectedDistro}>
<Button
variant="ghost"
size="large"
@@ -591,7 +586,7 @@ export function DialogWslServer(props: DialogWslServerProps = {}) {
</div>
</div>
<div class="text-12-regular text-text-weak whitespace-pre-wrap break-words">{opencodeMessage()}</div>
<Show when={opencodeMismatchCheck()}>
<Show when={opencodeCheck()?.matchesDesktop === false ? opencodeCheck() : null}>
{(check) => (
<div class="rounded-md border border-border-weak-base px-3 py-3 flex flex-col gap-1">
<div class="text-12-regular text-text-weak">Path: {check().resolvedPath ?? "not found"}</div>
@@ -652,7 +647,7 @@ export function DialogWslServer(props: DialogWslServerProps = {}) {
</div>
</Show>
<Show when={activeStep() === "opencode" && allReady() && selectedDistro()}>
<Show when={activeStep() === "opencode" && allReady() && store.selectedDistro}>
<div class="flex items-center justify-end gap-2">
<Button variant="ghost" size="large" disabled={store.adding} onClick={() => dialog.close()}>
Cancel

View File

@@ -176,7 +176,6 @@ function setInitStep(step: InitStep) {
async function initialize() {
const needsMigration = !sqliteFileExists()
const sqliteDone = needsMigration ? defer<void>() : undefined
let overlay: BrowserWindow | null = null
const port = await allocatePort()
@@ -203,7 +202,6 @@ async function initialize() {
setInitStep({ phase: "sqlite_waiting" })
if (overlay) sendSqliteMigrationProgress(overlay, progress)
if (mainWindow) sendSqliteMigrationProgress(mainWindow, progress)
if (progress.type === "Done") sqliteDone?.resolve()
})
if (needsMigration) {
@@ -215,12 +213,6 @@ async function initialize() {
},
})
initEmitter.emit("sqlite", { type: "Done" })
sqliteDone?.resolve()
}
if (needsMigration) {
await sqliteDone?.promise
}
logger.log("spawning windows sidecar", { url })

View File

@@ -14,7 +14,6 @@ import type {
WslTranscriptLine,
} from "../preload/types"
import { LEGACY_LOCAL_SERVER_KEY, WSL_SERVERS_KEY } from "./constants"
import { spawnWslSidecar } from "./server"
import { getStore } from "./store"
import type { WslCommandLine } from "./wsl"
import {
@@ -412,10 +411,9 @@ export function createWslServersController(appVersion: string, spawnSidecar: Spa
stopAll() {
for (const item of state.servers) invalidateStartAttempt(item.config.id)
for (const [id] of sidecars) {
const existing = sidecars.get(id)
for (const existing of sidecars.values()) {
try {
existing?.listener.stop()
existing.listener.stop()
} catch {
// ignore
}

View File

@@ -436,11 +436,7 @@ render(() => {
}
})
const [defaultServer] = createResource(() =>
platform.getDefaultServer?.().then((url) => {
if (url) return ServerConnection.Key.make(url)
}),
)
const [defaultServer] = createResource(() => platform.getDefaultServer?.())
const [locale] = createResource(loadLocale)
const [storedServers] = createResource(async () => {
const raw = await platform.storage?.("opencode.global.dat").getItem("server")

View File

@@ -432,11 +432,7 @@ render(() => {
// Fetch sidecar credentials from Rust (available immediately, before health check)
const [sidecar] = createResource(() => commands.awaitInitialization(new Channel<InitStep>() as any))
const [defaultServer] = createResource(() =>
platform.getDefaultServer?.().then((url) => {
if (url) return ServerConnection.key({ type: "http", http: { url } })
}),
)
const [defaultServer] = createResource(() => platform.getDefaultServer?.())
const [locale] = createResource(loadLocale)
// Build the sidecar server connection once credentials arrive

View File

@@ -29,33 +29,33 @@ const Context = createContext<ReturnType<typeof init>>()
function init() {
const [active, setActive] = createSignal<Active | undefined>()
const timer = { current: undefined as ReturnType<typeof setTimeout> | undefined }
const lock = { value: false }
let timer: ReturnType<typeof setTimeout> | undefined
let locked = false
onCleanup(() => {
if (timer.current === undefined) return
clearTimeout(timer.current)
timer.current = undefined
if (timer === undefined) return
clearTimeout(timer)
timer = undefined
})
const close = () => {
const current = active()
if (!current || lock.value) return
lock.value = true
if (!current || locked) return
locked = true
current.onClose?.()
current.setClosing(true)
const id = current.id
if (timer.current !== undefined) {
clearTimeout(timer.current)
timer.current = undefined
if (timer !== undefined) {
clearTimeout(timer)
timer = undefined
}
timer.current = setTimeout(() => {
timer.current = undefined
timer = setTimeout(() => {
timer = undefined
current.dispose()
if (active()?.id === id) setActive(undefined)
lock.value = false
locked = false
}, 100)
}
@@ -80,11 +80,11 @@ function init() {
setActive(undefined)
}
if (timer.current !== undefined) {
clearTimeout(timer.current)
timer.current = undefined
if (timer !== undefined) {
clearTimeout(timer)
timer = undefined
}
lock.value = false
locked = false
const id = Math.random().toString(36).slice(2)
let dispose: (() => void) | undefined