Skip to content

Commit ac53806

Browse files
authored
fix(app): repair malformed persisted tabs (#39645)
1 parent c08350b commit ac53806

3 files changed

Lines changed: 48 additions & 7 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { ServerConnection } from "./server"
2+
import type { Tab } from "./tabs"
3+
4+
export function migrateTabs(value: unknown, fallback: ServerConnection.Key): Tab[] {
5+
if (!Array.isArray(value)) return []
6+
return value.flatMap<Tab>((tab) => {
7+
if (!tab || typeof tab !== "object") return []
8+
if ("server" in tab && typeof tab.server !== "string") return []
9+
const server = ("server" in tab ? tab.server : fallback) as ServerConnection.Key
10+
if (tab.type === "session" && typeof tab.sessionId === "string") {
11+
return [{ type: tab.type, server, sessionId: tab.sessionId }]
12+
}
13+
if (
14+
tab.type === "draft" &&
15+
typeof tab.draftID === "string" &&
16+
typeof tab.directory === "string" &&
17+
(tab.worktree === undefined || typeof tab.worktree === "string")
18+
) {
19+
return [{ type: tab.type, server, draftID: tab.draftID, directory: tab.directory, worktree: tab.worktree }]
20+
}
21+
return []
22+
})
23+
}

packages/app/src/context/tabs.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createRoot, getOwner, onCleanup } from "solid-js"
33
import { createTabMemory } from "./tab-memory"
44
import { nextTabAfterClose, pushClosedTab, removeClosedTabs, takeClosedTab, type ClosedTab } from "./closed-tabs"
55
import type { SessionTab, Tab } from "./tabs"
6+
import { migrateTabs } from "./tab-migration"
67
import type { ServerConnection } from "./server"
78

89
const server = "local\nhttp://localhost:4096" as ServerConnection.Key
@@ -11,6 +12,28 @@ function sessionTab(sessionId: string): SessionTab {
1112
return { type: "session", server, sessionId }
1213
}
1314

15+
describe("tab migration", () => {
16+
test("drops null and malformed persisted tabs", () => {
17+
expect(
18+
migrateTabs(
19+
[null, sessionTab("a"), { type: "session", server }, { type: "unknown", server }, "invalid"],
20+
server,
21+
),
22+
).toEqual([sessionTab("a")])
23+
})
24+
25+
test("adds the fallback server to valid legacy tabs", () => {
26+
expect(migrateTabs([{ type: "session", sessionId: "a", dirBase64: "legacy" }], server)).toEqual([
27+
sessionTab("a"),
28+
])
29+
})
30+
31+
test("replaces invalid top-level persisted data", () => {
32+
expect(migrateTabs(null, server)).toEqual([])
33+
expect(migrateTabs({}, server)).toEqual([])
34+
})
35+
})
36+
1437
describe("tab memory", () => {
1538
test("keeps state until its tab is removed", () => {
1639
createRoot((dispose) => {

packages/app/src/context/tabs.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { sessionHref } from "@/utils/session-route"
1212
import { createTabMemory } from "./tab-memory"
1313
import { nextTabAfterClose, pushClosedTab, removeClosedTabs, takeClosedTab, type ClosedTab } from "./closed-tabs"
1414
import { createDraftPromptSession, type PromptModel } from "./prompt-state"
15+
import { migrateTabs } from "./tab-migration"
1516

1617
export type SessionTab = {
1718
type: "session"
@@ -59,13 +60,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
5960
const [store, setStore, _, ready] = persisted(
6061
{
6162
...Persist.window("tabs"),
62-
migrate: (value: unknown) => {
63-
if (!Array.isArray(value)) return value
64-
return value.map((tab) => {
65-
if (!tab || typeof tab !== "object" || "server" in tab) return tab
66-
return { ...tab, server: fallback }
67-
})
68-
},
63+
migrate: (value: unknown) => migrateTabs(value, fallback),
6964
},
7065
createStore<Tab[]>([]),
7166
)

0 commit comments

Comments
 (0)