@buoy-gg/shared-ui 7.0.15 → 7.0.17

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.
Files changed (121) hide show
  1. package/lib/commonjs/JsModal.web.js +1588 -0
  2. package/lib/commonjs/env/EnvironmentIndicator.web.js +114 -0
  3. package/lib/commonjs/env/EnvironmentSelectorInline.web.js +178 -0
  4. package/lib/commonjs/hooks/index.js +7 -0
  5. package/lib/commonjs/hooks/safe-area-impl.js +1 -1
  6. package/lib/commonjs/hooks/usePersistedState.js +71 -0
  7. package/lib/commonjs/icons/index.js +28 -0
  8. package/lib/commonjs/index.js +45 -3
  9. package/lib/commonjs/storage/devToolsStorageKeys.js +22 -0
  10. package/lib/commonjs/stores/ignoredPatternsCore.js +68 -0
  11. package/lib/commonjs/stores/ignoredPatternsStore.js +16 -52
  12. package/lib/commonjs/stores/ignoredPatternsStore.web.js +236 -0
  13. package/lib/commonjs/stores/index.js +1 -1
  14. package/lib/commonjs/ui/components/ModalHeader.js +69 -0
  15. package/lib/commonjs/ui/components/ToolButton.js +90 -0
  16. package/lib/commonjs/ui/components/index.js +7 -0
  17. package/lib/commonjs/ui/gameUI/constants/gameUIColors.js +5 -3
  18. package/lib/commonjs/ui/index.js +8 -0
  19. package/lib/commonjs/ui/toolTheme.js +96 -0
  20. package/lib/commonjs/utils/index.js +38 -0
  21. package/lib/commonjs/utils/stateSnapshotRegistry.js +62 -0
  22. package/lib/module/JsModal.web.js +1586 -0
  23. package/lib/module/env/EnvironmentIndicator.web.js +108 -0
  24. package/lib/module/env/EnvironmentSelectorInline.web.js +173 -0
  25. package/lib/module/hooks/index.js +1 -0
  26. package/lib/module/hooks/safe-area-impl.js +1 -1
  27. package/lib/module/hooks/usePersistedState.js +68 -0
  28. package/lib/module/icons/index.js +2 -0
  29. package/lib/module/index.js +7 -3
  30. package/lib/module/storage/devToolsStorageKeys.js +22 -0
  31. package/lib/module/stores/ignoredPatternsCore.js +62 -0
  32. package/lib/module/stores/ignoredPatternsStore.js +5 -51
  33. package/lib/module/stores/ignoredPatternsStore.web.js +221 -0
  34. package/lib/module/stores/index.js +1 -1
  35. package/lib/module/ui/components/ModalHeader.js +70 -2
  36. package/lib/module/ui/components/ToolButton.js +86 -0
  37. package/lib/module/ui/components/index.js +1 -0
  38. package/lib/module/ui/gameUI/constants/gameUIColors.js +4 -2
  39. package/lib/module/ui/index.js +3 -0
  40. package/lib/module/ui/toolTheme.js +93 -0
  41. package/lib/module/utils/index.js +2 -0
  42. package/lib/module/utils/stateSnapshotRegistry.js +55 -0
  43. package/lib/typescript/commonjs/JsModal.web.d.ts +138 -0
  44. package/lib/typescript/commonjs/JsModal.web.d.ts.map +1 -0
  45. package/lib/typescript/commonjs/env/EnvironmentIndicator.web.d.ts +16 -0
  46. package/lib/typescript/commonjs/env/EnvironmentIndicator.web.d.ts.map +1 -0
  47. package/lib/typescript/commonjs/env/EnvironmentSelectorInline.web.d.ts +8 -0
  48. package/lib/typescript/commonjs/env/EnvironmentSelectorInline.web.d.ts.map +1 -0
  49. package/lib/typescript/commonjs/hooks/index.d.ts +1 -0
  50. package/lib/typescript/commonjs/hooks/index.d.ts.map +1 -1
  51. package/lib/typescript/commonjs/hooks/safe-area-impl.d.ts +1 -1
  52. package/lib/typescript/commonjs/hooks/usePersistedState.d.ts +16 -0
  53. package/lib/typescript/commonjs/hooks/usePersistedState.d.ts.map +1 -0
  54. package/lib/typescript/commonjs/icons/index.d.ts +2 -0
  55. package/lib/typescript/commonjs/icons/index.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/index.d.ts +1 -1
  57. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  58. package/lib/typescript/commonjs/storage/devToolsStorageKeys.d.ts +19 -0
  59. package/lib/typescript/commonjs/storage/devToolsStorageKeys.d.ts.map +1 -1
  60. package/lib/typescript/commonjs/stores/ignoredPatternsCore.d.ts +37 -0
  61. package/lib/typescript/commonjs/stores/ignoredPatternsCore.d.ts.map +1 -0
  62. package/lib/typescript/commonjs/stores/ignoredPatternsStore.d.ts +3 -27
  63. package/lib/typescript/commonjs/stores/ignoredPatternsStore.d.ts.map +1 -1
  64. package/lib/typescript/commonjs/stores/ignoredPatternsStore.web.d.ts +72 -0
  65. package/lib/typescript/commonjs/stores/ignoredPatternsStore.web.d.ts.map +1 -0
  66. package/lib/typescript/commonjs/ui/components/ModalHeader.d.ts +13 -1
  67. package/lib/typescript/commonjs/ui/components/ModalHeader.d.ts.map +1 -1
  68. package/lib/typescript/commonjs/ui/components/ToolButton.d.ts +24 -0
  69. package/lib/typescript/commonjs/ui/components/ToolButton.d.ts.map +1 -0
  70. package/lib/typescript/commonjs/ui/components/index.d.ts +4 -0
  71. package/lib/typescript/commonjs/ui/components/index.d.ts.map +1 -1
  72. package/lib/typescript/commonjs/ui/gameUI/constants/gameUIColors.d.ts +56 -0
  73. package/lib/typescript/commonjs/ui/gameUI/constants/gameUIColors.d.ts.map +1 -1
  74. package/lib/typescript/commonjs/ui/index.d.ts +1 -0
  75. package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
  76. package/lib/typescript/commonjs/ui/toolTheme.d.ts +68 -0
  77. package/lib/typescript/commonjs/ui/toolTheme.d.ts.map +1 -0
  78. package/lib/typescript/commonjs/utils/index.d.ts +2 -0
  79. package/lib/typescript/commonjs/utils/index.d.ts.map +1 -1
  80. package/lib/typescript/commonjs/utils/stateSnapshotRegistry.d.ts +67 -0
  81. package/lib/typescript/commonjs/utils/stateSnapshotRegistry.d.ts.map +1 -0
  82. package/lib/typescript/module/JsModal.web.d.ts +138 -0
  83. package/lib/typescript/module/JsModal.web.d.ts.map +1 -0
  84. package/lib/typescript/module/env/EnvironmentIndicator.web.d.ts +16 -0
  85. package/lib/typescript/module/env/EnvironmentIndicator.web.d.ts.map +1 -0
  86. package/lib/typescript/module/env/EnvironmentSelectorInline.web.d.ts +8 -0
  87. package/lib/typescript/module/env/EnvironmentSelectorInline.web.d.ts.map +1 -0
  88. package/lib/typescript/module/hooks/index.d.ts +1 -0
  89. package/lib/typescript/module/hooks/index.d.ts.map +1 -1
  90. package/lib/typescript/module/hooks/safe-area-impl.d.ts +1 -1
  91. package/lib/typescript/module/hooks/usePersistedState.d.ts +16 -0
  92. package/lib/typescript/module/hooks/usePersistedState.d.ts.map +1 -0
  93. package/lib/typescript/module/icons/index.d.ts +2 -0
  94. package/lib/typescript/module/icons/index.d.ts.map +1 -1
  95. package/lib/typescript/module/index.d.ts +1 -1
  96. package/lib/typescript/module/index.d.ts.map +1 -1
  97. package/lib/typescript/module/storage/devToolsStorageKeys.d.ts +19 -0
  98. package/lib/typescript/module/storage/devToolsStorageKeys.d.ts.map +1 -1
  99. package/lib/typescript/module/stores/ignoredPatternsCore.d.ts +37 -0
  100. package/lib/typescript/module/stores/ignoredPatternsCore.d.ts.map +1 -0
  101. package/lib/typescript/module/stores/ignoredPatternsStore.d.ts +3 -27
  102. package/lib/typescript/module/stores/ignoredPatternsStore.d.ts.map +1 -1
  103. package/lib/typescript/module/stores/ignoredPatternsStore.web.d.ts +72 -0
  104. package/lib/typescript/module/stores/ignoredPatternsStore.web.d.ts.map +1 -0
  105. package/lib/typescript/module/ui/components/ModalHeader.d.ts +13 -1
  106. package/lib/typescript/module/ui/components/ModalHeader.d.ts.map +1 -1
  107. package/lib/typescript/module/ui/components/ToolButton.d.ts +24 -0
  108. package/lib/typescript/module/ui/components/ToolButton.d.ts.map +1 -0
  109. package/lib/typescript/module/ui/components/index.d.ts +4 -0
  110. package/lib/typescript/module/ui/components/index.d.ts.map +1 -1
  111. package/lib/typescript/module/ui/gameUI/constants/gameUIColors.d.ts +56 -0
  112. package/lib/typescript/module/ui/gameUI/constants/gameUIColors.d.ts.map +1 -1
  113. package/lib/typescript/module/ui/index.d.ts +1 -0
  114. package/lib/typescript/module/ui/index.d.ts.map +1 -1
  115. package/lib/typescript/module/ui/toolTheme.d.ts +68 -0
  116. package/lib/typescript/module/ui/toolTheme.d.ts.map +1 -0
  117. package/lib/typescript/module/utils/index.d.ts +2 -0
  118. package/lib/typescript/module/utils/index.d.ts.map +1 -1
  119. package/lib/typescript/module/utils/stateSnapshotRegistry.d.ts +67 -0
  120. package/lib/typescript/module/utils/stateSnapshotRegistry.d.ts.map +1 -0
  121. package/package.json +4 -4
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * EnvironmentIndicator — Web (React DOM) twin of EnvironmentIndicator.tsx.
5
+ * Same env→color/label map and layout; lucide glyph icons are omitted from
6
+ * the badge on native too (dot + label only), so parity holds.
7
+ */
8
+ import { gameUIColors } from "../ui/gameUI/constants/gameUIColors.js";
9
+ import { normalizeEnvironment } from "../types/types.js";
10
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
+ export function getEnvironmentConfig(environment) {
12
+ switch (normalizeEnvironment(String(environment))) {
13
+ case 'dev':
14
+ return {
15
+ label: 'DEV',
16
+ backgroundColor: gameUIColors.warning
17
+ };
18
+ case 'qa':
19
+ return {
20
+ label: 'QA',
21
+ backgroundColor: gameUIColors.optional
22
+ };
23
+ case 'staging':
24
+ return {
25
+ label: 'STAGING',
26
+ backgroundColor: gameUIColors.success
27
+ };
28
+ case 'prod':
29
+ return {
30
+ label: 'PROD',
31
+ backgroundColor: gameUIColors.error
32
+ };
33
+ case 'local':
34
+ default:
35
+ return {
36
+ label: 'LOCAL',
37
+ backgroundColor: gameUIColors.info
38
+ };
39
+ }
40
+ }
41
+ function ChevronDownGlyph({
42
+ color
43
+ }) {
44
+ // native: ChevronDown size 12 from shared lucide icons
45
+ return /*#__PURE__*/_jsx("svg", {
46
+ width: 12,
47
+ height: 12,
48
+ viewBox: "0 0 24 24",
49
+ "aria-hidden": "true",
50
+ children: /*#__PURE__*/_jsx("path", {
51
+ d: "M6 9l6 6 6-6",
52
+ fill: "none",
53
+ stroke: color,
54
+ strokeWidth: "2",
55
+ strokeLinecap: "round",
56
+ strokeLinejoin: "round"
57
+ })
58
+ });
59
+ }
60
+ export function EnvironmentIndicator({
61
+ environment,
62
+ interactive = false,
63
+ onPress
64
+ }) {
65
+ const envConfig = getEnvironmentConfig(environment);
66
+ return /*#__PURE__*/_jsxs("button", {
67
+ onClick: interactive ? onPress : undefined,
68
+ "aria-label": `Environment: ${envConfig.label}`,
69
+ style: {
70
+ display: 'flex',
71
+ flexDirection: 'row',
72
+ alignItems: 'center',
73
+ // native: paddingVertical 6, paddingLeft 8, paddingRight interactive?4:8
74
+ padding: `6px ${interactive ? 4 : 8}px 6px 8px`,
75
+ flexShrink: 0,
76
+ background: 'none',
77
+ border: 'none',
78
+ cursor: interactive ? 'pointer' : 'default'
79
+ },
80
+ children: [/*#__PURE__*/_jsx("div", {
81
+ style: {
82
+ width: 8,
83
+ height: 8,
84
+ borderRadius: 4,
85
+ backgroundColor: envConfig.backgroundColor,
86
+ marginRight: 6,
87
+ // native: shadowOpacity 0.6, shadowRadius 4 glow
88
+ boxShadow: `0 0 4px ${envConfig.backgroundColor}99`
89
+ }
90
+ }), /*#__PURE__*/_jsx("span", {
91
+ style: {
92
+ fontSize: 11,
93
+ fontWeight: 600,
94
+ color: gameUIColors.primaryLight,
95
+ letterSpacing: 0.5
96
+ },
97
+ children: envConfig.label
98
+ }), interactive && /*#__PURE__*/_jsx("span", {
99
+ style: {
100
+ marginLeft: 2,
101
+ display: 'flex'
102
+ },
103
+ children: /*#__PURE__*/_jsx(ChevronDownGlyph, {
104
+ color: "#7A8599"
105
+ })
106
+ })]
107
+ });
108
+ }
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * EnvironmentSelectorInline — Web (React DOM) twin of
5
+ * EnvironmentSelectorInline.tsx: the interactive environment badge that
6
+ * expands a drop-UP panel (absolute bottom:100%, opens upward so the bubble
7
+ * layout doesn't shift). Same geometry: OPTION_HEIGHT 36, 200ms animation,
8
+ * selected row gets a green check.
9
+ */
10
+ import { useState } from 'react';
11
+ import { gameUIColors } from "../ui/gameUI/constants/gameUIColors.js";
12
+ import { getEnvironmentConfig } from "./EnvironmentIndicator.web.js";
13
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
+ const OPTION_HEIGHT = 36; // native EnvironmentSelectorInline OPTION_HEIGHT
15
+
16
+ function ChevronUpGlyph({
17
+ color,
18
+ flipped
19
+ }) {
20
+ return /*#__PURE__*/_jsx("svg", {
21
+ width: 10,
22
+ height: 10,
23
+ viewBox: "0 0 24 24",
24
+ "aria-hidden": "true",
25
+ style: {
26
+ transform: flipped ? 'rotate(180deg)' : 'rotate(0deg)',
27
+ transition: 'transform 200ms ease' // native: 200ms rotation
28
+ },
29
+ children: /*#__PURE__*/_jsx("path", {
30
+ d: "M18 15l-6-6-6 6",
31
+ fill: "none",
32
+ stroke: color,
33
+ strokeWidth: "2",
34
+ strokeLinecap: "round",
35
+ strokeLinejoin: "round"
36
+ })
37
+ });
38
+ }
39
+ function CheckGlyph() {
40
+ // native: Check size 12 color #4AFF9F strokeWidth 3
41
+ return /*#__PURE__*/_jsx("svg", {
42
+ width: 12,
43
+ height: 12,
44
+ viewBox: "0 0 24 24",
45
+ "aria-hidden": "true",
46
+ children: /*#__PURE__*/_jsx("path", {
47
+ d: "M20 6L9 17l-5-5",
48
+ fill: "none",
49
+ stroke: "#4AFF9F",
50
+ strokeWidth: "3",
51
+ strokeLinecap: "round",
52
+ strokeLinejoin: "round"
53
+ })
54
+ });
55
+ }
56
+ export function EnvironmentSelectorInline({
57
+ currentEnvironment,
58
+ availableEnvironments,
59
+ onSelect
60
+ }) {
61
+ const [expanded, setExpanded] = useState(false);
62
+ const [hoveredEnv, setHoveredEnv] = useState(null);
63
+ const current = getEnvironmentConfig(currentEnvironment);
64
+ const panelHeight = availableEnvironments.length * OPTION_HEIGHT;
65
+ return /*#__PURE__*/_jsxs("div", {
66
+ style: {
67
+ position: 'relative',
68
+ zIndex: 1000,
69
+ flexShrink: 0
70
+ },
71
+ children: [/*#__PURE__*/_jsx("div", {
72
+ style: {
73
+ position: 'absolute',
74
+ bottom: '100%',
75
+ left: 0,
76
+ right: 0,
77
+ height: expanded ? panelHeight : 0,
78
+ opacity: expanded ? 1 : 0,
79
+ overflow: 'hidden',
80
+ transition: 'height 200ms ease, opacity 200ms ease',
81
+ backgroundColor: 'rgba(8,12,21,0.95)',
82
+ border: '1px solid #00B8E666',
83
+ borderBottomWidth: 0,
84
+ borderTopLeftRadius: 6,
85
+ borderTopRightRadius: 6,
86
+ boxShadow: '0 -4px 8px rgba(0,0,0,0.3)'
87
+ },
88
+ children: availableEnvironments.map(env => {
89
+ const config = getEnvironmentConfig(env);
90
+ const isSelected = String(env).toLowerCase() === String(currentEnvironment).toLowerCase();
91
+ return /*#__PURE__*/_jsxs("button", {
92
+ onClick: () => {
93
+ setExpanded(false);
94
+ onSelect(env);
95
+ },
96
+ onMouseEnter: () => setHoveredEnv(String(env)),
97
+ onMouseLeave: () => setHoveredEnv(null),
98
+ style: {
99
+ display: 'flex',
100
+ alignItems: 'center',
101
+ justifyContent: 'space-between',
102
+ width: '100%',
103
+ height: OPTION_HEIGHT,
104
+ padding: '0 10px',
105
+ background: isSelected ? 'rgba(128,128,128,0.1)' : hoveredEnv === String(env) ? 'rgba(128,128,128,0.2)' : 'none',
106
+ border: 'none',
107
+ cursor: 'pointer'
108
+ },
109
+ children: [/*#__PURE__*/_jsxs("span", {
110
+ style: {
111
+ display: 'flex',
112
+ alignItems: 'center'
113
+ },
114
+ children: [/*#__PURE__*/_jsx("span", {
115
+ style: {
116
+ width: 6,
117
+ height: 6,
118
+ borderRadius: 3,
119
+ backgroundColor: config.backgroundColor,
120
+ marginRight: 8
121
+ }
122
+ }), /*#__PURE__*/_jsx("span", {
123
+ style: {
124
+ fontSize: 10,
125
+ fontWeight: 600,
126
+ color: gameUIColors.primaryLight,
127
+ letterSpacing: 0.5
128
+ },
129
+ children: config.label
130
+ })]
131
+ }), isSelected && /*#__PURE__*/_jsx(CheckGlyph, {})]
132
+ }, String(env));
133
+ })
134
+ }), /*#__PURE__*/_jsxs("button", {
135
+ onClick: () => setExpanded(e => !e),
136
+ "aria-expanded": expanded,
137
+ "aria-label": `Environment: ${current.label}. Click to switch.`,
138
+ style: {
139
+ display: 'flex',
140
+ alignItems: 'center',
141
+ gap: 6,
142
+ padding: '6px 6px 6px 8px',
143
+ borderRadius: 6,
144
+ // native: expanded trigger fuses with panel (squared top corners)
145
+ borderTopLeftRadius: expanded ? 0 : 6,
146
+ borderTopRightRadius: expanded ? 0 : 6,
147
+ backgroundColor: expanded ? 'rgba(8,12,21,0.95)' : 'transparent',
148
+ border: 'none',
149
+ cursor: 'pointer'
150
+ },
151
+ children: [/*#__PURE__*/_jsx("span", {
152
+ style: {
153
+ width: 8,
154
+ height: 8,
155
+ borderRadius: 4,
156
+ backgroundColor: current.backgroundColor,
157
+ boxShadow: `0 0 4px ${current.backgroundColor}99`
158
+ }
159
+ }), /*#__PURE__*/_jsx("span", {
160
+ style: {
161
+ fontSize: 11,
162
+ fontWeight: 600,
163
+ color: gameUIColors.primaryLight,
164
+ letterSpacing: 0.5
165
+ },
166
+ children: current.label
167
+ }), /*#__PURE__*/_jsx(ChevronUpGlyph, {
168
+ color: "#7A8599",
169
+ flipped: expanded
170
+ })]
171
+ })]
172
+ });
173
+ }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  export { useFilterManager } from "./useFilterManager.js";
4
+ export { usePersistedState } from "./usePersistedState.js";
4
5
  export { useSafeAreaInsets } from "./useSafeAreaInsets.js";
5
6
  export { getSafeAreaInsets } from "./useSafeAreaInsets.js";
6
7
  export { useDeviceInfo, getDeviceInfo, logDeviceInfo } from "./useDeviceInfo.js";
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * Auto-generated safe area implementation
5
5
  * Detected: none
6
- * Generated at: 2026-08-05T21:40:54.216Z
6
+ * Generated at: 2026-08-07T14:32:36.664Z
7
7
  *
8
8
  * DO NOT EDIT - This file is generated by scripts/detect-safe-area.js
9
9
  *
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+
3
+ import { useCallback, useEffect, useRef, useState } from "react";
4
+ import { persistentStorage } from "../utils/persistentStorage.js";
5
+
6
+ /**
7
+ * Per-key cache so remounts (and multiple consumers of the same key) render
8
+ * the last-known value immediately instead of flashing the default while the
9
+ * async backend loads again.
10
+ */
11
+ const hydratedCache = new Map();
12
+
13
+ /**
14
+ * `useState` persisted through `persistentStorage` (FileSystem → AsyncStorage
15
+ * → memory). Replaces the load-then-`hasLoaded`-guard `useEffect` pattern
16
+ * every tool used to hand-roll.
17
+ *
18
+ * Values are JSON-serialized; the default is returned until hydration
19
+ * completes (subsequent mounts of the same key hydrate synchronously from an
20
+ * in-memory cache). The third tuple element reports hydration so callers can
21
+ * defer writes that must not clobber a not-yet-loaded value.
22
+ *
23
+ * ```tsx
24
+ * const [tab, setTab, tabHydrated] = usePersistedState("@react_buoy_custom_flags_tab", "flags");
25
+ * ```
26
+ */
27
+ export function usePersistedState(key, defaultValue) {
28
+ const [value, setValue] = useState(() => hydratedCache.has(key) ? hydratedCache.get(key) : defaultValue);
29
+ const [hydrated, setHydrated] = useState(() => hydratedCache.has(key));
30
+ const keyRef = useRef(key);
31
+ keyRef.current = key;
32
+ useEffect(() => {
33
+ if (hydratedCache.has(key)) {
34
+ setValue(hydratedCache.get(key));
35
+ setHydrated(true);
36
+ return;
37
+ }
38
+ let cancelled = false;
39
+ persistentStorage.getItem(key).then(raw => {
40
+ if (cancelled) return;
41
+ if (raw != null) {
42
+ try {
43
+ const parsed = JSON.parse(raw);
44
+ hydratedCache.set(key, parsed);
45
+ setValue(parsed);
46
+ } catch {
47
+ // Corrupt payload — fall back to the default and let the next
48
+ // write repair it.
49
+ }
50
+ }
51
+ setHydrated(true);
52
+ }).catch(() => {
53
+ if (!cancelled) setHydrated(true);
54
+ });
55
+ return () => {
56
+ cancelled = true;
57
+ };
58
+ }, [key]);
59
+ const set = useCallback(next => {
60
+ setValue(prev => {
61
+ const resolved = typeof next === "function" ? next(prev) : next;
62
+ hydratedCache.set(keyRef.current, resolved);
63
+ persistentStorage.setItem(keyRef.current, JSON.stringify(resolved)).catch(() => {});
64
+ return resolved;
65
+ });
66
+ }, []);
67
+ return [value, set, hydrated];
68
+ }
@@ -11,7 +11,9 @@ export { EnvIcon, ENV_ICON_COLOR } from "@buoy-gg/floating-tools-core";
11
11
  // into core, so these are the same artwork the Flutter build renders. Tool
12
12
  // packages re-export from here rather than drawing their own copies.
13
13
  export { ConsoleIcon, CONSOLE_ICON_COLOR } from "@buoy-gg/floating-tools-core";
14
+ export { TimeMachineIcon, TIME_MACHINE_ICON_COLOR } from "@buoy-gg/floating-tools-core";
14
15
  export { ImagesIcon, IMAGES_ICON_COLOR } from "@buoy-gg/floating-tools-core";
16
+ export { AssetsIcon, ASSETS_ICON_COLOR } from "@buoy-gg/floating-tools-core";
15
17
  export { ImpersonateIcon, IMPERSONATE_ICON_COLOR } from "@buoy-gg/floating-tools-core";
16
18
  export { ImageOverlayIcon, IMAGE_OVERLAY_ICON_COLOR } from "@buoy-gg/floating-tools-core";
17
19
  export { ZustandIcon, ZUSTAND_ICON_COLOR } from "@buoy-gg/floating-tools-core";
@@ -26,6 +26,10 @@ parseValue, formatValue, getTypeColor, truncateText, flattenObject, formatPath,
26
26
  loadOptionalModule, getCachedOptionalModule,
27
27
  // Render-commit capture bridge (provider: highlight-updates, consumer: perf-monitor)
28
28
  setRenderCaptureProvider, getRenderCaptureProvider,
29
+ // State snapshot bridge (providers: storage/redux/zustand/jotai/react-query, consumer: time-machine)
30
+ registerStateSnapshotProvider, getStateSnapshotProviders, getStateSnapshotProvider, subscribeToStateSnapshotProviders,
31
+ // Minimal superjson (Date/Map/Set/BigInt-safe round-trip) for snapshot payloads
32
+ superJsonSerialize, superJsonDeserialize,
29
33
  // JS-bundle reload (core's `app` adapter + perf-monitor's batch runner)
30
34
  reloadApp, scheduleReloadApp, resolveReloadStrategy, REMOTE_RELOAD_DELAY_MS,
31
35
  // Subscribable base class for self-managing listeners
@@ -53,7 +57,7 @@ export * from "./storage/index.js";
53
57
  export { useSafeAreaInsets, useFilterManager, useDeviceInfo, getDeviceInfo, logDeviceInfo } from "./hooks/index.js";
54
58
 
55
59
  // JsModal
56
- export { JsModal } from "./JsModal.js";
60
+ export { JsModal } from "./JsModal";
57
61
  // game ui colors
58
62
  export { gameUIColors, buoyColors } from "./ui/gameUI/constants/gameUIColors.js";
59
63
  // Runtime re-tint hook — only the desktop dashboard's theme switcher uses it.
@@ -61,11 +65,11 @@ export { refreshGameUIColors } from "./ui/gameUI/constants/gameUIColors.js";
61
65
  export { macOSColors } from "./ui/gameUI/constants/macOSDesignSystemColors.js";
62
66
  export { dialColors } from "./ui/gameUI/constants/gameUIColors.js";
63
67
  //EnvironmentIndicator
64
- export { EnvironmentIndicator } from "./env/EnvironmentIndicator.js";
68
+ export { EnvironmentIndicator } from "./env/EnvironmentIndicator";
65
69
  // EnvironmentSelector
66
70
  export { EnvironmentSelector } from "./env/EnvironmentSelector.js";
67
71
  // EnvironmentSelectorInline
68
- export { EnvironmentSelectorInline } from "./env/EnvironmentSelectorInline.js";
72
+ export { EnvironmentSelectorInline } from "./env/EnvironmentSelectorInline";
69
73
  export { normalizeEnvironment } from "./types/types.js";
70
74
 
71
75
  // Context exports
@@ -31,6 +31,28 @@ export const devToolsStorageKeys = {
31
31
  dimensions: () => `${devToolsStorageKeys.modal.root()}_dimensions`,
32
32
  hintAcknowledged: () => `${devToolsStorageKeys.modal.root()}_hint_acknowledged`
33
33
  },
34
+ /**
35
+ * Namespace factory for third-party custom tools. Keys share the
36
+ * `@react_buoy` prefix, so `isDevToolsStorageKey` filtering (and the Storage
37
+ * tool's internal-key handling) covers custom-tool persistence for free.
38
+ *
39
+ * ```ts
40
+ * const keys = devToolsStorageKeys.custom("feature-flags");
41
+ * keys.modal(); // "@react_buoy_custom_feature-flags_modal"
42
+ * keys.key("overrides"); // "@react_buoy_custom_feature-flags_overrides"
43
+ * ```
44
+ */
45
+ custom: toolId => {
46
+ const root = `${devToolsStorageKeys.base}_custom_${toolId}`;
47
+ return {
48
+ root: () => root,
49
+ /** JsModal `persistenceKey` for this tool's window dimensions. */
50
+ modal: () => `${root}_modal`,
51
+ settings: () => `${root}_settings`,
52
+ /** Arbitrary tool-scoped key. */
53
+ key: suffix => `${root}_${suffix}`
54
+ };
55
+ },
34
56
  /**
35
57
  * Settings-related storage keys
36
58
  */
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Pure, dependency-free part of the ignored-patterns feature — split from
5
+ * ignoredPatternsStore.ts so web consumers (which shim `@buoy-gg/shared-ui`
6
+ * and must not pull the store's relative `../utils/persistentStorage` import
7
+ * with its expo/async-storage requires into the bundle graph) can share the
8
+ * exact matching semantics. The store imports and re-exports everything here;
9
+ * the native barrel is unchanged.
10
+ */
11
+
12
+ /**
13
+ * How a pattern matches a URL:
14
+ *
15
+ * - `contains`: case-insensitive substring match on the full URL (legacy behavior).
16
+ * - `exact`: smart equality — if pattern starts with `/`, matches URL.pathname;
17
+ * if it starts with `http://`/`https://`, matches origin+pathname; otherwise
18
+ * matches URL.host.
19
+ */
20
+
21
+ /** A single exclude pattern with its match mode. */
22
+
23
+ /** Patterns hidden by default — Buoy's own license API traffic. */
24
+ export const DEFAULT_IGNORED_PATTERNS = [{
25
+ value: "api.keygen.sh",
26
+ mode: "contains"
27
+ }];
28
+
29
+ /**
30
+ * Returns true when `url` matches the ignored `pattern` according to its mode.
31
+ *
32
+ * `contains` → case-insensitive substring on the full URL (legacy behavior).
33
+ * `exact` → smart equality: pattern starting with `/` compares URL.pathname,
34
+ * full URLs compare origin+pathname (ignoring query/hash), bare
35
+ * values compare URL.host. Falls back to literal equality if URL
36
+ * parsing fails (e.g. relative URLs).
37
+ */
38
+ export function urlMatchesIgnoredPattern(url, pattern) {
39
+ const lowerUrl = url.toLowerCase();
40
+ const lowerValue = pattern.value.toLowerCase();
41
+ if (pattern.mode === "contains") {
42
+ return lowerUrl.includes(lowerValue);
43
+ }
44
+ try {
45
+ const parsed = new URL(url);
46
+ if (lowerValue.startsWith("/")) {
47
+ return parsed.pathname.toLowerCase() === lowerValue;
48
+ }
49
+ if (lowerValue.startsWith("http://") || lowerValue.startsWith("https://")) {
50
+ return `${parsed.origin}${parsed.pathname}`.toLowerCase() === lowerValue;
51
+ }
52
+ return parsed.host.toLowerCase() === lowerValue;
53
+ } catch {
54
+ return lowerUrl === lowerValue;
55
+ }
56
+ }
57
+
58
+ /** Convenience: does `url` match ANY of the ignored `patterns`? */
59
+ export function isUrlIgnored(url, patterns) {
60
+ if (!url || patterns.length === 0) return false;
61
+ return patterns.some(pattern => urlMatchesIgnoredPattern(url, pattern));
62
+ }
@@ -17,57 +17,11 @@ import { useEffect, useMemo, useState } from "react";
17
17
  import { persistentStorage } from "../utils/persistentStorage.js";
18
18
  import { devToolsStorageKeys } from "../storage/devToolsStorageKeys.js";
19
19
 
20
- /**
21
- * How an ignored-pattern entry should be compared against captured URLs.
22
- *
23
- * - `contains`: case-insensitive substring match on the full URL (legacy behavior).
24
- * - `exact`: smart equality — if pattern starts with `/`, matches URL.pathname;
25
- * if it starts with `http://`/`https://`, matches origin+pathname; otherwise
26
- * matches URL.host.
27
- */
28
-
29
- /** A single exclude pattern with its match mode. */
30
-
31
- /** Patterns hidden by default — Buoy's own license API traffic. */
32
- const DEFAULT_PATTERNS = [{
33
- value: "api.keygen.sh",
34
- mode: "contains"
35
- }];
36
-
37
- /**
38
- * Returns true when `url` matches the ignored `pattern` according to its mode.
39
- *
40
- * `contains` → case-insensitive substring on the full URL (legacy behavior).
41
- * `exact` → smart equality: pattern starting with `/` compares URL.pathname,
42
- * full URLs compare origin+pathname (ignoring query/hash), bare
43
- * values compare URL.host. Falls back to literal equality if URL
44
- * parsing fails (e.g. relative URLs).
45
- */
46
- export function urlMatchesIgnoredPattern(url, pattern) {
47
- const lowerUrl = url.toLowerCase();
48
- const lowerValue = pattern.value.toLowerCase();
49
- if (pattern.mode === "contains") {
50
- return lowerUrl.includes(lowerValue);
51
- }
52
- try {
53
- const parsed = new URL(url);
54
- if (lowerValue.startsWith("/")) {
55
- return parsed.pathname.toLowerCase() === lowerValue;
56
- }
57
- if (lowerValue.startsWith("http://") || lowerValue.startsWith("https://")) {
58
- return `${parsed.origin}${parsed.pathname}`.toLowerCase() === lowerValue;
59
- }
60
- return parsed.host.toLowerCase() === lowerValue;
61
- } catch {
62
- return lowerUrl === lowerValue;
63
- }
64
- }
65
-
66
- /** Convenience: does `url` match ANY of the ignored `patterns`? */
67
- export function isUrlIgnored(url, patterns) {
68
- if (!url || patterns.length === 0) return false;
69
- return patterns.some(pattern => urlMatchesIgnoredPattern(url, pattern));
70
- }
20
+ // Pure matching types/helpers live in ignoredPatternsCore.ts (web-shim-safe);
21
+ // re-exported here so the shared-ui barrel surface is unchanged.
22
+ import { DEFAULT_IGNORED_PATTERNS } from "./ignoredPatternsCore.js";
23
+ export { urlMatchesIgnoredPattern, isUrlIgnored } from "./ignoredPatternsCore.js";
24
+ const DEFAULT_PATTERNS = DEFAULT_IGNORED_PATTERNS;
71
25
 
72
26
  /** Tolerate the legacy `string[]` persisted format and stray junk. */
73
27
  function migratePatterns(raw) {