@apisurf/wireui 0.1.2 → 0.1.3

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 (51) hide show
  1. package/README.md +42 -26
  2. package/bin/wireui.mjs +1 -1
  3. package/dist/nitro.json +1 -1
  4. package/dist/public/assets/environments-299O-03t.js +1 -0
  5. package/dist/public/assets/feed-B8L4GK9D.js +1 -0
  6. package/dist/public/assets/globals-k1eOASJT.css +1 -0
  7. package/dist/public/assets/history-EqPRzguE.js +1 -0
  8. package/dist/public/assets/index-D3MuiLlr.js +11 -0
  9. package/dist/public/assets/kv-table-Dv6o68aD.js +2 -0
  10. package/dist/public/assets/preload-helper-CIs162tl.js +3 -0
  11. package/dist/public/assets/query-DE41dcUO.js +19 -0
  12. package/dist/public/assets/requests._requestId-DSbe2mSh.js +1 -0
  13. package/dist/public/assets/rolldown-runtime-CbXtAM7H.js +1 -0
  14. package/dist/public/assets/routes-CRl6TZP9.js +1 -0
  15. package/dist/public/assets/runs._runId-BGRrB0cL.js +1 -0
  16. package/dist/public/assets/scoped-DoOxDjHI.js +1 -0
  17. package/dist/public/assets/session-header-wONyPgx_.js +1 -0
  18. package/dist/public/assets/sessions-CqjoUG4A.js +1 -0
  19. package/dist/public/assets/split-Ddiw3mOT.js +1 -0
  20. package/dist/public/assets/tabs-C1EijW_Z.js +2 -0
  21. package/dist/server/_ssr/{api-Dw1Zb_PK.mjs → api-DUem3i24.mjs} +28 -17
  22. package/dist/server/_ssr/environments-BFBtf7iL.mjs +146 -0
  23. package/dist/server/_ssr/feed-BgweoqQ4.mjs +108 -0
  24. package/dist/server/_ssr/history-CbfbTHg9.mjs +195 -0
  25. package/dist/server/_ssr/{kv-table-DQUkoItO.mjs → kv-table-BONnSOLb.mjs} +2 -2
  26. package/dist/server/_ssr/{router-CPcgc_kh.mjs → prefs-iPvYd_Vz.mjs} +246 -1551
  27. package/dist/server/_ssr/{query-D4so9xiY.mjs → query-DBNlR5hk.mjs} +7 -6
  28. package/dist/server/_ssr/{requests._requestId-BFukRwqL.mjs → requests._requestId-DX2svRx4.mjs} +7 -23
  29. package/dist/server/_ssr/router-BpH2DFcQ.mjs +1068 -0
  30. package/dist/server/_ssr/{routes-DaV9vGHn.mjs → routes-DGIQrvhg.mjs} +18 -8
  31. package/dist/server/_ssr/runs._runId-DZ-DQirQ.mjs +850 -0
  32. package/dist/server/_ssr/{scoped-CnQ56K8h.mjs → scoped-CNX8KOhL.mjs} +2 -2
  33. package/dist/server/_ssr/session-header-CH-Pbd1E.mjs +153 -0
  34. package/dist/server/_ssr/{collections-CqidTQpw.mjs → sessions-D3LBcKOI.mjs} +35 -72
  35. package/dist/server/_ssr/{split-DWrMIs7K.mjs → split-qGbYHKc9.mjs} +2 -2
  36. package/dist/server/_ssr/ssr.mjs +10 -14
  37. package/dist/server/_tanstack-start-manifest_v-B2rWFyf7.mjs +79 -0
  38. package/dist/server/index.mjs +100 -51
  39. package/package.json +3 -3
  40. package/dist/public/assets/collections-CLHcfRfi.js +0 -1
  41. package/dist/public/assets/globals-CZiNFGaK.css +0 -1
  42. package/dist/public/assets/index-D5VFd_3H.js +0 -14
  43. package/dist/public/assets/kv-table-oB5gcq0i.js +0 -2
  44. package/dist/public/assets/query-BKAhHNIi.js +0 -19
  45. package/dist/public/assets/requests._requestId-Bu7E2eoI.js +0 -1
  46. package/dist/public/assets/routes-DDY2CfzA.js +0 -1
  47. package/dist/public/assets/runs._runId-CzwtVjYq.js +0 -1
  48. package/dist/public/assets/scoped-DB8raIWx.js +0 -1
  49. package/dist/public/assets/split-Br1RX2I4.js +0 -1
  50. package/dist/server/_ssr/runs._runId-Df_LmPHa.mjs +0 -836
  51. package/dist/server/_tanstack-start-manifest_v-CARKjHfs.mjs +0 -50
@@ -1,7 +1,6 @@
1
1
  import { __toESM } from "../_runtime.mjs";
2
- import { HeadContent, Link, Outlet, Scripts, createFileRoute, createRootRoute, createRouter, getRouteApi, lazyRouteComponent, notFound, require_jsx_runtime, require_react, useLocation, useNavigate, useRouter, useRouterState } from "../_libs/@tanstack/react-router+[...].mjs";
3
- import { TSS_SERVER_FUNCTION, createServerFn, getServerFnById } from "./ssr.mjs";
4
- //#region node_modules/.nitro/vite/services/ssr/assets/api-li-c8dNB.js
2
+ import { Link, getRouteApi, require_jsx_runtime, require_react, useLocation, useNavigate, useRouter } from "../_libs/@tanstack/react-router+[...].mjs";
3
+ //#region node_modules/.nitro/vite/services/ssr/assets/tabs-Cn8oWaeF.js
5
4
  var import_react = /* @__PURE__ */ __toESM(require_react());
6
5
  var import_jsx_runtime = require_jsx_runtime();
7
6
  function isObject(value) {
@@ -275,7 +274,7 @@ function icon(paths) {
275
274
  });
276
275
  };
277
276
  }
278
- var IconCollections = icon(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }) }));
277
+ var IconSessions = icon(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }) }));
279
278
  var IconHistory = icon(/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
280
279
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 12a9 9 0 1 0 3-6.7L3 8" }),
281
280
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 3v5h5" }),
@@ -382,13 +381,6 @@ var IconSplitStacked = icon(/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_
382
381
  height: "16",
383
382
  rx: "2"
384
383
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 12h18" })] }));
385
- var IconSidebar = icon(/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", {
386
- x: "3",
387
- y: "4",
388
- width: "18",
389
- height: "16",
390
- rx: "2"
391
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9 4v16" })] }));
392
384
  var IconUp = icon(/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 19V5" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M6 11l6-6 6 6" })] }));
393
385
  var IconDown = icon(/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 5v14" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M6 13l6 6 6-6" })] }));
394
386
  var IconAlert = icon(/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 3l9.5 17h-19z" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 10v4M12 17.5v.01" })] }));
@@ -585,24 +577,28 @@ function timingPhases(r) {
585
577
  /**
586
578
  * How a run went, in one colour.
587
579
  *
588
- * A failed check leaves the exit code at 0 by design, so it has to be read here
589
- * or a run that asserted something false would look green everywhere. Failed
590
- * requests are amber rather than red: a script that probes a 404 on purpose is
591
- * not broken.
580
+ * Only the script decides: it threw, a check failed, it never finished, or it
581
+ * passed. A failed check leaves the exit code at 0 by design, so it has to be
582
+ * read here or a run that asserted something false would look green
583
+ * everywhere. Failed requests do not count at all — a script that probes a 404
584
+ * or a timeout on purpose still did its job, and if it did not, it should
585
+ * throw or check.
592
586
  */
593
587
  function runTone(run) {
594
588
  if (run.exit_status === "error" || run.failed_check_count > 0) return "bad";
595
589
  if (run.finished_at == null) return "info";
596
- if (run.error_count > 0) return "warn";
597
590
  return "ok";
598
591
  }
599
592
  function runVerdict(run) {
600
593
  if (run.exit_status === "error") return "Threw";
601
594
  if (run.failed_check_count > 0) return `${run.failed_check_count} failed check${run.failed_check_count === 1 ? "" : "s"}`;
602
595
  if (run.finished_at == null) return "Running or killed";
603
- if (run.error_count > 0) return `${run.error_count} failed request${run.error_count === 1 ? "" : "s"}`;
604
596
  return "Passed";
605
597
  }
598
+ /** Says why a run's failed requests sit next to a green verdict. */
599
+ function requestErrorsHint(count) {
600
+ return `${count} request${count === 1 ? "" : "s"} errored or got a status ≥ 400. That alone does not fail a run — only a throw or a failed check does.`;
601
+ }
606
602
  var mono = css({
607
603
  fontFamily: "mono",
608
604
  fontSize: "12px"
@@ -832,21 +828,19 @@ function CopyButton({ text, label = "Copy" }) {
832
828
  function TabList({ items, value, onSelect, right, dense = false }) {
833
829
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
834
830
  role: "tablist",
835
- className: css({
831
+ className: cx(css({
836
832
  display: "flex",
837
833
  alignItems: "stretch",
838
- gap: "0.5",
839
- px: "3",
834
+ px: "2",
840
835
  borderBottom: "1px solid",
841
836
  borderColor: "line",
842
837
  flexShrink: 0,
843
- minH: dense ? "34px" : "38px",
844
838
  overflowX: "auto",
845
839
  scrollbarWidth: "none"
846
- }),
840
+ }), dense ? css({ h: "36px" }) : css({ h: "42px" })),
847
841
  children: [items.filter((item) => !item.hidden).map((item) => {
848
842
  const active = item.id === value;
849
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
843
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
850
844
  type: "button",
851
845
  role: "tab",
852
846
  "aria-selected": active,
@@ -855,17 +849,36 @@ function TabList({ items, value, onSelect, right, dense = false }) {
855
849
  position: "relative",
856
850
  display: "inline-flex",
857
851
  alignItems: "center",
858
- gap: "1.5",
859
- px: "2",
852
+ px: "0.5",
860
853
  border: "none",
861
854
  bg: "transparent",
862
855
  cursor: "pointer",
863
856
  color: "muted",
857
+ fontSize: "13px",
864
858
  fontWeight: "500",
865
859
  whiteSpace: "nowrap",
866
- _hover: { color: "fg" },
860
+ _focusVisible: { outline: "none" },
861
+ "& > .tab-label": {
862
+ display: "inline-flex",
863
+ alignItems: "center",
864
+ gap: "1.5",
865
+ px: "2.5",
866
+ h: "calc(100% - 10px)",
867
+ rounded: "md",
868
+ transition: "background 80ms, color 80ms"
869
+ },
870
+ _hover: {
871
+ color: "fg",
872
+ "& > .tab-label": { bg: "hover" }
873
+ },
874
+ "&:focus-visible > .tab-label": {
875
+ outline: "2px solid",
876
+ outlineColor: "accent",
877
+ outlineOffset: "-2px"
878
+ },
867
879
  "&[aria-selected=true]": {
868
880
  color: "fg",
881
+ fontWeight: "600",
869
882
  _after: {
870
883
  content: "\"\"",
871
884
  position: "absolute",
@@ -873,18 +886,33 @@ function TabList({ items, value, onSelect, right, dense = false }) {
873
886
  right: "2",
874
887
  bottom: "-1px",
875
888
  h: "2px",
876
- rounded: "full",
889
+ roundedTop: "full",
877
890
  bg: "accent"
878
891
  }
879
892
  }
880
893
  }),
881
- children: [item.label, item.count !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
882
- className: cx(css({
883
- fontSize: "11px",
884
- fontVariantNumeric: "tabular-nums"
885
- }), item.tone ? TONE_TEXT[item.tone] : css({ color: "faint" })),
886
- children: item.count
887
- }) : null]
894
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
895
+ className: "tab-label",
896
+ children: [item.label, item.count !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
897
+ className: cx(css({
898
+ fontSize: "11px",
899
+ fontWeight: "500",
900
+ fontVariantNumeric: "tabular-nums",
901
+ lineHeight: "16px",
902
+ minW: "16px",
903
+ px: "1.5",
904
+ rounded: "full",
905
+ textAlign: "center"
906
+ }), item.tone ? TONE_PILL[item.tone] : active ? css({
907
+ color: "accent",
908
+ bg: "accentSoft"
909
+ }) : css({
910
+ color: "muted",
911
+ bg: "hover"
912
+ })),
913
+ children: item.count
914
+ }) : null]
915
+ })
888
916
  }, item.id);
889
917
  }), right ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
890
918
  className: css({
@@ -898,6 +926,32 @@ function TabList({ items, value, onSelect, right, dense = false }) {
898
926
  }) : null]
899
927
  });
900
928
  }
929
+ /** A button that stays pressed: an on/off filter beside a Segmented. */
930
+ function Toggle({ checked, onChange, label }) {
931
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
932
+ type: "button",
933
+ "aria-pressed": checked,
934
+ onClick: () => onChange(!checked),
935
+ className: css({
936
+ h: "26px",
937
+ px: "2",
938
+ rounded: "md",
939
+ border: "1px solid",
940
+ borderColor: "line",
941
+ bg: "transparent",
942
+ color: "muted",
943
+ cursor: "pointer",
944
+ fontSize: "12px",
945
+ _hover: { color: "fg" },
946
+ "&[aria-pressed=true]": {
947
+ color: "accent",
948
+ borderColor: "accent",
949
+ bg: "accentSoft"
950
+ }
951
+ }),
952
+ children: label
953
+ });
954
+ }
901
955
  function Segmented({ items, value, onSelect }) {
902
956
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
903
957
  className: css({
@@ -1036,6 +1090,73 @@ function Stat({ label, value, sub, tone }) {
1036
1090
  ]
1037
1091
  });
1038
1092
  }
1093
+ /** A label and a number on one line — a {@link Stat} for a header with no room for cards. */
1094
+ function Metric({ label, value, tone, title }) {
1095
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1096
+ title,
1097
+ className: css({
1098
+ display: "inline-flex",
1099
+ alignItems: "baseline",
1100
+ gap: "1",
1101
+ fontSize: "12px",
1102
+ whiteSpace: "nowrap"
1103
+ }),
1104
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1105
+ className: css({ color: "muted" }),
1106
+ children: label
1107
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1108
+ className: cx(mono, css({ fontWeight: "600" }), tone ? TONE_TEXT[tone] : void 0),
1109
+ children: value
1110
+ })]
1111
+ });
1112
+ }
1113
+ /** {@link Stat}s joined into one bordered strip — a summary for a header. */
1114
+ function StatStrip({ items }) {
1115
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1116
+ className: css({
1117
+ display: "inline-flex",
1118
+ flexWrap: "wrap",
1119
+ alignSelf: "flex-start",
1120
+ maxW: "full",
1121
+ rounded: "md",
1122
+ border: "1px solid",
1123
+ borderColor: "lineStrong",
1124
+ bg: "raised",
1125
+ overflow: "hidden"
1126
+ }),
1127
+ children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1128
+ title: item.title,
1129
+ className: css({
1130
+ display: "flex",
1131
+ flexDir: "column",
1132
+ px: "3",
1133
+ py: "1",
1134
+ minW: "88px",
1135
+ borderRight: "1px solid",
1136
+ borderColor: "line",
1137
+ _last: { borderRight: "none" }
1138
+ }),
1139
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1140
+ className: css({
1141
+ fontSize: "11px",
1142
+ color: "muted",
1143
+ fontWeight: "500",
1144
+ whiteSpace: "nowrap"
1145
+ }),
1146
+ children: item.label
1147
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1148
+ className: cx(css({
1149
+ fontSize: "15px",
1150
+ fontWeight: "600",
1151
+ fontVariantNumeric: "tabular-nums",
1152
+ lineHeight: "1.35",
1153
+ whiteSpace: "nowrap"
1154
+ }), item.tone ? TONE_TEXT[item.tone] : void 0),
1155
+ children: item.value
1156
+ })]
1157
+ }, item.label))
1158
+ });
1159
+ }
1039
1160
  function StatGrid({ children }) {
1040
1161
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1041
1162
  className: css({
@@ -1271,6 +1392,20 @@ function formatDayHeading(now, ms) {
1271
1392
  if (days === 1) return "Yesterday";
1272
1393
  return formatDay(now, ms);
1273
1394
  }
1395
+ /** Rows, newest first, split under the day heading each falls on. */
1396
+ function groupByDay(rows, now, at) {
1397
+ const out = [];
1398
+ for (const row of rows) {
1399
+ const day = formatDayHeading(now, at(row));
1400
+ const last = out[out.length - 1];
1401
+ if (last && last.day === day) last.rows.push(row);
1402
+ else out.push({
1403
+ day,
1404
+ rows: [row]
1405
+ });
1406
+ }
1407
+ return out;
1408
+ }
1274
1409
  /** `4 min ago`; a date once "ago" stops being a useful answer. */
1275
1410
  function formatAgo(now, ms) {
1276
1411
  const seconds = Math.max(0, now - ms) / 1e3;
@@ -1302,11 +1437,44 @@ function basename(path) {
1302
1437
  }
1303
1438
  var tabKey = {
1304
1439
  overview: () => "overview",
1440
+ history: () => "history",
1441
+ environments: () => "environments",
1305
1442
  query: () => "query",
1306
1443
  run: (id) => `run:${id}`,
1307
1444
  request: (id) => `request:${id}`,
1308
1445
  script: (entry) => `script:${entry}`
1309
1446
  };
1447
+ /** Pages that are one tab however they are filtered. */
1448
+ var PAGES = /* @__PURE__ */ new Set([
1449
+ "history",
1450
+ "environments",
1451
+ "query"
1452
+ ]);
1453
+ /**
1454
+ * The tab a URL belongs to, read off the URL alone. Which tab owns the URL on
1455
+ * screen is decided here rather than by which page registered last: during a
1456
+ * navigation the URL changes before the page leaving it unmounts, and asking
1457
+ * that page would file the new URL under the old tab.
1458
+ *
1459
+ * A session is the one page whose identity is in the search params, because
1460
+ * a script's identity is a path.
1461
+ */
1462
+ function tabKeyFor(pathname, search = {}) {
1463
+ const path = pathname.replace(/\/+$/, "") || "/";
1464
+ if (path === "/") return tabKey.overview();
1465
+ const run = /^\/runs\/(\d+)$/.exec(path);
1466
+ if (run) return tabKey.run(Number(run[1]));
1467
+ const request = /^\/requests\/(\d+)$/.exec(path);
1468
+ if (request) return tabKey.request(Number(request[1]));
1469
+ if (path === "/sessions") return typeof search.entry === "string" && search.entry ? tabKey.script(search.entry) : null;
1470
+ const page = path.slice(1);
1471
+ return PAGES.has(page) ? page : null;
1472
+ }
1473
+ /** {@link tabKeyFor} for a stored href rather than the live location. */
1474
+ function tabKeyForHref(href) {
1475
+ const [path = "", query = ""] = href.split("#")[0].split("?");
1476
+ return tabKeyFor(path, { entry: new URLSearchParams(query).get("entry") ?? void 0 });
1477
+ }
1310
1478
  var TabsContext = import_react.createContext(null);
1311
1479
  function useTabs() {
1312
1480
  const value = import_react.useContext(TabsContext);
@@ -1341,9 +1509,17 @@ function TabsProvider({ children }) {
1341
1509
  const location = useLocation();
1342
1510
  const navigate = useNavigate();
1343
1511
  const [tabs, setTabs] = import_react.useState([]);
1344
- const [activeKey, setActiveKey] = import_react.useState(null);
1345
1512
  const [loaded, setLoaded] = import_react.useState(false);
1346
1513
  const href = location.href;
1514
+ const activeKey = tabKeyFor(location.pathname, location.search);
1515
+ const onScreen = import_react.useRef({
1516
+ href,
1517
+ key: activeKey
1518
+ });
1519
+ onScreen.current = {
1520
+ href,
1521
+ key: activeKey
1522
+ };
1347
1523
  import_react.useEffect(() => {
1348
1524
  try {
1349
1525
  const raw = sessionStorage.getItem(STORAGE_KEY$1);
@@ -1365,21 +1541,21 @@ function TabsProvider({ children }) {
1365
1541
  } : t));
1366
1542
  }, [href, activeKey]);
1367
1543
  const register = import_react.useCallback((tab) => {
1368
- setActiveKey(tab.key);
1544
+ if (tab.key !== onScreen.current.key) return;
1545
+ const at = onScreen.current.href;
1369
1546
  setTabs((current) => {
1370
1547
  const existing = current.findIndex((t) => t.key === tab.key);
1371
1548
  if (existing >= 0) {
1372
1549
  const next = [...current];
1373
1550
  next[existing] = {
1374
1551
  ...next[existing],
1375
- ...tab,
1376
- href
1552
+ ...tab
1377
1553
  };
1378
1554
  return next;
1379
1555
  }
1380
1556
  const fresh = {
1381
1557
  ...tab,
1382
- href,
1558
+ href: at,
1383
1559
  preview: true
1384
1560
  };
1385
1561
  const preview = current.findIndex((t) => t.preview);
@@ -1390,7 +1566,7 @@ function TabsProvider({ children }) {
1390
1566
  }
1391
1567
  return [...current, fresh];
1392
1568
  });
1393
- }, [href]);
1569
+ }, []);
1394
1570
  const pin = import_react.useCallback((key) => {
1395
1571
  setTabs((current) => current.map((t) => t.key === key ? {
1396
1572
  ...t,
@@ -1404,7 +1580,6 @@ function TabsProvider({ children }) {
1404
1580
  setTabs(next);
1405
1581
  if (key === activeKey) {
1406
1582
  const neighbour = next[Math.min(index, next.length - 1)];
1407
- if (!neighbour) setActiveKey(null);
1408
1583
  navigate({ href: neighbour?.href ?? "/" });
1409
1584
  }
1410
1585
  }, [
@@ -1435,9 +1610,31 @@ function TabsProvider({ children }) {
1435
1610
  children
1436
1611
  });
1437
1612
  }
1613
+ /**
1614
+ * A stored tab whose href points at another tab's page — which older builds
1615
+ * could save — is sent back to its own page, or dropped when that is unknown.
1616
+ */
1617
+ function repair(tab) {
1618
+ if (tabKeyForHref(tab.href) === tab.key) return tab;
1619
+ const home = homeOf(tab.key);
1620
+ return home ? {
1621
+ ...tab,
1622
+ href: home
1623
+ } : null;
1624
+ }
1625
+ /** Where a tab's page lives, from its key alone. */
1626
+ function homeOf(key) {
1627
+ const cut = key.indexOf(":");
1628
+ const kind = cut < 0 ? key : key.slice(0, cut);
1629
+ const id = cut < 0 ? "" : key.slice(cut + 1);
1630
+ if (kind === "overview") return "/";
1631
+ if ((kind === "run" || kind === "request") && id) return `/${kind}s/${id}`;
1632
+ if (kind === "script" && id) return `/sessions?entry=${encodeURIComponent(id)}`;
1633
+ return PAGES.has(kind) ? `/${kind}` : null;
1634
+ }
1438
1635
  /** Stored tabs first, then any the current page registered before they loaded. */
1439
1636
  function merge(stored, current) {
1440
- const valid = Array.isArray(stored) ? stored.filter((t) => t && typeof t.key === "string" && typeof t.href === "string") : [];
1637
+ const valid = Array.isArray(stored) ? stored.filter((t) => t && typeof t.key === "string" && typeof t.href === "string").map(repair).filter((t) => t != null) : [];
1441
1638
  const keys = new Set(valid.map((t) => t.key));
1442
1639
  const extra = current.filter((t) => !keys.has(t.key));
1443
1640
  const merged = [...valid, ...extra];
@@ -1452,82 +1649,9 @@ function merge(stored, current) {
1452
1649
  return t;
1453
1650
  }).toReversed();
1454
1651
  }
1455
- var createSsrRpc = (functionId) => {
1456
- const url = "/_serverFn/" + functionId;
1457
- const serverFnMeta = { id: functionId };
1458
- const fn = async (...args) => {
1459
- return (await getServerFnById(functionId, { origin: "server" }))(...args);
1460
- };
1461
- return Object.assign(fn, {
1462
- url,
1463
- serverFnMeta,
1464
- [TSS_SERVER_FUNCTION]: true
1465
- });
1466
- };
1467
- function record(input) {
1468
- return input && typeof input === "object" ? input : {};
1469
- }
1470
- function id(value, name) {
1471
- const n = typeof value === "string" ? Number(value) : value;
1472
- if (typeof n !== "number" || !Number.isInteger(n) || n <= 0) throw new Error(`${name} must be a positive integer`);
1473
- return n;
1474
- }
1475
- function optionalString(value) {
1476
- return typeof value === "string" ? value : void 0;
1477
- }
1478
- /** `undefined` → every environment, `null` → runs recorded without one. */
1479
- function env(value) {
1480
- if (value === null) return null;
1481
- return typeof value === "string" ? value : void 0;
1482
- }
1483
- /** The open database when its schema is one these queries can read, else null. */
1484
- /** Runs the sidebar holds. Older ones stay reachable from their script's page. */
1485
- var getWorkspace = createServerFn({ method: "GET" }).handler(createSsrRpc("65bc418eb60581304ebde68d2b8b775b58aa758789bcdd2e616a2bdd5657ef20"));
1486
- var getOverview = createServerFn({ method: "GET" }).validator((input) => ({ env: env(record(input).env) })).handler(createSsrRpc("edb0cb28f3af0347a499a4c076ecc5f8247f0ace0fb524957f40f6a706d7224d"));
1487
- var getHistory = createServerFn({ method: "GET" }).validator((input) => {
1488
- const r = record(input);
1489
- const kind = r.kind === "http" || r.kind === "stream" ? r.kind : void 0;
1490
- return {
1491
- q: optionalString(r.q),
1492
- env: env(r.env),
1493
- kind,
1494
- failed: r.failed === true,
1495
- bodies: r.bodies === true,
1496
- limit: typeof r.limit === "number" ? r.limit : void 0
1497
- };
1498
- }).handler(createSsrRpc("c1e4e973ed505d1515bcb9192482c65b1680975a5daa6973befb704515a618e3"));
1499
- var getScript = createServerFn({ method: "GET" }).validator((input) => {
1500
- const r = record(input);
1501
- const entry = optionalString(r.entry);
1502
- if (!entry) throw new Error("entry is required");
1503
- return {
1504
- entry,
1505
- env: env(r.env)
1506
- };
1507
- }).handler(createSsrRpc("5eff62884e56f81742c81b44dc7dcd1afc2e67323a30591a8b94b8895eda0dc9"));
1508
- /** A run's requests, in capture order. The sidebar tree loads these on expand. */
1509
- var getRunRequests = createServerFn({ method: "GET" }).validator((input) => ({ runId: id(record(input).runId, "runId") })).handler(createSsrRpc("3828e982aebdd19568d199b4820f9fb3ca9fab2c60ce502ee35e3f6075d422c9"));
1510
- var getRun = createServerFn({ method: "GET" }).validator((input) => ({ runId: id(record(input).runId, "runId") })).handler(createSsrRpc("56c165307cd91159eb72bfa882932f9552f05a5bbd6b5725f0fd5f4ad6b8295d"));
1511
- var getRequest = createServerFn({ method: "GET" }).validator((input) => ({ requestId: id(record(input).requestId, "requestId") })).handler(createSsrRpc("3772b5ea3e53d85a63e4df11f128b1c907e508d82cca6e71e6ee48dba1664133"));
1512
- var getSchema = createServerFn({ method: "GET" }).handler(createSsrRpc("06ff3639e7164884f56665ea621dc233dbfc78fa1557b1cb20105ff2fa90521e"));
1513
- var runSql = createServerFn({ method: "POST" }).validator((input) => {
1514
- const sql = optionalString(record(input).sql)?.trim();
1515
- if (!sql) throw new Error("sql is required");
1516
- return { sql };
1517
- }).handler(createSsrRpc("0f45629470ab3fbe618e9693d8685920d6ce48b2384512e5d9eb88f6b46ec2d9"));
1518
1652
  //#endregion
1519
- //#region node_modules/.nitro/vite/services/ssr/assets/router-CPcgc_kh.js
1520
- var __defProp = Object.defineProperty;
1521
- var __exportAll = (all, no_symbols) => {
1522
- let target = {};
1523
- for (var name in all) __defProp(target, name, {
1524
- get: all[name],
1525
- enumerable: true
1526
- });
1527
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
1528
- return target;
1529
- };
1530
- var root$4 = getRouteApi("__root__");
1653
+ //#region node_modules/.nitro/vite/services/ssr/assets/prefs-iPvYd_Vz.js
1654
+ var root = getRouteApi("__root__");
1531
1655
  var command = cx(mono, css({
1532
1656
  display: "block",
1533
1657
  textAlign: "left",
@@ -1570,7 +1694,7 @@ function RouteError({ error }) {
1570
1694
  * page returns it early without knowing which case applies.
1571
1695
  */
1572
1696
  function useUnreadable() {
1573
- const ws = root$4.useLoaderData();
1697
+ const ws = root.useLoaderData();
1574
1698
  if (ws.schema.state === "missing") return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Empty, {
1575
1699
  title: "No recording yet",
1576
1700
  icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconDatabase, { size: 24 }),
@@ -1617,7 +1741,6 @@ var DEFAULTS = {
1617
1741
  layout: "side",
1618
1742
  sidebarWidth: 300,
1619
1743
  sidebarOpen: true,
1620
- panel: "collections",
1621
1744
  live: false
1622
1745
  };
1623
1746
  var STORAGE_KEY = "wireui:prefs";
@@ -1711,7 +1834,9 @@ function readStored() {
1711
1834
  const raw = localStorage.getItem(STORAGE_KEY);
1712
1835
  if (!raw) return {};
1713
1836
  const parsed = JSON.parse(raw);
1714
- return typeof parsed === "object" && parsed ? parsed : {};
1837
+ if (typeof parsed !== "object" || !parsed) return {};
1838
+ delete parsed.panel;
1839
+ return parsed;
1715
1840
  } catch {
1716
1841
  return {};
1717
1842
  }
@@ -1742,1435 +1867,5 @@ function useNow(initial, intervalMs = 15e3) {
1742
1867
  }, [intervalMs]);
1743
1868
  return now;
1744
1869
  }
1745
- function useActive() {
1746
- return useRouterState({
1747
- select: (state) => {
1748
- const active = {
1749
- entry: null,
1750
- runId: null,
1751
- requestId: null
1752
- };
1753
- for (const match of state.matches) {
1754
- const data = match.loaderData;
1755
- if (!data || typeof data !== "object") continue;
1756
- if (data.run) {
1757
- active.runId = data.run.id;
1758
- active.entry = data.run.entry;
1759
- }
1760
- if (data.script) active.entry = data.script.entry;
1761
- if (data.detail) active.requestId = data.detail.request.id;
1762
- }
1763
- return active;
1764
- },
1765
- structuralSharing: true
1766
- });
1767
- }
1768
- var root$3 = getRouteApi("__root__");
1769
- /** Runs shown under a script before "show more". */
1770
- var RUNS_SHOWN = 8;
1771
- var treeRow = css({
1772
- display: "flex",
1773
- alignItems: "center",
1774
- gap: "1.5",
1775
- h: "26px",
1776
- pr: "2",
1777
- rounded: "sm",
1778
- cursor: "pointer",
1779
- color: "fg",
1780
- textDecoration: "none",
1781
- userSelect: "none",
1782
- minW: "0",
1783
- _hover: { bg: "hover" },
1784
- "&[data-active=true]": { bg: "selected" }
1785
- });
1786
- var chevron = css({
1787
- display: "inline-flex",
1788
- alignItems: "center",
1789
- justifyContent: "center",
1790
- w: "16px",
1791
- h: "16px",
1792
- color: "faint",
1793
- flexShrink: 0,
1794
- rounded: "xs",
1795
- border: "none",
1796
- bg: "transparent",
1797
- p: "0",
1798
- cursor: "pointer",
1799
- _hover: {
1800
- color: "fg",
1801
- bg: "hover"
1802
- }
1803
- });
1804
- function CollectionsPanel({ filterRef }) {
1805
- const ws = root$3.useLoaderData();
1806
- const { prefs } = usePrefs();
1807
- const active = useActive();
1808
- const [filter, setFilter] = import_react.useState("");
1809
- const [open, setOpen] = import_react.useState(() => /* @__PURE__ */ new Set());
1810
- import_react.useEffect(() => {
1811
- const keys = [active.entry ? `s:${active.entry}` : null, active.runId ? `r:${active.runId}` : null].filter((k) => k != null);
1812
- if (keys.length === 0) return;
1813
- setOpen((current) => keys.every((k) => current.has(k)) ? current : /* @__PURE__ */ new Set([...current, ...keys]));
1814
- }, [active.entry, active.runId]);
1815
- const toggle = import_react.useCallback((key, force) => {
1816
- setOpen((current) => {
1817
- const next = new Set(current);
1818
- if (force ?? !next.has(key)) next.add(key);
1819
- else next.delete(key);
1820
- return next;
1821
- });
1822
- }, []);
1823
- const runsByEntry = import_react.useMemo(() => {
1824
- const map = /* @__PURE__ */ new Map();
1825
- for (const run of ws.runs) {
1826
- if (!matchesEnv(prefs.env, run.env)) continue;
1827
- const list = map.get(run.entry) ?? [];
1828
- list.push(run);
1829
- map.set(run.entry, list);
1830
- }
1831
- return map;
1832
- }, [ws.runs, prefs.env]);
1833
- const needle = filter.trim().toLowerCase();
1834
- const groups = import_react.useMemo(() => {
1835
- const byDir = /* @__PURE__ */ new Map();
1836
- for (const script of ws.scripts) {
1837
- if (!runsByEntry.has(script.entry)) continue;
1838
- if (needle && !script.rel.toLowerCase().includes(needle) && !(script.label ?? "").toLowerCase().includes(needle)) continue;
1839
- const list = byDir.get(script.dir) ?? [];
1840
- list.push(script);
1841
- byDir.set(script.dir, list);
1842
- }
1843
- return [...byDir.entries()];
1844
- }, [
1845
- ws.scripts,
1846
- runsByEntry,
1847
- needle
1848
- ]);
1849
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1850
- className: css({
1851
- display: "flex",
1852
- flexDir: "column",
1853
- h: "full",
1854
- minH: "0"
1855
- }),
1856
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1857
- className: css({
1858
- px: "2.5",
1859
- pb: "2",
1860
- flexShrink: 0
1861
- }),
1862
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FilterInput, {
1863
- ref: filterRef,
1864
- placeholder: "Filter collections",
1865
- shortcut: "/",
1866
- value: filter,
1867
- onChange: (e) => setFilter(e.target.value)
1868
- })
1869
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1870
- className: css({
1871
- flex: "1",
1872
- minH: "0",
1873
- overflow: "auto",
1874
- px: "1.5",
1875
- pb: "3"
1876
- }),
1877
- children: groups.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Empty, {
1878
- title: ws.scripts.length === 0 ? "No collections yet" : "No matches",
1879
- icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconCollections, { size: 20 }),
1880
- children: ws.scripts.length === 0 ? "Every script you record with wire execute becomes a collection here." : "No script matches the filter in this environment."
1881
- }) : groups.map(([dir, scripts]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1882
- className: css({ mb: "2" }),
1883
- children: [dir ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1884
- title: dir,
1885
- className: cx(truncate, css({
1886
- fontSize: "10.5px",
1887
- color: "faint",
1888
- px: "2",
1889
- pt: "1.5",
1890
- pb: "0.5",
1891
- fontFamily: "mono"
1892
- })),
1893
- children: dir
1894
- }) : null, scripts.map((script) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScriptNode, {
1895
- script,
1896
- runs: runsByEntry.get(script.entry) ?? [],
1897
- open,
1898
- toggle,
1899
- active
1900
- }, script.entry))]
1901
- }, dir))
1902
- })]
1903
- });
1904
- }
1905
- function ScriptNode({ script, runs, open, toggle, active }) {
1906
- const { pin } = useTabs();
1907
- const key = `s:${script.entry}`;
1908
- const expanded = open.has(key);
1909
- const [showAll, setShowAll] = import_react.useState(false);
1910
- const failing = runs[0] ? runTone(runs[0]) === "bad" : false;
1911
- const shown = showAll ? runs : runs.slice(0, RUNS_SHOWN);
1912
- const isActive = active.entry === script.entry && active.runId == null;
1913
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
1914
- to: "/collections",
1915
- search: { entry: script.entry },
1916
- "data-active": isActive,
1917
- className: cx(treeRow, css({ pl: "1" })),
1918
- onClick: () => toggle(key, true),
1919
- onDoubleClick: () => pin(tabKey.script(script.entry)),
1920
- title: script.rel,
1921
- children: [
1922
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1923
- role: "button",
1924
- tabIndex: -1,
1925
- className: chevron,
1926
- "aria-label": expanded ? "Collapse" : "Expand",
1927
- onClick: (e) => {
1928
- e.preventDefault();
1929
- e.stopPropagation();
1930
- toggle(key);
1931
- },
1932
- children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconChevronDown, { size: 12 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconChevronRight, { size: 12 })
1933
- }),
1934
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1935
- className: css({
1936
- color: "muted",
1937
- display: "inline-flex"
1938
- }),
1939
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconCollections, { size: 14 })
1940
- }),
1941
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1942
- className: cx(truncate, css({
1943
- flex: "1",
1944
- fontWeight: "500"
1945
- })),
1946
- children: script.file
1947
- }),
1948
- failing ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dot, {
1949
- tone: "bad",
1950
- title: "Latest run failed"
1951
- }) : null,
1952
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1953
- className: css({
1954
- fontSize: "11px",
1955
- color: "faint",
1956
- fontVariantNumeric: "tabular-nums"
1957
- }),
1958
- children: runs.length
1959
- })
1960
- ]
1961
- }), expanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [shown.map((run) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RunNode, {
1962
- run,
1963
- open,
1964
- toggle,
1965
- active
1966
- }, run.id)), runs.length > shown.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
1967
- type: "button",
1968
- onClick: () => setShowAll(true),
1969
- className: cx(treeRow, css({
1970
- pl: "9",
1971
- border: "none",
1972
- bg: "transparent",
1973
- w: "full",
1974
- color: "accent",
1975
- fontSize: "12px"
1976
- })),
1977
- children: [
1978
- "Show ",
1979
- runs.length - shown.length,
1980
- " older runs"
1981
- ]
1982
- }) : null] }) : null] });
1983
- }
1984
- function RunNode({ run, open, toggle, active }) {
1985
- const now = useNow(root$3.useLoaderData().now, 6e4);
1986
- const { pin } = useTabs();
1987
- const key = `r:${run.id}`;
1988
- const expanded = open.has(key);
1989
- const isActive = active.runId === run.id && active.requestId == null;
1990
- const day = formatDayHeading(now, run.started_at);
1991
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
1992
- to: "/runs/$runId",
1993
- params: { runId: String(run.id) },
1994
- "data-active": isActive,
1995
- className: cx(treeRow, css({ pl: "5" })),
1996
- onClick: () => toggle(key, true),
1997
- onDoubleClick: () => pin(tabKey.run(run.id)),
1998
- title: `Run ${run.id}${run.label ? ` · ${run.label}` : ""}`,
1999
- children: [
2000
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2001
- role: "button",
2002
- tabIndex: -1,
2003
- className: chevron,
2004
- "aria-label": expanded ? "Collapse" : "Expand",
2005
- onClick: (e) => {
2006
- e.preventDefault();
2007
- e.stopPropagation();
2008
- toggle(key);
2009
- },
2010
- children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconChevronDown, { size: 12 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconChevronRight, { size: 12 })
2011
- }),
2012
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dot, { tone: runTone(run) }),
2013
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2014
- className: cx(truncate, css({ flex: "1" })),
2015
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2016
- className: css({ color: "muted" }),
2017
- children: day === "Today" ? "" : `${day} `
2018
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2019
- className: mono,
2020
- children: formatClock(run.started_at)
2021
- })]
2022
- }),
2023
- run.env ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, { children: run.env }) : null,
2024
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2025
- className: cx(mono, css({
2026
- fontSize: "11px",
2027
- color: "faint"
2028
- })),
2029
- children: run.finished_at ? formatMs(run.finished_at - run.started_at) : "…"
2030
- })
2031
- ]
2032
- }), expanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RunRequests, {
2033
- runId: run.id,
2034
- activeRequestId: active.requestId
2035
- }) : null] });
2036
- }
2037
- /** Module-level, so collapsing and re-expanding a run does not refetch it. */
2038
- var requestCache = /* @__PURE__ */ new Map();
2039
- function RunRequests({ runId, activeRequestId }) {
2040
- const { refreshKey } = usePrefs();
2041
- const { pin } = useTabs();
2042
- const cached = requestCache.get(runId);
2043
- const [rows, setRows] = import_react.useState(cached?.rows ?? null);
2044
- import_react.useEffect(() => {
2045
- if (cached && cached.key === refreshKey) return;
2046
- let cancelled = false;
2047
- getRunRequests({ data: { runId } }).then((result) => {
2048
- requestCache.set(runId, {
2049
- key: refreshKey,
2050
- rows: result
2051
- });
2052
- if (!cancelled) setRows(result);
2053
- });
2054
- return () => {
2055
- cancelled = true;
2056
- };
2057
- }, [
2058
- runId,
2059
- refreshKey,
2060
- cached
2061
- ]);
2062
- if (rows == null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2063
- className: css({
2064
- pl: "12",
2065
- h: "26px",
2066
- color: "faint",
2067
- fontSize: "12px",
2068
- display: "flex",
2069
- alignItems: "center"
2070
- }),
2071
- children: "Loading…"
2072
- });
2073
- if (rows.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2074
- className: css({
2075
- pl: "12",
2076
- h: "26px",
2077
- color: "faint",
2078
- fontSize: "12px",
2079
- display: "flex",
2080
- alignItems: "center"
2081
- }),
2082
- children: "No requests"
2083
- });
2084
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
2085
- to: "/requests/$requestId",
2086
- params: { requestId: String(row.id) },
2087
- "data-active": row.id === activeRequestId,
2088
- onDoubleClick: () => pin(tabKey.request(row.id)),
2089
- className: cx(treeRow, css({ pl: "10" })),
2090
- title: `${row.method} ${row.host}${row.path}`,
2091
- children: [
2092
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MethodBadge, {
2093
- method: row.method,
2094
- kind: row.kind,
2095
- width: "list"
2096
- }),
2097
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2098
- className: cx(truncate, mono, css({ flex: "1" })),
2099
- children: row.path
2100
- }),
2101
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusBadge, {
2102
- status: row.status,
2103
- error: row.error_name
2104
- })
2105
- ]
2106
- }, row.id)) });
2107
- }
2108
- var root$2 = getRouteApi("__root__");
2109
- function EnvironmentsPanel() {
2110
- const ws = root$2.useLoaderData();
2111
- const now = useNow(ws.now, 6e4);
2112
- const { prefs, set } = usePrefs();
2113
- const totalRuns = ws.envs.reduce((sum, e) => sum + e.run_count, 0);
2114
- if (ws.envs.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Empty, {
2115
- title: "No environments",
2116
- icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconEnvironments, { size: 20 }),
2117
- children: [
2118
- "Runs made with ",
2119
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "wire execute --env staging" }),
2120
- " are grouped here."
2121
- ]
2122
- });
2123
- const rows = [{
2124
- value: "*",
2125
- name: "All environments",
2126
- sub: plural(totalRuns, "run"),
2127
- failed: 0
2128
- }, ...ws.envs.map((e) => ({
2129
- value: e.env ?? "",
2130
- name: e.env ?? "No environment",
2131
- sub: `${plural(e.run_count, "run")} · ${plural(e.script_count, "script")} · ${formatAgo(now, e.last_started_at)}`,
2132
- failed: e.failed_run_count
2133
- }))];
2134
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2135
- className: css({
2136
- px: "1.5",
2137
- overflow: "auto",
2138
- h: "full"
2139
- }),
2140
- children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
2141
- type: "button",
2142
- "data-active": prefs.env === row.value,
2143
- onClick: () => set("env", row.value),
2144
- className: cx(treeRow, css({
2145
- h: "auto",
2146
- py: "1.5",
2147
- pl: "2",
2148
- w: "full",
2149
- border: "none",
2150
- bg: "transparent",
2151
- textAlign: "left"
2152
- })),
2153
- children: [
2154
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2155
- className: css({
2156
- color: prefs.env === row.value ? "accent" : "faint",
2157
- display: "inline-flex"
2158
- }),
2159
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconEnvironments, { size: 14 })
2160
- }),
2161
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2162
- className: css({
2163
- flex: "1",
2164
- minW: "0",
2165
- display: "flex",
2166
- flexDir: "column"
2167
- }),
2168
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2169
- className: cx(truncate, css({ fontWeight: "500" })),
2170
- children: row.name
2171
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2172
- className: cx(truncate, css({
2173
- fontSize: "11px",
2174
- color: "faint"
2175
- })),
2176
- children: row.sub
2177
- })]
2178
- }),
2179
- row.failed > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dot, {
2180
- tone: "bad",
2181
- title: `${plural(row.failed, "run")} threw`
2182
- }) : null
2183
- ]
2184
- }, row.value))
2185
- });
2186
- }
2187
- var root$1 = getRouteApi("__root__");
2188
- function HistoryPanel({ filterRef }) {
2189
- const now = useNow(root$1.useLoaderData().now, 6e4);
2190
- const { prefs, refreshKey } = usePrefs();
2191
- const { pin } = useTabs();
2192
- const active = useActive();
2193
- const [q, setQ] = import_react.useState("");
2194
- const [kind, setKind] = import_react.useState("all");
2195
- const [failed, setFailed] = import_react.useState(false);
2196
- const [bodies, setBodies] = import_react.useState(false);
2197
- const [rows, setRows] = import_react.useState(null);
2198
- const debounced = useDebounced(q, 180);
2199
- import_react.useEffect(() => {
2200
- let cancelled = false;
2201
- getHistory({ data: {
2202
- q: debounced,
2203
- env: envFilter(prefs.env),
2204
- kind: kind === "all" ? void 0 : kind,
2205
- failed,
2206
- bodies,
2207
- limit: 500
2208
- } }).then((result) => {
2209
- if (!cancelled) setRows(result);
2210
- });
2211
- return () => {
2212
- cancelled = true;
2213
- };
2214
- }, [
2215
- debounced,
2216
- kind,
2217
- failed,
2218
- bodies,
2219
- prefs.env,
2220
- refreshKey
2221
- ]);
2222
- const grouped = import_react.useMemo(() => {
2223
- const out = [];
2224
- for (const row of rows ?? []) {
2225
- const day = formatDayHeading(now, row.ts);
2226
- const last = out[out.length - 1];
2227
- if (last && last.day === day) last.rows.push(row);
2228
- else out.push({
2229
- day,
2230
- rows: [row]
2231
- });
2232
- }
2233
- return out;
2234
- }, [rows, now]);
2235
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2236
- className: css({
2237
- display: "flex",
2238
- flexDir: "column",
2239
- h: "full",
2240
- minH: "0"
2241
- }),
2242
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2243
- className: css({
2244
- px: "2.5",
2245
- pb: "2",
2246
- display: "flex",
2247
- flexDir: "column",
2248
- gap: "1.5",
2249
- flexShrink: 0
2250
- }),
2251
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FilterInput, {
2252
- ref: filterRef,
2253
- placeholder: bodies ? "Search URLs and payloads" : "Search URL, method or status",
2254
- shortcut: "/",
2255
- value: q,
2256
- onChange: (e) => setQ(e.target.value)
2257
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2258
- className: css({
2259
- display: "flex",
2260
- alignItems: "center",
2261
- gap: "1.5",
2262
- flexWrap: "wrap"
2263
- }),
2264
- children: [
2265
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Segmented, {
2266
- items: [
2267
- {
2268
- id: "all",
2269
- label: "All"
2270
- },
2271
- {
2272
- id: "http",
2273
- label: "HTTP"
2274
- },
2275
- {
2276
- id: "stream",
2277
- label: "WS/SSE"
2278
- }
2279
- ],
2280
- value: kind,
2281
- onSelect: setKind
2282
- }),
2283
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Toggle, {
2284
- checked: failed,
2285
- onChange: setFailed,
2286
- label: "Failed"
2287
- }),
2288
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Toggle, {
2289
- checked: bodies,
2290
- onChange: setBodies,
2291
- label: "In payloads"
2292
- })
2293
- ]
2294
- })]
2295
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2296
- className: css({
2297
- flex: "1",
2298
- minH: "0",
2299
- overflow: "auto",
2300
- px: "1.5",
2301
- pb: "3"
2302
- }),
2303
- children: rows == null ? null : rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Empty, {
2304
- title: "No requests",
2305
- icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconHistory, { size: 20 }),
2306
- children: q || failed || kind !== "all" ? "Nothing matches these filters." : "Nothing recorded in this environment yet."
2307
- }) : grouped.map((group) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2308
- className: css({
2309
- position: "sticky",
2310
- top: "0",
2311
- zIndex: "1",
2312
- bg: "chrome",
2313
- fontSize: "10.5px",
2314
- fontWeight: "600",
2315
- textTransform: "uppercase",
2316
- letterSpacing: "0.06em",
2317
- color: "faint",
2318
- px: "2",
2319
- pt: "2",
2320
- pb: "1"
2321
- }),
2322
- children: group.day
2323
- }), group.rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
2324
- to: "/requests/$requestId",
2325
- params: { requestId: String(row.id) },
2326
- "data-active": row.id === active.requestId,
2327
- onDoubleClick: () => pin(tabKey.request(row.id)),
2328
- className: cx(treeRow, css({
2329
- h: "auto",
2330
- py: "1",
2331
- pl: "2",
2332
- alignItems: "flex-start"
2333
- })),
2334
- title: `${row.method} ${row.host}${row.path}${row.query ? `?${row.query}` : ""}`,
2335
- children: [
2336
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2337
- className: css({ pt: "1px" }),
2338
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MethodBadge, {
2339
- method: row.method,
2340
- kind: row.kind,
2341
- width: "list"
2342
- })
2343
- }),
2344
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2345
- className: css({
2346
- flex: "1",
2347
- minW: "0",
2348
- display: "flex",
2349
- flexDir: "column"
2350
- }),
2351
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2352
- className: cx(truncate, mono),
2353
- children: row.path
2354
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2355
- className: cx(truncate, css({
2356
- fontSize: "11px",
2357
- color: "faint"
2358
- })),
2359
- children: [
2360
- formatClock(row.ts),
2361
- " · ",
2362
- row.file,
2363
- " · ",
2364
- row.host
2365
- ]
2366
- })]
2367
- }),
2368
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2369
- className: css({
2370
- display: "flex",
2371
- flexDir: "column",
2372
- alignItems: "flex-end",
2373
- flexShrink: 0
2374
- }),
2375
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusBadge, {
2376
- status: row.status,
2377
- error: row.error_name
2378
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2379
- className: cx(mono, css({
2380
- fontSize: "11px",
2381
- color: "faint"
2382
- })),
2383
- children: formatMs(row.duration_ms)
2384
- })]
2385
- })
2386
- ]
2387
- }, row.id))] }, group.day))
2388
- })]
2389
- });
2390
- }
2391
- function Toggle({ checked, onChange, label }) {
2392
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
2393
- type: "button",
2394
- "aria-pressed": checked,
2395
- onClick: () => onChange(!checked),
2396
- className: css({
2397
- h: "26px",
2398
- px: "2",
2399
- rounded: "md",
2400
- border: "1px solid",
2401
- borderColor: "line",
2402
- bg: "transparent",
2403
- color: "muted",
2404
- cursor: "pointer",
2405
- fontSize: "12px",
2406
- _hover: { color: "fg" },
2407
- "&[aria-pressed=true]": {
2408
- color: "accent",
2409
- borderColor: "accent",
2410
- bg: "accentSoft"
2411
- }
2412
- }),
2413
- children: label
2414
- });
2415
- }
2416
- function useDebounced(value, ms) {
2417
- const [current, setCurrent] = import_react.useState(value);
2418
- import_react.useEffect(() => {
2419
- const timer = window.setTimeout(() => setCurrent(value), ms);
2420
- return () => window.clearTimeout(timer);
2421
- }, [value, ms]);
2422
- return current;
2423
- }
2424
- var root = getRouteApi("__root__");
2425
- function Shell({ children }) {
2426
- const { prefs } = usePrefs();
2427
- const filterRef = import_react.useRef(null);
2428
- useSlashToFilter(filterRef);
2429
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2430
- className: css({
2431
- display: "flex",
2432
- flexDir: "column",
2433
- h: "100vh",
2434
- bg: "chrome"
2435
- }),
2436
- children: [
2437
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TopBar, {}),
2438
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2439
- className: css({
2440
- display: "flex",
2441
- flex: "1",
2442
- minH: "0"
2443
- }),
2444
- children: [
2445
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Rail, {}),
2446
- prefs.sidebarOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sidebar, { filterRef }) : null,
2447
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", {
2448
- className: css({
2449
- display: "flex",
2450
- flexDir: "column",
2451
- flex: "1",
2452
- minW: "0",
2453
- bg: "canvas",
2454
- borderLeft: "1px solid",
2455
- borderTop: "1px solid",
2456
- borderColor: "line",
2457
- roundedTopLeft: "lg",
2458
- overflow: "hidden"
2459
- }),
2460
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabStrip, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2461
- className: css({
2462
- flex: "1",
2463
- minH: "0",
2464
- display: "flex",
2465
- flexDir: "column"
2466
- }),
2467
- children
2468
- })]
2469
- })
2470
- ]
2471
- }),
2472
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusBar, {})
2473
- ]
2474
- });
2475
- }
2476
- /** `/` focuses the sidebar's filter, as in most tools with one. */
2477
- function useSlashToFilter(ref) {
2478
- const { prefs, set } = usePrefs();
2479
- import_react.useEffect(() => {
2480
- const onKey = (event) => {
2481
- if (event.key !== "/" || event.metaKey || event.ctrlKey || event.altKey) return;
2482
- const target = event.target;
2483
- if (target && (target.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName))) return;
2484
- event.preventDefault();
2485
- if (!prefs.sidebarOpen) set("sidebarOpen", true);
2486
- if (prefs.panel === "environments") set("panel", "collections");
2487
- window.requestAnimationFrame(() => ref.current?.focus());
2488
- };
2489
- window.addEventListener("keydown", onKey);
2490
- return () => window.removeEventListener("keydown", onKey);
2491
- }, [
2492
- prefs.sidebarOpen,
2493
- prefs.panel,
2494
- set,
2495
- ref
2496
- ]);
2497
- }
2498
- var THEME_NEXT = {
2499
- system: "light",
2500
- light: "dark",
2501
- dark: "system"
2502
- };
2503
- function TopBar() {
2504
- const ws = root.useLoaderData();
2505
- const { prefs, set, refresh } = usePrefs();
2506
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
2507
- className: css({
2508
- display: "flex",
2509
- alignItems: "center",
2510
- gap: "3",
2511
- h: "44px",
2512
- px: "3",
2513
- flexShrink: 0
2514
- }),
2515
- children: [
2516
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
2517
- to: "/",
2518
- className: css({
2519
- display: "flex",
2520
- alignItems: "center",
2521
- gap: "2",
2522
- color: "fg",
2523
- textDecoration: "none"
2524
- }),
2525
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
2526
- src: "/favicon.svg",
2527
- alt: "",
2528
- width: 22,
2529
- height: 22
2530
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2531
- className: css({
2532
- fontWeight: "700",
2533
- letterSpacing: "-0.01em",
2534
- fontSize: "14px"
2535
- }),
2536
- children: ["wire", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2537
- className: css({
2538
- color: "muted",
2539
- fontWeight: "500"
2540
- }),
2541
- children: "ui"
2542
- })]
2543
- })]
2544
- }),
2545
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2546
- className: css({ color: "lineStrong" }),
2547
- children: "/"
2548
- }),
2549
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2550
- title: ws.path,
2551
- className: cx(mono, truncate, css({
2552
- color: "muted",
2553
- minW: "0"
2554
- })),
2555
- children: basename(ws.path)
2556
- }),
2557
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2558
- className: css({
2559
- ml: "auto",
2560
- display: "flex",
2561
- alignItems: "center",
2562
- gap: "1.5"
2563
- }),
2564
- children: [
2565
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnvSelect, {}),
2566
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
2567
- type: "button",
2568
- "aria-pressed": prefs.live,
2569
- onClick: () => set("live", !prefs.live),
2570
- title: prefs.live ? "Live: re-reading the file every 2 s" : "Follow the file as runs write to it",
2571
- className: css({
2572
- display: "inline-flex",
2573
- alignItems: "center",
2574
- gap: "1.5",
2575
- h: "28px",
2576
- px: "2.5",
2577
- rounded: "md",
2578
- border: "1px solid",
2579
- borderColor: "line",
2580
- bg: "raised",
2581
- color: "muted",
2582
- cursor: "pointer",
2583
- fontSize: "12px",
2584
- fontWeight: "500",
2585
- _hover: { color: "fg" },
2586
- "&[aria-pressed=true]": {
2587
- color: "ok",
2588
- borderColor: "ok",
2589
- bg: "okSoft"
2590
- }
2591
- }),
2592
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: cx(css({
2593
- w: "7px",
2594
- h: "7px",
2595
- rounded: "full",
2596
- bg: "faint"
2597
- }), prefs.live && css({
2598
- bg: "ok",
2599
- animation: "pulse 1.6s ease-in-out infinite"
2600
- })) }), "Live"]
2601
- }),
2602
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconButton, {
2603
- label: "Refresh",
2604
- onClick: refresh,
2605
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconRefresh, {})
2606
- }),
2607
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconButton, {
2608
- label: prefs.layout === "side" ? "Stack request above response" : "Request and response side by side",
2609
- onClick: () => set("layout", prefs.layout === "side" ? "stacked" : "side"),
2610
- children: prefs.layout === "side" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconSplitSide, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconSplitStacked, {})
2611
- }),
2612
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconButton, {
2613
- label: `Theme: ${prefs.theme}`,
2614
- onClick: () => set("theme", THEME_NEXT[prefs.theme]),
2615
- children: prefs.theme === "system" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconMonitor, {}) : prefs.theme === "light" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconSun, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconMoon, {})
2616
- })
2617
- ]
2618
- })
2619
- ]
2620
- });
2621
- }
2622
- function EnvSelect() {
2623
- const ws = root.useLoaderData();
2624
- const { prefs, set } = usePrefs();
2625
- const known = ws.envs.map((e) => e.env ?? "");
2626
- const value = prefs.env === "*" || known.includes(prefs.env) ? prefs.env : "*";
2627
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", {
2628
- className: css({
2629
- display: "inline-flex",
2630
- alignItems: "center",
2631
- gap: "1.5",
2632
- h: "28px",
2633
- pl: "2",
2634
- pr: "1",
2635
- rounded: "md",
2636
- border: "1px solid",
2637
- borderColor: value === "*" ? "line" : "accent",
2638
- bg: value === "*" ? "raised" : "accentSoft",
2639
- color: value === "*" ? "muted" : "accent"
2640
- }),
2641
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconEnvironments, { size: 14 }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", {
2642
- "aria-label": "Environment",
2643
- value,
2644
- onChange: (e) => set("env", e.target.value),
2645
- className: css({
2646
- border: "none",
2647
- bg: "transparent",
2648
- outline: "none",
2649
- fontSize: "12px",
2650
- fontWeight: "500",
2651
- color: "fg",
2652
- cursor: "pointer",
2653
- maxW: "180px"
2654
- }),
2655
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
2656
- value: "*",
2657
- children: "All environments"
2658
- }), ws.envs.map((e) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
2659
- value: e.env ?? "",
2660
- children: e.env ?? "No environment"
2661
- }, e.env ?? ""))]
2662
- })]
2663
- });
2664
- }
2665
- var PANELS = [
2666
- {
2667
- id: "collections",
2668
- label: "Collections",
2669
- icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconCollections, { size: 18 })
2670
- },
2671
- {
2672
- id: "history",
2673
- label: "History",
2674
- icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconHistory, { size: 18 })
2675
- },
2676
- {
2677
- id: "environments",
2678
- label: "Environments",
2679
- icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconEnvironments, { size: 18 })
2680
- }
2681
- ];
2682
- var railButton = css({
2683
- display: "flex",
2684
- flexDir: "column",
2685
- alignItems: "center",
2686
- gap: "0.5",
2687
- w: "56px",
2688
- py: "1.5",
2689
- rounded: "md",
2690
- border: "none",
2691
- bg: "transparent",
2692
- color: "muted",
2693
- cursor: "pointer",
2694
- fontSize: "10px",
2695
- fontWeight: "500",
2696
- textDecoration: "none",
2697
- _hover: {
2698
- color: "fg",
2699
- bg: "hover"
2700
- },
2701
- "&[data-active=true]": {
2702
- color: "fg",
2703
- bg: "hover"
2704
- },
2705
- "&[data-active=true] svg": { color: "accent" }
2706
- });
2707
- function Rail() {
2708
- const { prefs, set } = usePrefs();
2709
- const select = (panel) => {
2710
- if (prefs.panel === panel && prefs.sidebarOpen) set("sidebarOpen", false);
2711
- else {
2712
- set("panel", panel);
2713
- set("sidebarOpen", true);
2714
- }
2715
- };
2716
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("nav", {
2717
- "aria-label": "Sections",
2718
- className: css({
2719
- display: "flex",
2720
- flexDir: "column",
2721
- alignItems: "center",
2722
- gap: "1",
2723
- w: "64px",
2724
- pt: "1",
2725
- pb: "2",
2726
- flexShrink: 0
2727
- }),
2728
- children: [
2729
- PANELS.map((panel) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
2730
- type: "button",
2731
- className: railButton,
2732
- "data-active": prefs.sidebarOpen && prefs.panel === panel.id,
2733
- onClick: () => select(panel.id),
2734
- children: [panel.icon, panel.label === "Environments" ? "Envs" : panel.label]
2735
- }, panel.id)),
2736
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: css({
2737
- w: "28px",
2738
- h: "1px",
2739
- bg: "line",
2740
- my: "1.5"
2741
- }) }),
2742
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
2743
- to: "/",
2744
- className: railButton,
2745
- activeProps: { "data-active": true },
2746
- activeOptions: { exact: true },
2747
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconOverview, { size: 18 }), "Overview"]
2748
- }),
2749
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
2750
- to: "/query",
2751
- className: railButton,
2752
- activeProps: { "data-active": true },
2753
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconDatabase, { size: 18 }), "SQL"]
2754
- }),
2755
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2756
- className: css({ mt: "auto" }),
2757
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconButton, {
2758
- label: prefs.sidebarOpen ? "Hide sidebar" : "Show sidebar",
2759
- "aria-pressed": prefs.sidebarOpen,
2760
- onClick: () => set("sidebarOpen", !prefs.sidebarOpen),
2761
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconSidebar, {})
2762
- })
2763
- })
2764
- ]
2765
- });
2766
- }
2767
- var MIN_SIDEBAR = 220;
2768
- var MAX_SIDEBAR = 560;
2769
- function Sidebar({ filterRef }) {
2770
- const { prefs, set } = usePrefs();
2771
- const [dragging, setDragging] = import_react.useState(false);
2772
- const [width, setWidth] = import_react.useState(prefs.sidebarWidth);
2773
- import_react.useEffect(() => setWidth(prefs.sidebarWidth), [prefs.sidebarWidth]);
2774
- const title = PANELS.find((p) => p.id === prefs.panel)?.label ?? "";
2775
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", {
2776
- className: css({
2777
- position: "relative",
2778
- display: "flex",
2779
- flexDir: "column",
2780
- flexShrink: 0,
2781
- minH: "0",
2782
- pt: "1"
2783
- }),
2784
- style: { width },
2785
- children: [
2786
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2787
- className: css({
2788
- display: "flex",
2789
- alignItems: "center",
2790
- h: "30px",
2791
- px: "3",
2792
- flexShrink: 0
2793
- }),
2794
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2795
- className: css({
2796
- fontWeight: "600",
2797
- fontSize: "12px"
2798
- }),
2799
- children: title
2800
- })
2801
- }),
2802
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2803
- className: css({
2804
- flex: "1",
2805
- minH: "0"
2806
- }),
2807
- children: [
2808
- prefs.panel === "collections" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CollectionsPanel, { filterRef }) : null,
2809
- prefs.panel === "history" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HistoryPanel, { filterRef }) : null,
2810
- prefs.panel === "environments" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnvironmentsPanel, {}) : null
2811
- ]
2812
- }),
2813
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2814
- role: "separator",
2815
- "aria-orientation": "vertical",
2816
- "aria-label": "Resize sidebar",
2817
- onPointerDown: (e) => {
2818
- e.preventDefault();
2819
- e.target.setPointerCapture(e.pointerId);
2820
- setDragging(true);
2821
- },
2822
- onPointerMove: (e) => {
2823
- if (!dragging) return;
2824
- const aside = e.target.parentElement;
2825
- if (!aside) return;
2826
- const left = aside.getBoundingClientRect().left;
2827
- setWidth(Math.min(MAX_SIDEBAR, Math.max(MIN_SIDEBAR, e.clientX - left)));
2828
- },
2829
- onPointerUp: () => {
2830
- if (!dragging) return;
2831
- setDragging(false);
2832
- set("sidebarWidth", width);
2833
- },
2834
- className: cx(css({
2835
- position: "absolute",
2836
- top: "0",
2837
- bottom: "0",
2838
- right: "-3px",
2839
- w: "6px",
2840
- cursor: "col-resize",
2841
- zIndex: "2",
2842
- _hover: {
2843
- bg: "accent",
2844
- opacity: .4
2845
- }
2846
- }), dragging && css({
2847
- bg: "accent",
2848
- opacity: .4
2849
- }))
2850
- })
2851
- ]
2852
- });
2853
- }
2854
- function TabStrip() {
2855
- const { tabs, activeKey, pin, close } = useTabs();
2856
- const navigate = useNavigate();
2857
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2858
- role: "tablist",
2859
- "aria-label": "Open tabs",
2860
- className: css({
2861
- display: "flex",
2862
- alignItems: "stretch",
2863
- h: "36px",
2864
- flexShrink: 0,
2865
- bg: "chrome",
2866
- borderBottom: "1px solid",
2867
- borderColor: "line",
2868
- overflowX: "auto",
2869
- scrollbarWidth: "none"
2870
- }),
2871
- children: tabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2872
- role: "tab",
2873
- tabIndex: 0,
2874
- "aria-selected": tab.key === activeKey,
2875
- "data-preview": tab.preview,
2876
- title: tab.preview ? `${tab.title} — preview; double-click to keep open` : tab.title,
2877
- onClick: () => void navigate({ href: tab.href }),
2878
- onKeyDown: (e) => {
2879
- if (e.key === "Enter") navigate({ href: tab.href });
2880
- },
2881
- onDoubleClick: () => pin(tab.key),
2882
- onAuxClick: (e) => {
2883
- if (e.button === 1) close(tab.key);
2884
- },
2885
- className: css({
2886
- position: "relative",
2887
- display: "flex",
2888
- alignItems: "center",
2889
- gap: "1.5",
2890
- pl: "3",
2891
- pr: "1",
2892
- minW: "0",
2893
- maxW: "220px",
2894
- flexShrink: 0,
2895
- borderRight: "1px solid",
2896
- borderColor: "line",
2897
- cursor: "pointer",
2898
- color: "muted",
2899
- userSelect: "none",
2900
- _hover: {
2901
- color: "fg",
2902
- bg: "hover"
2903
- },
2904
- "&[aria-selected=true]": {
2905
- bg: "canvas",
2906
- color: "fg",
2907
- _before: {
2908
- content: "\"\"",
2909
- position: "absolute",
2910
- left: "0",
2911
- right: "0",
2912
- top: "0",
2913
- h: "2px",
2914
- bg: "accent"
2915
- }
2916
- },
2917
- "&[data-preview=true] .tab-title": { fontStyle: "italic" },
2918
- "& .tab-close": { opacity: 0 },
2919
- "&:hover .tab-close, &[aria-selected=true] .tab-close": { opacity: 1 }
2920
- }),
2921
- children: [
2922
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabGlyph, { tab }),
2923
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2924
- className: cx("tab-title", truncate, css({ fontSize: "12px" }), tab.failed ? css({ color: "bad" }) : void 0),
2925
- children: tab.title
2926
- }),
2927
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconButton, {
2928
- label: "Close tab",
2929
- className: cx("tab-close", css({
2930
- w: "20px",
2931
- h: "20px"
2932
- })),
2933
- onClick: (e) => {
2934
- e.stopPropagation();
2935
- close(tab.key);
2936
- },
2937
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconClose, { size: 12 })
2938
- })
2939
- ]
2940
- }, tab.key))
2941
- });
2942
- }
2943
- function TabGlyph({ tab }) {
2944
- if (tab.icon === "request" && tab.method) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MethodBadge, {
2945
- method: tab.method,
2946
- kind: tab.kind
2947
- });
2948
- const glyph = {
2949
- overview: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconOverview, { size: 13 }),
2950
- collection: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconCollections, { size: 13 }),
2951
- run: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconRun, { size: 12 }),
2952
- request: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconRequest, { size: 13 }),
2953
- query: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconDatabase, { size: 13 })
2954
- }[tab.icon];
2955
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2956
- className: css({
2957
- display: "inline-flex",
2958
- color: "faint",
2959
- flexShrink: 0
2960
- }),
2961
- children: glyph
2962
- });
2963
- }
2964
- function StatusBar() {
2965
- const ws = root.useLoaderData();
2966
- const { prefs, lastRefresh } = usePrefs();
2967
- const now = useNow(ws.now, 1e3);
2968
- const c = ws.counts;
2969
- const item = css({
2970
- display: "inline-flex",
2971
- alignItems: "center",
2972
- gap: "1",
2973
- whiteSpace: "nowrap"
2974
- });
2975
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("footer", {
2976
- className: css({
2977
- display: "flex",
2978
- alignItems: "center",
2979
- gap: "4",
2980
- h: "24px",
2981
- px: "3",
2982
- flexShrink: 0,
2983
- fontSize: "11px",
2984
- color: "muted",
2985
- borderTop: "1px solid",
2986
- borderColor: "line",
2987
- overflow: "hidden"
2988
- }),
2989
- children: [
2990
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2991
- className: cx(item, mono, truncate, css({
2992
- minW: "0",
2993
- flexShrink: 1
2994
- })),
2995
- title: ws.path,
2996
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconDatabase, { size: 12 }), ws.path]
2997
- }),
2998
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2999
- className: item,
3000
- children: formatBytes(ws.bytes)
3001
- }),
3002
- c ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
3003
- className: item,
3004
- children: [
3005
- formatCount(c.runs),
3006
- " runs · ",
3007
- formatCount(c.requests + c.connections),
3008
- " requests"
3009
- ]
3010
- }) : null,
3011
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
3012
- className: item,
3013
- children: ["schema v", ws.schema.file]
3014
- }),
3015
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
3016
- className: cx(item, css({ ml: "auto" })),
3017
- children: [prefs.live ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
3018
- className: css({ color: "ok" }),
3019
- children: "● Live"
3020
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Read-only" }), lastRefresh ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
3021
- className: css({ color: "faint" }),
3022
- children: ["· read ", formatAgo(now, lastRefresh)]
3023
- }) : null]
3024
- })
3025
- ]
3026
- });
3027
- }
3028
- var globals_default = "/assets/globals-CZiNFGaK.css";
3029
- var Route$5 = createRootRoute({
3030
- head: () => ({
3031
- meta: [
3032
- { charSet: "utf-8" },
3033
- {
3034
- name: "viewport",
3035
- content: "width=device-width, initial-scale=1"
3036
- },
3037
- {
3038
- name: "color-scheme",
3039
- content: "light dark"
3040
- },
3041
- { title: "wireui" }
3042
- ],
3043
- links: [{
3044
- rel: "stylesheet",
3045
- href: globals_default
3046
- }, {
3047
- rel: "icon",
3048
- href: "/favicon.svg",
3049
- type: "image/svg+xml"
3050
- }]
3051
- }),
3052
- loader: () => getWorkspace(),
3053
- staleTime: 0,
3054
- errorComponent: RouteError,
3055
- notFoundComponent: NotFound,
3056
- component: RootLayout,
3057
- shellComponent: RootDocument
3058
- });
3059
- function RootLayout() {
3060
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Frame, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Outlet, {}) });
3061
- }
3062
- function Frame({ children }) {
3063
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PrefsProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabsProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { children }) }) });
3064
- }
3065
- function RootDocument({ children }) {
3066
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("html", {
3067
- lang: "en",
3068
- suppressHydrationWarning: true,
3069
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("head", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("script", { dangerouslySetInnerHTML: { __html: THEME_BOOT_SCRIPT } }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HeadContent, {})] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("body", { children: [children, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Scripts, {})] })]
3070
- });
3071
- }
3072
- var $$splitComponentImporter$4 = () => import("./routes-DaV9vGHn.mjs");
3073
- var Route$4 = createFileRoute("/")({
3074
- loader: () => getOverview({ data: {} }),
3075
- component: lazyRouteComponent($$splitComponentImporter$4, "component")
3076
- });
3077
- var $$splitComponentImporter$3 = () => import("./collections-CqidTQpw.mjs");
3078
- var Route$3 = createFileRoute("/collections")({
3079
- validateSearch: (search) => ({
3080
- entry: typeof search.entry === "string" ? search.entry : "",
3081
- tab: search.tab === "requests" || search.tab === "checks" ? search.tab : void 0
3082
- }),
3083
- loaderDeps: ({ search }) => ({ entry: search.entry }),
3084
- loader: async ({ deps }) => {
3085
- if (!deps.entry) throw notFound();
3086
- const page = await getScript({ data: { entry: deps.entry } });
3087
- if (!page) throw notFound();
3088
- return page;
3089
- },
3090
- component: lazyRouteComponent($$splitComponentImporter$3, "component")
3091
- });
3092
- /** Most recent {@link STRIP_RUNS} runs as bars, oldest on the left. */
3093
- var $$splitComponentImporter$2 = () => import("./query-D4so9xiY.mjs");
3094
- var Route$2 = createFileRoute("/query")({
3095
- validateSearch: (search) => ({ sql: typeof search.sql === "string" ? search.sql : void 0 }),
3096
- loader: () => getSchema(),
3097
- component: lazyRouteComponent($$splitComponentImporter$2, "component")
3098
- });
3099
- var $$splitComponentImporter$1 = () => import("./requests._requestId-BFukRwqL.mjs");
3100
- var REQ_TABS = [
3101
- "params",
3102
- "headers",
3103
- "body",
3104
- "cookies",
3105
- "tags"
3106
- ];
3107
- var RES_TABS = [
3108
- "body",
3109
- "messages",
3110
- "headers",
3111
- "cookies",
3112
- "timeline"
3113
- ];
3114
- var Route$1 = createFileRoute("/requests/$requestId")({
3115
- validateSearch: (search) => ({
3116
- req: REQ_TABS.includes(search.req) ? search.req : void 0,
3117
- res: RES_TABS.includes(search.res) ? search.res : void 0
3118
- }),
3119
- loader: async ({ params }) => {
3120
- const page = await getRequest({ data: { requestId: params.requestId } });
3121
- if (!page) throw notFound();
3122
- return page;
3123
- },
3124
- component: lazyRouteComponent($$splitComponentImporter$1, "component")
3125
- });
3126
- var $$splitComponentImporter = () => import("./runs._runId-Df_LmPHa.mjs");
3127
- var Route = createFileRoute("/runs/$runId")({
3128
- validateSearch: (search) => ({ tab: search.tab === "checks" || search.tab === "insights" || search.tab === "details" ? search.tab : void 0 }),
3129
- loader: async ({ params }) => {
3130
- const page = await getRun({ data: { runId: params.runId } });
3131
- if (!page) throw notFound();
3132
- return page;
3133
- },
3134
- component: lazyRouteComponent($$splitComponentImporter, "component")
3135
- });
3136
- var rootRouteChildren = {
3137
- IndexRoute: Route$4.update({
3138
- id: "/",
3139
- path: "/",
3140
- getParentRoute: () => Route$5
3141
- }),
3142
- CollectionsRoute: Route$3.update({
3143
- id: "/collections",
3144
- path: "/collections",
3145
- getParentRoute: () => Route$5
3146
- }),
3147
- QueryRoute: Route$2.update({
3148
- id: "/query",
3149
- path: "/query",
3150
- getParentRoute: () => Route$5
3151
- }),
3152
- RequestsRequestIdRoute: Route$1.update({
3153
- id: "/requests/$requestId",
3154
- path: "/requests/$requestId",
3155
- getParentRoute: () => Route$5
3156
- }),
3157
- RunsRunIdRoute: Route.update({
3158
- id: "/runs/$runId",
3159
- path: "/runs/$runId",
3160
- getParentRoute: () => Route$5
3161
- })
3162
- };
3163
- var routeTree = Route$5._addFileChildren(rootRouteChildren)._addFileTypes();
3164
- var router_exports = /* @__PURE__ */ __exportAll({ getRouter: () => getRouter });
3165
- function getRouter() {
3166
- return createRouter({
3167
- routeTree,
3168
- defaultPreload: "intent",
3169
- defaultPreloadStaleTime: 0,
3170
- scrollRestoration: true,
3171
- defaultNotFoundComponent: NotFound,
3172
- defaultErrorComponent: RouteError
3173
- });
3174
- }
3175
1870
  //#endregion
3176
- export { Button, Callout, Card, CopyButton, Dot, Empty, FilterInput, IconAlert, IconButton, IconCheckCircle, IconChevronDown, IconChevronLeft, IconChevronRight, IconCollections, IconDatabase, IconDown, IconFile, IconPlay, IconRun, IconUp, IconXCircle, Kbd, MethodBadge, Pill, Route, Route$1, Route$2, Route$3, Route$4, STATUS_TEXT, SectionTitle, Segmented, Stat, StatGrid, StatusBadge, TabList, bodyRaw, bodyText, css, cx, envFilter, faintText, formatAgo, formatBytes, formatClockMs, formatCount, formatDateTime, formatDay, formatMs, formatOffset, getOverview, getScript, isFailure, mono, pagePad, plural, queryParams, requestCookies, responseCookies, router_exports, runSql, runTone, runVerdict, scrollPane, statusTone, tabKey, table, timingPhases, toCurl, toneText, truncate, useNow, usePrefs, useTab, useUnreadable };
1871
+ export { Button, Callout, Card, CopyButton, Dot, Empty, FilterInput, IconAlert, IconButton, IconCheckCircle, IconChevronDown, IconChevronLeft, IconChevronRight, IconClose, IconDatabase, IconDown, IconEnvironments, IconFile, IconHistory, IconMonitor, IconMoon, IconOverview, IconPlay, IconRefresh, IconRequest, IconRun, IconSessions, IconSplitSide, IconSplitStacked, IconSun, IconUp, IconXCircle, Kbd, MethodBadge, Metric, NotFound, Pill, PrefsProvider, RouteError, STATUS_TEXT, SectionTitle, Segmented, Stat, StatGrid, StatStrip, StatusBadge, THEME_BOOT_SCRIPT, TabList, TabsProvider, Toggle, basename, bodyRaw, bodyText, css, cx, envFilter, faintText, formatAgo, formatBytes, formatClock, formatClockMs, formatCount, formatDateTime, formatDay, formatMs, formatOffset, groupByDay, isFailure, matchesEnv, mono, pagePad, plural, queryParams, requestCookies, requestErrorsHint, responseCookies, runTone, runVerdict, scrollPane, statusTone, tabKey, table, timingPhases, toCurl, toneText, truncate, useNow, usePrefs, useTab, useTabs, useUnreadable };