fix: type errors

This commit is contained in:
Alejandro Gómez
2025-12-18 16:00:56 +01:00
parent 1c981a4e12
commit a6650ff6e1
19 changed files with 448 additions and 293 deletions

View File

@@ -130,17 +130,20 @@ describe("findLowestAvailableWorkspaceNumber", () => {
describe("addWindow", () => {
// Helper to create minimal test state
const createTestState = (layoutConfig: LayoutConfig, existingLayout: MosaicNode<string> | null = null): GrimoireState => ({
const createTestState = (
layoutConfig: LayoutConfig,
existingLayout: MosaicNode<string> | null = null,
): GrimoireState => ({
__version: 9,
windows: {},
activeWorkspaceId: "test-workspace",
layoutConfig, // Global layout config (not per-workspace)
workspaces: {
"test-workspace": {
id: "test-workspace",
number: 1,
windowIds: [],
layout: existingLayout,
layoutConfig, // Per-workspace layout config
},
},
});
@@ -207,12 +210,19 @@ describe("addWindow", () => {
describe("second window with row config", () => {
it("should create horizontal split", () => {
const state = createTestState({
insertionMode: "row",
splitPercentage: 50,
insertionPosition: "second",
}, "window-1");
state.windows["window-1"] = { id: "window-1", appId: "profile", props: {} };
const state = createTestState(
{
insertionMode: "row",
splitPercentage: 50,
insertionPosition: "second",
},
"window-1",
);
state.windows["window-1"] = {
id: "window-1",
appId: "profile",
props: {},
};
state.workspaces["test-workspace"].windowIds = ["window-1"];
const result = addWindow(state, {
@@ -229,12 +239,19 @@ describe("addWindow", () => {
});
it("should respect custom split percentage", () => {
const state = createTestState({
insertionMode: "row",
splitPercentage: 70,
insertionPosition: "second",
}, "window-1");
state.windows["window-1"] = { id: "window-1", appId: "profile", props: {} };
const state = createTestState(
{
insertionMode: "row",
splitPercentage: 70,
insertionPosition: "second",
},
"window-1",
);
state.windows["window-1"] = {
id: "window-1",
appId: "profile",
props: {},
};
state.workspaces["test-workspace"].windowIds = ["window-1"];
const result = addWindow(state, {
@@ -250,12 +267,19 @@ describe("addWindow", () => {
});
it("should place new window on right when position is second", () => {
const state = createTestState({
insertionMode: "row",
splitPercentage: 50,
insertionPosition: "second",
}, "window-1");
state.windows["window-1"] = { id: "window-1", appId: "profile", props: {} };
const state = createTestState(
{
insertionMode: "row",
splitPercentage: 50,
insertionPosition: "second",
},
"window-1",
);
state.windows["window-1"] = {
id: "window-1",
appId: "profile",
props: {},
};
state.workspaces["test-workspace"].windowIds = ["window-1"];
const result = addWindow(state, {
@@ -270,12 +294,19 @@ describe("addWindow", () => {
});
it("should place new window on left when position is first", () => {
const state = createTestState({
insertionMode: "row",
splitPercentage: 50,
insertionPosition: "first",
}, "window-1");
state.windows["window-1"] = { id: "window-1", appId: "profile", props: {} };
const state = createTestState(
{
insertionMode: "row",
splitPercentage: 50,
insertionPosition: "first",
},
"window-1",
);
state.windows["window-1"] = {
id: "window-1",
appId: "profile",
props: {},
};
state.workspaces["test-workspace"].windowIds = ["window-1"];
const result = addWindow(state, {
@@ -292,12 +323,19 @@ describe("addWindow", () => {
describe("second window with column config", () => {
it("should create vertical split", () => {
const state = createTestState({
insertionMode: "column",
splitPercentage: 50,
insertionPosition: "second",
}, "window-1");
state.windows["window-1"] = { id: "window-1", appId: "profile", props: {} };
const state = createTestState(
{
insertionMode: "column",
splitPercentage: 50,
insertionPosition: "second",
},
"window-1",
);
state.windows["window-1"] = {
id: "window-1",
appId: "profile",
props: {},
};
state.workspaces["test-workspace"].windowIds = ["window-1"];
const result = addWindow(state, {
@@ -314,12 +352,19 @@ describe("addWindow", () => {
});
it("should place new window on bottom when position is second", () => {
const state = createTestState({
insertionMode: "column",
splitPercentage: 50,
insertionPosition: "second",
}, "window-1");
state.windows["window-1"] = { id: "window-1", appId: "profile", props: {} };
const state = createTestState(
{
insertionMode: "column",
splitPercentage: 50,
insertionPosition: "second",
},
"window-1",
);
state.windows["window-1"] = {
id: "window-1",
appId: "profile",
props: {},
};
state.workspaces["test-workspace"].windowIds = ["window-1"];
const result = addWindow(state, {
@@ -334,12 +379,19 @@ describe("addWindow", () => {
});
it("should place new window on top when position is first", () => {
const state = createTestState({
insertionMode: "column",
splitPercentage: 50,
insertionPosition: "first",
}, "window-1");
state.windows["window-1"] = { id: "window-1", appId: "profile", props: {} };
const state = createTestState(
{
insertionMode: "column",
splitPercentage: 50,
insertionPosition: "first",
},
"window-1",
);
state.windows["window-1"] = {
id: "window-1",
appId: "profile",
props: {},
};
state.workspaces["test-workspace"].windowIds = ["window-1"];
const result = addWindow(state, {
@@ -356,12 +408,19 @@ describe("addWindow", () => {
describe("second window with smart config", () => {
it("should create horizontal split for first split", () => {
const state = createTestState({
insertionMode: "smart",
splitPercentage: 50,
insertionPosition: "second",
}, "window-1");
state.windows["window-1"] = { id: "window-1", appId: "profile", props: {} };
const state = createTestState(
{
insertionMode: "smart",
splitPercentage: 50,
insertionPosition: "second",
},
"window-1",
);
state.windows["window-1"] = {
id: "window-1",
appId: "profile",
props: {},
};
state.workspaces["test-workspace"].windowIds = ["window-1"];
const result = addWindow(state, {
@@ -385,12 +444,19 @@ describe("addWindow", () => {
second: "window-2",
splitPercentage: 50,
};
const state = createTestState({
insertionMode: "smart",
splitPercentage: 50,
insertionPosition: "second",
}, existingLayout);
state.windows["window-1"] = { id: "window-1", appId: "profile", props: {} };
const state = createTestState(
{
insertionMode: "smart",
splitPercentage: 50,
insertionPosition: "second",
},
existingLayout,
);
state.windows["window-1"] = {
id: "window-1",
appId: "profile",
props: {},
};
state.windows["window-2"] = { id: "window-2", appId: "nip", props: {} };
state.workspaces["test-workspace"].windowIds = ["window-1", "window-2"];
@@ -400,10 +466,14 @@ describe("addWindow", () => {
});
const workspace = result.workspaces["test-workspace"];
// Should add column split to balance (1 row, 0 column → add column)
// NEW BEHAVIOR: Splits shallowest leaf (window-1 or window-2 at depth 1)
// Root remains row, but creates column split at the leaf
expect(workspace.layout).toMatchObject({
direction: "column",
direction: "row",
});
// The first child should now be a column split containing the original window and new window
const layout = workspace.layout as any;
expect(layout.first).toHaveProperty("direction", "column");
});
it("should balance by adding horizontal split when vertical exists", () => {
@@ -414,12 +484,19 @@ describe("addWindow", () => {
second: "window-2",
splitPercentage: 50,
};
const state = createTestState({
insertionMode: "smart",
splitPercentage: 50,
insertionPosition: "second",
}, existingLayout);
state.windows["window-1"] = { id: "window-1", appId: "profile", props: {} };
const state = createTestState(
{
insertionMode: "smart",
splitPercentage: 50,
insertionPosition: "second",
},
existingLayout,
);
state.windows["window-1"] = {
id: "window-1",
appId: "profile",
props: {},
};
state.windows["window-2"] = { id: "window-2", appId: "nip", props: {} };
state.workspaces["test-workspace"].windowIds = ["window-1", "window-2"];
@@ -429,10 +506,14 @@ describe("addWindow", () => {
});
const workspace = result.workspaces["test-workspace"];
// Should add row split to balance (0 row, 1 column → add row)
// NEW BEHAVIOR: Splits shallowest leaf (window-1 or window-2 at depth 1)
// Root remains column, but creates row split at the leaf
expect(workspace.layout).toMatchObject({
direction: "row",
direction: "column",
});
// The first child should now be a row split containing the original window and new window
const layout = workspace.layout as any;
expect(layout.first).toHaveProperty("direction", "row");
});
});
@@ -514,12 +595,19 @@ describe("addWindow", () => {
});
it("should preserve existing windows when adding new one", () => {
const state = createTestState({
insertionMode: "row",
splitPercentage: 50,
insertionPosition: "second",
}, "window-1");
state.windows["window-1"] = { id: "window-1", appId: "profile", props: {} };
const state = createTestState(
{
insertionMode: "row",
splitPercentage: 50,
insertionPosition: "second",
},
"window-1",
);
state.windows["window-1"] = {
id: "window-1",
appId: "profile",
props: {},
};
state.workspaces["test-workspace"].windowIds = ["window-1"];
const result = addWindow(state, {

View File

@@ -1,6 +1,11 @@
import { v4 as uuidv4 } from "uuid";
import type { MosaicNode } from "react-mosaic-component";
import { GrimoireState, WindowInstance, UserRelays } from "@/types/app";
import {
GrimoireState,
WindowInstance,
UserRelays,
LayoutConfig,
} from "@/types/app";
import { insertWindow } from "@/lib/layout-utils";
import { applyPresetToLayout, type LayoutPreset } from "@/lib/layout-presets";
@@ -48,6 +53,12 @@ export const createWorkspace = (
label,
layout: null,
windowIds: [],
layoutConfig: {
insertionMode: "smart",
splitPercentage: 50,
insertionPosition: "second",
autoPreset: undefined,
},
},
},
};
@@ -76,8 +87,8 @@ export const addWindow = (
commandString: payload.commandString,
};
// Insert window using global layout configuration
const newLayout = insertWindow(ws.layout, newWindowId, state.layoutConfig);
// Insert window using workspace's layout configuration
const newLayout = insertWindow(ws.layout, newWindowId, ws.layoutConfig);
return {
...state,
@@ -347,18 +358,27 @@ export const updateWindow = (
};
/**
* Updates the global layout configuration.
* Controls how new windows are inserted into all workspaces.
* Updates the active workspace's layout configuration.
* Controls how new windows are inserted into the active workspace.
*/
export const updateLayoutConfig = (
state: GrimoireState,
layoutConfig: Partial<GrimoireState["layoutConfig"]>,
layoutConfig: Partial<LayoutConfig>,
): GrimoireState => {
const activeId = state.activeWorkspaceId;
const activeWorkspace = state.workspaces[activeId];
return {
...state,
layoutConfig: {
...state.layoutConfig,
...layoutConfig,
workspaces: {
...state.workspaces,
[activeId]: {
...activeWorkspace,
layoutConfig: {
...activeWorkspace.layoutConfig,
...layoutConfig,
},
},
},
};
};

View File

@@ -1,7 +1,12 @@
import { useEffect, useCallback } from "react";
import { useAtom } from "jotai";
import { atomWithStorage, createJSONStorage } from "jotai/utils";
import { GrimoireState, AppId, WindowInstance } from "@/types/app";
import {
GrimoireState,
AppId,
WindowInstance,
LayoutConfig,
} from "@/types/app";
import { useLocale } from "@/hooks/useLocale";
import * as Logic from "./logic";
import { CURRENT_VERSION, validateState, migrateState } from "@/lib/migrations";
@@ -18,14 +23,14 @@ const initialState: GrimoireState = {
number: 1,
windowIds: [],
layout: null,
layoutConfig: {
insertionMode: "smart", // Smart auto-balancing by default
splitPercentage: 50, // Equal split
insertionPosition: "second", // New windows on right/bottom
autoPreset: undefined, // No preset maintenance
},
},
},
layoutConfig: {
insertionMode: "smart", // Smart auto-balancing by default
splitPercentage: 50, // Equal split
insertionPosition: "second", // New windows on right/bottom
autoPreset: undefined, // No preset maintenance
},
};
// Custom storage with error handling and migrations
@@ -230,14 +235,13 @@ export const useGrimoire = () => {
);
const updateLayoutConfig = useCallback(
(layoutConfig: Partial<GrimoireState["layoutConfig"]>) =>
(layoutConfig: Partial<LayoutConfig>) =>
setState((prev) => Logic.updateLayoutConfig(prev, layoutConfig)),
[setState],
);
const applyPresetLayout = useCallback(
(preset: any) =>
setState((prev) => Logic.applyPresetLayout(prev, preset)),
(preset: any) => setState((prev) => Logic.applyPresetLayout(prev, preset)),
[setState],
);