diff --git a/app-prefixable/src/app.tsx b/app-prefixable/src/app.tsx
index f82d1c07..9cf9504e 100644
--- a/app-prefixable/src/app.tsx
+++ b/app-prefixable/src/app.tsx
@@ -1,5 +1,5 @@
import { Router, Route, Navigate, useParams } from "@solidjs/router"
-import { createSignal, onMount, onCleanup, For } from "solid-js"
+import { createSignal, onMount, onCleanup, For, type Accessor } from "solid-js"
import { BasePathProvider, useBasePath } from "./context/base-path"
import { ServerProvider, useServer } from "./context/server"
import { BrandingProvider } from "./context/branding"
@@ -33,7 +33,7 @@ function getLastSessionHref(encodedDir: string): string {
function DirectoryIndex() {
const params = useParams<{ dir: string }>()
- return
+ return
}
function SessionIndex() {
@@ -41,7 +41,7 @@ function SessionIndex() {
const href = getLastSessionHref(params.dir)
if (href === "session") return
const id = href.replace(/^session\//, "")
- return
+ return
}
function AppRoutes() {
@@ -71,16 +71,21 @@ function AppRoutes() {
* Reads the active directory from window.location (outside Router context).
* Re-evaluates on popstate and on history.pushState/history.replaceState navigation.
*/
-function useActiveDirectory() {
+function useRouteState() {
const [dir, setDir] = createSignal(
typeof window === "undefined" ? undefined : deriveDirectoryFromPathname(),
)
+ const [pathname, setPathname] = createSignal(
+ typeof window === "undefined" ? "" : window.location.pathname,
+ )
onMount(() => {
- // Ensure correct value once mounted (covers SSR hydration)
- setDir(deriveDirectoryFromPathname())
+ function update() {
+ setDir(deriveDirectoryFromPathname())
+ setPathname(window.location.pathname)
+ }
- function update() { setDir(deriveDirectoryFromPathname()) }
+ update()
// Patch pushState/replaceState to detect SolidJS Router navigations
// instead of polling with setInterval
@@ -97,7 +102,10 @@ function useActiveDirectory() {
})
})
- return dir
+ return {
+ activeDirectory: dir,
+ pathname,
+ }
}
function useProjectsList() {
@@ -129,7 +137,11 @@ function useProjectsList() {
return projects
}
-function AppWithServer(props: { projects: () => Project[]; activeDirectory: () => string | undefined }) {
+function AppWithServer(props: {
+ projects: () => Project[]
+ activeDirectory: Accessor
+ pathname: Accessor
+}) {
const { serverUrl, activeServerKey } = useServer()
// Key by server config to force full remount when switching or editing servers
@@ -141,7 +153,11 @@ function AppWithServer(props: { projects: () => Project[]; activeDirectory: () =
-
+
@@ -158,11 +174,15 @@ function AppWithServer(props: { projects: () => Project[]; activeDirectory: () =
export function App() {
const projects = useProjectsList()
- const activeDirectory = useActiveDirectory()
+ const route = useRouteState()
return (
-
+
)
}
diff --git a/app-prefixable/src/context/global-events.tsx b/app-prefixable/src/context/global-events.tsx
index 8565e38f..7a23e643 100644
--- a/app-prefixable/src/context/global-events.tsx
+++ b/app-prefixable/src/context/global-events.tsx
@@ -44,6 +44,7 @@ const GlobalEventsContext = createContext()
export function GlobalEventsProvider(props: ParentProps & {
projects: () => { worktree: string }[]
activeDirectory: () => string | undefined
+ pathname: () => string
}) {
const { authHeaders, serverUrl, activeServer } = useServer()
@@ -458,11 +459,18 @@ export function GlobalEventsProvider(props: ParentProps & {
// Active project is excluded — it has its own EventProvider.
// Remote servers are skipped — local projects don't exist on remote servers.
createEffect(on(
- () => ({ dirs: props.projects().map((p) => p.worktree), active: props.activeDirectory(), isRemote: !activeServer().isDefault }),
+ () => ({
+ dirs: props.projects().map((p) => p.worktree),
+ active: props.activeDirectory(),
+ isRemote: !activeServer().isDefault,
+ isSettings: props.pathname().endsWith("/settings"),
+ }),
(current) => {
// When a remote server is active, disconnect all global event connections
// since local projects don't exist on the remote server.
- if (current.isRemote) {
+ // Also suspend these background connections on settings routes to avoid
+ // opening one SSE stream per saved project while the user edits settings.
+ if (current.isRemote || current.isSettings) {
for (const dir of [...connections.keys()]) {
disconnectDirectory(dir)
}
diff --git a/app-prefixable/src/context/server.tsx b/app-prefixable/src/context/server.tsx
index 278748a1..06ed3662 100644
--- a/app-prefixable/src/context/server.tsx
+++ b/app-prefixable/src/context/server.tsx
@@ -75,7 +75,7 @@ function loadServers(): ServerConfig[] {
const parsed: ServerConfig[] = raw
.filter(isValidServerEntry)
.map((s) => {
- const obj = s as Record
+ const obj = s as unknown as Record
// Prefer credentials from sessionStorage; fall back to authMethod stub
if (creds[obj.id as string]) {
return { ...s, auth: normalizeAuth({ auth: creds[obj.id as string] } as unknown as Record) }
diff --git a/app-prefixable/src/context/theme.tsx b/app-prefixable/src/context/theme.tsx
index 88e216aa..9b640022 100644
--- a/app-prefixable/src/context/theme.tsx
+++ b/app-prefixable/src/context/theme.tsx
@@ -40,6 +40,10 @@ export function ThemeProvider(props: ParentProps) {
const query = typeof window !== "undefined" && typeof window.matchMedia === "function"
? window.matchMedia("(prefers-color-scheme: dark)")
: undefined
+ const legacyQuery = query as (MediaQueryList & {
+ addListener?: (listener: (event: MediaQueryListEvent) => void) => void
+ removeListener?: (listener: (event: MediaQueryListEvent) => void) => void
+ }) | undefined
const [systemDark, setSystemDark] = createSignal(query?.matches ?? false)
@@ -48,9 +52,9 @@ export function ThemeProvider(props: ParentProps) {
if ("addEventListener" in query) {
query.addEventListener("change", handler)
onCleanup(() => query.removeEventListener("change", handler))
- } else if ("addListener" in query) {
- query.addListener(handler)
- onCleanup(() => query.removeListener(handler))
+ } else if (legacyQuery?.addListener && legacyQuery.removeListener) {
+ legacyQuery.addListener(handler)
+ onCleanup(() => legacyQuery.removeListener?.(handler))
}
}
diff --git a/app-prefixable/src/entry.tsx b/app-prefixable/src/entry.tsx
index b8a89b45..cfbad27a 100644
--- a/app-prefixable/src/entry.tsx
+++ b/app-prefixable/src/entry.tsx
@@ -11,8 +11,10 @@ if (!root) {
throw new Error("Root element not found")
}
+const mount = root
+
// Clear the loading text first
-root.innerHTML = ""
+mount.innerHTML = ""
async function start() {
const cleanup = await preventLegacyServiceWorkerCaching().catch((e) => {
@@ -27,17 +29,17 @@ async function start() {
}
console.log("[OpenCode] Rendering...")
- render(() => , root)
+ render(() => , mount)
console.log("[OpenCode] Rendered successfully")
- console.log("[OpenCode] Root innerHTML:", root.innerHTML.slice(0, 200))
+ console.log("[OpenCode] Root innerHTML:", mount.innerHTML.slice(0, 200))
}
start().catch((e) => {
console.error("[OpenCode] Render error:", e)
- root.innerHTML = ""
+ mount.innerHTML = ""
const message = document.createElement("div")
message.style.color = "red"
message.style.padding = "20px"
message.textContent = `Error: ${e instanceof Error && e.message.trim() ? e.message : String(e)}`
- root.appendChild(message)
+ mount.appendChild(message)
})
diff --git a/app-prefixable/src/pages/layout.tsx b/app-prefixable/src/pages/layout.tsx
index d7eea7a7..a7f13142 100644
--- a/app-prefixable/src/pages/layout.tsx
+++ b/app-prefixable/src/pages/layout.tsx
@@ -878,14 +878,16 @@ export function Layout(props: ParentProps) {
const [now, setNow] = createSignal(new Date());
onMount(() => {
- const timer = { id: 0 as ReturnType };
+ let timer: ReturnType | undefined;
const schedule = () => {
const next = new Date();
next.setHours(24, 0, 0, 0);
- timer.id = setTimeout(() => { setNow(new Date()); schedule(); }, next.getTime() - Date.now());
+ timer = setTimeout(() => { setNow(new Date()); schedule(); }, next.getTime() - Date.now());
};
schedule();
- onCleanup(() => clearTimeout(timer.id));
+ onCleanup(() => {
+ if (timer) clearTimeout(timer);
+ });
});
const pinnedSessions = createMemo(() => {
diff --git a/shared/proxy.ts b/shared/proxy.ts
index cb0c4558..9c11d7bb 100644
--- a/shared/proxy.ts
+++ b/shared/proxy.ts
@@ -115,7 +115,11 @@ export async function handleProxyRequest(path: string, req: Request): Promise