react-os-shell 4.2.2 → 4.4.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.
Files changed (58) hide show
  1. package/README.md +5 -0
  2. package/dist/{Browser-TPAIWPZX.js → Browser-YVV3HOKJ.js} +6 -6
  3. package/dist/{Browser-TPAIWPZX.js.map → Browser-YVV3HOKJ.js.map} +1 -1
  4. package/dist/{Calculator-RCNLBRHK.js → Calculator-GBC3FLAJ.js} +5 -5
  5. package/dist/{Calculator-RCNLBRHK.js.map → Calculator-GBC3FLAJ.js.map} +1 -1
  6. package/dist/ConfirmDialog-R7NZVI2V.js +4 -0
  7. package/dist/{ConfirmDialog-QMBUHDXK.js.map → ConfirmDialog-R7NZVI2V.js.map} +1 -1
  8. package/dist/{CurrencyConverter-CM5OTXWF.js → CurrencyConverter-URPZQBNV.js} +5 -5
  9. package/dist/{CurrencyConverter-CM5OTXWF.js.map → CurrencyConverter-URPZQBNV.js.map} +1 -1
  10. package/dist/{Documents-3BPIAGQT.js → Documents-5SFFQZBP.js} +6 -6
  11. package/dist/{Documents-3BPIAGQT.js.map → Documents-5SFFQZBP.js.map} +1 -1
  12. package/dist/{Files-CR3L5C2L.js → Files-YQJRHUGF.js} +8 -8
  13. package/dist/{Files-CR3L5C2L.js.map → Files-YQJRHUGF.js.map} +1 -1
  14. package/dist/{Notepad-C6JHCCF2.js → Notepad-FUQ74K4N.js} +6 -6
  15. package/dist/{Notepad-C6JHCCF2.js.map → Notepad-FUQ74K4N.js.map} +1 -1
  16. package/dist/{PomodoroTimer-EZMBYLQX.js → PomodoroTimer-QQOMBVP3.js} +6 -6
  17. package/dist/{PomodoroTimer-EZMBYLQX.js.map → PomodoroTimer-QQOMBVP3.js.map} +1 -1
  18. package/dist/{Preview-TFDEMTSH.js → Preview-OP4IUJFT.js} +6 -6
  19. package/dist/{Preview-TFDEMTSH.js.map → Preview-OP4IUJFT.js.map} +1 -1
  20. package/dist/{Sidebar-VF2IOOFX.js → Sidebar-E553SOXC.js} +4 -4
  21. package/dist/{Sidebar-VF2IOOFX.js.map → Sidebar-E553SOXC.js.map} +1 -1
  22. package/dist/{Spreadsheet-FRUPALW4.js → Spreadsheet-GADWOYDX.js} +6 -6
  23. package/dist/{Spreadsheet-FRUPALW4.js.map → Spreadsheet-GADWOYDX.js.map} +1 -1
  24. package/dist/{Stock-HL42NPTM.js → Stock-33C4U7CR.js} +5 -5
  25. package/dist/{Stock-HL42NPTM.js.map → Stock-33C4U7CR.js.map} +1 -1
  26. package/dist/{Weather-N74AKS6V.js → Weather-G3XHTB75.js} +5 -5
  27. package/dist/{Weather-N74AKS6V.js.map → Weather-G3XHTB75.js.map} +1 -1
  28. package/dist/{WorldClock-A2EDB57H.js → WorldClock-ZUVI3LBN.js} +5 -5
  29. package/dist/{WorldClock-A2EDB57H.js.map → WorldClock-ZUVI3LBN.js.map} +1 -1
  30. package/dist/apps/index.js +18 -18
  31. package/dist/chunk-3NZSR3ZG.js +20 -0
  32. package/dist/chunk-3NZSR3ZG.js.map +1 -0
  33. package/dist/{chunk-QJXXSMPG.js → chunk-5HCDJPX7.js} +3 -3
  34. package/dist/{chunk-QJXXSMPG.js.map → chunk-5HCDJPX7.js.map} +1 -1
  35. package/dist/{chunk-6Q4KYGMH.js → chunk-DKDD5KXG.js} +14 -3
  36. package/dist/chunk-DKDD5KXG.js.map +1 -0
  37. package/dist/{chunk-SI4XHEMM.js → chunk-EF5W3EXL.js} +285 -19
  38. package/dist/chunk-EF5W3EXL.js.map +1 -0
  39. package/dist/chunk-GDOCYHAG.js +7 -0
  40. package/dist/{chunk-D36OPVAT.js.map → chunk-GDOCYHAG.js.map} +1 -1
  41. package/dist/{chunk-CWFH34LM.js → chunk-LBY7KH2W.js} +3 -3
  42. package/dist/{chunk-CWFH34LM.js.map → chunk-LBY7KH2W.js.map} +1 -1
  43. package/dist/{chunk-5TU6DT3E.js → chunk-LSFZJZTQ.js} +3 -3
  44. package/dist/{chunk-5TU6DT3E.js.map → chunk-LSFZJZTQ.js.map} +1 -1
  45. package/dist/{chunk-RKNJSV2T.js → chunk-PZTYN2RX.js} +5 -5
  46. package/dist/{chunk-RKNJSV2T.js.map → chunk-PZTYN2RX.js.map} +1 -1
  47. package/dist/{chunk-52RFL3OQ.js → chunk-QAKURGOG.js} +3 -3
  48. package/dist/{chunk-52RFL3OQ.js.map → chunk-QAKURGOG.js.map} +1 -1
  49. package/dist/index.d.ts +493 -157
  50. package/dist/index.js +255 -85
  51. package/dist/index.js.map +1 -1
  52. package/package.json +3 -2
  53. package/dist/ConfirmDialog-QMBUHDXK.js +0 -4
  54. package/dist/chunk-6Q4KYGMH.js.map +0 -1
  55. package/dist/chunk-ADJ3CERD.js +0 -31
  56. package/dist/chunk-ADJ3CERD.js.map +0 -1
  57. package/dist/chunk-D36OPVAT.js +0 -7
  58. package/dist/chunk-SI4XHEMM.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { subscribePomo, getPomoSnapshot } from './chunk-5TU6DT3E.js';
2
- export { setShellTodoProvider } from './chunk-5TU6DT3E.js';
1
+ import { subscribePomo, getPomoSnapshot } from './chunk-LSFZJZTQ.js';
2
+ export { setShellTodoProvider } from './chunk-LSFZJZTQ.js';
3
3
  export { ImageAnnotator_default as ImageAnnotator } from './chunk-QU5L5ZYM.js';
4
4
  import { PREVIEW_OPENED_EVENT, openPreviewFile } from './chunk-DINNWYVB.js';
5
5
  export { Breadcrumbs } from './chunk-DINNWYVB.js';
@@ -7,20 +7,20 @@ import { SidebarLayout } from './chunk-YG45DAUT.js';
7
7
  export { SidebarLayout } from './chunk-YG45DAUT.js';
8
8
  import { toast_default } from './chunk-VENYVK3L.js';
9
9
  export { toast_default as toast } from './chunk-VENYVK3L.js';
10
- import { publishDesktopFolders, requestFilesTrashView, FolderGlyph, requestFilesDesktopFolderView, FileIconTile } from './chunk-52RFL3OQ.js';
10
+ import { publishDesktopFolders, requestFilesTrashView, FolderGlyph, requestFilesDesktopFolderView, FileIconTile } from './chunk-QAKURGOG.js';
11
11
  import { setPdfPreview } from './chunk-BV6SQ46N.js';
12
12
  import { playNotification, playStartup, soundsEnabled, getSoundConfig, SOUND_PACK_KEYS, SOUND_PACKS, SOUND_TYPES, SOUND_TYPE_LABELS, setSoundForType, previewSound, setAllSounds, playLogout } from './chunk-D7PYW2QS.js';
13
- import { useAuth, useShellAuth } from './chunk-ADJ3CERD.js';
14
- export { ShellAuthProvider, setShellAuthBridge, useShellAuth } from './chunk-ADJ3CERD.js';
13
+ import { useAuth } from './chunk-3NZSR3ZG.js';
14
+ export { setShellAuthBridge } from './chunk-3NZSR3ZG.js';
15
15
  import { EditableGrid } from './chunk-GP4Y3VCB.js';
16
16
  export { EditableGrid } from './chunk-GP4Y3VCB.js';
17
17
  import './chunk-4SQU5YV6.js';
18
- import { APP_VERSION, VERSION } from './chunk-D36OPVAT.js';
19
- export { VERSION } from './chunk-D36OPVAT.js';
20
- import { registerModalEscapeInterceptor, useIsMobile, useWindowManager, PopupMenu, PopupMenuLabel, PopupMenuDivider, PopupMenuItem, WINDOW_REGISTRY, isPageEntry, Modal, useShellPrefs, SIDEBAR_STRIP_W, forgetMaximizedWindowBoxes, ModalActions, useModalActive, client_default, LoadingSpinner, isShellApiClientConfigured, CancelButton, setWindowPosition } from './chunk-SI4XHEMM.js';
21
- export { CancelButton, ConfirmProvider, CopyButton, DocFavStar, Modal, ModalActions, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, ShellPrefsProvider, WindowCrashedFallback, WindowErrorBoundary, WindowManagerProvider, WindowTitle, commitExposeHighlight, confirm, confirmDestructive, exitExposeMode, getActiveWindowRoute, getExposeHighlight, getWindowPosition, isEntityEntry, isPageEntry, prompt, registerModalEscapeInterceptor, setExposeHighlight, setShellApiClient, setShellWindowRegistry, setWindowDefaultPosition, setWindowPosition, subscribeExposeHighlight, toggleExposeMode, useLocalStoragePrefs, useModalActive, useShellPrefs, useWidgetSettings, useWindowManager, useWindowMenuItem, useWindowTitle } from './chunk-SI4XHEMM.js';
22
- import { glassStyle, startMenuCategories, navSections, isSection, GLASS_INPUT_BG, navIcons, isReachable, sectionIcons, visibleChildren } from './chunk-6Q4KYGMH.js';
23
- export { GLASS_DIVIDER, GLASS_INPUT_BG, glassStyle, setShellNavIcons } from './chunk-6Q4KYGMH.js';
18
+ import { APP_VERSION, VERSION } from './chunk-GDOCYHAG.js';
19
+ export { VERSION } from './chunk-GDOCYHAG.js';
20
+ import { registerModalEscapeInterceptor, useIsMobile, useWindowManager, PopupMenu, PopupMenuLabel, PopupMenuDivider, PopupMenuItem, WINDOW_REGISTRY, isPageEntry, Modal, setPerfCollecting, drainPerfEvents, useShellPrefs, markMenuOpen, SIDEBAR_STRIP_W, forgetMaximizedWindowBoxes, ModalActions, useModalActive, client_default, LoadingSpinner, isShellApiClientConfigured, useUndo, CancelButton, setWindowPosition } from './chunk-EF5W3EXL.js';
21
+ export { CancelButton, ConfirmProvider, CopyButton, DocFavStar, Modal, ModalActions, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, ShellPrefsProvider, UndoProvider, WindowCrashedFallback, WindowErrorBoundary, WindowManagerProvider, WindowTitle, beginWindowGesture, commitExposeHighlight, confirm, confirmDestructive, exitExposeMode, getActiveWindowRoute, getExposeHighlight, getWindowPosition, isEntityEntry, isPageEntry, markMenuOpen, prompt, registerModalEscapeInterceptor, setExposeHighlight, setShellApiClient, setShellWindowRegistry, setWindowDefaultPosition, setWindowPosition, subscribeExposeHighlight, toggleExposeMode, useIsActiveWindow, useLocalStoragePrefs, useModalActive, useShellPrefs, useUndo, useUndoable, useUndoableState, useWidgetSettings, useWindowManager, useWindowMenuItem, useWindowTitle } from './chunk-EF5W3EXL.js';
22
+ import { glassStyle, startMenuCategories, navSections, isSection, GLASS_INPUT_BG, navIcons, isReachable, sectionIcons, useShellAuth, visibleChildren } from './chunk-DKDD5KXG.js';
23
+ export { GLASS_DIVIDER, GLASS_INPUT_BG, ShellAuthProvider, glassStyle, setShellNavIcons, useShellAuth } from './chunk-DKDD5KXG.js';
24
24
  import { createContext, lazy, forwardRef, useCallback, useState, useRef, useId, useEffect, useLayoutEffect, useMemo, useContext, isValidElement, cloneElement, Suspense, Fragment as Fragment$1, Children, useSyncExternalStore } from 'react';
25
25
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
26
26
  import { Dialog, DialogBackdrop, DialogPanel } from '@headlessui/react';
@@ -177,6 +177,8 @@ var CMD_S = isMac ? "\u2318S" : "Ctrl+S";
177
177
  var CMD_K = isMac ? "\u2318K" : "Ctrl+K";
178
178
  var CMD_DOT = isMac ? "\u2318." : "Ctrl+.";
179
179
  var CMD_A = isMac ? "\u2318A" : "Ctrl+A";
180
+ var CMD_Z = isMac ? "\u2318Z" : "Ctrl+Z";
181
+ var CMD_SHIFT_Z = isMac ? "\u2318\u21E7Z" : "Ctrl+Shift+Z";
180
182
  var ALT_SHIFT_D = isMac ? "\u2325\u21E7D" : "Alt+Shift+D";
181
183
  var ALT_SHIFT_E = isMac ? "\u2325\u21E7E" : "Alt+Shift+E";
182
184
  var ALT_SHIFT_N = isMac ? "\u2325\u21E7N" : "Alt+Shift+N";
@@ -211,6 +213,8 @@ var sections = [
211
213
  { keys: CMD_ENTER, description: "Submit" },
212
214
  { keys: CMD_S, description: "Save" },
213
215
  { keys: ALT_SHIFT_D, description: "Save as new (duplicate)" },
216
+ { keys: CMD_Z, description: "Undo the last change to the form" },
217
+ { keys: CMD_SHIFT_Z, description: "Redo it" },
214
218
  { keys: "ESC", description: "Close modal" }
215
219
  ]
216
220
  }
@@ -1424,15 +1428,28 @@ function classify(reading) {
1424
1428
  }
1425
1429
 
1426
1430
  // src/shell/perfLog.ts
1431
+ var ACTIVITY_ORDER = [
1432
+ { kind: "submenu", test: (r) => r.submenus > 0 },
1433
+ { kind: "menu", test: (r) => r.menus > 0 },
1434
+ { kind: "resize", test: (r) => r.resizeMs > 0 },
1435
+ { kind: "move", test: (r) => r.moveMs > 0 },
1436
+ { kind: "scroll", test: (r) => r.scrolls > 0 },
1437
+ { kind: "type", test: (r) => r.keys > 0 },
1438
+ { kind: "click", test: (r) => r.clicks > 0 || r.dragMs > 0 }
1439
+ ];
1440
+ function classifyActivity(r) {
1441
+ return ACTIVITY_ORDER.find((entry) => entry.test(r))?.kind ?? "idle";
1442
+ }
1427
1443
  var LOG_CAP = 2400;
1428
1444
  var MIN_GROUP_SAMPLES = 4;
1445
+ var MIN_EVENT_SAMPLES = 2;
1429
1446
  function appendRecord(log, record, cap = LOG_CAP) {
1430
1447
  const next = log.length >= cap ? log.slice(log.length - cap + 1) : log.slice();
1431
1448
  next.push(record);
1432
1449
  return next;
1433
1450
  }
1434
1451
  function isInteracting(r) {
1435
- return r.clicks > 0 || r.keys > 0 || r.scrolls > 0 || r.dragMs > 0;
1452
+ return classifyActivity(r) !== "idle";
1436
1453
  }
1437
1454
  function median(values) {
1438
1455
  if (!values.length) return 0;
@@ -1441,7 +1458,27 @@ function median(values) {
1441
1458
  return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
1442
1459
  }
1443
1460
  function group(records) {
1444
- return { samples: records.length, medianFps: median(records.map((r) => r.fps)) };
1461
+ const measured = records.filter((r) => r.fps > 0);
1462
+ return {
1463
+ samples: records.length,
1464
+ medianFps: median(measured.map((r) => r.fps)),
1465
+ worstMs: records.reduce((worst, r) => r.worstMs > worst ? r.worstMs : worst, 0),
1466
+ stalls: records.length - measured.length
1467
+ };
1468
+ }
1469
+ function bucket(records, keyOf) {
1470
+ const map = /* @__PURE__ */ new Map();
1471
+ for (const r of records) {
1472
+ const key = keyOf(r);
1473
+ if (key === null) continue;
1474
+ const existing = map.get(key);
1475
+ if (existing) existing.push(r);
1476
+ else map.set(key, [r]);
1477
+ }
1478
+ return map;
1479
+ }
1480
+ function rankWorstFirst(groups) {
1481
+ return groups.sort((a, b) => a.medianFps - b.medianFps);
1445
1482
  }
1446
1483
  function summariseLog(log) {
1447
1484
  const empty = {
@@ -1452,37 +1489,34 @@ function summariseLog(log) {
1452
1489
  verdictShare: { smooth: 0, gpu: 0, cpu: 0, unknown: 0 },
1453
1490
  interacting: null,
1454
1491
  idle: null,
1492
+ byActivity: [],
1455
1493
  byWindowCount: [],
1456
- worstWindows: []
1494
+ worstWindows: [],
1495
+ worstMenus: []
1457
1496
  };
1458
1497
  if (!log.length) return empty;
1459
- const measured = log.filter((r) => r.fps > 0);
1460
1498
  const verdictShare = { ...empty.verdictShare };
1461
1499
  for (const r of log) verdictShare[r.verdict] += 1 / log.length;
1462
- const interacting = measured.filter(isInteracting);
1463
- const idle = measured.filter((r) => !isInteracting(r));
1464
- const byWindows = /* @__PURE__ */ new Map();
1465
- const byKey = /* @__PURE__ */ new Map();
1466
- for (const r of measured) {
1467
- const bucket = byWindows.get(r.windows);
1468
- if (bucket) bucket.push(r);
1469
- else byWindows.set(r.windows, [r]);
1470
- if (r.active) {
1471
- const keyed = byKey.get(r.active);
1472
- if (keyed) keyed.push(r);
1473
- else byKey.set(r.active, [r]);
1474
- }
1475
- }
1500
+ const interacting = log.filter(isInteracting);
1501
+ const idle = log.filter((r) => !isInteracting(r));
1476
1502
  return {
1477
1503
  samples: log.length,
1478
1504
  durationMs: log[log.length - 1].t - log[0].t,
1479
- medianFps: median(measured.map((r) => r.fps)),
1505
+ medianFps: median(log.filter((r) => r.fps > 0).map((r) => r.fps)),
1480
1506
  worstFrameMs: log.reduce((worst, r) => r.worstMs > worst ? r.worstMs : worst, 0),
1481
1507
  verdictShare,
1482
1508
  interacting: interacting.length >= MIN_GROUP_SAMPLES ? group(interacting) : null,
1483
1509
  idle: idle.length >= MIN_GROUP_SAMPLES ? group(idle) : null,
1484
- byWindowCount: [...byWindows.entries()].filter(([, rs]) => rs.length >= MIN_GROUP_SAMPLES).map(([windows, rs]) => ({ windows, ...group(rs) })).sort((a, b) => a.windows - b.windows),
1485
- worstWindows: [...byKey.entries()].filter(([, rs]) => rs.length >= MIN_GROUP_SAMPLES).map(([key, rs]) => ({ key, ...group(rs) })).sort((a, b) => a.medianFps - b.medianFps)
1510
+ byActivity: rankWorstFirst(
1511
+ [...bucket(log, classifyActivity).entries()].filter(([, rs]) => rs.length >= MIN_EVENT_SAMPLES).map(([kind, rs]) => ({ kind, ...group(rs) }))
1512
+ ),
1513
+ byWindowCount: [...bucket(log, (r) => r.windows).entries()].filter(([, rs]) => rs.length >= MIN_GROUP_SAMPLES).map(([windows, rs]) => ({ windows, ...group(rs) })).sort((a, b) => a.windows - b.windows),
1514
+ worstWindows: rankWorstFirst(
1515
+ [...bucket(log, (r) => r.active).entries()].filter(([, rs]) => rs.length >= MIN_GROUP_SAMPLES).map(([key, rs]) => ({ key, ...group(rs) }))
1516
+ ),
1517
+ worstMenus: rankWorstFirst(
1518
+ [...bucket(log, (r) => r.menuKey).entries()].filter(([, rs]) => rs.length >= MIN_EVENT_SAMPLES).map(([key, rs]) => ({ key, ...group(rs) }))
1519
+ )
1486
1520
  };
1487
1521
  }
1488
1522
  var CSV_COLUMNS = [
@@ -1498,7 +1532,12 @@ var CSV_COLUMNS = [
1498
1532
  "clicks",
1499
1533
  "keys",
1500
1534
  "scrolls",
1501
- "dragMs"
1535
+ "dragMs",
1536
+ "menus",
1537
+ "submenus",
1538
+ "menuKey",
1539
+ "moveMs",
1540
+ "resizeMs"
1502
1541
  ];
1503
1542
  function toCsv(log) {
1504
1543
  const cell = (value) => {
@@ -1516,6 +1555,7 @@ var WINDOW_MS = 1e3;
1516
1555
  var HISTORY = 40;
1517
1556
  var PERSIST_EVERY_MS = 1e4;
1518
1557
  var LOG_STORAGE_KEY = "react-os-shell:perf-log";
1558
+ var MAX_DRAG_GAP_MS = 100;
1519
1559
  var VERDICT_STYLES = {
1520
1560
  smooth: { dot: "bg-emerald-400", text: "text-emerald-300" },
1521
1561
  gpu: { dot: "bg-amber-400", text: "text-amber-300" },
@@ -1537,7 +1577,7 @@ function readWindows() {
1537
1577
  });
1538
1578
  return { windows: panels.length, active };
1539
1579
  }
1540
- function PerfStats({ onClose, className = "" }) {
1580
+ function PerfStats({ onClose, onSubmit, className = "" }) {
1541
1581
  const framesRef = useRef([]);
1542
1582
  const blockedMsRef = useRef(0);
1543
1583
  const activityRef = useRef({ clicks: 0, keys: 0, scrolls: 0, dragMs: 0 });
@@ -1545,11 +1585,20 @@ function PerfStats({ onClose, className = "" }) {
1545
1585
  const logRef = useRef([]);
1546
1586
  const startedAtRef = useRef(0);
1547
1587
  const persistedAtRef = useRef(0);
1588
+ const snapshotRef = useRef(null);
1548
1589
  const [longTaskSupported, setLongTaskSupported] = useState(false);
1549
1590
  const [reading, setReading] = useState({ fps: 0, frameMs: 0, worstMs: 0, blockedPct: null });
1550
1591
  const [history, setHistory] = useState([]);
1551
1592
  const [heap, setHeap] = useState(null);
1552
1593
  const [logSize, setLogSize] = useState(0);
1594
+ const [composing, setComposing] = useState(false);
1595
+ const [message, setMessage] = useState("");
1596
+ const [sending, setSending] = useState(false);
1597
+ const [sendError, setSendError] = useState(null);
1598
+ useEffect(() => {
1599
+ setPerfCollecting(true);
1600
+ return () => setPerfCollecting(false);
1601
+ }, []);
1553
1602
  useEffect(() => {
1554
1603
  let raf = 0;
1555
1604
  const tick = (now) => {
@@ -1584,8 +1633,9 @@ function PerfStats({ onClose, className = "" }) {
1584
1633
  const onPointerMove = () => {
1585
1634
  if (!dragRef.current.down) return;
1586
1635
  const now = performance.now();
1587
- activityRef.current.dragMs += now - dragRef.current.since;
1636
+ const gap = now - dragRef.current.since;
1588
1637
  dragRef.current.since = now;
1638
+ if (gap <= MAX_DRAG_GAP_MS) activityRef.current.dragMs += gap;
1589
1639
  };
1590
1640
  const onPointerUp = () => {
1591
1641
  dragRef.current.down = false;
@@ -1639,6 +1689,7 @@ function PerfStats({ onClose, className = "" }) {
1639
1689
  const heapMB = memory ? memory.usedJSHeapSize / 1048576 : null;
1640
1690
  const nextReading = { ...frames, blockedPct };
1641
1691
  const activity = activityRef.current;
1692
+ const events = drainPerfEvents();
1642
1693
  const { windows, active } = readWindows();
1643
1694
  logRef.current = appendRecord(logRef.current, {
1644
1695
  t: Math.round(now - startedAtRef.current),
@@ -1651,7 +1702,12 @@ function PerfStats({ onClose, className = "" }) {
1651
1702
  clicks: activity.clicks,
1652
1703
  keys: activity.keys,
1653
1704
  scrolls: activity.scrolls,
1654
- dragMs: Math.round(activity.dragMs)
1705
+ dragMs: Math.round(activity.dragMs),
1706
+ menus: events.menus,
1707
+ submenus: events.submenus,
1708
+ menuKey: events.menuKey,
1709
+ moveMs: Math.round(events.moveMs),
1710
+ resizeMs: Math.round(events.resizeMs)
1655
1711
  });
1656
1712
  activity.clicks = 0;
1657
1713
  activity.keys = 0;
@@ -1668,38 +1724,65 @@ function PerfStats({ onClose, className = "" }) {
1668
1724
  }, FLUSH_MS);
1669
1725
  return () => clearInterval(id);
1670
1726
  }, [longTaskSupported, persist]);
1671
- const download = useCallback((body, type, extension) => {
1672
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
1673
- const url = URL.createObjectURL(new Blob([body], { type }));
1674
- const link = document.createElement("a");
1675
- link.href = url;
1676
- link.download = `perf-log-${stamp}.${extension}`;
1677
- link.click();
1678
- URL.revokeObjectURL(url);
1727
+ const beginReport = useCallback(() => {
1728
+ snapshotRef.current = logRef.current.slice();
1729
+ setSendError(null);
1730
+ setComposing(true);
1679
1731
  }, []);
1680
- const exportJson = useCallback(() => {
1681
- download(
1682
- JSON.stringify(
1732
+ const verdict = classify(reading);
1733
+ const buildReport = useCallback(() => {
1734
+ const records = snapshotRef.current ?? logRef.current;
1735
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
1736
+ return {
1737
+ message,
1738
+ filename: `perf-log-${stamp}.json`,
1739
+ summary: summariseLog(records),
1740
+ verdict: verdict.label,
1741
+ json: JSON.stringify(
1683
1742
  {
1684
1743
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1744
+ message,
1745
+ verdict: verdict.label,
1685
1746
  userAgent: navigator.userAgent,
1686
1747
  screen: { width: window.screen.width, height: window.screen.height, dpr: window.devicePixelRatio },
1687
1748
  reducedTransparency: document.documentElement.classList.contains("rosh-reduce-transparency"),
1688
- summary: summariseLog(logRef.current),
1689
- records: logRef.current
1749
+ summary: summariseLog(records),
1750
+ records
1690
1751
  },
1691
1752
  null,
1692
1753
  2
1693
- ),
1694
- "application/json",
1695
- "json"
1696
- );
1697
- }, [download]);
1698
- const exportCsv = useCallback(() => download(toCsv(logRef.current), "text/csv", "csv"), [download]);
1699
- const verdict = classify(reading);
1754
+ )
1755
+ };
1756
+ }, [message, verdict.label]);
1757
+ const downloadReport = useCallback((report) => {
1758
+ const url = URL.createObjectURL(new Blob([report.json], { type: "application/json" }));
1759
+ const link = document.createElement("a");
1760
+ link.href = url;
1761
+ link.download = report.filename;
1762
+ link.click();
1763
+ URL.revokeObjectURL(url);
1764
+ }, []);
1765
+ const send = useCallback(async () => {
1766
+ const report = buildReport();
1767
+ setSending(true);
1768
+ setSendError(null);
1769
+ try {
1770
+ if (onSubmit) await onSubmit(report);
1771
+ else downloadReport(report);
1772
+ snapshotRef.current = null;
1773
+ setMessage("");
1774
+ setComposing(false);
1775
+ } catch (err) {
1776
+ setSendError(err instanceof Error && err.message ? err.message : "Could not send. Try again.");
1777
+ } finally {
1778
+ setSending(false);
1779
+ }
1780
+ }, [buildReport, downloadReport, onSubmit]);
1700
1781
  const styles = VERDICT_STYLES[verdict.kind];
1701
1782
  const peak = Math.max(60, ...history);
1702
1783
  const loggedSeconds = Math.round(logSize * FLUSH_MS / 1e3);
1784
+ const sendLabel = onSubmit ? "Send" : "Download";
1785
+ const reportSamples = snapshotRef.current?.length ?? logSize;
1703
1786
  return /* @__PURE__ */ jsxs(
1704
1787
  "div",
1705
1788
  {
@@ -1762,34 +1845,73 @@ function PerfStats({ onClose, className = "" }) {
1762
1845
  ] }),
1763
1846
  /* @__PURE__ */ jsx("p", { className: "mt-0.5 leading-snug text-white/45", children: verdict.detail })
1764
1847
  ] }),
1765
- /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center justify-between border-t border-white/10 pt-1.5", children: [
1848
+ /* @__PURE__ */ jsx("div", { className: "mt-2 border-t border-white/10 pt-1.5", children: composing ? (
1849
+ /* Composer. `select-text` because the panel as a whole is
1850
+ `select-none` — without it the user cannot correct their own
1851
+ sentence. */
1852
+ /* @__PURE__ */ jsxs("div", { className: "select-text space-y-1.5", children: [
1853
+ /* @__PURE__ */ jsx("label", { className: "block text-white/40", htmlFor: "perf-report-message", children: "What were you doing?" }),
1854
+ /* @__PURE__ */ jsx(
1855
+ "textarea",
1856
+ {
1857
+ id: "perf-report-message",
1858
+ value: message,
1859
+ onChange: (e) => setMessage(e.target.value),
1860
+ rows: 3,
1861
+ autoFocus: true,
1862
+ placeholder: "e.g. opening the second-level menu stutters",
1863
+ className: "w-full resize-none rounded border border-white/15 bg-black/40 px-1.5 py-1 font-mono text-[10px] text-white/90 placeholder:text-white/25 focus:border-white/40 focus:outline-none"
1864
+ }
1865
+ ),
1866
+ sendError && /* @__PURE__ */ jsx("p", { className: "leading-snug text-rose-300", children: sendError }),
1867
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1868
+ /* @__PURE__ */ jsxs("span", { className: "text-white/35", children: [
1869
+ reportSamples,
1870
+ " samples"
1871
+ ] }),
1872
+ /* @__PURE__ */ jsxs("span", { className: "flex gap-1", children: [
1873
+ /* @__PURE__ */ jsx(
1874
+ "button",
1875
+ {
1876
+ onClick: () => {
1877
+ setComposing(false);
1878
+ snapshotRef.current = null;
1879
+ setSendError(null);
1880
+ },
1881
+ disabled: sending,
1882
+ className: "rounded border border-white/15 px-1.5 py-0.5 text-white/50 transition-colors hover:border-white/30 hover:text-white disabled:opacity-40",
1883
+ children: "Cancel"
1884
+ }
1885
+ ),
1886
+ /* @__PURE__ */ jsx(
1887
+ "button",
1888
+ {
1889
+ onClick: send,
1890
+ disabled: sending,
1891
+ className: "rounded border border-white/30 bg-white/10 px-1.5 py-0.5 text-white/90 transition-colors hover:bg-white/20 disabled:cursor-not-allowed disabled:opacity-40",
1892
+ children: sending ? "Sending\u2026" : sendLabel
1893
+ }
1894
+ )
1895
+ ] })
1896
+ ] })
1897
+ ] })
1898
+ ) : /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1766
1899
  /* @__PURE__ */ jsxs("span", { className: "text-white/35", children: [
1767
1900
  "log ",
1768
1901
  logSize,
1769
1902
  " \xB7 ",
1770
1903
  loggedSeconds < 60 ? `${loggedSeconds}s` : `${Math.round(loggedSeconds / 60)}m`
1771
1904
  ] }),
1772
- /* @__PURE__ */ jsxs("span", { className: "flex gap-1", children: [
1773
- /* @__PURE__ */ jsx(
1774
- "button",
1775
- {
1776
- onClick: exportJson,
1777
- disabled: !logSize,
1778
- className: "rounded border border-white/15 px-1.5 py-0.5 text-white/60 transition-colors hover:border-white/30 hover:text-white disabled:cursor-not-allowed disabled:opacity-40",
1779
- children: "JSON"
1780
- }
1781
- ),
1782
- /* @__PURE__ */ jsx(
1783
- "button",
1784
- {
1785
- onClick: exportCsv,
1786
- disabled: !logSize,
1787
- className: "rounded border border-white/15 px-1.5 py-0.5 text-white/60 transition-colors hover:border-white/30 hover:text-white disabled:cursor-not-allowed disabled:opacity-40",
1788
- children: "CSV"
1789
- }
1790
- )
1791
- ] })
1792
- ] })
1905
+ /* @__PURE__ */ jsx(
1906
+ "button",
1907
+ {
1908
+ onClick: beginReport,
1909
+ disabled: !logSize,
1910
+ className: "rounded border border-white/15 px-1.5 py-0.5 text-white/60 transition-colors hover:border-white/30 hover:text-white disabled:cursor-not-allowed disabled:opacity-40",
1911
+ children: onSubmit ? "Report this" : "Save report"
1912
+ }
1913
+ )
1914
+ ] }) })
1793
1915
  ]
1794
1916
  }
1795
1917
  );
@@ -2982,7 +3104,13 @@ function Desktop({ profile }) {
2982
3104
  const taskbarAtBottom = prefs.taskbar_position !== "top" && prefs.taskbar_position !== "left" && prefs.taskbar_position !== "right";
2983
3105
  const side = prefs.taskbar_position === "right" ? "left-3" : "right-3";
2984
3106
  const bottom = taskbarAtBottom ? versionShowing ? "bottom-24" : "bottom-16" : versionShowing ? "bottom-8" : "bottom-3";
2985
- return /* @__PURE__ */ jsx("div", { className: `absolute z-[240] ${side} ${bottom}`, children: /* @__PURE__ */ jsx(PerfStats, { onClose: () => saveShellPrefs({ show_desktop_stats: false }) }) });
3107
+ return /* @__PURE__ */ jsx("div", { className: `absolute z-[240] ${side} ${bottom}`, children: /* @__PURE__ */ jsx(
3108
+ PerfStats,
3109
+ {
3110
+ onClose: () => saveShellPrefs({ show_desktop_stats: false }),
3111
+ onSubmit: host.onSubmitPerfReport
3112
+ }
3113
+ ) });
2986
3114
  })(),
2987
3115
  prefs.show_desktop_version === true && (host.productVersion ?? APP_VERSION) && /* @__PURE__ */ jsx(
2988
3116
  "button",
@@ -3321,6 +3449,15 @@ function StartMenu({
3321
3449
  useEffect(() => {
3322
3450
  setHoveredChild(null);
3323
3451
  }, [hoveredSection]);
3452
+ useEffect(() => {
3453
+ if (open) markMenuOpen("menu", "start");
3454
+ }, [open]);
3455
+ useEffect(() => {
3456
+ if (hoveredSection) markMenuOpen("submenu", hoveredSection);
3457
+ }, [hoveredSection]);
3458
+ useEffect(() => {
3459
+ if (hoveredChild) markMenuOpen("submenu", hoveredChild);
3460
+ }, [hoveredChild]);
3324
3461
  useLayoutEffect(() => {
3325
3462
  if (!flyoutRef.current || !hoveredSection || search.length >= 2) return;
3326
3463
  const h = flyoutRef.current.offsetHeight;
@@ -3827,7 +3964,7 @@ function MobileAppLanding({
3827
3964
  }
3828
3965
  );
3829
3966
  }
3830
- var Sidebar = lazy(() => import('./Sidebar-VF2IOOFX.js'));
3967
+ var Sidebar = lazy(() => import('./Sidebar-E553SOXC.js'));
3831
3968
  function useFavorites(wallpapers) {
3832
3969
  const { prefs, save } = useShellPrefs();
3833
3970
  const favorites = prefs.favorite_pages || [];
@@ -4836,7 +4973,7 @@ function Customization({ omit, section } = {}) {
4836
4973
  /* @__PURE__ */ jsxs("span", { className: "text-sm text-gray-700", children: [
4837
4974
  "Show performance stats",
4838
4975
  /* @__PURE__ */ jsx("span", { className: "block text-xs text-gray-400", children: "Overlays frame rate, worst frame time and a bottleneck reading on the desktop, beside the version number. Turn it on to check whether Reduce transparency actually helps on this machine \u2014 or whether the slowness is coming from somewhere else." }),
4839
- /* @__PURE__ */ jsx("span", { className: "block text-xs text-gray-400 mt-1", children: "While it is on, each reading is recorded with what was happening at the time \u2014 how many windows were open, which one was on top, and counts of clicks, keystrokes, scrolls and window drags. No page content or keystroke text is recorded. The log stays on this device and is only shared if you export it with the JSON or CSV button on the overlay." })
4976
+ /* @__PURE__ */ jsx("span", { className: "block text-xs text-gray-400 mt-1", children: "While it is on, each reading is recorded with what was happening at the time \u2014 how many windows were open, which one was on top, which menu or submenu you opened, how long you spent moving or resizing a window, and counts of clicks, keystrokes and scrolls. No page content or keystroke text is recorded. The log stays on this device unless you use the overlay's Report button, which sends it \u2014 with whatever you type in the box \u2014 to the team as a bug report." })
4840
4977
  ] })
4841
4978
  ] })
4842
4979
  ] })
@@ -8796,11 +8933,14 @@ function BulkImportGrid({ columns, onImport, description, mergeDuplicates = fals
8796
8933
  const cleaned = data.map((row) => {
8797
8934
  const r = [...row];
8798
8935
  while (r.length < colCount) r.push("");
8799
- return r.slice(0, colCount).map((cell, i) => i === 0 ? cell : cleanNumber(cell));
8936
+ return r.slice(0, colCount).map((cell, i) => {
8937
+ const kind = colKind(columns[i], i);
8938
+ return kind === "price" || kind === "qty" ? cleanNumber(cell) : cell;
8939
+ });
8800
8940
  });
8801
8941
  while (cleaned.length < 15) cleaned.push(Array(colCount).fill(""));
8802
8942
  setGridData(cleaned);
8803
- }, [colCount]);
8943
+ }, [colCount, columns]);
8804
8944
  const handleCSV = (e) => {
8805
8945
  const file = e.target.files?.[0];
8806
8946
  if (!file) return;
@@ -8840,7 +8980,8 @@ function BulkImportGrid({ columns, onImport, description, mergeDuplicates = fals
8840
8980
  const obj = {};
8841
8981
  columns.forEach((col, i) => {
8842
8982
  const val = row[i]?.trim() || "";
8843
- obj[col.key] = i === 0 ? val : cleanNumber(val);
8983
+ const kind = colKind(col, i);
8984
+ obj[col.key] = kind === "price" || kind === "qty" ? cleanNumber(val) : val;
8844
8985
  });
8845
8986
  return obj;
8846
8987
  });
@@ -8973,6 +9114,35 @@ function BulkImportGrid({ columns, onImport, description, mergeDuplicates = fals
8973
9114
  error && /* @__PURE__ */ jsx("p", { className: "text-xs text-red-600 px-4 py-1", children: error })
8974
9115
  ] });
8975
9116
  }
9117
+ var BTN2 = "text-xs text-gray-500 hover:text-gray-700 disabled:opacity-40 disabled:hover:text-gray-500 disabled:cursor-default";
9118
+ function UndoControls({ className = "" }) {
9119
+ const { undo, redo, canUndo, canRedo, undoLabel, redoLabel, enabled } = useUndo();
9120
+ if (!enabled) return null;
9121
+ return /* @__PURE__ */ jsxs("div", { className: `flex items-center gap-2 ${className}`, children: [
9122
+ /* @__PURE__ */ jsx(
9123
+ "button",
9124
+ {
9125
+ type: "button",
9126
+ onClick: undo,
9127
+ disabled: !canUndo,
9128
+ title: canUndo ? `Undo ${undoLabel} (${CMD_Z})` : "Nothing to undo",
9129
+ className: BTN2,
9130
+ children: "Undo"
9131
+ }
9132
+ ),
9133
+ /* @__PURE__ */ jsx(
9134
+ "button",
9135
+ {
9136
+ type: "button",
9137
+ onClick: redo,
9138
+ disabled: !canRedo,
9139
+ title: canRedo ? `Redo ${redoLabel} (${CMD_SHIFT_Z})` : "Nothing to redo",
9140
+ className: BTN2,
9141
+ children: "Redo"
9142
+ }
9143
+ )
9144
+ ] });
9145
+ }
8976
9146
  var C20 = 33;
8977
9147
  var C40 = 67;
8978
9148
  var toInt = (v) => typeof v === "number" ? v : parseInt(String(v ?? "")) || 0;
@@ -10467,6 +10637,6 @@ function useEditHotkey(callback) {
10467
10637
  }, [callback, isActive]);
10468
10638
  }
10469
10639
 
10470
- export { ALT, ALT_SHIFT_D, ALT_SHIFT_E, ALT_SHIFT_N, Accordion, AuthScreen, Avatar, AvatarGroup, BLOCKED_PCT_CPU, Banner, BarChart, BehaviorPanel, BulkImportGrid, Button_default as Button, CMD_A, CMD_DOT, CMD_ENTER, CMD_K, CMD_S, Card, ChangePasswordForm, ChatTemplate, Checkbox_default as Checkbox, CheckoutTemplate, ColoredBadge, ContainerFillChart, Customization, DEV_BANNER_TEXT, DashboardTemplate, DataTablePage, DateRangePicker, Desktop, DesktopHostProvider, DevIndicator, DonutChart, ENTER, EmailTemplate, EmptyState, EntityList, ErrorPage, FilterBar, FormField, FormLayoutPage, GalleryTemplate, GlobalSearch, HelpCenter, INPUT_BASE, Input_default as Input, Kanban, LOG_CAP, Label, Layout, ListFooter, ListLoadError, LoadingSpinner2 as LoadingSpinner, MIN_GROUP_SAMPLES, MOD, Markdown, MediaUploadField, MediaUploadGrid, MetricBar, MilestoneTimeline, NativeSelect, NotificationBell, PageHeader, Pagination, PdfActionButton, PerfStats, Radio_default as Radio, ResizableTable, SHIFT, SMOOTH_FPS, SearchableSelect, Select_default as Select, ServerStatusIndicator, ShellEntityFetcherProvider, ShortcutHelp, SidebarActionButton, SidebarGroupLabel, SidebarNavItem, SoundsPanel, Sparkline, StartMenu, StatCard, StatusBadge, StatusBadgeProvider, SystemPreferences, Tabs, Textarea_default as Textarea, Tooltip, TopNav, WidgetManager, appendRecord as appendPerfRecord, applyDevTitle, classify as classifyPerf, createWindowRegistry, findDuplicateKeys, formatDate, inputClasses, isDevEnv, isInteracting, isMac, isSeverityTone, mediaFileName, mergeBulkItems, toCsv as perfLogToCsv, severityOf, summariseFrames, summariseLog as summarisePerfLog, toISODate2 as toISODate, useClickOutside, useColumnConfig, useDesktopHost, useEditHotkey, useFilters, useInfiniteScroll, useNewHotkey, useShellEntityFetcher, useSort, useTableNav };
10640
+ export { ALT, ALT_SHIFT_D, ALT_SHIFT_E, ALT_SHIFT_N, Accordion, AuthScreen, Avatar, AvatarGroup, BLOCKED_PCT_CPU, Banner, BarChart, BehaviorPanel, BulkImportGrid, Button_default as Button, CMD_A, CMD_DOT, CMD_ENTER, CMD_K, CMD_S, Card, ChangePasswordForm, ChatTemplate, Checkbox_default as Checkbox, CheckoutTemplate, ColoredBadge, ContainerFillChart, Customization, DEV_BANNER_TEXT, DashboardTemplate, DataTablePage, DateRangePicker, Desktop, DesktopHostProvider, DevIndicator, DonutChart, ENTER, EmailTemplate, EmptyState, EntityList, ErrorPage, FilterBar, FormField, FormLayoutPage, GalleryTemplate, GlobalSearch, HelpCenter, INPUT_BASE, Input_default as Input, Kanban, LOG_CAP, Label, Layout, ListFooter, ListLoadError, LoadingSpinner2 as LoadingSpinner, MIN_EVENT_SAMPLES, MIN_GROUP_SAMPLES, MOD, Markdown, MediaUploadField, MediaUploadGrid, MetricBar, MilestoneTimeline, NativeSelect, NotificationBell, PageHeader, Pagination, PdfActionButton, PerfStats, Radio_default as Radio, ResizableTable, SHIFT, SMOOTH_FPS, SearchableSelect, Select_default as Select, ServerStatusIndicator, ShellEntityFetcherProvider, ShortcutHelp, SidebarActionButton, SidebarGroupLabel, SidebarNavItem, SoundsPanel, Sparkline, StartMenu, StatCard, StatusBadge, StatusBadgeProvider, SystemPreferences, Tabs, Textarea_default as Textarea, Tooltip, TopNav, UndoControls, WidgetManager, appendRecord as appendPerfRecord, applyDevTitle, classifyActivity, classify as classifyPerf, createWindowRegistry, findDuplicateKeys, formatDate, inputClasses, isDevEnv, isInteracting, isMac, isSeverityTone, mediaFileName, mergeBulkItems, toCsv as perfLogToCsv, severityOf, summariseFrames, summariseLog as summarisePerfLog, toISODate2 as toISODate, useClickOutside, useColumnConfig, useDesktopHost, useEditHotkey, useFilters, useInfiniteScroll, useNewHotkey, useShellEntityFetcher, useSort, useTableNav };
10471
10641
  //# sourceMappingURL=index.js.map
10472
10642
  //# sourceMappingURL=index.js.map