cogsbox-state 0.5.461 → 0.5.463

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/CogsState.tsx CHANGED
@@ -284,7 +284,7 @@ export type EndType<T, IsArrayElement = false> = {
284
284
  hideMessage?: boolean;
285
285
  }) => JSX.Element;
286
286
  lastSynced?: SyncInfo;
287
- } & (IsArrayElement extends true ? { cut: () => void } : {});
287
+ } & (IsArrayElement extends true ? { cutThis: () => void } : {});
288
288
 
289
289
  export type StateObject<T> = (T extends any[]
290
290
  ? ArrayEndType<T>
@@ -419,13 +419,7 @@ export type OptionsType<T extends unknown = unknown, TApiParams = never> = {
419
419
  onSuccess?: (data: any) => void;
420
420
  onError?: (error: any) => void;
421
421
  };
422
- middleware?: ({
423
- updateLog,
424
- update,
425
- }: {
426
- updateLog: UpdateTypeDetail[] | undefined;
427
- update: UpdateTypeDetail;
428
- }) => void;
422
+ middleware?: ({ update }: { update: UpdateTypeDetail }) => void;
429
423
 
430
424
  modifyState?: (state: T) => T;
431
425
  localStorage?: {
@@ -578,26 +572,21 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
578
572
  }
579
573
  ) => {
580
574
  let newInitialState = initialState;
581
- console.log('optsc', opt?.__useSync);
582
575
  const [statePart, initialOptionsPart] =
583
576
  transformStateFunc<State>(newInitialState);
584
577
 
585
- // Store notifications if provided
586
578
  if (opt?.__fromSyncSchema && opt?.__syncNotifications) {
587
579
  getGlobalStore
588
580
  .getState()
589
581
  .setInitialStateOptions('__notifications', opt.__syncNotifications);
590
582
  }
591
583
 
592
- // Store apiParams map if provided
593
584
  if (opt?.__fromSyncSchema && opt?.__apiParamsMap) {
594
585
  getGlobalStore
595
586
  .getState()
596
587
  .setInitialStateOptions('__apiParamsMap', opt.__apiParamsMap);
597
588
  }
598
589
 
599
- // ... rest of your existing createCogsState code unchanged ...
600
-
601
590
  Object.keys(statePart).forEach((key) => {
602
591
  let existingOptions = initialOptionsPart[key] || {};
603
592
 
@@ -653,6 +642,7 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
653
642
  stateKey: StateKey,
654
643
  options?: Prettify<OptionsType<(typeof statePart)[StateKey]>>
655
644
  ) => {
645
+ console.time('useCogsState');
656
646
  const [componentId] = useState(options?.componentId ?? uuidv4());
657
647
 
658
648
  setOptions({
@@ -667,6 +657,8 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
667
657
  ? options.modifyState(thiState)
668
658
  : thiState;
669
659
 
660
+ console.timeEnd('useCogsState');
661
+
670
662
  const updater = useCogsStateFn<(typeof statePart)[StateKey]>(partialState, {
671
663
  stateKey: stateKey as string,
672
664
  syncUpdate: options?.syncUpdate,
@@ -788,7 +780,7 @@ export function createCogsStateFromSync<
788
780
  const {
789
781
  getInitialOptions,
790
782
 
791
- setStateLog,
783
+ addStateLog,
792
784
  updateInitialStateGlobal,
793
785
  } = getGlobalStore.getState();
794
786
  const saveToLocalStorage = <T,>(
@@ -978,6 +970,8 @@ function markEntireStateAsServerSynced(
978
970
  });
979
971
  }
980
972
  }
973
+ let updateBatchQueue = new Map<string, Array<UpdateArg<any>>>();
974
+ let batchFlushScheduled = false;
981
975
 
982
976
  export function useCogsStateFn<TStateObject extends unknown>(
983
977
  stateObject: TStateObject,
@@ -994,7 +988,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
994
988
  dependencies,
995
989
  serverState,
996
990
  __useSync,
997
- syncOptions,
998
991
  }: {
999
992
  stateKey?: string;
1000
993
  componentId?: string;
@@ -1003,13 +996,11 @@ export function useCogsStateFn<TStateObject extends unknown>(
1003
996
  syncOptions?: SyncOptionsType<any>;
1004
997
  } & OptionsType<TStateObject> = {}
1005
998
  ) {
999
+ console.time('useCogsStateFn top');
1006
1000
  const [reactiveForce, forceUpdate] = useState({}); //this is the key to reactivity
1007
1001
  const { sessionId } = useCogsConfig();
1008
-
1009
1002
  let noStateKey = stateKey ? false : true;
1010
1003
  const [thisKey] = useState(stateKey ?? uuidv4());
1011
- const stateLog = getGlobalStore.getState().stateLog[thisKey];
1012
- const componentUpdatesRef = useRef(new Set<string>());
1013
1004
  const componentIdRef = useRef(componentId ?? uuidv4());
1014
1005
  const latestInitialOptionsRef = useRef<OptionsType<TStateObject> | null>(
1015
1006
  null
@@ -1019,9 +1010,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
1019
1010
 
1020
1011
  useEffect(() => {
1021
1012
  if (syncUpdate && syncUpdate.stateKey === thisKey && syncUpdate.path?.[0]) {
1022
- // Update the actual state value
1023
-
1024
- // Create combined key and update sync info
1025
1013
  const syncKey = `${syncUpdate.stateKey}:${syncUpdate.path.join('.')}`;
1026
1014
  getGlobalStore.getState().setSyncInfo(syncKey, {
1027
1015
  timeStamp: syncUpdate.timeStamp!,
@@ -1254,6 +1242,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
1254
1242
  notifyComponents(thisKey);
1255
1243
  }
1256
1244
  }, [thisKey, ...(dependencies || [])]);
1245
+
1257
1246
  useLayoutEffect(() => {
1258
1247
  if (noStateKey) {
1259
1248
  setAndMergeOptions(thisKey as string, {
@@ -1323,22 +1312,22 @@ export function useCogsStateFn<TStateObject extends unknown>(
1323
1312
  }, []);
1324
1313
 
1325
1314
  const syncApiRef = useRef<SyncApi | null>(null);
1315
+ console.timeEnd('useCogsStateFn top');
1316
+
1326
1317
  const effectiveSetState = (
1327
1318
  newStateOrFunction: UpdateArg<TStateObject> | InsertParams<TStateObject>,
1328
1319
  path: string[],
1329
1320
  updateObj: UpdateOptions
1330
1321
  ) => {
1322
+ console.time('top of effectiveSetState');
1331
1323
  const fullPath = [thisKey, ...path].join('.');
1332
- if (Array.isArray(path)) {
1333
- const pathKey = `${thisKey}-${path.join('.')}`;
1334
- componentUpdatesRef.current.add(pathKey);
1335
- }
1336
1324
  const store = getGlobalStore.getState();
1337
1325
 
1338
- // FETCH ONCE at the beginning
1339
1326
  const shadowMeta = store.getShadowMetadata(thisKey, path);
1340
1327
  const nestedShadowValue = store.getShadowValue(fullPath) as TStateObject;
1328
+ console.timeEnd('top of effectiveSetState');
1341
1329
 
1330
+ console.time('top of payload');
1342
1331
  const payload = (
1343
1332
  updateObj.updateType === 'insert' && isFunction(newStateOrFunction)
1344
1333
  ? newStateOrFunction({ state: nestedShadowValue, uuid: uuidv4() })
@@ -1358,17 +1347,15 @@ export function useCogsStateFn<TStateObject extends unknown>(
1358
1347
  oldValue: nestedShadowValue,
1359
1348
  newValue: payload,
1360
1349
  } satisfies UpdateTypeDetail;
1350
+ console.timeEnd('top of payload');
1361
1351
 
1352
+ console.time('switch in effectiveSetState');
1362
1353
  // Perform the update
1363
1354
  switch (updateObj.updateType) {
1364
1355
  case 'insert': {
1365
1356
  store.insertShadowArrayElement(thisKey, path, newUpdate.newValue);
1366
- // The array at `path` has been modified. Mark it AND all its parents as dirty.
1367
1357
  store.markAsDirty(thisKey, path, { bubble: true });
1368
-
1369
- // ALSO mark the newly inserted item itself as dirty
1370
- // Get the new item's path and mark it as dirty
1371
- const arrayMeta = store.getShadowMetadata(thisKey, path);
1358
+ const arrayMeta = shadowMeta;
1372
1359
  if (arrayMeta?.arrayKeys) {
1373
1360
  const newItemKey =
1374
1361
  arrayMeta.arrayKeys[arrayMeta.arrayKeys.length - 1];
@@ -1392,7 +1379,9 @@ export function useCogsStateFn<TStateObject extends unknown>(
1392
1379
  break;
1393
1380
  }
1394
1381
  }
1382
+ console.timeEnd('switch in effectiveSetState');
1395
1383
  const shouldSync = updateObj.sync !== false;
1384
+ console.time('signals');
1396
1385
 
1397
1386
  if (shouldSync && syncApiRef.current && syncApiRef.current.connected) {
1398
1387
  syncApiRef.current.updateState({ operation: newUpdate });
@@ -1545,6 +1534,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
1545
1534
  });
1546
1535
  }
1547
1536
  }
1537
+
1548
1538
  if (updateObj.updateType === 'cut') {
1549
1539
  const arrayPath = path.slice(0, -1);
1550
1540
  const arrayMeta = store.getShadowMetadata(thisKey, arrayPath);
@@ -1562,16 +1552,14 @@ export function useCogsStateFn<TStateObject extends unknown>(
1562
1552
  });
1563
1553
  }
1564
1554
  }
1555
+ console.timeEnd('signals');
1565
1556
 
1566
- // Assumes `isDeepEqual` is available in this scope.
1567
- // Assumes `isDeepEqual` is available in this scope.
1568
-
1569
- const newState = getGlobalStore.getState().getShadowValue(thisKey);
1570
- const rootMeta = getGlobalStore.getState().getShadowMetadata(thisKey, []);
1557
+ console.time('notify');
1558
+ const rootMeta = store.getShadowMetadata(thisKey, []);
1571
1559
  const notifiedComponents = new Set<string>();
1572
1560
 
1573
1561
  if (!rootMeta?.components) {
1574
- return newState;
1562
+ return;
1575
1563
  }
1576
1564
 
1577
1565
  // --- PASS 1: Notify specific subscribers based on update type ---
@@ -1753,26 +1741,9 @@ export function useCogsStateFn<TStateObject extends unknown>(
1753
1741
  }
1754
1742
  });
1755
1743
  notifiedComponents.clear();
1756
- setStateLog(thisKey, (prevLogs) => {
1757
- const logs = [...(prevLogs ?? []), newUpdate];
1758
- const aggregatedLogs = new Map<string, typeof newUpdate>();
1759
-
1760
- logs.forEach((log) => {
1761
- const uniqueKey = `${log.stateKey}:${JSON.stringify(log.path)}`;
1762
- const existing = aggregatedLogs.get(uniqueKey);
1763
-
1764
- if (existing) {
1765
- existing.timeStamp = Math.max(existing.timeStamp, log.timeStamp);
1766
- existing.newValue = log.newValue;
1767
- existing.oldValue = existing.oldValue ?? log.oldValue;
1768
- existing.updateType = log.updateType;
1769
- } else {
1770
- aggregatedLogs.set(uniqueKey, { ...(log as any) });
1771
- }
1772
- });
1773
-
1774
- return Array.from(aggregatedLogs.values());
1775
- });
1744
+ console.timeEnd('notify');
1745
+ console.time('end stuff');
1746
+ addStateLog(thisKey, newUpdate);
1776
1747
 
1777
1748
  saveToLocalStorage(
1778
1749
  payload,
@@ -1783,12 +1754,10 @@ export function useCogsStateFn<TStateObject extends unknown>(
1783
1754
 
1784
1755
  if (latestInitialOptionsRef.current?.middleware) {
1785
1756
  latestInitialOptionsRef.current!.middleware({
1786
- updateLog: stateLog,
1787
1757
  update: newUpdate,
1788
1758
  });
1789
1759
  }
1790
-
1791
- return newState;
1760
+ console.timeEnd('end stuff');
1792
1761
  };
1793
1762
 
1794
1763
  if (!getGlobalStore.getState().initialStateGlobal[thisKey]) {
@@ -1796,12 +1765,15 @@ export function useCogsStateFn<TStateObject extends unknown>(
1796
1765
  }
1797
1766
 
1798
1767
  const updaterFinal = useMemo(() => {
1799
- return createProxyHandler<TStateObject>(
1768
+ console.time('createProxyHandler');
1769
+ const handler = createProxyHandler<TStateObject>(
1800
1770
  thisKey,
1801
1771
  effectiveSetState,
1802
1772
  componentIdRef.current,
1803
1773
  sessionId
1804
1774
  );
1775
+ console.timeEnd('createProxyHandler'); // <--- AND THIS
1776
+ return handler;
1805
1777
  }, [thisKey, sessionId]);
1806
1778
 
1807
1779
  const cogsSyncFn = __useSync;
@@ -1848,12 +1820,9 @@ function hashTransforms(transforms: any[]) {
1848
1820
  if (!transforms || transforms.length === 0) {
1849
1821
  return '';
1850
1822
  }
1851
- // This creates a string representation of the transforms AND their dependencies.
1852
- // Example: "filter['red']sort['score','asc']"
1853
1823
  return transforms
1854
1824
  .map(
1855
1825
  (transform) =>
1856
- // Safely stringify dependencies. An empty array becomes '[]'.
1857
1826
  `${transform.type}${JSON.stringify(transform.dependencies || [])}`
1858
1827
  )
1859
1828
  .join('');
@@ -1896,8 +1865,6 @@ const registerComponentDependency = (
1896
1865
  const fullComponentId = `${stateKey}////${componentId}`;
1897
1866
  const { addPathComponent, getShadowMetadata } = getGlobalStore.getState();
1898
1867
 
1899
- // First, check if the component should even be registered.
1900
- // This check is safe to do outside the setter.
1901
1868
  const rootMeta = getShadowMetadata(stateKey, []);
1902
1869
  const component = rootMeta?.components?.get(fullComponentId);
1903
1870
 
@@ -1913,7 +1880,6 @@ const registerComponentDependency = (
1913
1880
  return;
1914
1881
  }
1915
1882
 
1916
- // Now, call the single, safe, atomic function to perform the update.
1917
1883
  addPathComponent(stateKey, dependencyPath, fullComponentId);
1918
1884
  };
1919
1885
  const notifySelectionComponents = (
@@ -1961,48 +1927,40 @@ const notifySelectionComponents = (
1961
1927
  }
1962
1928
  }
1963
1929
  };
1930
+
1964
1931
  function createProxyHandler<T>(
1965
1932
  stateKey: string,
1966
1933
  effectiveSetState: EffectiveSetState<T>,
1967
1934
  componentId: string,
1968
1935
  sessionId?: string
1969
1936
  ): StateObject<T> {
1970
- type CacheEntry = {
1971
- proxy: any;
1972
- stateVersion: number;
1973
- };
1974
-
1975
- const shapeCache = new Map<string, CacheEntry>();
1937
+ const proxyCache = new Map<string, any>();
1976
1938
  let stateVersion = 0;
1939
+ console.time('rebuildStateShape Outer');
1977
1940
 
1978
- const invalidateCachePath = (path: string[]) => {
1979
- const pathKey = path.join('.');
1980
- for (const [key] of shapeCache) {
1981
- if (key === pathKey || key.startsWith(pathKey + '.')) {
1982
- shapeCache.delete(key);
1983
- }
1984
- }
1985
- stateVersion++;
1986
- };
1941
+ let recursionTimerName: string | null = null;
1987
1942
 
1988
1943
  function rebuildStateShape({
1989
- currentState,
1990
1944
  path = [],
1991
1945
  meta,
1992
1946
  componentId,
1993
1947
  }: {
1994
- currentState: T;
1995
1948
  path: string[];
1996
1949
  componentId: string;
1997
1950
  meta?: MetaData;
1998
1951
  }): any {
1999
- const cacheKey = path.map(String).join('.');
1952
+ console.time('rebuildStateShape Inner');
1953
+ const derivationSignature = meta
1954
+ ? JSON.stringify(meta.validIds || meta.transforms)
1955
+ : '';
1956
+ const cacheKey = path.join('.') + ':' + derivationSignature;
1957
+ console.log('PROXY CACHE KEY ', cacheKey);
1958
+ if (proxyCache.has(cacheKey)) {
1959
+ console.log('PROXY CACHE HIT');
1960
+ return proxyCache.get(cacheKey);
1961
+ }
2000
1962
  const stateKeyPathKey = [stateKey, ...path].join('.');
2001
1963
 
2002
- currentState = getGlobalStore
2003
- .getState()
2004
- .getShadowValue(stateKeyPathKey, meta?.validIds);
2005
-
2006
1964
  type CallableStateObject<T> = {
2007
1965
  (): T;
2008
1966
  } & {
@@ -2022,9 +1980,11 @@ function createProxyHandler<T>(
2022
1980
  },
2023
1981
 
2024
1982
  get(target: any, prop: string) {
2025
- // V--------- THE CRUCIAL FIX IS HERE ---------V
2026
- // This handles requests for internal functions on the proxy,
2027
- // returning the function itself instead of treating it as state.
1983
+ if (path.length === 0) {
1984
+ // Create a unique name for this specific timer instance
1985
+ recursionTimerName = `Recursion-${Math.random()}`;
1986
+ console.time(recursionTimerName);
1987
+ }
2028
1988
  if (prop === '_rebuildStateShape') {
2029
1989
  return rebuildStateShape;
2030
1990
  }
@@ -2200,1167 +2160,1154 @@ function createProxyHandler<T>(
2200
2160
  return [];
2201
2161
  };
2202
2162
  }
2203
- if (Array.isArray(currentState)) {
2204
- if (prop === 'getSelected') {
2205
- return () => {
2206
- const fullKey = stateKey + '.' + path.join('.');
2207
- registerComponentDependency(stateKey, componentId, [
2208
- ...path,
2209
- 'getSelected',
2210
- ]);
2211
-
2212
- const selectedIndicesMap =
2213
- getGlobalStore.getState().selectedIndicesMap;
2214
- if (!selectedIndicesMap || !selectedIndicesMap.has(fullKey)) {
2215
- return undefined;
2216
- }
2217
-
2218
- const selectedItemKey = selectedIndicesMap.get(fullKey)!;
2219
- if (meta?.validIds) {
2220
- if (!meta.validIds.includes(selectedItemKey)) {
2221
- return undefined;
2222
- }
2223
- }
2224
2163
 
2225
- const value = getGlobalStore
2226
- .getState()
2227
- .getShadowValue(selectedItemKey);
2164
+ if (prop === 'getSelected') {
2165
+ return () => {
2166
+ const fullKey = stateKey + '.' + path.join('.');
2167
+ registerComponentDependency(stateKey, componentId, [
2168
+ ...path,
2169
+ 'getSelected',
2170
+ ]);
2171
+
2172
+ const selectedIndicesMap =
2173
+ getGlobalStore.getState().selectedIndicesMap;
2174
+ if (!selectedIndicesMap || !selectedIndicesMap.has(fullKey)) {
2175
+ return undefined;
2176
+ }
2228
2177
 
2229
- if (!value) {
2178
+ const selectedItemKey = selectedIndicesMap.get(fullKey)!;
2179
+ if (meta?.validIds) {
2180
+ if (!meta.validIds.includes(selectedItemKey)) {
2230
2181
  return undefined;
2231
2182
  }
2183
+ }
2232
2184
 
2233
- return rebuildStateShape({
2234
- currentState: value,
2235
- path: selectedItemKey.split('.').slice(1) as string[],
2236
- componentId: componentId!,
2237
- });
2238
- };
2239
- }
2240
- if (prop === 'getSelectedIndex') {
2241
- return () => {
2242
- const selectedIndex = getGlobalStore
2243
- .getState()
2244
- .getSelectedIndex(
2245
- stateKey + '.' + path.join('.'),
2246
- meta?.validIds
2247
- );
2248
-
2249
- return selectedIndex;
2250
- };
2251
- }
2252
- if (prop === 'clearSelected') {
2253
- notifySelectionComponents(stateKey, path);
2254
- return () => {
2255
- getGlobalStore.getState().clearSelectedIndex({
2256
- arrayKey: stateKey + '.' + path.join('.'),
2257
- });
2258
- };
2259
- }
2185
+ const value = getGlobalStore
2186
+ .getState()
2187
+ .getShadowValue(selectedItemKey);
2260
2188
 
2261
- if (prop === 'useVirtualView') {
2262
- return (
2263
- options: VirtualViewOptions
2264
- ): VirtualStateObjectResult<any[]> => {
2265
- const {
2266
- itemHeight = 50,
2267
- overscan = 6,
2268
- stickToBottom = false,
2269
- scrollStickTolerance = 75,
2270
- } = options;
2271
-
2272
- const containerRef = useRef<HTMLDivElement | null>(null);
2273
- const [range, setRange] = useState({
2274
- startIndex: 0,
2275
- endIndex: 10,
2276
- });
2277
- const [rerender, forceUpdate] = useState({});
2278
- const initialScrollRef = useRef(true);
2279
-
2280
- // Scroll state management
2281
- const scrollStateRef = useRef({
2282
- isUserScrolling: false,
2283
- lastScrollTop: 0,
2284
- scrollUpCount: 0,
2285
- isNearBottom: true,
2286
- });
2189
+ if (!value) {
2190
+ return undefined;
2191
+ }
2287
2192
 
2288
- // Measurement cache
2289
- const measurementCache = useRef(
2290
- new Map<string, { height: number; offset: number }>()
2193
+ return rebuildStateShape({
2194
+ path: selectedItemKey.split('.').slice(1) as string[],
2195
+ componentId: componentId!,
2196
+ });
2197
+ };
2198
+ }
2199
+ if (prop === 'getSelectedIndex') {
2200
+ return () => {
2201
+ const selectedIndex = getGlobalStore
2202
+ .getState()
2203
+ .getSelectedIndex(
2204
+ stateKey + '.' + path.join('.'),
2205
+ meta?.validIds
2291
2206
  );
2292
2207
 
2293
- // Separate effect for handling rerender updates
2294
- useLayoutEffect(() => {
2295
- if (
2296
- !stickToBottom ||
2297
- !containerRef.current ||
2298
- scrollStateRef.current.isUserScrolling
2299
- )
2300
- return;
2208
+ return selectedIndex;
2209
+ };
2210
+ }
2211
+ if (prop === 'clearSelected') {
2212
+ notifySelectionComponents(stateKey, path);
2213
+ return () => {
2214
+ getGlobalStore.getState().clearSelectedIndex({
2215
+ arrayKey: stateKey + '.' + path.join('.'),
2216
+ });
2217
+ };
2218
+ }
2301
2219
 
2302
- const container = containerRef.current;
2303
- container.scrollTo({
2304
- top: container.scrollHeight,
2305
- behavior: initialScrollRef.current ? 'instant' : 'smooth',
2306
- });
2307
- }, [rerender, stickToBottom]);
2220
+ if (prop === 'useVirtualView') {
2221
+ return (
2222
+ options: VirtualViewOptions
2223
+ ): VirtualStateObjectResult<any[]> => {
2224
+ const {
2225
+ itemHeight = 50,
2226
+ overscan = 6,
2227
+ stickToBottom = false,
2228
+ scrollStickTolerance = 75,
2229
+ } = options;
2230
+
2231
+ const containerRef = useRef<HTMLDivElement | null>(null);
2232
+ const [range, setRange] = useState({
2233
+ startIndex: 0,
2234
+ endIndex: 10,
2235
+ });
2236
+ const [rerender, forceUpdate] = useState({});
2237
+ const initialScrollRef = useRef(true);
2238
+
2239
+ // Scroll state management
2240
+ const scrollStateRef = useRef({
2241
+ isUserScrolling: false,
2242
+ lastScrollTop: 0,
2243
+ scrollUpCount: 0,
2244
+ isNearBottom: true,
2245
+ });
2246
+
2247
+ // Measurement cache
2248
+ const measurementCache = useRef(
2249
+ new Map<string, { height: number; offset: number }>()
2250
+ );
2251
+
2252
+ // Separate effect for handling rerender updates
2253
+ useLayoutEffect(() => {
2254
+ if (
2255
+ !stickToBottom ||
2256
+ !containerRef.current ||
2257
+ scrollStateRef.current.isUserScrolling
2258
+ )
2259
+ return;
2308
2260
 
2309
- const arrayKeys =
2261
+ const container = containerRef.current;
2262
+ container.scrollTo({
2263
+ top: container.scrollHeight,
2264
+ behavior: initialScrollRef.current ? 'instant' : 'smooth',
2265
+ });
2266
+ }, [rerender, stickToBottom]);
2267
+
2268
+ const arrayKeys =
2269
+ getGlobalStore.getState().getShadowMetadata(stateKey, path)
2270
+ ?.arrayKeys || [];
2271
+
2272
+ // Calculate total height and offsets
2273
+ const { totalHeight, itemOffsets } = useMemo(() => {
2274
+ let runningOffset = 0;
2275
+ const offsets = new Map<
2276
+ string,
2277
+ { height: number; offset: number }
2278
+ >();
2279
+ const allItemKeys =
2310
2280
  getGlobalStore.getState().getShadowMetadata(stateKey, path)
2311
2281
  ?.arrayKeys || [];
2312
2282
 
2313
- // Calculate total height and offsets
2314
- const { totalHeight, itemOffsets } = useMemo(() => {
2315
- let runningOffset = 0;
2316
- const offsets = new Map<
2317
- string,
2318
- { height: number; offset: number }
2319
- >();
2320
- const allItemKeys =
2321
- getGlobalStore.getState().getShadowMetadata(stateKey, path)
2322
- ?.arrayKeys || [];
2323
-
2324
- allItemKeys.forEach((itemKey) => {
2325
- const itemPath = itemKey.split('.').slice(1);
2326
- const measuredHeight =
2327
- getGlobalStore
2328
- .getState()
2329
- .getShadowMetadata(stateKey, itemPath)?.virtualizer
2330
- ?.itemHeight || itemHeight;
2331
-
2332
- offsets.set(itemKey, {
2333
- height: measuredHeight,
2334
- offset: runningOffset,
2335
- });
2283
+ allItemKeys.forEach((itemKey) => {
2284
+ const itemPath = itemKey.split('.').slice(1);
2285
+ const measuredHeight =
2286
+ getGlobalStore
2287
+ .getState()
2288
+ .getShadowMetadata(stateKey, itemPath)?.virtualizer
2289
+ ?.itemHeight || itemHeight;
2336
2290
 
2337
- runningOffset += measuredHeight;
2291
+ offsets.set(itemKey, {
2292
+ height: measuredHeight,
2293
+ offset: runningOffset,
2338
2294
  });
2339
2295
 
2340
- measurementCache.current = offsets;
2341
- return { totalHeight: runningOffset, itemOffsets: offsets };
2342
- }, [arrayKeys.length, itemHeight]);
2343
-
2344
- // Improved initial positioning effect
2345
- useLayoutEffect(() => {
2346
- if (
2347
- stickToBottom &&
2348
- arrayKeys.length > 0 &&
2349
- containerRef.current &&
2350
- !scrollStateRef.current.isUserScrolling &&
2351
- initialScrollRef.current
2352
- ) {
2353
- const container = containerRef.current;
2354
-
2355
- // Wait for container to have dimensions
2356
- const waitForContainer = () => {
2357
- if (container.clientHeight > 0) {
2358
- const visibleCount = Math.ceil(
2359
- container.clientHeight / itemHeight
2360
- );
2361
- const endIndex = arrayKeys.length - 1;
2362
- const startIndex = Math.max(
2363
- 0,
2364
- endIndex - visibleCount - overscan
2365
- );
2366
-
2367
- setRange({ startIndex, endIndex });
2368
-
2369
- // Ensure scroll after range is set
2370
- requestAnimationFrame(() => {
2371
- scrollToBottom('instant');
2372
- initialScrollRef.current = false; // Mark initial scroll as done
2373
- });
2374
- } else {
2375
- // Container not ready, try again
2376
- requestAnimationFrame(waitForContainer);
2377
- }
2378
- };
2296
+ runningOffset += measuredHeight;
2297
+ });
2379
2298
 
2380
- waitForContainer();
2381
- }
2382
- }, [arrayKeys.length, stickToBottom, itemHeight, overscan]);
2299
+ measurementCache.current = offsets;
2300
+ return { totalHeight: runningOffset, itemOffsets: offsets };
2301
+ }, [arrayKeys.length, itemHeight]);
2383
2302
 
2384
- // Combined scroll handler
2385
- const handleScroll = useCallback(() => {
2303
+ // Improved initial positioning effect
2304
+ useLayoutEffect(() => {
2305
+ if (
2306
+ stickToBottom &&
2307
+ arrayKeys.length > 0 &&
2308
+ containerRef.current &&
2309
+ !scrollStateRef.current.isUserScrolling &&
2310
+ initialScrollRef.current
2311
+ ) {
2386
2312
  const container = containerRef.current;
2387
- if (!container) return;
2388
2313
 
2389
- const currentScrollTop = container.scrollTop;
2390
- const { scrollHeight, clientHeight } = container;
2391
- const scrollState = scrollStateRef.current;
2392
-
2393
- // Check if user is near bottom
2394
- const distanceFromBottom =
2395
- scrollHeight - (currentScrollTop + clientHeight);
2396
- const wasNearBottom = scrollState.isNearBottom;
2397
- scrollState.isNearBottom =
2398
- distanceFromBottom <= scrollStickTolerance;
2399
-
2400
- // Detect scroll direction
2401
- if (currentScrollTop < scrollState.lastScrollTop) {
2402
- // User scrolled up
2403
- scrollState.scrollUpCount++;
2404
-
2405
- if (scrollState.scrollUpCount > 3 && wasNearBottom) {
2406
- // User has deliberately scrolled away from bottom
2407
- scrollState.isUserScrolling = true;
2408
- console.log('User scrolled away from bottom');
2409
- }
2410
- } else if (scrollState.isNearBottom) {
2411
- // Reset if we're back near the bottom
2412
- scrollState.isUserScrolling = false;
2413
- scrollState.scrollUpCount = 0;
2414
- }
2314
+ // Wait for container to have dimensions
2315
+ const waitForContainer = () => {
2316
+ if (container.clientHeight > 0) {
2317
+ const visibleCount = Math.ceil(
2318
+ container.clientHeight / itemHeight
2319
+ );
2320
+ const endIndex = arrayKeys.length - 1;
2321
+ const startIndex = Math.max(
2322
+ 0,
2323
+ endIndex - visibleCount - overscan
2324
+ );
2415
2325
 
2416
- scrollState.lastScrollTop = currentScrollTop;
2326
+ setRange({ startIndex, endIndex });
2417
2327
 
2418
- // Update visible range
2419
- let newStartIndex = 0;
2420
- for (let i = 0; i < arrayKeys.length; i++) {
2421
- const itemKey = arrayKeys[i];
2422
- const item = measurementCache.current.get(itemKey!);
2423
- if (item && item.offset + item.height > currentScrollTop) {
2424
- newStartIndex = i;
2425
- break;
2328
+ // Ensure scroll after range is set
2329
+ requestAnimationFrame(() => {
2330
+ scrollToBottom('instant');
2331
+ initialScrollRef.current = false; // Mark initial scroll as done
2332
+ });
2333
+ } else {
2334
+ // Container not ready, try again
2335
+ requestAnimationFrame(waitForContainer);
2426
2336
  }
2427
- }
2337
+ };
2428
2338
 
2429
- // Only update if range actually changed
2430
- if (newStartIndex !== range.startIndex) {
2431
- const visibleCount = Math.ceil(clientHeight / itemHeight);
2432
- setRange({
2433
- startIndex: Math.max(0, newStartIndex - overscan),
2434
- endIndex: Math.min(
2435
- arrayKeys.length - 1,
2436
- newStartIndex + visibleCount + overscan
2437
- ),
2438
- });
2339
+ waitForContainer();
2340
+ }
2341
+ }, [arrayKeys.length, stickToBottom, itemHeight, overscan]);
2342
+
2343
+ // Combined scroll handler
2344
+ const handleScroll = useCallback(() => {
2345
+ const container = containerRef.current;
2346
+ if (!container) return;
2347
+
2348
+ const currentScrollTop = container.scrollTop;
2349
+ const { scrollHeight, clientHeight } = container;
2350
+ const scrollState = scrollStateRef.current;
2351
+
2352
+ // Check if user is near bottom
2353
+ const distanceFromBottom =
2354
+ scrollHeight - (currentScrollTop + clientHeight);
2355
+ const wasNearBottom = scrollState.isNearBottom;
2356
+ scrollState.isNearBottom =
2357
+ distanceFromBottom <= scrollStickTolerance;
2358
+
2359
+ // Detect scroll direction
2360
+ if (currentScrollTop < scrollState.lastScrollTop) {
2361
+ // User scrolled up
2362
+ scrollState.scrollUpCount++;
2363
+
2364
+ if (scrollState.scrollUpCount > 3 && wasNearBottom) {
2365
+ // User has deliberately scrolled away from bottom
2366
+ scrollState.isUserScrolling = true;
2367
+ console.log('User scrolled away from bottom');
2439
2368
  }
2440
- }, [
2441
- arrayKeys.length,
2442
- range.startIndex,
2443
- itemHeight,
2444
- overscan,
2445
- scrollStickTolerance,
2446
- ]);
2369
+ } else if (scrollState.isNearBottom) {
2370
+ // Reset if we're back near the bottom
2371
+ scrollState.isUserScrolling = false;
2372
+ scrollState.scrollUpCount = 0;
2373
+ }
2447
2374
 
2448
- // Set up scroll listener
2449
- useEffect(() => {
2450
- const container = containerRef.current;
2451
- if (!container || !stickToBottom) return;
2375
+ scrollState.lastScrollTop = currentScrollTop;
2376
+
2377
+ // Update visible range
2378
+ let newStartIndex = 0;
2379
+ for (let i = 0; i < arrayKeys.length; i++) {
2380
+ const itemKey = arrayKeys[i];
2381
+ const item = measurementCache.current.get(itemKey!);
2382
+ if (item && item.offset + item.height > currentScrollTop) {
2383
+ newStartIndex = i;
2384
+ break;
2385
+ }
2386
+ }
2452
2387
 
2453
- container.addEventListener('scroll', handleScroll, {
2454
- passive: true,
2388
+ // Only update if range actually changed
2389
+ if (newStartIndex !== range.startIndex) {
2390
+ const visibleCount = Math.ceil(clientHeight / itemHeight);
2391
+ setRange({
2392
+ startIndex: Math.max(0, newStartIndex - overscan),
2393
+ endIndex: Math.min(
2394
+ arrayKeys.length - 1,
2395
+ newStartIndex + visibleCount + overscan
2396
+ ),
2455
2397
  });
2398
+ }
2399
+ }, [
2400
+ arrayKeys.length,
2401
+ range.startIndex,
2402
+ itemHeight,
2403
+ overscan,
2404
+ scrollStickTolerance,
2405
+ ]);
2406
+
2407
+ // Set up scroll listener
2408
+ useEffect(() => {
2409
+ const container = containerRef.current;
2410
+ if (!container || !stickToBottom) return;
2411
+
2412
+ container.addEventListener('scroll', handleScroll, {
2413
+ passive: true,
2414
+ });
2456
2415
 
2457
- return () => {
2458
- container.removeEventListener('scroll', handleScroll);
2459
- };
2460
- }, [handleScroll, stickToBottom]);
2461
- const scrollToBottom = useCallback(
2462
- (behavior: ScrollBehavior = 'smooth') => {
2463
- const container = containerRef.current;
2464
- if (!container) return;
2465
-
2466
- // Reset scroll state
2467
- scrollStateRef.current.isUserScrolling = false;
2468
- scrollStateRef.current.isNearBottom = true;
2469
- scrollStateRef.current.scrollUpCount = 0;
2470
-
2471
- const performScroll = () => {
2472
- // Multiple attempts to ensure we hit the bottom
2473
- const attemptScroll = (attempts = 0) => {
2474
- if (attempts > 5) return; // Prevent infinite loops
2475
-
2476
- const currentHeight = container.scrollHeight;
2477
- const currentScroll = container.scrollTop;
2478
- const clientHeight = container.clientHeight;
2479
-
2480
- // Check if we're already at the bottom
2481
- if (currentScroll + clientHeight >= currentHeight - 1) {
2482
- return;
2483
- }
2416
+ return () => {
2417
+ container.removeEventListener('scroll', handleScroll);
2418
+ };
2419
+ }, [handleScroll, stickToBottom]);
2420
+ const scrollToBottom = useCallback(
2421
+ (behavior: ScrollBehavior = 'smooth') => {
2422
+ const container = containerRef.current;
2423
+ if (!container) return;
2484
2424
 
2485
- container.scrollTo({
2486
- top: currentHeight,
2487
- behavior: behavior,
2488
- });
2425
+ // Reset scroll state
2426
+ scrollStateRef.current.isUserScrolling = false;
2427
+ scrollStateRef.current.isNearBottom = true;
2428
+ scrollStateRef.current.scrollUpCount = 0;
2489
2429
 
2490
- // In slow environments, check again after a short delay
2491
- setTimeout(() => {
2492
- const newHeight = container.scrollHeight;
2493
- const newScroll = container.scrollTop;
2430
+ const performScroll = () => {
2431
+ // Multiple attempts to ensure we hit the bottom
2432
+ const attemptScroll = (attempts = 0) => {
2433
+ if (attempts > 5) return; // Prevent infinite loops
2494
2434
 
2495
- // If height changed or we're not at bottom, try again
2496
- if (
2497
- newHeight !== currentHeight ||
2498
- newScroll + clientHeight < newHeight - 1
2499
- ) {
2500
- attemptScroll(attempts + 1);
2501
- }
2502
- }, 50);
2503
- };
2435
+ const currentHeight = container.scrollHeight;
2436
+ const currentScroll = container.scrollTop;
2437
+ const clientHeight = container.clientHeight;
2504
2438
 
2505
- attemptScroll();
2506
- };
2439
+ // Check if we're already at the bottom
2440
+ if (currentScroll + clientHeight >= currentHeight - 1) {
2441
+ return;
2442
+ }
2507
2443
 
2508
- // Use requestIdleCallback for better performance in slow environments
2509
- if ('requestIdleCallback' in window) {
2510
- requestIdleCallback(performScroll, { timeout: 100 });
2511
- } else {
2512
- // Fallback to rAF chain
2513
- requestAnimationFrame(() => {
2514
- requestAnimationFrame(performScroll);
2444
+ container.scrollTo({
2445
+ top: currentHeight,
2446
+ behavior: behavior,
2515
2447
  });
2516
- }
2517
- },
2518
- []
2519
- );
2520
- // Auto-scroll to bottom when new content arrives
2521
- // Consolidated auto-scroll effect with debouncing
2522
- useEffect(() => {
2523
- if (!stickToBottom || !containerRef.current) return;
2524
-
2525
- const container = containerRef.current;
2526
- const scrollState = scrollStateRef.current;
2527
2448
 
2528
- // Debounced scroll function
2529
- let scrollTimeout: NodeJS.Timeout;
2530
- const debouncedScrollToBottom = () => {
2531
- clearTimeout(scrollTimeout);
2532
- scrollTimeout = setTimeout(() => {
2533
- if (
2534
- !scrollState.isUserScrolling &&
2535
- scrollState.isNearBottom
2536
- ) {
2537
- scrollToBottom(
2538
- initialScrollRef.current ? 'instant' : 'smooth'
2539
- );
2540
- }
2541
- }, 100);
2542
- };
2449
+ // In slow environments, check again after a short delay
2450
+ setTimeout(() => {
2451
+ const newHeight = container.scrollHeight;
2452
+ const newScroll = container.scrollTop;
2543
2453
 
2544
- // Single MutationObserver for all DOM changes
2545
- const observer = new MutationObserver(() => {
2546
- if (!scrollState.isUserScrolling) {
2547
- debouncedScrollToBottom();
2548
- }
2549
- });
2454
+ // If height changed or we're not at bottom, try again
2455
+ if (
2456
+ newHeight !== currentHeight ||
2457
+ newScroll + clientHeight < newHeight - 1
2458
+ ) {
2459
+ attemptScroll(attempts + 1);
2460
+ }
2461
+ }, 50);
2462
+ };
2550
2463
 
2551
- observer.observe(container, {
2552
- childList: true,
2553
- subtree: true,
2554
- attributes: true,
2555
- attributeFilter: ['style', 'class'], // More specific than just 'height'
2556
- });
2464
+ attemptScroll();
2465
+ };
2557
2466
 
2558
- // Handle image loads with event delegation
2559
- const handleImageLoad = (e: Event) => {
2467
+ // Use requestIdleCallback for better performance in slow environments
2468
+ if ('requestIdleCallback' in window) {
2469
+ requestIdleCallback(performScroll, { timeout: 100 });
2470
+ } else {
2471
+ // Fallback to rAF chain
2472
+ requestAnimationFrame(() => {
2473
+ requestAnimationFrame(performScroll);
2474
+ });
2475
+ }
2476
+ },
2477
+ []
2478
+ );
2479
+ // Auto-scroll to bottom when new content arrives
2480
+ // Consolidated auto-scroll effect with debouncing
2481
+ useEffect(() => {
2482
+ if (!stickToBottom || !containerRef.current) return;
2483
+
2484
+ const container = containerRef.current;
2485
+ const scrollState = scrollStateRef.current;
2486
+
2487
+ // Debounced scroll function
2488
+ let scrollTimeout: NodeJS.Timeout;
2489
+ const debouncedScrollToBottom = () => {
2490
+ clearTimeout(scrollTimeout);
2491
+ scrollTimeout = setTimeout(() => {
2560
2492
  if (
2561
- e.target instanceof HTMLImageElement &&
2562
- !scrollState.isUserScrolling
2493
+ !scrollState.isUserScrolling &&
2494
+ scrollState.isNearBottom
2563
2495
  ) {
2564
- debouncedScrollToBottom();
2496
+ scrollToBottom(
2497
+ initialScrollRef.current ? 'instant' : 'smooth'
2498
+ );
2565
2499
  }
2566
- };
2500
+ }, 100);
2501
+ };
2502
+
2503
+ // Single MutationObserver for all DOM changes
2504
+ const observer = new MutationObserver(() => {
2505
+ if (!scrollState.isUserScrolling) {
2506
+ debouncedScrollToBottom();
2507
+ }
2508
+ });
2567
2509
 
2568
- container.addEventListener('load', handleImageLoad, true);
2510
+ observer.observe(container, {
2511
+ childList: true,
2512
+ subtree: true,
2513
+ attributes: true,
2514
+ attributeFilter: ['style', 'class'], // More specific than just 'height'
2515
+ });
2569
2516
 
2570
- // Initial scroll with proper timing
2571
- if (initialScrollRef.current) {
2572
- // For initial load, wait for next tick to ensure DOM is ready
2573
- setTimeout(() => {
2574
- scrollToBottom('instant');
2575
- }, 0);
2576
- } else {
2517
+ // Handle image loads with event delegation
2518
+ const handleImageLoad = (e: Event) => {
2519
+ if (
2520
+ e.target instanceof HTMLImageElement &&
2521
+ !scrollState.isUserScrolling
2522
+ ) {
2577
2523
  debouncedScrollToBottom();
2578
2524
  }
2525
+ };
2579
2526
 
2580
- return () => {
2581
- clearTimeout(scrollTimeout);
2582
- observer.disconnect();
2583
- container.removeEventListener('load', handleImageLoad, true);
2584
- };
2585
- }, [stickToBottom, arrayKeys.length, scrollToBottom]);
2586
- // Create virtual state
2587
- const virtualState = useMemo(() => {
2588
- const store = getGlobalStore.getState();
2589
- const sourceArray = store.getShadowValue(
2590
- [stateKey, ...path].join('.')
2591
- ) as any[];
2592
- const currentKeys =
2593
- store.getShadowMetadata(stateKey, path)?.arrayKeys || [];
2594
-
2595
- const slicedArray = sourceArray.slice(
2596
- range.startIndex,
2597
- range.endIndex + 1
2598
- );
2599
- const slicedIds = currentKeys.slice(
2600
- range.startIndex,
2601
- range.endIndex + 1
2602
- );
2527
+ container.addEventListener('load', handleImageLoad, true);
2603
2528
 
2604
- return rebuildStateShape({
2605
- currentState: slicedArray as any,
2606
- path,
2607
- componentId: componentId!,
2608
- meta: { ...meta, validIds: slicedIds },
2609
- });
2610
- }, [range.startIndex, range.endIndex, arrayKeys.length]);
2611
-
2612
- return {
2613
- virtualState,
2614
- virtualizerProps: {
2615
- outer: {
2616
- ref: containerRef,
2617
- style: {
2618
- overflowY: 'auto',
2619
- height: '100%',
2620
- position: 'relative',
2621
- },
2622
- },
2623
- inner: {
2624
- style: {
2625
- height: `${totalHeight}px`,
2626
- position: 'relative',
2627
- },
2529
+ // Initial scroll with proper timing
2530
+ if (initialScrollRef.current) {
2531
+ // For initial load, wait for next tick to ensure DOM is ready
2532
+ setTimeout(() => {
2533
+ scrollToBottom('instant');
2534
+ }, 0);
2535
+ } else {
2536
+ debouncedScrollToBottom();
2537
+ }
2538
+
2539
+ return () => {
2540
+ clearTimeout(scrollTimeout);
2541
+ observer.disconnect();
2542
+ container.removeEventListener('load', handleImageLoad, true);
2543
+ };
2544
+ }, [stickToBottom, arrayKeys.length, scrollToBottom]);
2545
+ // Create virtual state
2546
+ const virtualState = useMemo(() => {
2547
+ const store = getGlobalStore.getState();
2548
+ const sourceArray = store.getShadowValue(
2549
+ [stateKey, ...path].join('.')
2550
+ ) as any[];
2551
+ const currentKeys =
2552
+ store.getShadowMetadata(stateKey, path)?.arrayKeys || [];
2553
+
2554
+ const slicedArray = sourceArray.slice(
2555
+ range.startIndex,
2556
+ range.endIndex + 1
2557
+ );
2558
+ const slicedIds = currentKeys.slice(
2559
+ range.startIndex,
2560
+ range.endIndex + 1
2561
+ );
2562
+
2563
+ return rebuildStateShape({
2564
+ path,
2565
+ componentId: componentId!,
2566
+ meta: { ...meta, validIds: slicedIds },
2567
+ });
2568
+ }, [range.startIndex, range.endIndex, arrayKeys.length]);
2569
+
2570
+ return {
2571
+ virtualState,
2572
+ virtualizerProps: {
2573
+ outer: {
2574
+ ref: containerRef,
2575
+ style: {
2576
+ overflowY: 'auto',
2577
+ height: '100%',
2578
+ position: 'relative',
2628
2579
  },
2629
- list: {
2630
- style: {
2631
- transform: `translateY(${
2632
- measurementCache.current.get(
2633
- arrayKeys[range.startIndex]!
2634
- )?.offset || 0
2635
- }px)`,
2636
- },
2580
+ },
2581
+ inner: {
2582
+ style: {
2583
+ height: `${totalHeight}px`,
2584
+ position: 'relative',
2637
2585
  },
2638
2586
  },
2639
- scrollToBottom,
2640
- scrollToIndex: (
2641
- index: number,
2642
- behavior: ScrollBehavior = 'smooth'
2643
- ) => {
2644
- if (containerRef.current && arrayKeys[index]) {
2645
- const offset =
2646
- measurementCache.current.get(arrayKeys[index]!)?.offset ||
2647
- 0;
2648
- containerRef.current.scrollTo({ top: offset, behavior });
2649
- }
2587
+ list: {
2588
+ style: {
2589
+ transform: `translateY(${
2590
+ measurementCache.current.get(arrayKeys[range.startIndex]!)
2591
+ ?.offset || 0
2592
+ }px)`,
2593
+ },
2650
2594
  },
2651
- };
2652
- };
2653
- }
2654
- if (prop === 'stateMap') {
2655
- return (
2656
- callbackfn: (
2657
- setter: any,
2595
+ },
2596
+ scrollToBottom,
2597
+ scrollToIndex: (
2658
2598
  index: number,
2599
+ behavior: ScrollBehavior = 'smooth'
2600
+ ) => {
2601
+ if (containerRef.current && arrayKeys[index]) {
2602
+ const offset =
2603
+ measurementCache.current.get(arrayKeys[index]!)?.offset ||
2604
+ 0;
2605
+ containerRef.current.scrollTo({ top: offset, behavior });
2606
+ }
2607
+ },
2608
+ };
2609
+ };
2610
+ }
2611
+ if (prop === 'stateMap') {
2612
+ return (
2613
+ callbackfn: (
2614
+ setter: any,
2615
+ index: number,
2616
+
2617
+ arraySetter: any
2618
+ ) => void
2619
+ ) => {
2620
+ const [arrayKeys, setArrayKeys] = useState<any>(
2621
+ meta?.validIds ??
2622
+ getGlobalStore.getState().getShadowMetadata(stateKey, path)
2623
+ ?.arrayKeys
2624
+ );
2625
+ // getGlobalStore.getState().subscribeToPath(stateKeyPathKey, () => {
2626
+ // console.log(
2627
+ // "stateKeyPathKeyccccccccccccccccc",
2628
+ // stateKeyPathKey
2629
+ // );
2630
+ // setArrayKeys(
2631
+ // getGlobalStore.getState().getShadowMetadata(stateKey, path)
2632
+ // );
2633
+ // });
2634
+
2635
+ const shadowValue = getGlobalStore
2636
+ .getState()
2637
+ .getShadowValue(stateKeyPathKey, meta?.validIds) as any[];
2638
+ if (!arrayKeys) {
2639
+ throw new Error('No array keys found for mapping');
2640
+ }
2641
+ const arraySetter = rebuildStateShape({
2642
+ path,
2643
+ componentId: componentId!,
2644
+ meta,
2645
+ });
2659
2646
 
2660
- arraySetter: any
2661
- ) => void
2662
- ) => {
2663
- const [arrayKeys, setArrayKeys] = useState<any>(
2664
- meta?.validIds ??
2665
- getGlobalStore.getState().getShadowMetadata(stateKey, path)
2666
- ?.arrayKeys
2667
- );
2668
- // getGlobalStore.getState().subscribeToPath(stateKeyPathKey, () => {
2669
- // console.log(
2670
- // "stateKeyPathKeyccccccccccccccccc",
2671
- // stateKeyPathKey
2672
- // );
2673
- // setArrayKeys(
2674
- // getGlobalStore.getState().getShadowMetadata(stateKey, path)
2675
- // );
2676
- // });
2677
-
2678
- const shadowValue = getGlobalStore
2679
- .getState()
2680
- .getShadowValue(stateKeyPathKey, meta?.validIds) as any[];
2681
- if (!arrayKeys) {
2682
- throw new Error('No array keys found for mapping');
2683
- }
2684
- const arraySetter = rebuildStateShape({
2685
- currentState: shadowValue as any,
2686
- path,
2647
+ return shadowValue.map((item, index) => {
2648
+ const itemPath = arrayKeys[index]?.split('.').slice(1);
2649
+ const itemSetter = rebuildStateShape({
2650
+ path: itemPath as any,
2687
2651
  componentId: componentId!,
2688
2652
  meta,
2689
2653
  });
2690
2654
 
2691
- return shadowValue.map((item, index) => {
2692
- const itemPath = arrayKeys[index]?.split('.').slice(1);
2693
- const itemSetter = rebuildStateShape({
2694
- currentState: item,
2695
- path: itemPath as any,
2696
- componentId: componentId!,
2697
- meta,
2698
- });
2699
-
2700
- return callbackfn(
2701
- itemSetter,
2702
- index,
2655
+ return callbackfn(
2656
+ itemSetter,
2657
+ index,
2703
2658
 
2704
- arraySetter
2705
- );
2706
- });
2707
- };
2708
- }
2659
+ arraySetter
2660
+ );
2661
+ });
2662
+ };
2663
+ }
2709
2664
 
2710
- if (prop === '$stateMap') {
2711
- return (callbackfn: any) =>
2712
- createElement(SignalMapRenderer, {
2713
- proxy: {
2714
- _stateKey: stateKey,
2715
- _path: path,
2716
- _mapFn: callbackfn,
2717
- _meta: meta,
2718
- },
2719
- rebuildStateShape,
2720
- });
2721
- } // In createProxyHandler -> handler -> get -> if (Array.isArray(currentState))
2665
+ if (prop === '$stateMap') {
2666
+ return (callbackfn: any) =>
2667
+ createElement(SignalMapRenderer, {
2668
+ proxy: {
2669
+ _stateKey: stateKey,
2670
+ _path: path,
2671
+ _mapFn: callbackfn,
2672
+ _meta: meta,
2673
+ },
2674
+ rebuildStateShape,
2675
+ });
2676
+ } // In createProxyHandler -> handler -> get -> if (Array.isArray(currentState))
2677
+
2678
+ if (prop === 'stateFind') {
2679
+ return (
2680
+ callbackfn: (value: any, index: number) => boolean
2681
+ ): StateObject<any> | undefined => {
2682
+ // 1. Use the correct set of keys: filtered/sorted from meta, or all keys from the store.
2683
+ const arrayKeys =
2684
+ meta?.validIds ??
2685
+ getGlobalStore.getState().getShadowMetadata(stateKey, path)
2686
+ ?.arrayKeys;
2687
+
2688
+ if (!arrayKeys) {
2689
+ return undefined;
2690
+ }
2722
2691
 
2723
- if (prop === 'stateFind') {
2724
- return (
2725
- callbackfn: (value: any, index: number) => boolean
2726
- ): StateObject<any> | undefined => {
2727
- // 1. Use the correct set of keys: filtered/sorted from meta, or all keys from the store.
2728
- const arrayKeys =
2729
- meta?.validIds ??
2730
- getGlobalStore.getState().getShadowMetadata(stateKey, path)
2731
- ?.arrayKeys;
2692
+ // 2. Iterate through the keys, get the value for each, and run the callback.
2693
+ for (let i = 0; i < arrayKeys.length; i++) {
2694
+ const itemKey = arrayKeys[i];
2695
+ if (!itemKey) continue; // Safety check
2732
2696
 
2733
- if (!arrayKeys) {
2734
- return undefined;
2735
- }
2697
+ const itemValue = getGlobalStore
2698
+ .getState()
2699
+ .getShadowValue(itemKey);
2736
2700
 
2737
- // 2. Iterate through the keys, get the value for each, and run the callback.
2738
- for (let i = 0; i < arrayKeys.length; i++) {
2739
- const itemKey = arrayKeys[i];
2740
- if (!itemKey) continue; // Safety check
2701
+ // 3. If the callback returns true, we've found our item.
2702
+ if (callbackfn(itemValue, i)) {
2703
+ // Get the item's path relative to the stateKey (e.g., ['messages', '42'] -> ['42'])
2704
+ const itemPath = itemKey.split('.').slice(1);
2741
2705
 
2742
- const itemValue = getGlobalStore
2743
- .getState()
2744
- .getShadowValue(itemKey);
2745
-
2746
- // 3. If the callback returns true, we've found our item.
2747
- if (callbackfn(itemValue, i)) {
2748
- // Get the item's path relative to the stateKey (e.g., ['messages', '42'] -> ['42'])
2749
- const itemPath = itemKey.split('.').slice(1);
2750
-
2751
- // 4. Rebuild a new, fully functional StateObject for just that item and return it.
2752
- return rebuildStateShape({
2753
- currentState: itemValue,
2754
- path: itemPath,
2755
- componentId: componentId,
2756
- meta, // Pass along meta for potential further chaining
2757
- });
2758
- }
2706
+ // 4. Rebuild a new, fully functional StateObject for just that item and return it.
2707
+ return rebuildStateShape({
2708
+ path: itemPath,
2709
+ componentId: componentId,
2710
+ meta, // Pass along meta for potential further chaining
2711
+ });
2759
2712
  }
2713
+ }
2760
2714
 
2761
- // 5. If the loop finishes without finding anything, return undefined.
2762
- return undefined;
2763
- };
2764
- }
2765
- if (prop === 'stateFilter') {
2766
- return (callbackfn: (value: any, index: number) => boolean) => {
2767
- const arrayKeys =
2768
- meta?.validIds ??
2769
- getGlobalStore.getState().getShadowMetadata(stateKey, path)
2770
- ?.arrayKeys;
2771
-
2772
- if (!arrayKeys) {
2773
- throw new Error('No array keys found for filtering.');
2774
- }
2715
+ // 5. If the loop finishes without finding anything, return undefined.
2716
+ return undefined;
2717
+ };
2718
+ }
2719
+ if (prop === 'stateFilter') {
2720
+ return (callbackfn: (value: any, index: number) => boolean) => {
2721
+ const currentState = getGlobalStore
2722
+ .getState()
2723
+ .getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
2724
+ if (!Array.isArray(currentState)) return [];
2725
+ const arrayKeys =
2726
+ meta?.validIds ??
2727
+ getGlobalStore.getState().getShadowMetadata(stateKey, path)
2728
+ ?.arrayKeys;
2729
+
2730
+ if (!arrayKeys) {
2731
+ throw new Error('No array keys found for filtering.');
2732
+ }
2775
2733
 
2776
- const newValidIds: string[] = [];
2777
- const filteredArray = currentState.filter(
2778
- (val: any, index: number) => {
2779
- const didPass = callbackfn(val, index);
2780
- if (didPass) {
2781
- newValidIds.push(arrayKeys[index]!);
2782
- return true;
2783
- }
2784
- return false;
2734
+ const newValidIds: string[] = [];
2735
+ const filteredArray = currentState.filter(
2736
+ (val: any, index: number) => {
2737
+ const didPass = callbackfn(val, index);
2738
+ if (didPass) {
2739
+ newValidIds.push(arrayKeys[index]!);
2740
+ return true;
2785
2741
  }
2786
- );
2742
+ return false;
2743
+ }
2744
+ );
2787
2745
 
2788
- return rebuildStateShape({
2789
- currentState: filteredArray as any,
2790
- path,
2791
- componentId: componentId!,
2792
- meta: {
2793
- validIds: newValidIds,
2794
- transforms: [
2795
- ...(meta?.transforms || []),
2796
- {
2797
- type: 'filter',
2798
- fn: callbackfn,
2799
- },
2800
- ],
2801
- },
2802
- });
2803
- };
2804
- }
2805
- if (prop === 'stateSort') {
2806
- return (compareFn: (a: any, b: any) => number) => {
2807
- const arrayKeys =
2808
- meta?.validIds ??
2809
- getGlobalStore.getState().getShadowMetadata(stateKey, path)
2810
- ?.arrayKeys;
2811
- if (!arrayKeys) {
2812
- throw new Error('No array keys found for sorting');
2746
+ return rebuildStateShape({
2747
+ path,
2748
+ componentId: componentId!,
2749
+ meta: {
2750
+ validIds: newValidIds,
2751
+ transforms: [
2752
+ ...(meta?.transforms || []),
2753
+ {
2754
+ type: 'filter',
2755
+ fn: callbackfn,
2756
+ },
2757
+ ],
2758
+ },
2759
+ });
2760
+ };
2761
+ }
2762
+ if (prop === 'stateSort') {
2763
+ return (compareFn: (a: any, b: any) => number) => {
2764
+ const currentState = getGlobalStore
2765
+ .getState()
2766
+ .getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
2767
+ if (!Array.isArray(currentState)) return []; // Guard clause
2768
+ const arrayKeys =
2769
+ meta?.validIds ??
2770
+ getGlobalStore.getState().getShadowMetadata(stateKey, path)
2771
+ ?.arrayKeys;
2772
+ if (!arrayKeys) {
2773
+ throw new Error('No array keys found for sorting');
2774
+ }
2775
+ const itemsWithIds = currentState.map((item, index) => ({
2776
+ item,
2777
+ key: arrayKeys[index],
2778
+ }));
2779
+
2780
+ itemsWithIds
2781
+ .sort((a, b) => compareFn(a.item, b.item))
2782
+ .filter(Boolean);
2783
+
2784
+ return rebuildStateShape({
2785
+ path,
2786
+ componentId: componentId!,
2787
+ meta: {
2788
+ validIds: itemsWithIds.map((i) => i.key) as string[],
2789
+ transforms: [
2790
+ ...(meta?.transforms || []),
2791
+ { type: 'sort', fn: compareFn },
2792
+ ],
2793
+ },
2794
+ });
2795
+ };
2796
+ }
2797
+ // In createProxyHandler, inside the get trap where you have other array methods:
2798
+ if (prop === 'stream') {
2799
+ return function <U = InferArrayElement<T>, R = U>(
2800
+ options: StreamOptions<U, R> = {}
2801
+ ): StreamHandle<U> {
2802
+ const {
2803
+ bufferSize = 100,
2804
+ flushInterval = 100,
2805
+ bufferStrategy = 'accumulate',
2806
+ store,
2807
+ onFlush,
2808
+ } = options;
2809
+
2810
+ let buffer: U[] = [];
2811
+ let isPaused = false;
2812
+ let flushTimer: NodeJS.Timeout | null = null;
2813
+
2814
+ const addToBuffer = (item: U) => {
2815
+ if (isPaused) return;
2816
+
2817
+ if (bufferStrategy === 'sliding' && buffer.length >= bufferSize) {
2818
+ buffer.shift();
2819
+ } else if (
2820
+ bufferStrategy === 'dropping' &&
2821
+ buffer.length >= bufferSize
2822
+ ) {
2823
+ return;
2813
2824
  }
2814
- const itemsWithIds = currentState.map((item, index) => ({
2815
- item,
2816
- key: arrayKeys[index],
2817
- }));
2818
2825
 
2819
- itemsWithIds
2820
- .sort((a, b) => compareFn(a.item, b.item))
2821
- .filter(Boolean);
2826
+ buffer.push(item);
2822
2827
 
2823
- return rebuildStateShape({
2824
- currentState: itemsWithIds.map((i) => i.item) as any,
2825
- path,
2826
- componentId: componentId!,
2827
- meta: {
2828
- validIds: itemsWithIds.map((i) => i.key) as string[],
2829
- transforms: [
2830
- ...(meta?.transforms || []),
2831
- { type: 'sort', fn: compareFn },
2832
- ],
2833
- },
2834
- });
2828
+ if (buffer.length >= bufferSize) {
2829
+ flushBuffer();
2830
+ }
2835
2831
  };
2836
- }
2837
- // In createProxyHandler, inside the get trap where you have other array methods:
2838
- if (prop === 'stream') {
2839
- return function <U = InferArrayElement<T>, R = U>(
2840
- options: StreamOptions<U, R> = {}
2841
- ): StreamHandle<U> {
2842
- const {
2843
- bufferSize = 100,
2844
- flushInterval = 100,
2845
- bufferStrategy = 'accumulate',
2846
- store,
2847
- onFlush,
2848
- } = options;
2849
-
2850
- let buffer: U[] = [];
2851
- let isPaused = false;
2852
- let flushTimer: NodeJS.Timeout | null = null;
2853
-
2854
- const addToBuffer = (item: U) => {
2855
- if (isPaused) return;
2856
-
2857
- if (
2858
- bufferStrategy === 'sliding' &&
2859
- buffer.length >= bufferSize
2860
- ) {
2861
- buffer.shift();
2862
- } else if (
2863
- bufferStrategy === 'dropping' &&
2864
- buffer.length >= bufferSize
2865
- ) {
2866
- return;
2867
- }
2868
2832
 
2869
- buffer.push(item);
2833
+ const flushBuffer = () => {
2834
+ if (buffer.length === 0) return;
2870
2835
 
2871
- if (buffer.length >= bufferSize) {
2872
- flushBuffer();
2873
- }
2874
- };
2875
-
2876
- const flushBuffer = () => {
2877
- if (buffer.length === 0) return;
2836
+ const toFlush = [...buffer];
2837
+ buffer = [];
2878
2838
 
2879
- const toFlush = [...buffer];
2880
- buffer = [];
2881
-
2882
- if (store) {
2883
- const result = store(toFlush);
2884
- if (result !== undefined) {
2885
- const items = Array.isArray(result) ? result : [result];
2886
- items.forEach((item) => {
2887
- effectiveSetState(item as any, path, {
2888
- updateType: 'insert',
2889
- });
2890
- });
2891
- }
2892
- } else {
2893
- toFlush.forEach((item) => {
2839
+ if (store) {
2840
+ const result = store(toFlush);
2841
+ if (result !== undefined) {
2842
+ const items = Array.isArray(result) ? result : [result];
2843
+ items.forEach((item) => {
2894
2844
  effectiveSetState(item as any, path, {
2895
2845
  updateType: 'insert',
2896
2846
  });
2897
2847
  });
2898
2848
  }
2849
+ } else {
2850
+ toFlush.forEach((item) => {
2851
+ effectiveSetState(item as any, path, {
2852
+ updateType: 'insert',
2853
+ });
2854
+ });
2855
+ }
2899
2856
 
2900
- onFlush?.(toFlush);
2901
- };
2857
+ onFlush?.(toFlush);
2858
+ };
2902
2859
 
2903
- if (flushInterval > 0) {
2904
- flushTimer = setInterval(flushBuffer, flushInterval);
2905
- }
2860
+ if (flushInterval > 0) {
2861
+ flushTimer = setInterval(flushBuffer, flushInterval);
2862
+ }
2906
2863
 
2907
- const streamId = uuidv4();
2908
- const currentMeta =
2909
- getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
2910
- {};
2911
- const streams = currentMeta.streams || new Map();
2912
- streams.set(streamId, { buffer, flushTimer });
2864
+ const streamId = uuidv4();
2865
+ const currentMeta =
2866
+ getGlobalStore.getState().getShadowMetadata(stateKey, path) || {};
2867
+ const streams = currentMeta.streams || new Map();
2868
+ streams.set(streamId, { buffer, flushTimer });
2913
2869
 
2914
- getGlobalStore.getState().setShadowMetadata(stateKey, path, {
2915
- ...currentMeta,
2916
- streams,
2917
- });
2870
+ getGlobalStore.getState().setShadowMetadata(stateKey, path, {
2871
+ ...currentMeta,
2872
+ streams,
2873
+ });
2918
2874
 
2919
- return {
2920
- write: (data: U) => addToBuffer(data),
2921
- writeMany: (data: U[]) => data.forEach(addToBuffer),
2922
- flush: () => flushBuffer(),
2923
- pause: () => {
2924
- isPaused = true;
2925
- },
2926
- resume: () => {
2927
- isPaused = false;
2928
- if (buffer.length > 0) flushBuffer();
2929
- },
2930
- close: () => {
2931
- flushBuffer();
2932
- if (flushTimer) clearInterval(flushTimer);
2875
+ return {
2876
+ write: (data: U) => addToBuffer(data),
2877
+ writeMany: (data: U[]) => data.forEach(addToBuffer),
2878
+ flush: () => flushBuffer(),
2879
+ pause: () => {
2880
+ isPaused = true;
2881
+ },
2882
+ resume: () => {
2883
+ isPaused = false;
2884
+ if (buffer.length > 0) flushBuffer();
2885
+ },
2886
+ close: () => {
2887
+ flushBuffer();
2888
+ if (flushTimer) clearInterval(flushTimer);
2933
2889
 
2934
- const meta = getGlobalStore
2935
- .getState()
2936
- .getShadowMetadata(stateKey, path);
2937
- if (meta?.streams) {
2938
- meta.streams.delete(streamId);
2939
- }
2940
- },
2941
- };
2890
+ const meta = getGlobalStore
2891
+ .getState()
2892
+ .getShadowMetadata(stateKey, path);
2893
+ if (meta?.streams) {
2894
+ meta.streams.delete(streamId);
2895
+ }
2896
+ },
2942
2897
  };
2943
- }
2898
+ };
2899
+ }
2944
2900
 
2945
- if (prop === 'stateList') {
2946
- return (
2947
- callbackfn: (
2948
- setter: any,
2949
- index: number,
2950
- arraySetter: any
2951
- ) => ReactNode
2952
- ) => {
2953
- const StateListWrapper = () => {
2954
- const componentIdsRef = useRef<Map<string, string>>(new Map());
2901
+ if (prop === 'stateList') {
2902
+ return (
2903
+ callbackfn: (
2904
+ setter: any,
2905
+ index: number,
2906
+ arraySetter: any
2907
+ ) => ReactNode
2908
+ ) => {
2909
+ const StateListWrapper = () => {
2910
+ const componentIdsRef = useRef<Map<string, string>>(new Map());
2911
+
2912
+ const cacheKey =
2913
+ meta?.transforms && meta.transforms.length > 0
2914
+ ? `${componentId}-${hashTransforms(meta.transforms)}`
2915
+ : `${componentId}-base`;
2916
+
2917
+ const [updateTrigger, forceUpdate] = useState({});
2918
+
2919
+ const { validIds, arrayValues } = useMemo(() => {
2920
+ const cached = getGlobalStore
2921
+ .getState()
2922
+ .getShadowMetadata(stateKey, path)
2923
+ ?.transformCaches?.get(cacheKey);
2955
2924
 
2956
- const cacheKey =
2957
- meta?.transforms && meta.transforms.length > 0
2958
- ? `${componentId}-${hashTransforms(meta.transforms)}`
2959
- : `${componentId}-base`;
2925
+ let freshValidIds: string[];
2960
2926
 
2961
- const [updateTrigger, forceUpdate] = useState({});
2927
+ if (cached && cached.validIds) {
2928
+ freshValidIds = cached.validIds;
2929
+ } else {
2930
+ freshValidIds = applyTransforms(
2931
+ stateKey,
2932
+ path,
2933
+ meta?.transforms
2934
+ );
2962
2935
 
2963
- const { validIds, arrayValues } = useMemo(() => {
2964
- const cached = getGlobalStore
2936
+ getGlobalStore
2965
2937
  .getState()
2966
- .getShadowMetadata(stateKey, path)
2967
- ?.transformCaches?.get(cacheKey);
2968
-
2969
- let freshValidIds: string[];
2970
-
2971
- if (cached && cached.validIds) {
2972
- freshValidIds = cached.validIds;
2973
- } else {
2974
- freshValidIds = applyTransforms(
2975
- stateKey,
2976
- path,
2977
- meta?.transforms
2978
- );
2979
-
2980
- getGlobalStore
2981
- .getState()
2982
- .setTransformCache(stateKey, path, cacheKey, {
2983
- validIds: freshValidIds,
2984
- computedAt: Date.now(),
2985
- transforms: meta?.transforms || [],
2986
- });
2987
- }
2938
+ .setTransformCache(stateKey, path, cacheKey, {
2939
+ validIds: freshValidIds,
2940
+ computedAt: Date.now(),
2941
+ transforms: meta?.transforms || [],
2942
+ });
2943
+ }
2988
2944
 
2989
- const freshValues = getGlobalStore
2990
- .getState()
2991
- .getShadowValue(stateKeyPathKey, freshValidIds);
2945
+ const freshValues = getGlobalStore
2946
+ .getState()
2947
+ .getShadowValue(stateKeyPathKey, freshValidIds);
2992
2948
 
2993
- return {
2994
- validIds: freshValidIds,
2995
- arrayValues: freshValues || [],
2996
- };
2997
- }, [cacheKey, updateTrigger]);
2949
+ return {
2950
+ validIds: freshValidIds,
2951
+ arrayValues: freshValues || [],
2952
+ };
2953
+ }, [cacheKey, updateTrigger]);
2998
2954
 
2999
- useEffect(() => {
3000
- const unsubscribe = getGlobalStore
3001
- .getState()
3002
- .subscribeToPath(stateKeyPathKey, (e) => {
3003
- // A data change has occurred for the source array.
2955
+ useEffect(() => {
2956
+ const unsubscribe = getGlobalStore
2957
+ .getState()
2958
+ .subscribeToPath(stateKeyPathKey, (e) => {
2959
+ // A data change has occurred for the source array.
3004
2960
 
3005
- if (e.type === 'GET_SELECTED') {
3006
- return;
3007
- }
3008
- const shadowMeta = getGlobalStore
3009
- .getState()
3010
- .getShadowMetadata(stateKey, path);
3011
-
3012
- const caches = shadowMeta?.transformCaches;
3013
- if (caches) {
3014
- // Iterate over ALL keys in the cache map.
3015
- for (const key of caches.keys()) {
3016
- // If the key belongs to this component instance, delete it.
3017
- // This purges caches for 'sort by name', 'sort by score', etc.
3018
- if (key.startsWith(componentId)) {
3019
- caches.delete(key);
3020
- }
2961
+ if (e.type === 'GET_SELECTED') {
2962
+ return;
2963
+ }
2964
+ const shadowMeta = getGlobalStore
2965
+ .getState()
2966
+ .getShadowMetadata(stateKey, path);
2967
+
2968
+ const caches = shadowMeta?.transformCaches;
2969
+ if (caches) {
2970
+ // Iterate over ALL keys in the cache map.
2971
+ for (const key of caches.keys()) {
2972
+ // If the key belongs to this component instance, delete it.
2973
+ // This purges caches for 'sort by name', 'sort by score', etc.
2974
+ if (key.startsWith(componentId)) {
2975
+ caches.delete(key);
3021
2976
  }
3022
2977
  }
2978
+ }
3023
2979
 
3024
- if (
3025
- e.type === 'INSERT' ||
3026
- e.type === 'REMOVE' ||
3027
- e.type === 'CLEAR_SELECTION'
3028
- ) {
3029
- forceUpdate({});
3030
- }
3031
- });
3032
-
3033
- return () => {
3034
- unsubscribe();
3035
- };
2980
+ if (
2981
+ e.type === 'INSERT' ||
2982
+ e.type === 'REMOVE' ||
2983
+ e.type === 'CLEAR_SELECTION'
2984
+ ) {
2985
+ forceUpdate({});
2986
+ }
2987
+ });
3036
2988
 
3037
- // This effect's logic now depends on the componentId to perform the purge.
3038
- }, [componentId, stateKeyPathKey]);
2989
+ return () => {
2990
+ unsubscribe();
2991
+ };
3039
2992
 
3040
- if (!Array.isArray(arrayValues)) {
3041
- return null;
3042
- }
2993
+ // This effect's logic now depends on the componentId to perform the purge.
2994
+ }, [componentId, stateKeyPathKey]);
3043
2995
 
3044
- const arraySetter = rebuildStateShape({
3045
- currentState: arrayValues as any,
3046
- path,
3047
- componentId: componentId!,
3048
- meta: {
3049
- ...meta,
3050
- validIds: validIds,
3051
- },
3052
- });
2996
+ if (!Array.isArray(arrayValues)) {
2997
+ return null;
2998
+ }
3053
2999
 
3054
- return (
3055
- <>
3056
- {arrayValues.map((item, localIndex) => {
3057
- const itemKey = validIds[localIndex];
3000
+ const arraySetter = rebuildStateShape({
3001
+ path,
3002
+ componentId: componentId!,
3003
+ meta: {
3004
+ ...meta,
3005
+ validIds: validIds,
3006
+ },
3007
+ });
3058
3008
 
3059
- if (!itemKey) {
3060
- return null;
3061
- }
3009
+ return (
3010
+ <>
3011
+ {arrayValues.map((item, localIndex) => {
3012
+ const itemKey = validIds[localIndex];
3062
3013
 
3063
- let itemComponentId =
3064
- componentIdsRef.current.get(itemKey);
3065
- if (!itemComponentId) {
3066
- itemComponentId = uuidv4();
3067
- componentIdsRef.current.set(itemKey, itemComponentId);
3068
- }
3014
+ if (!itemKey) {
3015
+ return null;
3016
+ }
3069
3017
 
3070
- const itemPath = itemKey.split('.').slice(1);
3018
+ let itemComponentId = componentIdsRef.current.get(itemKey);
3019
+ if (!itemComponentId) {
3020
+ itemComponentId = uuidv4();
3021
+ componentIdsRef.current.set(itemKey, itemComponentId);
3022
+ }
3071
3023
 
3072
- return createElement(MemoizedCogsItemWrapper, {
3073
- key: itemKey,
3074
- stateKey,
3075
- itemComponentId,
3076
- itemPath,
3077
- localIndex,
3078
- arraySetter,
3079
- rebuildStateShape,
3080
- renderFn: callbackfn,
3081
- });
3082
- })}
3083
- </>
3084
- );
3085
- };
3024
+ const itemPath = itemKey.split('.').slice(1);
3086
3025
 
3087
- return <StateListWrapper />;
3088
- };
3089
- }
3090
- if (prop === 'stateFlattenOn') {
3091
- return (fieldName: string) => {
3092
- const arrayToMap = currentState as any[];
3093
- shapeCache.clear();
3094
- stateVersion++;
3095
- const flattenedResults = arrayToMap.flatMap(
3096
- (val: any) => val[fieldName] ?? []
3026
+ return createElement(MemoizedCogsItemWrapper, {
3027
+ key: itemKey,
3028
+ stateKey,
3029
+ itemComponentId,
3030
+ itemPath,
3031
+ localIndex,
3032
+ arraySetter,
3033
+ rebuildStateShape,
3034
+ renderFn: callbackfn,
3035
+ });
3036
+ })}
3037
+ </>
3097
3038
  );
3098
- return rebuildStateShape({
3099
- currentState: flattenedResults as any,
3100
- path: [...path, '[*]', fieldName],
3101
- componentId: componentId!,
3102
- meta,
3103
- });
3104
3039
  };
3105
- }
3106
- if (prop === 'index') {
3107
- return (index: number) => {
3108
- const arrayKeys = getGlobalStore
3109
- .getState()
3110
- .getShadowMetadata(stateKey, path)
3111
- ?.arrayKeys?.filter(
3112
- (key) =>
3113
- !meta?.validIds ||
3114
- (meta?.validIds && meta?.validIds?.includes(key))
3115
- );
3116
- const itemId = arrayKeys?.[index];
3117
- if (!itemId) return undefined;
3118
- const value = getGlobalStore
3119
- .getState()
3120
- .getShadowValue(itemId, meta?.validIds);
3121
- const state = rebuildStateShape({
3122
- currentState: value,
3123
- path: itemId.split('.').slice(1) as string[],
3124
- componentId: componentId!,
3125
- meta,
3126
- });
3127
- return state;
3128
- };
3129
- }
3130
- if (prop === 'last') {
3131
- return () => {
3132
- const currentArray = getGlobalStore
3133
- .getState()
3134
- .getShadowValue(stateKey, path) as any[];
3135
- if (currentArray.length === 0) return undefined;
3136
- const lastIndex = currentArray.length - 1;
3137
- const lastValue = currentArray[lastIndex];
3138
- const newPath = [...path, lastIndex.toString()];
3139
- return rebuildStateShape({
3140
- currentState: lastValue,
3141
- path: newPath,
3142
- componentId: componentId!,
3143
- meta,
3144
- });
3145
- };
3146
- }
3147
- if (prop === 'insert') {
3148
- return (
3149
- payload: InsertParams<InferArrayElement<T>>,
3150
- index?: number
3151
- ) => {
3152
- effectiveSetState(payload as any, path, { updateType: 'insert' });
3153
- return rebuildStateShape({
3154
- currentState: getGlobalStore
3155
- .getState()
3156
- .getShadowValue(stateKey, path),
3157
- path,
3158
- componentId: componentId!,
3159
- meta,
3160
- });
3161
- };
3162
- }
3163
- if (prop === 'uniqueInsert') {
3164
- return (
3165
- payload: UpdateArg<T>,
3166
- fields?: (keyof InferArrayElement<T>)[],
3167
- onMatch?: (existingItem: any) => any
3168
- ) => {
3169
- const currentArray = getGlobalStore
3170
- .getState()
3171
- .getShadowValue(stateKey, path) as any[];
3172
- const newValue = isFunction<T>(payload)
3173
- ? payload(currentArray as any)
3174
- : (payload as any);
3175
-
3176
- let matchedItem: any = null;
3177
- const isUnique = !currentArray.some((item) => {
3178
- const isMatch = fields
3179
- ? fields.every((field) =>
3180
- isDeepEqual(item[field], newValue[field])
3181
- )
3182
- : isDeepEqual(item, newValue);
3183
- if (isMatch) matchedItem = item;
3184
- return isMatch;
3185
- });
3186
-
3187
- if (isUnique) {
3188
- invalidateCachePath(path);
3189
- effectiveSetState(newValue, path, { updateType: 'insert' });
3190
- } else if (onMatch && matchedItem) {
3191
- const updatedItem = onMatch(matchedItem);
3192
- const updatedArray = currentArray.map((item) =>
3193
- isDeepEqual(item, matchedItem) ? updatedItem : item
3194
- );
3195
- invalidateCachePath(path);
3196
- effectiveSetState(updatedArray as any, path, {
3197
- updateType: 'update',
3198
- });
3199
- }
3200
- };
3201
- }
3202
3040
 
3203
- if (prop === 'cut') {
3204
- return (index?: number, options?: { waitForSync?: boolean }) => {
3205
- const validKeys =
3206
- meta?.validIds ??
3207
- getGlobalStore.getState().getShadowMetadata(stateKey, path)
3208
- ?.arrayKeys;
3209
-
3210
- if (!validKeys || validKeys.length === 0) return;
3041
+ return <StateListWrapper />;
3042
+ };
3043
+ }
3044
+ if (prop === 'stateFlattenOn') {
3045
+ return (fieldName: string) => {
3046
+ const currentState = getGlobalStore
3047
+ .getState()
3048
+ .getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
3049
+ if (!Array.isArray(currentState)) return []; // Guard clause
3050
+ const arrayToMap = currentState as any[];
3211
3051
 
3212
- const indexToCut =
3213
- index == -1
3214
- ? validKeys.length - 1
3215
- : index !== undefined
3216
- ? index
3217
- : validKeys.length - 1;
3052
+ stateVersion++;
3053
+ const flattenedResults = arrayToMap.flatMap(
3054
+ (val: any) => val[fieldName] ?? []
3055
+ );
3056
+ return rebuildStateShape({
3057
+ path: [...path, '[*]', fieldName],
3058
+ componentId: componentId!,
3059
+ meta,
3060
+ });
3061
+ };
3062
+ }
3063
+ if (prop === 'index') {
3064
+ return (index: number) => {
3065
+ const arrayKeys = getGlobalStore
3066
+ .getState()
3067
+ .getShadowMetadata(stateKey, path)
3068
+ ?.arrayKeys?.filter(
3069
+ (key) =>
3070
+ !meta?.validIds ||
3071
+ (meta?.validIds && meta?.validIds?.includes(key))
3072
+ );
3073
+ const itemId = arrayKeys?.[index];
3074
+ if (!itemId) return undefined;
3075
+ const value = getGlobalStore
3076
+ .getState()
3077
+ .getShadowValue(itemId, meta?.validIds);
3078
+ const state = rebuildStateShape({
3079
+ path: itemId.split('.').slice(1) as string[],
3080
+ componentId: componentId!,
3081
+ meta,
3082
+ });
3083
+ return state;
3084
+ };
3085
+ }
3086
+ if (prop === 'last') {
3087
+ return () => {
3088
+ const currentArray = getGlobalStore
3089
+ .getState()
3090
+ .getShadowValue(stateKey, path) as any[];
3091
+ if (currentArray.length === 0) return undefined;
3092
+ const lastIndex = currentArray.length - 1;
3093
+ const lastValue = currentArray[lastIndex];
3094
+ const newPath = [...path, lastIndex.toString()];
3095
+ return rebuildStateShape({
3096
+ path: newPath,
3097
+ componentId: componentId!,
3098
+ meta,
3099
+ });
3100
+ };
3101
+ }
3102
+ if (prop === 'insert') {
3103
+ return (
3104
+ payload: InsertParams<InferArrayElement<T>>,
3105
+ index?: number
3106
+ ) => {
3107
+ effectiveSetState(payload as any, path, { updateType: 'insert' });
3108
+ return rebuildStateShape({
3109
+ path,
3110
+ componentId: componentId!,
3111
+ meta,
3112
+ });
3113
+ };
3114
+ }
3115
+ if (prop === 'uniqueInsert') {
3116
+ return (
3117
+ payload: UpdateArg<T>,
3118
+ fields?: (keyof InferArrayElement<T>)[],
3119
+ onMatch?: (existingItem: any) => any
3120
+ ) => {
3121
+ const currentArray = getGlobalStore
3122
+ .getState()
3123
+ .getShadowValue(stateKey, path) as any[];
3124
+ const newValue = isFunction<T>(payload)
3125
+ ? payload(currentArray as any)
3126
+ : (payload as any);
3127
+
3128
+ let matchedItem: any = null;
3129
+ const isUnique = !currentArray.some((item) => {
3130
+ const isMatch = fields
3131
+ ? fields.every((field) =>
3132
+ isDeepEqual(item[field], newValue[field])
3133
+ )
3134
+ : isDeepEqual(item, newValue);
3135
+ if (isMatch) matchedItem = item;
3136
+ return isMatch;
3137
+ });
3218
3138
 
3219
- const fullIdToCut = validKeys[indexToCut];
3220
- if (!fullIdToCut) return; // Index out of bounds
3139
+ if (isUnique) {
3140
+ effectiveSetState(newValue, path, { updateType: 'insert' });
3141
+ } else if (onMatch && matchedItem) {
3142
+ const updatedItem = onMatch(matchedItem);
3143
+ const updatedArray = currentArray.map((item) =>
3144
+ isDeepEqual(item, matchedItem) ? updatedItem : item
3145
+ );
3221
3146
 
3222
- const pathForCut = fullIdToCut.split('.').slice(1);
3223
- effectiveSetState(currentState, pathForCut, {
3224
- updateType: 'cut',
3147
+ effectiveSetState(updatedArray as any, path, {
3148
+ updateType: 'update',
3225
3149
  });
3226
- };
3227
- }
3228
- if (prop === 'cutSelected') {
3229
- return () => {
3230
- const validKeys = applyTransforms(
3231
- stateKey,
3232
- path,
3233
- meta?.transforms
3234
- );
3150
+ }
3151
+ };
3152
+ }
3235
3153
 
3236
- if (!validKeys || validKeys.length === 0) return;
3154
+ if (prop === 'cut') {
3155
+ return (index?: number, options?: { waitForSync?: boolean }) => {
3156
+ const currentState = getGlobalStore
3157
+ .getState()
3158
+ .getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
3159
+ const validKeys =
3160
+ meta?.validIds ??
3161
+ getGlobalStore.getState().getShadowMetadata(stateKey, path)
3162
+ ?.arrayKeys;
3163
+
3164
+ if (!validKeys || validKeys.length === 0) return;
3165
+
3166
+ const indexToCut =
3167
+ index == -1
3168
+ ? validKeys.length - 1
3169
+ : index !== undefined
3170
+ ? index
3171
+ : validKeys.length - 1;
3172
+
3173
+ const fullIdToCut = validKeys[indexToCut];
3174
+ if (!fullIdToCut) return; // Index out of bounds
3175
+
3176
+ const pathForCut = fullIdToCut.split('.').slice(1);
3177
+ effectiveSetState(currentState, pathForCut, {
3178
+ updateType: 'cut',
3179
+ });
3180
+ };
3181
+ }
3182
+ if (prop === 'cutSelected') {
3183
+ return () => {
3184
+ const validKeys = applyTransforms(stateKey, path, meta?.transforms);
3185
+ const currentState = getGlobalStore
3186
+ .getState()
3187
+ .getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
3188
+ if (!validKeys || validKeys.length === 0) return;
3237
3189
 
3238
- const indexKeyToCut = getGlobalStore
3239
- .getState()
3240
- .selectedIndicesMap.get(stateKeyPathKey);
3190
+ const indexKeyToCut = getGlobalStore
3191
+ .getState()
3192
+ .selectedIndicesMap.get(stateKeyPathKey);
3241
3193
 
3242
- let indexToCut = validKeys.findIndex(
3243
- (key) => key === indexKeyToCut
3244
- );
3194
+ let indexToCut = validKeys.findIndex(
3195
+ (key) => key === indexKeyToCut
3196
+ );
3245
3197
 
3246
- const pathForCut = validKeys[
3247
- indexToCut == -1 ? validKeys.length - 1 : indexToCut
3248
- ]
3249
- ?.split('.')
3250
- .slice(1);
3251
- getGlobalStore
3252
- .getState()
3253
- .clearSelectedIndex({ arrayKey: stateKeyPathKey });
3254
- const parentPath = pathForCut?.slice(0, -1)!;
3255
- notifySelectionComponents(stateKey, parentPath);
3256
- effectiveSetState(currentState, pathForCut!, {
3257
- updateType: 'cut',
3258
- });
3259
- };
3260
- }
3261
- if (prop === 'cutByValue') {
3262
- return (value: string | number | boolean) => {
3263
- // Step 1: Get the list of all unique keys for the current view.
3264
- const arrayMeta = getGlobalStore
3265
- .getState()
3266
- .getShadowMetadata(stateKey, path);
3267
- const relevantKeys = meta?.validIds ?? arrayMeta?.arrayKeys;
3198
+ const pathForCut = validKeys[
3199
+ indexToCut == -1 ? validKeys.length - 1 : indexToCut
3200
+ ]
3201
+ ?.split('.')
3202
+ .slice(1);
3203
+ getGlobalStore
3204
+ .getState()
3205
+ .clearSelectedIndex({ arrayKey: stateKeyPathKey });
3206
+ const parentPath = pathForCut?.slice(0, -1)!;
3207
+ notifySelectionComponents(stateKey, parentPath);
3208
+ effectiveSetState(currentState, pathForCut!, {
3209
+ updateType: 'cut',
3210
+ });
3211
+ };
3212
+ }
3213
+ if (prop === 'cutByValue') {
3214
+ return (value: string | number | boolean) => {
3215
+ // Step 1: Get the list of all unique keys for the current view.
3216
+ const arrayMeta = getGlobalStore
3217
+ .getState()
3218
+ .getShadowMetadata(stateKey, path);
3219
+ const relevantKeys = meta?.validIds ?? arrayMeta?.arrayKeys;
3268
3220
 
3269
- if (!relevantKeys) return;
3221
+ if (!relevantKeys) return;
3270
3222
 
3271
- let keyToCut: string | null = null;
3223
+ let keyToCut: string | null = null;
3272
3224
 
3273
- // Step 2: Iterate through the KEYS, get the value for each, and find the match.
3274
- for (const key of relevantKeys) {
3275
- const itemValue = getGlobalStore.getState().getShadowValue(key);
3276
- if (itemValue === value) {
3277
- keyToCut = key;
3278
- break; // We found the key, no need to search further.
3279
- }
3225
+ // Step 2: Iterate through the KEYS, get the value for each, and find the match.
3226
+ for (const key of relevantKeys) {
3227
+ const itemValue = getGlobalStore.getState().getShadowValue(key);
3228
+ if (itemValue === value) {
3229
+ keyToCut = key;
3230
+ break; // We found the key, no need to search further.
3280
3231
  }
3232
+ }
3281
3233
 
3282
- // Step 3: If we found a matching key, use it to perform the cut.
3283
- if (keyToCut) {
3284
- const itemPath = keyToCut.split('.').slice(1);
3285
- effectiveSetState(null as any, itemPath, { updateType: 'cut' });
3286
- }
3287
- };
3288
- }
3234
+ // Step 3: If we found a matching key, use it to perform the cut.
3235
+ if (keyToCut) {
3236
+ const itemPath = keyToCut.split('.').slice(1);
3237
+ effectiveSetState(null as any, itemPath, { updateType: 'cut' });
3238
+ }
3239
+ };
3240
+ }
3289
3241
 
3290
- if (prop === 'toggleByValue') {
3291
- return (value: string | number | boolean) => {
3292
- // Step 1: Get the list of all unique keys for the current view.
3293
- const arrayMeta = getGlobalStore
3294
- .getState()
3295
- .getShadowMetadata(stateKey, path);
3296
- const relevantKeys = meta?.validIds ?? arrayMeta?.arrayKeys;
3242
+ if (prop === 'toggleByValue') {
3243
+ return (value: string | number | boolean) => {
3244
+ // Step 1: Get the list of all unique keys for the current view.
3245
+ const arrayMeta = getGlobalStore
3246
+ .getState()
3247
+ .getShadowMetadata(stateKey, path);
3248
+ const relevantKeys = meta?.validIds ?? arrayMeta?.arrayKeys;
3297
3249
 
3298
- if (!relevantKeys) return;
3250
+ if (!relevantKeys) return;
3299
3251
 
3300
- let keyToCut: string | null = null;
3252
+ let keyToCut: string | null = null;
3301
3253
 
3302
- // Step 2: Iterate through the KEYS to find the one matching the value. This is the robust way.
3303
- for (const key of relevantKeys) {
3304
- const itemValue = getGlobalStore.getState().getShadowValue(key);
3305
- console.log('itemValue sdasdasdasd', itemValue);
3306
- if (itemValue === value) {
3307
- keyToCut = key;
3308
- break; // Found it!
3309
- }
3254
+ // Step 2: Iterate through the KEYS to find the one matching the value. This is the robust way.
3255
+ for (const key of relevantKeys) {
3256
+ const itemValue = getGlobalStore.getState().getShadowValue(key);
3257
+ console.log('itemValue sdasdasdasd', itemValue);
3258
+ if (itemValue === value) {
3259
+ keyToCut = key;
3260
+ break; // Found it!
3310
3261
  }
3262
+ }
3263
+ console.log('itemValue keyToCut', keyToCut);
3264
+ // Step 3: Act based on whether the key was found.
3265
+ if (keyToCut) {
3266
+ // Item exists, so we CUT it using its *actual* key.
3267
+ const itemPath = keyToCut.split('.').slice(1);
3311
3268
  console.log('itemValue keyToCut', keyToCut);
3312
- // Step 3: Act based on whether the key was found.
3313
- if (keyToCut) {
3314
- // Item exists, so we CUT it using its *actual* key.
3315
- const itemPath = keyToCut.split('.').slice(1);
3316
- console.log('itemValue keyToCut', keyToCut);
3317
- effectiveSetState(value as any, itemPath, {
3318
- updateType: 'cut',
3319
- });
3320
- } else {
3321
- // Item does not exist, so we INSERT it.
3322
- effectiveSetState(value as any, path, { updateType: 'insert' });
3323
- }
3324
- };
3325
- }
3326
- if (prop === 'findWith') {
3327
- return (
3328
- searchKey: keyof InferArrayElement<T>,
3329
- searchValue: any
3330
- ) => {
3331
- const arrayKeys = getGlobalStore
3332
- .getState()
3333
- .getShadowMetadata(stateKey, path)?.arrayKeys;
3269
+ effectiveSetState(value as any, itemPath, {
3270
+ updateType: 'cut',
3271
+ });
3272
+ } else {
3273
+ // Item does not exist, so we INSERT it.
3274
+ effectiveSetState(value as any, path, { updateType: 'insert' });
3275
+ }
3276
+ };
3277
+ }
3278
+ if (prop === 'findWith') {
3279
+ return (searchKey: keyof InferArrayElement<T>, searchValue: any) => {
3280
+ const arrayKeys = getGlobalStore
3281
+ .getState()
3282
+ .getShadowMetadata(stateKey, path)?.arrayKeys;
3334
3283
 
3335
- if (!arrayKeys) {
3336
- throw new Error('No array keys found for sorting');
3337
- }
3284
+ if (!arrayKeys) {
3285
+ throw new Error('No array keys found for sorting');
3286
+ }
3338
3287
 
3339
- let value = null;
3340
- let foundPath: string[] = [];
3288
+ let value = null;
3289
+ let foundPath: string[] = [];
3341
3290
 
3342
- for (const fullPath of arrayKeys) {
3343
- let shadowValue = getGlobalStore
3344
- .getState()
3345
- .getShadowValue(fullPath, meta?.validIds);
3346
- if (shadowValue && shadowValue[searchKey] === searchValue) {
3347
- value = shadowValue;
3348
- foundPath = fullPath.split('.').slice(1);
3349
- break;
3350
- }
3291
+ for (const fullPath of arrayKeys) {
3292
+ let shadowValue = getGlobalStore
3293
+ .getState()
3294
+ .getShadowValue(fullPath, meta?.validIds);
3295
+ if (shadowValue && shadowValue[searchKey] === searchValue) {
3296
+ value = shadowValue;
3297
+ foundPath = fullPath.split('.').slice(1);
3298
+ break;
3351
3299
  }
3300
+ }
3352
3301
 
3353
- return rebuildStateShape({
3354
- currentState: value as any,
3355
- path: foundPath,
3356
- componentId: componentId!,
3357
- meta,
3358
- });
3359
- };
3360
- }
3302
+ return rebuildStateShape({
3303
+ path: foundPath,
3304
+ componentId: componentId!,
3305
+ meta,
3306
+ });
3307
+ };
3361
3308
  }
3362
3309
 
3363
- if (prop === 'cut') {
3310
+ if (prop === 'cutThis') {
3364
3311
  let shadowValue = getGlobalStore
3365
3312
  .getState()
3366
3313
  .getShadowValue(path.join('.'));
@@ -3661,29 +3608,86 @@ function createProxyHandler<T>(
3661
3608
  if (prop === '_stateKey') return stateKey;
3662
3609
  if (prop === '_path') return path;
3663
3610
  if (prop === 'update') {
3611
+ if (recursionTimerName) {
3612
+ console.timeEnd(recursionTimerName);
3613
+ recursionTimerName = null;
3614
+ }
3615
+
3664
3616
  return (payload: UpdateArg<T>) => {
3665
- // Step 1: This is the same. It performs the data update.
3666
- effectiveSetState(payload as any, path, { updateType: 'update' });
3617
+ // Check if we're in a React event handler
3618
+ const error = new Error();
3619
+ const stack = error.stack || '';
3620
+ const inReactEvent =
3621
+ stack.includes('onClick') ||
3622
+ stack.includes('dispatchEvent') ||
3623
+ stack.includes('batchedUpdates');
3624
+
3625
+ // Only batch if we're in a React event
3626
+ if (inReactEvent) {
3627
+ const batchKey = `${stateKey}.${path.join('.')}`;
3628
+
3629
+ // Schedule flush if not already scheduled
3630
+ if (!batchFlushScheduled) {
3631
+ updateBatchQueue.clear();
3632
+ batchFlushScheduled = true;
3633
+
3634
+ queueMicrotask(() => {
3635
+ // Process all batched updates
3636
+ for (const [key, updates] of updateBatchQueue) {
3637
+ const parts = key.split('.');
3638
+ const batchStateKey = parts[0];
3639
+ const batchPath = parts.slice(1);
3640
+
3641
+ // Compose all updates for this path
3642
+ const composedUpdate = updates.reduce(
3643
+ (composed, update) => {
3644
+ if (
3645
+ typeof update === 'function' &&
3646
+ typeof composed === 'function'
3647
+ ) {
3648
+ // Compose functions
3649
+ return (state: any) => update(composed(state));
3650
+ }
3651
+ // If not functions, last one wins
3652
+ return update;
3653
+ }
3654
+ );
3655
+
3656
+ // Call effectiveSetState ONCE with composed update
3657
+ effectiveSetState(composedUpdate as any, batchPath, {
3658
+ updateType: 'update',
3659
+ });
3660
+ }
3661
+
3662
+ updateBatchQueue.clear();
3663
+ batchFlushScheduled = false;
3664
+ });
3665
+ }
3666
+
3667
+ // Add to batch
3668
+ const existing = updateBatchQueue.get(batchKey) || [];
3669
+ existing.push(payload);
3670
+ updateBatchQueue.set(batchKey, existing);
3671
+ } else {
3672
+ // NOT in React event - execute immediately
3673
+ console.time('update inner');
3674
+ effectiveSetState(payload as any, path, { updateType: 'update' });
3675
+ console.timeEnd('update inner');
3676
+ }
3667
3677
 
3668
3678
  return {
3669
- /**
3670
- * Marks this specific item, which was just updated, as 'synced' (not dirty).
3671
- */
3672
3679
  synced: () => {
3673
- // This function "remembers" the path of the item that was just updated.
3674
3680
  const shadowMeta = getGlobalStore
3675
3681
  .getState()
3676
3682
  .getShadowMetadata(stateKey, path);
3677
3683
 
3678
- // It updates ONLY the metadata for that specific item.
3679
3684
  getGlobalStore.getState().setShadowMetadata(stateKey, path, {
3680
3685
  ...shadowMeta,
3681
- isDirty: false, // EXPLICITLY set to false, not just undefined
3682
- stateSource: 'server', // Mark as coming from server
3683
- lastServerSync: Date.now(), // Add timestamp
3686
+ isDirty: false,
3687
+ stateSource: 'server',
3688
+ lastServerSync: Date.now(),
3684
3689
  });
3685
3690
 
3686
- // Force a re-render for components watching this path
3687
3691
  const fullPath = [stateKey, ...path].join('.');
3688
3692
  getGlobalStore.getState().notifyPathSubscribers(fullPath, {
3689
3693
  type: 'SYNC_STATUS_CHANGE',
@@ -3697,10 +3701,9 @@ function createProxyHandler<T>(
3697
3701
  if (prop === 'toggle') {
3698
3702
  const currentValueAtPath = getGlobalStore
3699
3703
  .getState()
3700
- .getShadowValue([stateKey, ...path].join('.'));
3704
+ .getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
3701
3705
 
3702
- console.log('currentValueAtPath', currentValueAtPath);
3703
- if (typeof currentState != 'boolean') {
3706
+ if (typeof currentValueAtPath != 'boolean') {
3704
3707
  throw new Error('toggle() can only be used on boolean values');
3705
3708
  }
3706
3709
  return () => {
@@ -3728,7 +3731,6 @@ function createProxyHandler<T>(
3728
3731
  .getState()
3729
3732
  .getShadowValue(stateKey, nextPath);
3730
3733
  return rebuildStateShape({
3731
- currentState: nextValue,
3732
3734
  path: nextPath,
3733
3735
  componentId: componentId!,
3734
3736
  meta,
@@ -3737,12 +3739,11 @@ function createProxyHandler<T>(
3737
3739
  };
3738
3740
 
3739
3741
  const proxyInstance = new Proxy(baseFunction, handler);
3740
- shapeCache.set(cacheKey, {
3741
- proxy: proxyInstance,
3742
- stateVersion: stateVersion,
3743
- });
3742
+ proxyCache.set(cacheKey, proxyInstance);
3743
+ console.timeEnd('rebuildStateShape Inner');
3744
3744
  return proxyInstance;
3745
3745
  }
3746
+ console.timeEnd('rebuildStateShape Outer');
3746
3747
 
3747
3748
  const baseObj = {
3748
3749
  revertToInitialState: (obj?: { validationKey?: string }) => {
@@ -3766,11 +3767,10 @@ function createProxyHandler<T>(
3766
3767
  getGlobalStore.getState().initialStateGlobal[stateKey];
3767
3768
 
3768
3769
  getGlobalStore.getState().clearSelectedIndexesForState(stateKey);
3769
- shapeCache.clear();
3770
+
3770
3771
  stateVersion++;
3771
3772
  getGlobalStore.getState().initializeShadowState(stateKey, initialState);
3772
- const newProxy = rebuildStateShape({
3773
- currentState: initialState,
3773
+ rebuildStateShape({
3774
3774
  path: [],
3775
3775
  componentId: componentId!,
3776
3776
  });
@@ -3797,7 +3797,6 @@ function createProxyHandler<T>(
3797
3797
  return initialState;
3798
3798
  },
3799
3799
  updateInitialState: (newState: T) => {
3800
- shapeCache.clear();
3801
3800
  stateVersion++;
3802
3801
 
3803
3802
  const newUpdaterState = createProxyHandler(
@@ -3839,7 +3838,6 @@ function createProxyHandler<T>(
3839
3838
  },
3840
3839
  };
3841
3840
  const returnShape = rebuildStateShape({
3842
- currentState: getGlobalStore.getState().getShadowValue(stateKey, []),
3843
3841
  componentId,
3844
3842
  path: [],
3845
3843
  });