@flytedan/flytebot-design-system 0.15.0 → 0.17.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.
package/dist/index.cjs CHANGED
@@ -172,6 +172,7 @@ __export(index_exports, {
172
172
  channelWeightOf: () => channelWeightOf,
173
173
  computeNeedMore: () => computeNeedMore,
174
174
  computeVirtualWindow: () => computeVirtualWindow,
175
+ createLocalStorageAppearanceStore: () => createLocalStorageAppearanceStore,
175
176
  createVersionStore: () => createVersionStore,
176
177
  eqFilter: () => eqFilter,
177
178
  extensionOf: () => extensionOf,
@@ -211,6 +212,7 @@ __export(index_exports, {
211
212
  useChatEngine: () => useChatEngine,
212
213
  useFeatureStatus: () => useFeatureStatus,
213
214
  usePopoverPosition: () => usePopoverPosition,
215
+ useResponsiveRail: () => useResponsiveRail,
214
216
  useRuntimeMode: () => useRuntimeMode,
215
217
  useServerTable: () => useServerTable,
216
218
  useSessionContext: () => useSessionContext,
@@ -1106,37 +1108,48 @@ function Spinner({ size = "md", label = "Loading", className = "", style, ...res
1106
1108
  }
1107
1109
 
1108
1110
  // src/components/navigation/SidebarNav.tsx
1111
+ var React6 = __toESM(require("react"), 1);
1109
1112
  var import_jsx_runtime13 = require("react/jsx-runtime");
1110
1113
  function Tip({ label, children }) {
1111
1114
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { title: label, style: { display: "inline-flex", flex: "none" }, children });
1112
1115
  }
1113
- function SidebarNav({ brand, groups = [], current: current2, onNavigate, footer, open = false, className = "", ...rest }) {
1114
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("nav", { className: ["fd-side", className].filter(Boolean).join(" "), "data-open": open ? "true" : "false", ...rest, children: [
1115
- brand ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: { height: "var(--topbar-h)", display: "flex", alignItems: "center", padding: "0 18px", borderBottom: "1px solid var(--border)", flex: "none", overflow: "hidden" }, children: brand }) : null,
1116
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: { flex: 1, overflow: "auto", padding: "4px 10px 18px" }, children: groups.map((g, gi) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
1117
- g.label ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "fd-side-group", children: g.label }) : null,
1118
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "fd-stack", style: { gap: 2 }, children: g.items.map((it) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1119
- "button",
1120
- {
1121
- type: "button",
1122
- className: "fd-side-item",
1123
- "aria-current": current2 === it.value ? "page" : void 0,
1124
- onClick: onNavigate ? () => onNavigate(it.value) : void 0,
1125
- children: [
1126
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "fd-side-item-icon", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("i", { className: "ph ph-" + it.icon, "aria-hidden": "true" }) }),
1127
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, title: truncationTitle(it.label), children: it.label }),
1128
- it.preview ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Tip, { label: "Designed, backend not wired yet", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("i", { className: "ph ph-traffic-cone", style: { fontSize: 13, color: "var(--warn-solid)", flex: "none" } }) }) : null,
1129
- it.locked ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Tip, { label: "You do not have permission for this", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("i", { className: "ph ph-lock-simple", style: { fontSize: 13, color: "var(--text-muted)", flex: "none" } }) }) : null,
1130
- it.count !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "fd-side-item-count", children: it.count }) : null,
1131
- it.loading ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "fd-skel fd-skel-text", style: { width: 22, height: 11, marginLeft: "auto" } }) : null
1132
- ]
1133
- },
1134
- it.value
1135
- )) })
1136
- ] }, gi)) }),
1137
- footer ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: { borderTop: "1px solid var(--border)", padding: 14, flex: "none" }, children: footer }) : null
1138
- ] });
1139
- }
1116
+ var SidebarNav = React6.forwardRef(function SidebarNav2({ brand, groups = [], current: current2, onNavigate, footer, open = false, className = "", ...rest }, ref) {
1117
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1118
+ "nav",
1119
+ {
1120
+ ref,
1121
+ tabIndex: -1,
1122
+ className: ["fd-side", className].filter(Boolean).join(" "),
1123
+ "data-open": open ? "true" : "false",
1124
+ ...rest,
1125
+ children: [
1126
+ brand ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: { height: "var(--topbar-h)", display: "flex", alignItems: "center", padding: "0 18px", borderBottom: "1px solid var(--border)", flex: "none", overflow: "hidden" }, children: brand }) : null,
1127
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: { flex: 1, overflow: "auto", padding: "4px 10px 18px" }, children: groups.map((g, gi) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
1128
+ g.label ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "fd-side-group", children: g.label }) : null,
1129
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "fd-stack", style: { gap: 2 }, children: g.items.map((it) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1130
+ "button",
1131
+ {
1132
+ type: "button",
1133
+ className: "fd-side-item",
1134
+ "aria-current": current2 === it.value ? "page" : void 0,
1135
+ onClick: onNavigate ? () => onNavigate(it.value) : void 0,
1136
+ children: [
1137
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "fd-side-item-icon", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("i", { className: "ph ph-" + it.icon, "aria-hidden": "true" }) }),
1138
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, title: truncationTitle(it.label), children: it.label }),
1139
+ it.preview ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Tip, { label: "Designed, backend not wired yet", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("i", { className: "ph ph-traffic-cone", style: { fontSize: 13, color: "var(--warn-solid)", flex: "none" } }) }) : null,
1140
+ it.locked ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Tip, { label: "You do not have permission for this", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("i", { className: "ph ph-lock-simple", style: { fontSize: 13, color: "var(--text-muted)", flex: "none" } }) }) : null,
1141
+ it.count !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "fd-side-item-count", children: it.count }) : null,
1142
+ it.loading ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "fd-skel fd-skel-text", style: { width: 22, height: 11, marginLeft: "auto" } }) : null
1143
+ ]
1144
+ },
1145
+ it.value
1146
+ )) })
1147
+ ] }, gi)) }),
1148
+ footer ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: { borderTop: "1px solid var(--border)", padding: 14, flex: "none" }, children: footer }) : null
1149
+ ]
1150
+ }
1151
+ );
1152
+ });
1140
1153
 
1141
1154
  // src/components/navigation/FlytedeskBrand.tsx
1142
1155
  var import_jsx_runtime14 = require("react/jsx-runtime");
@@ -1177,13 +1190,13 @@ function Tabs({ tabs = [], value, onChange, className = "", ...rest }) {
1177
1190
  }
1178
1191
 
1179
1192
  // src/components/navigation/Topbar.tsx
1180
- var React6 = __toESM(require("react"), 1);
1193
+ var React7 = __toESM(require("react"), 1);
1181
1194
  var import_jsx_runtime16 = require("react/jsx-runtime");
1182
1195
  function useIsNarrow(breakpoint) {
1183
- const [narrow, setNarrow] = React6.useState(
1196
+ const [narrow, setNarrow] = React7.useState(
1184
1197
  () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(`(max-width: ${breakpoint}px)`).matches
1185
1198
  );
1186
- React6.useEffect(() => {
1199
+ React7.useEffect(() => {
1187
1200
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
1188
1201
  const mq = window.matchMedia(`(max-width: ${breakpoint}px)`);
1189
1202
  const apply = () => setNarrow(mq.matches);
@@ -1204,11 +1217,11 @@ function Topbar({
1204
1217
  ...rest
1205
1218
  }) {
1206
1219
  const narrow = useIsNarrow(overflowBreakpoint);
1207
- const [moreOpen, setMoreOpen] = React6.useState(false);
1208
- const moreRef = React6.useRef(null);
1220
+ const [moreOpen, setMoreOpen] = React7.useState(false);
1221
+ const moreRef = React7.useRef(null);
1209
1222
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("header", { className: ["fd-topbar", narrow ? "is-narrow" : "", className].filter(Boolean).join(" "), ...rest, children: [
1210
1223
  onMenu ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: "fd-btn-icon", "aria-label": "Menu", onClick: onMenu, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("i", { className: "ph ph-list" }) }) : null,
1211
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("nav", { className: "fd-crumb", "aria-label": "Breadcrumb", children: crumbs.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(React6.Fragment, { children: [
1224
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("nav", { className: "fd-crumb", "aria-label": "Breadcrumb", children: crumbs.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(React7.Fragment, { children: [
1212
1225
  i > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("i", { className: "ph ph-caret-right", style: { fontSize: 11, opacity: 0.6 }, "aria-hidden": "true" }) : null,
1213
1226
  i === crumbs.length - 1 ? (
1214
1227
  // MP-141: the current crumb mixes a static route label with (usually) a
@@ -1417,18 +1430,18 @@ function Toast({ title, children, tone = "success", onUndo, onDismiss, className
1417
1430
  }
1418
1431
 
1419
1432
  // src/components/feedback/Tooltip.tsx
1420
- var React7 = __toESM(require("react"), 1);
1433
+ var React8 = __toESM(require("react"), 1);
1421
1434
  var import_jsx_runtime24 = require("react/jsx-runtime");
1422
1435
  var CLOSE_DELAY_MS = 120;
1423
1436
  function Tooltip({ label, placement = "top", children, className = "" }) {
1424
- const anchorRef = React7.useRef(null);
1425
- const bubbleRef = React7.useRef(null);
1426
- const [reason, setReason] = React7.useState(null);
1427
- const closeTimer = React7.useRef(null);
1428
- const id = React7.useId();
1437
+ const anchorRef = React8.useRef(null);
1438
+ const bubbleRef = React8.useRef(null);
1439
+ const [reason, setReason] = React8.useState(null);
1440
+ const closeTimer = React8.useRef(null);
1441
+ const id = React8.useId();
1429
1442
  const descId = id + "-desc";
1430
1443
  const open = reason != null;
1431
- const cancelClose = React7.useCallback(() => {
1444
+ const cancelClose = React8.useCallback(() => {
1432
1445
  if (closeTimer.current) {
1433
1446
  clearTimeout(closeTimer.current);
1434
1447
  closeTimer.current = null;
@@ -1438,7 +1451,7 @@ function Tooltip({ label, placement = "top", children, className = "" }) {
1438
1451
  cancelClose();
1439
1452
  setReason(why);
1440
1453
  };
1441
- const hide = React7.useCallback(() => {
1454
+ const hide = React8.useCallback(() => {
1442
1455
  cancelClose();
1443
1456
  setReason(null);
1444
1457
  }, [cancelClose]);
@@ -1453,7 +1466,7 @@ function Tooltip({ label, placement = "top", children, className = "" }) {
1453
1466
  setReason((r) => r === "hover" ? null : r);
1454
1467
  }, CLOSE_DELAY_MS);
1455
1468
  };
1456
- React7.useEffect(() => cancelClose, [cancelClose]);
1469
+ React8.useEffect(() => cancelClose, [cancelClose]);
1457
1470
  const pos = usePopoverPosition(open, anchorRef, {
1458
1471
  placement: placement === "bottom" ? "bottom-center" : "top-center",
1459
1472
  offset: 8,
@@ -1463,7 +1476,7 @@ function Tooltip({ label, placement = "top", children, className = "" }) {
1463
1476
  onDetach: hide
1464
1477
  });
1465
1478
  const zIndex = useOverlayLayer(open);
1466
- React7.useEffect(() => {
1479
+ React8.useEffect(() => {
1467
1480
  if (!open) return;
1468
1481
  const key = (e) => {
1469
1482
  if (e.key === "Escape") hide();
@@ -1491,7 +1504,7 @@ function Tooltip({ label, placement = "top", children, className = "" }) {
1491
1504
  }
1492
1505
  if (keyboard) show("focus");
1493
1506
  };
1494
- const described = React7.isValidElement(children) ? React7.cloneElement(children, {
1507
+ const described = React8.isValidElement(children) ? React8.cloneElement(children, {
1495
1508
  "aria-describedby": [children.props["aria-describedby"], descId].filter(Boolean).join(" ")
1496
1509
  }) : children;
1497
1510
  return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
@@ -1499,7 +1512,7 @@ function Tooltip({ label, placement = "top", children, className = "" }) {
1499
1512
  {
1500
1513
  ref: anchorRef,
1501
1514
  className: "fd-tooltip-anchor",
1502
- "aria-describedby": React7.isValidElement(children) ? void 0 : descId,
1515
+ "aria-describedby": React8.isValidElement(children) ? void 0 : descId,
1503
1516
  onPointerEnter: (e) => {
1504
1517
  if (isMouse(e)) hover();
1505
1518
  },
@@ -1543,7 +1556,7 @@ function Tooltip({ label, placement = "top", children, className = "" }) {
1543
1556
  }
1544
1557
 
1545
1558
  // src/components/data/Clamp.tsx
1546
- var React8 = __toESM(require("react"), 1);
1559
+ var React9 = __toESM(require("react"), 1);
1547
1560
  var import_jsx_runtime25 = require("react/jsx-runtime");
1548
1561
  function Clamp({
1549
1562
  children,
@@ -1555,16 +1568,16 @@ function Clamp({
1555
1568
  onToggle,
1556
1569
  className = ""
1557
1570
  }) {
1558
- const [open, setOpen] = React8.useState(false);
1559
- const [overflows, setOverflows] = React8.useState(false);
1560
- const bodyRef = React8.useRef(null);
1561
- const charClamped = React8.useMemo(() => {
1571
+ const [open, setOpen] = React9.useState(false);
1572
+ const [overflows, setOverflows] = React9.useState(false);
1573
+ const bodyRef = React9.useRef(null);
1574
+ const charClamped = React9.useMemo(() => {
1562
1575
  if (open || !maxChars || typeof text !== "string" || text.length <= maxChars) return null;
1563
1576
  const cut = text.slice(0, maxChars);
1564
1577
  const at = cut.lastIndexOf(" ");
1565
1578
  return (at > maxChars * 0.6 ? cut.slice(0, at) : cut).trimEnd() + "\u2026";
1566
1579
  }, [text, maxChars, open]);
1567
- React8.useLayoutEffect(() => {
1580
+ React9.useLayoutEffect(() => {
1568
1581
  const node = bodyRef.current;
1569
1582
  if (maxChars || !node || !maxHeight) return;
1570
1583
  const measure = () => {
@@ -1703,18 +1716,18 @@ function DataTable({
1703
1716
  }
1704
1717
 
1705
1718
  // src/components/data/FilterBar.tsx
1706
- var React9 = __toESM(require("react"), 1);
1719
+ var React10 = __toESM(require("react"), 1);
1707
1720
  var import_jsx_runtime28 = require("react/jsx-runtime");
1708
1721
  var isSet = (v) => !(v == null || v === "" || v === false);
1709
1722
  function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align = "left", className = "", ...rest }) {
1710
- const [panel, setPanel] = React9.useState(null);
1723
+ const [panel, setPanel] = React10.useState(null);
1711
1724
  const zIndex = useOverlayLayer(!!panel);
1712
- const rootRef = React9.useRef(null);
1713
- const textInputRef = React9.useRef(null);
1714
- const optionsRef = React9.useRef(null);
1725
+ const rootRef = React10.useRef(null);
1726
+ const textInputRef = React10.useRef(null);
1727
+ const optionsRef = React10.useRef(null);
1715
1728
  const editingField = panel && panel.kind === "edit" ? fields.find((f) => f.key === panel.key) : void 0;
1716
1729
  const dismissible = !!panel && (panel.kind === "add" || panel.kind === "edit" && editingField?.type === "select");
1717
- React9.useEffect(() => {
1730
+ React10.useEffect(() => {
1718
1731
  if (!dismissible) return;
1719
1732
  const away = (e) => {
1720
1733
  if (rootRef.current && !rootRef.current.contains(e.target)) setPanel(null);
@@ -1729,7 +1742,7 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
1729
1742
  document.removeEventListener("keydown", esc);
1730
1743
  };
1731
1744
  }, [dismissible]);
1732
- React9.useEffect(() => {
1745
+ React10.useEffect(() => {
1733
1746
  if (!panel || panel.kind !== "edit") return;
1734
1747
  if (editingField?.type === "text") {
1735
1748
  textInputRef.current?.focus();
@@ -1895,7 +1908,7 @@ function StatTile({ label, value, sub, delta, loading = false, compact: compact3
1895
1908
  }
1896
1909
 
1897
1910
  // src/components/data/CodeBlock.tsx
1898
- var React10 = __toESM(require("react"), 1);
1911
+ var React11 = __toESM(require("react"), 1);
1899
1912
  var import_jsx_runtime30 = require("react/jsx-runtime");
1900
1913
  var JS_KW = /^(?:const|let|var|function|return|if|else|for|while|do|new|class|extends|import|from|export|default|await|async|try|catch|finally|throw|typeof|instanceof|of|in|switch|case|break|continue|delete|void|yield|static|get|set|this|super|interface|type|enum|implements|public|private|protected|readonly|abstract|as|satisfies|declare|namespace)$/;
1901
1914
  var RULES = {
@@ -2051,13 +2064,13 @@ function tokenize(src, lang) {
2051
2064
  return out;
2052
2065
  }
2053
2066
  function Highlighted({ code, lang }) {
2054
- const toks = React10.useMemo(() => tokenize(code, lang), [code, lang]);
2067
+ const toks = React11.useMemo(() => tokenize(code, lang), [code, lang]);
2055
2068
  return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_jsx_runtime30.Fragment, { children: toks.map((tk, i) => tk.c ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "tok-" + tk.c, children: tk.t }, i) : tk.t) });
2056
2069
  }
2057
2070
  function useCopy() {
2058
- const [done, setDone] = React10.useState(false);
2059
- const timer = React10.useRef(null);
2060
- React10.useEffect(() => () => {
2071
+ const [done, setDone] = React11.useState(false);
2072
+ const timer = React11.useRef(null);
2073
+ React11.useEffect(() => () => {
2061
2074
  if (timer.current) clearTimeout(timer.current);
2062
2075
  }, []);
2063
2076
  const copy = (text) => {
@@ -2093,8 +2106,8 @@ function CodeBlock({
2093
2106
  actions,
2094
2107
  className = ""
2095
2108
  }) {
2096
- const [wrap, setWrap] = React10.useState(!!wrapDefault);
2097
- const [open, setOpen] = React10.useState(false);
2109
+ const [wrap, setWrap] = React11.useState(!!wrapDefault);
2110
+ const [open, setOpen] = React11.useState(false);
2098
2111
  const [copied, copy] = useCopy();
2099
2112
  const body = String(code).replace(/\n$/, "");
2100
2113
  const lines = body.split("\n");
@@ -2123,7 +2136,7 @@ function CodeBlock({
2123
2136
  ] });
2124
2137
  }
2125
2138
  function DiffBlock({ diff = "", filename, collapseAfter = 34, className = "" }) {
2126
- const [open, setOpen] = React10.useState(false);
2139
+ const [open, setOpen] = React11.useState(false);
2127
2140
  const [copied, copy] = useCopy();
2128
2141
  const raw = String(diff).replace(/\n$/, "");
2129
2142
  const all = raw.split("\n").map((line) => {
@@ -2169,7 +2182,7 @@ function DiffBlock({ diff = "", filename, collapseAfter = 34, className = "" })
2169
2182
  }
2170
2183
 
2171
2184
  // src/components/data/Markdown.tsx
2172
- var React11 = __toESM(require("react"), 1);
2185
+ var React12 = __toESM(require("react"), 1);
2173
2186
  var import_jsx_runtime31 = require("react/jsx-runtime");
2174
2187
  var SAFE = /^(?:https?:|mailto:|tel:|#|\/|\.{1,2}\/)/i;
2175
2188
  var safeHref = (h) => SAFE.test(String(h || "").trim()) ? String(h).trim() : null;
@@ -2208,7 +2221,7 @@ function inlineNodes(src, ctx, keyBase) {
2208
2221
  const href = safeHref(g(2));
2209
2222
  out.push(href ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("a", { href, title: g(3) || void 0, target: /^https?:/i.test(href) ? "_blank" : void 0, rel: "noopener noreferrer", onClick: ctx.onLinkClick, children: inlineNodes(g(1), ctx, key()) }, key()) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { children: g(1) }, key()));
2210
2223
  } else if (kind === "cite") {
2211
- out.push(ctx.renderCitation ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(React11.Fragment, { children: ctx.renderCitation(g(1)) }, key()) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("sup", { className: "fd-md-cite", children: g(1) }, key()));
2224
+ out.push(ctx.renderCitation ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(React12.Fragment, { children: ctx.renderCitation(g(1)) }, key()) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("sup", { className: "fd-md-cite", children: g(1) }, key()));
2212
2225
  } else if (kind === "strongem") out.push(/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("strong", { children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("em", { children: inlineNodes(g(1), ctx, key()) }) }, key()));
2213
2226
  else if (kind === "strong") out.push(/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("strong", { children: inlineNodes(g(1) || g(2), ctx, key()) }, key()));
2214
2227
  else if (kind === "em") out.push(/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("em", { children: inlineNodes(g(1) || g(2), ctx, key()) }, key()));
@@ -2373,7 +2386,7 @@ function Markdown({
2373
2386
  onLinkClick
2374
2387
  }) {
2375
2388
  const ctx = { allowImages, headingOffset, renderCitation, onLinkClick, codeProps };
2376
- const blocks = React11.useMemo(() => parseBlocks(source), [source]);
2389
+ const blocks = React12.useMemo(() => parseBlocks(source), [source]);
2377
2390
  return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: ["fd-md", className].filter(Boolean).join(" "), children: renderBlocks(blocks, ctx, "b") });
2378
2391
  }
2379
2392
  function MarkdownInline({ source = "", className = "", onLinkClick }) {
@@ -2487,7 +2500,7 @@ function Pagination({
2487
2500
  }
2488
2501
 
2489
2502
  // src/components/data/RelativeTime.tsx
2490
- var React12 = __toESM(require("react"), 1);
2503
+ var React13 = __toESM(require("react"), 1);
2491
2504
  var import_jsx_runtime34 = require("react/jsx-runtime");
2492
2505
  var MIN = 6e4;
2493
2506
  var HOUR = 36e5;
@@ -2539,9 +2552,9 @@ function formatDuration(ms) {
2539
2552
  return h + "h" + (m % 60 ? " " + m % 60 + "m" : "");
2540
2553
  }
2541
2554
  function RelativeTime({ value, prefix, className = "", refresh: refresh2 = true, ...rest }) {
2542
- const [, force] = React12.useState(0);
2555
+ const [, force] = React13.useState(0);
2543
2556
  const t = value instanceof Date ? value.getTime() : Date.parse(value);
2544
- React12.useEffect(() => {
2557
+ React13.useEffect(() => {
2545
2558
  if (!refresh2 || !t || isNaN(t)) return;
2546
2559
  const abs = Math.abs(t - Date.now());
2547
2560
  if (abs > DAY) return;
@@ -2557,10 +2570,10 @@ function RelativeTime({ value, prefix, className = "", refresh: refresh2 = true,
2557
2570
  }
2558
2571
 
2559
2572
  // src/components/data/ScoreMeter.tsx
2560
- var React14 = __toESM(require("react"), 1);
2573
+ var React15 = __toESM(require("react"), 1);
2561
2574
 
2562
2575
  // src/components/data/SegmentedMeter.tsx
2563
- var React13 = __toESM(require("react"), 1);
2576
+ var React14 = __toESM(require("react"), 1);
2564
2577
 
2565
2578
  // src/kits/files.ts
2566
2579
  function formatBytes(n) {
@@ -2765,7 +2778,7 @@ function SegmentedMeter({
2765
2778
  const pct = safeTotal ? Math.min(100, Math.round(used / safeTotal * 100)) : 0;
2766
2779
  const w = (v) => safeTotal ? Math.max(0, Math.min(100, Number(v) / safeTotal * 100)) : 0;
2767
2780
  const fill = (s, i) => s.color || (s.variant ? VARIANT_FILL[s.variant] : void 0) || CHART[i % CHART.length];
2768
- const generatedId = React13.useId();
2781
+ const generatedId = React14.useId();
2769
2782
  const uid2 = id || generatedId;
2770
2783
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: ["fd-segmeter", className].filter(Boolean).join(" "), onClick, children: [
2771
2784
  label || showTotal ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "fd-segmeter-head", children: [
@@ -2849,10 +2862,10 @@ function ScoreMeter({
2849
2862
  breakdownWidth = 280,
2850
2863
  className = ""
2851
2864
  }) {
2852
- const [open, setOpen] = React14.useState(false);
2853
- const ref = React14.useRef(null);
2865
+ const [open, setOpen] = React15.useState(false);
2866
+ const ref = React15.useRef(null);
2854
2867
  const value = Math.max(0, Math.min(100, Math.round(Number(score) || 0)));
2855
- const body = /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(React14.Fragment, { children: [
2868
+ const body = /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(React15.Fragment, { children: [
2856
2869
  /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "fd-scoremeter-rail", style: { width }, "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(SegmentedMeter, { total: 100, segments, height, showTotal: false, showPercent: false, legend: false }) }),
2857
2870
  showScore ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "fd-scoremeter-value fd-tabular", "aria-hidden": "true", children: value }) : null
2858
2871
  ] });
@@ -2871,7 +2884,7 @@ function ScoreMeter({
2871
2884
  }
2872
2885
  );
2873
2886
  }
2874
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(React14.Fragment, { children: [
2887
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(React15.Fragment, { children: [
2875
2888
  /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2876
2889
  "button",
2877
2890
  {
@@ -2902,13 +2915,13 @@ function ScoreMeter({
2902
2915
  }
2903
2916
 
2904
2917
  // src/components/data/SortMenu.tsx
2905
- var React15 = __toESM(require("react"), 1);
2918
+ var React16 = __toESM(require("react"), 1);
2906
2919
  var import_jsx_runtime37 = require("react/jsx-runtime");
2907
2920
  function SortMenu({ fields = [], sort, onSort, align = "right", size = "md", className = "", ...rest }) {
2908
- const [open, setOpen] = React15.useState(false);
2921
+ const [open, setOpen] = React16.useState(false);
2909
2922
  const zIndex = useOverlayLayer(open);
2910
- const ref = React15.useRef(null);
2911
- React15.useEffect(() => {
2923
+ const ref = React16.useRef(null);
2924
+ React16.useEffect(() => {
2912
2925
  if (!open) return;
2913
2926
  const away = (e) => {
2914
2927
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -2970,7 +2983,7 @@ function SortMenu({ fields = [], sort, onSort, align = "right", size = "md", cla
2970
2983
  }
2971
2984
 
2972
2985
  // src/components/data/StepList.tsx
2973
- var React16 = __toESM(require("react"), 1);
2986
+ var React17 = __toESM(require("react"), 1);
2974
2987
  var import_jsx_runtime38 = require("react/jsx-runtime");
2975
2988
  var KIND_ICON = { tool: "wrench", search: "magnifying-glass", read: "file-text", write: "pencil-simple", reasoning: "brain", run: "terminal", fetch: "cloud-arrow-down" };
2976
2989
  function StepList({
@@ -2983,9 +2996,9 @@ function StepList({
2983
2996
  dense = false,
2984
2997
  className = ""
2985
2998
  }) {
2986
- const [openState, setOpenState] = React16.useState(defaultOpen);
2999
+ const [openState, setOpenState] = React17.useState(defaultOpen);
2987
3000
  const open = openProp == null ? openState : openProp;
2988
- const [expanded, setExpanded] = React16.useState({});
3001
+ const [expanded, setExpanded] = React17.useState({});
2989
3002
  if (!steps.length) return null;
2990
3003
  const running = steps.some((s) => s.status === "running");
2991
3004
  const failed = steps.filter((s) => s.status === "error").length;
@@ -3043,7 +3056,7 @@ function StepList({
3043
3056
  }
3044
3057
 
3045
3058
  // src/components/data/VirtualList.tsx
3046
- var React17 = __toESM(require("react"), 1);
3059
+ var React18 = __toESM(require("react"), 1);
3047
3060
  var import_jsx_runtime39 = require("react/jsx-runtime");
3048
3061
  var VIRTUAL_LIST_BUFFER_ROWS = 20;
3049
3062
  var VIRTUAL_LIST_ROW_GAP = 6;
@@ -3095,35 +3108,35 @@ function VirtualList({
3095
3108
  className = "",
3096
3109
  style
3097
3110
  }) {
3098
- const [scrollTop, setScrollTop] = React17.useState(0);
3099
- const loaded = React17.useMemo(
3111
+ const [scrollTop, setScrollTop] = React18.useState(0);
3112
+ const loaded = React18.useMemo(
3100
3113
  () => ({ firstItemIndex, count: items.length }),
3101
3114
  [firstItemIndex, items.length]
3102
3115
  );
3103
3116
  const total = Math.max(totalCount == null ? 0 : totalCount, firstItemIndex + items.length);
3104
3117
  const bandKey = firstItemIndex + ":" + items.length;
3105
- const win = React17.useMemo(
3118
+ const win = React18.useMemo(
3106
3119
  () => computeVirtualWindow({ scrollTop, viewportHeight: height, itemHeight, itemCount: total, loaded }),
3107
3120
  [scrollTop, height, itemHeight, total, loaded]
3108
3121
  );
3109
- const need = React17.useMemo(() => computeNeedMore(win, loaded), [win, loaded]);
3122
+ const need = React18.useMemo(() => computeNeedMore(win, loaded), [win, loaded]);
3110
3123
  const wantStart = need.start && hasMore?.start !== false;
3111
3124
  const wantEnd = need.end && hasMore?.end !== false;
3112
3125
  const bucket2 = Math.floor(win.visibleStart / VIRTUAL_LIST_BUFFER_ROWS);
3113
3126
  const askKey = bandKey + "@" + bucket2;
3114
- const asked = React17.useRef({ start: null, end: null });
3115
- React17.useEffect(() => {
3127
+ const asked = React18.useRef({ start: null, end: null });
3128
+ React18.useEffect(() => {
3116
3129
  if (loading || !wantStart || asked.current.start === askKey) return;
3117
3130
  asked.current.start = askKey;
3118
3131
  onNeedMore("start", win);
3119
3132
  }, [wantStart, loading, askKey, onNeedMore, win]);
3120
- React17.useEffect(() => {
3133
+ React18.useEffect(() => {
3121
3134
  if (loading || !wantEnd || asked.current.end === askKey) return;
3122
3135
  asked.current.end = askKey;
3123
3136
  onNeedMore("end", win);
3124
3137
  }, [wantEnd, loading, askKey, onNeedMore, win]);
3125
- const lastKeysRef = React17.useRef(null);
3126
- const newKeys = React17.useMemo(() => {
3138
+ const lastKeysRef = React18.useRef(null);
3139
+ const newKeys = React18.useMemo(() => {
3127
3140
  const prev = lastKeysRef.current;
3128
3141
  if (!prev) return /* @__PURE__ */ new Set();
3129
3142
  const fresh = /* @__PURE__ */ new Set();
@@ -3133,7 +3146,7 @@ function VirtualList({
3133
3146
  }
3134
3147
  return fresh;
3135
3148
  }, [items]);
3136
- React17.useEffect(() => {
3149
+ React18.useEffect(() => {
3137
3150
  lastKeysRef.current = new Set(items.map(keyOf));
3138
3151
  }, [items]);
3139
3152
  const gapStyle = { "--fd-vlist-gap": rowGap + "px" };
@@ -3178,7 +3191,7 @@ function VirtualList({
3178
3191
  }
3179
3192
 
3180
3193
  // src/components/data/EntityRow.tsx
3181
- var React18 = __toESM(require("react"), 1);
3194
+ var React19 = __toESM(require("react"), 1);
3182
3195
  var import_jsx_runtime40 = require("react/jsx-runtime");
3183
3196
  var ENTITY_ROW_METRIC_WIDTH = 74;
3184
3197
  var METRIC_TONE_COLOR = {
@@ -3191,7 +3204,7 @@ function EntityRowMetrics({ metrics, className = "", style }) {
3191
3204
  const key = m.id || m.label || i;
3192
3205
  const className2 = ["fd-erow-metric", m.tone && m.tone !== "default" ? "is-" + m.tone : "", m.tooltip != null ? "is-described" : ""].filter(Boolean).join(" ");
3193
3206
  const tone = { color: METRIC_TONE_COLOR[m.tone || "default"] };
3194
- const content = /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(React18.Fragment, { children: [
3207
+ const content = /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(React19.Fragment, { children: [
3195
3208
  m.icon ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("i", { className: "ph ph-" + m.icon, "aria-hidden": "true" }) : null,
3196
3209
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "fd-erow-metric-value fd-tabular", children: m.value }),
3197
3210
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "fd-sr", children: " " + m.label })
@@ -3283,14 +3296,14 @@ function EntityRow({
3283
3296
  }
3284
3297
 
3285
3298
  // src/components/data/TransferList.tsx
3286
- var React20 = __toESM(require("react"), 1);
3299
+ var React21 = __toESM(require("react"), 1);
3287
3300
  var import_react_dom4 = require("react-dom");
3288
3301
 
3289
3302
  // src/components/forms/field.tsx
3290
- var React19 = __toESM(require("react"), 1);
3303
+ var React20 = __toESM(require("react"), 1);
3291
3304
  var import_jsx_runtime41 = require("react/jsx-runtime");
3292
3305
  function useFieldId(id) {
3293
- const generated = React19.useId();
3306
+ const generated = React20.useId();
3294
3307
  return id || generated;
3295
3308
  }
3296
3309
  function FieldLabel({ htmlFor, id, required = false, onClick, children }) {
@@ -3408,12 +3421,12 @@ function TransferList({
3408
3421
  listHeight = 440,
3409
3422
  className = ""
3410
3423
  }) {
3411
- const [drag, setDrag] = React20.useState(null);
3412
- const [dragOverSide, setDragOverSide] = React20.useState(null);
3413
- const dragRef = React20.useRef(null);
3414
- const armedRef = React20.useRef(null);
3415
- const sideRefs = React20.useRef({ left: null, right: null });
3416
- const hitTestSide = React20.useCallback((x, y) => {
3424
+ const [drag, setDrag] = React21.useState(null);
3425
+ const [dragOverSide, setDragOverSide] = React21.useState(null);
3426
+ const dragRef = React21.useRef(null);
3427
+ const armedRef = React21.useRef(null);
3428
+ const sideRefs = React21.useRef({ left: null, right: null });
3429
+ const hitTestSide = React21.useCallback((x, y) => {
3417
3430
  for (const side of ["left", "right"]) {
3418
3431
  const el = sideRefs.current[side];
3419
3432
  if (!el) continue;
@@ -3422,7 +3435,7 @@ function TransferList({
3422
3435
  }
3423
3436
  return null;
3424
3437
  }, []);
3425
- const findItem = React20.useCallback(
3438
+ const findItem = React21.useCallback(
3426
3439
  (side, key) => (side === "left" ? left.items : right.items).find((it) => keyOf(it) === key),
3427
3440
  [left.items, right.items, keyOf]
3428
3441
  );
@@ -3444,7 +3457,7 @@ function TransferList({
3444
3457
  height: rect.height
3445
3458
  };
3446
3459
  };
3447
- React20.useEffect(() => {
3460
+ React21.useEffect(() => {
3448
3461
  const onMovePointer = (e) => {
3449
3462
  const armed = armedRef.current;
3450
3463
  if (!armed || e.pointerId !== armed.pointerId) return;
@@ -3497,7 +3510,7 @@ function TransferList({
3497
3510
  window.removeEventListener("pointercancel", endDrag);
3498
3511
  };
3499
3512
  }, [hitTestSide, findItem, onMove]);
3500
- React20.useEffect(() => {
3513
+ React21.useEffect(() => {
3501
3514
  if (!drag || typeof document === "undefined") return;
3502
3515
  const prev = document.body.style.userSelect;
3503
3516
  document.body.style.userSelect = "none";
@@ -3605,11 +3618,11 @@ function TransferList({
3605
3618
  }
3606
3619
 
3607
3620
  // src/components/forms/Checkbox.tsx
3608
- var React21 = __toESM(require("react"), 1);
3621
+ var React22 = __toESM(require("react"), 1);
3609
3622
  var import_jsx_runtime45 = require("react/jsx-runtime");
3610
3623
  function Checkbox({ label, description, card = false, indeterminate = false, className = "", ...rest }) {
3611
- const ref = React21.useRef(null);
3612
- React21.useEffect(() => {
3624
+ const ref = React22.useRef(null);
3625
+ React22.useEffect(() => {
3613
3626
  if (ref.current) ref.current.indeterminate = indeterminate;
3614
3627
  }, [indeterminate]);
3615
3628
  return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("label", { className: ["fd-choice", card ? "fd-choice-card" : "", className].filter(Boolean).join(" "), children: [
@@ -3668,7 +3681,7 @@ function Textarea({ label, help, error, required = false, rows = 4, disabled = f
3668
3681
  }
3669
3682
 
3670
3683
  // src/components/forms/NumberInput.tsx
3671
- var React22 = __toESM(require("react"), 1);
3684
+ var React23 = __toESM(require("react"), 1);
3672
3685
  var import_jsx_runtime49 = require("react/jsx-runtime");
3673
3686
  function NumberInput({
3674
3687
  label,
@@ -3696,10 +3709,10 @@ function NumberInput({
3696
3709
  const n = Number(String(v == null ? "" : v).replace(/[^0-9.-]/g, ""));
3697
3710
  return isNaN(n) ? null : n;
3698
3711
  };
3699
- const [text, setText] = React22.useState(value == null || value === "" ? "" : String(value));
3700
- const [editing, setEditing] = React22.useState(false);
3701
- const timer = React22.useRef(null);
3702
- React22.useEffect(() => {
3712
+ const [text, setText] = React23.useState(value == null || value === "" ? "" : String(value));
3713
+ const [editing, setEditing] = React23.useState(false);
3714
+ const timer = React23.useRef(null);
3715
+ React23.useEffect(() => {
3703
3716
  if (!editing) setText(value == null || value === "" ? "" : String(value));
3704
3717
  }, [value, editing]);
3705
3718
  const clamp = (n) => Math.min(max, Math.max(min, n));
@@ -3723,7 +3736,7 @@ function NumberInput({
3723
3736
  const release = () => {
3724
3737
  if (timer.current) clearTimeout(timer.current);
3725
3738
  };
3726
- React22.useEffect(() => () => {
3739
+ React23.useEffect(() => () => {
3727
3740
  if (timer.current) clearTimeout(timer.current);
3728
3741
  }, []);
3729
3742
  const shown = editing ? text : (() => {
@@ -3810,7 +3823,7 @@ function NumberInput({
3810
3823
  }
3811
3824
 
3812
3825
  // src/components/forms/Select.tsx
3813
- var React23 = __toESM(require("react"), 1);
3826
+ var React24 = __toESM(require("react"), 1);
3814
3827
  var import_jsx_runtime50 = require("react/jsx-runtime");
3815
3828
  var norm = (o) => typeof o === "string" ? { value: o, label: o } : o;
3816
3829
  function Select({
@@ -3837,13 +3850,13 @@ function Select({
3837
3850
  const isOn = (v) => multiple ? vals.includes(v) : v === value;
3838
3851
  const hasSearch = searchable === void 0 ? opts.length > 8 : searchable;
3839
3852
  const { fieldId, labelId, ariaLabelledBy } = useTriggerLabelling(label, id);
3840
- const [open, setOpen] = React23.useState(false);
3841
- const [q, setQ] = React23.useState("");
3842
- const [active, setActive] = React23.useState(-1);
3843
- const rootRef = React23.useRef(null);
3844
- const boxRef = React23.useRef(null);
3845
- const listRef = React23.useRef(null);
3846
- const typeBuf = React23.useRef({ s: "", t: 0 });
3853
+ const [open, setOpen] = React24.useState(false);
3854
+ const [q, setQ] = React24.useState("");
3855
+ const [active, setActive] = React24.useState(-1);
3856
+ const rootRef = React24.useRef(null);
3857
+ const boxRef = React24.useRef(null);
3858
+ const listRef = React24.useRef(null);
3859
+ const typeBuf = React24.useRef({ s: "", t: 0 });
3847
3860
  const selected = multiple ? null : opts.find((o) => o.value === value);
3848
3861
  const chosen = multiple ? opts.filter((o) => vals.includes(o.value)) : [];
3849
3862
  const boxText = multiple ? chosen.length === 0 ? placeholder : chosen.length === 1 ? chosen[0].label : summary ? summary(chosen.map((o) => o.value)) : chosen.length + " selected" : selected ? selected.label : placeholder;
@@ -3868,7 +3881,7 @@ function Select({
3868
3881
  }
3869
3882
  setOpen(!open);
3870
3883
  };
3871
- React23.useEffect(() => {
3884
+ React24.useEffect(() => {
3872
3885
  if (!open || active < 0 || !listRef.current) return;
3873
3886
  const el = listRef.current.querySelector('[data-i="' + active + '"]');
3874
3887
  if (el) {
@@ -3989,7 +4002,7 @@ function Select({
3989
4002
  ),
3990
4003
  q ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { className: "fd-body-sm fd-muted", children: visible.length }) : null
3991
4004
  ] }) : void 0,
3992
- footer: multiple ? /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(React23.Fragment, { children: [
4005
+ footer: multiple ? /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(React24.Fragment, { children: [
3993
4006
  /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
3994
4007
  "button",
3995
4008
  {
@@ -4020,7 +4033,7 @@ function Select({
4020
4033
  'Nothing matches "',
4021
4034
  q,
4022
4035
  '".'
4023
- ] }) : groups.map((grp) => /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(React23.Fragment, { children: [
4036
+ ] }) : groups.map((grp) => /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(React24.Fragment, { children: [
4024
4037
  grp.g ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: "fd-pop-group", children: grp.g }) : null,
4025
4038
  grp.items.map(({ o, i }) => /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(
4026
4039
  "button",
@@ -4057,7 +4070,7 @@ function Select({
4057
4070
  }
4058
4071
 
4059
4072
  // src/components/forms/DatePicker.tsx
4060
- var React24 = __toESM(require("react"), 1);
4073
+ var React25 = __toESM(require("react"), 1);
4061
4074
  var import_jsx_runtime51 = require("react/jsx-runtime");
4062
4075
  var MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
4063
4076
  var DOW = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
@@ -4075,10 +4088,10 @@ function Calendar({ value, range = false, onPick, initialMonth, months = 1 }) {
4075
4088
  const today = /* @__PURE__ */ new Date();
4076
4089
  const sel = range ? value || {} : { start: value, end: value };
4077
4090
  const anchor = parse(sel.start) || parse(initialMonth) || today;
4078
- const [vy, setVy] = React24.useState(anchor.getFullYear());
4079
- const [vm, setVm] = React24.useState(anchor.getMonth());
4080
- const [mode2, setMode] = React24.useState("days");
4081
- const [hover, setHover] = React24.useState(null);
4091
+ const [vy, setVy] = React25.useState(anchor.getFullYear());
4092
+ const [vm, setVm] = React25.useState(anchor.getMonth());
4093
+ const [mode2, setMode] = React25.useState("days");
4094
+ const [hover, setHover] = React25.useState(null);
4082
4095
  const s = parse(sel.start), e = parse(sel.end);
4083
4096
  const hoverEnd = range && s && !e && hover ? parse(hover) : null;
4084
4097
  const inRange = (d) => {
@@ -4194,9 +4207,9 @@ function Calendar({ value, range = false, onPick, initialMonth, months = 1 }) {
4194
4207
  }
4195
4208
  function DatePicker({ label, help, error, required = false, disabled = false, range = false, value, onChange, placeholder, id, className = "", style, ...rest }) {
4196
4209
  const { fieldId, labelId, ariaLabelledBy } = useTriggerLabelling(label, id);
4197
- const [open, setOpen] = React24.useState(false);
4198
- const rootRef = React24.useRef(null);
4199
- const boxRef = React24.useRef(null);
4210
+ const [open, setOpen] = React25.useState(false);
4211
+ const rootRef = React25.useRef(null);
4212
+ const boxRef = React25.useRef(null);
4200
4213
  const display = range ? value && value.start ? fmt(value.start) + (value.end ? " \u2192 " + fmt(value.end) : " \u2192 \u2026") : "" : fmt(value);
4201
4214
  const toggle = () => {
4202
4215
  if (!disabled) setOpen(!open);
@@ -4259,7 +4272,7 @@ function DatePicker({ label, help, error, required = false, disabled = false, ra
4259
4272
  }
4260
4273
 
4261
4274
  // src/components/forms/TimePicker.tsx
4262
- var React25 = __toESM(require("react"), 1);
4275
+ var React26 = __toESM(require("react"), 1);
4263
4276
  var import_jsx_runtime52 = require("react/jsx-runtime");
4264
4277
  var pad = (n) => String(n).padStart(2, "0");
4265
4278
  function ClockFace({ value = "09:00", onChange }) {
@@ -4268,9 +4281,9 @@ function ClockFace({ value = "09:00", onChange }) {
4268
4281
  if (isNaN(m)) m = 0;
4269
4282
  const pm = h24 >= 12;
4270
4283
  const h12 = h24 % 12 === 0 ? 12 : h24 % 12;
4271
- const [mode2, setMode] = React25.useState("h");
4272
- const faceRef = React25.useRef(null);
4273
- const dragging = React25.useRef(false);
4284
+ const [mode2, setMode] = React26.useState("h");
4285
+ const faceRef = React26.useRef(null);
4286
+ const dragging = React26.useRef(false);
4274
4287
  const set = (h, mm, isPm) => onChange((isPm ? h % 12 + 12 : h % 12) + ":" + pad(mm));
4275
4288
  const R = 108, NR = 80;
4276
4289
  const nums = mode2 === "h" ? Array.from({ length: 12 }, (_, i) => i + 1) : Array.from({ length: 12 }, (_, i) => i * 5);
@@ -4343,9 +4356,9 @@ function ClockFace({ value = "09:00", onChange }) {
4343
4356
  }
4344
4357
  function TimePicker({ label, help, error, required = false, disabled = false, value = "", onChange, placeholder = "Pick a time", id, className = "", style }) {
4345
4358
  const { fieldId, labelId, ariaLabelledBy } = useTriggerLabelling(label, id);
4346
- const [open, setOpen] = React25.useState(false);
4347
- const rootRef = React25.useRef(null);
4348
- const boxRef = React25.useRef(null);
4359
+ const [open, setOpen] = React26.useState(false);
4360
+ const rootRef = React26.useRef(null);
4361
+ const boxRef = React26.useRef(null);
4349
4362
  const disp = () => {
4350
4363
  if (!value) return "";
4351
4364
  const [h, m] = value.split(":").map(Number);
@@ -4388,7 +4401,7 @@ function TimePicker({ label, help, error, required = false, disabled = false, va
4388
4401
  offset: 6,
4389
4402
  width: 262,
4390
4403
  role: "presentation",
4391
- footer: /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(React25.Fragment, { children: [
4404
+ footer: /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(React26.Fragment, { children: [
4392
4405
  /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
4393
4406
  "button",
4394
4407
  {
@@ -4416,7 +4429,7 @@ function TimePicker({ label, help, error, required = false, disabled = false, va
4416
4429
  }
4417
4430
 
4418
4431
  // src/components/forms/Slider.tsx
4419
- var React26 = __toESM(require("react"), 1);
4432
+ var React27 = __toESM(require("react"), 1);
4420
4433
  var import_jsx_runtime53 = require("react/jsx-runtime");
4421
4434
  function Slider({
4422
4435
  label,
@@ -4434,7 +4447,7 @@ function Slider({
4434
4447
  ...rest
4435
4448
  }) {
4436
4449
  const fieldId = useFieldId(id);
4437
- const [dragging, setDragging] = React26.useState(false);
4450
+ const [dragging, setDragging] = React27.useState(false);
4438
4451
  const v = value === void 0 ? min : Number(value);
4439
4452
  const pct = max === min ? 0 : (v - min) / (max - min) * 100;
4440
4453
  return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: ["fd-field", className].filter(Boolean).join(" "), children: [
@@ -4468,7 +4481,7 @@ function Slider({
4468
4481
  }
4469
4482
 
4470
4483
  // src/components/forms/RangeSlider.tsx
4471
- var React27 = __toESM(require("react"), 1);
4484
+ var React28 = __toESM(require("react"), 1);
4472
4485
  var import_jsx_runtime54 = require("react/jsx-runtime");
4473
4486
  function RangeSlider({
4474
4487
  label,
@@ -4488,9 +4501,9 @@ function RangeSlider({
4488
4501
  }) {
4489
4502
  const fmt2 = format || ((v) => String(v));
4490
4503
  const [a, b] = value || [min, max];
4491
- const [drag, setDrag] = React27.useState(null);
4492
- const [focus, setFocus] = React27.useState(null);
4493
- const railRef = React27.useRef(null);
4504
+ const [drag, setDrag] = React28.useState(null);
4505
+ const [focus, setFocus] = React28.useState(null);
4506
+ const railRef = React28.useRef(null);
4494
4507
  const pct = (v) => max === min ? 0 : (v - min) / (max - min) * 100;
4495
4508
  const clampPair = (i, v) => {
4496
4509
  v = Math.min(max, Math.max(min, Math.round(v / step) * step));
@@ -4505,7 +4518,7 @@ function RangeSlider({
4505
4518
  const r = railRef.current.getBoundingClientRect();
4506
4519
  return min + Math.min(1, Math.max(0, (e.clientX - r.left) / r.width)) * (max - min);
4507
4520
  };
4508
- React27.useEffect(() => {
4521
+ React28.useEffect(() => {
4509
4522
  if (drag === null) return;
4510
4523
  const mv = (e) => onChange && onChange(clampPair(drag, fromEvent(e)));
4511
4524
  const upH = () => setDrag(null);
@@ -4586,7 +4599,7 @@ function RangeSlider({
4586
4599
  /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("div", { className: "fd-range" + (hasLabels ? " has-labels" : ""), onPointerDown: onRailDown, children: [
4587
4600
  /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("span", { className: "fd-range-rail", ref: railRef }),
4588
4601
  /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("span", { className: "fd-range-fill", style: { left: pct(a) + "%", width: pct(b) - pct(a) + "%" } }),
4589
- marks.map((m) => /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(React27.Fragment, { children: [
4602
+ marks.map((m) => /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(React28.Fragment, { children: [
4590
4603
  /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("span", { className: "fd-range-mark" + (m.value >= a && m.value <= b ? " is-in" : ""), style: { left: pct(m.value) + "%" } }),
4591
4604
  m.label ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("span", { className: "fd-range-mark-label", style: { left: pct(m.value) + "%" }, children: m.label }) : null
4592
4605
  ] }, m.value)),
@@ -4615,7 +4628,7 @@ function RangeSlider({
4615
4628
  }
4616
4629
 
4617
4630
  // src/components/forms/Dropzone.tsx
4618
- var React28 = __toESM(require("react"), 1);
4631
+ var React29 = __toESM(require("react"), 1);
4619
4632
  var import_jsx_runtime55 = require("react/jsx-runtime");
4620
4633
  function Dropzone({
4621
4634
  onFiles,
@@ -4630,8 +4643,8 @@ function Dropzone({
4630
4643
  className = "",
4631
4644
  style
4632
4645
  }) {
4633
- const [over, setOver] = React28.useState(false);
4634
- const depth = React28.useRef(0);
4646
+ const [over, setOver] = React29.useState(false);
4647
+ const depth = React29.useRef(0);
4635
4648
  const has = (e) => {
4636
4649
  const dt = e.dataTransfer;
4637
4650
  if (!dt) return false;
@@ -4693,8 +4706,8 @@ function FilePickButton({
4693
4706
  className = "",
4694
4707
  children
4695
4708
  }) {
4696
- const ref = React28.useRef(null);
4697
- return /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(React28.Fragment, { children: [
4709
+ const ref = React29.useRef(null);
4710
+ return /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(React29.Fragment, { children: [
4698
4711
  /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
4699
4712
  "button",
4700
4713
  {
@@ -4727,10 +4740,10 @@ function FilePickButton({
4727
4740
  }
4728
4741
  function useStagedFiles(upload, opts) {
4729
4742
  const o = opts || {};
4730
- const [items, setItems] = React28.useState([]);
4731
- const controllers = React28.useRef({});
4743
+ const [items, setItems] = React29.useState([]);
4744
+ const controllers = React29.useRef({});
4732
4745
  const patch = (id, next) => setItems((list) => list.map((f) => f.id === id ? Object.assign({}, f, next) : f));
4733
- const run = React28.useCallback((att) => {
4746
+ const run = React29.useCallback((att) => {
4734
4747
  if (!upload) return;
4735
4748
  const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
4736
4749
  controllers.current[att.id] = ac;
@@ -4747,14 +4760,14 @@ function useStagedFiles(upload, opts) {
4747
4760
  delete controllers.current[att.id];
4748
4761
  });
4749
4762
  }, [upload]);
4750
- const add = React28.useCallback((files) => {
4763
+ const add = React29.useCallback((files) => {
4751
4764
  if (!upload) return [];
4752
4765
  const atts = Array.from(files).map((f) => toAttachment(f));
4753
4766
  setItems((list) => list.concat(atts));
4754
4767
  atts.forEach(run);
4755
4768
  return atts;
4756
4769
  }, [upload, run]);
4757
- const remove = React28.useCallback((att) => {
4770
+ const remove = React29.useCallback((att) => {
4758
4771
  const ac = controllers.current[att.id];
4759
4772
  if (ac) {
4760
4773
  try {
@@ -4765,10 +4778,10 @@ function useStagedFiles(upload, opts) {
4765
4778
  }
4766
4779
  setItems((list) => list.filter((f) => f.id !== att.id));
4767
4780
  }, []);
4768
- const retry = React28.useCallback((att) => {
4781
+ const retry = React29.useCallback((att) => {
4769
4782
  run(att);
4770
4783
  }, [run]);
4771
- const clear = React28.useCallback(() => {
4784
+ const clear = React29.useCallback(() => {
4772
4785
  Object.values(controllers.current).forEach((ac) => {
4773
4786
  try {
4774
4787
  ac && ac.abort();
@@ -4892,7 +4905,7 @@ function FileGrid({ files = [], onOpen, onRemove, onRetry, tiles = true, maxHeig
4892
4905
  }
4893
4906
 
4894
4907
  // src/components/forms/MarkdownEditor.tsx
4895
- var React29 = __toESM(require("react"), 1);
4908
+ var React30 = __toESM(require("react"), 1);
4896
4909
  var import_jsx_runtime57 = require("react/jsx-runtime");
4897
4910
  var isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent || "");
4898
4911
  var INLINE_RE = /(\*\*\*[^*\n]+\*\*\*|\*\*[^*\n]+\*\*|__[^_\n]+__|~~[^~\n]+~~|`[^`\n]+`|\*[^*\s][^*\n]*\*|(?<![A-Za-z0-9_])_[^_\s][^_\n]*_|\[[^\]\n]*\]\([^)\s\n]*\)|https?:\/\/\S+)/g;
@@ -5106,7 +5119,7 @@ function syncDom(root, value) {
5106
5119
  while (root.children.length > lines.length) root.removeChild(root.lastChild);
5107
5120
  }
5108
5121
  var LIST_CONT = RE_LI;
5109
- var MarkdownEditor = React29.forwardRef(function MarkdownEditor2({
5122
+ var MarkdownEditor = React30.forwardRef(function MarkdownEditor2({
5110
5123
  value = "",
5111
5124
  onChange,
5112
5125
  onSubmit,
@@ -5125,10 +5138,10 @@ var MarkdownEditor = React29.forwardRef(function MarkdownEditor2({
5125
5138
  className = "",
5126
5139
  id
5127
5140
  }, ref) {
5128
- const boxRef = React29.useRef(null);
5129
- const composing = React29.useRef(false);
5130
- const pendingCaret = React29.useRef(null);
5131
- React29.useLayoutEffect(() => {
5141
+ const boxRef = React30.useRef(null);
5142
+ const composing = React30.useRef(false);
5143
+ const pendingCaret = React30.useRef(null);
5144
+ React30.useLayoutEffect(() => {
5132
5145
  const root = boxRef.current;
5133
5146
  if (!root || composing.current) return;
5134
5147
  const active = document.activeElement === root || root.contains(document.activeElement);
@@ -5137,7 +5150,7 @@ var MarkdownEditor = React29.forwardRef(function MarkdownEditor2({
5137
5150
  pendingCaret.current = null;
5138
5151
  if (active && caret != null) placeCaret(root, caret);
5139
5152
  }, [value]);
5140
- React29.useEffect(() => {
5153
+ React30.useEffect(() => {
5141
5154
  if (autoFocus && boxRef.current) boxRef.current.focus();
5142
5155
  }, [autoFocus]);
5143
5156
  const caretNow = () => {
@@ -5204,7 +5217,7 @@ var MarkdownEditor = React29.forwardRef(function MarkdownEditor2({
5204
5217
  api.replaceRange(from, to, next, from + next.length);
5205
5218
  }
5206
5219
  };
5207
- React29.useImperativeHandle(ref, () => api);
5220
+ React30.useImperativeHandle(ref, () => api);
5208
5221
  function detect(text, caret) {
5209
5222
  if (!onTrigger) return;
5210
5223
  const upto = text.slice(0, caret);
@@ -5356,16 +5369,16 @@ var MarkdownEditor = React29.forwardRef(function MarkdownEditor2({
5356
5369
  });
5357
5370
 
5358
5371
  // src/components/platform/AccountMenu.tsx
5359
- var React31 = __toESM(require("react"), 1);
5372
+ var React32 = __toESM(require("react"), 1);
5360
5373
 
5361
5374
  // src/kits/sessionContext.tsx
5362
- var React30 = __toESM(require("react"), 1);
5363
- var SessionContext = React30.createContext(null);
5375
+ var React31 = __toESM(require("react"), 1);
5376
+ var SessionContext = React31.createContext(null);
5364
5377
  function SessionProvider({ value, children }) {
5365
- return React30.createElement(SessionContext.Provider, { value }, children);
5378
+ return React31.createElement(SessionContext.Provider, { value }, children);
5366
5379
  }
5367
5380
  function useSessionContext() {
5368
- const ctx = React30.useContext(SessionContext);
5381
+ const ctx = React31.useContext(SessionContext);
5369
5382
  if (!ctx) {
5370
5383
  throw new Error(
5371
5384
  "useSessionContext() was called outside a <SessionProvider>. Wrap your app root in <SessionProvider value={...}> with your real session, or \u2014 in the docs site or a demo \u2014 in <DemoSessionProvider>, before rendering AccountMenu, Gate, ProfilePage or AppSwitcher."
@@ -5376,20 +5389,8 @@ function useSessionContext() {
5376
5389
 
5377
5390
  // src/components/platform/AccountMenu.tsx
5378
5391
  var import_jsx_runtime58 = require("react/jsx-runtime");
5379
- var DEFAULT_LINKS = [
5380
- { id: "profile", label: "Your profile", icon: "user-circle", href: "../admin/index.html#profile" },
5381
- { id: "preferences", label: "Preferences", icon: "sliders-horizontal", href: "../admin/index.html#preferences" }
5382
- ];
5383
- var DEFAULT_GROUPS = [
5384
- {
5385
- label: "Administration",
5386
- links: [
5387
- { id: "users", label: "Users", icon: "users-three", href: "../admin/index.html#users", perm: "users.view" },
5388
- { id: "roles", label: "Roles & permissions", icon: "shield-check", href: "../admin/index.html#roles", perm: "roles.manage" },
5389
- { id: "flags", label: "Feature flags", icon: "toggle-right", href: "../admin/index.html#flags", perm: "flags.manage" }
5390
- ]
5391
- }
5392
- ];
5392
+ var DEFAULT_LINKS = [];
5393
+ var DEFAULT_GROUPS = [];
5393
5394
  function Item({ item, onPick }) {
5394
5395
  return /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(
5395
5396
  "button",
@@ -5417,10 +5418,10 @@ function AccountMenu({
5417
5418
  ...rest
5418
5419
  }) {
5419
5420
  const { user, roles, can: can2, signOut: ctxSignOut, demo } = useSessionContext();
5420
- const [open, setOpen] = React31.useState(false);
5421
- const [switching, setSwitching] = React31.useState(false);
5422
- const [signingOut, setSigningOut] = React31.useState(false);
5423
- const ref = React31.useRef(null);
5421
+ const [open, setOpen] = React32.useState(false);
5422
+ const [switching, setSwitching] = React32.useState(false);
5423
+ const [signingOut, setSigningOut] = React32.useState(false);
5424
+ const ref = React32.useRef(null);
5424
5425
  const visibleLinks = links.filter((l) => !l.perm || can2(l.perm));
5425
5426
  const visibleGroups = groups.map((g) => ({ label: g.label, links: g.links.filter((l) => !l.perm || can2(l.perm)) })).filter((g) => g.links.length);
5426
5427
  const pick = (item) => {
@@ -5523,13 +5524,13 @@ function AccountMenu({
5523
5524
  }
5524
5525
 
5525
5526
  // src/components/platform/AppSwitcher.tsx
5526
- var React32 = __toESM(require("react"), 1);
5527
+ var React33 = __toESM(require("react"), 1);
5527
5528
  var import_jsx_runtime59 = require("react/jsx-runtime");
5528
5529
  function AppSwitcher({ apps: appsProp, onNavigate, className = "" }) {
5529
5530
  const { apps: contextApps } = useSessionContext();
5530
5531
  const apps = appsProp ?? contextApps ?? [];
5531
- const [open, setOpen] = React32.useState(false);
5532
- const anchorRef = React32.useRef(null);
5532
+ const [open, setOpen] = React33.useState(false);
5533
+ const anchorRef = React33.useRef(null);
5533
5534
  if (!apps.length) return null;
5534
5535
  const items = apps.map((app) => ({
5535
5536
  id: app.key,
@@ -5543,7 +5544,7 @@ function AppSwitcher({ apps: appsProp, onNavigate, className = "" }) {
5543
5544
  window.location.href = app.url;
5544
5545
  }
5545
5546
  }));
5546
- return /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(React32.Fragment, { children: [
5547
+ return /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(React33.Fragment, { children: [
5547
5548
  /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("span", { ref: anchorRef, className: ["fd-appswitch-anchor", className].filter(Boolean).join(" "), style: { display: "inline-flex" }, children: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
5548
5549
  IconButton,
5549
5550
  {
@@ -5572,10 +5573,10 @@ function AppSwitcher({ apps: appsProp, onNavigate, className = "" }) {
5572
5573
  }
5573
5574
 
5574
5575
  // src/components/platform/AppearanceMenu.tsx
5575
- var React34 = __toESM(require("react"), 1);
5576
+ var React35 = __toESM(require("react"), 1);
5576
5577
 
5577
5578
  // src/kits/appearance.ts
5578
- var React33 = __toESM(require("react"), 1);
5579
+ var React34 = __toESM(require("react"), 1);
5579
5580
  var DEFAULT_STORAGE_KEY = "fd-appearance";
5580
5581
  var DEFAULT_PREFS = {
5581
5582
  theme: "system",
@@ -5624,6 +5625,43 @@ function savePrefs(prefs, opts) {
5624
5625
  } catch {
5625
5626
  }
5626
5627
  }
5628
+ function createLocalStorageAppearanceStore(opts) {
5629
+ const key = opts?.storageKey ?? DEFAULT_STORAGE_KEY;
5630
+ const storage = resolveStorage(opts);
5631
+ let snapshot = loadPrefs(opts);
5632
+ const listeners3 = /* @__PURE__ */ new Set();
5633
+ const notify = () => listeners3.forEach((l) => l());
5634
+ const onStorageEvent = (e) => {
5635
+ if (e.key !== null && e.key !== key) return;
5636
+ snapshot = loadPrefs(opts);
5637
+ notify();
5638
+ };
5639
+ return {
5640
+ getSnapshot: () => snapshot,
5641
+ write(prefs) {
5642
+ snapshot = prefs;
5643
+ if (storage) {
5644
+ try {
5645
+ storage.setItem(key, JSON.stringify(prefs));
5646
+ } catch {
5647
+ }
5648
+ }
5649
+ notify();
5650
+ },
5651
+ subscribe(onChange) {
5652
+ listeners3.add(onChange);
5653
+ if (listeners3.size === 1 && typeof window !== "undefined") {
5654
+ window.addEventListener("storage", onStorageEvent);
5655
+ }
5656
+ return () => {
5657
+ listeners3.delete(onChange);
5658
+ if (listeners3.size === 0 && typeof window !== "undefined") {
5659
+ window.removeEventListener("storage", onStorageEvent);
5660
+ }
5661
+ };
5662
+ }
5663
+ };
5664
+ }
5627
5665
  function applyPrefs(prefs, root = document.documentElement) {
5628
5666
  root.setAttribute("data-theme", resolveTheme(prefs.theme));
5629
5667
  root.setAttribute("data-text-scale", prefs.textScale);
@@ -5632,13 +5670,12 @@ function applyPrefs(prefs, root = document.documentElement) {
5632
5670
  if (prefs.reduceMotion) root.setAttribute("data-motion", "reduce");
5633
5671
  else root.removeAttribute("data-motion");
5634
5672
  }
5635
- function useAppearancePrefs(opts) {
5636
- const [prefs, setPrefs] = React33.useState(() => loadPrefs(opts));
5637
- React33.useEffect(() => {
5673
+ function useAppearancePrefs(store) {
5674
+ const prefs = React34.useSyncExternalStore(store.subscribe, store.getSnapshot);
5675
+ React34.useEffect(() => {
5638
5676
  applyPrefs(prefs);
5639
- savePrefs(prefs, opts);
5640
5677
  }, [prefs]);
5641
- React33.useEffect(() => {
5678
+ React34.useEffect(() => {
5642
5679
  if (prefs.theme !== "system" || typeof window === "undefined" || typeof window.matchMedia !== "function") {
5643
5680
  return;
5644
5681
  }
@@ -5647,9 +5684,12 @@ function useAppearancePrefs(opts) {
5647
5684
  mq.addEventListener("change", onChange);
5648
5685
  return () => mq.removeEventListener("change", onChange);
5649
5686
  }, [prefs]);
5650
- const update = React33.useCallback((patch) => {
5651
- setPrefs((p) => ({ ...p, ...patch }));
5652
- }, []);
5687
+ const update = React34.useCallback(
5688
+ (patch) => {
5689
+ store.write({ ...store.getSnapshot(), ...patch });
5690
+ },
5691
+ [store]
5692
+ );
5653
5693
  return [prefs, update];
5654
5694
  }
5655
5695
 
@@ -5666,12 +5706,16 @@ var TEXT_SCALE_OPTIONS = [
5666
5706
  { value: "lg", label: "L" },
5667
5707
  { value: "xl", label: "XL" }
5668
5708
  ];
5669
- function AppearanceMenu({ className = "", ...storeOptions }) {
5670
- const [prefs, update] = useAppearancePrefs(storeOptions);
5671
- const [open, setOpen] = React34.useState(false);
5672
- const anchorRef = React34.useRef(null);
5709
+ function AppearanceMenu({ className = "", store: storeProp, storageKey, storage, defaults }) {
5710
+ const store = React35.useMemo(
5711
+ () => storeProp ?? createLocalStorageAppearanceStore({ storageKey, storage, defaults }),
5712
+ [storeProp, storageKey, storage, defaults]
5713
+ );
5714
+ const [prefs, update] = useAppearancePrefs(store);
5715
+ const [open, setOpen] = React35.useState(false);
5716
+ const anchorRef = React35.useRef(null);
5673
5717
  const patch = (p) => update(p);
5674
- return /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(React34.Fragment, { children: [
5718
+ return /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(React35.Fragment, { children: [
5675
5719
  /* @__PURE__ */ (0, import_jsx_runtime60.jsx)("span", { ref: anchorRef, className: ["fd-appearance-anchor", className].filter(Boolean).join(" "), style: { display: "inline-flex" }, children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
5676
5720
  IconButton,
5677
5721
  {
@@ -5740,14 +5784,14 @@ function AppearanceMenu({ className = "", ...storeOptions }) {
5740
5784
  }
5741
5785
 
5742
5786
  // src/components/platform/ApiSpecBrowser.tsx
5743
- var React35 = __toESM(require("react"), 1);
5787
+ var React36 = __toESM(require("react"), 1);
5744
5788
  var import_jsx_runtime61 = require("react/jsx-runtime");
5745
5789
  var METHOD_TONE = { GET: "success", POST: "info", PATCH: "warning", PUT: "warning", DELETE: "danger" };
5746
5790
  function Json({ obj }) {
5747
5791
  return /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("pre", { className: "fd-json", children: JSON.stringify(obj, null, 2) });
5748
5792
  }
5749
5793
  function Endpoint({ s, onRequest }) {
5750
- const [tried, setTried] = React35.useState(null);
5794
+ const [tried, setTried] = React36.useState(null);
5751
5795
  const run = async () => {
5752
5796
  setTried("busy");
5753
5797
  const t0 = (window.performance || Date).now();
@@ -5820,10 +5864,10 @@ function ApiSpecBrowser({
5820
5864
  className = "",
5821
5865
  ...rest
5822
5866
  }) {
5823
- const [q, setQ] = React35.useState("");
5824
- const [method, setMethod] = React35.useState(null);
5825
- const [mod, setMod] = React35.useState(null);
5826
- const [listOnly, setListOnly] = React35.useState(false);
5867
+ const [q, setQ] = React36.useState("");
5868
+ const [method, setMethod] = React36.useState(null);
5869
+ const [mod, setMod] = React36.useState(null);
5870
+ const [listOnly, setListOnly] = React36.useState(false);
5827
5871
  const activeModule = modules && modules.find((m) => m.id === mod);
5828
5872
  const hits = spec.filter((s) => (!method || s.method === method) && (!listOnly || s.isList) && (!activeModule || activeModule.endpoints.indexOf(s.id) >= 0) && (!q || (s.path + " " + s.title + " " + s.purpose + " " + (s.usedBy || []).join(" ")).toLowerCase().includes(q.toLowerCase())));
5829
5873
  const effGroups = groups && groups.length ? groups : [["All endpoints", spec.map((s) => s.id)]];
@@ -5902,139 +5946,174 @@ function ApiSpecBrowser({
5902
5946
  }
5903
5947
 
5904
5948
  // src/components/platform/ProfilePage.tsx
5905
- var React36 = __toESM(require("react"), 1);
5949
+ var React37 = __toESM(require("react"), 1);
5906
5950
  var import_jsx_runtime62 = require("react/jsx-runtime");
5907
- var TIMEZONES = ["America/New_York", "America/Chicago", "America/Denver", "America/Los_Angeles", "America/Anchorage", "Pacific/Honolulu", "Europe/London", "Europe/Berlin"];
5908
- var NOTIFY = [
5909
- { key: "planShared", label: "A plan is shared with me", detail: "Someone sends you a plan or a client link." },
5910
- { key: "planChanged", label: "A plan I own changes", detail: "Budget moves, campuses added or removed." },
5911
- { key: "goalMissed", label: "The reviewer flags a missed criterion", detail: "After every solve, if a criterion is short." },
5912
- { key: "flagChanged", label: "A feature flag affecting me changes", detail: "Someone toggles a feature you can see." },
5913
- { key: "weekly", label: "Weekly digest", detail: "Monday summary of plan activity." }
5914
- ];
5915
- function ProfilePage({ user: userProp, onSave, onNavigate, sessions, showSessions = true, className = "", ...rest }) {
5951
+ function fieldInitialValue(field, user) {
5952
+ if (field.value) return field.value(user);
5953
+ const raw = user[field.key];
5954
+ return raw == null ? "" : String(raw);
5955
+ }
5956
+ function groupFields(fields) {
5957
+ const order = [];
5958
+ const byGroup = /* @__PURE__ */ new Map();
5959
+ for (const field of fields) {
5960
+ const group = field.group ?? "Identity";
5961
+ if (!byGroup.has(group)) {
5962
+ byGroup.set(group, { group, description: field.groupDescription, fields: [] });
5963
+ order.push(group);
5964
+ }
5965
+ const entry = byGroup.get(group);
5966
+ if (entry.description === void 0) entry.description = field.groupDescription;
5967
+ entry.fields.push(field);
5968
+ }
5969
+ return order.map((g) => byGroup.get(g));
5970
+ }
5971
+ function ProfilePage({
5972
+ user: userProp,
5973
+ fields = [],
5974
+ notifications = [],
5975
+ extra,
5976
+ onChangePhoto,
5977
+ onSave,
5978
+ onNavigate,
5979
+ sessions,
5980
+ onSignOutEverywhere,
5981
+ className = ""
5982
+ }) {
5916
5983
  const { user: sessionUser, roles, permissions, saveProfile: saveProfile2 } = useSessionContext();
5917
5984
  const user = userProp || sessionUser;
5918
- const [draft, setDraft] = React36.useState(() => ({
5919
- name: user.name || "",
5920
- title: user.title || "",
5921
- email: user.email || "",
5922
- phone: user.phone || "",
5923
- timezone: user.timezone || TIMEZONES[2],
5924
- bio: user.bio || "",
5925
- notify: user.notify || { planShared: true, planChanged: true, goalMissed: true, flagChanged: false, weekly: true }
5926
- }));
5927
- const [saving, setSaving] = React36.useState(false);
5928
- const [saved, setSaved] = React36.useState(false);
5929
- const set = (k, v) => {
5930
- setDraft((d) => Object.assign({}, d, { [k]: v }));
5985
+ const initialFieldValues = React37.useMemo(() => {
5986
+ const values2 = {};
5987
+ for (const field of fields) values2[field.key] = fieldInitialValue(field, user);
5988
+ return values2;
5989
+ }, [user.id, fields]);
5990
+ const initialNotify = React37.useMemo(() => {
5991
+ const values2 = {};
5992
+ for (const n of notifications) values2[n.key] = !!user.notify?.[n.key];
5993
+ return values2;
5994
+ }, [user.id, notifications]);
5995
+ const [values, setValues] = React37.useState(initialFieldValues);
5996
+ const [notify, setNotify] = React37.useState(initialNotify);
5997
+ const [saving, setSaving] = React37.useState(false);
5998
+ const [saved, setSaved] = React37.useState(false);
5999
+ const [prevUserId, setPrevUserId] = React37.useState(user.id);
6000
+ if (user.id !== prevUserId) {
6001
+ setPrevUserId(user.id);
6002
+ setValues(initialFieldValues);
6003
+ setNotify(initialNotify);
6004
+ setSaved(false);
6005
+ }
6006
+ const setField = (key, v) => {
6007
+ setValues((d) => ({ ...d, [key]: v }));
5931
6008
  setSaved(false);
5932
6009
  };
5933
- const setNotify = (k) => {
5934
- setDraft((d) => Object.assign({}, d, { notify: Object.assign({}, d.notify, { [k]: !d.notify[k] }) }));
6010
+ const toggleNotify = (key) => {
6011
+ setNotify((d) => ({ ...d, [key]: !d[key] }));
5935
6012
  setSaved(false);
5936
6013
  };
5937
- const dirty = draft.name !== (user.name || "") || draft.title !== (user.title || "") || draft.phone !== (user.phone || "") || draft.timezone !== (user.timezone || TIMEZONES[2]) || draft.bio !== (user.bio || "") || JSON.stringify(draft.notify) !== JSON.stringify(user.notify || { planShared: true, planChanged: true, goalMissed: true, flagChanged: false, weekly: true });
5938
- const canSave = !!(onSave || saveProfile2);
6014
+ const dirty = fields.some((f) => values[f.key] !== initialFieldValues[f.key]) || notifications.some((n) => notify[n.key] !== initialNotify[n.key]);
6015
+ const canSave = !!(onSave || saveProfile2) && (fields.length > 0 || notifications.length > 0);
5939
6016
  const save = async () => {
5940
6017
  if (!canSave) return;
5941
6018
  setSaving(true);
5942
- if (onSave) await onSave(draft);
5943
- else await saveProfile2(draft);
5944
- setSaving(false);
5945
- setSaved(true);
6019
+ const draft = { ...values };
6020
+ if (notifications.length > 0) draft.notify = notify;
6021
+ try {
6022
+ if (onSave) await onSave(draft);
6023
+ else await saveProfile2(draft);
6024
+ setSaved(true);
6025
+ } finally {
6026
+ setSaving(false);
6027
+ }
5946
6028
  };
5947
- const liveSessions = sessions || [
5948
- { id: "s1", device: "MacBook Pro \xB7 Chrome", where: "Denver, CO", when: "Active now", current: true },
5949
- { id: "s2", device: "iPhone 15 \xB7 Safari", where: "Denver, CO", when: "2 hours ago" },
5950
- { id: "s3", device: "Windows \xB7 Edge", where: "Chicago, IL", when: "Aug 12" }
5951
- ];
5952
- return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 20, maxWidth: 860 }, ...rest, children: [
6029
+ const discard = () => {
6030
+ setValues(initialFieldValues);
6031
+ setNotify(initialNotify);
6032
+ setSaved(false);
6033
+ };
6034
+ const displayName = fields.some((f) => f.key === "name") ? values.name || user.name : user.name;
6035
+ const fieldGroups = groupFields(fields);
6036
+ return /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 20, maxWidth: 860 }, children: [
5953
6037
  /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: "fd-stack", style: { gap: 10 }, children: [
5954
6038
  /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { className: "fd-overline fd-muted", children: "Account" }),
5955
6039
  /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("h1", { className: "fd-h1", style: { margin: 0 }, children: "Your profile" }),
5956
6040
  /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("p", { className: "fd-body fd-secondary fd-prose", style: { margin: 0 }, children: "How you appear to colleagues and clients across every flytedesk app, and what we send you." })
5957
6041
  ] }),
5958
6042
  /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Card, { elevation: "flat", children: /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: "fd-row", style: { gap: 16, flexWrap: "wrap", alignItems: "flex-start" }, children: [
5959
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Avatar, { name: draft.name || user.name, size: "lg" }),
6043
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Avatar, { name: displayName, size: "lg" }),
5960
6044
  /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: "fd-stack", style: { gap: 4, flex: 1, minWidth: 200 }, children: [
5961
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { className: "fd-h3", style: { margin: 0 }, children: draft.name || user.name }),
5962
- /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("span", { className: "fd-body-sm fd-muted", children: [
5963
- draft.title || "No title set",
5964
- " \xB7 ",
5965
- user.team || "No team"
5966
- ] }),
6045
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { className: "fd-h3", style: { margin: 0 }, children: displayName }),
5967
6046
  /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("span", { className: "fd-row", style: { gap: 6, flexWrap: "wrap", paddingTop: 4 }, children: [
5968
6047
  roles.map((r) => /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Badge, { tone: r.id === "owner" ? "success" : "neutral", children: r.name }, r.id)),
5969
6048
  user.sso ? /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Badge, { tone: "info", icon: "shield-check", children: "SSO" }) : null
5970
6049
  ] })
5971
6050
  ] }),
5972
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Button, { variant: "secondary", size: "sm", icon: "image", children: "Change photo" })
6051
+ onChangePhoto ? /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Button, { variant: "secondary", size: "sm", icon: "image", onClick: onChangePhoto, children: "Change photo" }) : null
5973
6052
  ] }) }),
5974
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6053
+ fieldGroups.map(({ group, description, fields: groupFieldDefs }) => /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
5975
6054
  Card,
5976
6055
  {
5977
- title: /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("span", { className: "fd-stack", style: { gap: 2 }, children: [
5978
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { children: "Identity" }),
5979
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { className: "fd-body-sm fd-muted", style: { fontWeight: 400 }, children: "Name and title appear on plans you share" })
5980
- ] }),
6056
+ title: description ? /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("span", { className: "fd-stack", style: { gap: 2 }, children: [
6057
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { children: group }),
6058
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { className: "fd-body-sm fd-muted", style: { fontWeight: 400 }, children: description })
6059
+ ] }) : group,
5981
6060
  elevation: "flat",
5982
6061
  children: /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: "fd-stack", style: { gap: 14 }, children: [
5983
- /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: [
5984
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Input, { label: "Full name", value: draft.name, onChange: (e) => set("name", e.target.value) }),
5985
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Input, { label: "Job title", value: draft.title, onChange: (e) => set("title", e.target.value), placeholder: "e.g. Senior media planner" })
5986
- ] }),
5987
- /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: [
5988
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6062
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: groupFieldDefs.filter((f) => f.type !== "textarea").map((field) => {
6063
+ const help = typeof field.help === "function" ? field.help(user) : field.help;
6064
+ const readOnly = field.readOnly || field.type === "readonly";
6065
+ if (field.type === "select") {
6066
+ return /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6067
+ Select,
6068
+ {
6069
+ label: field.label,
6070
+ value: values[field.key],
6071
+ onChange: readOnly ? void 0 : (e) => setField(field.key, e.target.value),
6072
+ options: field.options ?? [],
6073
+ placeholder: field.placeholder,
6074
+ help,
6075
+ disabled: readOnly
6076
+ },
6077
+ field.key
6078
+ );
6079
+ }
6080
+ return /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
5989
6081
  Input,
5990
6082
  {
5991
- label: "Work email",
5992
- value: draft.email,
5993
- readOnly: true,
5994
- disabled: true,
5995
- help: user.sso ? "Managed by your identity provider \u2014 change it there." : "Contact an administrator to change this."
5996
- }
5997
- ),
5998
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Input, { label: "Phone", value: draft.phone, onChange: (e) => set("phone", e.target.value), placeholder: "Optional" })
5999
- ] }),
6000
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6001
- Textarea,
6002
- {
6003
- label: "Short bio",
6004
- value: draft.bio,
6005
- onChange: (e) => set("bio", e.target.value),
6006
- rows: 3,
6007
- placeholder: "Optional \u2014 shown to colleagues on shared plans.",
6008
- help: "Never shown to clients."
6009
- }
6010
- )
6083
+ label: field.label,
6084
+ value: values[field.key],
6085
+ onChange: readOnly ? void 0 : (e) => setField(field.key, e.target.value),
6086
+ readOnly,
6087
+ disabled: readOnly,
6088
+ placeholder: field.placeholder,
6089
+ help
6090
+ },
6091
+ field.key
6092
+ );
6093
+ }) }),
6094
+ groupFieldDefs.filter((f) => f.type === "textarea").map((field) => {
6095
+ const help = typeof field.help === "function" ? field.help(user) : field.help;
6096
+ return /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6097
+ Textarea,
6098
+ {
6099
+ label: field.label,
6100
+ value: values[field.key],
6101
+ onChange: field.readOnly ? void 0 : (e) => setField(field.key, e.target.value),
6102
+ readOnly: field.readOnly,
6103
+ disabled: field.readOnly,
6104
+ rows: field.rows ?? 3,
6105
+ placeholder: field.placeholder,
6106
+ help
6107
+ },
6108
+ field.key
6109
+ );
6110
+ })
6011
6111
  ] })
6012
- }
6013
- ),
6014
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Card, { title: "Working preferences", elevation: "flat", children: /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: [
6015
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6016
- Select,
6017
- {
6018
- label: "Time zone",
6019
- value: draft.timezone,
6020
- onChange: (e) => set("timezone", e.target.value),
6021
- options: TIMEZONES,
6022
- help: "Flight dates and schedules render in this zone."
6023
- }
6024
- ),
6025
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Select, { label: "Start of week", options: ["Monday", "Sunday"], placeholder: "Monday" })
6026
- ] }) }),
6027
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6028
- Card,
6029
- {
6030
- title: /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("span", { className: "fd-stack", style: { gap: 2 }, children: [
6031
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { children: "Notifications" }),
6032
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { className: "fd-body-sm fd-muted", style: { fontWeight: 400 }, children: "Email only for now \u2014 in-app notifications are on the roadmap" })
6033
- ] }),
6034
- elevation: "flat",
6035
- children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("div", { className: "fd-stack", style: { gap: 14 }, children: NOTIFY.map((n) => /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Switch, { checked: !!draft.notify[n.key], onChange: () => setNotify(n.key), label: n.label, description: n.detail }, n.key)) })
6036
- }
6037
- ),
6112
+ },
6113
+ group
6114
+ )),
6115
+ extra,
6116
+ notifications.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Card, { title: "Notifications", elevation: "flat", children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("div", { className: "fd-stack", style: { gap: 14 }, children: notifications.map((n) => /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Switch, { checked: !!notify[n.key], onChange: () => toggleNotify(n.key), label: n.label, description: n.description }, n.key)) }) }) : null,
6038
6117
  /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)(
6039
6118
  Card,
6040
6119
  {
@@ -6066,13 +6145,13 @@ function ProfilePage({ user: userProp, onSave, onNavigate, sessions, showSession
6066
6145
  ]
6067
6146
  }
6068
6147
  ),
6069
- showSessions ? /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6148
+ sessions ? /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6070
6149
  Card,
6071
6150
  {
6072
6151
  title: "Signed-in devices",
6073
6152
  elevation: "flat",
6074
- action: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Button, { size: "sm", variant: "ghost", icon: "sign-out", children: "Sign out everywhere" }),
6075
- children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("div", { className: "fd-stack", style: { gap: 0 }, children: liveSessions.map((s) => /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: "fd-row", style: { gap: 12, padding: "10px 0", borderTop: "1px solid var(--border)", flexWrap: "wrap" }, children: [
6153
+ action: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Button, { size: "sm", variant: "ghost", icon: "sign-out", onClick: onSignOutEverywhere, children: "Sign out everywhere" }),
6154
+ children: /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("div", { className: "fd-stack", style: { gap: 0 }, children: sessions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("p", { className: "fd-body-sm fd-muted", style: { margin: 0 }, children: "No other signed-in devices." }) : sessions.map((s) => /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("div", { className: "fd-row", style: { gap: 12, padding: "10px 0", borderTop: "1px solid var(--border)", flexWrap: "wrap" }, children: [
6076
6155
  /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("i", { className: "ph ph-device-mobile", style: { fontSize: 16, color: "var(--text-muted)" }, "aria-hidden": "true" }),
6077
6156
  /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)("span", { className: "fd-stack", style: { gap: 1, flex: 1, minWidth: 160 }, children: [
6078
6157
  /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { className: "fd-body-sm", style: { fontWeight: 600 }, children: s.device }),
@@ -6098,29 +6177,17 @@ function ProfilePage({ user: userProp, onSave, onNavigate, sessions, showSession
6098
6177
  boxShadow: "0 -4px 16px rgba(11,13,17,.06)"
6099
6178
  }, children: [
6100
6179
  /* @__PURE__ */ (0, import_jsx_runtime62.jsx)("span", { className: "fd-body-sm", style: { fontWeight: 600, flex: 1 }, children: saved ? "Saved." : dirty ? "Unsaved changes" : "No pending changes" }),
6101
- /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(
6102
- Button,
6103
- {
6104
- size: "sm",
6105
- variant: "ghost",
6106
- disabled: !dirty || saving,
6107
- onClick: () => {
6108
- setDraft({ name: user.name || "", title: user.title || "", email: user.email || "", phone: user.phone || "", timezone: user.timezone || TIMEZONES[2], bio: user.bio || "", notify: user.notify || { planShared: true, planChanged: true, goalMissed: true, flagChanged: false, weekly: true } });
6109
- setSaved(false);
6110
- },
6111
- children: "Discard"
6112
- }
6113
- ),
6180
+ /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Button, { size: "sm", variant: "ghost", disabled: !dirty || saving, onClick: discard, children: "Discard" }),
6114
6181
  /* @__PURE__ */ (0, import_jsx_runtime62.jsx)(Button, { size: "sm", icon: "check", disabled: !dirty || !canSave, loading: saving, onClick: save, children: "Save changes" })
6115
6182
  ] })
6116
6183
  ] });
6117
6184
  }
6118
6185
 
6119
6186
  // src/components/platform/RoadmapTimeline.tsx
6120
- var React39 = __toESM(require("react"), 1);
6187
+ var React40 = __toESM(require("react"), 1);
6121
6188
 
6122
6189
  // src/kits/session.ts
6123
- var React37 = __toESM(require("react"), 1);
6190
+ var React38 = __toESM(require("react"), 1);
6124
6191
  var PERMISSION_CATALOG = [
6125
6192
  { group: "Plans", items: [
6126
6193
  { key: "plan.view", label: "View plans", detail: "Read any plan in the workspace." },
@@ -7687,8 +7754,8 @@ function roadmap(overrides) {
7687
7754
  };
7688
7755
  }
7689
7756
  function useSession() {
7690
- const [s, setS] = React37.useState(getSession);
7691
- React37.useEffect(() => subscribe(setS), []);
7757
+ const [s, setS] = React38.useState(getSession);
7758
+ React38.useEffect(() => subscribe(setS), []);
7692
7759
  return s;
7693
7760
  }
7694
7761
  var SessionKit = {
@@ -7721,7 +7788,7 @@ var SessionKit = {
7721
7788
  };
7722
7789
  function DemoSessionProvider({ children }) {
7723
7790
  const session = useSession();
7724
- const value = React37.useMemo(
7791
+ const value = React38.useMemo(
7725
7792
  () => ({
7726
7793
  user: session.user,
7727
7794
  roles: session.roles,
@@ -7737,11 +7804,11 @@ function DemoSessionProvider({ children }) {
7737
7804
  }),
7738
7805
  [session]
7739
7806
  );
7740
- return React37.createElement(SessionProvider, { value }, children);
7807
+ return React38.createElement(SessionProvider, { value }, children);
7741
7808
  }
7742
7809
 
7743
7810
  // src/kits/runtime.ts
7744
- var React38 = __toESM(require("react"), 1);
7811
+ var React39 = __toESM(require("react"), 1);
7745
7812
  var WIRED_ENDPOINTS = [
7746
7813
  // Nothing yet. Every id below would come from a real service:
7747
7814
  // "plan.get", "placements.list", …
@@ -7912,8 +7979,8 @@ var RuntimeKit = {
7912
7979
  };
7913
7980
  RuntimeKit.declare(FEATURE_NEEDS);
7914
7981
  function useRuntimeMode() {
7915
- const [m, setM] = React38.useState(RuntimeKit.getMode());
7916
- React38.useEffect(() => RuntimeKit.subscribe(setM), []);
7982
+ const [m, setM] = React39.useState(RuntimeKit.getMode());
7983
+ React39.useEffect(() => RuntimeKit.subscribe(setM), []);
7917
7984
  return m;
7918
7985
  }
7919
7986
  function useFeatureStatus(key) {
@@ -7996,12 +8063,12 @@ function RoadmapCard({ item, byKey, onOpenFlag }) {
7996
8063
  }
7997
8064
  function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
7998
8065
  const session = SessionKit.useSession();
7999
- const [project, setProject] = React39.useState("");
8000
- const [q, setQ] = React39.useState("");
8001
- const scrollRef = React39.useRef(null);
8002
- const nowRef = React39.useRef(null);
8003
- const rm = React39.useMemo(() => SessionKit.roadmap({ isComplete: RuntimeKit.isComplete }), [session]);
8004
- const byKey = React39.useMemo(() => {
8066
+ const [project, setProject] = React40.useState("");
8067
+ const [q, setQ] = React40.useState("");
8068
+ const scrollRef = React40.useRef(null);
8069
+ const nowRef = React40.useRef(null);
8070
+ const rm = React40.useMemo(() => SessionKit.roadmap({ isComplete: RuntimeKit.isComplete }), [session]);
8071
+ const byKey = React40.useMemo(() => {
8005
8072
  const m = {};
8006
8073
  rm.items.forEach((i) => {
8007
8074
  m[i.key] = i;
@@ -8010,7 +8077,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
8010
8077
  }, [rm]);
8011
8078
  const items = rm.items.filter((i) => (!project || i.project === project) && (!q || (i.label + " " + i.description + " " + (i.backend || "") + " " + i.key).toLowerCase().includes(q.toLowerCase())));
8012
8079
  const projects = [...new Set(rm.items.map((i) => i.project))];
8013
- React39.useEffect(() => {
8080
+ React40.useEffect(() => {
8014
8081
  let raf1 = 0, raf2 = 0;
8015
8082
  const place = () => {
8016
8083
  const box = scrollRef.current, mark = nowRef.current;
@@ -8084,7 +8151,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
8084
8151
  lastPhase = item.phase;
8085
8152
  const inPhase = items.filter((i) => i.phase === item.phase).length;
8086
8153
  const isBoundary = n === firstPending;
8087
- return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)(React39.Fragment, { children: [
8154
+ return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)(React40.Fragment, { children: [
8088
8155
  showPhase ? /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { className: "fd-rm-era", children: [
8089
8156
  /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("span", { className: "fd-row", style: { gap: 8, flexWrap: "wrap", alignItems: "baseline" }, children: [
8090
8157
  /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { style: { fontWeight: 700 }, children: item.phase === 0 ? "Shipped" : "Phase " + item.phase + " \u2014 " + item.phaseName }),
@@ -8128,7 +8195,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
8128
8195
  }
8129
8196
 
8130
8197
  // src/components/platform/ComingSoon.tsx
8131
- var React40 = __toESM(require("react"), 1);
8198
+ var React41 = __toESM(require("react"), 1);
8132
8199
  var import_react_dom5 = require("react-dom");
8133
8200
  var import_jsx_runtime64 = require("react/jsx-runtime");
8134
8201
  var BYPASS_STORE = "fd.soon.bypass.v1";
@@ -8146,8 +8213,8 @@ function writeBypassed(list) {
8146
8213
  }
8147
8214
  }
8148
8215
  function useBypass(key) {
8149
- const [on, setOn] = React40.useState(() => !!key && readBypassed().indexOf(key) >= 0);
8150
- React40.useEffect(() => {
8216
+ const [on, setOn] = React41.useState(() => !!key && readBypassed().indexOf(key) >= 0);
8217
+ React41.useEffect(() => {
8151
8218
  setOn(!!key && readBypassed().indexOf(key) >= 0);
8152
8219
  }, [key]);
8153
8220
  const set = (next) => {
@@ -8194,9 +8261,9 @@ function SoonCard({ label, detail, backend, eta, effort, onRoadmap, allowed, onB
8194
8261
  ] });
8195
8262
  }
8196
8263
  function useHoverCard(open) {
8197
- const anchor = React40.useRef(null);
8198
- const [pos, setPos] = React40.useState(null);
8199
- React40.useLayoutEffect(() => {
8264
+ const anchor = React41.useRef(null);
8265
+ const [pos, setPos] = React41.useState(null);
8266
+ React41.useLayoutEffect(() => {
8200
8267
  if (!open || !anchor.current) {
8201
8268
  setPos(null);
8202
8269
  return;
@@ -8323,10 +8390,10 @@ function ComingSoon({
8323
8390
  ] });
8324
8391
  }
8325
8392
  function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, onBypass, blur, tip, className, rest, children }) {
8326
- const [open, setOpen] = React40.useState(false);
8393
+ const [open, setOpen] = React41.useState(false);
8327
8394
  const [anchor, pos] = useHoverCard(open);
8328
8395
  const zIndex = useOverlayLayer(open);
8329
- const close = React40.useRef(null);
8396
+ const close = React41.useRef(null);
8330
8397
  const show = () => {
8331
8398
  if (close.current) {
8332
8399
  clearTimeout(close.current);
@@ -8342,7 +8409,7 @@ function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, o
8342
8409
  setOpen(false);
8343
8410
  }, 140);
8344
8411
  };
8345
- React40.useEffect(() => () => {
8412
+ React41.useEffect(() => () => {
8346
8413
  if (close.current) clearTimeout(close.current);
8347
8414
  }, []);
8348
8415
  return /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)(
@@ -8513,14 +8580,14 @@ function PermissionHint({ perm, children }) {
8513
8580
  }
8514
8581
 
8515
8582
  // src/components/platform/ModeSwitch.tsx
8516
- var React41 = __toESM(require("react"), 1);
8583
+ var React42 = __toESM(require("react"), 1);
8517
8584
  var import_jsx_runtime66 = require("react/jsx-runtime");
8518
8585
  function ModeSwitch({ canToggle = false, requestCount = 0, onClearLog, renderLog, onOpenSpec, summary }) {
8519
8586
  const mode2 = useRuntimeMode();
8520
- const [open, setOpen] = React41.useState(false);
8587
+ const [open, setOpen] = React42.useState(false);
8521
8588
  const zIndex = useOverlayLayer(open);
8522
- const ref = React41.useRef(null);
8523
- React41.useEffect(() => {
8589
+ const ref = React42.useRef(null);
8590
+ React42.useEffect(() => {
8524
8591
  if (!open) return;
8525
8592
  const away = (e) => {
8526
8593
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -8776,7 +8843,7 @@ function SaturationDistribution({ campuses = [], floorBand, floorScore, onSelect
8776
8843
  }
8777
8844
 
8778
8845
  // src/components/planner/MixGap.tsx
8779
- var React42 = __toESM(require("react"), 1);
8846
+ var React43 = __toESM(require("react"), 1);
8780
8847
  var import_jsx_runtime69 = require("react/jsx-runtime");
8781
8848
  var MIX_GAP_LAYOUT = {
8782
8849
  trackHeight: 30,
@@ -8791,7 +8858,7 @@ function mixGapOffset(value, max) {
8791
8858
  }
8792
8859
  function MixGap({ rows = [], loading = false, className = "" }) {
8793
8860
  const max = Math.max(1, ...rows.flatMap((r) => [r.target, r.realized]));
8794
- const [hover, setHover] = React42.useState(null);
8861
+ const [hover, setHover] = React43.useState(null);
8795
8862
  const toneOf = (gap) => gap >= -1 ? "ok" : gap >= -4 ? "warn" : "danger";
8796
8863
  const TONE = { ok: "var(--ok-solid)", warn: "var(--warn-solid)", danger: "var(--danger-solid)" };
8797
8864
  const { trackHeight, barHeight, targetWidth, labelGap } = MIX_GAP_LAYOUT;
@@ -8934,7 +9001,7 @@ function ChannelContribution({
8934
9001
  }
8935
9002
 
8936
9003
  // src/components/planner/BudgetReallocator.tsx
8937
- var React43 = __toESM(require("react"), 1);
9004
+ var React44 = __toESM(require("react"), 1);
8938
9005
  var import_jsx_runtime71 = require("react/jsx-runtime");
8939
9006
  var bandFor = (crp) => crp >= 200 ? "dominant" : crp >= 100 ? "strong" : crp >= 50 ? "adequate" : "weak";
8940
9007
  function BudgetReallocator({
@@ -8950,8 +9017,8 @@ function BudgetReallocator({
8950
9017
  onCancel,
8951
9018
  className = ""
8952
9019
  }) {
8953
- const [draft, setDraft] = React43.useState(spend);
8954
- React43.useEffect(() => setDraft(spend), [spend]);
9020
+ const [draft, setDraft] = React44.useState(spend);
9021
+ React44.useEffect(() => setDraft(spend), [spend]);
8955
9022
  const dirty = draft !== spend;
8956
9023
  const nextCrp = scoreFor ? scoreFor(draft) : crp;
8957
9024
  const nextBand = bandFor(nextCrp);
@@ -8966,10 +9033,10 @@ function BudgetReallocator({
8966
9033
  setDraft(spend);
8967
9034
  if (onCancel) onCancel();
8968
9035
  };
8969
- const trackRef = React43.useRef(null);
8970
- const chipRef = React43.useRef(null);
8971
- const [chipLeft, setChipLeft] = React43.useState(null);
8972
- React43.useLayoutEffect(() => {
9036
+ const trackRef = React44.useRef(null);
9037
+ const chipRef = React44.useRef(null);
9038
+ const [chipLeft, setChipLeft] = React44.useState(null);
9039
+ React44.useLayoutEffect(() => {
8973
9040
  const track = trackRef.current;
8974
9041
  if (!dirty || !track) {
8975
9042
  setChipLeft(null);
@@ -9156,7 +9223,7 @@ function ImportanceTag({ level, showLabel = true, size = "sm", className = "" })
9156
9223
  }
9157
9224
 
9158
9225
  // src/components/planner/PreferenceMeter.tsx
9159
- var React44 = __toESM(require("react"), 1);
9226
+ var React45 = __toESM(require("react"), 1);
9160
9227
  var import_jsx_runtime74 = require("react/jsx-runtime");
9161
9228
  function preferenceScore(criteria) {
9162
9229
  const earned = criteria.reduce((n, c) => n + (Number(c.earned) || 0), 0);
@@ -9186,7 +9253,7 @@ function PreferenceMeter({
9186
9253
  className = ""
9187
9254
  }) {
9188
9255
  const value = score == null ? preferenceScore(criteria) : score;
9189
- const segments = React44.useMemo(() => preferenceSegments(criteria), [criteria]);
9256
+ const segments = React45.useMemo(() => preferenceSegments(criteria), [criteria]);
9190
9257
  return /* @__PURE__ */ (0, import_jsx_runtime74.jsx)(
9191
9258
  ScoreMeter,
9192
9259
  {
@@ -9225,10 +9292,10 @@ function PreferenceMeter({
9225
9292
  }
9226
9293
 
9227
9294
  // src/components/chat/AgentChatPanel.tsx
9228
- var React50 = __toESM(require("react"), 1);
9295
+ var React51 = __toESM(require("react"), 1);
9229
9296
 
9230
9297
  // src/components/chat/chatEngine.ts
9231
- var React45 = __toESM(require("react"), 1);
9298
+ var React46 = __toESM(require("react"), 1);
9232
9299
  var CHAT_UNAVAILABLE = "chat_unavailable";
9233
9300
  var JOB_PENDING = ["queued", "running"];
9234
9301
  var JOB_SUCCESS = ["completed", "recovered"];
@@ -9282,22 +9349,22 @@ function useChatEngine(opts) {
9282
9349
  onClear,
9283
9350
  onFeedback
9284
9351
  } = opts || {};
9285
- const [status, setStatus] = React45.useState("idle");
9286
- const [threadId, setThreadId] = React45.useState(null);
9287
- const [messages, setMessages] = React45.useState([]);
9288
- const [queue, setQueue] = React45.useState([]);
9289
- const [fatal, setFatal] = React45.useState(null);
9290
- const [busy, setBusy] = React45.useState(false);
9291
- const [turnStartedAt, setTurnStartedAt] = React45.useState(null);
9292
- const listRef = React45.useRef([]);
9293
- const queueRef = React45.useRef([]);
9294
- const busyRef = React45.useRef(false);
9295
- const stoppedRef = React45.useRef(false);
9296
- const abortRef = React45.useRef(null);
9297
- const serverCount = React45.useRef(0);
9298
- const threadRef = React45.useRef(null);
9299
- const mounted = React45.useRef(true);
9300
- React45.useEffect(() => {
9352
+ const [status, setStatus] = React46.useState("idle");
9353
+ const [threadId, setThreadId] = React46.useState(null);
9354
+ const [messages, setMessages] = React46.useState([]);
9355
+ const [queue, setQueue] = React46.useState([]);
9356
+ const [fatal, setFatal] = React46.useState(null);
9357
+ const [busy, setBusy] = React46.useState(false);
9358
+ const [turnStartedAt, setTurnStartedAt] = React46.useState(null);
9359
+ const listRef = React46.useRef([]);
9360
+ const queueRef = React46.useRef([]);
9361
+ const busyRef = React46.useRef(false);
9362
+ const stoppedRef = React46.useRef(false);
9363
+ const abortRef = React46.useRef(null);
9364
+ const serverCount = React46.useRef(0);
9365
+ const threadRef = React46.useRef(null);
9366
+ const mounted = React46.useRef(true);
9367
+ React46.useEffect(() => {
9301
9368
  mounted.current = true;
9302
9369
  return () => {
9303
9370
  mounted.current = false;
@@ -9319,14 +9386,14 @@ function useChatEngine(opts) {
9319
9386
  }
9320
9387
  return false;
9321
9388
  };
9322
- const loadThread = React45.useCallback(async (id) => {
9389
+ const loadThread = React46.useCallback(async (id) => {
9323
9390
  const data = await apiAdapter.getThread(id);
9324
9391
  const list = [...data && data.messages || []];
9325
9392
  serverCount.current = list.length;
9326
9393
  commit(list);
9327
9394
  return list;
9328
9395
  }, [apiAdapter]);
9329
- React45.useEffect(() => {
9396
+ React46.useEffect(() => {
9330
9397
  if (!apiAdapter) {
9331
9398
  setStatus("idle");
9332
9399
  setFatal(null);
@@ -9539,7 +9606,7 @@ function useChatEngine(opts) {
9539
9606
  await dispatchTurn(turn);
9540
9607
  }
9541
9608
  }
9542
- const send = React45.useCallback((text, attachments) => {
9609
+ const send = React46.useCallback((text, attachments) => {
9543
9610
  const body = (text || "").trim();
9544
9611
  if (!body && !(attachments && attachments.length)) return;
9545
9612
  if (status === "disconnected") return;
@@ -9549,7 +9616,7 @@ function useChatEngine(opts) {
9549
9616
  stoppedRef.current = false;
9550
9617
  drain();
9551
9618
  }, [status]);
9552
- const stop = React45.useCallback(() => {
9619
+ const stop = React46.useCallback(() => {
9553
9620
  stoppedRef.current = true;
9554
9621
  const ac = abortRef.current;
9555
9622
  if (ac) {
@@ -9568,11 +9635,11 @@ function useChatEngine(opts) {
9568
9635
  store.del(STORAGE_PREFIX + threadRef.current);
9569
9636
  }
9570
9637
  }, [apiAdapter]);
9571
- const removeQueued = React45.useCallback((id) => {
9638
+ const removeQueued = React46.useCallback((id) => {
9572
9639
  queueRef.current = queueRef.current.filter((t) => t.id !== id);
9573
9640
  setQueue(queueRef.current.slice());
9574
9641
  }, []);
9575
- const retry = React45.useCallback(() => {
9642
+ const retry = React46.useCallback(() => {
9576
9643
  const list = listRef.current;
9577
9644
  let at = -1;
9578
9645
  for (let i = list.length - 1; i >= 0; i--) if (list[i].role === "user") {
@@ -9588,19 +9655,19 @@ function useChatEngine(opts) {
9588
9655
  setQueue(queueRef.current.slice());
9589
9656
  drain();
9590
9657
  }, []);
9591
- const clear = React45.useCallback(() => {
9658
+ const clear = React46.useCallback(() => {
9592
9659
  commit([]);
9593
9660
  serverCount.current = 0;
9594
9661
  queueRef.current = [];
9595
9662
  setQueue([]);
9596
9663
  onClear && onClear();
9597
9664
  }, [onClear]);
9598
- const setFeedback = React45.useCallback((id, value) => {
9665
+ const setFeedback = React46.useCallback((id, value) => {
9599
9666
  patch(id, (m) => ({ feedback: m.feedback === value ? null : value }));
9600
9667
  const msg = listRef.current.find((m) => m.id === id);
9601
9668
  onFeedback && onFeedback({ message: msg, feedback: msg ? msg.feedback : value });
9602
9669
  }, [onFeedback]);
9603
- const reload = React45.useCallback(async () => {
9670
+ const reload = React46.useCallback(async () => {
9604
9671
  if (!threadRef.current) return;
9605
9672
  setStatus("loading");
9606
9673
  try {
@@ -9638,10 +9705,10 @@ var ChatKit = {
9638
9705
  };
9639
9706
 
9640
9707
  // src/components/chat/ChatTranscript.tsx
9641
- var React47 = __toESM(require("react"), 1);
9708
+ var React48 = __toESM(require("react"), 1);
9642
9709
 
9643
9710
  // src/components/chat/ChatTurn.tsx
9644
- var React46 = __toESM(require("react"), 1);
9711
+ var React47 = __toESM(require("react"), 1);
9645
9712
  var import_jsx_runtime75 = require("react/jsx-runtime");
9646
9713
  function JsonView({ value }) {
9647
9714
  let text;
@@ -9678,7 +9745,7 @@ function PacketCard({ packet, schema, render, onApply, applied }) {
9678
9745
  ] });
9679
9746
  }
9680
9747
  function ThinkingBlock({ text, durationMs, streaming, defaultOpen = false }) {
9681
- const [open, setOpen] = React46.useState(defaultOpen);
9748
+ const [open, setOpen] = React47.useState(defaultOpen);
9682
9749
  if (!text) return null;
9683
9750
  return /* @__PURE__ */ (0, import_jsx_runtime75.jsxs)("div", { className: "fdc-think" + (open ? " is-open" : ""), children: [
9684
9751
  /* @__PURE__ */ (0, import_jsx_runtime75.jsxs)("button", { type: "button", className: "fdc-think-head", onClick: () => setOpen((v) => !v), "aria-expanded": open, children: [
@@ -9695,7 +9762,7 @@ function ThinkingBlock({ text, durationMs, streaming, defaultOpen = false }) {
9695
9762
  ] });
9696
9763
  }
9697
9764
  function Citations({ items = [], onOpen }) {
9698
- const [open, setOpen] = React46.useState(false);
9765
+ const [open, setOpen] = React47.useState(false);
9699
9766
  if (!items.length) return null;
9700
9767
  return /* @__PURE__ */ (0, import_jsx_runtime75.jsxs)("div", { className: "fdc-cites", children: [
9701
9768
  /* @__PURE__ */ (0, import_jsx_runtime75.jsxs)("button", { type: "button", className: "fdc-cites-head", onClick: () => setOpen((v) => !v), "aria-expanded": open, children: [
@@ -9721,7 +9788,7 @@ function clampText(text, max) {
9721
9788
  return (at > max * 0.6 ? cut.slice(0, at) : cut).trimEnd() + "\u2026";
9722
9789
  }
9723
9790
  function MessageBody({ message: m, ctx }) {
9724
- const [expanded, setExpanded] = React46.useState(false);
9791
+ const [expanded, setExpanded] = React47.useState(false);
9725
9792
  const isUser = m.role === "user";
9726
9793
  const raw = m.text || "";
9727
9794
  const clamped = !expanded && !m.streaming ? clampText(raw, ctx.maxVisibleChars) : null;
@@ -9788,9 +9855,9 @@ function MessageBody({ message: m, ctx }) {
9788
9855
  ] });
9789
9856
  }
9790
9857
  function useCopyRun() {
9791
- const [done, setDone] = React46.useState(false);
9792
- const t = React46.useRef(null);
9793
- React46.useEffect(() => () => {
9858
+ const [done, setDone] = React47.useState(false);
9859
+ const t = React47.useRef(null);
9860
+ React47.useEffect(() => () => {
9794
9861
  if (t.current) clearTimeout(t.current);
9795
9862
  }, []);
9796
9863
  return [done, (text) => {
@@ -9817,7 +9884,7 @@ function RunActions({ group, ctx }) {
9817
9884
  ] }),
9818
9885
  isAssistant && ctx.onRetry ? /* @__PURE__ */ (0, import_jsx_runtime75.jsx)("button", { type: "button", className: "fdc-act", onClick: ctx.onRetry, "aria-label": "Retry this turn", children: /* @__PURE__ */ (0, import_jsx_runtime75.jsx)("i", { className: "ph ph-arrow-clockwise", "aria-hidden": "true" }) }) : null,
9819
9886
  group.role === "user" && ctx.onEdit ? /* @__PURE__ */ (0, import_jsx_runtime75.jsx)("button", { type: "button", className: "fdc-act", onClick: () => ctx.onEdit(last), "aria-label": "Edit and resend", children: /* @__PURE__ */ (0, import_jsx_runtime75.jsx)("i", { className: "ph ph-pencil-simple", "aria-hidden": "true" }) }) : null,
9820
- isAssistant && ctx.onFeedback ? /* @__PURE__ */ (0, import_jsx_runtime75.jsxs)(React46.Fragment, { children: [
9887
+ isAssistant && ctx.onFeedback ? /* @__PURE__ */ (0, import_jsx_runtime75.jsxs)(React47.Fragment, { children: [
9821
9888
  /* @__PURE__ */ (0, import_jsx_runtime75.jsx)("button", { type: "button", className: "fdc-act" + (fb === "up" ? " is-on" : ""), onClick: () => ctx.onFeedback(last.id, "up"), "aria-pressed": fb === "up", "aria-label": "Good response", children: /* @__PURE__ */ (0, import_jsx_runtime75.jsx)("i", { className: "ph ph-thumbs-up", "aria-hidden": "true" }) }),
9822
9889
  /* @__PURE__ */ (0, import_jsx_runtime75.jsx)("button", { type: "button", className: "fdc-act" + (fb === "down" ? " is-on" : ""), onClick: () => ctx.onFeedback(last.id, "down"), "aria-pressed": fb === "down", "aria-label": "Bad response", children: /* @__PURE__ */ (0, import_jsx_runtime75.jsx)("i", { className: "ph ph-thumbs-down", "aria-hidden": "true" }) })
9823
9890
  ] }) : null,
@@ -9912,10 +9979,10 @@ function ChatTranscript({
9912
9979
  renderEmpty,
9913
9980
  className = ""
9914
9981
  }) {
9915
- const scroller = React47.useRef(null);
9916
- const stick = React47.useRef(true);
9917
- const [pill, setPill] = React47.useState(0);
9918
- const seen = React47.useRef(0);
9982
+ const scroller = React48.useRef(null);
9983
+ const stick = React48.useRef(true);
9984
+ const [pill, setPill] = React48.useState(0);
9985
+ const seen = React48.useRef(0);
9919
9986
  const toBottom = (smooth) => {
9920
9987
  const el = scroller.current;
9921
9988
  if (!el) return;
@@ -9934,7 +10001,7 @@ function ChatTranscript({
9934
10001
  seen.current = messages.length;
9935
10002
  }
9936
10003
  };
9937
- React47.useLayoutEffect(() => {
10004
+ React48.useLayoutEffect(() => {
9938
10005
  const el = scroller.current;
9939
10006
  if (!el) return;
9940
10007
  if (stick.current) {
@@ -9942,7 +10009,7 @@ function ChatTranscript({
9942
10009
  seen.current = messages.length;
9943
10010
  } else setPill(Math.max(0, messages.length - seen.current));
9944
10011
  }, [messages]);
9945
- React47.useEffect(() => {
10012
+ React48.useEffect(() => {
9946
10013
  const el = scroller.current;
9947
10014
  const inner = el && el.firstChild;
9948
10015
  if (!el || !inner || typeof ResizeObserver === "undefined") return;
@@ -9952,7 +10019,7 @@ function ChatTranscript({
9952
10019
  ro.observe(inner);
9953
10020
  return () => ro.disconnect();
9954
10021
  }, []);
9955
- const groups = React47.useMemo(() => groupMessages(messages), [messages]);
10022
+ const groups = React48.useMemo(() => groupMessages(messages), [messages]);
9956
10023
  const empty = !messages.length && status === "ready";
9957
10024
  return /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: ["fdc-scroll", className].filter(Boolean).join(" "), ref: scroller, onScroll, children: [
9958
10025
  /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "fdc-log", role: "log", "aria-label": "Conversation", children: [
@@ -9976,7 +10043,7 @@ function ChatTranscript({
9976
10043
  const prev = groups[i - 1];
9977
10044
  const k = dayKey(g.messages[0].timestamp);
9978
10045
  const showDay = !!k && (!prev || dayKey(prev.messages[0].timestamp) !== k);
9979
- return /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)(React47.Fragment, { children: [
10046
+ return /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)(React48.Fragment, { children: [
9980
10047
  showDay ? /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("div", { className: "fdc-day", children: /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("span", { children: dayLabel(k) }) }) : null,
9981
10048
  /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(ChatTurn, { group: g, ctx })
9982
10049
  ] }, g.key || i);
@@ -9993,7 +10060,7 @@ function ChatTranscript({
9993
10060
  var TranscriptKit = { groupMessages };
9994
10061
 
9995
10062
  // src/components/chat/ChatComposer.tsx
9996
- var React48 = __toESM(require("react"), 1);
10063
+ var React49 = __toESM(require("react"), 1);
9997
10064
  var import_jsx_runtime77 = require("react/jsx-runtime");
9998
10065
  function ChatComposer({
9999
10066
  onSubmit,
@@ -10021,17 +10088,17 @@ function ChatComposer({
10021
10088
  onReject,
10022
10089
  onOpenAttachment
10023
10090
  }) {
10024
- const [text, setText] = React48.useState(draft || "");
10025
- const [trigger, setTrigger] = React48.useState(null);
10026
- const [mentionItems, setMentionItems] = React48.useState([]);
10027
- const [listening, setListening] = React48.useState(false);
10028
- const [notice, setNotice] = React48.useState(null);
10029
- const editor = React48.useRef(null);
10030
- const wrap = React48.useRef(null);
10031
- const stopVoice = React48.useRef(null);
10091
+ const [text, setText] = React49.useState(draft || "");
10092
+ const [trigger, setTrigger] = React49.useState(null);
10093
+ const [mentionItems, setMentionItems] = React49.useState([]);
10094
+ const [listening, setListening] = React49.useState(false);
10095
+ const [notice, setNotice] = React49.useState(null);
10096
+ const editor = React49.useRef(null);
10097
+ const wrap = React49.useRef(null);
10098
+ const stopVoice = React49.useRef(null);
10032
10099
  const staged = useStagedFiles(fileUploadHandler, { onError: () => {
10033
10100
  } });
10034
- React48.useEffect(() => {
10101
+ React49.useEffect(() => {
10035
10102
  if (draft != null && draft !== text) setText(draft);
10036
10103
  }, [draft]);
10037
10104
  const change = (v) => {
@@ -10067,7 +10134,7 @@ function ChatComposer({
10067
10134
  e.preventDefault();
10068
10135
  staged.add(found.files);
10069
10136
  };
10070
- React48.useEffect(() => {
10137
+ React49.useEffect(() => {
10071
10138
  if (!trigger || trigger.type !== "mention" || !mentionSources) {
10072
10139
  setMentionItems([]);
10073
10140
  return;
@@ -10085,7 +10152,7 @@ function ChatComposer({
10085
10152
  const q = (trigger.query || "").toLowerCase();
10086
10153
  setMentionItems(mentionSources.filter((m) => !q || (m.label + " " + (m.description || "")).toLowerCase().includes(q)));
10087
10154
  }, [trigger, mentionSources]);
10088
- const slashItems = React48.useMemo(() => {
10155
+ const slashItems = React49.useMemo(() => {
10089
10156
  if (!trigger || trigger.type !== "slash" || !slashCommands) return [];
10090
10157
  const q = (trigger.query || "").toLowerCase();
10091
10158
  return slashCommands.filter((c) => !q || (c.id + " " + c.label + " " + (c.description || "")).toLowerCase().includes(q));
@@ -10259,12 +10326,12 @@ function ChatComposer({
10259
10326
  }
10260
10327
 
10261
10328
  // src/components/chat/ChatSessionBar.tsx
10262
- var React49 = __toESM(require("react"), 1);
10329
+ var React50 = __toESM(require("react"), 1);
10263
10330
  var import_jsx_runtime78 = require("react/jsx-runtime");
10264
10331
  var compact2 = meterFormats.compact;
10265
10332
  function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extras }) {
10266
- const [open, setOpen] = React49.useState(false);
10267
- const anchor = React49.useRef(null);
10333
+ const [open, setOpen] = React50.useState(false);
10334
+ const anchor = React50.useRef(null);
10268
10335
  const stats = sessionStats || null;
10269
10336
  const cu = contextUsage || null;
10270
10337
  const limits = usageLimits || null;
@@ -10279,7 +10346,7 @@ function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extr
10279
10346
  if (stats && stats.runningTasks) bits.push(stats.runningTasks + " running task" + (stats.runningTasks === 1 ? "" : "s"));
10280
10347
  if (cu) bits.push(pct + "% context");
10281
10348
  const expandable = !!(cu || limits);
10282
- return /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)(React49.Fragment, { children: [
10349
+ return /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)(React50.Fragment, { children: [
10283
10350
  /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("div", { className: "fdc-bar", children: [
10284
10351
  /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)(
10285
10352
  "button",
@@ -10420,7 +10487,7 @@ function ModelControls({
10420
10487
  ] })
10421
10488
  });
10422
10489
  }
10423
- return /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)(React49.Fragment, { children: [
10490
+ return /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)(React50.Fragment, { children: [
10424
10491
  /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(
10425
10492
  MenuButton,
10426
10493
  {
@@ -10537,11 +10604,11 @@ function AgentChatPanel({
10537
10604
  onFeedback,
10538
10605
  onEditMessage
10539
10606
  }) {
10540
- const [panelWidth, setPanelWidth] = React50.useState(width);
10541
- const [applied, setApplied] = React50.useState({});
10542
- const [draft, setDraft] = React50.useState("");
10543
- const dragging = React50.useRef(null);
10544
- React50.useEffect(() => setPanelWidth(width), [width]);
10607
+ const [panelWidth, setPanelWidth] = React51.useState(width);
10608
+ const [applied, setApplied] = React51.useState({});
10609
+ const [draft, setDraft] = React51.useState("");
10610
+ const dragging = React51.useRef(null);
10611
+ React51.useEffect(() => setPanelWidth(width), [width]);
10545
10612
  const engine = useChatEngine({
10546
10613
  contextType,
10547
10614
  contextId,
@@ -10745,7 +10812,7 @@ function AgentChatPanel({
10745
10812
  }
10746
10813
 
10747
10814
  // src/components/files/FileViewer.tsx
10748
- var React51 = __toESM(require("react"), 1);
10815
+ var React52 = __toESM(require("react"), 1);
10749
10816
  var import_jsx_runtime80 = require("react/jsx-runtime");
10750
10817
  function previewKindOf(file) {
10751
10818
  const mime = (file.mime || "").toLowerCase();
@@ -10802,13 +10869,13 @@ function parseDelimited(text, delimiter) {
10802
10869
  return rows;
10803
10870
  }
10804
10871
  function usePreview(file, kind, resolveUrl) {
10805
- const [state, setState] = React51.useState({ status: "loading" });
10806
- const [attempt, setAttempt] = React51.useState(0);
10807
- const resolveRef = React51.useRef(resolveUrl);
10808
- React51.useEffect(() => {
10872
+ const [state, setState] = React52.useState({ status: "loading" });
10873
+ const [attempt, setAttempt] = React52.useState(0);
10874
+ const resolveRef = React52.useRef(resolveUrl);
10875
+ React52.useEffect(() => {
10809
10876
  resolveRef.current = resolveUrl;
10810
10877
  }, [resolveUrl]);
10811
- React51.useEffect(() => {
10878
+ React52.useEffect(() => {
10812
10879
  if (kind === "none") {
10813
10880
  setState({ status: "ready" });
10814
10881
  return;
@@ -10838,7 +10905,7 @@ function usePreview(file, kind, resolveUrl) {
10838
10905
  if (ac) ac.abort();
10839
10906
  };
10840
10907
  }, [file.id, kind, attempt]);
10841
- return { ...state, retry: React51.useCallback(() => setAttempt((a) => a + 1), []) };
10908
+ return { ...state, retry: React52.useCallback(() => setAttempt((a) => a + 1), []) };
10842
10909
  }
10843
10910
  function PreviewBody({ file, kind, state }) {
10844
10911
  if (kind === "image") return /* @__PURE__ */ (0, import_jsx_runtime80.jsx)("img", { className: "fd-fv-image", src: state.url, alt: file.name || "Preview" });
@@ -10883,13 +10950,13 @@ function PreviewBody({ file, kind, state }) {
10883
10950
  ] });
10884
10951
  }
10885
10952
  function FileViewer({ file, resolveUrl, sidePanel, sidePanelLabel = "Details", onClose, actions, className = "" }) {
10886
- const kind = React51.useMemo(() => previewKindOf(file), [file]);
10953
+ const kind = React52.useMemo(() => previewKindOf(file), [file]);
10887
10954
  const preview = usePreview(file, kind, resolveUrl);
10888
- const [tab, setTab] = React51.useState("preview");
10889
- const [downloading, setDownloading] = React51.useState(false);
10890
- const [downloadError, setDownloadError] = React51.useState(null);
10955
+ const [tab, setTab] = React52.useState("preview");
10956
+ const [downloading, setDownloading] = React52.useState(false);
10957
+ const [downloadError, setDownloadError] = React52.useState(null);
10891
10958
  const hasSide = sidePanel != null;
10892
- React51.useEffect(() => {
10959
+ React52.useEffect(() => {
10893
10960
  setTab("preview");
10894
10961
  }, [file.id]);
10895
10962
  const download = () => {
@@ -10956,7 +11023,7 @@ function FileViewer({ file, resolveUrl, sidePanel, sidePanelLabel = "Details", o
10956
11023
  }
10957
11024
 
10958
11025
  // src/components/files/FileBrowser.tsx
10959
- var React52 = __toESM(require("react"), 1);
11026
+ var React53 = __toESM(require("react"), 1);
10960
11027
  var import_jsx_runtime81 = require("react/jsx-runtime");
10961
11028
  function childrenOf(folders, parentId) {
10962
11029
  return folders.filter((f) => f.parentId === parentId).sort((a, b) => a.name.localeCompare(b.name));
@@ -11027,10 +11094,10 @@ function FolderPickerMenu({
11027
11094
  destinations,
11028
11095
  onPick
11029
11096
  }) {
11030
- const [open, setOpen] = React52.useState(false);
11097
+ const [open, setOpen] = React53.useState(false);
11031
11098
  const zIndex = useOverlayLayer(open);
11032
- const ref = React52.useRef(null);
11033
- React52.useEffect(() => {
11099
+ const ref = React53.useRef(null);
11100
+ React53.useEffect(() => {
11034
11101
  if (!open) return;
11035
11102
  const away = (e) => {
11036
11103
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -11082,8 +11149,8 @@ function FolderPickerMenu({
11082
11149
  ] });
11083
11150
  }
11084
11151
  function RowShell({ icon, name, meta, onOpen, draggableId, isDropTarget, onDropPayload, actions, className = "" }) {
11085
- const [over, setOver] = React52.useState(false);
11086
- const dragCounter = React52.useRef(0);
11152
+ const [over, setOver] = React53.useState(false);
11153
+ const dragCounter = React53.useRef(0);
11087
11154
  const dropHandlers = isDropTarget ? {
11088
11155
  onDragEnter: (e) => {
11089
11156
  if (!hasInternalPayload(e)) return;
@@ -11131,8 +11198,8 @@ function RowShell({ icon, name, meta, onOpen, draggableId, isDropTarget, onDropP
11131
11198
  }
11132
11199
  function TreeNode({ folder, depth, folders, currentFolderId, onNavigate, onMoveInto, canMove }) {
11133
11200
  const kids = childrenOf(folders, folder.id);
11134
- const [expanded, setExpanded] = React52.useState(() => currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId));
11135
- React52.useEffect(() => {
11201
+ const [expanded, setExpanded] = React53.useState(() => currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId));
11202
+ React53.useEffect(() => {
11136
11203
  if (currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId)) setExpanded(true);
11137
11204
  }, [currentFolderId, folder.id, folders]);
11138
11205
  const isCurrent = folder.id === currentFolderId;
@@ -11198,15 +11265,15 @@ function FileBrowser({
11198
11265
  const canUpload = perm.canUpload !== false && !!onUpload;
11199
11266
  const canRemoveFile = perm.canRemoveFile !== false && !!onRemoveFile;
11200
11267
  const canDeleteFolder = perm.canDeleteFolder !== false && !!onDeleteFolder;
11201
- const [createOpen, setCreateOpen] = React52.useState(false);
11202
- const [createName, setCreateName] = React52.useState("");
11203
- const [renameTarget, setRenameTarget] = React52.useState(null);
11204
- const [renameValue, setRenameValue] = React52.useState("");
11205
- const [deleteTarget, setDeleteTarget] = React52.useState(null);
11206
- const uploadInputRef = React52.useRef(null);
11207
- const crumbFolders = React52.useMemo(() => ancestorsOf(folders, currentFolderId), [folders, currentFolderId]);
11208
- const subfolders = React52.useMemo(() => childrenOf(folders, currentFolderId), [folders, currentFolderId]);
11209
- const currentFiles = React52.useMemo(
11268
+ const [createOpen, setCreateOpen] = React53.useState(false);
11269
+ const [createName, setCreateName] = React53.useState("");
11270
+ const [renameTarget, setRenameTarget] = React53.useState(null);
11271
+ const [renameValue, setRenameValue] = React53.useState("");
11272
+ const [deleteTarget, setDeleteTarget] = React53.useState(null);
11273
+ const uploadInputRef = React53.useRef(null);
11274
+ const crumbFolders = React53.useMemo(() => ancestorsOf(folders, currentFolderId), [folders, currentFolderId]);
11275
+ const subfolders = React53.useMemo(() => childrenOf(folders, currentFolderId), [folders, currentFolderId]);
11276
+ const currentFiles = React53.useMemo(
11210
11277
  () => files.filter((f) => f.folderId === currentFolderId).sort((a, b) => a.name.localeCompare(b.name)),
11211
11278
  [files, currentFolderId]
11212
11279
  );
@@ -11438,8 +11505,8 @@ function BreadcrumbCrumb({
11438
11505
  isDropTarget,
11439
11506
  onDropPayload
11440
11507
  }) {
11441
- const [over, setOver] = React52.useState(false);
11442
- const dragCounter = React52.useRef(0);
11508
+ const [over, setOver] = React53.useState(false);
11509
+ const dragCounter = React53.useRef(0);
11443
11510
  const dropHandlers = isDropTarget ? {
11444
11511
  onDragEnter: (e) => {
11445
11512
  if (!hasInternalPayload(e)) return;
@@ -11468,7 +11535,7 @@ function BreadcrumbCrumb({
11468
11535
  }
11469
11536
 
11470
11537
  // src/kits/query.ts
11471
- var React53 = __toESM(require("react"), 1);
11538
+ var React54 = __toESM(require("react"), 1);
11472
11539
  function eqFilter(get2) {
11473
11540
  return (row, value) => Array.isArray(value) ? value.includes(get2(row)) : get2(row) === value;
11474
11541
  }
@@ -11500,22 +11567,22 @@ function compare(a, b, dir) {
11500
11567
  var API = null;
11501
11568
  var PREFS = null;
11502
11569
  function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
11503
- const [query, setQuery] = React53.useState(() => {
11570
+ const [query, setQuery] = React54.useState(() => {
11504
11571
  const store = PREFS || window.PlannerPrefs;
11505
11572
  const saved = prefsKey && store ? store.getTable(prefsKey) : {};
11506
11573
  return { ...DEFAULTS, ...defaults || {}, ...saved.pageSize ? { pageSize: saved.pageSize } : {}, ...saved.sort ? { sort: saved.sort, dir: saved.dir || "desc" } : {} };
11507
11574
  });
11508
- const savePref = React53.useCallback((patch2) => {
11575
+ const savePref = React54.useCallback((patch2) => {
11509
11576
  const store = PREFS || window.PlannerPrefs;
11510
11577
  if (prefsKey && store) store.setTable(prefsKey, patch2);
11511
11578
  }, [prefsKey]);
11512
- const [res, setRes] = React53.useState(null);
11513
- const [loading, setLoading] = React53.useState(true);
11514
- const seq2 = React53.useRef(0);
11579
+ const [res, setRes] = React54.useState(null);
11580
+ const [loading, setLoading] = React54.useState(true);
11581
+ const seq2 = React54.useRef(0);
11515
11582
  const depKey = (deps || []).join("|");
11516
11583
  const paramKey = JSON.stringify(params || {});
11517
11584
  const queryKey = JSON.stringify(query);
11518
- React53.useEffect(() => {
11585
+ React54.useEffect(() => {
11519
11586
  const id = ++seq2.current;
11520
11587
  setLoading(true);
11521
11588
  const t = setTimeout(() => {
@@ -11812,6 +11879,92 @@ function createVersionStore(initialState, options) {
11812
11879
  }
11813
11880
  };
11814
11881
  }
11882
+
11883
+ // src/kits/useResponsiveRail.ts
11884
+ var React55 = __toESM(require("react"), 1);
11885
+ function matchesBreakpoint(breakpoint) {
11886
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(`(max-width: ${breakpoint}px)`).matches;
11887
+ }
11888
+ var FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
11889
+ function getFocusable(container) {
11890
+ return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR));
11891
+ }
11892
+ function useResponsiveRail(options = {}) {
11893
+ const { breakpoint = 1024, initialOpen = true, onPersist } = options;
11894
+ const navRef = React55.useRef(null);
11895
+ const [overlay, setOverlay] = React55.useState(() => matchesBreakpoint(breakpoint));
11896
+ const [open, setOpenRaw] = React55.useState(() => matchesBreakpoint(breakpoint) ? false : initialOpen);
11897
+ const setOpen = React55.useCallback(
11898
+ (value) => {
11899
+ setOpenRaw(value);
11900
+ if (!matchesBreakpoint(breakpoint)) onPersist?.(value);
11901
+ },
11902
+ [breakpoint, onPersist]
11903
+ );
11904
+ const toggle = React55.useCallback(() => setOpen(!open), [open, setOpen]);
11905
+ const close = React55.useCallback(() => setOpenRaw(false), []);
11906
+ React55.useEffect(() => {
11907
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
11908
+ const mq = window.matchMedia(`(max-width: ${breakpoint}px)`);
11909
+ const handler = (e) => {
11910
+ setOverlay(e.matches);
11911
+ setOpenRaw(!e.matches);
11912
+ };
11913
+ mq.addEventListener("change", handler);
11914
+ return () => mq.removeEventListener("change", handler);
11915
+ }, [breakpoint]);
11916
+ React55.useEffect(() => {
11917
+ if (!overlay || !open) return;
11918
+ const handler = (e) => {
11919
+ if (e.key === "Escape") close();
11920
+ };
11921
+ document.addEventListener("keydown", handler);
11922
+ return () => document.removeEventListener("keydown", handler);
11923
+ }, [overlay, open, close]);
11924
+ React55.useEffect(() => {
11925
+ if (!overlay || !open) return;
11926
+ const previousOverflow = document.body.style.overflow;
11927
+ document.body.style.overflow = "hidden";
11928
+ return () => {
11929
+ document.body.style.overflow = previousOverflow;
11930
+ };
11931
+ }, [overlay, open]);
11932
+ React55.useEffect(() => {
11933
+ if (!overlay || !open) return;
11934
+ const container = navRef.current;
11935
+ if (!container) return;
11936
+ const previouslyFocused = document.activeElement;
11937
+ const focusable = getFocusable(container);
11938
+ (focusable[0] ?? container).focus();
11939
+ const handler = (e) => {
11940
+ if (e.key !== "Tab") return;
11941
+ const items = getFocusable(container);
11942
+ if (items.length === 0) {
11943
+ e.preventDefault();
11944
+ return;
11945
+ }
11946
+ const first = items[0];
11947
+ const last = items[items.length - 1];
11948
+ const active = document.activeElement;
11949
+ const outside = !(active instanceof Node) || !container.contains(active);
11950
+ if (e.shiftKey) {
11951
+ if (outside || active === first) {
11952
+ e.preventDefault();
11953
+ last.focus();
11954
+ }
11955
+ } else if (outside || active === last) {
11956
+ e.preventDefault();
11957
+ first.focus();
11958
+ }
11959
+ };
11960
+ document.addEventListener("keydown", handler);
11961
+ return () => {
11962
+ document.removeEventListener("keydown", handler);
11963
+ previouslyFocused?.focus?.();
11964
+ };
11965
+ }, [overlay, open]);
11966
+ return { open, overlay, navRef, toggle, close, setOpen };
11967
+ }
11815
11968
  // Annotate the CommonJS export names for ESM import in node:
11816
11969
  0 && (module.exports = {
11817
11970
  AccountMenu,
@@ -11956,6 +12109,7 @@ function createVersionStore(initialState, options) {
11956
12109
  channelWeightOf,
11957
12110
  computeNeedMore,
11958
12111
  computeVirtualWindow,
12112
+ createLocalStorageAppearanceStore,
11959
12113
  createVersionStore,
11960
12114
  eqFilter,
11961
12115
  extensionOf,
@@ -11995,6 +12149,7 @@ function createVersionStore(initialState, options) {
11995
12149
  useChatEngine,
11996
12150
  useFeatureStatus,
11997
12151
  usePopoverPosition,
12152
+ useResponsiveRail,
11998
12153
  useRuntimeMode,
11999
12154
  useServerTable,
12000
12155
  useSessionContext,