mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-22 17:22:47 +00:00
fix(app): keep saved workspace tabs when a store changes before hydration (#7547)
On every page load the layout mode publisher sets the workspace layout store before its stored state has been read. Zustand persist wrote the unhydrated defaults, and hydration merges the stored layout into memory without writing it back, so storage held no tabs until the next layout change. Leaving the page inside that window lost every workspace's tabs. Validated persist storage now drops writes that arrive while its read is in flight.
This commit is contained in:
@@ -13,29 +13,43 @@ export function createValidatedPersistStorage<State>(
|
||||
});
|
||||
|
||||
const accepted = new Map<string, StorageValue<State>>();
|
||||
const reading = new Set<string>();
|
||||
|
||||
async function read(name: string): Promise<StorageValue<State> | null> {
|
||||
const raw = await backingStorage.getItem(name);
|
||||
if (raw === null) return null;
|
||||
|
||||
let decoded: unknown;
|
||||
try {
|
||||
decoded = JSON.parse(raw);
|
||||
} catch {
|
||||
await backingStorage.removeItem(name);
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = envelopeSchema.safeParse(decoded);
|
||||
if (!result.success) {
|
||||
await backingStorage.removeItem(name);
|
||||
return null;
|
||||
}
|
||||
return result.data;
|
||||
}
|
||||
|
||||
return {
|
||||
getItem: async (name) => {
|
||||
accepted.delete(name);
|
||||
const raw = await backingStorage.getItem(name);
|
||||
if (raw === null) return null;
|
||||
|
||||
let decoded: unknown;
|
||||
reading.add(name);
|
||||
try {
|
||||
decoded = JSON.parse(raw);
|
||||
} catch {
|
||||
await backingStorage.removeItem(name);
|
||||
return null;
|
||||
return await read(name);
|
||||
} finally {
|
||||
reading.delete(name);
|
||||
}
|
||||
|
||||
const result = envelopeSchema.safeParse(decoded);
|
||||
if (!result.success) {
|
||||
await backingStorage.removeItem(name);
|
||||
return null;
|
||||
}
|
||||
return result.data;
|
||||
},
|
||||
setItem: async (name, value) => {
|
||||
// A store that changes before hydration settles writes the defaults it started with.
|
||||
// Hydration then merges the stored state in memory without writing it back, so that
|
||||
// write would replace the stored state until the next change.
|
||||
if (reading.has(name)) return;
|
||||
const previous = accepted.get(name);
|
||||
if (
|
||||
options.skipUnchanged &&
|
||||
|
||||
@@ -52,3 +52,39 @@ it("skips unchanged reconciliations and immediately saves real layout changes",
|
||||
store.getState().layoutByWorkspace[workspaceKey],
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps the saved layout when the shell publishes its layout mode before hydration", async () => {
|
||||
const values = new Map<string, string>();
|
||||
const storage: StateStorage = {
|
||||
getItem: (name) => values.get(name) ?? null,
|
||||
setItem: (name, value) => {
|
||||
values.set(name, value);
|
||||
},
|
||||
removeItem: (name) => {
|
||||
values.delete(name);
|
||||
},
|
||||
};
|
||||
const workspaceKey = "server:workspace";
|
||||
const saved = createWorkspaceLayoutStore(undefined, storage);
|
||||
await saved.persist.rehydrate();
|
||||
saved.getState().openTab({
|
||||
workspaceKey,
|
||||
target: { kind: "file", path: "notes.md" },
|
||||
intent: "reveal",
|
||||
});
|
||||
|
||||
// A page load: the shell publishes its mode from the first commit, before the read settles.
|
||||
const loaded = createWorkspaceLayoutStore(undefined, storage);
|
||||
loaded.getState().setLayoutMode("single-pane");
|
||||
await new Promise<void>((resolve) => loaded.persist.onFinishHydration(() => resolve()));
|
||||
expect(loaded.getState().layoutByWorkspace[workspaceKey]).toEqual(
|
||||
saved.getState().layoutByWorkspace[workspaceKey],
|
||||
);
|
||||
|
||||
// Leaving the page before any other layout change must not lose the tabs.
|
||||
const reloaded = createWorkspaceLayoutStore(undefined, storage);
|
||||
await reloaded.persist.rehydrate();
|
||||
expect(reloaded.getState().layoutByWorkspace[workspaceKey]).toEqual(
|
||||
saved.getState().layoutByWorkspace[workspaceKey],
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user