cogsbox-state 0.5.476 → 0.5.478

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
@@ -1,6 +1,10 @@
1
1
  'use client';
2
2
  import { pluginStore } from './pluginStore';
3
- import { FormWrapperParams, type CogsPlugin } from './plugins';
3
+ import {
4
+ FormWrapperParams,
5
+ type ChainMethodCallables,
6
+ type CogsPlugin,
7
+ } from './plugins';
4
8
  import {
5
9
  createElement,
6
10
  startTransition,
@@ -79,7 +83,8 @@ type CutFunctionType<T> = (
79
83
  options?: { waitForSync?: boolean }
80
84
  ) => StateObject<T>;
81
85
 
82
- export type InferArrayElement<T> = T extends (infer U)[] ? U : never;
86
+ export type InferArrayElement<T> =
87
+ NonNullable<T> extends (infer U)[] ? U : never;
83
88
 
84
89
  export type FormControl<T> = (obj: FormElementParams<T>) => JSX.Element;
85
90
 
@@ -175,6 +180,7 @@ export type EndType<
175
180
  selectText: () => void;
176
181
  };
177
182
  $removeStorage: () => void;
183
+ $setRaw: (value: T) => void;
178
184
  $sync: () => void;
179
185
  $validationWrapper: ({
180
186
  children,
@@ -303,20 +309,21 @@ export type StateObject<
303
309
  > = {
304
310
  (): T;
305
311
  (newValue: T | ((prev: T) => T)): void;
306
- } & (T extends any[]
312
+ } & ([NonNullable<T>] extends [any[]] // <-- Wrap in brackets
307
313
  ? ArrayEndType<T, TPlugins>
308
- : T extends Record<string, unknown> | object
309
- ? { [K in keyof T]-?: StateObject<T[K], TPlugins> }
310
- : EndType<T, TPlugins>) & // primitives just get EndType
314
+ : [NonNullable<T>] extends [Record<string, unknown> | object] // <-- Wrap in brackets
315
+ ? {
316
+ [K in keyof NonNullable<T>]-?: StateObject<NonNullable<T>[K], TPlugins>;
317
+ }
318
+ : {}) & // Fallback to {} since we intersect EndType below anyway
311
319
  EndType<T, TPlugins> & {
312
- // NO third param = no $cutThis
313
- $toggle: T extends boolean ? () => void : never;
320
+ $toggle: NonNullable<T> extends boolean ? () => void : never;
314
321
  $validate: () => { success: boolean; data?: T; error?: any };
315
322
  $_componentId: string | null;
316
323
  $getComponents: () => ComponentsType;
317
324
  $_initialState: T;
318
325
  $updateInitialState: (newState: T | null) => {
319
- fetchId: (field: keyof T) => string | number;
326
+ fetchId: (field: keyof NonNullable<T>) => string | number;
320
327
  };
321
328
  $initializeAndMergeShadowState: (newState: any | null) => void;
322
329
  $_isLoading: boolean;
@@ -332,10 +339,23 @@ export type StateObject<
332
339
  }) => void
333
340
  ) => void;
334
341
  $getLocalStorage: (key: string) => LocalStorageData<T> | null;
335
- };
336
-
342
+ } & PluginChainMethodCallables<TPlugins>;
337
343
  export type CogsUpdate<T extends unknown> = UpdateType<T>;
338
344
 
345
+ type UnionToIntersection<T> = (
346
+ T extends any ? (arg: T) => void : never
347
+ ) extends (arg: infer I) => void
348
+ ? I
349
+ : never;
350
+
351
+ type PluginChainMethodCallables<
352
+ TPlugins extends readonly CogsPlugin<any, any, any, any, any>[],
353
+ > = UnionToIntersection<
354
+ TPlugins[number] extends CogsPlugin<any, any, any, any, any, infer TMethods>
355
+ ? ChainMethodCallables<TMethods>
356
+ : {}
357
+ >;
358
+
339
359
  type EffectiveSetStateArg<
340
360
  T,
341
361
  UpdateType extends 'update' | 'insert' | 'cut',
@@ -504,16 +524,6 @@ export type FormsElementsType<
504
524
  key?: string;
505
525
  }) => React.ReactNode;
506
526
  };
507
- export type CogsInitialState<T> =
508
- | {
509
- initialState: T;
510
- }
511
- | CreateStateOptionsType<T>;
512
-
513
- export type TransformedStateType<T> = {
514
- [P in keyof T]: T[P] extends CogsInitialState<infer U> ? U : T[P];
515
- };
516
-
517
527
  const {
518
528
  getInitialOptions,
519
529
  updateInitialStateGlobal,
@@ -692,16 +702,6 @@ function setOptions<StateKey, Opt>({
692
702
 
693
703
  return mergedOptions;
694
704
  }
695
- export function addStateOptions<T>(
696
- initialState: T,
697
- options: CreateStateOptionsType<T>
698
- ) {
699
- return {
700
- ...options,
701
- initialState,
702
- _addStateOptions: true,
703
- };
704
- }
705
705
  export type PluginData = {
706
706
  plugin: CogsPlugin<any, any, any, any, any>;
707
707
  options: any;
@@ -721,60 +721,70 @@ export const createCogsState = <
721
721
  validation?: ValidationOptionsType;
722
722
  }
723
723
  ) => {
724
- type PluginOptions = {
725
- [K in TPlugins[number] as K['name']]?: K extends CogsPlugin<
726
- string,
727
- infer O,
728
- any,
729
- any,
730
- any
731
- >
724
+ type ExtractPluginOptions<T> = T extends {
725
+ useHook?: (params: { options: infer O }) => any; // infers O here
726
+ }
727
+ ? O
728
+ : T extends CogsPlugin<string, infer O, any, any, any> // AND here
732
729
  ? O
733
730
  : never;
731
+
732
+ type PluginOptions = {
733
+ [K in TPlugins[number] as K['name']]?: ExtractPluginOptions<K>;
734
734
  };
735
735
 
736
736
  if (opt?.plugins) {
737
737
  pluginStore.getState().setRegisteredPlugins(opt.plugins as any);
738
738
  }
739
739
 
740
+ type UnionToIntersection<U> = (
741
+ U extends any ? (k: U) => void : never
742
+ ) extends (k: infer I) => void
743
+ ? I
744
+ : never;
745
+
746
+ type FullState = State &
747
+ UnionToIntersection<
748
+ {
749
+ [K in keyof TPlugins]: TPlugins[K] extends {
750
+ initialState: () => infer S;
751
+ }
752
+ ? S
753
+ : {};
754
+ }[number]
755
+ >;
756
+
757
+ const pluginState: Record<string, unknown> = {};
758
+ if (opt?.plugins) {
759
+ for (const plugin of opt.plugins) {
760
+ if (typeof plugin.initialState === 'function') {
761
+ Object.assign(pluginState, plugin.initialState());
762
+ }
763
+ }
764
+ }
765
+ const mergedInitialState = { ...pluginState, ...initialState } as FullState;
766
+
740
767
  const [statePart, initialOptionsPart] =
741
- transformStateFunc<State>(initialState);
768
+ transformStateFunc<FullState>(mergedInitialState);
742
769
 
743
- // FIX: Store options INCLUDING validation for each state key
744
770
  Object.keys(statePart).forEach((key) => {
745
- let existingOptions = initialOptionsPart[key] || {};
746
-
747
- const mergedOptions: any = {
748
- ...existingOptions,
749
- };
771
+ let mergedOptions: any = {};
750
772
 
751
773
  if (opt?.formElements) {
752
- mergedOptions.formElements = {
753
- ...opt.formElements,
754
- ...(existingOptions.formElements || {}),
755
- };
774
+ mergedOptions.formElements = opt.formElements;
756
775
  }
757
776
 
758
777
  mergedOptions.validation = {
759
778
  onBlur: 'error',
760
779
  ...opt?.validation,
761
- ...(existingOptions.validation || {}),
762
780
  };
763
781
 
764
- if (opt?.validation?.key && !existingOptions.validation?.key) {
765
- mergedOptions.validation.key = `${opt.validation.key}.${key}`;
766
- }
767
-
768
782
  const existingGlobalOptions = getInitialOptions(key);
769
783
 
770
784
  const finalOptions = existingGlobalOptions
771
785
  ? {
772
786
  ...existingGlobalOptions,
773
- ...mergedOptions,
774
- formElements: {
775
- ...existingGlobalOptions.formElements,
776
- ...mergedOptions.formElements,
777
- },
787
+ formElements: opt?.formElements,
778
788
  validation: {
779
789
  ...existingGlobalOptions.validation,
780
790
  ...mergedOptions.validation,
@@ -782,7 +792,9 @@ export const createCogsState = <
782
792
  }
783
793
  : mergedOptions;
784
794
 
785
- setInitialStateOptions(key, finalOptions);
795
+ if (Object.keys(finalOptions).length > 0) {
796
+ setInitialStateOptions(key, finalOptions);
797
+ }
786
798
  });
787
799
 
788
800
  Object.keys(statePart).forEach((key) => {
@@ -793,19 +805,42 @@ export const createCogsState = <
793
805
  } & {};
794
806
  type StateKeys = keyof typeof statePart;
795
807
 
808
+ // Flattens the 4x spam into a single clean object
809
+ type CleanIntersection<T> = T extends object ? { [K in keyof T]: T[K] } : T;
810
+
811
+ // Helper to find which keys are "keyed" (like queryParams)
812
+ type KeyedKeys<P> = {
813
+ [K in keyof P]-?: NonNullable<P[K]> extends { __key: 'keyed'; map: any }
814
+ ? K
815
+ : never;
816
+ }[keyof P];
817
+
796
818
  const useCogsState = <StateKey extends StateKeys>(
797
819
  stateKey: StateKey,
798
820
  options?: Prettify<
799
821
  OptionsType<(typeof statePart)[StateKey], never> & {
800
822
  [PName in keyof PluginOptions]?: PluginOptions[PName] extends infer P
801
823
  ? P extends Record<string, any>
802
- ? {
803
- [K in keyof P]: P[K] extends { __key: 'keyed'; map: infer TMap }
804
- ? StateKey extends keyof TMap
805
- ? TMap[StateKey]
806
- : never
807
- : P[K];
808
- }
824
+ ? Prettify<
825
+ // 1. NON-KEYED: Partial physically forces stateRoom to be optional
826
+ Partial<Pick<P, Exclude<keyof P, KeyedKeys<P>>>> &
827
+ // 2. KEYED: Resolves queryParams and squashes the 4x intersection
828
+ {
829
+ [K in KeyedKeys<P> as StateKey extends keyof NonNullable<
830
+ P[K]
831
+ >['map']
832
+ ? NonNullable<P[K]>['map'][StateKey] extends undefined
833
+ ? never
834
+ : keyof NonNullable<P[K]>['map'][StateKey] extends never
835
+ ? never
836
+ : K
837
+ : never]: CleanIntersection<
838
+ StateKey extends keyof NonNullable<P[K]>['map']
839
+ ? NonNullable<P[K]>['map'][StateKey]
840
+ : never
841
+ >;
842
+ }
843
+ >
809
844
  : P
810
845
  : never;
811
846
  }
@@ -825,7 +860,7 @@ export const createCogsState = <
825
860
  const thiState =
826
861
  getShadowValue(stateKey as string, []) || statePart[stateKey as string];
827
862
 
828
- const updater = useCogsStateFn<(typeof statePart)[StateKey], TPlugins>(
863
+ const updater = useCogsStateFn<(typeof statePart)[StateKey]>(
829
864
  thiState,
830
865
  {
831
866
  stateKey: stateKey as string,
@@ -858,7 +893,7 @@ export const createCogsState = <
858
893
  };
859
894
  }, [stateKey, updater]);
860
895
 
861
- return updater;
896
+ return updater as StateObject<(typeof statePart)[StateKey]>;
862
897
  };
863
898
 
864
899
  function setCogsOptionsByKey<StateKey extends StateKeys>(
@@ -984,6 +1019,18 @@ const loadFromLocalStorage = (localStorageKey: string) => {
984
1019
  return null;
985
1020
  }
986
1021
  };
1022
+
1023
+ const removeFromLocalStorage = (localStorageKey?: string) => {
1024
+ if (!localStorageKey) return;
1025
+
1026
+ try {
1027
+ if (typeof window !== 'undefined' && window.localStorage) {
1028
+ window.localStorage.removeItem(localStorageKey);
1029
+ }
1030
+ } catch (error) {
1031
+ console.error('Error removing from localStorage:', error);
1032
+ }
1033
+ };
987
1034
  const loadAndApplyLocalStorage = (stateKey: string, options: any) => {
988
1035
  const currentState = getShadowValue(stateKey, []);
989
1036
  const { sessionId } = useCogsConfig();
@@ -1210,7 +1257,7 @@ function getComponentNotifications(
1210
1257
  const pathMeta = getShadowMetadata(stateKey, currentPath);
1211
1258
 
1212
1259
  if (pathMeta?.pathComponents) {
1213
- pathMeta.pathComponents.forEach((componentId: string) => {
1260
+ pathMeta.pathComponents.forEach((componentId) => {
1214
1261
  const component = rootMeta.components?.get(componentId);
1215
1262
  if (component) {
1216
1263
  componentsToNotify.add(component);
@@ -1499,7 +1546,7 @@ function createEffectiveSetState<T>(
1499
1546
 
1500
1547
  export function useCogsStateFn<
1501
1548
  TStateObject extends unknown,
1502
- const TPlugins extends readonly CogsPlugin<any, any, any, any, any>[],
1549
+ const TPlugins extends readonly CogsPlugin<any, any, any, any, any>[] = [],
1503
1550
  >(
1504
1551
  stateObject: TStateObject,
1505
1552
  {
@@ -1959,6 +2006,61 @@ function getScopedData(stateKey: string, path: string[], meta?: MetaData) {
1959
2006
  };
1960
2007
  }
1961
2008
 
2009
+ function pathMatchesPattern(path: string[], pattern?: string[]) {
2010
+ if (!pattern) return true;
2011
+ if (path.length !== pattern.length) return false;
2012
+
2013
+ return pattern.every((segment, index) => {
2014
+ return segment === '*' || segment === path[index];
2015
+ });
2016
+ }
2017
+
2018
+ function valueMatchesChainTarget(value: any, target: string) {
2019
+ if (target === 'any') return true;
2020
+ if (target === 'array') return Array.isArray(value);
2021
+ if (target === 'boolean') return typeof value === 'boolean';
2022
+ if (target === 'object') {
2023
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
2024
+ }
2025
+ if (target === 'primitive') {
2026
+ return (
2027
+ value === null || (typeof value !== 'object' && !Array.isArray(value))
2028
+ );
2029
+ }
2030
+ return false;
2031
+ }
2032
+
2033
+ function getFieldRefsForPath(stateKey: string, path: string[]) {
2034
+ const meta = getGlobalStore.getState().getShadowMetadata(stateKey, path);
2035
+ if (!meta?.clientActivityState?.elements) return [];
2036
+ const refs: RefObject<any>[] = [];
2037
+ meta.clientActivityState.elements.forEach((entry: any) => {
2038
+ if (entry.domRef?.current) refs.push(entry.domRef);
2039
+ });
2040
+ return refs;
2041
+ }
2042
+
2043
+ function getFieldElementsForPath(stateKey: string, path: string[]) {
2044
+ const refs = getFieldRefsForPath(stateKey, path);
2045
+ return refs.map((ref) => ref.current).filter(Boolean);
2046
+ }
2047
+
2048
+ function setFieldDisabledForPath(
2049
+ stateKey: string,
2050
+ path: string[],
2051
+ disabled: boolean
2052
+ ) {
2053
+ getFieldElementsForPath(stateKey, path).forEach((el: any) => {
2054
+ if ('disabled' in el) {
2055
+ el.disabled = disabled;
2056
+ return;
2057
+ }
2058
+
2059
+ el.style.pointerEvents = disabled ? 'none' : '';
2060
+ el.setAttribute('aria-disabled', String(disabled));
2061
+ });
2062
+ }
2063
+
1962
2064
  function createProxyHandler<
1963
2065
  T,
1964
2066
  const TPlugins extends readonly CogsPlugin<any, any, any, any, any>[],
@@ -1979,6 +2081,11 @@ function createProxyHandler<
1979
2081
  componentId: string;
1980
2082
  meta?: MetaData;
1981
2083
  }): any {
2084
+ const rawMeta = getShadowMetadata(stateKey, path);
2085
+ if (rawMeta?.isRaw) {
2086
+ return getShadowValue(stateKey, path);
2087
+ }
2088
+
1982
2089
  const derivationSignature = meta
1983
2090
  ? JSON.stringify(meta.arrayViews || meta.transforms)
1984
2091
  : '';
@@ -2055,6 +2162,91 @@ function createProxyHandler<
2055
2162
  }
2056
2163
 
2057
2164
  if (typeof prop === 'string' && !prop.startsWith('$')) {
2165
+ const { value } = getScopedData(stateKey, path, meta);
2166
+ const hasStateChild =
2167
+ value !== null &&
2168
+ typeof value === 'object' &&
2169
+ !Array.isArray(value) &&
2170
+ Object.prototype.hasOwnProperty.call(value, prop);
2171
+
2172
+ if (hasStateChild) {
2173
+ const nextPath = [...path, prop];
2174
+ return rebuildStateShape({
2175
+ path: nextPath,
2176
+ componentId: componentId!,
2177
+ meta,
2178
+ });
2179
+ }
2180
+
2181
+ const registeredPlugins = pluginStore.getState().registeredPlugins;
2182
+ for (const plugin of registeredPlugins) {
2183
+ const chainMethod = (plugin.chainMethods as any)?.[prop];
2184
+ if (!chainMethod) continue;
2185
+ if (!pathMatchesPattern(path, chainMethod.pathPattern)) continue;
2186
+ if (!valueMatchesChainTarget(value, chainMethod.target)) continue;
2187
+
2188
+ return (...args: any[]) => {
2189
+ const store = pluginStore.getState();
2190
+ const options = store.pluginOptions
2191
+ .get(stateKey)
2192
+ ?.get(plugin.name);
2193
+ const hookData = store.getHookResult(stateKey, plugin.name);
2194
+
2195
+ return chainMethod.handler(
2196
+ {
2197
+ stateKey,
2198
+ path,
2199
+ pluginName: plugin.name,
2200
+ options,
2201
+ hookData,
2202
+ $get: () => getScopedData(stateKey, path, meta).value,
2203
+ $update: (payload: any) => {
2204
+ effectiveSetState(payload, path, {
2205
+ updateType: 'update',
2206
+ });
2207
+
2208
+ return {
2209
+ synced: () => {
2210
+ const shadowMeta = getGlobalStore
2211
+ .getState()
2212
+ .getShadowMetadata(stateKey, path);
2213
+
2214
+ setShadowMetadata(stateKey, path, {
2215
+ ...shadowMeta,
2216
+ isDirty: false,
2217
+ stateSource: 'server',
2218
+ lastServerSync: Date.now(),
2219
+ });
2220
+ },
2221
+ };
2222
+ },
2223
+ $applyOperation: (
2224
+ operation: UpdateTypeDetail,
2225
+ metaData?: Record<string, any>
2226
+ ) => {
2227
+ effectiveSetState(operation.newValue, operation.path, {
2228
+ updateType: operation.updateType,
2229
+ itemId: operation.itemId,
2230
+ metaData,
2231
+ });
2232
+ },
2233
+ getFieldMetaData: () =>
2234
+ getPluginMetaDataMap(stateKey, path)?.get(plugin.name),
2235
+ setFieldMetaData: (data: Record<string, any>) =>
2236
+ setPluginMetaData(stateKey, path, plugin.name, data),
2237
+ removeFieldMetaData: () =>
2238
+ removePluginMetaData(stateKey, path, plugin.name),
2239
+ getFieldRefs: () => getFieldRefsForPath(stateKey, path),
2240
+ getFieldElements: () =>
2241
+ getFieldElementsForPath(stateKey, path),
2242
+ setFieldDisabled: (disabled: boolean) =>
2243
+ setFieldDisabledForPath(stateKey, path, disabled),
2244
+ },
2245
+ ...args
2246
+ );
2247
+ };
2248
+ }
2249
+
2058
2250
  const nextPath = [...path, prop];
2059
2251
  return rebuildStateShape({
2060
2252
  path: nextPath,
@@ -2171,8 +2363,18 @@ function createProxyHandler<
2171
2363
  const localKey = isFunction(initalOptionsGet?.localStorage?.key)
2172
2364
  ? initalOptionsGet.localStorage.key(initialState)
2173
2365
  : initalOptionsGet?.localStorage?.key;
2174
- const storageKey = `${sessionId}-${stateKey}-${localKey}`;
2175
- if (storageKey) localStorage.removeItem(storageKey);
2366
+ const storageKey =
2367
+ sessionId && localKey
2368
+ ? `${sessionId}-${stateKey}-${localKey}`
2369
+ : undefined;
2370
+ removeFromLocalStorage(storageKey);
2371
+ };
2372
+ }
2373
+ if (prop === '$setRaw') {
2374
+ return (value: any) => {
2375
+ const currentMeta = getShadowMetadata(stateKey, path) || {};
2376
+ setShadowMetadata(stateKey, path, { ...currentMeta, isRaw: true });
2377
+ effectiveSetState(value, path, { updateType: 'update' });
2176
2378
  };
2177
2379
  }
2178
2380
 
@@ -3323,10 +3525,11 @@ function createProxyHandler<
3323
3525
  const localKey = isFunction(initalOptionsGet?.localStorage?.key)
3324
3526
  ? initalOptionsGet?.localStorage?.key(revertState)
3325
3527
  : initalOptionsGet?.localStorage?.key;
3326
- const storageKey = `${sessionId}-${stateKey}-${localKey}`;
3327
- if (storageKey) {
3328
- localStorage.removeItem(storageKey);
3329
- }
3528
+ const storageKey =
3529
+ sessionId && localKey
3530
+ ? `${sessionId}-${stateKey}-${localKey}`
3531
+ : undefined;
3532
+ removeFromLocalStorage(storageKey);
3330
3533
 
3331
3534
  notifyComponents(stateKey);
3332
3535
 
@@ -3350,11 +3553,11 @@ function createProxyHandler<
3350
3553
  ? initalOptionsGet?.localStorage?.key(initialState)
3351
3554
  : initalOptionsGet?.localStorage?.key;
3352
3555
 
3353
- const storageKey = `${sessionId}-${stateKey}-${localKey}`;
3354
-
3355
- if (localStorage.getItem(storageKey)) {
3356
- localStorage.removeItem(storageKey);
3357
- }
3556
+ const storageKey =
3557
+ sessionId && localKey
3558
+ ? `${sessionId}-${stateKey}-${localKey}`
3559
+ : undefined;
3560
+ removeFromLocalStorage(storageKey);
3358
3561
  startTransition(() => {
3359
3562
  updateInitialStateGlobal(stateKey, newState);
3360
3563
  initializeShadowState(stateKey, newState);
@@ -271,6 +271,7 @@ export function FormElementWrapper({
271
271
 
272
272
  useEffect(() => {
273
273
  const { getShadowMetadata, setShadowMetadata } = getGlobalStore.getState();
274
+ console.log('FormElementWrapper effect running for:', stateKey, path);
274
275
 
275
276
  // Initialize clientActivityState if needed
276
277
  const currentMeta = getShadowMetadata(stateKey, path) || {};
@@ -302,7 +303,7 @@ export function FormElementWrapper({
302
303
  inputType: formElementRef.current?.type,
303
304
  mountedAt: Date.now(),
304
305
  });
305
-
306
+ console.log('currentMeta', currentMeta);
306
307
  setShadowMetadata(stateKey, path, currentMeta);
307
308
 
308
309
  // Subscribe to path updates
@@ -551,6 +552,7 @@ export function FormElementWrapper({
551
552
  </ValidationWrapper>
552
553
  );
553
554
  }
555
+
554
556
  export function useRegisterComponent(
555
557
  stateKey: string,
556
558
  componentId: string,
@@ -8,7 +8,6 @@ import {
8
8
  } from './plugins';
9
9
  import type { CogsPlugin } from './plugins';
10
10
  import type { StateObject, UpdateTypeDetail } from './CogsState';
11
- import { ClientActivityState, FormEventType } from './store';
12
11
 
13
12
  const { setHookResult, removeHookResult } = pluginStore.getState();
14
13
 
@@ -118,7 +117,7 @@ const PluginInstance = React.memo(
118
117
  options,
119
118
  hookData: hookDataRef.current,
120
119
  ...deconstructed,
121
- ...scopedMetadata, // <-- Use the new scoped context
120
+ ...scopedMetadata,
122
121
  });
123
122
  }
124
123
  };
@@ -177,6 +176,7 @@ export function PluginRunner({ children }: { children: React.ReactNode }) {
177
176
  // this component will re-render to update the list of PluginInstances.
178
177
  useEffect(() => {
179
178
  const unsubscribe = pluginStore.subscribe(forceUpdate);
179
+
180
180
  return unsubscribe;
181
181
  }, []);
182
182