fix(app): repair malformed persisted tabs (#39645)

This commit is contained in:
OpeOginni 2026-07-30 11:29:40 +02:00 committed by GitHub
commit ac5380614f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 48 additions and 7 deletions

View file

@ -0,0 +1,23 @@
import type { ServerConnection } from "./server"
import type { Tab } from "./tabs"
export function migrateTabs(value: unknown, fallback: ServerConnection.Key): Tab[] {
if (!Array.isArray(value)) return []
return value.flatMap<Tab>((tab) => {
if (!tab || typeof tab !== "object") return []
if ("server" in tab && typeof tab.server !== "string") return []
const server = ("server" in tab ? tab.server : fallback) as ServerConnection.Key
if (tab.type === "session" && typeof tab.sessionId === "string") {
return [{ type: tab.type, server, sessionId: tab.sessionId }]
}
if (
tab.type === "draft" &&
typeof tab.draftID === "string" &&
typeof tab.directory === "string" &&
(tab.worktree === undefined || typeof tab.worktree === "string")
) {
return [{ type: tab.type, server, draftID: tab.draftID, directory: tab.directory, worktree: tab.worktree }]
}
return []
})
}

View file

@ -3,6 +3,7 @@ import { createRoot, getOwner, onCleanup } from "solid-js"
import { createTabMemory } from "./tab-memory"
import { nextTabAfterClose, pushClosedTab, removeClosedTabs, takeClosedTab, type ClosedTab } from "./closed-tabs"
import type { SessionTab, Tab } from "./tabs"
import { migrateTabs } from "./tab-migration"
import type { ServerConnection } from "./server"
const server = "local\nhttp://localhost:4096" as ServerConnection.Key
@ -11,6 +12,28 @@ function sessionTab(sessionId: string): SessionTab {
return { type: "session", server, sessionId }
}
describe("tab migration", () => {
test("drops null and malformed persisted tabs", () => {
expect(
migrateTabs(
[null, sessionTab("a"), { type: "session", server }, { type: "unknown", server }, "invalid"],
server,
),
).toEqual([sessionTab("a")])
})
test("adds the fallback server to valid legacy tabs", () => {
expect(migrateTabs([{ type: "session", sessionId: "a", dirBase64: "legacy" }], server)).toEqual([
sessionTab("a"),
])
})
test("replaces invalid top-level persisted data", () => {
expect(migrateTabs(null, server)).toEqual([])
expect(migrateTabs({}, server)).toEqual([])
})
})
describe("tab memory", () => {
test("keeps state until its tab is removed", () => {
createRoot((dispose) => {

View file

@ -12,6 +12,7 @@ import { sessionHref } from "@/utils/session-route"
import { createTabMemory } from "./tab-memory"
import { nextTabAfterClose, pushClosedTab, removeClosedTabs, takeClosedTab, type ClosedTab } from "./closed-tabs"
import { createDraftPromptSession, type PromptModel } from "./prompt-state"
import { migrateTabs } from "./tab-migration"
export type SessionTab = {
type: "session"
@ -59,13 +60,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
const [store, setStore, _, ready] = persisted(
{
...Persist.window("tabs"),
migrate: (value: unknown) => {
if (!Array.isArray(value)) return value
return value.map((tab) => {
if (!tab || typeof tab !== "object" || "server" in tab) return tab
return { ...tab, server: fallback }
})
},
migrate: (value: unknown) => migrateTabs(value, fallback),
},
createStore<Tab[]>([]),
)