fix(app): improve tab handling (#30669)

This commit is contained in:
Brendan Allan
2026-06-05 11:41:30 +08:00
committed by GitHub
parent 75c5d0ab83
commit dfed951ebd
12 changed files with 238 additions and 203 deletions
+27 -37
View File
@@ -1,10 +1,11 @@
import { createStore, produce } from "solid-js/store"
import { batch, createEffect, createMemo, onCleanup, onMount, type Accessor } from "solid-js"
import { useLocation } from "@solidjs/router"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { makeEventListener } from "@solid-primitives/event-listener"
import { useServerSync } from "./server-sync"
import { useServerSDK } from "./server-sdk"
import { useServer } from "./server"
import { ServerConnection, useServer } from "./server"
import { usePlatform } from "./platform"
import { Project } from "@opencode-ai/sdk/v2"
import { Persist, persisted, removePersisted } from "@/utils/persist"
@@ -13,6 +14,9 @@ import { same } from "@/utils/same"
import { createScrollPersistence, type SessionScroll } from "./layout-scroll"
import { createPathHelpers } from "./file/path"
import type { ProjectAvatarVariant } from "@opencode-ai/ui/v2/project-avatar-v2"
import { createSessionKeyReader, ensureSessionKey, pruneSessionKeys } from "./layout-helpers"
export { createSessionKeyReader, ensureSessionKey, pruneSessionKeys }
export type { ProjectAvatarVariant }
@@ -69,42 +73,10 @@ export type LocalProject = Partial<Project> & { worktree: string; expanded: bool
export type ReviewDiffStyle = "unified" | "split"
export function ensureSessionKey(key: string, touch: (key: string) => void, seed: (key: string) => void) {
touch(key)
seed(key)
return key
}
export function createSessionKeyReader(sessionKey: string | Accessor<string>, ensure: (key: string) => void) {
const key = typeof sessionKey === "function" ? sessionKey : () => sessionKey
return () => {
const value = key()
ensure(value)
return value
}
}
export function pruneSessionKeys(input: {
keep?: string
max: number
used: Map<string, number>
view: string[]
tabs: string[]
}) {
if (!input.keep) return []
const keys = new Set<string>([...input.view, ...input.tabs])
if (keys.size <= input.max) return []
const score = (key: string) => {
if (key === input.keep) return Number.MAX_SAFE_INTEGER
return input.used.get(key) ?? 0
}
return Array.from(keys)
.sort((a, b) => score(b) - score(a))
.slice(input.max)
}
export type LayoutRoute =
| { type: "home" }
| { type: "dir-new-sesssion"; dir: string; dirBase64: string; server?: ServerConnection.Key }
| { type: "session"; dir: string; dirBase64: string; sessionId: string; server?: ServerConnection.Key }
function nextSessionTabsForOpen(current: SessionTabs | undefined, tab: string): SessionTabs {
const all = current?.all ?? []
@@ -146,6 +118,21 @@ const normalizeStoredSessionTabs = (key: string, tabs: SessionTabs) => {
}
}
const currentRoute = (pathname: string): LayoutRoute => {
const parts = pathname.split("/").filter(Boolean)
if (parts.length === 0) return { type: "home" }
const dirBase64 = parts[0]
const dir = decode64(dirBase64)
if (!dir) return { type: "home" }
if (parts[1] !== "session") return { type: "home" }
const id = parts[2]
if (id) return { type: "session", dir, dirBase64, sessionId: id }
return { type: "dir-new-sesssion", dir, dirBase64 }
}
export const { use: useLayout, provider: LayoutProvider } = createSimpleContext({
name: "Layout",
init: () => {
@@ -153,6 +140,8 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
const serverSync = useServerSync()
const server = useServer()
const platform = usePlatform()
const location = useLocation()
const route = createMemo(() => currentRoute(location.pathname))
const isRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null && !Array.isArray(value)
@@ -557,6 +546,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
})
return {
route,
ready,
handoff: {
tabs: createMemo(() => store.handoff?.tabs),