Skip to content

Commit a144457

Browse files
committed
chore: update deps, fix pane size storage, harden claude sdk session
- bump dependencies in package.json and pnpm-lock.yaml - add tests and fixes for renderer pane size storage - update viewSlice for pane size handling - harden claude sdkSession with additional tests and logic
1 parent 0a26326 commit a144457

7 files changed

Lines changed: 300 additions & 69 deletions

File tree

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
},
5151
"dependencies": {
5252
"@agentclientprotocol/sdk": "^0.21.0",
53-
"@anthropic-ai/claude-agent-sdk": "^0.2.139",
53+
"@anthropic-ai/claude-agent-sdk": "^0.3.142",
5454
"@chenglou/pretext": "^0.0.6",
5555
"@dnd-kit/abstract": "^0.4.0",
5656
"@dnd-kit/collision": "^0.4.0",
@@ -132,8 +132,8 @@
132132
"wait-on": "^9.0.4"
133133
},
134134
"optionalDependencies": {
135-
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.2.139",
136-
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.2.139"
135+
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.142",
136+
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.142"
137137
},
138138
"lint-staged": {
139139
"*.{js,jsx,ts,tsx,mjs,cjs}": [

pnpm-lock.yaml

Lines changed: 45 additions & 45 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { beforeEach, describe, expect, it } from "vitest";
2+
import {
3+
migratePaneSizeStorage,
4+
readStoredSizes,
5+
SPLIT_SIZE_STORAGE_PREFIX,
6+
swapPaneIdsInStorage,
7+
writeStoredSizes,
8+
} from "./paneSizeStorage";
9+
10+
function key(axis: "vertical" | "horizontal", ids: string[]): string {
11+
return `${SPLIT_SIZE_STORAGE_PREFIX}:${axis}:${ids.join("\0")}`;
12+
}
13+
14+
describe("paneSizeStorage", () => {
15+
beforeEach(() => {
16+
localStorage.clear();
17+
});
18+
19+
describe("migratePaneSizeStorage", () => {
20+
it("renames a single pane id in the storage key while preserving sizes", () => {
21+
writeStoredSizes(key("vertical", ["draft-a", "thread-x"]), [70, 30]);
22+
migratePaneSizeStorage("draft-a", "thread-a");
23+
expect(readStoredSizes(key("vertical", ["draft-a", "thread-x"]), 2)).toEqual([50, 50]);
24+
expect(readStoredSizes(key("vertical", ["thread-a", "thread-x"]), 2)).toEqual([70, 30]);
25+
});
26+
27+
it("is a no-op when old and new ids are equal", () => {
28+
writeStoredSizes(key("vertical", ["a", "b"]), [60, 40]);
29+
migratePaneSizeStorage("a", "a");
30+
expect(readStoredSizes(key("vertical", ["a", "b"]), 2)).toEqual([60, 40]);
31+
});
32+
33+
it("leaves unrelated keys untouched", () => {
34+
writeStoredSizes(key("vertical", ["a", "b"]), [70, 30]);
35+
writeStoredSizes(key("vertical", ["c", "d"]), [25, 75]);
36+
migratePaneSizeStorage("a", "z");
37+
expect(readStoredSizes(key("vertical", ["z", "b"]), 2)).toEqual([70, 30]);
38+
expect(readStoredSizes(key("vertical", ["c", "d"]), 2)).toEqual([25, 75]);
39+
});
40+
});
41+
42+
describe("swapPaneIdsInStorage", () => {
43+
it("swaps two pane ids in a single split, preserving slot sizes", () => {
44+
// Three panes [A, X, Y] sized 20/30/50. After swapping A and Y, the
45+
// layout becomes [Y, X, A] but slot widths must stay 20/30/50 so the
46+
// visual placements don't jump.
47+
writeStoredSizes(key("vertical", ["A", "X", "Y"]), [20, 30, 50]);
48+
swapPaneIdsInStorage("A", "Y");
49+
expect(readStoredSizes(key("vertical", ["Y", "X", "A"]), 3)).toEqual([20, 30, 50]);
50+
// Old key is gone.
51+
expect(localStorage.getItem(key("vertical", ["A", "X", "Y"]))).toBeNull();
52+
});
53+
54+
it("swaps pane ids across two different splits, preserving each slot's size", () => {
55+
writeStoredSizes(key("vertical", ["A", "X", "Y"]), [20, 30, 50]);
56+
writeStoredSizes(key("horizontal", ["P", "Q", "B"]), [40, 35, 25]);
57+
swapPaneIdsInStorage("A", "B");
58+
expect(readStoredSizes(key("vertical", ["B", "X", "Y"]), 3)).toEqual([20, 30, 50]);
59+
expect(readStoredSizes(key("horizontal", ["P", "Q", "A"]), 3)).toEqual([40, 35, 25]);
60+
});
61+
62+
it("is a no-op when the two ids are equal", () => {
63+
writeStoredSizes(key("vertical", ["a", "b"]), [60, 40]);
64+
swapPaneIdsInStorage("a", "a");
65+
expect(readStoredSizes(key("vertical", ["a", "b"]), 2)).toEqual([60, 40]);
66+
});
67+
68+
it("leaves keys that contain neither id untouched", () => {
69+
writeStoredSizes(key("vertical", ["c", "d"]), [70, 30]);
70+
swapPaneIdsInStorage("a", "b");
71+
expect(readStoredSizes(key("vertical", ["c", "d"]), 2)).toEqual([70, 30]);
72+
});
73+
});
74+
});

src/renderer/components/layout/paneSizeStorage.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,16 @@ export function writeStoredSizes(key: string, sizes: number[]) {
4848
}
4949

5050
/**
51-
* Rewrite split-size localStorage keys when a pane id changes (e.g., a draft
52-
* pane is replaced by the real thread id once the first message is sent).
53-
* Without this, the storage key — derived from the pane id list — no longer
54-
* matches and the user's custom proportions silently fall back to equal sizes.
51+
* Rewrite split-size localStorage keys by remapping pane ids. The storage key
52+
* encodes the full pane id list (in tree order), so any change to that list —
53+
* a rename (draft → real thread id) or a swap (drag-to-replace pane reorder) —
54+
* shifts the key. Without rewriting the stored entry, `readStoredSizes` falls
55+
* back to equal sizes and the user's custom proportions are silently lost.
56+
*
57+
* Sizes are stored as a positional array; this only rewrites the *key*, so
58+
* size-per-slot is preserved while the contents of those slots change.
5559
*/
56-
export function migratePaneSizeStorage(oldPaneId: string, newPaneId: string): void {
57-
if (oldPaneId === newPaneId) return;
60+
function remapPaneIdsInStorage(mapper: (paneId: string) => string): void {
5861
if (typeof localStorage === "undefined") return;
5962

6063
const matchingKeys: string[] = [];
@@ -70,8 +73,8 @@ export function migratePaneSizeStorage(oldPaneId: string, newPaneId: string): vo
7073
if (axisSeparator === -1) continue;
7174
const idsPart = key.slice(axisSeparator + 1);
7275
const ids = idsPart.split(PANE_ID_SEPARATOR);
73-
if (!ids.includes(oldPaneId)) continue;
74-
const nextIds = ids.map((id) => (id === oldPaneId ? newPaneId : id));
76+
const nextIds = ids.map(mapper);
77+
if (nextIds.every((id, i) => id === ids[i])) continue;
7578
const nextKey = `${key.slice(0, axisSeparator + 1)}${nextIds.join(PANE_ID_SEPARATOR)}`;
7679
if (nextKey === key) continue;
7780
const value = localStorage.getItem(key);
@@ -84,3 +87,22 @@ export function migratePaneSizeStorage(oldPaneId: string, newPaneId: string): vo
8487
}
8588
}
8689
}
90+
91+
export function migratePaneSizeStorage(oldPaneId: string, newPaneId: string): void {
92+
if (oldPaneId === newPaneId) return;
93+
remapPaneIdsInStorage((id) => (id === oldPaneId ? newPaneId : id));
94+
}
95+
96+
/**
97+
* Swap two pane ids in all split-size localStorage keys at once. Used when the
98+
* user drags one pane onto another to swap positions: each slot keeps its size,
99+
* only the pane id occupying that slot changes.
100+
*/
101+
export function swapPaneIdsInStorage(firstPaneId: string, secondPaneId: string): void {
102+
if (firstPaneId === secondPaneId) return;
103+
remapPaneIdsInStorage((id) => {
104+
if (id === firstPaneId) return secondPaneId;
105+
if (id === secondPaneId) return firstPaneId;
106+
return id;
107+
});
108+
}

src/renderer/state/slices/viewSlice.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
swapPaneIdsInLayout,
1010
type PaneLayoutInsertTarget,
1111
} from "@/shared/paneLayout";
12+
import { swapPaneIdsInStorage } from "@/renderer/components/layout/paneSizeStorage";
1213
import { reorderIds, type ReorderPlacement } from "../reorder";
1314
import {
1415
clearFinishedAndDone,
@@ -441,6 +442,10 @@ export const createViewSlice: SliceCreator<ViewSlice> = (set) => ({
441442
) {
442443
return {};
443444
}
445+
// Rewrite stored split sizes so each slot keeps its width after the swap.
446+
// The storage key is derived from the pane id list, so the layout swap
447+
// alone would shift the key and lose the user's custom proportions.
448+
swapPaneIdsInStorage(firstPaneId, secondPaneId);
444449
const layout = swapPaneIdsInLayout(currentPaneLayout(state.view), firstPaneId, secondPaneId);
445450
return {
446451
view: {

0 commit comments

Comments
 (0)