refactor: collapse migrations

This commit is contained in:
Alejandro Gómez
2025-12-18 16:49:24 +01:00
parent f283ef6208
commit f6f813d382
9 changed files with 52 additions and 201 deletions

View File

@@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest";
import { migrateState, validateState, CURRENT_VERSION } from "./migrations";
describe("migrations", () => {
describe("v6 to v9 migration (v6→v7→v8→v9)", () => {
describe("v6 to v8 migration (v6→v7→v8)", () => {
it("should convert numeric labels to number field and add global layoutConfig", () => {
const oldState = {
__version: 6,
@@ -26,7 +26,7 @@ describe("migrations", () => {
const migrated = migrateState(oldState);
// Should migrate to v9
// Should migrate to v8
expect(migrated.__version).toBe(CURRENT_VERSION);
// v6→v7: numeric labels converted to number
@@ -35,14 +35,8 @@ describe("migrations", () => {
expect(migrated.workspaces.ws2.number).toBe(2);
expect(migrated.workspaces.ws2.label).toBeUndefined();
// v7→v8→v9: layoutConfig added to each workspace
expect(migrated.workspaces.ws1.layoutConfig).toEqual({
insertionMode: "smart",
splitPercentage: 50,
insertionPosition: "second",
autoPreset: undefined,
});
expect(migrated.workspaces.ws2.layoutConfig).toEqual({
// v7→v8: global layoutConfig added
expect(migrated.layoutConfig).toEqual({
insertionMode: "smart",
splitPercentage: 50,
insertionPosition: "second",
@@ -50,7 +44,7 @@ describe("migrations", () => {
});
});
it("should convert non-numeric labels to number with label and add per-workspace layoutConfig", () => {
it("should convert non-numeric labels to number with label and add global layoutConfig", () => {
const oldState = {
__version: 6,
windows: {},
@@ -81,12 +75,11 @@ describe("migrations", () => {
expect(migrated.workspaces.ws2.number).toBe(2);
expect(migrated.workspaces.ws2.label).toBe("Development");
// v7→v8→v9: layoutConfig added to each workspace
expect(migrated.workspaces.ws1.layoutConfig).toBeDefined();
expect(migrated.workspaces.ws2.layoutConfig).toBeDefined();
// v7→v8: global layoutConfig added
expect(migrated.layoutConfig).toBeDefined();
});
it("should handle mixed numeric and text labels and add per-workspace layoutConfig", () => {
it("should handle mixed numeric and text labels and add global layoutConfig", () => {
const oldState = {
__version: 6,
windows: {},
@@ -125,10 +118,8 @@ describe("migrations", () => {
expect(migrated.workspaces.ws3.number).toBe(3);
expect(migrated.workspaces.ws3.label).toBeUndefined();
// v7→v8→v9: layoutConfig added to each workspace
expect(migrated.workspaces.ws1.layoutConfig).toBeDefined();
expect(migrated.workspaces.ws2.layoutConfig).toBeDefined();
expect(migrated.workspaces.ws3.layoutConfig).toBeDefined();
// v7→v8: global layoutConfig added
expect(migrated.layoutConfig).toBeDefined();
});
it("should validate migrated state", () => {
@@ -151,104 +142,6 @@ describe("migrations", () => {
});
});
describe("v8 to v9 migration", () => {
it("should preserve per-workspace layoutConfig", () => {
const v8State = {
__version: 8,
windows: {
w1: { id: "w1", appId: "profile", props: {} },
w2: { id: "w2", appId: "nip", props: {} },
},
activeWorkspaceId: "ws1",
workspaces: {
ws1: {
id: "ws1",
number: 1,
label: undefined,
layout: null,
windowIds: [],
layoutConfig: {
insertionMode: "smart",
splitPercentage: 50,
insertionPosition: "second",
autoPreset: undefined,
},
},
ws2: {
id: "ws2",
number: 2,
label: "Development",
layout: {
direction: "row",
first: "w1",
second: "w2",
splitPercentage: 50,
},
windowIds: ["w1", "w2"],
layoutConfig: {
insertionMode: "row",
splitPercentage: 70,
insertionPosition: "first",
autoPreset: undefined,
},
},
},
};
const migrated = migrateState(v8State);
expect(migrated.__version).toBe(9);
// layoutConfig should remain per-workspace
expect(migrated.workspaces.ws1.layoutConfig).toEqual({
insertionMode: "smart",
splitPercentage: 50,
insertionPosition: "second",
autoPreset: undefined,
});
expect(migrated.workspaces.ws2.layoutConfig).toEqual({
insertionMode: "row",
splitPercentage: 70,
insertionPosition: "first",
autoPreset: undefined,
});
// Other fields should be preserved
expect(migrated.workspaces.ws2.label).toBe("Development");
expect(migrated.workspaces.ws2.layout).toEqual({
direction: "row",
first: "w1",
second: "w2",
splitPercentage: 50,
});
});
it("should validate v8→v9 migrated state", () => {
const v8State = {
__version: 8,
windows: { w1: { id: "w1", appId: "profile", props: {} } },
activeWorkspaceId: "ws1",
workspaces: {
ws1: {
id: "ws1",
number: 1,
layout: "w1",
windowIds: ["w1"],
layoutConfig: {
insertionMode: "smart",
splitPercentage: 50,
insertionPosition: "second",
autoPreset: undefined,
},
},
},
};
const migrated = migrateState(v8State);
expect(validateState(migrated)).toBe(true);
});
});
describe("validateState", () => {
it("should validate correct state structure", () => {
const state = {

View File

@@ -8,7 +8,7 @@
import { GrimoireState } from "@/types/app";
import { toast } from "sonner";
export const CURRENT_VERSION = 9;
export const CURRENT_VERSION = 8;
/**
* Migration function type
@@ -67,52 +67,18 @@ const migrations: Record<number, MigrationFn> = {
workspaces: migratedWorkspaces,
};
},
// Migration from v7 to v8 - adds layoutConfig to workspaces
// Migration from v7 to v8 - adds global layoutConfig
7: (state: any) => {
const migratedWorkspaces: Record<string, any> = {};
// Add default layoutConfig to each workspace
for (const [id, workspace] of Object.entries(state.workspaces || {})) {
const ws = workspace as Record<string, any>;
migratedWorkspaces[id] = {
...ws,
layoutConfig: {
insertionMode: "smart", // New smart default (auto-balance)
splitPercentage: 50, // Matches old 50/50 behavior
insertionPosition: "second", // Matches old right/bottom behavior
autoPreset: undefined, // No preset by default
},
};
}
// Add global layoutConfig with smart defaults
return {
...state,
__version: 8,
workspaces: migratedWorkspaces,
};
},
// Migration from v8 to v9 - preserve per-workspace layoutConfig
8: (state: any) => {
// Ensure all workspaces have layoutConfig (add default if missing)
const migratedWorkspaces: Record<string, any> = {};
for (const [id, workspace] of Object.entries(state.workspaces || {})) {
const ws = workspace as Record<string, any>;
migratedWorkspaces[id] = {
...ws,
layoutConfig: ws.layoutConfig || {
insertionMode: "smart",
splitPercentage: 50,
insertionPosition: "second",
autoPreset: undefined,
},
};
}
return {
...state,
__version: 9,
workspaces: migratedWorkspaces,
layoutConfig: {
insertionMode: "smart", // Smart auto-balancing
splitPercentage: 50, // Equal split
insertionPosition: "second", // New windows on right/bottom
autoPreset: undefined, // No preset by default
},
};
},
};
@@ -133,11 +99,17 @@ export function validateState(state: any): state is GrimoireState {
!state.windows ||
!state.workspaces ||
!state.activeWorkspaceId ||
!state.layoutConfig ||
typeof state.__version !== "number"
) {
return false;
}
// layoutConfig must be an object
if (typeof state.layoutConfig !== "object") {
return false;
}
// Windows must be an object
if (typeof state.windows !== "object") {
return false;
@@ -154,16 +126,11 @@ export function validateState(state: any): state is GrimoireState {
}
// All window IDs in workspaces must exist in windows
// Each workspace must have layoutConfig
for (const workspace of Object.values(state.workspaces)) {
const ws = workspace as any;
if (!Array.isArray(ws.windowIds)) {
return false;
}
// Verify workspace has layoutConfig
if (!ws.layoutConfig || typeof ws.layoutConfig !== "object") {
return false;
}
for (const windowId of ws.windowIds) {
if (!state.windows[windowId]) {
return false;

View File

@@ -123,7 +123,15 @@ export function parseTagStructure(tag: TagDefinition): {
parts.push(`${current.either.join(" | ")}`);
} else {
// Replace 'free' with 'text' for better readability
const type = current.type === "free" ? "text" : current.type;
let type = current.type === "free" ? "text" : current.type;
// Add examples for specific types
if (type === "url") {
type = "url (e.g. https://grimoire.rocks)";
} else if (type === "relay") {
type = "relay (e.g. wss://grimoire.rocks)";
}
parts.push(type);
}
current = current.next;