From f5dac474a78fa578178d94da7f3a658b9342a540 Mon Sep 17 00:00:00 2001 From: David Krcek Date: Wed, 10 Jun 2026 23:47:08 +0200 Subject: [PATCH] fix(workspace): deterministic bootstrap pane id to stop hydration mismatch makePane() used uuidv4() for the very first pane, so the SSR render and the client's first render produced different editor-tabpanel ids, tripping a React hydration mismatch. Use a fixed BOOTSTRAP_PANE_ID for the pre-rehydration pane; persisted panes (real uuids) replace it client-side after rehydration, so there is no SSR comparison to fail. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/stores/workspaceStore.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/stores/workspaceStore.ts b/src/stores/workspaceStore.ts index b16d58e..02e0bdf 100644 --- a/src/stores/workspaceStore.ts +++ b/src/stores/workspaceStore.ts @@ -157,6 +157,14 @@ function makePane(): PaneState { return { id: uuidv4(), tabs: [], activeTabId: null } } +// Deterministic id for the very first pane the store hands out before persist +// rehydration. A random uuid here would differ between the SSR render and the +// client's first render, tripping a React hydration mismatch on the editor +// tabpanel `id` (editor-tabpanel-${pane.id}). After rehydration the persisted +// panes (with their own uuids) replace this one, client-side only — no SSR +// comparison — so a fixed bootstrap id is safe and unique among panes. +const BOOTSTRAP_PANE_ID = '__bootstrap-pane__' + const DEFAULT_SPLIT_RATIO = 0.5 function leaf(paneId: string): LayoutNode { @@ -575,7 +583,7 @@ export function migrateWorkspace(persisted: unknown, version: number): { export const useWorkspaceStore = create()( persist( (set, get) => { - const initialPane = makePane() + const initialPane: PaneState = { id: BOOTSTRAP_PANE_ID, tabs: [], activeTabId: null } return { panes: [initialPane], layout: leaf(initialPane.id),