authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-09 13:00:27-07:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-09 14:02:29-07:00
log82c0a45ea0334a8ffd5747e080ca693f54921d66
tree5efb5ab19b40c8386ba206604f15f145a2de7db1
parente765051f148bf6006e30118eb47f5d33ea9e32be

Use Debouncer for composer draft persistence writes

- Replace manual timeout/pending-value debounce logic with `@tanstack/react-pacer` `Debouncer` - Keep `removeItem`/`flush` behavior while simplifying and standardizing persistence timing

1 files changed, 11 insertions(+), 27 deletions(-)

apps/web/src/composerDraftStore.ts+11-27
...@@ -14,6 +14,7 @@ import {...@@ -14,6 +14,7 @@ import {
14 DEFAULT_RUNTIME_MODE,14 DEFAULT_RUNTIME_MODE,
15 type ChatImageAttachment,15 type ChatImageAttachment,
16} from "./types";16} from "./types";
17import { Debouncer } from "@tanstack/react-pacer";
17import { create } from "zustand";18import { create } from "zustand";
18import { createJSONStorage, persist, type StateStorage } from "zustand/middleware";19import { createJSONStorage, persist, type StateStorage } from "zustand/middleware";
1920
...@@ -27,41 +28,24 @@ interface DebouncedStorage extends StateStorage {...@@ -27,41 +28,24 @@ interface DebouncedStorage extends StateStorage {
27}28}
2829
29export function createDebouncedStorage(baseStorage: StateStorage): DebouncedStorage {30export function createDebouncedStorage(baseStorage: StateStorage): DebouncedStorage {
30 let timer: ReturnType<typeof setTimeout> | null = null;31 const debouncedSetItem = new Debouncer(
31 let pendingName: string | null = null;32 (name: string, value: string) => {
32 let pendingValue: string | null = null;33 baseStorage.setItem(name, value);
34 },
35 { wait: COMPOSER_PERSIST_DEBOUNCE_MS },
36 );
37
33 return {38 return {
34 getItem: (name) => baseStorage.getItem(name),39 getItem: (name) => baseStorage.getItem(name),
35 setItem: (name, value) => {40 setItem: (name, value) => {
36 pendingName = name;41 debouncedSetItem.maybeExecute(name, value);
37 pendingValue = value;
38 if (timer !== null) {
39 clearTimeout(timer);
40 }
41 timer = setTimeout(() => {
42 timer = null;
43 pendingName = null;
44 pendingValue = null;
45 baseStorage.setItem(name, value);
46 }, COMPOSER_PERSIST_DEBOUNCE_MS);
47 },42 },
48 removeItem: (name) => {43 removeItem: (name) => {
49 if (timer !== null) {44 debouncedSetItem.cancel();
50 clearTimeout(timer);
51 timer = null;
52 pendingName = null;
53 pendingValue = null;
54 }
55 baseStorage.removeItem(name);45 baseStorage.removeItem(name);
56 },46 },
57 flush: () => {47 flush: () => {
58 if (timer !== null && pendingName !== null && pendingValue !== null) {48 debouncedSetItem.flush();
59 clearTimeout(timer);
60 timer = null;
61 baseStorage.setItem(pendingName, pendingValue);
62 pendingName = null;
63 pendingValue = null;
64 }
65 },49 },
66 };50 };
67}51}