瀏覽代碼

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

OpeOginni 2 周之前
父節點
當前提交
ac5380614f
共有 3 個文件被更改,包括 48 次插入7 次删除
  1. 23 0
      packages/app/src/context/tab-migration.ts
  2. 23 0
      packages/app/src/context/tabs.test.ts
  3. 2 7
      packages/app/src/context/tabs.tsx

+ 23 - 0
packages/app/src/context/tab-migration.ts

@@ -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 []
+  })
+}

+ 23 - 0
packages/app/src/context/tabs.test.ts

@@ -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) => {

+ 2 - 7
packages/app/src/context/tabs.tsx

@@ -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[]>([]),
     )