cogsbox-state 0.5.463 → 0.5.465

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
@@ -16,7 +16,6 @@ import {
16
16
  } from 'react';
17
17
  import { createRoot } from 'react-dom/client';
18
18
  import {
19
- debounce,
20
19
  getDifferences,
21
20
  isArray,
22
21
  isFunction,
@@ -28,8 +27,11 @@ import superjson from 'superjson';
28
27
  import { v4 as uuidv4 } from 'uuid';
29
28
 
30
29
  import {
30
+ buildShadowNode,
31
31
  formRefStore,
32
32
  getGlobalStore,
33
+ METADATA_KEYS,
34
+ ValidationError,
33
35
  ValidationStatus,
34
36
  type ComponentsType,
35
37
  } from './store.js';
@@ -38,7 +40,6 @@ import { Operation } from 'fast-json-patch';
38
40
  import { useInView } from 'react-intersection-observer';
39
41
  import * as z3 from 'zod/v3';
40
42
  import * as z4 from 'zod/v4';
41
- import z from 'zod';
42
43
 
43
44
  type Prettify<T> = T extends any ? { [K in keyof T]: T[K] } : never;
44
45
 
@@ -245,10 +246,7 @@ export type UpdateType<T> = (payload: UpdateArg<T>) => { synced: () => void };
245
246
 
246
247
  export type InsertType<T> = (payload: InsertParams<T>, index?: number) => void;
247
248
  export type InsertTypeObj<T> = (payload: InsertParams<T>) => void;
248
- export type ValidationError = {
249
- path: (string | number)[];
250
- message: string;
251
- };
249
+
252
250
  type EffectFunction<T, R> = (state: T, deps: any[]) => R;
253
251
  export type EndType<T, IsArrayElement = false> = {
254
252
  addZodValidation: (errors: ValidationError[]) => void;
@@ -259,7 +257,7 @@ export type EndType<T, IsArrayElement = false> = {
259
257
  _stateKey: string;
260
258
  formElement: (control: FormControl<T>, opts?: FormOptsType) => JSX.Element;
261
259
  get: () => T;
262
- getState: () => T;
260
+
263
261
  $get: () => T;
264
262
  $derive: <R>(fn: EffectFunction<T, R>) => R;
265
263
 
@@ -306,7 +304,7 @@ export type StateObject<T> = (T extends any[]
306
304
  _isLoading: boolean;
307
305
  _serverState: T;
308
306
  revertToInitialState: (obj?: { validationKey?: string }) => T;
309
- getDifferences: () => string[];
307
+
310
308
  middleware: (
311
309
  middles: ({
312
310
  updateLog,
@@ -337,7 +335,8 @@ type UpdateOptions = {
337
335
  type EffectiveSetState<TStateObject> = (
338
336
  newStateOrFunction:
339
337
  | EffectiveSetStateArg<TStateObject, 'update'>
340
- | EffectiveSetStateArg<TStateObject, 'insert'>,
338
+ | EffectiveSetStateArg<TStateObject, 'insert'>
339
+ | null,
341
340
  path: string[],
342
341
  updateObj: UpdateOptions,
343
342
  validationKey?: string
@@ -449,10 +448,13 @@ type FormsElementsType<T> = {
449
448
  children: React.ReactNode;
450
449
  status: ValidationStatus; // Instead of 'active' boolean
451
450
 
451
+ hasErrors: boolean;
452
+ hasWarnings: boolean;
453
+ allErrors: ValidationError[];
454
+
452
455
  path: string[];
453
456
  message?: string;
454
- data?: T;
455
- key?: string;
457
+ getData?: () => T;
456
458
  }) => React.ReactNode;
457
459
  syncRender?: (options: SyncRenderOptions<T>) => React.ReactNode;
458
460
  };
@@ -476,11 +478,74 @@ export type TransformedStateType<T> = {
476
478
  [P in keyof T]: T[P] extends CogsInitialState<infer U> ? U : T[P];
477
479
  };
478
480
 
479
- function setAndMergeOptions(stateKey: string, newOptions: OptionsType<any>) {
480
- const getInitialOptions = getGlobalStore.getState().getInitialOptions;
481
- const setInitialStateOptions =
482
- getGlobalStore.getState().setInitialStateOptions;
481
+ const {
482
+ getInitialOptions,
483
+ updateInitialStateGlobal,
484
+ // ALIAS THE NEW FUNCTIONS TO THE OLD NAMES
485
+ getShadowMetadata,
486
+ setShadowMetadata,
487
+ getShadowValue,
488
+ initializeShadowState,
489
+ updateShadowAtPath,
490
+ insertShadowArrayElement,
491
+ removeShadowArrayElement,
492
+ getSelectedIndex,
493
+ setInitialStateOptions,
494
+ setServerStateUpdate,
495
+ markAsDirty,
496
+ registerComponent,
497
+ unregisterComponent,
498
+ addPathComponent,
499
+ clearSelectedIndexesForState,
500
+ addStateLog,
501
+ setSyncInfo,
502
+ clearSelectedIndex,
503
+ getSyncInfo,
504
+ notifyPathSubscribers,
505
+ subscribeToPath,
506
+ // Note: The old functions are no longer imported under their original names
507
+ } = getGlobalStore.getState();
508
+ function getArrayData(stateKey: string, path: string[], meta?: MetaData) {
509
+ const shadowMeta = getShadowMetadata(stateKey, path);
510
+ const isArray = !!shadowMeta?.arrayKeys;
483
511
 
512
+ if (!isArray) {
513
+ const value = getGlobalStore.getState().getShadowValue(stateKey, path);
514
+ return { isArray: false, value, keys: [] };
515
+ }
516
+
517
+ const arrayPathKey = path.join('.');
518
+ const viewIds = meta?.arrayViews?.[arrayPathKey] ?? shadowMeta.arrayKeys;
519
+
520
+ // FIX: If the derived view is empty, return an empty array and keys.
521
+ if (Array.isArray(viewIds) && viewIds.length === 0) {
522
+ return { isArray: true, value: [], keys: [] };
523
+ }
524
+
525
+ const value = getGlobalStore
526
+ .getState()
527
+ .getShadowValue(stateKey, path, viewIds);
528
+
529
+ return { isArray: true, value, keys: viewIds ?? [] };
530
+ }
531
+
532
+ function findArrayItem(
533
+ array: any[],
534
+ keys: string[],
535
+ predicate: (item: any, index: number) => boolean
536
+ ): { key: string; index: number; value: any } | null {
537
+ for (let i = 0; i < array.length; i++) {
538
+ if (predicate(array[i], i)) {
539
+ const key = keys[i];
540
+ if (key) {
541
+ return { key, index: i, value: array[i] };
542
+ }
543
+ }
544
+ }
545
+ return null;
546
+ }
547
+
548
+ function setAndMergeOptions(stateKey: string, newOptions: OptionsType<any>) {
484
549
  const initialOptions = getInitialOptions(stateKey as string) || {};
485
550
 
486
551
  setInitialStateOptions(stateKey as string, {
@@ -499,8 +564,7 @@ function setOptions<StateKey, Opt>({
499
564
  }) {
500
565
  const initialOptions = getInitialOptions(stateKey as string) || {};
501
566
  const initialOptionsPartState = initialOptionsPart[stateKey as string] || {};
502
- const setInitialStateOptions =
503
- getGlobalStore.getState().setInitialStateOptions;
567
+
504
568
  const mergedOptions = { ...initialOptionsPartState, ...initialOptions };
505
569
 
506
570
  let needToAdd = false;
@@ -621,10 +685,10 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
621
685
  const existingGlobalOptions = getInitialOptions(key);
622
686
 
623
687
  if (!existingGlobalOptions) {
624
- getGlobalStore.getState().setInitialStateOptions(key, mergedOptions);
688
+ setInitialStateOptions(key, mergedOptions);
625
689
  } else {
626
690
  // Merge with existing global options
627
- getGlobalStore.getState().setInitialStateOptions(key, {
691
+ setInitialStateOptions(key, {
628
692
  ...existingGlobalOptions,
629
693
  ...mergedOptions,
630
694
  });
@@ -633,16 +697,15 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
633
697
  });
634
698
 
635
699
  Object.keys(statePart).forEach((key) => {
636
- getGlobalStore.getState().initializeShadowState(key, statePart[key]);
700
+ initializeShadowState(key, statePart[key]);
637
701
  });
638
-
702
+ console.log('new stateObject ', getGlobalStore.getState().shadowStateStore);
639
703
  type StateKeys = keyof typeof statePart;
640
704
 
641
705
  const useCogsState = <StateKey extends StateKeys>(
642
706
  stateKey: StateKey,
643
707
  options?: Prettify<OptionsType<(typeof statePart)[StateKey]>>
644
708
  ) => {
645
- console.time('useCogsState');
646
709
  const [componentId] = useState(options?.componentId ?? uuidv4());
647
710
 
648
711
  setOptions({
@@ -651,14 +714,11 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
651
714
  initialOptionsPart,
652
715
  });
653
716
  const thiState =
654
- getGlobalStore.getState().getShadowValue(stateKey as string) ||
655
- statePart[stateKey as string];
717
+ getShadowValue(stateKey as string, []) || statePart[stateKey as string];
656
718
  const partialState = options?.modifyState
657
719
  ? options.modifyState(thiState)
658
720
  : thiState;
659
721
 
660
- console.timeEnd('useCogsState');
661
-
662
722
  const updater = useCogsStateFn<(typeof statePart)[StateKey]>(partialState, {
663
723
  stateKey: stateKey as string,
664
724
  syncUpdate: options?.syncUpdate,
@@ -777,12 +837,7 @@ export function createCogsStateFromSync<
777
837
  __syncSchemas: schemas,
778
838
  }) as any;
779
839
  }
780
- const {
781
- getInitialOptions,
782
840
 
783
- addStateLog,
784
- updateInitialStateGlobal,
785
- } = getGlobalStore.getState();
786
841
  const saveToLocalStorage = <T,>(
787
842
  state: T,
788
843
  thisKey: string,
@@ -814,7 +869,7 @@ const saveToLocalStorage = <T,>(
814
869
  } catch {
815
870
  // Ignore errors, will use undefined
816
871
  }
817
- const shadowMeta = getGlobalStore.getState().getShadowMetadata(thisKey, []);
872
+ const shadowMeta = getShadowMetadata(thisKey, []);
818
873
 
819
874
  const data: LocalStorageData<T> = {
820
875
  state,
@@ -850,7 +905,7 @@ const loadFromLocalStorage = (localStorageKey: string) => {
850
905
  }
851
906
  };
852
907
  const loadAndApplyLocalStorage = (stateKey: string, options: any) => {
853
- const currentState = getGlobalStore.getState().getShadowValue(stateKey);
908
+ const currentState = getShadowValue(stateKey, []);
854
909
  const { sessionId } = useCogsConfig();
855
910
  const localkey = isFunction(options?.localStorage?.key)
856
911
  ? options.localStorage.key(currentState)
@@ -881,7 +936,7 @@ type LocalStorageData<T> = {
881
936
  };
882
937
 
883
938
  const notifyComponents = (thisKey: string) => {
884
- const stateEntry = getGlobalStore.getState().getShadowMetadata(thisKey, []);
939
+ const stateEntry = getShadowMetadata(thisKey, []);
885
940
  if (!stateEntry) return;
886
941
 
887
942
  // Batch component updates
@@ -903,40 +958,15 @@ const notifyComponents = (thisKey: string) => {
903
958
  });
904
959
  };
905
960
 
906
- export const notifyComponent = (stateKey: string, componentId: string) => {
907
- const stateEntry = getGlobalStore.getState().getShadowMetadata(stateKey, []);
908
- if (stateEntry) {
909
- const fullComponentId = `${stateKey}////${componentId}`;
910
- const component = stateEntry?.components?.get(fullComponentId);
911
- const reactiveTypes = component
912
- ? Array.isArray(component.reactiveType)
913
- ? component.reactiveType
914
- : [component.reactiveType || 'component']
915
- : null;
916
-
917
- // Skip if reactivity is disabled
918
- if (reactiveTypes?.includes('none')) {
919
- return;
920
- }
921
-
922
- if (component) {
923
- // Force an update to ensure the current value is saved
924
-
925
- component.forceUpdate();
926
- }
927
- }
928
- };
929
961
  function markEntireStateAsServerSynced(
930
962
  stateKey: string,
931
963
  path: string[],
932
964
  data: any,
933
965
  timestamp: number
934
966
  ) {
935
- const store = getGlobalStore.getState();
936
-
937
967
  // Mark current path as synced
938
- const currentMeta = store.getShadowMetadata(stateKey, path);
939
- store.setShadowMetadata(stateKey, path, {
968
+ const currentMeta = getShadowMetadata(stateKey, path);
969
+ setShadowMetadata(stateKey, path, {
940
970
  ...currentMeta,
941
971
  isDirty: false,
942
972
  stateSource: 'server',
@@ -945,10 +975,11 @@ function markEntireStateAsServerSynced(
945
975
 
946
976
  // If it's an array, mark each item as synced
947
977
  if (Array.isArray(data)) {
948
- const arrayMeta = store.getShadowMetadata(stateKey, path);
978
+ const arrayMeta = getShadowMetadata(stateKey, path);
949
979
  if (arrayMeta?.arrayKeys) {
950
980
  arrayMeta.arrayKeys.forEach((itemKey, index) => {
951
- const itemPath = itemKey.split('.').slice(1);
981
+ // Fix: Don't split the itemKey, just use it directly
982
+ const itemPath = [...path, itemKey];
952
983
  const itemData = data[index];
953
984
  if (itemData !== undefined) {
954
985
  markEntireStateAsServerSynced(
@@ -970,8 +1001,346 @@ function markEntireStateAsServerSynced(
970
1001
  });
971
1002
  }
972
1003
  }
973
- let updateBatchQueue = new Map<string, Array<UpdateArg<any>>>();
974
- let batchFlushScheduled = false;
1004
+ // 5. Batch queue
1005
+ let updateBatchQueue: any[] = [];
1006
+ let isFlushScheduled = false;
1007
+
1008
+ function scheduleFlush() {
1009
+ if (!isFlushScheduled) {
1010
+ isFlushScheduled = true;
1011
+ queueMicrotask(flushQueue);
1012
+ }
1013
+ }
1014
+ function handleUpdate(
1015
+ stateKey: string,
1016
+ path: string[],
1017
+ payload: any
1018
+ ): { type: 'update'; oldValue: any; newValue: any; shadowMeta: any } {
1019
+ // ✅ FIX: Get the old value before the update.
1020
+ const oldValue = getGlobalStore.getState().getShadowValue(stateKey, path);
1021
+
1022
+ const newValue = isFunction(payload) ? payload(oldValue) : payload;
1023
+
1024
+ // ✅ FIX: The new `updateShadowAtPath` handles metadata preservation automatically.
1025
+ // The manual loop has been removed.
1026
+ updateShadowAtPath(stateKey, path, newValue);
1027
+
1028
+ markAsDirty(stateKey, path, { bubble: true });
1029
+
1030
+ // Return the metadata of the node *after* the update.
1031
+ const newShadowMeta = getShadowMetadata(stateKey, path);
1032
+
1033
+ return {
1034
+ type: 'update',
1035
+ oldValue: oldValue,
1036
+ newValue,
1037
+ shadowMeta: newShadowMeta,
1038
+ };
1039
+ }
1040
+ // 2. Update signals
1041
+ function updateSignals(shadowMeta: any, displayValue: any) {
1042
+ if (!shadowMeta?.signals?.length) return;
1043
+
1044
+ shadowMeta.signals.forEach(({ parentId, position, effect }: any) => {
1045
+ const parent = document.querySelector(`[data-parent-id="${parentId}"]`);
1046
+ if (!parent) return;
1047
+
1048
+ const childNodes = Array.from(parent.childNodes);
1049
+ if (!childNodes[position]) return;
1050
+
1051
+ let finalDisplayValue = displayValue;
1052
+ if (effect && displayValue !== null) {
1053
+ try {
1054
+ finalDisplayValue = new Function('state', `return (${effect})(state)`)(
1055
+ displayValue
1056
+ );
1057
+ } catch (err) {
1058
+ console.error('Error evaluating effect function:', err);
1059
+ }
1060
+ }
1061
+
1062
+ if (finalDisplayValue !== null && typeof finalDisplayValue === 'object') {
1063
+ finalDisplayValue = JSON.stringify(finalDisplayValue);
1064
+ }
1065
+
1066
+ childNodes[position].textContent = String(finalDisplayValue ?? '');
1067
+ });
1068
+ }
1069
+
1070
+ function getComponentNotifications(
1071
+ stateKey: string,
1072
+ path: string[],
1073
+ result: any
1074
+ ): Set<any> {
1075
+ const rootMeta = getShadowMetadata(stateKey, []);
1076
+
1077
+ if (!rootMeta?.components) {
1078
+ return new Set();
1079
+ }
1080
+
1081
+ const componentsToNotify = new Set<any>();
1082
+
1083
+ // --- PASS 1: Notify specific subscribers based on update type ---
1084
+
1085
+ if (result.type === 'update') {
1086
+ // --- Bubble-up Notification ---
1087
+ // An update to `user.address.street` notifies listeners of `street`, `address`, and `user`.
1088
+ let currentPath = [...path];
1089
+ while (true) {
1090
+ const pathMeta = getShadowMetadata(stateKey, currentPath);
1091
+
1092
+ if (pathMeta?.pathComponents) {
1093
+ pathMeta.pathComponents.forEach((componentId: string) => {
1094
+ const component = rootMeta.components?.get(componentId);
1095
+ // NEW: Add component to the set instead of calling forceUpdate()
1096
+ if (component) {
1097
+ const reactiveTypes = Array.isArray(component.reactiveType)
1098
+ ? component.reactiveType
1099
+ : [component.reactiveType || 'component'];
1100
+ if (!reactiveTypes.includes('none')) {
1101
+ componentsToNotify.add(component);
1102
+ }
1103
+ }
1104
+ });
1105
+ }
1106
+
1107
+ if (currentPath.length === 0) break;
1108
+ currentPath.pop(); // Go up one level
1109
+ }
1110
+
1111
+ // --- Deep Object Change Notification ---
1112
+ // If the new value is an object, notify components subscribed to sub-paths that changed.
1113
+ if (
1114
+ result.newValue &&
1115
+ typeof result.newValue === 'object' &&
1116
+ !isArray(result.newValue)
1117
+ ) {
1118
+ const changedSubPaths = getDifferences(result.newValue, result.oldValue);
1119
+
1120
+ changedSubPaths.forEach((subPathString: string) => {
1121
+ const subPath = subPathString.split('.');
1122
+ const fullSubPath = [...path, ...subPath];
1123
+ const subPathMeta = getShadowMetadata(stateKey, fullSubPath);
1124
+
1125
+ if (subPathMeta?.pathComponents) {
1126
+ subPathMeta.pathComponents.forEach((componentId: string) => {
1127
+ const component = rootMeta.components?.get(componentId);
1128
+ // NEW: Add component to the set
1129
+ if (component) {
1130
+ const reactiveTypes = Array.isArray(component.reactiveType)
1131
+ ? component.reactiveType
1132
+ : [component.reactiveType || 'component'];
1133
+ if (!reactiveTypes.includes('none')) {
1134
+ componentsToNotify.add(component);
1135
+ }
1136
+ }
1137
+ });
1138
+ }
1139
+ });
1140
+ }
1141
+ } else if (result.type === 'insert' || result.type === 'cut') {
1142
+ // For array structural changes (add/remove), notify components listening to the parent array.
1143
+ const parentArrayPath = result.type === 'insert' ? path : path.slice(0, -1);
1144
+ const parentMeta = getShadowMetadata(stateKey, parentArrayPath);
1145
+
1146
+ if (parentMeta?.pathComponents) {
1147
+ parentMeta.pathComponents.forEach((componentId: string) => {
1148
+ const component = rootMeta.components?.get(componentId);
1149
+ // NEW: Add component to the set
1150
+ if (component) {
1151
+ componentsToNotify.add(component);
1152
+ }
1153
+ });
1154
+ }
1155
+ }
1156
+
1157
+ // --- PASS 2: Handle 'all' and 'deps' reactivity types ---
1158
+ // Iterate over all components for this stateKey that haven't been notified yet.
1159
+ rootMeta.components.forEach((component, componentId) => {
1160
+ // If we've already added this component, skip it.
1161
+ if (componentsToNotify.has(component)) {
1162
+ return;
1163
+ }
1164
+
1165
+ const reactiveTypes = Array.isArray(component.reactiveType)
1166
+ ? component.reactiveType
1167
+ : [component.reactiveType || 'component'];
1168
+
1169
+ if (reactiveTypes.includes('all')) {
1170
+ componentsToNotify.add(component);
1171
+ } else if (reactiveTypes.includes('deps') && component.depsFunction) {
1172
+ const currentState = getShadowValue(stateKey, []);
1173
+ const newDeps = component.depsFunction(currentState);
1174
+
1175
+ if (
1176
+ newDeps === true ||
1177
+ (Array.isArray(newDeps) && !isDeepEqual(component.prevDeps, newDeps))
1178
+ ) {
1179
+ component.prevDeps = newDeps as any; // Update the dependencies for the next check
1180
+ componentsToNotify.add(component);
1181
+ }
1182
+ }
1183
+ });
1184
+
1185
+ return componentsToNotify;
1186
+ }
1187
+
1188
+ function handleInsert(
1189
+ stateKey: string,
1190
+ path: string[],
1191
+ payload: any
1192
+ ): { type: 'insert'; newValue: any; shadowMeta: any } {
1193
+ let newValue;
1194
+ if (isFunction(payload)) {
1195
+ const { value: currentValue } = getScopedData(stateKey, path);
1196
+ newValue = payload({ state: currentValue, uuid: uuidv4() });
1197
+ } else {
1198
+ newValue = payload;
1199
+ }
1200
+
1201
+ insertShadowArrayElement(stateKey, path, newValue);
1202
+ markAsDirty(stateKey, path, { bubble: true });
1203
+
1204
+ const updatedMeta = getShadowMetadata(stateKey, path);
1205
+ if (updatedMeta?.arrayKeys) {
1206
+ const newItemKey = updatedMeta.arrayKeys[updatedMeta.arrayKeys.length - 1];
1207
+ if (newItemKey) {
1208
+ const newItemPath = newItemKey.split('.').slice(1);
1209
+ markAsDirty(stateKey, newItemPath, { bubble: false });
1210
+ }
1211
+ }
1212
+
1213
+ return { type: 'insert', newValue, shadowMeta: updatedMeta };
1214
+ }
1215
+
1216
+ function handleCut(
1217
+ stateKey: string,
1218
+ path: string[]
1219
+ ): { type: 'cut'; oldValue: any; parentPath: string[] } {
1220
+ const parentArrayPath = path.slice(0, -1);
1221
+ const oldValue = getShadowValue(stateKey, path);
1222
+ removeShadowArrayElement(stateKey, path);
1223
+ markAsDirty(stateKey, parentArrayPath, { bubble: true });
1224
+ return { type: 'cut', oldValue: oldValue, parentPath: parentArrayPath };
1225
+ }
1226
+
1227
+ function flushQueue() {
1228
+ const allComponentsToNotify = new Set<any>();
1229
+ const signalUpdates: { shadowMeta: any; displayValue: any }[] = [];
1230
+
1231
+ const logsToAdd: UpdateTypeDetail[] = [];
1232
+
1233
+ for (const item of updateBatchQueue) {
1234
+ if (item.status && item.updateType) {
1235
+ logsToAdd.push(item as UpdateTypeDetail);
1236
+ continue;
1237
+ }
1238
+
1239
+ const result = item;
1240
+
1241
+ const displayValue = result.type === 'cut' ? null : result.newValue;
1242
+ if (result.shadowMeta?.signals?.length > 0) {
1243
+ signalUpdates.push({ shadowMeta: result.shadowMeta, displayValue });
1244
+ }
1245
+
1246
+ const componentNotifications = getComponentNotifications(
1247
+ result.stateKey,
1248
+ result.path,
1249
+ result
1250
+ );
1251
+
1252
+ componentNotifications.forEach((component) => {
1253
+ allComponentsToNotify.add(component);
1254
+ });
1255
+ }
1256
+
1257
+ if (logsToAdd.length > 0) {
1258
+ addStateLog(logsToAdd);
1259
+ }
1260
+
1261
+ signalUpdates.forEach(({ shadowMeta, displayValue }) => {
1262
+ updateSignals(shadowMeta, displayValue);
1263
+ });
1264
+
1265
+ allComponentsToNotify.forEach((component) => {
1266
+ component.forceUpdate();
1267
+ });
1268
+
1269
+ // --- Step 3: CLEANUP ---
1270
+ // Clear the queue for the next batch of updates.
1271
+ updateBatchQueue = [];
1272
+ isFlushScheduled = false;
1273
+ }
1274
+
1275
+ function createEffectiveSetState<T>(
1276
+ thisKey: string,
1277
+ syncApiRef: React.MutableRefObject<any>,
1278
+ sessionId: string | undefined,
1279
+ latestInitialOptionsRef: React.MutableRefObject<OptionsType<T> | null>
1280
+ ): EffectiveSetState<T> {
1281
+ // The returned function is the core setter that gets called by all state operations.
1282
+ // It is now much simpler, delegating all work to the executeUpdate function.
1283
+ return (newStateOrFunction, path, updateObj, validationKey?) => {
1284
+ executeUpdate(thisKey, path, newStateOrFunction, updateObj);
1285
+ };
1286
+
1287
+ // This inner function handles the logic for a single state update.
1288
+ function executeUpdate(
1289
+ stateKey: string,
1290
+ path: string[],
1291
+ payload: any,
1292
+ options: UpdateOptions
1293
+ ) {
1294
+ // --- Step 1: Execute the core state change (Synchronous & Fast) ---
1295
+ // This part modifies the in-memory state representation immediately.
1296
+ let result: any;
1297
+ switch (options.updateType) {
1298
+ case 'update':
1299
+ result = handleUpdate(stateKey, path, payload);
1300
+ break;
1301
+ case 'insert':
1302
+ result = handleInsert(stateKey, path, payload);
1303
+ break;
1304
+ case 'cut':
1305
+ result = handleCut(stateKey, path);
1306
+ break;
1307
+ }
1308
+
1309
+ result.stateKey = stateKey;
1310
+ result.path = path;
1311
+ updateBatchQueue.push(result);
1312
+ scheduleFlush();
1313
+
1314
+ const newUpdate: UpdateTypeDetail = {
1315
+ timeStamp: Date.now(),
1316
+ stateKey,
1317
+ path,
1318
+ updateType: options.updateType,
1319
+ status: 'new',
1320
+ oldValue: result.oldValue,
1321
+ newValue: result.newValue ?? null,
1322
+ };
1323
+
1324
+ updateBatchQueue.push(newUpdate);
1325
+
1326
+ if (result.newValue !== undefined) {
1327
+ saveToLocalStorage(
1328
+ result.newValue,
1329
+ stateKey,
1330
+ latestInitialOptionsRef.current,
1331
+ sessionId
1332
+ );
1333
+ }
1334
+
1335
+ if (latestInitialOptionsRef.current?.middleware) {
1336
+ latestInitialOptionsRef.current.middleware({ update: newUpdate });
1337
+ }
1338
+
1339
+ if (options.sync !== false && syncApiRef.current?.connected) {
1340
+ syncApiRef.current.updateState({ operation: newUpdate });
1341
+ }
1342
+ }
1343
+ }
975
1344
 
976
1345
  export function useCogsStateFn<TStateObject extends unknown>(
977
1346
  stateObject: TStateObject,
@@ -996,7 +1365,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
996
1365
  syncOptions?: SyncOptionsType<any>;
997
1366
  } & OptionsType<TStateObject> = {}
998
1367
  ) {
999
- console.time('useCogsStateFn top');
1000
1368
  const [reactiveForce, forceUpdate] = useState({}); //this is the key to reactivity
1001
1369
  const { sessionId } = useCogsConfig();
1002
1370
  let noStateKey = stateKey ? false : true;
@@ -1011,7 +1379,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
1011
1379
  useEffect(() => {
1012
1380
  if (syncUpdate && syncUpdate.stateKey === thisKey && syncUpdate.path?.[0]) {
1013
1381
  const syncKey = `${syncUpdate.stateKey}:${syncUpdate.path.join('.')}`;
1014
- getGlobalStore.getState().setSyncInfo(syncKey, {
1382
+ setSyncInfo(syncKey, {
1015
1383
  timeStamp: syncUpdate.timeStamp!,
1016
1384
  userId: syncUpdate.userId!,
1017
1385
  });
@@ -1081,7 +1449,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
1081
1449
 
1082
1450
  // Effect 1: When this component's serverState prop changes, broadcast it
1083
1451
  useEffect(() => {
1084
- getGlobalStore.getState().setServerStateUpdate(thisKey, serverState);
1452
+ setServerStateUpdate(thisKey, serverState);
1085
1453
  }, [serverState, thisKey]);
1086
1454
 
1087
1455
  // Effect 2: Listen for server state updates from ANY component
@@ -1091,115 +1459,79 @@ export function useCogsStateFn<TStateObject extends unknown>(
1091
1459
  .subscribeToPath(thisKey, (event) => {
1092
1460
  if (event?.type === 'SERVER_STATE_UPDATE') {
1093
1461
  const serverStateData = event.serverState;
1094
- console.log('SERVER_STATE_UPDATE', event);
1462
+
1095
1463
  if (
1096
- serverStateData?.status === 'success' &&
1097
- serverStateData.data !== undefined
1464
+ serverStateData?.status !== 'success' ||
1465
+ serverStateData.data === undefined
1098
1466
  ) {
1099
- const newOptions = { serverState: serverStateData };
1100
- setAndMergeOptions(thisKey, newOptions);
1101
-
1102
- const mergeConfig =
1103
- typeof serverStateData.merge === 'object'
1104
- ? serverStateData.merge
1105
- : serverStateData.merge === true
1106
- ? { strategy: 'append' }
1107
- : null;
1467
+ return; // Ignore if no valid data
1468
+ }
1108
1469
 
1109
- const currentState = getGlobalStore
1110
- .getState()
1111
- .getShadowValue(thisKey);
1112
- const incomingData = serverStateData.data;
1113
- if (
1114
- mergeConfig &&
1115
- Array.isArray(currentState) &&
1116
- Array.isArray(incomingData)
1117
- ) {
1118
- const keyField = mergeConfig.key;
1119
- const existingIds = new Set(
1120
- currentState.map((item: any) => item[keyField])
1121
- );
1470
+ console.log(
1471
+ '✅ SERVER_STATE_UPDATE received with data:',
1472
+ serverStateData
1473
+ );
1122
1474
 
1123
- const newUniqueItems = incomingData.filter((item: any) => {
1124
- return !existingIds.has(item[keyField]);
1125
- });
1475
+ setAndMergeOptions(thisKey, { serverState: serverStateData });
1126
1476
 
1127
- if (newUniqueItems.length > 0) {
1128
- newUniqueItems.forEach((item) => {
1129
- getGlobalStore
1130
- .getState()
1131
- .insertShadowArrayElement(thisKey, [], item);
1477
+ const mergeConfig =
1478
+ typeof serverStateData.merge === 'object'
1479
+ ? serverStateData.merge
1480
+ : serverStateData.merge === true
1481
+ ? { strategy: 'append' }
1482
+ : null;
1132
1483
 
1133
- // MARK NEW SERVER ITEMS AS SYNCED
1134
- const arrayMeta = getGlobalStore
1135
- .getState()
1136
- .getShadowMetadata(thisKey, []);
1137
-
1138
- if (arrayMeta?.arrayKeys) {
1139
- const newItemKey =
1140
- arrayMeta.arrayKeys[arrayMeta.arrayKeys.length - 1];
1141
- if (newItemKey) {
1142
- const newItemPath = newItemKey.split('.').slice(1);
1143
-
1144
- // Mark the new server item as synced, not dirty
1145
- getGlobalStore
1146
- .getState()
1147
- .setShadowMetadata(thisKey, newItemPath, {
1148
- isDirty: false,
1149
- stateSource: 'server',
1150
- lastServerSync:
1151
- serverStateData.timestamp || Date.now(),
1152
- });
1484
+ // FIX 1: The path for the root value is now `[]`.
1485
+ const currentState = getShadowValue(thisKey, []);
1486
+ const incomingData = serverStateData.data;
1153
1487
 
1154
- // Also mark all its child fields as synced if it's an object
1155
- const itemValue = getGlobalStore
1156
- .getState()
1157
- .getShadowValue(newItemKey);
1158
- if (
1159
- itemValue &&
1160
- typeof itemValue === 'object' &&
1161
- !Array.isArray(itemValue)
1162
- ) {
1163
- Object.keys(itemValue).forEach((fieldKey) => {
1164
- const fieldPath = [...newItemPath, fieldKey];
1165
- getGlobalStore
1166
- .getState()
1167
- .setShadowMetadata(thisKey, fieldPath, {
1168
- isDirty: false,
1169
- stateSource: 'server',
1170
- lastServerSync:
1171
- serverStateData.timestamp || Date.now(),
1172
- });
1173
- });
1174
- }
1175
- }
1176
- }
1177
- });
1178
- }
1179
- } else {
1180
- // For replace strategy or initial load
1181
- getGlobalStore
1182
- .getState()
1183
- .initializeShadowState(thisKey, incomingData);
1184
-
1185
- // Mark the entire state tree as synced from server
1186
- markEntireStateAsServerSynced(
1187
- thisKey,
1188
- [],
1189
- incomingData,
1190
- serverStateData.timestamp
1488
+ if (
1489
+ mergeConfig &&
1490
+ mergeConfig.strategy === 'append' &&
1491
+ 'key' in mergeConfig && // Type guard for key
1492
+ Array.isArray(currentState) &&
1493
+ Array.isArray(incomingData)
1494
+ ) {
1495
+ const keyField = mergeConfig.key;
1496
+ if (!keyField) {
1497
+ console.error(
1498
+ "CogsState: Merge strategy 'append' requires a 'key' field."
1191
1499
  );
1500
+ return;
1192
1501
  }
1502
+ console.log('SERVER_STATE_UPDATE 2');
1503
+ const existingIds = new Set(
1504
+ currentState.map((item: any) => item[keyField])
1505
+ );
1193
1506
 
1194
- const meta = getGlobalStore
1195
- .getState()
1196
- .getShadowMetadata(thisKey, []);
1197
- getGlobalStore.getState().setShadowMetadata(thisKey, [], {
1198
- ...meta,
1199
- stateSource: 'server',
1200
- lastServerSync: serverStateData.timestamp || Date.now(),
1201
- isDirty: false,
1202
- });
1507
+ const newUniqueItems = incomingData.filter(
1508
+ (item: any) => !existingIds.has(item[keyField])
1509
+ );
1510
+
1511
+ if (newUniqueItems.length > 0) {
1512
+ newUniqueItems.forEach((item) => {
1513
+ insertShadowArrayElement(thisKey, [], item);
1514
+ });
1515
+ }
1516
+
1517
+ // Mark the entire final state as synced
1518
+ const finalState = getShadowValue(thisKey, []);
1519
+ markEntireStateAsServerSynced(
1520
+ thisKey,
1521
+ [],
1522
+ finalState,
1523
+ serverStateData.timestamp
1524
+ );
1525
+ } else {
1526
+ // This handles the "replace" strategy (initial load)
1527
+ initializeShadowState(thisKey, incomingData);
1528
+
1529
+ markEntireStateAsServerSynced(
1530
+ thisKey,
1531
+ [],
1532
+ incomingData,
1533
+ serverStateData.timestamp
1534
+ );
1203
1535
  }
1204
1536
  }
1205
1537
  });
@@ -1217,6 +1549,17 @@ export function useCogsStateFn<TStateObject extends unknown>(
1217
1549
 
1218
1550
  const options = getInitialOptions(thisKey as string);
1219
1551
 
1552
+ const features = {
1553
+ syncEnabled: !!cogsSyncFn && !!syncOpt,
1554
+ validationEnabled: !!(
1555
+ options?.validation?.zodSchemaV4 || options?.validation?.zodSchemaV3
1556
+ ),
1557
+ localStorageEnabled: !!options?.localStorage?.key,
1558
+ };
1559
+ setShadowMetadata(thisKey, [], {
1560
+ ...existingMeta,
1561
+ features,
1562
+ });
1220
1563
  if (options?.defaultState !== undefined || defaultState !== undefined) {
1221
1564
  const finalDefaultState = options?.defaultState || defaultState;
1222
1565
 
@@ -1229,10 +1572,10 @@ export function useCogsStateFn<TStateObject extends unknown>(
1229
1572
 
1230
1573
  const { value: resolvedState, source, timestamp } = resolveInitialState();
1231
1574
 
1232
- getGlobalStore.getState().initializeShadowState(thisKey, resolvedState);
1575
+ initializeShadowState(thisKey, resolvedState);
1233
1576
 
1234
1577
  // Set shadow metadata with the correct source info
1235
- getGlobalStore.getState().setShadowMetadata(thisKey, [], {
1578
+ setShadowMetadata(thisKey, [], {
1236
1579
  stateSource: source,
1237
1580
  lastServerSync: source === 'server' ? timestamp : undefined,
1238
1581
  isDirty: false,
@@ -1256,29 +1599,27 @@ export function useCogsStateFn<TStateObject extends unknown>(
1256
1599
  const componentKey = `${thisKey}////${componentIdRef.current}`;
1257
1600
 
1258
1601
  // Register component in shadow metadata at root level
1259
- const rootMeta = getGlobalStore.getState().getShadowMetadata(thisKey, []);
1602
+ const rootMeta = getShadowMetadata(thisKey, []);
1260
1603
  const components = rootMeta?.components || new Map();
1261
1604
 
1262
1605
  components.set(componentKey, {
1263
1606
  forceUpdate: () => forceUpdate({}),
1264
- reactiveType: reactiveType ?? ['component', 'deps'],
1607
+ reactiveType: reactiveType ?? ['component'],
1265
1608
  paths: new Set(),
1266
1609
  depsFunction: reactiveDeps || undefined,
1267
- deps: reactiveDeps
1268
- ? reactiveDeps(getGlobalStore.getState().getShadowValue(thisKey))
1269
- : [],
1610
+ deps: reactiveDeps ? reactiveDeps(getShadowValue(thisKey, [])) : [],
1270
1611
  prevDeps: reactiveDeps // Initialize prevDeps with the same initial value
1271
- ? reactiveDeps(getGlobalStore.getState().getShadowValue(thisKey))
1612
+ ? reactiveDeps(getShadowValue(thisKey, []))
1272
1613
  : [],
1273
1614
  });
1274
1615
 
1275
- getGlobalStore.getState().setShadowMetadata(thisKey, [], {
1616
+ setShadowMetadata(thisKey, [], {
1276
1617
  ...rootMeta,
1277
1618
  components,
1278
1619
  });
1279
1620
  forceUpdate({});
1280
1621
  return () => {
1281
- const meta = getGlobalStore.getState().getShadowMetadata(thisKey, []);
1622
+ const meta = getShadowMetadata(thisKey, []);
1282
1623
  const component = meta?.components?.get(componentKey);
1283
1624
 
1284
1625
  // Remove from each path we registered to
@@ -1306,473 +1647,31 @@ export function useCogsStateFn<TStateObject extends unknown>(
1306
1647
 
1307
1648
  // Remove from root components
1308
1649
  if (meta?.components) {
1309
- getGlobalStore.getState().setShadowMetadata(thisKey, [], meta);
1650
+ setShadowMetadata(thisKey, [], meta);
1310
1651
  }
1311
1652
  };
1312
1653
  }, []);
1313
1654
 
1314
1655
  const syncApiRef = useRef<SyncApi | null>(null);
1315
- console.timeEnd('useCogsStateFn top');
1316
-
1317
- const effectiveSetState = (
1318
- newStateOrFunction: UpdateArg<TStateObject> | InsertParams<TStateObject>,
1319
- path: string[],
1320
- updateObj: UpdateOptions
1321
- ) => {
1322
- console.time('top of effectiveSetState');
1323
- const fullPath = [thisKey, ...path].join('.');
1324
- const store = getGlobalStore.getState();
1325
-
1326
- const shadowMeta = store.getShadowMetadata(thisKey, path);
1327
- const nestedShadowValue = store.getShadowValue(fullPath) as TStateObject;
1328
- console.timeEnd('top of effectiveSetState');
1329
-
1330
- console.time('top of payload');
1331
- const payload = (
1332
- updateObj.updateType === 'insert' && isFunction(newStateOrFunction)
1333
- ? newStateOrFunction({ state: nestedShadowValue, uuid: uuidv4() })
1334
- : isFunction(newStateOrFunction)
1335
- ? newStateOrFunction(nestedShadowValue)
1336
- : newStateOrFunction
1337
- ) as TStateObject;
1338
-
1339
- const timeStamp = Date.now();
1340
-
1341
- const newUpdate = {
1342
- timeStamp,
1343
- stateKey: thisKey,
1344
- path,
1345
- updateType: updateObj.updateType,
1346
- status: 'new' as const,
1347
- oldValue: nestedShadowValue,
1348
- newValue: payload,
1349
- } satisfies UpdateTypeDetail;
1350
- console.timeEnd('top of payload');
1351
-
1352
- console.time('switch in effectiveSetState');
1353
- // Perform the update
1354
- switch (updateObj.updateType) {
1355
- case 'insert': {
1356
- store.insertShadowArrayElement(thisKey, path, newUpdate.newValue);
1357
- store.markAsDirty(thisKey, path, { bubble: true });
1358
- const arrayMeta = shadowMeta;
1359
- if (arrayMeta?.arrayKeys) {
1360
- const newItemKey =
1361
- arrayMeta.arrayKeys[arrayMeta.arrayKeys.length - 1];
1362
- if (newItemKey) {
1363
- const newItemPath = newItemKey.split('.').slice(1); // Remove stateKey
1364
- store.markAsDirty(thisKey, newItemPath, { bubble: false });
1365
- }
1366
- }
1367
- break;
1368
- }
1369
- case 'cut': {
1370
- const parentArrayPath = path.slice(0, -1);
1371
-
1372
- store.removeShadowArrayElement(thisKey, path);
1373
- store.markAsDirty(thisKey, parentArrayPath, { bubble: true });
1374
- break;
1375
- }
1376
- case 'update': {
1377
- store.updateShadowAtPath(thisKey, path, newUpdate.newValue);
1378
- store.markAsDirty(thisKey, path, { bubble: true });
1379
- break;
1380
- }
1381
- }
1382
- console.timeEnd('switch in effectiveSetState');
1383
- const shouldSync = updateObj.sync !== false;
1384
- console.time('signals');
1385
-
1386
- if (shouldSync && syncApiRef.current && syncApiRef.current.connected) {
1387
- syncApiRef.current.updateState({ operation: newUpdate });
1388
- }
1389
- // Handle signals - reuse shadowMeta from the beginning
1390
- if (shadowMeta?.signals && shadowMeta.signals.length > 0) {
1391
- // Use updatedShadowValue if we need the new value, otherwise use payload
1392
- const displayValue = updateObj.updateType === 'cut' ? null : payload;
1393
-
1394
- shadowMeta.signals.forEach(({ parentId, position, effect }) => {
1395
- const parent = document.querySelector(`[data-parent-id="${parentId}"]`);
1396
- if (parent) {
1397
- const childNodes = Array.from(parent.childNodes);
1398
- if (childNodes[position]) {
1399
- let finalDisplayValue = displayValue;
1400
- if (effect && displayValue !== null) {
1401
- try {
1402
- finalDisplayValue = new Function(
1403
- 'state',
1404
- `return (${effect})(state)`
1405
- )(displayValue);
1406
- } catch (err) {
1407
- console.error('Error evaluating effect function:', err);
1408
- }
1409
- }
1410
-
1411
- if (
1412
- finalDisplayValue !== null &&
1413
- finalDisplayValue !== undefined &&
1414
- typeof finalDisplayValue === 'object'
1415
- ) {
1416
- finalDisplayValue = JSON.stringify(finalDisplayValue) as any;
1417
- }
1418
-
1419
- childNodes[position].textContent = String(finalDisplayValue ?? '');
1420
- }
1421
- }
1422
- });
1423
- }
1424
-
1425
- // Update in effectiveSetState for insert handling:
1426
- if (updateObj.updateType === 'insert') {
1427
- // Use shadowMeta from beginning if it's an array
1428
- if (shadowMeta?.mapWrappers && shadowMeta.mapWrappers.length > 0) {
1429
- // Get fresh array keys after insert
1430
- const sourceArrayKeys =
1431
- store.getShadowMetadata(thisKey, path)?.arrayKeys || [];
1432
- const newItemKey = sourceArrayKeys[sourceArrayKeys.length - 1]!;
1433
- const newItemValue = store.getShadowValue(newItemKey);
1434
- const fullSourceArray = store.getShadowValue(
1435
- [thisKey, ...path].join('.')
1436
- );
1437
-
1438
- if (!newItemKey || newItemValue === undefined) return;
1439
-
1440
- shadowMeta.mapWrappers.forEach((wrapper) => {
1441
- let shouldRender = true;
1442
- let insertPosition = -1;
1443
-
1444
- // Check if wrapper has transforms
1445
- if (wrapper.meta?.transforms && wrapper.meta.transforms.length > 0) {
1446
- // Check if new item passes all filters
1447
- for (const transform of wrapper.meta.transforms) {
1448
- if (transform.type === 'filter') {
1449
- if (!transform.fn(newItemValue, -1)) {
1450
- shouldRender = false;
1451
- break;
1452
- }
1453
- }
1454
- }
1455
-
1456
- if (shouldRender) {
1457
- // Get current valid keys by applying transforms
1458
- const currentValidKeys = applyTransforms(
1459
- thisKey,
1460
- path,
1461
- wrapper.meta.transforms
1462
- );
1463
-
1464
- // Find where to insert based on sort
1465
- const sortTransform = wrapper.meta.transforms.find(
1466
- (t: any) => t.type === 'sort'
1467
- );
1468
- if (sortTransform) {
1469
- // Add new item to the list and sort to find position
1470
- const allItems = currentValidKeys.map((key) => ({
1471
- key,
1472
- value: store.getShadowValue(key),
1473
- }));
1474
-
1475
- allItems.push({ key: newItemKey, value: newItemValue });
1476
- allItems.sort((a, b) => sortTransform.fn(a.value, b.value));
1477
-
1478
- insertPosition = allItems.findIndex(
1479
- (item) => item.key === newItemKey
1480
- );
1481
- } else {
1482
- // No sort, insert at end
1483
- insertPosition = currentValidKeys.length;
1484
- }
1485
- }
1486
- } else {
1487
- // No transforms, always render at end
1488
- shouldRender = true;
1489
- insertPosition = sourceArrayKeys.length - 1;
1490
- }
1491
-
1492
- if (!shouldRender) {
1493
- return; // Skip this wrapper, item doesn't pass filters
1494
- }
1495
-
1496
- if (wrapper.containerRef && wrapper.containerRef.isConnected) {
1497
- const itemElement = document.createElement('div');
1498
- itemElement.setAttribute('data-item-path', newItemKey);
1499
-
1500
- // Insert at correct position
1501
- const children = Array.from(wrapper.containerRef.children);
1502
- if (insertPosition >= 0 && insertPosition < children.length) {
1503
- wrapper.containerRef.insertBefore(
1504
- itemElement,
1505
- children[insertPosition]!
1506
- );
1507
- } else {
1508
- wrapper.containerRef.appendChild(itemElement);
1509
- }
1510
-
1511
- const root = createRoot(itemElement);
1512
- const componentId = uuidv4();
1513
- const itemPath = newItemKey.split('.').slice(1);
1514
-
1515
- const arraySetter = wrapper.rebuildStateShape({
1516
- path: wrapper.path,
1517
- currentState: fullSourceArray,
1518
- componentId: wrapper.componentId,
1519
- meta: wrapper.meta,
1520
- });
1521
-
1522
- root.render(
1523
- createElement(MemoizedCogsItemWrapper, {
1524
- stateKey: thisKey,
1525
- itemComponentId: componentId,
1526
- itemPath: itemPath,
1527
- localIndex: insertPosition,
1528
- arraySetter: arraySetter,
1529
- rebuildStateShape: wrapper.rebuildStateShape,
1530
- renderFn: wrapper.mapFn,
1531
- })
1532
- );
1533
- }
1534
- });
1535
- }
1536
- }
1537
-
1538
- if (updateObj.updateType === 'cut') {
1539
- const arrayPath = path.slice(0, -1);
1540
- const arrayMeta = store.getShadowMetadata(thisKey, arrayPath);
1541
-
1542
- if (arrayMeta?.mapWrappers && arrayMeta.mapWrappers.length > 0) {
1543
- arrayMeta.mapWrappers.forEach((wrapper) => {
1544
- if (wrapper.containerRef && wrapper.containerRef.isConnected) {
1545
- const elementToRemove = wrapper.containerRef.querySelector(
1546
- `[data-item-path="${fullPath}"]`
1547
- );
1548
- if (elementToRemove) {
1549
- elementToRemove.remove();
1550
- }
1551
- }
1552
- });
1553
- }
1554
- }
1555
- console.timeEnd('signals');
1556
-
1557
- console.time('notify');
1558
- const rootMeta = store.getShadowMetadata(thisKey, []);
1559
- const notifiedComponents = new Set<string>();
1560
-
1561
- if (!rootMeta?.components) {
1562
- return;
1563
- }
1564
-
1565
- // --- PASS 1: Notify specific subscribers based on update type ---
1566
-
1567
- if (updateObj.updateType === 'update') {
1568
- // --- Bubble-up Notification ---
1569
- // When a nested property changes, notify components listening at that exact path,
1570
- // and also "bubble up" to notify components listening on parent paths.
1571
- // e.g., an update to `user.address.street` notifies listeners of `street`, `address`, and `user`.
1572
- let currentPath = [...path]; // Create a mutable copy of the path
1573
-
1574
- while (true) {
1575
- const currentPathMeta = store.getShadowMetadata(thisKey, currentPath);
1576
-
1577
- if (currentPathMeta?.pathComponents) {
1578
- currentPathMeta.pathComponents.forEach((componentId) => {
1579
- if (notifiedComponents.has(componentId)) {
1580
- return; // Avoid sending redundant notifications
1581
- }
1582
- const component = rootMeta.components?.get(componentId);
1583
- if (component) {
1584
- const reactiveTypes = Array.isArray(component.reactiveType)
1585
- ? component.reactiveType
1586
- : [component.reactiveType || 'component'];
1587
-
1588
- // This notification logic applies to components that depend on object structures.
1589
- if (!reactiveTypes.includes('none')) {
1590
- component.forceUpdate();
1591
- notifiedComponents.add(componentId);
1592
- }
1593
- }
1594
- });
1595
- }
1596
-
1597
- if (currentPath.length === 0) {
1598
- break; // We've reached the root, stop bubbling.
1599
- }
1600
- currentPath.pop(); // Go up one level for the next iteration.
1601
- }
1602
-
1603
- // ADDITIONALLY, if the payload is an object, perform a deep-check and
1604
- // notify any components that are subscribed to specific sub-paths that changed.
1605
- if (
1606
- payload &&
1607
- typeof payload === 'object' &&
1608
- !isArray(payload) &&
1609
- nestedShadowValue &&
1610
- typeof nestedShadowValue === 'object' &&
1611
- !isArray(nestedShadowValue)
1612
- ) {
1613
- // Get a list of dot-separated paths that have changed (e.g., ['name', 'address.city'])
1614
- const changedSubPaths = getDifferences(payload, nestedShadowValue);
1615
-
1616
- changedSubPaths.forEach((subPathString) => {
1617
- const subPath = subPathString.split('.');
1618
- const fullSubPath = [...path, ...subPath];
1619
-
1620
- // Get the metadata (and subscribers) for this specific nested path
1621
- const subPathMeta = store.getShadowMetadata(thisKey, fullSubPath);
1622
- if (subPathMeta?.pathComponents) {
1623
- subPathMeta.pathComponents.forEach((componentId) => {
1624
- // Avoid sending a redundant update
1625
- if (notifiedComponents.has(componentId)) {
1626
- return;
1627
- }
1628
- const component = rootMeta.components?.get(componentId);
1629
- if (component) {
1630
- const reactiveTypes = Array.isArray(component.reactiveType)
1631
- ? component.reactiveType
1632
- : [component.reactiveType || 'component'];
1633
-
1634
- if (!reactiveTypes.includes('none')) {
1635
- component.forceUpdate();
1636
- notifiedComponents.add(componentId);
1637
- }
1638
- }
1639
- });
1640
- }
1641
- });
1642
- }
1643
- } else if (
1644
- updateObj.updateType === 'insert' ||
1645
- updateObj.updateType === 'cut'
1646
- ) {
1647
- // For array structural changes, notify components listening to the parent array.
1648
- const parentArrayPath =
1649
- updateObj.updateType === 'insert' ? path : path.slice(0, -1);
1650
-
1651
- const parentMeta = store.getShadowMetadata(thisKey, parentArrayPath);
1652
-
1653
- // Handle signal updates for array length, etc.
1654
- if (parentMeta?.signals && parentMeta.signals.length > 0) {
1655
- const parentFullPath = [thisKey, ...parentArrayPath].join('.');
1656
- const parentValue = store.getShadowValue(parentFullPath);
1657
-
1658
- parentMeta.signals.forEach(({ parentId, position, effect }) => {
1659
- const parent = document.querySelector(
1660
- `[data-parent-id="${parentId}"]`
1661
- );
1662
- if (parent) {
1663
- const childNodes = Array.from(parent.childNodes);
1664
- if (childNodes[position]) {
1665
- let displayValue = parentValue;
1666
- if (effect) {
1667
- try {
1668
- displayValue = new Function(
1669
- 'state',
1670
- `return (${effect})(state)`
1671
- )(parentValue);
1672
- } catch (err) {
1673
- console.error('Error evaluating effect function:', err);
1674
- displayValue = parentValue;
1675
- }
1676
- }
1677
-
1678
- if (
1679
- displayValue !== null &&
1680
- displayValue !== undefined &&
1681
- typeof displayValue === 'object'
1682
- ) {
1683
- displayValue = JSON.stringify(displayValue);
1684
- }
1685
-
1686
- childNodes[position].textContent = String(displayValue ?? '');
1687
- }
1688
- }
1689
- });
1690
- }
1691
-
1692
- // Notify components subscribed to the array itself.
1693
- if (parentMeta?.pathComponents) {
1694
- parentMeta.pathComponents.forEach((componentId) => {
1695
- if (!notifiedComponents.has(componentId)) {
1696
- const component = rootMeta.components?.get(componentId);
1697
- if (component) {
1698
- component.forceUpdate();
1699
- notifiedComponents.add(componentId);
1700
- }
1701
- }
1702
- });
1703
- }
1704
- }
1705
-
1706
- rootMeta.components.forEach((component, componentId) => {
1707
- if (notifiedComponents.has(componentId)) {
1708
- return;
1709
- }
1710
-
1711
- const reactiveTypes = Array.isArray(component.reactiveType)
1712
- ? component.reactiveType
1713
- : [component.reactiveType || 'component'];
1714
-
1715
- if (reactiveTypes.includes('all')) {
1716
- component.forceUpdate();
1717
- notifiedComponents.add(componentId);
1718
- return;
1719
- }
1720
-
1721
- if (reactiveTypes.includes('deps')) {
1722
- if (component.depsFunction) {
1723
- const currentState = store.getShadowValue(thisKey);
1724
- const newDeps = component.depsFunction(currentState);
1725
- let shouldUpdate = false;
1726
-
1727
- if (newDeps === true) {
1728
- shouldUpdate = true;
1729
- } else if (Array.isArray(newDeps)) {
1730
- if (!isDeepEqual(component.prevDeps, newDeps)) {
1731
- component.prevDeps = newDeps;
1732
- shouldUpdate = true;
1733
- }
1734
- }
1735
-
1736
- if (shouldUpdate) {
1737
- component.forceUpdate();
1738
- notifiedComponents.add(componentId);
1739
- }
1740
- }
1741
- }
1742
- });
1743
- notifiedComponents.clear();
1744
- console.timeEnd('notify');
1745
- console.time('end stuff');
1746
- addStateLog(thisKey, newUpdate);
1747
-
1748
- saveToLocalStorage(
1749
- payload,
1750
- thisKey,
1751
- latestInitialOptionsRef.current,
1752
- sessionId
1753
- );
1754
-
1755
- if (latestInitialOptionsRef.current?.middleware) {
1756
- latestInitialOptionsRef.current!.middleware({
1757
- update: newUpdate,
1758
- });
1759
- }
1760
- console.timeEnd('end stuff');
1761
- };
1656
+ const effectiveSetState = createEffectiveSetState(
1657
+ thisKey,
1658
+ syncApiRef,
1659
+ sessionId,
1660
+ latestInitialOptionsRef
1661
+ );
1762
1662
 
1763
1663
  if (!getGlobalStore.getState().initialStateGlobal[thisKey]) {
1764
1664
  updateInitialStateGlobal(thisKey, stateObject);
1765
1665
  }
1766
1666
 
1767
1667
  const updaterFinal = useMemo(() => {
1768
- console.time('createProxyHandler');
1769
1668
  const handler = createProxyHandler<TStateObject>(
1770
1669
  thisKey,
1771
1670
  effectiveSetState,
1772
1671
  componentIdRef.current,
1773
1672
  sessionId
1774
1673
  );
1775
- console.timeEnd('createProxyHandler'); // <--- AND THIS
1674
+
1776
1675
  return handler;
1777
1676
  }, [thisKey, sessionId]);
1778
1677
 
@@ -1789,30 +1688,15 @@ export function useCogsStateFn<TStateObject extends unknown>(
1789
1688
  return updaterFinal;
1790
1689
  }
1791
1690
 
1792
- export type MetaData = {
1793
- /**
1794
- * An array of the full, unique string IDs (e.g., `"stateKey.arrayName.id:123"`)
1795
- * of the items that belong to the current derived "view" of an array.
1796
- * This is the primary mechanism for tracking the state of filtered or sorted lists.
1797
- *
1798
- * - `stateFilter` populates this with only the IDs of items that passed the filter.
1799
- * - `stateSort` reorders this list to match the new sort order.
1800
- * - All subsequent chained operations (like `.get()`, `.index()`, or `.cut()`)
1801
- * MUST consult this list first to know which items they apply to and in what order.
1802
- */
1803
- validIds?: string[];
1804
-
1805
- /**
1806
- * An array of the actual filter functions that have been applied in a chain.
1807
- * This is primarily used by reactive renderers like `$stateMap` to make predictions.
1808
- *
1809
- * For example, when a new item is inserted into the original source array, a
1810
- * `$stateMap` renderer on a filtered view can use these functions to test if the
1811
- * newly inserted item should be dynamically rendered in its view.
1812
- */
1691
+ type MetaData = {
1692
+ // Map array paths to their filtered/sorted ID order
1693
+ arrayViews?: {
1694
+ [arrayPath: string]: string[]; // e.g. { "todos": ["id:xxx", "id:yyy"], "todos.id:xxx.subtasks": ["id:aaa"] }
1695
+ };
1813
1696
  transforms?: Array<{
1814
1697
  type: 'filter' | 'sort';
1815
1698
  fn: Function;
1699
+ path: string[]; // Which array this transform applies to
1816
1700
  }>;
1817
1701
  };
1818
1702
 
@@ -1827,35 +1711,40 @@ function hashTransforms(transforms: any[]) {
1827
1711
  )
1828
1712
  .join('');
1829
1713
  }
1714
+
1830
1715
  const applyTransforms = (
1831
1716
  stateKey: string,
1832
1717
  path: string[],
1833
1718
  transforms?: Array<{ type: 'filter' | 'sort'; fn: Function }>
1834
1719
  ): string[] => {
1835
- let arrayKeys =
1836
- getGlobalStore.getState().getShadowMetadata(stateKey, path)?.arrayKeys ||
1837
- [];
1838
-
1720
+ let ids = getShadowMetadata(stateKey, path)?.arrayKeys || [];
1721
+ console.log('ids', ids);
1839
1722
  if (!transforms || transforms.length === 0) {
1840
- return arrayKeys;
1723
+ return ids;
1841
1724
  }
1842
1725
 
1843
- let itemsWithKeys = arrayKeys.map((key) => ({
1844
- key,
1845
- value: getGlobalStore.getState().getShadowValue(key),
1846
- }));
1847
-
1726
+ // Apply each transform using just IDs
1848
1727
  for (const transform of transforms) {
1849
1728
  if (transform.type === 'filter') {
1850
- itemsWithKeys = itemsWithKeys.filter(({ value }, index) =>
1851
- transform.fn(value, index)
1852
- );
1729
+ const filtered: any[] = [];
1730
+ ids.forEach((id, index) => {
1731
+ const value = getShadowValue(stateKey, [...path, id]);
1732
+
1733
+ if (transform.fn(value, index)) {
1734
+ filtered.push(id);
1735
+ }
1736
+ });
1737
+ ids = filtered;
1853
1738
  } else if (transform.type === 'sort') {
1854
- itemsWithKeys.sort((a, b) => transform.fn(a.value, b.value));
1739
+ ids.sort((a, b) => {
1740
+ const aValue = getShadowValue(stateKey, [...path, a]);
1741
+ const bValue = getShadowValue(stateKey, [...path, b]);
1742
+ return transform.fn(aValue, bValue);
1743
+ });
1855
1744
  }
1856
1745
  }
1857
1746
 
1858
- return itemsWithKeys.map(({ key }) => key);
1747
+ return ids;
1859
1748
  };
1860
1749
  const registerComponentDependency = (
1861
1750
  stateKey: string,
@@ -1863,7 +1752,6 @@ const registerComponentDependency = (
1863
1752
  dependencyPath: string[]
1864
1753
  ) => {
1865
1754
  const fullComponentId = `${stateKey}////${componentId}`;
1866
- const { addPathComponent, getShadowMetadata } = getGlobalStore.getState();
1867
1755
 
1868
1756
  const rootMeta = getShadowMetadata(stateKey, []);
1869
1757
  const component = rootMeta?.components?.get(fullComponentId);
@@ -1927,6 +1815,28 @@ const notifySelectionComponents = (
1927
1815
  }
1928
1816
  }
1929
1817
  };
1818
+ function getScopedData(stateKey: string, path: string[], meta?: MetaData) {
1819
+ const shadowMeta = getShadowMetadata(stateKey, path);
1820
+ const arrayPathKey = path.join('.');
1821
+ const arrayKeys = meta?.arrayViews?.[arrayPathKey];
1822
+
1823
+ // FIX: If the derived view is empty, return an empty array directly.
1824
+ if (Array.isArray(arrayKeys) && arrayKeys.length === 0) {
1825
+ return {
1826
+ shadowMeta,
1827
+ value: [],
1828
+ arrayKeys: shadowMeta?.arrayKeys,
1829
+ };
1830
+ }
1831
+
1832
+ const value = getShadowValue(stateKey, path, arrayKeys);
1833
+
1834
+ return {
1835
+ shadowMeta,
1836
+ value,
1837
+ arrayKeys: shadowMeta?.arrayKeys,
1838
+ };
1839
+ }
1930
1840
 
1931
1841
  function createProxyHandler<T>(
1932
1842
  stateKey: string,
@@ -1936,9 +1846,58 @@ function createProxyHandler<T>(
1936
1846
  ): StateObject<T> {
1937
1847
  const proxyCache = new Map<string, any>();
1938
1848
  let stateVersion = 0;
1939
- console.time('rebuildStateShape Outer');
1940
1849
 
1941
- let recursionTimerName: string | null = null;
1850
+ const methodNames = new Set([
1851
+ 'sync',
1852
+ 'getStatus',
1853
+ 'removeStorage',
1854
+ 'showValidationErrors',
1855
+ 'getSelected',
1856
+ 'getSelectedIndex',
1857
+ 'clearSelected',
1858
+ 'useVirtualView',
1859
+ 'stateMap',
1860
+ '$stateMap',
1861
+ 'stateFind',
1862
+ 'stateFilter',
1863
+ 'stateSort',
1864
+ 'stream',
1865
+ 'stateList',
1866
+ 'stateFlattenOn',
1867
+ 'index',
1868
+ 'last',
1869
+ 'insert',
1870
+ 'uniqueInsert',
1871
+ 'cut',
1872
+ 'cutSelected',
1873
+ 'cutByValue',
1874
+ 'toggleByValue',
1875
+ 'findWith',
1876
+ 'cutThis',
1877
+ 'get',
1878
+ 'getState',
1879
+ '$derive',
1880
+ '$get',
1881
+ 'lastSynced',
1882
+ 'getLocalStorage',
1883
+ 'isSelected',
1884
+ 'setSelected',
1885
+ 'toggleSelected',
1886
+ '_componentId',
1887
+ 'addZodValidation',
1888
+ 'clearZodValidation',
1889
+ 'applyJsonPatch',
1890
+ 'getComponents',
1891
+ 'getAllFormRefs',
1892
+ 'getFormRef',
1893
+ 'validationWrapper',
1894
+ '_stateKey',
1895
+ '_path',
1896
+ 'update',
1897
+ 'toggle',
1898
+ 'formElement',
1899
+ // Add ANY other method names here
1900
+ ]);
1942
1901
 
1943
1902
  function rebuildStateShape({
1944
1903
  path = [],
@@ -1949,75 +1908,35 @@ function createProxyHandler<T>(
1949
1908
  componentId: string;
1950
1909
  meta?: MetaData;
1951
1910
  }): any {
1952
- console.time('rebuildStateShape Inner');
1953
1911
  const derivationSignature = meta
1954
- ? JSON.stringify(meta.validIds || meta.transforms)
1912
+ ? JSON.stringify(meta.arrayViews || meta.transforms)
1955
1913
  : '';
1956
1914
  const cacheKey = path.join('.') + ':' + derivationSignature;
1957
- console.log('PROXY CACHE KEY ', cacheKey);
1958
1915
  if (proxyCache.has(cacheKey)) {
1959
- console.log('PROXY CACHE HIT');
1960
1916
  return proxyCache.get(cacheKey);
1961
1917
  }
1962
1918
  const stateKeyPathKey = [stateKey, ...path].join('.');
1963
1919
 
1964
- type CallableStateObject<T> = {
1965
- (): T;
1966
- } & {
1967
- [key: string]: any;
1968
- };
1969
-
1970
- const baseFunction = function () {
1971
- return getGlobalStore().getShadowValue(stateKey, path);
1972
- } as unknown as CallableStateObject<T>;
1973
-
1974
1920
  // We attach baseObj properties *inside* the get trap now to avoid recursion
1975
1921
  // This is a placeholder for the proxy.
1976
1922
 
1977
1923
  const handler = {
1978
- apply(target: any, thisArg: any, args: any[]) {
1979
- //return getGlobalStore().getShadowValue(stateKey, path);
1980
- },
1981
-
1982
1924
  get(target: any, prop: string) {
1983
- if (path.length === 0) {
1984
- // Create a unique name for this specific timer instance
1985
- recursionTimerName = `Recursion-${Math.random()}`;
1986
- console.time(recursionTimerName);
1925
+ if (path.length === 0 && prop in rootLevelMethods) {
1926
+ return rootLevelMethods[prop as keyof typeof rootLevelMethods];
1927
+ }
1928
+ if (!methodNames.has(prop)) {
1929
+ const nextPath = [...path, prop];
1930
+ return rebuildStateShape({
1931
+ path: nextPath,
1932
+ componentId: componentId!,
1933
+ meta,
1934
+ });
1987
1935
  }
1988
1936
  if (prop === '_rebuildStateShape') {
1989
1937
  return rebuildStateShape;
1990
1938
  }
1991
- const baseObjProps = Object.getOwnPropertyNames(baseObj);
1992
- if (baseObjProps.includes(prop) && path.length === 0) {
1993
- return (baseObj as any)[prop];
1994
- }
1995
- // ^--------- END OF FIX ---------^
1996
-
1997
- if (prop === 'getDifferences') {
1998
- return () => {
1999
- const shadowMeta = getGlobalStore
2000
- .getState()
2001
- .getShadowMetadata(stateKey, []);
2002
- const currentState = getGlobalStore
2003
- .getState()
2004
- .getShadowValue(stateKey);
2005
1939
 
2006
- // Use the appropriate base state for comparison
2007
- let baseState;
2008
- if (
2009
- shadowMeta?.stateSource === 'server' &&
2010
- shadowMeta.baseServerState
2011
- ) {
2012
- baseState = shadowMeta.baseServerState;
2013
- } else {
2014
- baseState =
2015
- getGlobalStore.getState().initialStateGlobal[stateKey];
2016
- }
2017
-
2018
- return getDifferences(currentState, baseState);
2019
- };
2020
- }
2021
1940
  if (prop === 'sync' && path.length === 0) {
2022
1941
  return async function () {
2023
1942
  const options = getGlobalStore
@@ -2058,7 +1977,7 @@ function createProxyHandler<T>(
2058
1977
  const shadowMeta = getGlobalStore
2059
1978
  .getState()
2060
1979
  .getShadowMetadata(stateKey, []);
2061
- getGlobalStore.getState().setShadowMetadata(stateKey, [], {
1980
+ setShadowMetadata(stateKey, [], {
2062
1981
  ...shadowMeta,
2063
1982
  isDirty: false,
2064
1983
  lastServerSync: Date.now(),
@@ -2082,56 +2001,46 @@ function createProxyHandler<T>(
2082
2001
  // Fixed getStatus function in createProxyHandler
2083
2002
  if (prop === '_status' || prop === 'getStatus') {
2084
2003
  const getStatusFunc = () => {
2085
- const shadowMeta = getGlobalStore
2086
- .getState()
2087
- .getShadowMetadata(stateKey, path);
2088
- const value = getGlobalStore
2089
- .getState()
2090
- .getShadowValue(stateKeyPathKey);
2004
+ // Use the optimized helper to get all data in one efficient call
2005
+ const { shadowMeta, value } = getScopedData(stateKey, path, meta);
2091
2006
 
2092
- // Priority 1: Explicitly dirty items
2007
+ // Priority 1: Explicitly dirty items. This is the most important status.
2093
2008
  if (shadowMeta?.isDirty === true) {
2094
2009
  return 'dirty';
2095
2010
  }
2096
2011
 
2097
- // Priority 2: Explicitly synced items (isDirty: false)
2098
- if (shadowMeta?.isDirty === false) {
2099
- return 'synced';
2100
- }
2101
-
2102
- // Priority 3: Items from server source (should be synced even without explicit isDirty flag)
2103
- if (shadowMeta?.stateSource === 'server') {
2012
+ // Priority 2: Synced items. This condition is now cleaner.
2013
+ // An item is considered synced if it came from the server OR was explicitly
2014
+ // marked as not dirty (isDirty: false), covering all sync-related cases.
2015
+ if (
2016
+ shadowMeta?.stateSource === 'server' ||
2017
+ shadowMeta?.isDirty === false
2018
+ ) {
2104
2019
  return 'synced';
2105
2020
  }
2106
2021
 
2107
- // Priority 4: Items restored from localStorage
2022
+ // Priority 3: Items restored from localStorage.
2108
2023
  if (shadowMeta?.stateSource === 'localStorage') {
2109
2024
  return 'restored';
2110
2025
  }
2111
2026
 
2112
- // Priority 5: Items from default/initial state
2027
+ // Priority 4: Items from default/initial state.
2113
2028
  if (shadowMeta?.stateSource === 'default') {
2114
2029
  return 'fresh';
2115
2030
  }
2116
2031
 
2117
- // Priority 6: Check if this is part of initial server load
2118
- // Look up the tree to see if parent has server source
2119
- const rootMeta = getGlobalStore
2120
- .getState()
2121
- .getShadowMetadata(stateKey, []);
2122
- if (rootMeta?.stateSource === 'server' && !shadowMeta?.isDirty) {
2123
- return 'synced';
2124
- }
2032
+ // REMOVED the redundant "root" check. The item's own `stateSource` is sufficient.
2125
2033
 
2126
- // Priority 7: If no metadata exists but value exists, it's probably fresh
2034
+ // Priority 5: A value exists but has no metadata. This is a fallback.
2127
2035
  if (value !== undefined && !shadowMeta) {
2128
2036
  return 'fresh';
2129
2037
  }
2130
2038
 
2131
- // Fallback
2039
+ // Fallback if no other condition is met.
2132
2040
  return 'unknown';
2133
2041
  };
2134
2042
 
2043
+ // This part remains the same
2135
2044
  return prop === '_status' ? getStatusFunc() : getStatusFunc;
2136
2045
  }
2137
2046
  if (prop === 'removeStorage') {
@@ -2148,14 +2057,15 @@ function createProxyHandler<T>(
2148
2057
  }
2149
2058
  if (prop === 'showValidationErrors') {
2150
2059
  return () => {
2151
- const meta = getGlobalStore
2152
- .getState()
2153
- .getShadowMetadata(stateKey, path);
2060
+ const { shadowMeta } = getScopedData(stateKey, path, meta);
2154
2061
  if (
2155
- meta?.validation?.status === 'VALIDATION_FAILED' &&
2156
- meta.validation.message
2062
+ shadowMeta?.validation?.status === 'INVALID' &&
2063
+ shadowMeta.validation.errors.length > 0
2157
2064
  ) {
2158
- return [meta.validation.message];
2065
+ // Return only error-severity messages (not warnings)
2066
+ return shadowMeta.validation.errors
2067
+ .filter((err) => err.severity === 'error')
2068
+ .map((err) => err.message);
2159
2069
  }
2160
2070
  return [];
2161
2071
  };
@@ -2163,55 +2073,77 @@ function createProxyHandler<T>(
2163
2073
 
2164
2074
  if (prop === 'getSelected') {
2165
2075
  return () => {
2166
- const fullKey = stateKey + '.' + path.join('.');
2076
+ const arrayKey = [stateKey, ...path].join('.');
2167
2077
  registerComponentDependency(stateKey, componentId, [
2168
2078
  ...path,
2169
2079
  'getSelected',
2170
2080
  ]);
2171
2081
 
2172
- const selectedIndicesMap =
2173
- getGlobalStore.getState().selectedIndicesMap;
2174
- if (!selectedIndicesMap || !selectedIndicesMap.has(fullKey)) {
2082
+ const selectedItemKey = getGlobalStore
2083
+ .getState()
2084
+ .selectedIndicesMap.get(arrayKey);
2085
+ if (!selectedItemKey) {
2175
2086
  return undefined;
2176
2087
  }
2177
2088
 
2178
- const selectedItemKey = selectedIndicesMap.get(fullKey)!;
2179
- if (meta?.validIds) {
2180
- if (!meta.validIds.includes(selectedItemKey)) {
2181
- return undefined;
2182
- }
2183
- }
2089
+ const viewKey = path.join('.');
2090
+ const currentViewIds = meta?.arrayViews?.[viewKey];
2091
+ const selectedItemId = selectedItemKey.split('.').pop();
2184
2092
 
2185
- const value = getGlobalStore
2186
- .getState()
2187
- .getShadowValue(selectedItemKey);
2093
+ // FIX: Only return the selected item if it exists in the current filtered/sorted view.
2094
+ if (currentViewIds && !currentViewIds.includes(selectedItemId!)) {
2095
+ return undefined;
2096
+ }
2188
2097
 
2189
- if (!value) {
2098
+ const value = getShadowValue(
2099
+ stateKey,
2100
+ selectedItemKey.split('.').slice(1)
2101
+ );
2102
+ if (value === undefined) {
2190
2103
  return undefined;
2191
2104
  }
2192
2105
 
2193
2106
  return rebuildStateShape({
2194
2107
  path: selectedItemKey.split('.').slice(1) as string[],
2195
2108
  componentId: componentId!,
2109
+ meta,
2196
2110
  });
2197
2111
  };
2198
2112
  }
2199
2113
  if (prop === 'getSelectedIndex') {
2200
2114
  return () => {
2201
- const selectedIndex = getGlobalStore
2115
+ // Key for the array in the global selection map (e.g., "myState.products")
2116
+ const arrayKey = stateKey + '.' + path.join('.');
2117
+ // Key for this specific view in the meta object (e.g., "products")
2118
+ const viewKey = path.join('.');
2119
+
2120
+ // Get the full path of the selected item (e.g., "myState.products.id:abc")
2121
+ const selectedItemKey = getGlobalStore
2202
2122
  .getState()
2203
- .getSelectedIndex(
2204
- stateKey + '.' + path.join('.'),
2205
- meta?.validIds
2206
- );
2123
+ .selectedIndicesMap.get(arrayKey);
2207
2124
 
2208
- return selectedIndex;
2125
+ if (!selectedItemKey) {
2126
+ return -1; // Nothing is selected for this array.
2127
+ }
2128
+
2129
+ // Get the list of item IDs for the current filtered/sorted view.
2130
+ const { keys: viewIds } = getArrayData(stateKey, path, meta);
2131
+
2132
+ if (!viewIds) {
2133
+ return -1; // Should not happen if it's an array, but a safe guard.
2134
+ }
2135
+
2136
+ // FIX: Extract just the ID from the full selected item path.
2137
+ const selectedId = selectedItemKey.split('.').pop();
2138
+
2139
+ // Return the index of that ID within the current view's list of IDs.
2140
+ return (viewIds as string[]).indexOf(selectedId as string);
2209
2141
  };
2210
2142
  }
2211
2143
  if (prop === 'clearSelected') {
2212
2144
  notifySelectionComponents(stateKey, path);
2213
2145
  return () => {
2214
- getGlobalStore.getState().clearSelectedIndex({
2146
+ clearSelectedIndex({
2215
2147
  arrayKey: stateKey + '.' + path.join('.'),
2216
2148
  });
2217
2149
  };
@@ -2265,9 +2197,7 @@ function createProxyHandler<T>(
2265
2197
  });
2266
2198
  }, [rerender, stickToBottom]);
2267
2199
 
2268
- const arrayKeys =
2269
- getGlobalStore.getState().getShadowMetadata(stateKey, path)
2270
- ?.arrayKeys || [];
2200
+ const { arrayKeys = [] } = getScopedData(stateKey, path, meta);
2271
2201
 
2272
2202
  // Calculate total height and offsets
2273
2203
  const { totalHeight, itemOffsets } = useMemo(() => {
@@ -2545,9 +2475,7 @@ function createProxyHandler<T>(
2545
2475
  // Create virtual state
2546
2476
  const virtualState = useMemo(() => {
2547
2477
  const store = getGlobalStore.getState();
2548
- const sourceArray = store.getShadowValue(
2549
- [stateKey, ...path].join('.')
2550
- ) as any[];
2478
+ const sourceArray = store.getShadowValue(stateKey, path) as any[];
2551
2479
  const currentKeys =
2552
2480
  store.getShadowMetadata(stateKey, path)?.arrayKeys || [];
2553
2481
 
@@ -2559,11 +2487,11 @@ function createProxyHandler<T>(
2559
2487
  range.startIndex,
2560
2488
  range.endIndex + 1
2561
2489
  );
2562
-
2490
+ const arrayPath = path.length > 0 ? path.join('.') : 'root';
2563
2491
  return rebuildStateShape({
2564
2492
  path,
2565
2493
  componentId: componentId!,
2566
- meta: { ...meta, validIds: slicedIds },
2494
+ meta: { ...meta, arrayViews: { [arrayPath]: slicedIds } },
2567
2495
  });
2568
2496
  }, [range.startIndex, range.endIndex, arrayKeys.length]);
2569
2497
 
@@ -2610,150 +2538,84 @@ function createProxyHandler<T>(
2610
2538
  }
2611
2539
  if (prop === 'stateMap') {
2612
2540
  return (
2613
- callbackfn: (
2614
- setter: any,
2615
- index: number,
2616
-
2617
- arraySetter: any
2618
- ) => void
2541
+ callbackfn: (setter: any, index: number, arraySetter: any) => void
2619
2542
  ) => {
2620
- const [arrayKeys, setArrayKeys] = useState<any>(
2621
- meta?.validIds ??
2622
- getGlobalStore.getState().getShadowMetadata(stateKey, path)
2623
- ?.arrayKeys
2543
+ // FIX: Use getArrayData to reliably get both the value and the keys of the current view.
2544
+ const { value: shadowValue, keys: arrayKeys } = getArrayData(
2545
+ stateKey,
2546
+ path,
2547
+ meta
2624
2548
  );
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');
2549
+
2550
+ if (!arrayKeys || !Array.isArray(shadowValue)) {
2551
+ return []; // It's valid to map over an empty array.
2640
2552
  }
2553
+
2641
2554
  const arraySetter = rebuildStateShape({
2642
2555
  path,
2643
2556
  componentId: componentId!,
2644
2557
  meta,
2645
2558
  });
2646
2559
 
2647
- return shadowValue.map((item, index) => {
2648
- const itemPath = arrayKeys[index]?.split('.').slice(1);
2649
- const itemSetter = rebuildStateShape({
2650
- path: itemPath as any,
2651
- componentId: componentId!,
2652
- meta,
2653
- });
2654
-
2655
- return callbackfn(
2656
- itemSetter,
2657
- index,
2658
-
2659
- arraySetter
2660
- );
2661
- });
2662
- };
2663
- }
2664
-
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))
2560
+ return shadowValue.map((_item, index) => {
2561
+ const itemKey = arrayKeys[index];
2562
+ if (!itemKey) return undefined;
2677
2563
 
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
- }
2691
-
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
2696
-
2697
- const itemValue = getGlobalStore
2698
- .getState()
2699
- .getShadowValue(itemKey);
2700
-
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);
2564
+ // FIX: Construct the correct path to the item in the original store.
2565
+ // The path is the array's path plus the specific item's unique key.
2566
+ const itemPath = [...path, itemKey];
2705
2567
 
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
- });
2712
- }
2713
- }
2568
+ const itemSetter = rebuildStateShape({
2569
+ path: itemPath, // This now correctly points to the item in the shadow store.
2570
+ componentId: componentId!,
2571
+ meta,
2572
+ });
2714
2573
 
2715
- // 5. If the loop finishes without finding anything, return undefined.
2716
- return undefined;
2574
+ return callbackfn(itemSetter, index, arraySetter);
2575
+ });
2717
2576
  };
2718
2577
  }
2578
+
2719
2579
  if (prop === 'stateFilter') {
2720
2580
  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;
2581
+ const arrayPathKey = path.length > 0 ? path.join('.') : 'root';
2729
2582
 
2730
- if (!arrayKeys) {
2731
- throw new Error('No array keys found for filtering.');
2583
+ // FIX: Get keys from `getArrayData` which correctly resolves them from meta or the full list.
2584
+ const { keys: currentViewIds, value: array } = getArrayData(
2585
+ stateKey,
2586
+ path,
2587
+ meta
2588
+ );
2589
+
2590
+ if (!Array.isArray(array)) {
2591
+ throw new Error('stateFilter can only be used on arrays');
2732
2592
  }
2733
2593
 
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;
2594
+ // Filter the array and collect the IDs of the items that pass
2595
+ const filteredIds: string[] = [];
2596
+ array.forEach((item, index) => {
2597
+ if (callbackfn(item, index)) {
2598
+ // currentViewIds[index] is the original ID before filtering
2599
+ const id = currentViewIds[index];
2600
+ if (id) {
2601
+ filteredIds.push(id);
2741
2602
  }
2742
- return false;
2743
2603
  }
2744
- );
2604
+ });
2745
2605
 
2606
+ // The rest is the same...
2746
2607
  return rebuildStateShape({
2747
2608
  path,
2748
2609
  componentId: componentId!,
2749
2610
  meta: {
2750
- validIds: newValidIds,
2611
+ ...meta,
2612
+ arrayViews: {
2613
+ ...(meta?.arrayViews || {}),
2614
+ [arrayPathKey]: filteredIds,
2615
+ },
2751
2616
  transforms: [
2752
2617
  ...(meta?.transforms || []),
2753
- {
2754
- type: 'filter',
2755
- fn: callbackfn,
2756
- },
2618
+ { type: 'filter', fn: callbackfn, path },
2757
2619
  ],
2758
2620
  },
2759
2621
  });
@@ -2761,34 +2623,39 @@ function createProxyHandler<T>(
2761
2623
  }
2762
2624
  if (prop === 'stateSort') {
2763
2625
  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) {
2626
+ const arrayPathKey = path.join('.');
2627
+
2628
+ // FIX: Use the more robust `getArrayData` which always correctly resolves the keys for a view.
2629
+ const { value: currentArray, keys: currentViewIds } = getArrayData(
2630
+ stateKey,
2631
+ path,
2632
+ meta
2633
+ );
2634
+
2635
+ if (!Array.isArray(currentArray) || !currentViewIds) {
2773
2636
  throw new Error('No array keys found for sorting');
2774
2637
  }
2775
- const itemsWithIds = currentState.map((item, index) => ({
2638
+
2639
+ // ... (rest of the function is the same and now works)
2640
+ const itemsWithIds = currentArray.map((item, index) => ({
2776
2641
  item,
2777
- key: arrayKeys[index],
2642
+ key: currentViewIds[index],
2778
2643
  }));
2779
-
2780
- itemsWithIds
2781
- .sort((a, b) => compareFn(a.item, b.item))
2782
- .filter(Boolean);
2644
+ itemsWithIds.sort((a, b) => compareFn(a.item, b.item));
2645
+ const sortedIds = itemsWithIds.map((i) => i.key as string);
2783
2646
 
2784
2647
  return rebuildStateShape({
2785
2648
  path,
2786
2649
  componentId: componentId!,
2787
2650
  meta: {
2788
- validIds: itemsWithIds.map((i) => i.key) as string[],
2651
+ ...meta,
2652
+ arrayViews: {
2653
+ ...(meta?.arrayViews || {}),
2654
+ [arrayPathKey]: sortedIds,
2655
+ },
2789
2656
  transforms: [
2790
2657
  ...(meta?.transforms || []),
2791
- { type: 'sort', fn: compareFn },
2658
+ { type: 'sort', fn: compareFn, path },
2792
2659
  ],
2793
2660
  },
2794
2661
  });
@@ -2862,12 +2729,11 @@ function createProxyHandler<T>(
2862
2729
  }
2863
2730
 
2864
2731
  const streamId = uuidv4();
2865
- const currentMeta =
2866
- getGlobalStore.getState().getShadowMetadata(stateKey, path) || {};
2732
+ const currentMeta = getShadowMetadata(stateKey, path) || {};
2867
2733
  const streams = currentMeta.streams || new Map();
2868
2734
  streams.set(streamId, { buffer, flushTimer });
2869
2735
 
2870
- getGlobalStore.getState().setShadowMetadata(stateKey, path, {
2736
+ setShadowMetadata(stateKey, path, {
2871
2737
  ...currentMeta,
2872
2738
  streams,
2873
2739
  });
@@ -2906,58 +2772,45 @@ function createProxyHandler<T>(
2906
2772
  arraySetter: any
2907
2773
  ) => ReactNode
2908
2774
  ) => {
2775
+ console.log('meta outside', JSON.stringify(meta));
2909
2776
  const StateListWrapper = () => {
2910
2777
  const componentIdsRef = useRef<Map<string, string>>(new Map());
2911
2778
 
2912
- const cacheKey =
2913
- meta?.transforms && meta.transforms.length > 0
2914
- ? `${componentId}-${hashTransforms(meta.transforms)}`
2915
- : `${componentId}-base`;
2916
-
2917
2779
  const [updateTrigger, forceUpdate] = useState({});
2918
2780
 
2919
- const { validIds, arrayValues } = useMemo(() => {
2920
- const cached = getGlobalStore
2921
- .getState()
2922
- .getShadowMetadata(stateKey, path)
2923
- ?.transformCaches?.get(cacheKey);
2924
-
2925
- let freshValidIds: string[];
2781
+ console.log('updateTrigger updateTrigger updateTrigger');
2926
2782
 
2927
- if (cached && cached.validIds) {
2928
- freshValidIds = cached.validIds;
2929
- } else {
2930
- freshValidIds = applyTransforms(
2931
- stateKey,
2932
- path,
2933
- meta?.transforms
2934
- );
2935
-
2936
- getGlobalStore
2937
- .getState()
2938
- .setTransformCache(stateKey, path, cacheKey, {
2939
- validIds: freshValidIds,
2940
- computedAt: Date.now(),
2941
- transforms: meta?.transforms || [],
2942
- });
2943
- }
2783
+ const validIds = applyTransforms(
2784
+ stateKey,
2785
+ path,
2786
+ meta?.transforms
2787
+ );
2788
+ //the above get the new coorect valid ids i need ot udpate the meta object with this info
2789
+ const arrayPathKey = path.length > 0 ? path.join('.') : 'root';
2790
+ const updatedMeta = {
2791
+ ...meta,
2792
+ arrayViews: {
2793
+ ...(meta?.arrayViews || {}),
2794
+ [arrayPathKey]: validIds, // Update the arrayViews with the new valid IDs
2795
+ },
2796
+ };
2944
2797
 
2945
- const freshValues = getGlobalStore
2946
- .getState()
2947
- .getShadowValue(stateKeyPathKey, freshValidIds);
2798
+ // Now use the updated meta when getting array data
2799
+ const { value: arrayValues } = getArrayData(
2800
+ stateKey,
2801
+ path,
2802
+ updatedMeta
2803
+ );
2948
2804
 
2949
- return {
2950
- validIds: freshValidIds,
2951
- arrayValues: freshValues || [],
2952
- };
2953
- }, [cacheKey, updateTrigger]);
2805
+ console.log('validIds', validIds);
2806
+ console.log('arrayValues', arrayValues);
2954
2807
 
2955
2808
  useEffect(() => {
2956
2809
  const unsubscribe = getGlobalStore
2957
2810
  .getState()
2958
2811
  .subscribeToPath(stateKeyPathKey, (e) => {
2959
2812
  // A data change has occurred for the source array.
2960
-
2813
+ console.log('changed array statelist ', e);
2961
2814
  if (e.type === 'GET_SELECTED') {
2962
2815
  return;
2963
2816
  }
@@ -2997,14 +2850,13 @@ function createProxyHandler<T>(
2997
2850
  return null;
2998
2851
  }
2999
2852
 
2853
+ // Continue using updatedMeta for the rest of your logic instead of meta
3000
2854
  const arraySetter = rebuildStateShape({
3001
2855
  path,
3002
2856
  componentId: componentId!,
3003
- meta: {
3004
- ...meta,
3005
- validIds: validIds,
3006
- },
2857
+ meta: updatedMeta, // Use updated meta here
3007
2858
  });
2859
+ console.log('arrayValues', arrayValues);
3008
2860
 
3009
2861
  return (
3010
2862
  <>
@@ -3021,7 +2873,7 @@ function createProxyHandler<T>(
3021
2873
  componentIdsRef.current.set(itemKey, itemComponentId);
3022
2874
  }
3023
2875
 
3024
- const itemPath = itemKey.split('.').slice(1);
2876
+ const itemPath = [...path, itemKey];
3025
2877
 
3026
2878
  return createElement(MemoizedCogsItemWrapper, {
3027
2879
  key: itemKey,
@@ -3043,16 +2895,18 @@ function createProxyHandler<T>(
3043
2895
  }
3044
2896
  if (prop === 'stateFlattenOn') {
3045
2897
  return (fieldName: string) => {
2898
+ // FIX: Get the definitive list of IDs for the current view from meta.arrayViews.
2899
+ const arrayPathKey = path.join('.');
2900
+ const viewIds = meta?.arrayViews?.[arrayPathKey];
2901
+
3046
2902
  const currentState = getGlobalStore
3047
2903
  .getState()
3048
- .getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
3049
- if (!Array.isArray(currentState)) return []; // Guard clause
3050
- const arrayToMap = currentState as any[];
2904
+ .getShadowValue(stateKey, path, viewIds);
2905
+
2906
+ if (!Array.isArray(currentState)) return [];
3051
2907
 
3052
2908
  stateVersion++;
3053
- const flattenedResults = arrayToMap.flatMap(
3054
- (val: any) => val[fieldName] ?? []
3055
- );
2909
+
3056
2910
  return rebuildStateShape({
3057
2911
  path: [...path, '[*]', fieldName],
3058
2912
  componentId: componentId!,
@@ -3062,36 +2916,55 @@ function createProxyHandler<T>(
3062
2916
  }
3063
2917
  if (prop === 'index') {
3064
2918
  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];
2919
+ const arrayPathKey = path.join('.');
2920
+ const viewIds = meta?.arrayViews?.[arrayPathKey];
2921
+
2922
+ if (viewIds) {
2923
+ const itemId = viewIds[index];
2924
+ if (!itemId) return undefined;
2925
+ return rebuildStateShape({
2926
+ path: [...path, itemId],
2927
+ componentId: componentId!,
2928
+ meta,
2929
+ });
2930
+ }
2931
+
2932
+ // ✅ FIX: Get the metadata and use the `arrayKeys` property.
2933
+ const shadowMeta = getShadowMetadata(stateKey, path);
2934
+ if (!shadowMeta?.arrayKeys) return undefined;
2935
+
2936
+ const itemId = shadowMeta.arrayKeys[index];
3074
2937
  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[],
2938
+
2939
+ return rebuildStateShape({
2940
+ path: [...path, itemId],
3080
2941
  componentId: componentId!,
3081
2942
  meta,
3082
2943
  });
3083
- return state;
3084
2944
  };
3085
2945
  }
3086
2946
  if (prop === 'last') {
3087
2947
  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()];
2948
+ // FIX: Use getArrayData to get the keys for the current view (filtered or not).
2949
+ const { keys: currentViewIds } = getArrayData(stateKey, path, meta);
2950
+
2951
+ // If the array is empty, there is no last item.
2952
+ if (!currentViewIds || currentViewIds.length === 0) {
2953
+ return undefined;
2954
+ }
2955
+
2956
+ // Get the unique ID of the last item in the current view.
2957
+ const lastItemKey = currentViewIds[currentViewIds.length - 1];
2958
+
2959
+ // If for some reason the key is invalid, return undefined.
2960
+ if (!lastItemKey) {
2961
+ return undefined;
2962
+ }
2963
+
2964
+ // ✅ FIX: The new path uses the item's unique key, not its numerical index.
2965
+ const newPath = [...path, lastItemKey];
2966
+
2967
+ // Return a new proxy scoped to that specific item.
3095
2968
  return rebuildStateShape({
3096
2969
  path: newPath,
3097
2970
  componentId: componentId!,
@@ -3105,11 +2978,6 @@ function createProxyHandler<T>(
3105
2978
  index?: number
3106
2979
  ) => {
3107
2980
  effectiveSetState(payload as any, path, { updateType: 'insert' });
3108
- return rebuildStateShape({
3109
- path,
3110
- componentId: componentId!,
3111
- meta,
3112
- });
3113
2981
  };
3114
2982
  }
3115
2983
  if (prop === 'uniqueInsert') {
@@ -3118,9 +2986,13 @@ function createProxyHandler<T>(
3118
2986
  fields?: (keyof InferArrayElement<T>)[],
3119
2987
  onMatch?: (existingItem: any) => any
3120
2988
  ) => {
3121
- const currentArray = getGlobalStore
3122
- .getState()
3123
- .getShadowValue(stateKey, path) as any[];
2989
+ const { value: currentArray } = getScopedData(
2990
+ stateKey,
2991
+ path,
2992
+ meta
2993
+ ) as {
2994
+ value: any[];
2995
+ };
3124
2996
  const newValue = isFunction<T>(payload)
3125
2997
  ? payload(currentArray as any)
3126
2998
  : (payload as any);
@@ -3150,167 +3022,133 @@ function createProxyHandler<T>(
3150
3022
  }
3151
3023
  };
3152
3024
  }
3153
-
3154
3025
  if (prop === 'cut') {
3155
3026
  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;
3027
+ const shadowMeta = getShadowMetadata(stateKey, path);
3028
+ if (!shadowMeta?.arrayKeys || shadowMeta.arrayKeys.length === 0)
3029
+ return;
3165
3030
 
3166
3031
  const indexToCut =
3167
- index == -1
3168
- ? validKeys.length - 1
3032
+ index === -1
3033
+ ? shadowMeta.arrayKeys.length - 1
3169
3034
  : index !== undefined
3170
3035
  ? index
3171
- : validKeys.length - 1;
3036
+ : shadowMeta.arrayKeys.length - 1;
3172
3037
 
3173
- const fullIdToCut = validKeys[indexToCut];
3174
- if (!fullIdToCut) return; // Index out of bounds
3038
+ const idToCut = shadowMeta.arrayKeys[indexToCut];
3039
+ if (!idToCut) return;
3175
3040
 
3176
- const pathForCut = fullIdToCut.split('.').slice(1);
3177
- effectiveSetState(currentState, pathForCut, {
3041
+ effectiveSetState(null, [...path, idToCut], {
3178
3042
  updateType: 'cut',
3179
3043
  });
3180
3044
  };
3181
3045
  }
3182
3046
  if (prop === 'cutSelected') {
3183
3047
  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;
3048
+ const arrayKey = [stateKey, ...path].join('.');
3189
3049
 
3190
- const indexKeyToCut = getGlobalStore
3050
+ const { keys: currentViewIds } = getArrayData(stateKey, path, meta);
3051
+ if (!currentViewIds || currentViewIds.length === 0) {
3052
+ return;
3053
+ }
3054
+ const selectedItemKey = getGlobalStore
3191
3055
  .getState()
3192
- .selectedIndicesMap.get(stateKeyPathKey);
3056
+ .selectedIndicesMap.get(arrayKey);
3193
3057
 
3194
- let indexToCut = validKeys.findIndex(
3195
- (key) => key === indexKeyToCut
3196
- );
3058
+ if (!selectedItemKey) {
3059
+ return;
3060
+ }
3061
+ const selectedId = selectedItemKey.split('.').pop() as string;
3062
+ if (!(currentViewIds as any[]).includes(selectedId!)) {
3063
+ return;
3064
+ }
3065
+ const pathForCut = selectedItemKey.split('.').slice(1);
3066
+ getGlobalStore.getState().clearSelectedIndex({ arrayKey });
3197
3067
 
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)!;
3068
+ const parentPath = pathForCut.slice(0, -1);
3207
3069
  notifySelectionComponents(stateKey, parentPath);
3208
- effectiveSetState(currentState, pathForCut!, {
3070
+
3071
+ effectiveSetState(null, pathForCut, {
3209
3072
  updateType: 'cut',
3210
3073
  });
3211
3074
  };
3212
3075
  }
3213
3076
  if (prop === 'cutByValue') {
3214
3077
  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;
3220
-
3221
- if (!relevantKeys) return;
3222
-
3223
- let keyToCut: string | null = null;
3078
+ const {
3079
+ isArray,
3080
+ value: array,
3081
+ keys,
3082
+ } = getArrayData(stateKey, path, meta);
3224
3083
 
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.
3231
- }
3232
- }
3084
+ if (!isArray) return;
3233
3085
 
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' });
3086
+ const found = findArrayItem(array, keys, (item) => item === value);
3087
+ if (found) {
3088
+ effectiveSetState(null, [...path, found.key], {
3089
+ updateType: 'cut',
3090
+ });
3238
3091
  }
3239
3092
  };
3240
3093
  }
3241
3094
 
3242
3095
  if (prop === 'toggleByValue') {
3243
3096
  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;
3097
+ const {
3098
+ isArray,
3099
+ value: array,
3100
+ keys,
3101
+ } = getArrayData(stateKey, path, meta);
3249
3102
 
3250
- if (!relevantKeys) return;
3103
+ if (!isArray) return;
3251
3104
 
3252
- let keyToCut: string | null = null;
3105
+ const found = findArrayItem(array, keys, (item) => item === value);
3253
3106
 
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!
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);
3268
- console.log('itemValue keyToCut', keyToCut);
3269
- effectiveSetState(value as any, itemPath, {
3107
+ if (found) {
3108
+ const pathForItem = [...path, found.key];
3109
+
3110
+ effectiveSetState(null, pathForItem, {
3270
3111
  updateType: 'cut',
3271
3112
  });
3272
3113
  } else {
3273
- // Item does not exist, so we INSERT it.
3274
3114
  effectiveSetState(value as any, path, { updateType: 'insert' });
3275
3115
  }
3276
3116
  };
3277
3117
  }
3278
3118
  if (prop === 'findWith') {
3279
- return (searchKey: keyof InferArrayElement<T>, searchValue: any) => {
3280
- const arrayKeys = getGlobalStore
3281
- .getState()
3282
- .getShadowMetadata(stateKey, path)?.arrayKeys;
3119
+ return (searchKey: string, searchValue: any) => {
3120
+ const { isArray, value, keys } = getArrayData(stateKey, path, meta);
3283
3121
 
3284
- if (!arrayKeys) {
3285
- throw new Error('No array keys found for sorting');
3122
+ if (!isArray) {
3123
+ throw new Error('findWith can only be used on arrays');
3286
3124
  }
3287
3125
 
3288
- let value = null;
3289
- let foundPath: string[] = [];
3126
+ const found = findArrayItem(
3127
+ value,
3128
+ keys,
3129
+ (item) => item?.[searchKey] === searchValue
3130
+ );
3290
3131
 
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;
3299
- }
3132
+ // FIX: If found, return a proxy to the item by appending its key to the current path.
3133
+ if (found) {
3134
+ return rebuildStateShape({
3135
+ path: [...path, found.key], // e.g., ['itemInstances', 'inst-1', 'properties', 'prop-b']
3136
+ componentId: componentId!,
3137
+ meta,
3138
+ });
3300
3139
  }
3301
3140
 
3141
+ // If not found, return an 'empty' proxy that will resolve to undefined on .get()
3142
+ // This prevents "cannot read property 'get' of undefined" errors.
3302
3143
  return rebuildStateShape({
3303
- path: foundPath,
3144
+ path: [...path, `not_found_${uuidv4()}`],
3304
3145
  componentId: componentId!,
3305
3146
  meta,
3306
3147
  });
3307
3148
  };
3308
3149
  }
3309
-
3310
3150
  if (prop === 'cutThis') {
3311
- let shadowValue = getGlobalStore
3312
- .getState()
3313
- .getShadowValue(path.join('.'));
3151
+ const { value: shadowValue } = getScopedData(stateKey, path, meta);
3314
3152
 
3315
3153
  return () => {
3316
3154
  effectiveSetState(shadowValue, path, { updateType: 'cut' });
@@ -3320,16 +3158,8 @@ function createProxyHandler<T>(
3320
3158
  if (prop === 'get') {
3321
3159
  return () => {
3322
3160
  registerComponentDependency(stateKey, componentId, path);
3323
- return getGlobalStore
3324
- .getState()
3325
- .getShadowValue(stateKeyPathKey, meta?.validIds);
3326
- };
3327
- }
3328
- if (prop === 'getState') {
3329
- return () => {
3330
- return getGlobalStore
3331
- .getState()
3332
- .getShadowValue(stateKeyPathKey, meta?.validIds);
3161
+ const { value } = getScopedData(stateKey, path, meta);
3162
+ return value;
3333
3163
  };
3334
3164
  }
3335
3165
 
@@ -3350,32 +3180,27 @@ function createProxyHandler<T>(
3350
3180
  }
3351
3181
  if (prop === 'lastSynced') {
3352
3182
  const syncKey = `${stateKey}:${path.join('.')}`;
3353
- return getGlobalStore.getState().getSyncInfo(syncKey);
3183
+ return getSyncInfo(syncKey);
3354
3184
  }
3355
3185
  if (prop == 'getLocalStorage') {
3356
3186
  return (key: string) =>
3357
3187
  loadFromLocalStorage(sessionId + '-' + stateKey + '-' + key);
3358
3188
  }
3359
-
3360
3189
  if (prop === 'isSelected') {
3361
- const parentPath = [stateKey, ...path].slice(0, -1);
3362
- notifySelectionComponents(stateKey, path, undefined);
3363
- if (
3364
- Array.isArray(
3365
- getGlobalStore
3366
- .getState()
3367
- .getShadowValue(parentPath.join('.'), meta?.validIds)
3368
- )
3369
- ) {
3370
- const itemId = path[path.length - 1];
3371
- const fullParentKey = parentPath.join('.');
3190
+ const parentPathArray = path.slice(0, -1);
3191
+ const parentMeta = getShadowMetadata(stateKey, parentPathArray);
3372
3192
 
3193
+ // FIX: Check if the parent is an array by looking for arrayKeys in its metadata.
3194
+ if (parentMeta?.arrayKeys) {
3195
+ const fullParentKey = stateKey + '.' + parentPathArray.join('.');
3373
3196
  const selectedItemKey = getGlobalStore
3374
3197
  .getState()
3375
3198
  .selectedIndicesMap.get(fullParentKey);
3376
3199
 
3377
3200
  const fullItemKey = stateKey + '.' + path.join('.');
3378
3201
 
3202
+ // Logic remains the same.
3203
+ notifySelectionComponents(stateKey, parentPathArray, undefined);
3379
3204
  return selectedItemKey === fullItemKey;
3380
3205
  }
3381
3206
  return undefined;
@@ -3429,11 +3254,6 @@ function createProxyHandler<T>(
3429
3254
  if (path.length == 0) {
3430
3255
  if (prop === 'addZodValidation') {
3431
3256
  return (zodErrors: any[]) => {
3432
- const init = getGlobalStore
3433
- .getState()
3434
- .getInitialOptions(stateKey)?.validation;
3435
-
3436
- // For each error, set shadow metadata
3437
3257
  zodErrors.forEach((error) => {
3438
3258
  const currentMeta =
3439
3259
  getGlobalStore
@@ -3445,42 +3265,38 @@ function createProxyHandler<T>(
3445
3265
  .setShadowMetadata(stateKey, error.path, {
3446
3266
  ...currentMeta,
3447
3267
  validation: {
3448
- status: 'VALIDATION_FAILED',
3449
- message: error.message,
3268
+ status: 'INVALID',
3269
+ errors: [
3270
+ {
3271
+ source: 'client',
3272
+ message: error.message,
3273
+ severity: 'error',
3274
+ code: error.code,
3275
+ },
3276
+ ],
3277
+ lastValidated: Date.now(),
3450
3278
  validatedValue: undefined,
3451
3279
  },
3452
3280
  });
3453
- getGlobalStore.getState().notifyPathSubscribers(error.path, {
3454
- type: 'VALIDATION_FAILED',
3455
- message: error.message,
3456
- validatedValue: undefined,
3457
- });
3458
3281
  });
3459
3282
  };
3460
3283
  }
3461
3284
  if (prop === 'clearZodValidation') {
3462
3285
  return (path?: string[]) => {
3463
- // Clear specific paths
3464
3286
  if (!path) {
3465
3287
  throw new Error('clearZodValidation requires a path');
3466
- return;
3467
3288
  }
3468
- const currentMeta =
3469
- getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
3470
- {};
3471
-
3472
- if (currentMeta.validation) {
3473
- getGlobalStore.getState().setShadowMetadata(stateKey, path, {
3474
- ...currentMeta,
3475
- validation: undefined,
3476
- });
3477
3289
 
3478
- getGlobalStore
3479
- .getState()
3480
- .notifyPathSubscribers([stateKey, ...path].join('.'), {
3481
- type: 'VALIDATION_CLEARED',
3482
- });
3483
- }
3290
+ const currentMeta = getShadowMetadata(stateKey, path) || {};
3291
+
3292
+ setShadowMetadata(stateKey, path, {
3293
+ ...currentMeta,
3294
+ validation: {
3295
+ status: 'NOT_VALIDATED',
3296
+ errors: [],
3297
+ lastValidated: Date.now(),
3298
+ },
3299
+ });
3484
3300
  };
3485
3301
  }
3486
3302
  if (prop === 'applyJsonPatch') {
@@ -3511,6 +3327,7 @@ function createProxyHandler<T>(
3511
3327
  value: any;
3512
3328
  };
3513
3329
  store.updateShadowAtPath(stateKey, relativePath, value);
3330
+
3514
3331
  store.markAsDirty(stateKey, relativePath, { bubble: true });
3515
3332
 
3516
3333
  // Bubble up - notify components at this path and all parent paths
@@ -3575,9 +3392,7 @@ function createProxyHandler<T>(
3575
3392
  }
3576
3393
 
3577
3394
  if (prop === 'getComponents')
3578
- return () =>
3579
- getGlobalStore.getState().getShadowMetadata(stateKey, [])
3580
- ?.components;
3395
+ return () => getShadowMetadata(stateKey, [])?.components;
3581
3396
  if (prop === 'getAllFormRefs')
3582
3397
  return () =>
3583
3398
  formRefStore.getState().getFormRefsByStateKey(stateKey);
@@ -3608,88 +3423,34 @@ function createProxyHandler<T>(
3608
3423
  if (prop === '_stateKey') return stateKey;
3609
3424
  if (prop === '_path') return path;
3610
3425
  if (prop === 'update') {
3611
- if (recursionTimerName) {
3612
- console.timeEnd(recursionTimerName);
3613
- recursionTimerName = null;
3614
- }
3615
-
3426
+ // This method is now greatly simplified.
3427
+ // All the complex batching logic has been removed because our new,
3428
+ // universal `createEffectiveSetState` function handles it automatically for all operations.
3616
3429
  return (payload: UpdateArg<T>) => {
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
- }
3430
+ // Simply call effectiveSetState. It will automatically handle queuing
3431
+ // this operation in the batch for efficient processing.
3432
+ effectiveSetState(payload as any, path, { updateType: 'update' });
3677
3433
 
3434
+ // The .synced() method is a useful feature that allows developers
3435
+ // to manually mark a piece of state as "synced with the server"
3436
+ // after an update. This part of the functionality is preserved.
3678
3437
  return {
3679
3438
  synced: () => {
3680
3439
  const shadowMeta = getGlobalStore
3681
3440
  .getState()
3682
3441
  .getShadowMetadata(stateKey, path);
3683
3442
 
3684
- getGlobalStore.getState().setShadowMetadata(stateKey, path, {
3443
+ // Update the metadata for this specific path
3444
+ setShadowMetadata(stateKey, path, {
3685
3445
  ...shadowMeta,
3686
3446
  isDirty: false,
3687
3447
  stateSource: 'server',
3688
3448
  lastServerSync: Date.now(),
3689
3449
  });
3690
3450
 
3451
+ // Notify any components that might be subscribed to the sync status
3691
3452
  const fullPath = [stateKey, ...path].join('.');
3692
- getGlobalStore.getState().notifyPathSubscribers(fullPath, {
3453
+ notifyPathSubscribers(fullPath, {
3693
3454
  type: 'SYNC_STATUS_CHANGE',
3694
3455
  isDirty: false,
3695
3456
  });
@@ -3697,11 +3458,12 @@ function createProxyHandler<T>(
3697
3458
  };
3698
3459
  };
3699
3460
  }
3700
-
3701
3461
  if (prop === 'toggle') {
3702
- const currentValueAtPath = getGlobalStore
3703
- .getState()
3704
- .getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
3462
+ const { value: currentValueAtPath } = getScopedData(
3463
+ stateKey,
3464
+ path,
3465
+ meta
3466
+ );
3705
3467
 
3706
3468
  if (typeof currentValueAtPath != 'boolean') {
3707
3469
  throw new Error('toggle() can only be used on boolean values');
@@ -3738,19 +3500,14 @@ function createProxyHandler<T>(
3738
3500
  },
3739
3501
  };
3740
3502
 
3741
- const proxyInstance = new Proxy(baseFunction, handler);
3503
+ const proxyInstance = new Proxy({}, handler);
3742
3504
  proxyCache.set(cacheKey, proxyInstance);
3743
- console.timeEnd('rebuildStateShape Inner');
3505
+
3744
3506
  return proxyInstance;
3745
3507
  }
3746
- console.timeEnd('rebuildStateShape Outer');
3747
3508
 
3748
- const baseObj = {
3509
+ const rootLevelMethods = {
3749
3510
  revertToInitialState: (obj?: { validationKey?: string }) => {
3750
- const init = getGlobalStore
3751
- .getState()
3752
- .getInitialOptions(stateKey)?.validation;
3753
-
3754
3511
  const shadowMeta = getGlobalStore
3755
3512
  .getState()
3756
3513
  .getShadowMetadata(stateKey, []);
@@ -3766,10 +3523,10 @@ function createProxyHandler<T>(
3766
3523
  const initialState =
3767
3524
  getGlobalStore.getState().initialStateGlobal[stateKey];
3768
3525
 
3769
- getGlobalStore.getState().clearSelectedIndexesForState(stateKey);
3526
+ clearSelectedIndexesForState(stateKey);
3770
3527
 
3771
3528
  stateVersion++;
3772
- getGlobalStore.getState().initializeShadowState(stateKey, initialState);
3529
+ initializeShadowState(stateKey, initialState);
3773
3530
  rebuildStateShape({
3774
3531
  path: [],
3775
3532
  componentId: componentId!,
@@ -3819,7 +3576,8 @@ function createProxyHandler<T>(
3819
3576
  }
3820
3577
  startTransition(() => {
3821
3578
  updateInitialStateGlobal(stateKey, newState);
3822
- getGlobalStore.getState().initializeShadowState(stateKey, newState);
3579
+ initializeShadowState(stateKey, newState);
3580
+ // initializeShadowStateNEW(stateKey, newState);
3823
3581
 
3824
3582
  const stateEntry = getGlobalStore
3825
3583
  .getState()
@@ -3837,6 +3595,7 @@ function createProxyHandler<T>(
3837
3595
  };
3838
3596
  },
3839
3597
  };
3598
+
3840
3599
  const returnShape = rebuildStateShape({
3841
3600
  componentId,
3842
3601
  path: [],
@@ -3855,153 +3614,6 @@ export function $cogsSignal(proxy: {
3855
3614
  return createElement(SignalRenderer, { proxy });
3856
3615
  }
3857
3616
 
3858
- function SignalMapRenderer({
3859
- proxy,
3860
- rebuildStateShape,
3861
- }: {
3862
- proxy: {
3863
- _stateKey: string;
3864
- _path: string[];
3865
- _meta?: MetaData;
3866
- _mapFn: (
3867
- setter: any,
3868
- index: number,
3869
-
3870
- arraySetter: any
3871
- ) => ReactNode;
3872
- };
3873
- rebuildStateShape: (stuff: {
3874
- currentState: any;
3875
- path: string[];
3876
- componentId: string;
3877
- meta?: MetaData;
3878
- }) => any;
3879
- }): JSX.Element | null {
3880
- const containerRef = useRef<HTMLDivElement>(null);
3881
- const instanceIdRef = useRef<string>(`map-${crypto.randomUUID()}`);
3882
- const isSetupRef = useRef(false);
3883
- const rootsMapRef = useRef<Map<string, any>>(new Map());
3884
-
3885
- // Setup effect - store the map function in shadow metadata
3886
- useEffect(() => {
3887
- const container = containerRef.current;
3888
- if (!container || isSetupRef.current) return;
3889
-
3890
- const timeoutId = setTimeout(() => {
3891
- // Store map wrapper in metadata
3892
- const currentMeta =
3893
- getGlobalStore
3894
- .getState()
3895
- .getShadowMetadata(proxy._stateKey, proxy._path) || {};
3896
-
3897
- const mapWrappers = currentMeta.mapWrappers || [];
3898
- mapWrappers.push({
3899
- instanceId: instanceIdRef.current,
3900
- mapFn: proxy._mapFn,
3901
- containerRef: container,
3902
- rebuildStateShape: rebuildStateShape,
3903
- path: proxy._path,
3904
- componentId: instanceIdRef.current,
3905
- meta: proxy._meta,
3906
- });
3907
-
3908
- getGlobalStore
3909
- .getState()
3910
- .setShadowMetadata(proxy._stateKey, proxy._path, {
3911
- ...currentMeta,
3912
- mapWrappers,
3913
- });
3914
-
3915
- isSetupRef.current = true;
3916
-
3917
- // Initial render
3918
- renderInitialItems();
3919
- }, 0);
3920
-
3921
- // Cleanup
3922
- return () => {
3923
- clearTimeout(timeoutId);
3924
- if (instanceIdRef.current) {
3925
- const currentMeta =
3926
- getGlobalStore
3927
- .getState()
3928
- .getShadowMetadata(proxy._stateKey, proxy._path) || {};
3929
- if (currentMeta.mapWrappers) {
3930
- currentMeta.mapWrappers = currentMeta.mapWrappers.filter(
3931
- (w) => w.instanceId !== instanceIdRef.current
3932
- );
3933
- getGlobalStore
3934
- .getState()
3935
- .setShadowMetadata(proxy._stateKey, proxy._path, currentMeta);
3936
- }
3937
- }
3938
- rootsMapRef.current.forEach((root) => root.unmount());
3939
- };
3940
- }, []);
3941
-
3942
- const renderInitialItems = () => {
3943
- const container = containerRef.current;
3944
- if (!container) return;
3945
-
3946
- const value = getGlobalStore
3947
- .getState()
3948
- .getShadowValue(
3949
- [proxy._stateKey, ...proxy._path].join('.'),
3950
- proxy._meta?.validIds
3951
- ) as any[];
3952
-
3953
- if (!Array.isArray(value)) return;
3954
-
3955
- // --- BUG FIX IS HERE ---
3956
- // Prioritize the filtered IDs from the meta object, just like the regular `stateMap`.
3957
- // This ensures the keys match the filtered data.
3958
- const arrayKeys =
3959
- proxy._meta?.validIds ??
3960
- getGlobalStore.getState().getShadowMetadata(proxy._stateKey, proxy._path)
3961
- ?.arrayKeys ??
3962
- [];
3963
- // --- END OF FIX ---
3964
-
3965
- const arraySetter = rebuildStateShape({
3966
- currentState: value,
3967
- path: proxy._path,
3968
- componentId: instanceIdRef.current,
3969
- meta: proxy._meta,
3970
- });
3971
-
3972
- value.forEach((item, index) => {
3973
- const itemKey = arrayKeys[index]!; // Now this will be the correct key for the filtered item
3974
- if (!itemKey) return; // Safeguard if there's a mismatch
3975
-
3976
- const itemComponentId = uuidv4();
3977
- const itemElement = document.createElement('div');
3978
-
3979
- itemElement.setAttribute('data-item-path', itemKey);
3980
- container.appendChild(itemElement);
3981
-
3982
- const root = createRoot(itemElement);
3983
- rootsMapRef.current.set(itemKey, root);
3984
-
3985
- const itemPath = itemKey.split('.').slice(1) as string[];
3986
-
3987
- // Render CogsItemWrapper instead of direct render
3988
- root.render(
3989
- createElement(MemoizedCogsItemWrapper, {
3990
- stateKey: proxy._stateKey,
3991
- itemComponentId: itemComponentId,
3992
- itemPath: itemPath,
3993
- localIndex: index,
3994
- arraySetter: arraySetter,
3995
- rebuildStateShape: rebuildStateShape,
3996
- renderFn: proxy._mapFn,
3997
- })
3998
- );
3999
- });
4000
- };
4001
-
4002
- return <div ref={containerRef} data-map-container={instanceIdRef.current} />;
4003
- }
4004
-
4005
3617
  function SignalRenderer({
4006
3618
  proxy,
4007
3619
  }: {
@@ -4016,12 +3628,10 @@ function SignalRenderer({
4016
3628
  const instanceIdRef = useRef<string | null>(null);
4017
3629
  const isSetupRef = useRef(false);
4018
3630
  const signalId = `${proxy._stateKey}-${proxy._path.join('.')}`;
4019
- const value = getGlobalStore
4020
- .getState()
4021
- .getShadowValue(
4022
- [proxy._stateKey, ...proxy._path].join('.'),
4023
- proxy._meta?.validIds
4024
- );
3631
+ const arrayPathKey = proxy._path.join('.');
3632
+ const viewIds = proxy._meta?.arrayViews?.[arrayPathKey];
3633
+
3634
+ const value = getShadowValue(proxy._stateKey, proxy._path, viewIds);
4025
3635
 
4026
3636
  // Setup effect - runs only once
4027
3637
  useEffect(() => {
@@ -4216,7 +3826,7 @@ function ListItemWrapper({
4216
3826
  );
4217
3827
 
4218
3828
  useEffect(() => {
4219
- getGlobalStore.getState().subscribeToPath(fullKey, (e) => {
3829
+ subscribeToPath(fullKey, (e) => {
4220
3830
  forceUpdate({});
4221
3831
  });
4222
3832
  }, []);
@@ -4230,7 +3840,7 @@ function ListItemWrapper({
4230
3840
  hasReportedInitialHeight.current = true;
4231
3841
  const newHeight = element.offsetHeight;
4232
3842
 
4233
- getGlobalStore.getState().setShadowMetadata(stateKey, itemPath, {
3843
+ setShadowMetadata(stateKey, itemPath, {
4234
3844
  virtualizer: {
4235
3845
  itemHeight: newHeight,
4236
3846
  domRef: element,
@@ -4239,7 +3849,7 @@ function ListItemWrapper({
4239
3849
 
4240
3850
  const arrayPath = itemPath.slice(0, -1);
4241
3851
  const arrayPathKey = [stateKey, ...arrayPath].join('.');
4242
- getGlobalStore.getState().notifyPathSubscribers(arrayPathKey, {
3852
+ notifyPathSubscribers(arrayPathKey, {
4243
3853
  type: 'ITEMHEIGHT',
4244
3854
  itemKey: itemPath.join('.'),
4245
3855
 
@@ -4248,8 +3858,7 @@ function ListItemWrapper({
4248
3858
  }
4249
3859
  }, [inView, imagesLoaded, stateKey, itemPath]);
4250
3860
 
4251
- const fullItemPath = [stateKey, ...itemPath].join('.');
4252
- const itemValue = getGlobalStore.getState().getShadowValue(fullItemPath);
3861
+ const itemValue = getShadowValue(stateKey, itemPath);
4253
3862
 
4254
3863
  if (itemValue === undefined) {
4255
3864
  return null;
@@ -4276,7 +3885,6 @@ function FormElementWrapper({
4276
3885
  stateKey: string;
4277
3886
  path: string[];
4278
3887
  rebuildStateShape: (options: {
4279
- currentState: any;
4280
3888
  path: string[];
4281
3889
  componentId: string;
4282
3890
  meta?: any;
@@ -4290,9 +3898,7 @@ function FormElementWrapper({
4290
3898
 
4291
3899
  const stateKeyPathKey = [stateKey, ...path].join('.');
4292
3900
  useRegisterComponent(stateKey, componentId, forceUpdate);
4293
- const globalStateValue = getGlobalStore
4294
- .getState()
4295
- .getShadowValue(stateKeyPathKey);
3901
+ const globalStateValue = getShadowValue(stateKey, path);
4296
3902
  const [localValue, setLocalValue] = useState<any>(globalStateValue);
4297
3903
  const isCurrentlyDebouncing = useRef(false);
4298
3904
  const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
@@ -4340,21 +3946,21 @@ function FormElementWrapper({
4340
3946
 
4341
3947
  debounceTimeoutRef.current = setTimeout(() => {
4342
3948
  isCurrentlyDebouncing.current = false;
4343
-
4344
- // Update state
4345
3949
  setState(newValue, path, { updateType: 'update' });
4346
3950
 
4347
- // Perform LIVE validation (gentle)
4348
- const { getInitialOptions, setShadowMetadata, getShadowMetadata } =
4349
- getGlobalStore.getState();
3951
+ // NEW: Check if validation is enabled via features
3952
+ const rootMeta = getGlobalStore
3953
+ .getState()
3954
+ .getShadowMetadata(stateKey, []);
3955
+ if (!rootMeta?.features?.validationEnabled) return;
3956
+
4350
3957
  const validationOptions = getInitialOptions(stateKey)?.validation;
4351
3958
  const zodSchema =
4352
3959
  validationOptions?.zodSchemaV4 || validationOptions?.zodSchemaV3;
4353
3960
 
4354
3961
  if (zodSchema) {
4355
- const fullState = getGlobalStore.getState().getShadowValue(stateKey);
3962
+ const fullState = getShadowValue(stateKey, []);
4356
3963
  const result = zodSchema.safeParse(fullState);
4357
-
4358
3964
  const currentMeta = getShadowMetadata(stateKey, path) || {};
4359
3965
 
4360
3966
  if (!result.success) {
@@ -4362,6 +3968,7 @@ function FormElementWrapper({
4362
3968
  'issues' in result.error
4363
3969
  ? result.error.issues
4364
3970
  : (result.error as any).errors;
3971
+
4365
3972
  const pathErrors = errors.filter(
4366
3973
  (error: any) =>
4367
3974
  JSON.stringify(error.path) === JSON.stringify(path)
@@ -4371,30 +3978,37 @@ function FormElementWrapper({
4371
3978
  setShadowMetadata(stateKey, path, {
4372
3979
  ...currentMeta,
4373
3980
  validation: {
4374
- status: 'INVALID_LIVE',
4375
- message: pathErrors[0]?.message,
3981
+ status: 'INVALID',
3982
+ errors: [
3983
+ {
3984
+ source: 'client',
3985
+ message: pathErrors[0]?.message,
3986
+ severity: 'warning', // Gentle error during typing
3987
+ },
3988
+ ],
3989
+ lastValidated: Date.now(),
4376
3990
  validatedValue: newValue,
4377
3991
  },
4378
3992
  });
4379
3993
  } else {
4380
- // This field has no errors - clear validation
4381
3994
  setShadowMetadata(stateKey, path, {
4382
3995
  ...currentMeta,
4383
3996
  validation: {
4384
- status: 'VALID_LIVE',
3997
+ status: 'VALID',
3998
+ errors: [],
3999
+ lastValidated: Date.now(),
4385
4000
  validatedValue: newValue,
4386
- message: undefined,
4387
4001
  },
4388
4002
  });
4389
4003
  }
4390
4004
  } else {
4391
- // Validation passed - clear any existing errors
4392
4005
  setShadowMetadata(stateKey, path, {
4393
4006
  ...currentMeta,
4394
4007
  validation: {
4395
- status: 'VALID_LIVE',
4008
+ status: 'VALID',
4009
+ errors: [],
4010
+ lastValidated: Date.now(),
4396
4011
  validatedValue: newValue,
4397
- message: undefined,
4398
4012
  },
4399
4013
  });
4400
4014
  }
@@ -4417,7 +4031,8 @@ function FormElementWrapper({
4417
4031
  isCurrentlyDebouncing.current = false;
4418
4032
  setState(localValue, path, { updateType: 'update' });
4419
4033
  }
4420
-
4034
+ const rootMeta = getShadowMetadata(stateKey, []);
4035
+ if (!rootMeta?.features?.validationEnabled) return;
4421
4036
  const { getInitialOptions } = getGlobalStore.getState();
4422
4037
  const validationOptions = getInitialOptions(stateKey)?.validation;
4423
4038
  const zodSchema =
@@ -4428,19 +4043,20 @@ function FormElementWrapper({
4428
4043
  // Get the full path including stateKey
4429
4044
 
4430
4045
  // Update validation state to "validating"
4431
- const currentMeta = getGlobalStore
4432
- .getState()
4433
- .getShadowMetadata(stateKey, path);
4434
- getGlobalStore.getState().setShadowMetadata(stateKey, path, {
4046
+ const currentMeta = getShadowMetadata(stateKey, path);
4047
+
4048
+ setShadowMetadata(stateKey, path, {
4435
4049
  ...currentMeta,
4436
4050
  validation: {
4437
- status: 'DIRTY',
4051
+ status: 'VALIDATING',
4052
+ errors: [],
4053
+ lastValidated: Date.now(),
4438
4054
  validatedValue: localValue,
4439
4055
  },
4440
4056
  });
4441
4057
 
4442
4058
  // Validate full state
4443
- const fullState = getGlobalStore.getState().getShadowValue(stateKey);
4059
+ const fullState = getShadowValue(stateKey, []);
4444
4060
  const result = zodSchema.safeParse(fullState);
4445
4061
  console.log('result ', result);
4446
4062
  if (!result.success) {
@@ -4504,20 +4120,27 @@ function FormElementWrapper({
4504
4120
 
4505
4121
  console.log('Filtered path errors:', pathErrors);
4506
4122
  // Update shadow metadata with validation result
4507
- getGlobalStore.getState().setShadowMetadata(stateKey, path, {
4123
+ setShadowMetadata(stateKey, path, {
4508
4124
  ...currentMeta,
4509
4125
  validation: {
4510
- status: 'VALIDATION_FAILED',
4511
- message: pathErrors[0]?.message,
4126
+ status: 'INVALID',
4127
+ errors: pathErrors.map((err: any) => ({
4128
+ source: 'client' as const,
4129
+ message: err.message,
4130
+ severity: 'error' as const, // Hard error on blur
4131
+ })),
4132
+ lastValidated: Date.now(),
4512
4133
  validatedValue: localValue,
4513
4134
  },
4514
4135
  });
4515
4136
  } else {
4516
4137
  // Validation passed
4517
- getGlobalStore.getState().setShadowMetadata(stateKey, path, {
4138
+ setShadowMetadata(stateKey, path, {
4518
4139
  ...currentMeta,
4519
4140
  validation: {
4520
- status: 'VALID_PENDING_SYNC',
4141
+ status: 'VALID',
4142
+ errors: [],
4143
+ lastValidated: Date.now(),
4521
4144
  validatedValue: localValue,
4522
4145
  },
4523
4146
  });
@@ -4526,7 +4149,6 @@ function FormElementWrapper({
4526
4149
  }, [stateKey, path, localValue, setState]);
4527
4150
 
4528
4151
  const baseState = rebuildStateShape({
4529
- currentState: globalStateValue,
4530
4152
  path: path,
4531
4153
  componentId: componentId,
4532
4154
  });
@@ -4565,9 +4187,6 @@ function useRegisterComponent(
4565
4187
  const fullComponentId = `${stateKey}////${componentId}`;
4566
4188
 
4567
4189
  useLayoutEffect(() => {
4568
- const { registerComponent, unregisterComponent } =
4569
- getGlobalStore.getState();
4570
-
4571
4190
  // Call the safe, centralized function to register
4572
4191
  registerComponent(stateKey, fullComponentId, {
4573
4192
  forceUpdate: () => forceUpdate({}),