@buoy-gg/shared-ui 3.0.1 → 3.0.2

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 (89) hide show
  1. package/lib/commonjs/JsModal.js +2 -1
  2. package/lib/commonjs/clipboard/clipboard-impl.js +28 -2
  3. package/lib/commonjs/dataViewer/VirtualizedDataExplorer.js +3 -5
  4. package/lib/commonjs/hooks/safe-area-impl.js +1 -1
  5. package/lib/commonjs/icons/lucide-icons.js +104 -22
  6. package/lib/commonjs/index.js +7 -0
  7. package/lib/commonjs/license/DeviceLimitModal.js +2 -1
  8. package/lib/commonjs/license/LicenseEntryModal.js +2 -1
  9. package/lib/commonjs/license/ManageDevicesModal.js +2 -1
  10. package/lib/commonjs/stores/BaseEventStore.js +72 -2
  11. package/lib/commonjs/ui/components/CompactRow.js +62 -65
  12. package/lib/commonjs/ui/components/EventHistoryViewer/EventPickerModal.js +3 -2
  13. package/lib/commonjs/ui/components/ExpandableSectionWithModal.js +2 -1
  14. package/lib/commonjs/ui/components/WindowControls.js +9 -3
  15. package/lib/commonjs/ui/console/CyberpunkConsoleSection.js +6 -5
  16. package/lib/commonjs/ui/console/GalaxyButton.js +2 -1
  17. package/lib/commonjs/ui/gameUI/components/GameUIStatusHeader.js +2 -1
  18. package/lib/commonjs/utils/absoluteFill.js +28 -0
  19. package/lib/commonjs/utils/index.js +7 -0
  20. package/lib/module/JsModal.js +2 -1
  21. package/lib/module/clipboard/clipboard-impl.js +28 -2
  22. package/lib/module/dataViewer/VirtualizedDataExplorer.js +3 -5
  23. package/lib/module/hooks/safe-area-impl.js +1 -1
  24. package/lib/module/icons/lucide-icons.js +100 -19
  25. package/lib/module/index.js +2 -0
  26. package/lib/module/license/DeviceLimitModal.js +2 -1
  27. package/lib/module/license/LicenseEntryModal.js +2 -1
  28. package/lib/module/license/ManageDevicesModal.js +2 -1
  29. package/lib/module/stores/BaseEventStore.js +72 -2
  30. package/lib/module/ui/components/CompactRow.js +62 -65
  31. package/lib/module/ui/components/EventHistoryViewer/EventPickerModal.js +3 -2
  32. package/lib/module/ui/components/ExpandableSectionWithModal.js +2 -1
  33. package/lib/module/ui/components/WindowControls.js +10 -4
  34. package/lib/module/ui/console/CyberpunkConsoleSection.js +6 -5
  35. package/lib/module/ui/console/GalaxyButton.js +2 -1
  36. package/lib/module/ui/gameUI/components/GameUIStatusHeader.js +2 -1
  37. package/lib/module/utils/absoluteFill.js +24 -0
  38. package/lib/module/utils/index.js +1 -0
  39. package/lib/typescript/commonjs/JsModal.d.ts.map +1 -1
  40. package/lib/typescript/commonjs/clipboard/clipboard-impl.d.ts +3 -2
  41. package/lib/typescript/commonjs/clipboard/clipboard-impl.d.ts.map +1 -1
  42. package/lib/typescript/commonjs/dataViewer/VirtualizedDataExplorer.d.ts.map +1 -1
  43. package/lib/typescript/commonjs/hooks/safe-area-impl.d.ts +1 -1
  44. package/lib/typescript/commonjs/icons/lucide-icons.d.ts +4 -2
  45. package/lib/typescript/commonjs/icons/lucide-icons.d.ts.map +1 -1
  46. package/lib/typescript/commonjs/index.d.ts +1 -1
  47. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  48. package/lib/typescript/commonjs/license/DeviceLimitModal.d.ts.map +1 -1
  49. package/lib/typescript/commonjs/license/LicenseEntryModal.d.ts.map +1 -1
  50. package/lib/typescript/commonjs/license/ManageDevicesModal.d.ts.map +1 -1
  51. package/lib/typescript/commonjs/stores/BaseEventStore.d.ts +28 -0
  52. package/lib/typescript/commonjs/stores/BaseEventStore.d.ts.map +1 -1
  53. package/lib/typescript/commonjs/ui/components/CompactRow.d.ts.map +1 -1
  54. package/lib/typescript/commonjs/ui/components/EventHistoryViewer/EventPickerModal.d.ts.map +1 -1
  55. package/lib/typescript/commonjs/ui/components/ExpandableSectionWithModal.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/ui/components/WindowControls.d.ts.map +1 -1
  57. package/lib/typescript/commonjs/ui/console/CyberpunkConsoleSection.d.ts.map +1 -1
  58. package/lib/typescript/commonjs/ui/console/GalaxyButton.d.ts.map +1 -1
  59. package/lib/typescript/commonjs/ui/gameUI/components/GameUIStatusHeader.d.ts.map +1 -1
  60. package/lib/typescript/commonjs/utils/absoluteFill.d.ts +18 -0
  61. package/lib/typescript/commonjs/utils/absoluteFill.d.ts.map +1 -0
  62. package/lib/typescript/commonjs/utils/index.d.ts +1 -0
  63. package/lib/typescript/commonjs/utils/index.d.ts.map +1 -1
  64. package/lib/typescript/module/JsModal.d.ts.map +1 -1
  65. package/lib/typescript/module/clipboard/clipboard-impl.d.ts +3 -2
  66. package/lib/typescript/module/clipboard/clipboard-impl.d.ts.map +1 -1
  67. package/lib/typescript/module/dataViewer/VirtualizedDataExplorer.d.ts.map +1 -1
  68. package/lib/typescript/module/hooks/safe-area-impl.d.ts +1 -1
  69. package/lib/typescript/module/icons/lucide-icons.d.ts +4 -2
  70. package/lib/typescript/module/icons/lucide-icons.d.ts.map +1 -1
  71. package/lib/typescript/module/index.d.ts +1 -1
  72. package/lib/typescript/module/index.d.ts.map +1 -1
  73. package/lib/typescript/module/license/DeviceLimitModal.d.ts.map +1 -1
  74. package/lib/typescript/module/license/LicenseEntryModal.d.ts.map +1 -1
  75. package/lib/typescript/module/license/ManageDevicesModal.d.ts.map +1 -1
  76. package/lib/typescript/module/stores/BaseEventStore.d.ts +28 -0
  77. package/lib/typescript/module/stores/BaseEventStore.d.ts.map +1 -1
  78. package/lib/typescript/module/ui/components/CompactRow.d.ts.map +1 -1
  79. package/lib/typescript/module/ui/components/EventHistoryViewer/EventPickerModal.d.ts.map +1 -1
  80. package/lib/typescript/module/ui/components/ExpandableSectionWithModal.d.ts.map +1 -1
  81. package/lib/typescript/module/ui/components/WindowControls.d.ts.map +1 -1
  82. package/lib/typescript/module/ui/console/CyberpunkConsoleSection.d.ts.map +1 -1
  83. package/lib/typescript/module/ui/console/GalaxyButton.d.ts.map +1 -1
  84. package/lib/typescript/module/ui/gameUI/components/GameUIStatusHeader.d.ts.map +1 -1
  85. package/lib/typescript/module/utils/absoluteFill.d.ts +18 -0
  86. package/lib/typescript/module/utils/absoluteFill.d.ts.map +1 -0
  87. package/lib/typescript/module/utils/index.d.ts +1 -0
  88. package/lib/typescript/module/utils/index.d.ts.map +1 -1
  89. package/package.json +4 -4
@@ -13,7 +13,12 @@ var _jsxRuntime = require("react/jsx-runtime");
13
13
  // ============================================================================
14
14
  // Global expandable setting — controlled via setExpandableWindowControls()
15
15
  // ============================================================================
16
- let _expandableEnabled = true; // Default ON
16
+
17
+ // The expandable behavior is a touch affordance (big tap targets). On web a
18
+ // mouse clicks the small dots directly, so it's always off there — the
19
+ // setting only applies to native touch platforms.
20
+ const EXPANDABLE_SUPPORTED = _reactNative.Platform.OS !== "web";
21
+ let _expandableEnabled = true; // Default ON (native touch devices)
17
22
 
18
23
  /**
19
24
  * Set whether window controls use the expandable iPad-style behavior.
@@ -73,8 +78,9 @@ function WindowControls({
73
78
  const ToggleModeIcon = mode === "floating" ? _index.DockBottom : _index.FloatWindow;
74
79
  const toggleModeLabel = mode === "floating" ? "Dock to bottom sheet" : "Make floating window";
75
80
 
76
- // When expandable is disabled, render original directly-tappable buttons
77
- if (!_expandableEnabled) {
81
+ // When expandable is disabled (or unsupported on this platform), render
82
+ // original directly-tappable buttons
83
+ if (!_expandableEnabled || !EXPANDABLE_SUPPORTED) {
78
84
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
79
85
  style: styles.container,
80
86
  children: [onMinimize && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.CyberpunkConsoleSection = CyberpunkConsoleSection;
7
7
  var _react = require("react");
8
8
  var _reactNative = require("react-native");
9
+ var _absoluteFill = require("../../utils/absoluteFill.js");
9
10
  var _index = require("../../icons/index.js");
10
11
  var _index2 = require("../gameUI/index.js");
11
12
  var _jsxRuntime = require("react/jsx-runtime");
@@ -484,31 +485,31 @@ const styles = _reactNative.StyleSheet.create({
484
485
  backgroundColor: "rgba(5, 5, 10, 0.6)" // Darker glass background
485
486
  },
486
487
  glassLayer1: {
487
- ..._reactNative.StyleSheet.absoluteFillObject,
488
+ ..._absoluteFill.absoluteFill,
488
489
  backgroundColor: "rgba(10, 10, 15, 0.7)",
489
490
  opacity: 0.8
490
491
  },
491
492
  glassLayer2: {
492
- ..._reactNative.StyleSheet.absoluteFillObject,
493
+ ..._absoluteFill.absoluteFill,
493
494
  backgroundColor: "rgba(15, 15, 25, 0.5)",
494
495
  opacity: 0.6,
495
496
  top: "20%",
496
497
  left: "20%"
497
498
  },
498
499
  glassLayer3: {
499
- ..._reactNative.StyleSheet.absoluteFillObject,
500
+ ..._absoluteFill.absoluteFill,
500
501
  backgroundColor: "rgba(20, 20, 35, 0.3)",
501
502
  opacity: 0.4,
502
503
  top: "40%",
503
504
  left: "40%"
504
505
  },
505
506
  glassShimmer: {
506
- ..._reactNative.StyleSheet.absoluteFillObject,
507
+ ..._absoluteFill.absoluteFill,
507
508
  backgroundColor: "rgba(255, 255, 255, 0.03)",
508
509
  opacity: 0.6
509
510
  },
510
511
  glitchOverlayLayer: {
511
- ..._reactNative.StyleSheet.absoluteFillObject,
512
+ ..._absoluteFill.absoluteFill,
512
513
  borderRadius: 12,
513
514
  borderWidth: 1,
514
515
  justifyContent: "center",
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.GalaxyButton = GalaxyButton;
7
7
  var _react = require("react");
8
8
  var _reactNative = require("react-native");
9
+ var _absoluteFill = require("../../utils/absoluteFill.js");
9
10
  var _jsxRuntime = require("react/jsx-runtime");
10
11
  const {
11
12
  width: screenWidth
@@ -76,7 +77,7 @@ const styles = _reactNative.StyleSheet.create({
76
77
  overflow: "hidden"
77
78
  },
78
79
  starsContainer: {
79
- ..._reactNative.StyleSheet.absoluteFillObject,
80
+ ..._absoluteFill.absoluteFill,
80
81
  overflow: "hidden"
81
82
  },
82
83
  starsLayer: {
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.GameUIStatusHeader = GameUIStatusHeader;
7
7
  var _reactNative = require("react-native");
8
+ var _absoluteFill = require("../../../utils/absoluteFill.js");
8
9
  var _gameUIColors = require("../constants/gameUIColors.js");
9
10
  var _jsxRuntime = require("react/jsx-runtime");
10
11
  /**
@@ -82,7 +83,7 @@ const styles = _reactNative.StyleSheet.create({
82
83
  overflow: "hidden"
83
84
  },
84
85
  glow: {
85
- ..._reactNative.StyleSheet.absoluteFillObject,
86
+ ..._absoluteFill.absoluteFill,
86
87
  opacity: 0.5
87
88
  },
88
89
  content: {
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.absoluteFill = void 0;
7
+ /**
8
+ * Spreadable absolute-fill style.
9
+ *
10
+ * Replaces `StyleSheet.absoluteFillObject`, which was REMOVED in React Native
11
+ * 0.85 (returns `undefined` there). Spreading `undefined` is a silent no-op, so
12
+ * any overlay/backdrop/full-screen Pressable that relied on it collapses to zero
13
+ * size on RN 0.85+ — invisible and untappable.
14
+ *
15
+ * `StyleSheet.absoluteFill` is not a safe replacement for the spread pattern
16
+ * because its type changed across versions: it's a registered style *number* on
17
+ * RN <= 0.84 (spreading a number yields `{}`) and a frozen object only on 0.85+.
18
+ *
19
+ * This plain object works identically on every RN version, whether spread
20
+ * (`{ ...absoluteFill }`) or passed directly (`style={absoluteFill}`).
21
+ */
22
+ const absoluteFill = exports.absoluteFill = {
23
+ position: "absolute",
24
+ top: 0,
25
+ left: 0,
26
+ right: 0,
27
+ bottom: 0
28
+ };
@@ -9,6 +9,12 @@ Object.defineProperty(exports, "Subscribable", {
9
9
  return _subscribable.Subscribable;
10
10
  }
11
11
  });
12
+ Object.defineProperty(exports, "absoluteFill", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _absoluteFill.absoluteFill;
16
+ }
17
+ });
12
18
  Object.defineProperty(exports, "displayValue", {
13
19
  enumerable: true,
14
20
  get: function () {
@@ -195,6 +201,7 @@ Object.defineProperty(exports, "useSafeSegments", {
195
201
  return _safeExpoRouter.useSafeSegments;
196
202
  }
197
203
  });
204
+ var _absoluteFill = require("./absoluteFill.js");
198
205
  var _displayValue = require("./displayValue.js");
199
206
  var _getSafeAreaInsets = require("./getSafeAreaInsets.js");
200
207
  var _persistentStorage = require("./persistentStorage.js");
@@ -14,6 +14,7 @@
14
14
 
15
15
  import { useState, useRef, useEffect, useMemo, useCallback, memo, isValidElement, cloneElement } from "react";
16
16
  import { View, StyleSheet, TouchableWithoutFeedback, Dimensions, PanResponder, Animated, ScrollView, Text } from "react-native";
17
+ import { absoluteFill } from "./utils/absoluteFill.js";
17
18
  import { useSafeAreaInsets } from "./hooks/useSafeAreaInsets.js";
18
19
  import { gameUIColors, buoyColors } from "./ui/gameUI/index.js";
19
20
  import { DraggableHeader, ModalHintBanner, WindowControls } from "./ui/components/index.js";
@@ -1237,7 +1238,7 @@ const JsModalComponent = ({
1237
1238
  // ============================================================================
1238
1239
  const styles = StyleSheet.create({
1239
1240
  fullScreenContainer: {
1240
- ...StyleSheet.absoluteFillObject,
1241
+ ...absoluteFill,
1241
1242
  zIndex: 1000
1242
1243
  },
1243
1244
  bottomSheetWrapper: {
@@ -16,9 +16,17 @@
16
16
  * Fallback chain:
17
17
  * 1. expo-clipboard
18
18
  * 2. @react-native-clipboard/clipboard
19
- * 3. Graceful failure
19
+ * 3. Web clipboard API (navigator.clipboard)
20
+ * 4. Graceful failure
20
21
  */
21
22
 
23
+ // navigator.clipboard isn't in React Native's TS lib — narrow it manually
24
+
25
+ function getWebClipboard() {
26
+ if (typeof navigator === "undefined") return null;
27
+ const clipboard = navigator.clipboard;
28
+ return clipboard && typeof clipboard.writeText === "function" ? clipboard : null;
29
+ }
22
30
  // Grab module references at load time (top-level try-catch for Metro)
23
31
  // Always require both — we decide which actually works at call time
24
32
  let _expoClipboard = null;
@@ -63,6 +71,21 @@ async function detect(text) {
63
71
  return true;
64
72
  } catch {/* rn-clipboard not functional */}
65
73
  }
74
+
75
+ // 3. Web fallback (react-native-web / desktop dashboard / browsers)
76
+ const webClipboard = getWebClipboard();
77
+ if (webClipboard) {
78
+ try {
79
+ await webClipboard.writeText(text);
80
+ _detectedType = "web";
81
+ _clipboardFn = async t => {
82
+ await webClipboard.writeText(t);
83
+ return true;
84
+ };
85
+ _detected = true;
86
+ return true;
87
+ } catch {/* clipboard permission denied */}
88
+ }
66
89
  _detected = true;
67
90
  return false;
68
91
  }
@@ -71,7 +94,10 @@ export const clipboardType = () => {
71
94
  };
72
95
  export const isClipboardAvailable = () => {
73
96
  // Before first use, optimistically return true if we have a module ref
74
- if (!_detected) return _expoClipboard != null || _rnClipboard != null;
97
+ // or the web clipboard API is present
98
+ if (!_detected) {
99
+ return _expoClipboard != null || _rnClipboard != null || getWebClipboard() != null;
100
+ }
75
101
  return _clipboardFn != null;
76
102
  };
77
103
  export const clipboardFunction = async text => {
@@ -723,7 +723,6 @@ const useDataFlattening = (data, maxDepth = 10, autoExpandFirstLevel = false) =>
723
723
  const VirtualizedItemComponent = ({
724
724
  item,
725
725
  onToggleExpanded,
726
- data,
727
726
  index,
728
727
  onSelect,
729
728
  isSelected
@@ -787,9 +786,9 @@ const VirtualizedItemComponent = ({
787
786
  children: formatValue(item.value, item.valueType)
788
787
  })]
789
788
  })]
790
- }), item.id === "root" && data !== undefined && data !== null ? /*#__PURE__*/_jsx(CopyButton, {
791
- value: data,
792
- size: 16,
789
+ }), item.value !== undefined ? /*#__PURE__*/_jsx(CopyButton, {
790
+ value: item.value,
791
+ size: 14,
793
792
  buttonStyle: {
794
793
  marginLeft: 8,
795
794
  marginRight: 8
@@ -874,7 +873,6 @@ export const VirtualizedDataExplorer = ({
874
873
  item: item,
875
874
  index: index,
876
875
  onToggleExpanded: toggleExpanded,
877
- data: data,
878
876
  onSelect: setSelectedIndex,
879
877
  isSelected: selectedIndex === index
880
878
  });
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * Auto-generated safe area implementation
5
5
  * Detected: none
6
- * Generated at: 2026-05-28T20:48:12.491Z
6
+ * Generated at: 2026-06-23T21:16:34.896Z
7
7
  *
8
8
  * DO NOT EDIT - This file is generated by scripts/detect-safe-area.js
9
9
  *
@@ -1361,7 +1361,65 @@ export const Plus = ({
1361
1361
  strokeWidth: strokeWidth
1362
1362
  })]
1363
1363
  });
1364
+
1365
+ // Refresh / reload icon: a smooth 270° ring (open on the right) with a
1366
+ // clockwise triangle arrowhead. Built from the buoy icon-editor design
1367
+ // (smootharc ring + triangle) using the View-based border trick so it stays
1368
+ // crisp at any size. `strokeWidth` is accepted for API compatibility but the
1369
+ // proportions are fixed to keep the perfected look.
1364
1370
  export const RefreshCw = ({
1371
+ size = 24,
1372
+ color = "currentColor",
1373
+ strokeWidth: _strokeWidth = 2,
1374
+ ...props
1375
+ }) => {
1376
+ const RING_RADIUS = 7; // outer radius, centered in the 24x24 viewBox
1377
+ const RING_STROKE = 2.4;
1378
+ const ARROW_SIZE = 5.2;
1379
+ const ARROW_HALF = ARROW_SIZE * 0.577; // equilateral-ish half-width
1380
+ // Arrowhead anchor (from the editor design, mapped into the 0..24 viewBox)
1381
+ const arrowLeft = 12 + 3.162277660168379;
1382
+ const arrowTop = 12 - 2.846049894151541 - ARROW_HALF;
1383
+ return /*#__PURE__*/_jsxs(Svg, {
1384
+ width: size,
1385
+ height: size,
1386
+ viewBox: "0 0 24 24",
1387
+ ...props,
1388
+ children: [/*#__PURE__*/_jsx(View, {
1389
+ style: {
1390
+ position: "absolute",
1391
+ left: 12 - RING_RADIUS,
1392
+ top: 12 - RING_RADIUS,
1393
+ width: RING_RADIUS * 2,
1394
+ height: RING_RADIUS * 2,
1395
+ borderRadius: RING_RADIUS,
1396
+ borderWidth: RING_STROKE,
1397
+ borderTopColor: color,
1398
+ borderLeftColor: color,
1399
+ borderBottomColor: color,
1400
+ borderRightColor: "transparent"
1401
+ }
1402
+ }), /*#__PURE__*/_jsx(View, {
1403
+ style: {
1404
+ position: "absolute",
1405
+ left: arrowLeft,
1406
+ top: arrowTop,
1407
+ width: 0,
1408
+ height: 0,
1409
+ borderTopWidth: ARROW_HALF,
1410
+ borderBottomWidth: ARROW_HALF,
1411
+ borderLeftWidth: ARROW_SIZE,
1412
+ borderTopColor: "transparent",
1413
+ borderBottomColor: "transparent",
1414
+ borderLeftColor: color,
1415
+ transform: [{
1416
+ rotate: "45deg"
1417
+ }]
1418
+ }
1419
+ })]
1420
+ });
1421
+ };
1422
+ export const Home = ({
1365
1423
  size = 24,
1366
1424
  color = "currentColor",
1367
1425
  strokeWidth = 2,
@@ -1372,41 +1430,64 @@ export const RefreshCw = ({
1372
1430
  viewBox: "0 0 24 24",
1373
1431
  ...props,
1374
1432
  children: [/*#__PURE__*/_jsx(Line, {
1375
- x1: 23,
1376
- y1: 4,
1377
- x2: 23,
1378
- y2: 10,
1433
+ x1: 3,
1434
+ y1: 10,
1435
+ x2: 12,
1436
+ y2: 3,
1379
1437
  stroke: color,
1380
1438
  strokeWidth: strokeWidth
1381
1439
  }), /*#__PURE__*/_jsx(Line, {
1382
- x1: 23,
1383
- y1: 10,
1384
- x2: 17,
1440
+ x1: 12,
1441
+ y1: 3,
1442
+ x2: 21,
1385
1443
  y2: 10,
1386
1444
  stroke: color,
1387
1445
  strokeWidth: strokeWidth
1388
1446
  }), /*#__PURE__*/_jsx(Line, {
1389
- x1: 1,
1390
- y1: 20,
1391
- x2: 1,
1392
- y2: 14,
1447
+ x1: 5,
1448
+ y1: 9,
1449
+ x2: 5,
1450
+ y2: 21,
1393
1451
  stroke: color,
1394
1452
  strokeWidth: strokeWidth
1395
1453
  }), /*#__PURE__*/_jsx(Line, {
1396
- x1: 1,
1397
- y1: 14,
1398
- x2: 7,
1399
- y2: 14,
1454
+ x1: 19,
1455
+ y1: 9,
1456
+ x2: 19,
1457
+ y2: 21,
1400
1458
  stroke: color,
1401
1459
  strokeWidth: strokeWidth
1402
- }), /*#__PURE__*/_jsx(Circle, {
1403
- cx: 12,
1404
- cy: 12,
1405
- r: 9,
1460
+ }), /*#__PURE__*/_jsx(Line, {
1461
+ x1: 5,
1462
+ y1: 21,
1463
+ x2: 19,
1464
+ y2: 21,
1465
+ stroke: color,
1466
+ strokeWidth: strokeWidth
1467
+ }), /*#__PURE__*/_jsx(Line, {
1468
+ x1: 10,
1469
+ y1: 21,
1470
+ x2: 10,
1471
+ y2: 15,
1472
+ stroke: color,
1473
+ strokeWidth: strokeWidth
1474
+ }), /*#__PURE__*/_jsx(Line, {
1475
+ x1: 10,
1476
+ y1: 15,
1477
+ x2: 14,
1478
+ y2: 15,
1479
+ stroke: color,
1480
+ strokeWidth: strokeWidth
1481
+ }), /*#__PURE__*/_jsx(Line, {
1482
+ x1: 14,
1483
+ y1: 15,
1484
+ x2: 14,
1485
+ y2: 21,
1406
1486
  stroke: color,
1407
1487
  strokeWidth: strokeWidth
1408
1488
  })]
1409
1489
  });
1490
+ export const HomeIcon = Home;
1410
1491
  export const Search = ({
1411
1492
  size = 24,
1412
1493
  color = "currentColor",
@@ -8,6 +8,8 @@ export * from "./stores/index.js";
8
8
 
9
9
  // Utils exports - selectively export to avoid conflicts
10
10
  export {
11
+ // Absolute-fill style (replaces StyleSheet.absoluteFillObject, removed in RN 0.85)
12
+ absoluteFill,
11
13
  // Display utilities
12
14
  displayValue, parseDisplayValue,
13
15
  // Safe area utilities
@@ -8,6 +8,7 @@
8
8
 
9
9
  import React, { useState, useEffect, useCallback } from "react";
10
10
  import { View, Text, TouchableOpacity, StyleSheet, Modal, ActivityIndicator, ScrollView, Alert, Platform, Linking } from "react-native";
11
+ import { absoluteFill } from "../utils/absoluteFill.js";
11
12
  import { macOSColors } from "../ui/gameUI/constants/macOSDesignSystemColors.js";
12
13
  import { X, Smartphone, Trash2, RefreshCw, AlertTriangle } from "../icons/lucide-icons.js";
13
14
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -266,7 +267,7 @@ const styles = StyleSheet.create({
266
267
  alignItems: "center"
267
268
  },
268
269
  backdrop: {
269
- ...StyleSheet.absoluteFillObject,
270
+ ...absoluteFill,
270
271
  backgroundColor: "rgba(0, 0, 0, 0.8)"
271
272
  },
272
273
  modal: {
@@ -10,6 +10,7 @@
10
10
 
11
11
  import React, { useCallback } from "react";
12
12
  import { View, Text, TouchableOpacity, StyleSheet, Linking, Modal, Platform } from "react-native";
13
+ import { absoluteFill } from "../utils/absoluteFill.js";
13
14
  import { macOSColors } from "../ui/gameUI/constants/macOSDesignSystemColors.js";
14
15
  import { X, Key, Link, FileCode, Copy } from "../icons/lucide-icons.js";
15
16
 
@@ -194,7 +195,7 @@ const styles = StyleSheet.create({
194
195
  alignItems: "center"
195
196
  },
196
197
  backdrop: {
197
- ...StyleSheet.absoluteFillObject,
198
+ ...absoluteFill,
198
199
  backgroundColor: "rgba(0, 0, 0, 0.8)"
199
200
  },
200
201
  modal: {
@@ -9,6 +9,7 @@
9
9
 
10
10
  import React, { useState, useCallback, useEffect } from "react";
11
11
  import { View, Text, TouchableOpacity, StyleSheet, ActivityIndicator, Modal, ScrollView, Alert, Platform } from "react-native";
12
+ import { absoluteFill } from "../utils/absoluteFill.js";
12
13
  import { gameUIColors } from "../ui/gameUI/constants/gameUIColors.js";
13
14
  import { X, Smartphone, Trash2, RefreshCw, CheckCircle, AlertTriangle } from "../icons/lucide-icons.js";
14
15
 
@@ -337,7 +338,7 @@ const styles = StyleSheet.create({
337
338
  alignItems: "center"
338
339
  },
339
340
  backdrop: {
340
- ...StyleSheet.absoluteFillObject,
341
+ ...absoluteFill,
341
342
  backgroundColor: "rgba(0, 0, 0, 0.7)"
342
343
  },
343
344
  modal: {
@@ -59,6 +59,8 @@ import { notifySubscriberCountChange } from "../utils/subscriberCountNotifier.js
59
59
  export class BaseEventStore extends Subscribable {
60
60
  events = [];
61
61
  arrayListeners = new Set();
62
+ captureSuppressed = false;
63
+ clearListeners = new Set();
62
64
  constructor(options) {
63
65
  super();
64
66
  this.maxEvents = options.maxEvents ?? 500;
@@ -92,7 +94,7 @@ export class BaseEventStore extends Subscribable {
92
94
  * Starts capturing if no one was subscribed.
93
95
  */
94
96
  onSubscribe() {
95
- if (this.getTotalSubscriberCount() === 1) {
97
+ if (this.getTotalSubscriberCount() === 1 && !this.captureSuppressed) {
96
98
  this.startCapturing();
97
99
  }
98
100
  notifySubscriberCountChange(this.storeName);
@@ -132,7 +134,7 @@ export class BaseEventStore extends Subscribable {
132
134
  this.arrayListeners.add(listener);
133
135
 
134
136
  // Start capturing if this is the first subscriber
135
- if (wasEmpty) {
137
+ if (wasEmpty && !this.captureSuppressed) {
136
138
  this.startCapturing();
137
139
  }
138
140
 
@@ -223,6 +225,74 @@ export class BaseEventStore extends Subscribable {
223
225
  clearEvents() {
224
226
  this.events = [];
225
227
  this.notifyArrayListeners();
228
+ this.clearListeners.forEach(listener => {
229
+ try {
230
+ listener();
231
+ } catch {
232
+ // Ignore listener errors
233
+ }
234
+ });
235
+ }
236
+
237
+ /**
238
+ * Listen for clearEvents() calls. Used in remote mirror mode to forward a
239
+ * clear performed in the dashboard UI to the synced device.
240
+ */
241
+ onClear(listener) {
242
+ this.clearListeners.add(listener);
243
+ return () => {
244
+ this.clearListeners.delete(listener);
245
+ };
246
+ }
247
+
248
+ // ===========================================================================
249
+ // REMOTE MIRROR MODE
250
+ // ===========================================================================
251
+
252
+ /**
253
+ * Suppress the auto start/stop capture lifecycle. Use when this store acts
254
+ * as a mirror of a remote device's events (e.g. the desktop dashboard):
255
+ * events arrive via replaceEvents() and the local interceptors must never
256
+ * start — on the dashboard they would capture the dashboard's own traffic.
257
+ */
258
+ disableCapture() {
259
+ this.captureSuppressed = true;
260
+ if (this.isCapturing()) {
261
+ this.stopCapturing();
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Replace the entire event list and notify array listeners. Used in remote
267
+ * mirror mode where full snapshots arrive from a synced device.
268
+ */
269
+ replaceEvents(events) {
270
+ this.events = this.dedupeById(events).slice(0, this.maxEvents);
271
+ this.notifyArrayListeners();
272
+ }
273
+
274
+ /**
275
+ * Drop events that share an `id` with an earlier event, keeping the first
276
+ * (newest, since events are newest-first) occurrence. Remote snapshots can
277
+ * momentarily carry duplicate ids — e.g. a device's request counter resets
278
+ * on reload while older events with the same id are still in the buffer —
279
+ * which makes React list keys (keyed on `id`) collide. Events without an
280
+ * `id` are passed through untouched.
281
+ */
282
+ dedupeById(events) {
283
+ const seen = new Set();
284
+ const result = [];
285
+ for (const event of events) {
286
+ const id = event?.id;
287
+ if (id == null) {
288
+ result.push(event);
289
+ continue;
290
+ }
291
+ if (seen.has(id)) continue;
292
+ seen.add(id);
293
+ result.push(event);
294
+ }
295
+ return result;
226
296
  }
227
297
 
228
298
  /**