react-os-shell 4.3.1 → 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 (44) hide show
  1. package/dist/{Browser-674YMWMK.js → Browser-YVV3HOKJ.js} +5 -5
  2. package/dist/{Browser-674YMWMK.js.map → Browser-YVV3HOKJ.js.map} +1 -1
  3. package/dist/{Calculator-3ULKK5ZA.js → Calculator-GBC3FLAJ.js} +4 -4
  4. package/dist/{Calculator-3ULKK5ZA.js.map → Calculator-GBC3FLAJ.js.map} +1 -1
  5. package/dist/ConfirmDialog-R7NZVI2V.js +4 -0
  6. package/dist/{ConfirmDialog-XJP67AHR.js.map → ConfirmDialog-R7NZVI2V.js.map} +1 -1
  7. package/dist/{CurrencyConverter-JC2N7AY3.js → CurrencyConverter-URPZQBNV.js} +4 -4
  8. package/dist/{CurrencyConverter-JC2N7AY3.js.map → CurrencyConverter-URPZQBNV.js.map} +1 -1
  9. package/dist/{Documents-MJKEUOOL.js → Documents-5SFFQZBP.js} +5 -5
  10. package/dist/{Documents-MJKEUOOL.js.map → Documents-5SFFQZBP.js.map} +1 -1
  11. package/dist/{Files-BTZMPTL6.js → Files-YQJRHUGF.js} +5 -5
  12. package/dist/{Files-BTZMPTL6.js.map → Files-YQJRHUGF.js.map} +1 -1
  13. package/dist/{Notepad-NGLFEJLA.js → Notepad-FUQ74K4N.js} +5 -5
  14. package/dist/{Notepad-NGLFEJLA.js.map → Notepad-FUQ74K4N.js.map} +1 -1
  15. package/dist/{PomodoroTimer-RPCRTUKC.js → PomodoroTimer-QQOMBVP3.js} +5 -5
  16. package/dist/{PomodoroTimer-RPCRTUKC.js.map → PomodoroTimer-QQOMBVP3.js.map} +1 -1
  17. package/dist/{Preview-H4S427VB.js → Preview-OP4IUJFT.js} +5 -5
  18. package/dist/{Preview-H4S427VB.js.map → Preview-OP4IUJFT.js.map} +1 -1
  19. package/dist/{Spreadsheet-2YYUKKSY.js → Spreadsheet-GADWOYDX.js} +5 -5
  20. package/dist/{Spreadsheet-2YYUKKSY.js.map → Spreadsheet-GADWOYDX.js.map} +1 -1
  21. package/dist/{Stock-YTQ2OYQ7.js → Stock-33C4U7CR.js} +4 -4
  22. package/dist/{Stock-YTQ2OYQ7.js.map → Stock-33C4U7CR.js.map} +1 -1
  23. package/dist/{Weather-4I4EFUJO.js → Weather-G3XHTB75.js} +4 -4
  24. package/dist/{Weather-4I4EFUJO.js.map → Weather-G3XHTB75.js.map} +1 -1
  25. package/dist/{WorldClock-7PZI7VRE.js → WorldClock-ZUVI3LBN.js} +4 -4
  26. package/dist/{WorldClock-7PZI7VRE.js.map → WorldClock-ZUVI3LBN.js.map} +1 -1
  27. package/dist/apps/index.js +15 -15
  28. package/dist/{chunk-PMPJLVZV.js → chunk-5HCDJPX7.js} +3 -3
  29. package/dist/{chunk-PMPJLVZV.js.map → chunk-5HCDJPX7.js.map} +1 -1
  30. package/dist/{chunk-VA6M46NL.js → chunk-EF5W3EXL.js} +47 -10
  31. package/dist/chunk-EF5W3EXL.js.map +1 -0
  32. package/dist/chunk-GDOCYHAG.js +7 -0
  33. package/dist/{chunk-K6MB36UJ.js.map → chunk-GDOCYHAG.js.map} +1 -1
  34. package/dist/{chunk-YF2XNVGN.js → chunk-LSFZJZTQ.js} +3 -3
  35. package/dist/{chunk-YF2XNVGN.js.map → chunk-LSFZJZTQ.js.map} +1 -1
  36. package/dist/{chunk-ASRXGCSQ.js → chunk-PZTYN2RX.js} +4 -4
  37. package/dist/{chunk-ASRXGCSQ.js.map → chunk-PZTYN2RX.js.map} +1 -1
  38. package/dist/index.d.ts +270 -155
  39. package/dist/index.js +209 -76
  40. package/dist/index.js.map +1 -1
  41. package/package.json +1 -1
  42. package/dist/ConfirmDialog-XJP67AHR.js +0 -4
  43. package/dist/chunk-K6MB36UJ.js +0 -7
  44. package/dist/chunk-VA6M46NL.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { subscribePomo, getPomoSnapshot } from './chunk-YF2XNVGN.js';
2
- export { setShellTodoProvider } from './chunk-YF2XNVGN.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';
@@ -15,10 +15,10 @@ 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-K6MB36UJ.js';
19
- export { VERSION } from './chunk-K6MB36UJ.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, useUndo, CancelButton, setWindowPosition } from './chunk-VA6M46NL.js';
21
- export { CancelButton, ConfirmProvider, CopyButton, DocFavStar, Modal, ModalActions, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, ShellPrefsProvider, UndoProvider, WindowCrashedFallback, WindowErrorBoundary, WindowManagerProvider, WindowTitle, commitExposeHighlight, confirm, confirmDestructive, exitExposeMode, getActiveWindowRoute, getExposeHighlight, getWindowPosition, isEntityEntry, isPageEntry, prompt, registerModalEscapeInterceptor, setExposeHighlight, setShellApiClient, setShellWindowRegistry, setWindowDefaultPosition, setWindowPosition, subscribeExposeHighlight, toggleExposeMode, useIsActiveWindow, useLocalStoragePrefs, useModalActive, useShellPrefs, useUndo, useUndoable, useUndoableState, useWidgetSettings, useWindowManager, useWindowMenuItem, useWindowTitle } from './chunk-VA6M46NL.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
22
  import { glassStyle, startMenuCategories, navSections, isSection, GLASS_INPUT_BG, navIcons, isReachable, sectionIcons, useShellAuth, visibleChildren } from './chunk-DKDD5KXG.js';
23
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';
@@ -1428,15 +1428,28 @@ function classify(reading) {
1428
1428
  }
1429
1429
 
1430
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
+ }
1431
1443
  var LOG_CAP = 2400;
1432
1444
  var MIN_GROUP_SAMPLES = 4;
1445
+ var MIN_EVENT_SAMPLES = 2;
1433
1446
  function appendRecord(log, record, cap = LOG_CAP) {
1434
1447
  const next = log.length >= cap ? log.slice(log.length - cap + 1) : log.slice();
1435
1448
  next.push(record);
1436
1449
  return next;
1437
1450
  }
1438
1451
  function isInteracting(r) {
1439
- return r.clicks > 0 || r.keys > 0 || r.scrolls > 0 || r.dragMs > 0;
1452
+ return classifyActivity(r) !== "idle";
1440
1453
  }
1441
1454
  function median(values) {
1442
1455
  if (!values.length) return 0;
@@ -1445,7 +1458,27 @@ function median(values) {
1445
1458
  return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
1446
1459
  }
1447
1460
  function group(records) {
1448
- 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);
1449
1482
  }
1450
1483
  function summariseLog(log) {
1451
1484
  const empty = {
@@ -1456,37 +1489,34 @@ function summariseLog(log) {
1456
1489
  verdictShare: { smooth: 0, gpu: 0, cpu: 0, unknown: 0 },
1457
1490
  interacting: null,
1458
1491
  idle: null,
1492
+ byActivity: [],
1459
1493
  byWindowCount: [],
1460
- worstWindows: []
1494
+ worstWindows: [],
1495
+ worstMenus: []
1461
1496
  };
1462
1497
  if (!log.length) return empty;
1463
- const measured = log.filter((r) => r.fps > 0);
1464
1498
  const verdictShare = { ...empty.verdictShare };
1465
1499
  for (const r of log) verdictShare[r.verdict] += 1 / log.length;
1466
- const interacting = measured.filter(isInteracting);
1467
- const idle = measured.filter((r) => !isInteracting(r));
1468
- const byWindows = /* @__PURE__ */ new Map();
1469
- const byKey = /* @__PURE__ */ new Map();
1470
- for (const r of measured) {
1471
- const bucket = byWindows.get(r.windows);
1472
- if (bucket) bucket.push(r);
1473
- else byWindows.set(r.windows, [r]);
1474
- if (r.active) {
1475
- const keyed = byKey.get(r.active);
1476
- if (keyed) keyed.push(r);
1477
- else byKey.set(r.active, [r]);
1478
- }
1479
- }
1500
+ const interacting = log.filter(isInteracting);
1501
+ const idle = log.filter((r) => !isInteracting(r));
1480
1502
  return {
1481
1503
  samples: log.length,
1482
1504
  durationMs: log[log.length - 1].t - log[0].t,
1483
- medianFps: median(measured.map((r) => r.fps)),
1505
+ medianFps: median(log.filter((r) => r.fps > 0).map((r) => r.fps)),
1484
1506
  worstFrameMs: log.reduce((worst, r) => r.worstMs > worst ? r.worstMs : worst, 0),
1485
1507
  verdictShare,
1486
1508
  interacting: interacting.length >= MIN_GROUP_SAMPLES ? group(interacting) : null,
1487
1509
  idle: idle.length >= MIN_GROUP_SAMPLES ? group(idle) : null,
1488
- byWindowCount: [...byWindows.entries()].filter(([, rs]) => rs.length >= MIN_GROUP_SAMPLES).map(([windows, rs]) => ({ windows, ...group(rs) })).sort((a, b) => a.windows - b.windows),
1489
- 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
+ )
1490
1520
  };
1491
1521
  }
1492
1522
  var CSV_COLUMNS = [
@@ -1502,7 +1532,12 @@ var CSV_COLUMNS = [
1502
1532
  "clicks",
1503
1533
  "keys",
1504
1534
  "scrolls",
1505
- "dragMs"
1535
+ "dragMs",
1536
+ "menus",
1537
+ "submenus",
1538
+ "menuKey",
1539
+ "moveMs",
1540
+ "resizeMs"
1506
1541
  ];
1507
1542
  function toCsv(log) {
1508
1543
  const cell = (value) => {
@@ -1520,6 +1555,7 @@ var WINDOW_MS = 1e3;
1520
1555
  var HISTORY = 40;
1521
1556
  var PERSIST_EVERY_MS = 1e4;
1522
1557
  var LOG_STORAGE_KEY = "react-os-shell:perf-log";
1558
+ var MAX_DRAG_GAP_MS = 100;
1523
1559
  var VERDICT_STYLES = {
1524
1560
  smooth: { dot: "bg-emerald-400", text: "text-emerald-300" },
1525
1561
  gpu: { dot: "bg-amber-400", text: "text-amber-300" },
@@ -1541,7 +1577,7 @@ function readWindows() {
1541
1577
  });
1542
1578
  return { windows: panels.length, active };
1543
1579
  }
1544
- function PerfStats({ onClose, className = "" }) {
1580
+ function PerfStats({ onClose, onSubmit, className = "" }) {
1545
1581
  const framesRef = useRef([]);
1546
1582
  const blockedMsRef = useRef(0);
1547
1583
  const activityRef = useRef({ clicks: 0, keys: 0, scrolls: 0, dragMs: 0 });
@@ -1549,11 +1585,20 @@ function PerfStats({ onClose, className = "" }) {
1549
1585
  const logRef = useRef([]);
1550
1586
  const startedAtRef = useRef(0);
1551
1587
  const persistedAtRef = useRef(0);
1588
+ const snapshotRef = useRef(null);
1552
1589
  const [longTaskSupported, setLongTaskSupported] = useState(false);
1553
1590
  const [reading, setReading] = useState({ fps: 0, frameMs: 0, worstMs: 0, blockedPct: null });
1554
1591
  const [history, setHistory] = useState([]);
1555
1592
  const [heap, setHeap] = useState(null);
1556
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
+ }, []);
1557
1602
  useEffect(() => {
1558
1603
  let raf = 0;
1559
1604
  const tick = (now) => {
@@ -1588,8 +1633,9 @@ function PerfStats({ onClose, className = "" }) {
1588
1633
  const onPointerMove = () => {
1589
1634
  if (!dragRef.current.down) return;
1590
1635
  const now = performance.now();
1591
- activityRef.current.dragMs += now - dragRef.current.since;
1636
+ const gap = now - dragRef.current.since;
1592
1637
  dragRef.current.since = now;
1638
+ if (gap <= MAX_DRAG_GAP_MS) activityRef.current.dragMs += gap;
1593
1639
  };
1594
1640
  const onPointerUp = () => {
1595
1641
  dragRef.current.down = false;
@@ -1643,6 +1689,7 @@ function PerfStats({ onClose, className = "" }) {
1643
1689
  const heapMB = memory ? memory.usedJSHeapSize / 1048576 : null;
1644
1690
  const nextReading = { ...frames, blockedPct };
1645
1691
  const activity = activityRef.current;
1692
+ const events = drainPerfEvents();
1646
1693
  const { windows, active } = readWindows();
1647
1694
  logRef.current = appendRecord(logRef.current, {
1648
1695
  t: Math.round(now - startedAtRef.current),
@@ -1655,7 +1702,12 @@ function PerfStats({ onClose, className = "" }) {
1655
1702
  clicks: activity.clicks,
1656
1703
  keys: activity.keys,
1657
1704
  scrolls: activity.scrolls,
1658
- 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)
1659
1711
  });
1660
1712
  activity.clicks = 0;
1661
1713
  activity.keys = 0;
@@ -1672,38 +1724,65 @@ function PerfStats({ onClose, className = "" }) {
1672
1724
  }, FLUSH_MS);
1673
1725
  return () => clearInterval(id);
1674
1726
  }, [longTaskSupported, persist]);
1675
- const download = useCallback((body, type, extension) => {
1676
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
1677
- const url = URL.createObjectURL(new Blob([body], { type }));
1678
- const link = document.createElement("a");
1679
- link.href = url;
1680
- link.download = `perf-log-${stamp}.${extension}`;
1681
- link.click();
1682
- URL.revokeObjectURL(url);
1727
+ const beginReport = useCallback(() => {
1728
+ snapshotRef.current = logRef.current.slice();
1729
+ setSendError(null);
1730
+ setComposing(true);
1683
1731
  }, []);
1684
- const exportJson = useCallback(() => {
1685
- download(
1686
- 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(
1687
1742
  {
1688
1743
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1744
+ message,
1745
+ verdict: verdict.label,
1689
1746
  userAgent: navigator.userAgent,
1690
1747
  screen: { width: window.screen.width, height: window.screen.height, dpr: window.devicePixelRatio },
1691
1748
  reducedTransparency: document.documentElement.classList.contains("rosh-reduce-transparency"),
1692
- summary: summariseLog(logRef.current),
1693
- records: logRef.current
1749
+ summary: summariseLog(records),
1750
+ records
1694
1751
  },
1695
1752
  null,
1696
1753
  2
1697
- ),
1698
- "application/json",
1699
- "json"
1700
- );
1701
- }, [download]);
1702
- const exportCsv = useCallback(() => download(toCsv(logRef.current), "text/csv", "csv"), [download]);
1703
- 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]);
1704
1781
  const styles = VERDICT_STYLES[verdict.kind];
1705
1782
  const peak = Math.max(60, ...history);
1706
1783
  const loggedSeconds = Math.round(logSize * FLUSH_MS / 1e3);
1784
+ const sendLabel = onSubmit ? "Send" : "Download";
1785
+ const reportSamples = snapshotRef.current?.length ?? logSize;
1707
1786
  return /* @__PURE__ */ jsxs(
1708
1787
  "div",
1709
1788
  {
@@ -1766,34 +1845,73 @@ function PerfStats({ onClose, className = "" }) {
1766
1845
  ] }),
1767
1846
  /* @__PURE__ */ jsx("p", { className: "mt-0.5 leading-snug text-white/45", children: verdict.detail })
1768
1847
  ] }),
1769
- /* @__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: [
1770
1899
  /* @__PURE__ */ jsxs("span", { className: "text-white/35", children: [
1771
1900
  "log ",
1772
1901
  logSize,
1773
1902
  " \xB7 ",
1774
1903
  loggedSeconds < 60 ? `${loggedSeconds}s` : `${Math.round(loggedSeconds / 60)}m`
1775
1904
  ] }),
1776
- /* @__PURE__ */ jsxs("span", { className: "flex gap-1", children: [
1777
- /* @__PURE__ */ jsx(
1778
- "button",
1779
- {
1780
- onClick: exportJson,
1781
- disabled: !logSize,
1782
- 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",
1783
- children: "JSON"
1784
- }
1785
- ),
1786
- /* @__PURE__ */ jsx(
1787
- "button",
1788
- {
1789
- onClick: exportCsv,
1790
- disabled: !logSize,
1791
- 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",
1792
- children: "CSV"
1793
- }
1794
- )
1795
- ] })
1796
- ] })
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
+ ] }) })
1797
1915
  ]
1798
1916
  }
1799
1917
  );
@@ -2986,7 +3104,13 @@ function Desktop({ profile }) {
2986
3104
  const taskbarAtBottom = prefs.taskbar_position !== "top" && prefs.taskbar_position !== "left" && prefs.taskbar_position !== "right";
2987
3105
  const side = prefs.taskbar_position === "right" ? "left-3" : "right-3";
2988
3106
  const bottom = taskbarAtBottom ? versionShowing ? "bottom-24" : "bottom-16" : versionShowing ? "bottom-8" : "bottom-3";
2989
- 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
+ ) });
2990
3114
  })(),
2991
3115
  prefs.show_desktop_version === true && (host.productVersion ?? APP_VERSION) && /* @__PURE__ */ jsx(
2992
3116
  "button",
@@ -3325,6 +3449,15 @@ function StartMenu({
3325
3449
  useEffect(() => {
3326
3450
  setHoveredChild(null);
3327
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]);
3328
3461
  useLayoutEffect(() => {
3329
3462
  if (!flyoutRef.current || !hoveredSection || search.length >= 2) return;
3330
3463
  const h = flyoutRef.current.offsetHeight;
@@ -4840,7 +4973,7 @@ function Customization({ omit, section } = {}) {
4840
4973
  /* @__PURE__ */ jsxs("span", { className: "text-sm text-gray-700", children: [
4841
4974
  "Show performance stats",
4842
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." }),
4843
- /* @__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." })
4844
4977
  ] })
4845
4978
  ] })
4846
4979
  ] })
@@ -10504,6 +10637,6 @@ function useEditHotkey(callback) {
10504
10637
  }, [callback, isActive]);
10505
10638
  }
10506
10639
 
10507
- 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, UndoControls, 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 };
10508
10641
  //# sourceMappingURL=index.js.map
10509
10642
  //# sourceMappingURL=index.js.map