@noya-app/noya-multiplayer-react 0.1.12 → 0.1.14

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.
@@ -5,14 +5,14 @@
5
5
  CLI Target: esnext
6
6
  CJS Build start
7
7
  ESM Build start
8
- ESM dist/index.mjs 23.51 KB
9
- ESM dist/index.mjs.map 43.29 KB
10
- ESM ⚡️ Build success in 19ms
11
- CJS dist/index.js 27.13 KB
12
- CJS dist/index.js.map 43.09 KB
13
- CJS ⚡️ Build success in 19ms
8
+ ESM dist/index.mjs 29.45 KB
9
+ ESM dist/index.mjs.map 53.24 KB
10
+ ESM ⚡️ Build success in 71ms
11
+ CJS dist/index.js 33.48 KB
12
+ CJS dist/index.js.map 53.03 KB
13
+ CJS ⚡️ Build success in 76ms
14
14
  DTS Build start
15
15
  "ComponentPropsWithoutRef" is imported from external module "react" but never used in "src/inspector/StateInspector.tsx" and "src/hooks.ts".
16
- DTS ⚡️ Build success in 1607ms
17
- DTS dist/index.d.ts 2.50 KB
18
- DTS dist/index.d.mts 2.50 KB
16
+ DTS ⚡️ Build success in 2151ms
17
+ DTS dist/index.d.ts 2.69 KB
18
+ DTS dist/index.d.mts 2.69 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @noya-app/state-manager
2
2
 
3
+ ## 0.1.14
4
+
5
+ ### Patch Changes
6
+
7
+ - 380da63: Add more inspector tools
8
+ - Updated dependencies [380da63]
9
+ - @noya-app/state-manager@0.1.14
10
+
11
+ ## 0.1.13
12
+
13
+ ### Patch Changes
14
+
15
+ - 149f2f9: Improve multiplayer
16
+ - Updated dependencies [149f2f9]
17
+ - @noya-app/state-manager@0.1.13
18
+
3
19
  ## 0.1.12
4
20
 
5
21
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -1,18 +1,20 @@
1
1
  import * as _noya_app_state_manager from '@noya-app/state-manager';
2
- import { ConnectionEvent, MultiplayerUser, StateManager, Get, StateManagerOptions, MultiplayerStateManager, MultiplayerStateManagerOptions } from '@noya-app/state-manager';
2
+ import { ConnectionEvent, MultiplayerUser, Task, MultiplayerStateManager, StateManager, Get, StateManagerOptions, MultiplayerStateManagerOptions } from '@noya-app/state-manager';
3
3
  export * from '@noya-app/state-manager';
4
4
  import React, { SetStateAction, ComponentProps } from 'react';
5
5
 
6
- type Anchor = "left" | "right";
7
- declare const StateInspector: React.MemoExoticComponent<(<S extends object, M>({ state, connectionEvents, connectedUsers, userId, unstyled, colorScheme, stateManager, anchor, ...props }: {
6
+ type Anchor = "left" | "right" | "bottom left" | "bottom right";
7
+ declare const StateInspector: React.MemoExoticComponent<(<S extends object, M>({ state, connectionEvents, connectedUsers, tasks, userId, unstyled, colorScheme, multiplayerStateManager, anchor, forceInit, ...props }: {
8
8
  state: S;
9
9
  connectionEvents?: ConnectionEvent<S>[] | undefined;
10
10
  connectedUsers?: MultiplayerUser[] | undefined;
11
+ tasks?: Task[] | undefined;
11
12
  userId?: string | undefined;
12
13
  unstyled?: boolean | undefined;
13
14
  colorScheme?: "light" | "dark" | undefined;
14
- stateManager: StateManager<S, M>;
15
+ multiplayerStateManager: MultiplayerStateManager<S, M>;
15
16
  anchor?: Anchor | undefined;
17
+ forceInit: () => void;
16
18
  } & Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">) => React.JSX.Element | null)>;
17
19
 
18
20
  declare function useSyncStateManager<S extends object, M>(stateManager: StateManager<S, M>): S;
@@ -20,13 +22,15 @@ declare function useSyncStateManager<S extends object, M, P extends string>(stat
20
22
  declare function useManagedState<S extends object, M = void>(createInitialState: () => S, options?: StateManagerOptions<S, M>): readonly [S, (...args: M extends {} ? [metadata: M, action: SetStateAction<S>] : [action: SetStateAction<S>]) => void, StateManager<S, M>];
21
23
  declare function useManagedHistory<S extends object, M = void>(stateManager: StateManager<S, M>): _noya_app_state_manager.HistorySnapshot<S, M>;
22
24
  declare function useSyncMultiplayerStateManager<S extends object, M = void>(multiplayerStateManager: MultiplayerStateManager<S, M>): S;
23
- declare function useMultiplayerState<S extends object, M = void>(initialState: S | (() => S), options: MultiplayerStateManagerOptions<S, M> & {
25
+ declare function useMultiplayerState<S extends object, M = void>(initialState: S | (() => S), options?: MultiplayerStateManagerOptions<S, M> & {
24
26
  sync?: (options: {
25
27
  ms: MultiplayerStateManager<S, M>;
26
28
  }) => void;
27
29
  trackConnectionEvents?: boolean;
30
+ trackTasks?: boolean;
28
31
  inspector?: boolean | Pick<ComponentProps<typeof StateInspector>, "colorScheme" | "anchor">;
29
32
  }): readonly [S, (...args: M extends {} ? [metadata: M, action: S | ((prevState: S) => S)] : [action: S | ((prevState: S) => S)]) => void, MultiplayerStateManager<S, M>, {
33
+ tasks: Task[];
30
34
  connectionEvents: ConnectionEvent<S>[] | undefined;
31
35
  connectedUsers: MultiplayerUser[];
32
36
  }];
package/dist/index.d.ts CHANGED
@@ -1,18 +1,20 @@
1
1
  import * as _noya_app_state_manager from '@noya-app/state-manager';
2
- import { ConnectionEvent, MultiplayerUser, StateManager, Get, StateManagerOptions, MultiplayerStateManager, MultiplayerStateManagerOptions } from '@noya-app/state-manager';
2
+ import { ConnectionEvent, MultiplayerUser, Task, MultiplayerStateManager, StateManager, Get, StateManagerOptions, MultiplayerStateManagerOptions } from '@noya-app/state-manager';
3
3
  export * from '@noya-app/state-manager';
4
4
  import React, { SetStateAction, ComponentProps } from 'react';
5
5
 
6
- type Anchor = "left" | "right";
7
- declare const StateInspector: React.MemoExoticComponent<(<S extends object, M>({ state, connectionEvents, connectedUsers, userId, unstyled, colorScheme, stateManager, anchor, ...props }: {
6
+ type Anchor = "left" | "right" | "bottom left" | "bottom right";
7
+ declare const StateInspector: React.MemoExoticComponent<(<S extends object, M>({ state, connectionEvents, connectedUsers, tasks, userId, unstyled, colorScheme, multiplayerStateManager, anchor, forceInit, ...props }: {
8
8
  state: S;
9
9
  connectionEvents?: ConnectionEvent<S>[] | undefined;
10
10
  connectedUsers?: MultiplayerUser[] | undefined;
11
+ tasks?: Task[] | undefined;
11
12
  userId?: string | undefined;
12
13
  unstyled?: boolean | undefined;
13
14
  colorScheme?: "light" | "dark" | undefined;
14
- stateManager: StateManager<S, M>;
15
+ multiplayerStateManager: MultiplayerStateManager<S, M>;
15
16
  anchor?: Anchor | undefined;
17
+ forceInit: () => void;
16
18
  } & Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">) => React.JSX.Element | null)>;
17
19
 
18
20
  declare function useSyncStateManager<S extends object, M>(stateManager: StateManager<S, M>): S;
@@ -20,13 +22,15 @@ declare function useSyncStateManager<S extends object, M, P extends string>(stat
20
22
  declare function useManagedState<S extends object, M = void>(createInitialState: () => S, options?: StateManagerOptions<S, M>): readonly [S, (...args: M extends {} ? [metadata: M, action: SetStateAction<S>] : [action: SetStateAction<S>]) => void, StateManager<S, M>];
21
23
  declare function useManagedHistory<S extends object, M = void>(stateManager: StateManager<S, M>): _noya_app_state_manager.HistorySnapshot<S, M>;
22
24
  declare function useSyncMultiplayerStateManager<S extends object, M = void>(multiplayerStateManager: MultiplayerStateManager<S, M>): S;
23
- declare function useMultiplayerState<S extends object, M = void>(initialState: S | (() => S), options: MultiplayerStateManagerOptions<S, M> & {
25
+ declare function useMultiplayerState<S extends object, M = void>(initialState: S | (() => S), options?: MultiplayerStateManagerOptions<S, M> & {
24
26
  sync?: (options: {
25
27
  ms: MultiplayerStateManager<S, M>;
26
28
  }) => void;
27
29
  trackConnectionEvents?: boolean;
30
+ trackTasks?: boolean;
28
31
  inspector?: boolean | Pick<ComponentProps<typeof StateInspector>, "colorScheme" | "anchor">;
29
32
  }): readonly [S, (...args: M extends {} ? [metadata: M, action: S | ((prevState: S) => S)] : [action: S | ((prevState: S) => S)]) => void, MultiplayerStateManager<S, M>, {
33
+ tasks: Task[];
30
34
  connectionEvents: ConnectionEvent<S>[] | undefined;
31
35
  connectedUsers: MultiplayerUser[];
32
36
  }];
package/dist/index.js CHANGED
@@ -75,7 +75,8 @@ function useObservable(observable) {
75
75
  }, [observable]);
76
76
  return value;
77
77
  }
78
- var trackEvents = new Observable(false);
78
+ var shouldTrackEvents$ = new Observable(false);
79
+ var shouldTrackTasks$ = new Observable(false);
79
80
 
80
81
  // src/inspector/useStateInspector.tsx
81
82
  var import_react4 = __toESM(require("react"));
@@ -241,7 +242,7 @@ function DisclosureSection({
241
242
  }
242
243
  }
243
244
  ),
244
- /* @__PURE__ */ import_react3.default.createElement("span", { style: { flex: "1 1 0" } }, title),
245
+ /* @__PURE__ */ import_react3.default.createElement("span", { style: { flex: "1 1 0", userSelect: "none" } }, title),
245
246
  right
246
247
  ),
247
248
  open && children
@@ -292,11 +293,13 @@ var StateInspector = (0, import_react3.memo)(function StateInspector2({
292
293
  state,
293
294
  connectionEvents,
294
295
  connectedUsers,
296
+ tasks,
295
297
  userId,
296
298
  unstyled,
297
299
  colorScheme = "light",
298
- stateManager,
300
+ multiplayerStateManager,
299
301
  anchor = "right",
302
+ forceInit,
300
303
  ...props
301
304
  }) {
302
305
  const [didMount, setDidMount] = import_react3.default.useState(false);
@@ -325,15 +328,22 @@ var StateInspector = (0, import_react3.memo)(function StateInspector2({
325
328
  "noya-multiplayer-react-show-data",
326
329
  true
327
330
  );
331
+ const [showTasks, setShowTasks] = useLocalStorageState(
332
+ "noya-multiplayer-react-show-tasks",
333
+ false
334
+ );
328
335
  (0, import_react3.useEffect)(() => {
329
- trackEvents.set(showEvents);
336
+ shouldTrackEvents$.set(showEvents);
330
337
  }, [showEvents]);
338
+ (0, import_react3.useEffect)(() => {
339
+ shouldTrackTasks$.set(showTasks);
340
+ }, [showTasks]);
331
341
  (0, import_react3.useEffect)(() => {
332
342
  if (eventsContainerRef.current) {
333
343
  eventsContainerRef.current.scrollTop = eventsContainerRef.current.scrollHeight;
334
344
  }
335
345
  }, [connectionEvents]);
336
- const historySnapshot = useManagedHistory(stateManager);
346
+ const historySnapshot = useManagedHistory(multiplayerStateManager.sm);
337
347
  (0, import_react3.useEffect)(() => {
338
348
  if (historyContainerRef.current) {
339
349
  historyContainerRef.current.scrollTop = historyContainerRef.current.scrollHeight;
@@ -357,7 +367,7 @@ var StateInspector = (0, import_react3.memo)(function StateInspector2({
357
367
  const baseStyle = {
358
368
  position: "fixed",
359
369
  top: 12,
360
- ...anchor === "right" ? { right: 12 } : { left: 12 },
370
+ ...anchor.includes("right") ? { right: 12 } : { left: 12 },
361
371
  bottom: 12,
362
372
  width: 400,
363
373
  background: colorScheme === "light" ? "rgba(255,255,255,0.95)" : "rgba(0,0,0,0.95)",
@@ -383,8 +393,8 @@ var StateInspector = (0, import_react3.memo)(function StateInspector2({
383
393
  style: {
384
394
  ...baseStyle,
385
395
  padding: "4px 10px",
386
- bottom: void 0,
387
- width: void 0
396
+ width: void 0,
397
+ ...anchor.includes("bottom") ? { bottom: 12, top: void 0 } : { bottom: void 0 }
388
398
  },
389
399
  onClick: () => setShowInspector(true)
390
400
  },
@@ -461,19 +471,42 @@ var StateInspector = (0, import_react3.memo)(function StateInspector2({
461
471
  title: "Data",
462
472
  colorScheme,
463
473
  setOpen: setShowData,
464
- open: showData
474
+ open: showData,
475
+ right: /* @__PURE__ */ import_react3.default.createElement(
476
+ "span",
477
+ {
478
+ style: {
479
+ display: "flex",
480
+ gap: "4px"
481
+ }
482
+ },
483
+ /* @__PURE__ */ import_react3.default.createElement(
484
+ SmallButton,
485
+ {
486
+ theme,
487
+ onClick: () => {
488
+ forceInit();
489
+ }
490
+ },
491
+ "Reset"
492
+ )
493
+ )
465
494
  },
466
- /* @__PURE__ */ import_react3.default.createElement(
467
- "div",
495
+ /* @__PURE__ */ import_react3.default.createElement("div", { style: styles.sectionInner }, /* @__PURE__ */ import_react3.default.createElement(InspectorRow, { colorScheme }, /* @__PURE__ */ import_react3.default.createElement(
496
+ import_react_inspector.ObjectInspector,
468
497
  {
469
- style: {
470
- ...styles.sectionInner,
471
- gap: "1px",
472
- padding: "1px 12px"
473
- }
474
- },
475
- /* @__PURE__ */ import_react3.default.createElement(import_react_inspector.ObjectInspector, { data: state, theme })
476
- )
498
+ name: multiplayerStateManager.sm.schema ? "state" : void 0,
499
+ data: state,
500
+ theme
501
+ }
502
+ )), multiplayerStateManager.sm.schema && /* @__PURE__ */ import_react3.default.createElement(InspectorRow, { colorScheme }, /* @__PURE__ */ import_react3.default.createElement(
503
+ import_react_inspector.ObjectInspector,
504
+ {
505
+ name: "schema",
506
+ data: multiplayerStateManager.sm.schema,
507
+ theme
508
+ }
509
+ )))
477
510
  ),
478
511
  /* @__PURE__ */ import_react3.default.createElement(
479
512
  DisclosureSection,
@@ -481,7 +514,68 @@ var StateInspector = (0, import_react3.memo)(function StateInspector2({
481
514
  open: showHistory,
482
515
  setOpen: setShowHistory,
483
516
  title: "History",
484
- colorScheme
517
+ colorScheme,
518
+ right: /* @__PURE__ */ import_react3.default.createElement(
519
+ "span",
520
+ {
521
+ style: {
522
+ display: "flex",
523
+ gap: "4px"
524
+ }
525
+ },
526
+ /* @__PURE__ */ import_react3.default.createElement(
527
+ SmallButton,
528
+ {
529
+ disabled: !historySnapshot.canUndo,
530
+ theme,
531
+ onClick: () => {
532
+ multiplayerStateManager.sm.undo();
533
+ }
534
+ },
535
+ /* @__PURE__ */ import_react3.default.createElement("span", { style: { width: "12px", height: "12px" } }, /* @__PURE__ */ import_react3.default.createElement(
536
+ "svg",
537
+ {
538
+ xmlns: "http://www.w3.org/2000/svg",
539
+ width: "1em",
540
+ height: "1em",
541
+ viewBox: "0 0 20 20"
542
+ },
543
+ /* @__PURE__ */ import_react3.default.createElement(
544
+ "path",
545
+ {
546
+ fill: "currentColor",
547
+ d: "M12 5H7V2L1 6l6 4V7h5c2.2 0 4 1.8 4 4s-1.8 4-4 4H7v2h5c3.3 0 6-2.7 6-6s-2.7-6-6-6"
548
+ }
549
+ )
550
+ ))
551
+ ),
552
+ /* @__PURE__ */ import_react3.default.createElement(
553
+ SmallButton,
554
+ {
555
+ disabled: !historySnapshot.canRedo,
556
+ theme,
557
+ onClick: () => {
558
+ multiplayerStateManager.sm.redo();
559
+ }
560
+ },
561
+ /* @__PURE__ */ import_react3.default.createElement("span", { style: { width: "12px", height: "12px" } }, /* @__PURE__ */ import_react3.default.createElement(
562
+ "svg",
563
+ {
564
+ xmlns: "http://www.w3.org/2000/svg",
565
+ width: "1em",
566
+ height: "1em",
567
+ viewBox: "0 0 20 20"
568
+ },
569
+ /* @__PURE__ */ import_react3.default.createElement(
570
+ "path",
571
+ {
572
+ fill: "currentColor",
573
+ d: "M8 5h5V2l6 4l-6 4V7H8c-2.2 0-4 1.8-4 4s1.8 4 4 4h5v2H8c-3.3 0-6-2.7-6-6s2.7-6 6-6"
574
+ }
575
+ )
576
+ ))
577
+ )
578
+ )
485
579
  },
486
580
  /* @__PURE__ */ import_react3.default.createElement("div", { ref: historyContainerRef, style: styles.sectionInner }, /* @__PURE__ */ import_react3.default.createElement(
487
581
  "div",
@@ -536,6 +630,33 @@ var StateInspector = (0, import_react3.memo)(function StateInspector2({
536
630
  ))
537
631
  )))
538
632
  ),
633
+ /* @__PURE__ */ import_react3.default.createElement(
634
+ DisclosureSection,
635
+ {
636
+ title: "Tasks",
637
+ colorScheme,
638
+ open: showTasks,
639
+ setOpen: setShowTasks
640
+ },
641
+ /* @__PURE__ */ import_react3.default.createElement("div", { style: styles.sectionInner }, tasks?.map((task) => /* @__PURE__ */ import_react3.default.createElement(
642
+ InspectorRow,
643
+ {
644
+ key: task.id,
645
+ colorScheme,
646
+ style: {
647
+ backgroundColor: task.status === "done" ? "rgba(0,255,0,0.2)" : task.status === "error" ? "rgba(255,0,0,0.2)" : void 0
648
+ }
649
+ },
650
+ /* @__PURE__ */ import_react3.default.createElement(
651
+ import_react_inspector.ObjectInspector,
652
+ {
653
+ name: task.name,
654
+ data: task.payload,
655
+ theme
656
+ }
657
+ )
658
+ )))
659
+ ),
539
660
  /* @__PURE__ */ import_react3.default.createElement(
540
661
  DisclosureSection,
541
662
  {
@@ -666,6 +787,42 @@ function ellipsis(str, maxLength, position = "end") {
666
787
  return `${str.slice(0, maxLength)}\u2026`;
667
788
  }
668
789
  }
790
+ function SmallButton({
791
+ children,
792
+ onClick,
793
+ style,
794
+ theme,
795
+ disabled
796
+ }) {
797
+ return /* @__PURE__ */ import_react3.default.createElement(
798
+ "button",
799
+ {
800
+ type: "button",
801
+ disabled,
802
+ onClick: (event) => {
803
+ event.stopPropagation();
804
+ onClick();
805
+ },
806
+ style: {
807
+ flex: "0",
808
+ appearance: "none",
809
+ background: "none",
810
+ color: theme.BASE_COLOR,
811
+ opacity: disabled ? 0.25 : 1,
812
+ border: "none",
813
+ fontSize: "12px",
814
+ whiteSpace: "nowrap",
815
+ display: "inline-flex",
816
+ alignItems: "center",
817
+ justifyContent: "center",
818
+ padding: "2px 0",
819
+ cursor: "pointer",
820
+ ...style
821
+ }
822
+ },
823
+ children
824
+ );
825
+ }
669
826
 
670
827
  // src/inspector/useStateInspector.tsx
671
828
  function createPortalElement() {
@@ -677,11 +834,13 @@ function useStateInspector({
677
834
  state,
678
835
  connectionEvents,
679
836
  connectedUsers,
837
+ tasks,
680
838
  userId,
681
839
  disabled = false,
682
840
  colorScheme,
683
841
  anchor,
684
- stateManager
842
+ multiplayerStateManager,
843
+ forceInit
685
844
  }) {
686
845
  const [root, setRoot] = import_react4.default.useState(null);
687
846
  (0, import_react4.useLayoutEffect)(() => {
@@ -705,10 +864,12 @@ function useStateInspector({
705
864
  state,
706
865
  connectionEvents,
707
866
  connectedUsers,
867
+ tasks,
708
868
  userId,
709
869
  colorScheme,
710
870
  anchor,
711
- stateManager
871
+ forceInit,
872
+ multiplayerStateManager
712
873
  }
713
874
  )
714
875
  );
@@ -719,8 +880,10 @@ function useStateInspector({
719
880
  connectionEvents,
720
881
  root,
721
882
  state,
722
- stateManager,
723
- userId
883
+ multiplayerStateManager,
884
+ userId,
885
+ tasks,
886
+ forceInit
724
887
  ]);
725
888
  }
726
889
 
@@ -792,58 +955,87 @@ function useSyncMultiplayerStateManager(multiplayerStateManager) {
792
955
  }
793
956
  var defaultStubSync = (0, import_state_manager.stubSync)();
794
957
  function useMultiplayerState(initialState, options) {
795
- const { sync = defaultStubSync, ...rest } = options;
958
+ const { sync = defaultStubSync, ...rest } = options ?? {};
796
959
  const [multiplayerStateManager] = (0, import_react5.useState)(
797
960
  () => new import_state_manager.MultiplayerStateManager(initialState, rest)
798
961
  );
799
962
  const syncRef = (0, import_react5.useRef)(sync);
800
963
  const [connectionEvents, setConnectionEvents] = (0, import_react5.useState)();
801
964
  const [connectedUsers, setConnectedUsers] = (0, import_react5.useState)([]);
965
+ const [tasks, setTasks] = (0, import_react5.useState)([]);
802
966
  const [userId, setUserId] = (0, import_react5.useState)();
803
967
  const extras = (0, import_react5.useMemo)(() => {
804
968
  return {
969
+ tasks,
805
970
  connectionEvents,
806
971
  connectedUsers
972
+ // evaluate: async (code: string) => {
973
+ // const payload = await rpcManager.request({ type: "eval", code });
974
+ // if (payload.type !== "eval") {
975
+ // throw new Error(`Unexpected rpc response type: ${payload.type}`);
976
+ // }
977
+ // },
807
978
  };
808
- }, [connectionEvents, connectedUsers]);
809
- const globalTrackEvents = useObservable(trackEvents);
810
- const trackConnectionEvents = options.trackConnectionEvents ?? globalTrackEvents;
979
+ }, [tasks, connectionEvents, connectedUsers]);
980
+ const globalTrackEvents = useObservable(shouldTrackEvents$);
981
+ const globalTrackTasks = useObservable(shouldTrackTasks$);
982
+ const shouldTrackEvents = options?.trackConnectionEvents ?? globalTrackEvents;
983
+ const shouldTrackTasks = options?.trackTasks ?? globalTrackTasks;
811
984
  const trackEventsCallbackRef = (0, import_react5.useRef)();
812
985
  const trackConnectedUsersCallbackRef = (0, import_react5.useRef)();
986
+ const trackTasksCallbackRef = (0, import_react5.useRef)();
813
987
  (0, import_react5.useEffect)(() => {
814
- trackEventsCallbackRef.current = trackConnectionEvents ? (e) => setConnectionEvents((events) => {
988
+ trackEventsCallbackRef.current = shouldTrackEvents ? (e) => setConnectionEvents((events) => {
815
989
  const updated = events ? [...events, e] : [e];
816
990
  return updated.length > 50 ? updated.slice(-50) : updated;
817
991
  }) : void 0;
818
- }, [trackConnectionEvents]);
992
+ }, [shouldTrackEvents]);
819
993
  (0, import_react5.useEffect)(() => {
820
994
  trackConnectedUsersCallbackRef.current = (users, userId2) => {
821
995
  setConnectedUsers(users);
822
996
  setUserId(userId2);
823
997
  };
824
- });
998
+ }, []);
999
+ (0, import_react5.useEffect)(() => {
1000
+ trackTasksCallbackRef.current = shouldTrackTasks ? (tasks2) => {
1001
+ setTasks((old) => {
1002
+ const newIds = new Set(tasks2.map((t) => t.id));
1003
+ const unchanged = old.filter((t) => !newIds.has(t.id));
1004
+ return [...unchanged, ...tasks2];
1005
+ });
1006
+ } : void 0;
1007
+ }, [shouldTrackTasks]);
825
1008
  (0, import_react5.useEffect)(() => {
826
1009
  if (syncRef.current) {
827
1010
  return syncRef.current({
828
1011
  ms: multiplayerStateManager,
829
1012
  onConnectionEvent: (e) => trackEventsCallbackRef.current?.(e),
830
- onChangeConnectedUsers: (users, userId2) => trackConnectedUsersCallbackRef.current?.(users, userId2)
1013
+ onChangeConnectedUsers: (users, userId2) => trackConnectedUsersCallbackRef.current?.(users, userId2),
1014
+ onChangeTasks: (tasks2) => trackTasksCallbackRef.current?.(tasks2)
831
1015
  });
832
1016
  }
833
1017
  }, [multiplayerStateManager]);
834
1018
  const state = useSyncMultiplayerStateManager(multiplayerStateManager);
1019
+ const forceInit = (0, import_react5.useCallback)(() => {
1020
+ const state2 = typeof initialState === "function" ? initialState() : initialState;
1021
+ const schema = options?.schema;
1022
+ multiplayerStateManager.sm.clearHistory();
1023
+ multiplayerStateManager.sendInit({ force: true, state: state2, schema });
1024
+ }, [initialState, multiplayerStateManager, options?.schema]);
835
1025
  useStateInspector({
836
- stateManager: multiplayerStateManager.sm,
1026
+ multiplayerStateManager,
837
1027
  state,
838
1028
  connectionEvents,
839
1029
  connectedUsers,
1030
+ tasks,
840
1031
  userId,
841
- disabled: !options.inspector,
842
- ...typeof options.inspector === "object" && options.inspector
1032
+ forceInit,
1033
+ disabled: !options?.inspector,
1034
+ ...typeof options?.inspector === "object" && options.inspector
843
1035
  });
844
1036
  return [
845
1037
  state,
846
- multiplayerStateManager.setOptimisticState,
1038
+ multiplayerStateManager.setState,
847
1039
  multiplayerStateManager,
848
1040
  extras
849
1041
  ];