fix(app): repair malformed persisted tabs (#39645)
This commit is contained in:
@@ -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 []
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { createRoot, getOwner, onCleanup } from "solid-js"
|
|||||||
import { createTabMemory } from "./tab-memory"
|
import { createTabMemory } from "./tab-memory"
|
||||||
import { nextTabAfterClose, pushClosedTab, removeClosedTabs, takeClosedTab, type ClosedTab } from "./closed-tabs"
|
import { nextTabAfterClose, pushClosedTab, removeClosedTabs, takeClosedTab, type ClosedTab } from "./closed-tabs"
|
||||||
import type { SessionTab, Tab } from "./tabs"
|
import type { SessionTab, Tab } from "./tabs"
|
||||||
|
import { migrateTabs } from "./tab-migration"
|
||||||
import type { ServerConnection } from "./server"
|
import type { ServerConnection } from "./server"
|
||||||
|
|
||||||
const server = "local\nhttp://localhost:4096" as ServerConnection.Key
|
const server = "local\nhttp://localhost:4096" as ServerConnection.Key
|
||||||
@@ -11,6 +12,28 @@ function sessionTab(sessionId: string): SessionTab {
|
|||||||
return { type: "session", server, sessionId }
|
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", () => {
|
describe("tab memory", () => {
|
||||||
test("keeps state until its tab is removed", () => {
|
test("keeps state until its tab is removed", () => {
|
||||||
createRoot((dispose) => {
|
createRoot((dispose) => {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { sessionHref } from "@/utils/session-route"
|
|||||||
import { createTabMemory } from "./tab-memory"
|
import { createTabMemory } from "./tab-memory"
|
||||||
import { nextTabAfterClose, pushClosedTab, removeClosedTabs, takeClosedTab, type ClosedTab } from "./closed-tabs"
|
import { nextTabAfterClose, pushClosedTab, removeClosedTabs, takeClosedTab, type ClosedTab } from "./closed-tabs"
|
||||||
import { createDraftPromptSession, type PromptModel } from "./prompt-state"
|
import { createDraftPromptSession, type PromptModel } from "./prompt-state"
|
||||||
|
import { migrateTabs } from "./tab-migration"
|
||||||
|
|
||||||
export type SessionTab = {
|
export type SessionTab = {
|
||||||
type: "session"
|
type: "session"
|
||||||
@@ -59,13 +60,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||||||
const [store, setStore, _, ready] = persisted(
|
const [store, setStore, _, ready] = persisted(
|
||||||
{
|
{
|
||||||
...Persist.window("tabs"),
|
...Persist.window("tabs"),
|
||||||
migrate: (value: unknown) => {
|
migrate: (value: unknown) => migrateTabs(value, fallback),
|
||||||
if (!Array.isArray(value)) return value
|
|
||||||
return value.map((tab) => {
|
|
||||||
if (!tab || typeof tab !== "object" || "server" in tab) return tab
|
|
||||||
return { ...tab, server: fallback }
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
createStore<Tab[]>([]),
|
createStore<Tab[]>([]),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user