@tanstack/pacer-devtools 1.1.5 → 1.2.0

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.
@@ -1,8 +1,8 @@
1
- import { createComponent, escape, ssr, ssrAttribute, ssrStyleProperty } from "solid-js/web";
1
+ import { createComponent, escape, ssr, ssrAttribute, ssrStyle, ssrStyleProperty } from "solid-js/web";
2
2
  import { Header, HeaderLogo, JsonTree, MainPanel, ThemeContextProvider, useTheme } from "@tanstack/devtools-ui";
3
- import { createStore } from "solid-js/store";
4
- import { For, createContext, createEffect, createMemo, createSignal, onCleanup, onMount, useContext } from "solid-js";
5
- import { pacerEventClient } from "@tanstack/pacer/event-client";
3
+ import { createStore, produce } from "solid-js/store";
4
+ import { For, Show, createContext, createEffect, createMemo, createSignal, onCleanup, onMount, useContext } from "solid-js";
5
+ import { getPacerDevtoolsInstance, pacerEventClient } from "@tanstack/pacer/event-client";
6
6
  import * as goober from "goober";
7
7
  import { useStore } from "@tanstack/solid-store";
8
8
  import clsx from "clsx";
@@ -23,109 +23,78 @@ const initialPacerDevtoolsStore = {
23
23
  lastUpdatedByKey: {}
24
24
  };
25
25
  const PacerDevtoolsContext = createContext([initialPacerDevtoolsStore, () => {}]);
26
- const updateOrAddToArray = (oldArray, newItem) => {
27
- const index = oldArray.findIndex((item) => item.key === newItem.key);
28
- if (index !== -1) return oldArray.map((item, i) => i === index ? newItem : item);
29
- return [...oldArray, newItem];
30
- };
26
+ /**
27
+ * Match TanStack Form devtools: subscribe with {@link pacerEventClient.on} per
28
+ * event suffix so the same `pacer:${suffix}` channel the library emits on is
29
+ * observed. `onAllPluginEvents` only sees `tanstack-devtools-global`, which is
30
+ * not always relayed the same way by the shell.
31
+ *
32
+ * `d-*` suffixes are used when the devtools panel pushes state back (see
33
+ * ActionButtons); those must update the panel store too.
34
+ */
35
+ const PACER_DEVTOOLS_UTIL_EVENTS = [
36
+ {
37
+ listKey: "asyncBatchers",
38
+ suffixes: ["AsyncBatcher", "d-AsyncBatcher"]
39
+ },
40
+ {
41
+ listKey: "asyncDebouncers",
42
+ suffixes: ["AsyncDebouncer", "d-AsyncDebouncer"]
43
+ },
44
+ {
45
+ listKey: "asyncQueuers",
46
+ suffixes: ["AsyncQueuer", "d-AsyncQueuer"]
47
+ },
48
+ {
49
+ listKey: "asyncRateLimiters",
50
+ suffixes: ["AsyncRateLimiter", "d-AsyncRateLimiter"]
51
+ },
52
+ {
53
+ listKey: "asyncThrottlers",
54
+ suffixes: ["AsyncThrottler", "d-AsyncThrottler"]
55
+ },
56
+ {
57
+ listKey: "batchers",
58
+ suffixes: ["Batcher", "d-Batcher"]
59
+ },
60
+ {
61
+ listKey: "debouncers",
62
+ suffixes: ["Debouncer", "d-Debouncer"]
63
+ },
64
+ {
65
+ listKey: "queuers",
66
+ suffixes: ["Queuer", "d-Queuer"]
67
+ },
68
+ {
69
+ listKey: "rateLimiters",
70
+ suffixes: ["RateLimiter", "d-RateLimiter"]
71
+ },
72
+ {
73
+ listKey: "throttlers",
74
+ suffixes: ["Throttler", "d-Throttler"]
75
+ }
76
+ ];
31
77
  function PacerContextProvider(props) {
32
78
  const [store, setStore] = createStore(initialPacerDevtoolsStore);
33
79
  createEffect(() => {
34
- onCleanup(pacerEventClient.onAllPluginEvents((_e) => {
35
- const e = _e;
36
- switch (e.type) {
37
- case "pacer:AsyncBatcher":
38
- setStore({
39
- asyncBatchers: updateOrAddToArray(store.asyncBatchers, e.payload),
40
- lastUpdatedByKey: {
41
- ...store.lastUpdatedByKey,
42
- [e.payload.key]: Date.now()
43
- }
44
- });
45
- break;
46
- case "pacer:AsyncDebouncer":
47
- setStore({
48
- asyncDebouncers: updateOrAddToArray(store.asyncDebouncers, e.payload),
49
- lastUpdatedByKey: {
50
- ...store.lastUpdatedByKey,
51
- [e.payload.key]: Date.now()
52
- }
53
- });
54
- break;
55
- case "pacer:AsyncQueuer":
56
- setStore({
57
- asyncQueuers: updateOrAddToArray(store.asyncQueuers, e.payload),
58
- lastUpdatedByKey: {
59
- ...store.lastUpdatedByKey,
60
- [e.payload.key]: Date.now()
61
- }
62
- });
63
- break;
64
- case "pacer:AsyncRateLimiter":
65
- setStore({
66
- asyncRateLimiters: updateOrAddToArray(store.asyncRateLimiters, e.payload),
67
- lastUpdatedByKey: {
68
- ...store.lastUpdatedByKey,
69
- [e.payload.key]: Date.now()
70
- }
71
- });
72
- break;
73
- case "pacer:AsyncThrottler":
74
- setStore({
75
- asyncThrottlers: updateOrAddToArray(store.asyncThrottlers, e.payload),
76
- lastUpdatedByKey: {
77
- ...store.lastUpdatedByKey,
78
- [e.payload.key]: Date.now()
79
- }
80
- });
81
- break;
82
- case "pacer:Batcher":
83
- setStore({
84
- batchers: updateOrAddToArray(store.batchers, e.payload),
85
- lastUpdatedByKey: {
86
- ...store.lastUpdatedByKey,
87
- [e.payload.key]: Date.now()
88
- }
89
- });
90
- break;
91
- case "pacer:Debouncer":
92
- setStore({
93
- debouncers: updateOrAddToArray(store.debouncers, e.payload),
94
- lastUpdatedByKey: {
95
- ...store.lastUpdatedByKey,
96
- [e.payload.key]: Date.now()
97
- }
98
- });
99
- break;
100
- case "pacer:Queuer":
101
- setStore({
102
- queuers: updateOrAddToArray(store.queuers, e.payload),
103
- lastUpdatedByKey: {
104
- ...store.lastUpdatedByKey,
105
- [e.payload.key]: Date.now()
106
- }
107
- });
108
- break;
109
- case "pacer:RateLimiter":
110
- setStore({
111
- rateLimiters: updateOrAddToArray(store.rateLimiters, e.payload),
112
- lastUpdatedByKey: {
113
- ...store.lastUpdatedByKey,
114
- [e.payload.key]: Date.now()
115
- }
116
- });
117
- break;
118
- case "pacer:Throttler":
119
- setStore({
120
- throttlers: updateOrAddToArray(store.throttlers, e.payload),
121
- lastUpdatedByKey: {
122
- ...store.lastUpdatedByKey,
123
- [e.payload.key]: Date.now()
124
- }
125
- });
126
- break;
127
- }
80
+ const cleanups = [];
81
+ for (const { listKey, suffixes } of PACER_DEVTOOLS_UTIL_EVENTS) for (const suffix of suffixes) cleanups.push(pacerEventClient.on(suffix, (e) => {
82
+ const key = e.payload.key;
83
+ if (!key) return;
84
+ const instance = getPacerDevtoolsInstance(key);
85
+ if (!instance || typeof instance !== "object") return;
86
+ setStore(produce((draft) => {
87
+ const list = draft[listKey];
88
+ const index = list.findIndex((item) => item.key === key);
89
+ const inst = instance;
90
+ if (index !== -1) list[index] = inst;
91
+ else list.push(inst);
92
+ draft.lastUpdatedByKey[key] = Date.now();
93
+ }));
128
94
  }));
95
+ onCleanup(() => {
96
+ for (const cleanup of cleanups) cleanup();
97
+ });
129
98
  });
130
99
  return createComponent(PacerDevtoolsContext.Provider, {
131
100
  value: [store, setStore],
@@ -447,9 +416,25 @@ const stylesFactory = (theme) => {
447
416
  const css = goober.css;
448
417
  const t = (light, dark) => theme === "light" ? light : dark;
449
418
  return {
419
+ shellRoot: css`
420
+ display: flex;
421
+ flex-direction: column;
422
+ box-sizing: border-box;
423
+ width: 100%;
424
+ height: 100%;
425
+ min-height: 0;
426
+ overflow: hidden;
427
+ `,
428
+ mainPanelShell: css`
429
+ display: flex;
430
+ flex-direction: column;
431
+ flex: 1 1 0%;
432
+ min-height: 0;
433
+ overflow: hidden !important;
434
+ `,
450
435
  mainContainer: css`
451
436
  display: flex;
452
- flex: 1;
437
+ flex: 1 1 0%;
453
438
  min-height: 0;
454
439
  overflow: hidden;
455
440
  padding: ${size[2]};
@@ -855,7 +840,7 @@ var _tmpl$$4 = [
855
840
  "<div",
856
841
  ">",
857
842
  "</div>"
858
- ], _tmpl$2$2 = [
843
+ ], _tmpl$2$3 = [
859
844
  "<div",
860
845
  "><div",
861
846
  ">",
@@ -873,7 +858,7 @@ function UtilList(props) {
873
858
  const styles = useStyles();
874
859
  return ssr(_tmpl$$4, ssrAttribute("class", escape(styles().utilList, true), false), escape(createComponent(For, {
875
860
  each: UTIL_GROUPS,
876
- children: (group) => props.utilState()[group.key].length > 0 && ssr(_tmpl$2$2, ssrAttribute("class", escape(styles().utilGroup, true), false), ssrAttribute("class", escape(styles().utilGroupHeader, true), false), escape(group.label), escape(createComponent(For, {
861
+ children: (group) => props.utilState()[group.key].length > 0 && ssr(_tmpl$2$3, ssrAttribute("class", escape(styles().utilGroup, true), false), ssrAttribute("class", escape(styles().utilGroupHeader, true), false), escape(group.label), escape(createComponent(For, {
877
862
  get each() {
878
863
  return props.utilState()[group.key];
879
864
  },
@@ -892,8 +877,25 @@ function UtilList(props) {
892
877
  })));
893
878
  }
894
879
  //#endregion
880
+ //#region src/utils/read-pacer-store-state.ts
881
+ /** TanStack Store (`@tanstack/store`) used by pacer utils. */
882
+ function isPacerUtilTanStackStore(x) {
883
+ return typeof x === "object" && x !== null && "subscribe" in x && typeof x.subscribe === "function" && "get" in x && typeof x.get === "function";
884
+ }
885
+ /**
886
+ * Core pacer utils expose TanStack `Store` (`get()` / `state`). React hooks may
887
+ * also expose a reactive `state` field. Use this for devtools UI that needs a
888
+ * plain snapshot (e.g. JsonTree).
889
+ */
890
+ function getPacerUtilStoreState(instance) {
891
+ const store = instance.store;
892
+ if (store && typeof store.get === "function") return store.get();
893
+ if (store && store.state !== void 0) return store.state;
894
+ return instance.state;
895
+ }
896
+ //#endregion
895
897
  //#region src/components/ActionButtons.tsx
896
- var _tmpl$$3 = ["<div", ">No actions available for this util</div>"], _tmpl$2$1 = [
898
+ var _tmpl$$3 = ["<div", ">No actions available for this util</div>"], _tmpl$2$2 = [
897
899
  "<div",
898
900
  ">",
899
901
  "",
@@ -930,11 +932,20 @@ var _tmpl$$3 = ["<div", ">No actions available for this util</div>"], _tmpl$2$1
930
932
  function ActionButtons(props) {
931
933
  const styles = useStyles();
932
934
  const utilInstance = props.instance;
933
- const utilState = utilInstance?.store?.state;
934
- const hasPending = utilState && "isPending" in utilState;
935
- const hasEmpty = utilState && "isEmpty" in utilState;
936
- const isPending = hasPending ? !!utilState.isPending : false;
937
- const isEmpty = hasEmpty ? utilState.isEmpty : void 0;
935
+ const store = utilInstance?.store;
936
+ const stateAccessor = isPacerUtilTanStackStore(store) ? useStore(store, (s) => s !== null && s !== void 0 && typeof s === "object" ? s : {}) : () => getPacerUtilStoreState(utilInstance);
937
+ const getState = () => stateAccessor();
938
+ const hasPending = () => {
939
+ return "isPending" in getState();
940
+ };
941
+ const isPending = () => {
942
+ const u = getState();
943
+ return "isPending" in u ? !!u.isPending : false;
944
+ };
945
+ const isEmptyFlag = () => {
946
+ const u = getState();
947
+ return "isEmpty" in u ? !!u.isEmpty : false;
948
+ };
938
949
  const hasFlush = typeof utilInstance.flush === "function";
939
950
  const hasCancel = typeof utilInstance.cancel === "function";
940
951
  const hasReset = typeof utilInstance.reset === "function";
@@ -942,10 +953,14 @@ function ActionButtons(props) {
942
953
  const hasStart = typeof utilInstance.start === "function";
943
954
  const hasStop = typeof utilInstance.stop === "function";
944
955
  const hasStartStop = hasStart && hasStop;
945
- const isRunning = utilState?.isRunning ?? true;
946
- if (!hasPending && !hasFlush && !hasCancel && !hasReset && !hasClear) return ssr(_tmpl$$3, ssrAttribute("class", escape(styles().sectionEmpty, true), false));
956
+ const isRunning = () => {
957
+ const u = getState();
958
+ return "isRunning" in u ? !!u.isRunning : true;
959
+ };
960
+ if (!hasPending() && !hasFlush && !hasCancel && !hasReset && !hasClear) return ssr(_tmpl$$3, ssrAttribute("class", escape(styles().sectionEmpty, true), false));
947
961
  `${props.utilName}`;
948
- return ssr(_tmpl$2$1, ssrAttribute("class", escape(styles().actionsRow, true), false), hasPending && ssr(_tmpl$3$1, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("class", escape(styles().actionDotBlue, true), false), isPending ? "Restore Pending" : "Trigger Pending"), hasFlush && ssr(_tmpl$4, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("disabled", props.utilName.toLowerCase().includes("debouncer") && !isPending || props.utilName.toLowerCase().includes("throttler") && !isPending || props.utilName.toLowerCase().includes("batcher") && isEmpty || props.utilName.toLowerCase().includes("queuer") && isEmpty, true), ssrAttribute("class", escape(styles().actionDotGreen, true), false)), hasCancel && ssr(_tmpl$5, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("disabled", props.utilName.toLowerCase().includes("debouncer") && !isPending || props.utilName.toLowerCase().includes("throttler") && !isPending || props.utilName.toLowerCase().includes("batcher") && isEmpty || props.utilName.toLowerCase().includes("queuer") && isEmpty, true), ssrAttribute("class", escape(styles().actionDotRed, true), false)), hasReset && ssr(_tmpl$6, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("class", escape(styles().actionDotYellow, true), false)), hasClear && ssr(_tmpl$7, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("disabled", props.utilName.toLowerCase().includes("batcher") && isEmpty || props.utilName.toLowerCase().includes("queuer") && isEmpty, true), ssrAttribute("class", escape(styles().actionDotOrange, true), false)), hasStartStop && ssr(_tmpl$3$1, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("class", escape(styles().actionDotPurple, true), false), isRunning ? "Stop" : "Start"));
962
+ const utilNameLower = props.utilName.toLowerCase();
963
+ return ssr(_tmpl$2$2, ssrAttribute("class", escape(styles().actionsRow, true), false), hasPending() && ssr(_tmpl$3$1, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("class", escape(styles().actionDotBlue, true), false), isPending() ? "Restore Pending" : "Trigger Pending"), hasFlush && ssr(_tmpl$4, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("disabled", utilNameLower.includes("debouncer") && !isPending() || utilNameLower.includes("throttler") && !isPending() || utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), true), ssrAttribute("class", escape(styles().actionDotGreen, true), false)), hasCancel && ssr(_tmpl$5, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("disabled", utilNameLower.includes("debouncer") && !isPending() || utilNameLower.includes("throttler") && !isPending() || utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), true), ssrAttribute("class", escape(styles().actionDotRed, true), false)), hasReset && ssr(_tmpl$6, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("class", escape(styles().actionDotYellow, true), false)), hasClear && ssr(_tmpl$7, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("disabled", utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), true), ssrAttribute("class", escape(styles().actionDotOrange, true), false)), hasStartStop && ssr(_tmpl$3$1, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("class", escape(styles().actionDotPurple, true), false), isRunning() ? "Stop" : "Start"));
949
964
  }
950
965
  //#endregion
951
966
  //#region src/components/StateHeader.tsx
@@ -968,6 +983,37 @@ var _tmpl$$2 = [
968
983
  "% reduction</div></div></div>"
969
984
  ];
970
985
  dayjs.extend(relativeTime);
986
+ function reductionFromState(entry, state) {
987
+ if (!state) return 0;
988
+ const completedExecutions = entry.type.toLowerCase().includes("async") ? Number(state.settleCount) || 0 : Number(state.executionCount) || 0;
989
+ if (entry.type.toLowerCase().includes("batcher")) {
990
+ const totalItemsProcessed = Number(state.totalItemsProcessed) || 0;
991
+ if (totalItemsProcessed === 0) return 0;
992
+ return Math.round((totalItemsProcessed - completedExecutions) / totalItemsProcessed * 100);
993
+ }
994
+ let requestCount = 0;
995
+ if (state.maybeExecuteCount !== void 0) requestCount = Number(state.maybeExecuteCount) || 0;
996
+ else if (state.addItemCount !== void 0) requestCount = Number(state.addItemCount) || 0;
997
+ else return 0;
998
+ if (requestCount === 0) return 0;
999
+ const reduction = requestCount - completedExecutions;
1000
+ return Math.round(reduction / requestCount * 100);
1001
+ }
1002
+ function StateHeaderInner(props) {
1003
+ const key = props.entry.instance.key;
1004
+ const updatedAt = () => props.utilState().lastUpdatedByKey[key] ?? Date.now();
1005
+ const getRelativeTime = () => {
1006
+ const at = updatedAt();
1007
+ const diffMs = props.now() - at;
1008
+ const diffSeconds = Math.floor(diffMs / 1e3);
1009
+ if (diffSeconds < 60) return `${diffSeconds} second${diffSeconds !== 1 ? "s" : ""} ago`;
1010
+ return dayjs(at).fromNow();
1011
+ };
1012
+ const store = props.entry.instance?.store;
1013
+ const stateAccessor = isPacerUtilTanStackStore(store) ? useStore(store, (s) => s !== null && s !== void 0 && typeof s === "object" ? s : {}) : () => getPacerUtilStoreState(props.entry.instance);
1014
+ const reductionPercentage = () => reductionFromState(props.entry, stateAccessor());
1015
+ return ssr(_tmpl$$2, ssrAttribute("class", escape(props.styles.stateHeader, true), false), ssrAttribute("class", escape(props.styles.stateTitle, true), false), escape(props.entry.type), ssrStyleProperty("display:", "flex") + ssrStyleProperty(";align-items:", "center") + ssrStyleProperty(";gap:", "16px"), ssrAttribute("class", escape(props.styles.infoGrid, true), false), ssrAttribute("class", escape(props.styles.infoLabel, true), false), ssrAttribute("class", escape(props.styles.infoValueMono, true), false), escape(key), ssrAttribute("class", escape(props.styles.infoLabel, true), false), ssrAttribute("class", escape(props.styles.infoValueMono, true), false), escape(new Date(updatedAt()).toLocaleTimeString()), escape(getRelativeTime()), ssrAttribute("class", escape(props.styles.infoValueMono, true), false), ssrStyleProperty("margin-left:", "auto") + ssrStyleProperty(";font-weight:", "bold"), escape(reductionPercentage()));
1016
+ }
971
1017
  function StateHeader(props) {
972
1018
  const styles = useStyles();
973
1019
  const [now, setNow] = createSignal(Date.now());
@@ -979,35 +1025,42 @@ function StateHeader(props) {
979
1025
  clearInterval(interval);
980
1026
  });
981
1027
  });
982
- const entry = props.selectedInstance();
983
- if (!entry) return null;
984
- const key = entry.instance.key;
985
- const updatedAt = props.utilState().lastUpdatedByKey[key] ?? Date.now();
986
- const getRelativeTime = () => {
987
- const diffMs = now() - updatedAt;
988
- const diffSeconds = Math.floor(diffMs / 1e3);
989
- if (diffSeconds < 60) return `${diffSeconds} second${diffSeconds !== 1 ? "s" : ""} ago`;
990
- return dayjs(updatedAt).fromNow();
991
- };
992
- const getReductionPercentage = () => {
993
- const state = entry.instance.store?.state || entry.instance.state;
994
- if (!state) return 0;
995
- const completedExecutions = entry.type.toLowerCase().includes("async") ? state.settleCount || 0 : state.executionCount || 0;
996
- if (entry.type.toLowerCase().includes("batcher")) {
997
- const totalItemsProcessed = state.totalItemsProcessed || 0;
998
- if (totalItemsProcessed === 0) return 0;
999
- return Math.round((totalItemsProcessed - completedExecutions) / totalItemsProcessed * 100);
1000
- }
1001
- let requestCount = 0;
1002
- if (state.maybeExecuteCount !== void 0) requestCount = state.maybeExecuteCount;
1003
- else if (state.addItemCount !== void 0) requestCount = state.addItemCount;
1004
- else return 0;
1005
- if (requestCount === 0) return 0;
1006
- const reduction = requestCount - completedExecutions;
1007
- return Math.round(reduction / requestCount * 100);
1008
- };
1009
- const reductionPercentage = getReductionPercentage();
1010
- return ssr(_tmpl$$2, ssrAttribute("class", escape(styles().stateHeader, true), false), ssrAttribute("class", escape(styles().stateTitle, true), false), escape(entry.type), ssrStyleProperty("display:", "flex") + ssrStyleProperty(";align-items:", "center") + ssrStyleProperty(";gap:", "16px"), ssrAttribute("class", escape(styles().infoGrid, true), false), ssrAttribute("class", escape(styles().infoLabel, true), false), ssrAttribute("class", escape(styles().infoValueMono, true), false), escape(key), ssrAttribute("class", escape(styles().infoLabel, true), false), ssrAttribute("class", escape(styles().infoValueMono, true), false), escape(new Date(updatedAt).toLocaleTimeString()), escape(getRelativeTime()), ssrAttribute("class", escape(styles().infoValueMono, true), false), ssrStyleProperty("margin-left:", "auto") + ssrStyleProperty(";font-weight:", "bold"), escape(reductionPercentage));
1028
+ return createComponent(Show, {
1029
+ get when() {
1030
+ return props.selectedInstance();
1031
+ },
1032
+ children: (entry) => createComponent(StateHeaderInner, {
1033
+ get entry() {
1034
+ return entry();
1035
+ },
1036
+ get styles() {
1037
+ return styles();
1038
+ },
1039
+ now,
1040
+ get utilState() {
1041
+ return props.utilState;
1042
+ }
1043
+ })
1044
+ });
1045
+ }
1046
+ //#endregion
1047
+ //#region src/components/UtilStateJsonTree.tsx
1048
+ /**
1049
+ * Subscribes to the util's TanStack Store so the tree updates live (same
1050
+ * idea as {@link UtilList} status). A plain snapshot would only refresh when the
1051
+ * devtools shell re-renders, which does not happen on nested store updates.
1052
+ */
1053
+ function UtilStateJsonTree(props) {
1054
+ const store = props.instance?.store;
1055
+ if (isPacerUtilTanStackStore(store)) {
1056
+ const snapshot = useStore(store, (s) => s);
1057
+ return createComponent(JsonTree, { get value() {
1058
+ return snapshot();
1059
+ } });
1060
+ }
1061
+ return createComponent(JsonTree, { get value() {
1062
+ return getPacerUtilStoreState(props.instance);
1063
+ } });
1011
1064
  }
1012
1065
  //#endregion
1013
1066
  //#region src/components/DetailsPanel.tsx
@@ -1015,7 +1068,7 @@ var _tmpl$$1 = [
1015
1068
  "<div",
1016
1069
  ">",
1017
1070
  "</div>"
1018
- ], _tmpl$2 = ["<div", ">Select a util from the left panel to view its state</div>"], _tmpl$3 = [
1071
+ ], _tmpl$2$1 = ["<div", ">Select a util from the left panel to view its state</div>"], _tmpl$3 = [
1019
1072
  "<div",
1020
1073
  "><div",
1021
1074
  "><div",
@@ -1034,7 +1087,7 @@ function DetailsPanel(props) {
1034
1087
  const styles = useStyles();
1035
1088
  return ssr(_tmpl$$1, ssrAttribute("class", escape(styles().stateDetails, true), false), (() => {
1036
1089
  const entry = props.selectedInstance();
1037
- if (!entry) return ssr(_tmpl$2, ssrAttribute("class", escape(styles().noSelection, true), false));
1090
+ if (!entry) return ssr(_tmpl$2$1, ssrAttribute("class", escape(styles().noSelection, true), false));
1038
1091
  return escape([createComponent(StateHeader, {
1039
1092
  get selectedInstance() {
1040
1093
  return props.selectedInstance;
@@ -1049,8 +1102,8 @@ function DetailsPanel(props) {
1049
1102
  get utilName() {
1050
1103
  return entry.type;
1051
1104
  }
1052
- })), ssrAttribute("class", escape(styles().detailSection, true), false), ssrAttribute("class", escape(styles().detailSectionHeader, true), false), ssrAttribute("class", escape(styles().stateContent, true), false), escape(createComponent(JsonTree, { get value() {
1053
- return entry.instance.store?.state;
1105
+ })), ssrAttribute("class", escape(styles().detailSection, true), false), ssrAttribute("class", escape(styles().detailSectionHeader, true), false), ssrAttribute("class", escape(styles().stateContent, true), false), escape(createComponent(UtilStateJsonTree, { get instance() {
1106
+ return entry.instance;
1054
1107
  } })), ssrAttribute("class", escape(styles().detailSection, true), false), ssrAttribute("class", escape(styles().detailSectionHeader, true), false), ssrAttribute("class", escape(styles().stateContent, true), false), escape(createComponent(JsonTree, { get value() {
1055
1108
  return entry.instance.options;
1056
1109
  } })))]);
@@ -1069,7 +1122,23 @@ var _tmpl$ = [
1069
1122
  "\"><div",
1070
1123
  ">Details</div>",
1071
1124
  "</div></div>"
1125
+ ], _tmpl$2 = [
1126
+ "<div",
1127
+ " style=\"",
1128
+ "\">",
1129
+ "</div>"
1072
1130
  ];
1131
+ function readResizeObserverBlockSize(entry) {
1132
+ const [box] = entry.borderBoxSize;
1133
+ if (box !== void 0) return box.blockSize;
1134
+ return entry.contentRect.height;
1135
+ }
1136
+ /** Outer plugin slot from TanStack Devtools (not the inner React mount div, which can report a tiny height). */
1137
+ function resolvePluginHost(el) {
1138
+ const byId = el.closest("[id^=\"plugin-container-\"]");
1139
+ if (byId) return byId;
1140
+ return el.parentElement?.parentElement?.parentElement ?? el.parentElement?.parentElement ?? el.parentElement;
1141
+ }
1073
1142
  function Shell() {
1074
1143
  const styles = useStyles();
1075
1144
  const state = usePacerDevtoolsState();
@@ -1077,6 +1146,8 @@ function Shell() {
1077
1146
  const [selectedKey, setSelectedKey] = createSignal(null);
1078
1147
  const [leftPanelWidth, setLeftPanelWidth] = createSignal(300);
1079
1148
  const [isDragging, setIsDragging] = createSignal(false);
1149
+ const [shellRootEl, setShellRootEl] = createSignal(null);
1150
+ const [slotHeightPx, setSlotHeightPx] = createSignal(void 0);
1080
1151
  const selectedInstance = createMemo(() => {
1081
1152
  const key = selectedKey();
1082
1153
  if (!key) return null;
@@ -1110,24 +1181,54 @@ function Shell() {
1110
1181
  document.removeEventListener("mousemove", handleMouseMove);
1111
1182
  document.removeEventListener("mouseup", handleMouseUp);
1112
1183
  });
1113
- return createComponent(MainPanel, { get children() {
1114
- return [createComponent(Header, { get children() {
1115
- return createComponent(HeaderLogo, {
1116
- flavor: {
1117
- light: "#84cc16",
1118
- dark: "#84cc16"
1119
- },
1120
- children: "TanStack Pacer"
1121
- });
1122
- } }), ssr(_tmpl$, ssrAttribute("class", escape(styles().mainContainer, true), false), ssrAttribute("class", escape(styles().leftPanel, true), false), ssrStyleProperty("width:", `${escape(leftPanelWidth(), true)}px`) + ssrStyleProperty(";min-width:", "150px") + ssrStyleProperty(";max-width:", "800px"), escape(createComponent(UtilList, {
1123
- selectedKey,
1124
- setSelectedKey,
1125
- utilState
1126
- })), `${escape(styles().dragHandle, true)} ${isDragging() ? "dragging" : ""}`, ssrAttribute("class", escape(styles().rightPanel, true), false), ssrStyleProperty("flex:", 1), ssrAttribute("class", escape(styles().panelHeader, true), false), escape(createComponent(DetailsPanel, {
1127
- selectedInstance,
1128
- utilState
1129
- })))];
1130
- } });
1184
+ /**
1185
+ * TanStack Devtools plugin slots use height: 100% without min-height: 0 on flex
1186
+ * ancestors, so percentage height often never constrains our tree. Observing the
1187
+ * outer `plugin-container-*` host (not the immediate React mount wrapper) and
1188
+ * setting an explicit pixel height makes inner overflow-y regions scroll without
1189
+ * capping to the mount div’s collapsed height.
1190
+ */
1191
+ createEffect(() => {
1192
+ const el = shellRootEl();
1193
+ if (!el || typeof ResizeObserver === "undefined") return;
1194
+ const pluginSlot = resolvePluginHost(el);
1195
+ if (!pluginSlot) return;
1196
+ const ro = new ResizeObserver((entries) => {
1197
+ const entry = entries[0];
1198
+ if (!entry) return;
1199
+ const target = entry.target;
1200
+ const h = target.clientHeight > 0 ? target.clientHeight : readResizeObserverBlockSize(entry);
1201
+ if (h > 0) setSlotHeightPx(Math.round(h));
1202
+ });
1203
+ ro.observe(pluginSlot);
1204
+ onCleanup(() => ro.disconnect());
1205
+ });
1206
+ return ssr(_tmpl$2, ssrAttribute("class", escape(styles().shellRoot, true), false), ssrStyle({ ...slotHeightPx() !== void 0 ? {
1207
+ height: `${slotHeightPx()}px`,
1208
+ "max-height": `${slotHeightPx()}px`
1209
+ } : {} }), escape(createComponent(MainPanel, {
1210
+ get ["class"]() {
1211
+ return styles().mainPanelShell;
1212
+ },
1213
+ get children() {
1214
+ return [createComponent(Header, { get children() {
1215
+ return createComponent(HeaderLogo, {
1216
+ flavor: {
1217
+ light: "#84cc16",
1218
+ dark: "#84cc16"
1219
+ },
1220
+ children: "TanStack Pacer"
1221
+ });
1222
+ } }), ssr(_tmpl$, ssrAttribute("class", escape(styles().mainContainer, true), false), ssrAttribute("class", escape(styles().leftPanel, true), false), ssrStyleProperty("width:", `${escape(leftPanelWidth(), true)}px`) + ssrStyleProperty(";min-width:", "150px") + ssrStyleProperty(";max-width:", "800px"), escape(createComponent(UtilList, {
1223
+ selectedKey,
1224
+ setSelectedKey,
1225
+ utilState
1226
+ })), `${escape(styles().dragHandle, true)} ${isDragging() ? "dragging" : ""}`, ssrAttribute("class", escape(styles().rightPanel, true), false), ssrStyleProperty("flex:", 1), ssrAttribute("class", escape(styles().panelHeader, true), false), escape(createComponent(DetailsPanel, {
1227
+ selectedInstance,
1228
+ utilState
1229
+ })))];
1230
+ }
1231
+ })));
1131
1232
  }
1132
1233
  //#endregion
1133
1234
  //#region src/components/index.tsx
package/dist/index.d.ts CHANGED
@@ -1,14 +1,10 @@
1
+ import { ClassType } from "@tanstack/devtools-utils/solid";
2
+
1
3
  //#region src/core.d.ts
2
4
  interface PacerDevtoolsInit {}
5
+ declare const PacerDevtoolsCore$1: ClassType;
3
6
  //#endregion
4
7
  //#region src/index.d.ts
5
- declare const PacerDevtoolsCore: new () => {
6
- "__#private@#isMounted": boolean;
7
- "__#private@#isMounting": boolean;
8
- "__#private@#abortMount": boolean;
9
- "__#private@#dispose"?: () => void;
10
- mount<T extends HTMLElement>(el: T, props: TanStackDevtoolsPluginProps): Promise<void>;
11
- unmount(): void;
12
- };
8
+ declare const PacerDevtoolsCore: typeof PacerDevtoolsCore$1;
13
9
  //#endregion
14
10
  export { PacerDevtoolsCore, type PacerDevtoolsInit };
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use client";
2
2
  import { constructCoreClass } from "@tanstack/devtools-utils/solid";
3
3
  //#region src/core.tsx
4
- const [PacerDevtoolsCore$1, PacerDevtoolsCoreNoOp] = constructCoreClass(() => import("./components-DDmnxkL2.js"));
4
+ const coreClasses = constructCoreClass(() => import("./components-CGEHIK6F.js"));
5
+ const PacerDevtoolsCore$1 = coreClasses[0];
6
+ const PacerDevtoolsCoreNoOp = coreClasses[1];
5
7
  //#endregion
6
8
  //#region src/index.ts
7
9
  const PacerDevtoolsCore = process.env.NODE_ENV !== "development" ? PacerDevtoolsCoreNoOp : PacerDevtoolsCore$1;
@@ -1,19 +1,7 @@
1
+ import { ClassType } from "@tanstack/devtools-utils/solid";
2
+
1
3
  //#region src/core.d.ts
2
4
  interface PacerDevtoolsInit {}
3
- declare const PacerDevtoolsCore: new () => {
4
- "__#private@#isMounted": boolean;
5
- "__#private@#isMounting": boolean;
6
- "__#private@#abortMount": boolean;
7
- "__#private@#dispose"?: () => void;
8
- mount<T extends HTMLElement>(el: T, props: TanStackDevtoolsPluginProps): Promise<void>;
9
- unmount(): void;
10
- }, PacerDevtoolsCoreNoOp: new () => {
11
- mount<T extends HTMLElement>(_el: T, _props: TanStackDevtoolsPluginProps): Promise<void>;
12
- unmount(): void;
13
- "__#private@#isMounted": boolean;
14
- "__#private@#isMounting": boolean;
15
- "__#private@#abortMount": boolean;
16
- "__#private@#dispose"?: () => void;
17
- };
5
+ declare const PacerDevtoolsCore: ClassType;
18
6
  //#endregion
19
7
  export { PacerDevtoolsCore, type PacerDevtoolsInit };
@@ -1,6 +1,8 @@
1
1
  "use client";
2
2
  import { constructCoreClass } from "@tanstack/devtools-utils/solid";
3
3
  //#region src/core.tsx
4
- const [PacerDevtoolsCore, PacerDevtoolsCoreNoOp] = constructCoreClass(() => import("../components-DDmnxkL2.js"));
4
+ const coreClasses = constructCoreClass(() => import("../components-CGEHIK6F.js"));
5
+ const PacerDevtoolsCore = coreClasses[0];
6
+ coreClasses[1];
5
7
  //#endregion
6
8
  export { PacerDevtoolsCore };
@@ -1,6 +1,8 @@
1
1
  "use client";
2
2
  import { constructCoreClass } from "@tanstack/devtools-utils/solid";
3
3
  //#region src/core.tsx
4
- const [PacerDevtoolsCore, PacerDevtoolsCoreNoOp] = constructCoreClass(() => import("../components-ByKT1JiI.js"));
4
+ const coreClasses = constructCoreClass(() => import("../components-vap4_VJT.js"));
5
+ const PacerDevtoolsCore = coreClasses[0];
6
+ coreClasses[1];
5
7
  //#endregion
6
8
  export { PacerDevtoolsCore };
package/dist/server.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use client";
2
2
  import { constructCoreClass } from "@tanstack/devtools-utils/solid";
3
3
  //#region src/core.tsx
4
- const [PacerDevtoolsCore$1, PacerDevtoolsCoreNoOp] = constructCoreClass(() => import("./components-ByKT1JiI.js"));
4
+ const coreClasses = constructCoreClass(() => import("./components-vap4_VJT.js"));
5
+ const PacerDevtoolsCore$1 = coreClasses[0];
6
+ const PacerDevtoolsCoreNoOp = coreClasses[1];
5
7
  //#endregion
6
8
  //#region src/index.ts
7
9
  const PacerDevtoolsCore = process.env.NODE_ENV !== "development" ? PacerDevtoolsCoreNoOp : PacerDevtoolsCore$1;