From 1aba61a0084f2aadb12293d1e2caa79c6b27c863 Mon Sep 17 00:00:00 2001 From: taurus Date: Tue, 25 Aug 2026 15:35:59 +0800 Subject: [PATCH 1/2] test(state): cover restored free-window id allocation --- tests/free-window-restored-id.spec.ts | 51 +++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/free-window-restored-id.spec.ts diff --git a/tests/free-window-restored-id.spec.ts b/tests/free-window-restored-id.spec.ts new file mode 100644 index 0000000..904ab82 --- /dev/null +++ b/tests/free-window-restored-id.spec.ts @@ -0,0 +1,51 @@ +// @vitest-environment jsdom +import { beforeEach, describe, expect, it } from 'vitest' +import { createSidebarStore, floatTab } from '../src/client/state.ts' + +beforeEach(() => { + localStorage.clear() +}) + +describe('restored free-window ids', () => { + it('seeds the shared uid counter past persisted float ids before minting another window', () => { + localStorage.setItem('dsh-sidebar:v1:s1', JSON.stringify({ + panelOpen: true, + width: 400, + activePane: 'pane:1', + nextTerminal: 1, + nextBrowser: 1, + expanded: [], + splits: { + kind: 'leaf', + id: 'pane:1', + active: 'tab:2', + tabs: [{ id: 'tab:2', type: 'browser', title: 'Docked' }], + }, + bottomOpen: false, + bottomHeight: 220, + bottomOpenedOnce: false, + bottomSplits: { + kind: 'leaf', + id: 'pane:3', + active: null, + tabs: [], + }, + floats: [{ + id: 'float:5', + tab: { id: 'tab:4', type: 'browser', title: 'Existing float' }, + x: 0, + y: 0, + w: 390, + h: 500, + }], + })) + + const store = createSidebarStore() + store.setSession('s1') + store.reduce(state => floatTab(state, 'tab:2', 400, 300)) + + const ids = store.getSnapshot().state!.floats.map(float => float.id) + expect(ids).toEqual(['float:5', 'float:6']) + expect(new Set(ids).size).toBe(ids.length) + }) +}) From 30879fff74be5921a03f71d63e20bc2cadc49cf3 Mon Sep 17 00:00:00 2001 From: taurus Date: Tue, 25 Aug 2026 15:38:53 +0800 Subject: [PATCH 2/2] fix(state): seed restored free-window ids --- src/client/state.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/state.ts b/src/client/state.ts index 5ecde61..abdd510 100644 --- a/src/client/state.ts +++ b/src/client/state.ts @@ -155,7 +155,7 @@ export function mintTabId(): string { /** * The largest numeric suffix across a raw persisted state's counter ids - * (`pane:N` / `tab:N` / `split:N`). The uid counter is module-global and + * (`pane:N` / `tab:N` / `split:N` / `float:N`). The uid counter is module-global and * resets on every reload, so a split minted AFTER a reload would collide * with the persisted ids (a fresh "pane:1" beside the persisted "pane:1"); * mapLeaf would then visit BOTH leaves and every open would land in both @@ -166,7 +166,7 @@ function maxCounterId(parsed: unknown): number { let max = 0 const consider = (id: unknown): void => { if (typeof id !== 'string') return - const match = /^(?:pane|tab|split):(\d+)$/.exec(id) + const match = /^(?:pane|tab|split|float):(\d+)$/.exec(id) if (match !== null) max = Math.max(max, Number(match[1])) } const walk = (node: unknown): void => {