@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.js CHANGED
@@ -885,37 +885,48 @@ function Spinner({ size = "md", label = "Loading", className = "", style, ...res
885
885
  }
886
886
 
887
887
  // src/components/navigation/SidebarNav.tsx
888
+ import * as React6 from "react";
888
889
  import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
889
890
  function Tip({ label, children }) {
890
891
  return /* @__PURE__ */ jsx13("span", { title: label, style: { display: "inline-flex", flex: "none" }, children });
891
892
  }
892
- function SidebarNav({ brand, groups = [], current: current2, onNavigate, footer, open = false, className = "", ...rest }) {
893
- return /* @__PURE__ */ jsxs10("nav", { className: ["fd-side", className].filter(Boolean).join(" "), "data-open": open ? "true" : "false", ...rest, children: [
894
- brand ? /* @__PURE__ */ jsx13("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,
895
- /* @__PURE__ */ jsx13("div", { style: { flex: 1, overflow: "auto", padding: "4px 10px 18px" }, children: groups.map((g, gi) => /* @__PURE__ */ jsxs10("div", { children: [
896
- g.label ? /* @__PURE__ */ jsx13("div", { className: "fd-side-group", children: g.label }) : null,
897
- /* @__PURE__ */ jsx13("div", { className: "fd-stack", style: { gap: 2 }, children: g.items.map((it) => /* @__PURE__ */ jsxs10(
898
- "button",
899
- {
900
- type: "button",
901
- className: "fd-side-item",
902
- "aria-current": current2 === it.value ? "page" : void 0,
903
- onClick: onNavigate ? () => onNavigate(it.value) : void 0,
904
- children: [
905
- /* @__PURE__ */ jsx13("span", { className: "fd-side-item-icon", children: /* @__PURE__ */ jsx13("i", { className: "ph ph-" + it.icon, "aria-hidden": "true" }) }),
906
- /* @__PURE__ */ jsx13("span", { style: { flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, title: truncationTitle(it.label), children: it.label }),
907
- it.preview ? /* @__PURE__ */ jsx13(Tip, { label: "Designed, backend not wired yet", children: /* @__PURE__ */ jsx13("i", { className: "ph ph-traffic-cone", style: { fontSize: 13, color: "var(--warn-solid)", flex: "none" } }) }) : null,
908
- it.locked ? /* @__PURE__ */ jsx13(Tip, { label: "You do not have permission for this", children: /* @__PURE__ */ jsx13("i", { className: "ph ph-lock-simple", style: { fontSize: 13, color: "var(--text-muted)", flex: "none" } }) }) : null,
909
- it.count !== void 0 ? /* @__PURE__ */ jsx13("span", { className: "fd-side-item-count", children: it.count }) : null,
910
- it.loading ? /* @__PURE__ */ jsx13("span", { className: "fd-skel fd-skel-text", style: { width: 22, height: 11, marginLeft: "auto" } }) : null
911
- ]
912
- },
913
- it.value
914
- )) })
915
- ] }, gi)) }),
916
- footer ? /* @__PURE__ */ jsx13("div", { style: { borderTop: "1px solid var(--border)", padding: 14, flex: "none" }, children: footer }) : null
917
- ] });
918
- }
893
+ var SidebarNav = React6.forwardRef(function SidebarNav2({ brand, groups = [], current: current2, onNavigate, footer, open = false, className = "", ...rest }, ref) {
894
+ return /* @__PURE__ */ jsxs10(
895
+ "nav",
896
+ {
897
+ ref,
898
+ tabIndex: -1,
899
+ className: ["fd-side", className].filter(Boolean).join(" "),
900
+ "data-open": open ? "true" : "false",
901
+ ...rest,
902
+ children: [
903
+ brand ? /* @__PURE__ */ jsx13("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,
904
+ /* @__PURE__ */ jsx13("div", { style: { flex: 1, overflow: "auto", padding: "4px 10px 18px" }, children: groups.map((g, gi) => /* @__PURE__ */ jsxs10("div", { children: [
905
+ g.label ? /* @__PURE__ */ jsx13("div", { className: "fd-side-group", children: g.label }) : null,
906
+ /* @__PURE__ */ jsx13("div", { className: "fd-stack", style: { gap: 2 }, children: g.items.map((it) => /* @__PURE__ */ jsxs10(
907
+ "button",
908
+ {
909
+ type: "button",
910
+ className: "fd-side-item",
911
+ "aria-current": current2 === it.value ? "page" : void 0,
912
+ onClick: onNavigate ? () => onNavigate(it.value) : void 0,
913
+ children: [
914
+ /* @__PURE__ */ jsx13("span", { className: "fd-side-item-icon", children: /* @__PURE__ */ jsx13("i", { className: "ph ph-" + it.icon, "aria-hidden": "true" }) }),
915
+ /* @__PURE__ */ jsx13("span", { style: { flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, title: truncationTitle(it.label), children: it.label }),
916
+ it.preview ? /* @__PURE__ */ jsx13(Tip, { label: "Designed, backend not wired yet", children: /* @__PURE__ */ jsx13("i", { className: "ph ph-traffic-cone", style: { fontSize: 13, color: "var(--warn-solid)", flex: "none" } }) }) : null,
917
+ it.locked ? /* @__PURE__ */ jsx13(Tip, { label: "You do not have permission for this", children: /* @__PURE__ */ jsx13("i", { className: "ph ph-lock-simple", style: { fontSize: 13, color: "var(--text-muted)", flex: "none" } }) }) : null,
918
+ it.count !== void 0 ? /* @__PURE__ */ jsx13("span", { className: "fd-side-item-count", children: it.count }) : null,
919
+ it.loading ? /* @__PURE__ */ jsx13("span", { className: "fd-skel fd-skel-text", style: { width: 22, height: 11, marginLeft: "auto" } }) : null
920
+ ]
921
+ },
922
+ it.value
923
+ )) })
924
+ ] }, gi)) }),
925
+ footer ? /* @__PURE__ */ jsx13("div", { style: { borderTop: "1px solid var(--border)", padding: 14, flex: "none" }, children: footer }) : null
926
+ ]
927
+ }
928
+ );
929
+ });
919
930
 
920
931
  // src/components/navigation/FlytedeskBrand.tsx
921
932
  import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
@@ -956,13 +967,13 @@ function Tabs({ tabs = [], value, onChange, className = "", ...rest }) {
956
967
  }
957
968
 
958
969
  // src/components/navigation/Topbar.tsx
959
- import * as React6 from "react";
970
+ import * as React7 from "react";
960
971
  import { Fragment as Fragment3, jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
961
972
  function useIsNarrow(breakpoint) {
962
- const [narrow, setNarrow] = React6.useState(
973
+ const [narrow, setNarrow] = React7.useState(
963
974
  () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(`(max-width: ${breakpoint}px)`).matches
964
975
  );
965
- React6.useEffect(() => {
976
+ React7.useEffect(() => {
966
977
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
967
978
  const mq = window.matchMedia(`(max-width: ${breakpoint}px)`);
968
979
  const apply = () => setNarrow(mq.matches);
@@ -983,11 +994,11 @@ function Topbar({
983
994
  ...rest
984
995
  }) {
985
996
  const narrow = useIsNarrow(overflowBreakpoint);
986
- const [moreOpen, setMoreOpen] = React6.useState(false);
987
- const moreRef = React6.useRef(null);
997
+ const [moreOpen, setMoreOpen] = React7.useState(false);
998
+ const moreRef = React7.useRef(null);
988
999
  return /* @__PURE__ */ jsxs13("header", { className: ["fd-topbar", narrow ? "is-narrow" : "", className].filter(Boolean).join(" "), ...rest, children: [
989
1000
  onMenu ? /* @__PURE__ */ jsx16("button", { type: "button", className: "fd-btn-icon", "aria-label": "Menu", onClick: onMenu, children: /* @__PURE__ */ jsx16("i", { className: "ph ph-list" }) }) : null,
990
- /* @__PURE__ */ jsx16("nav", { className: "fd-crumb", "aria-label": "Breadcrumb", children: crumbs.map((c, i) => /* @__PURE__ */ jsxs13(React6.Fragment, { children: [
1001
+ /* @__PURE__ */ jsx16("nav", { className: "fd-crumb", "aria-label": "Breadcrumb", children: crumbs.map((c, i) => /* @__PURE__ */ jsxs13(React7.Fragment, { children: [
991
1002
  i > 0 ? /* @__PURE__ */ jsx16("i", { className: "ph ph-caret-right", style: { fontSize: 11, opacity: 0.6 }, "aria-hidden": "true" }) : null,
992
1003
  i === crumbs.length - 1 ? (
993
1004
  // MP-141: the current crumb mixes a static route label with (usually) a
@@ -1196,18 +1207,18 @@ function Toast({ title, children, tone = "success", onUndo, onDismiss, className
1196
1207
  }
1197
1208
 
1198
1209
  // src/components/feedback/Tooltip.tsx
1199
- import * as React7 from "react";
1210
+ import * as React8 from "react";
1200
1211
  import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
1201
1212
  var CLOSE_DELAY_MS = 120;
1202
1213
  function Tooltip({ label, placement = "top", children, className = "" }) {
1203
- const anchorRef = React7.useRef(null);
1204
- const bubbleRef = React7.useRef(null);
1205
- const [reason, setReason] = React7.useState(null);
1206
- const closeTimer = React7.useRef(null);
1207
- const id = React7.useId();
1214
+ const anchorRef = React8.useRef(null);
1215
+ const bubbleRef = React8.useRef(null);
1216
+ const [reason, setReason] = React8.useState(null);
1217
+ const closeTimer = React8.useRef(null);
1218
+ const id = React8.useId();
1208
1219
  const descId = id + "-desc";
1209
1220
  const open = reason != null;
1210
- const cancelClose = React7.useCallback(() => {
1221
+ const cancelClose = React8.useCallback(() => {
1211
1222
  if (closeTimer.current) {
1212
1223
  clearTimeout(closeTimer.current);
1213
1224
  closeTimer.current = null;
@@ -1217,7 +1228,7 @@ function Tooltip({ label, placement = "top", children, className = "" }) {
1217
1228
  cancelClose();
1218
1229
  setReason(why);
1219
1230
  };
1220
- const hide = React7.useCallback(() => {
1231
+ const hide = React8.useCallback(() => {
1221
1232
  cancelClose();
1222
1233
  setReason(null);
1223
1234
  }, [cancelClose]);
@@ -1232,7 +1243,7 @@ function Tooltip({ label, placement = "top", children, className = "" }) {
1232
1243
  setReason((r) => r === "hover" ? null : r);
1233
1244
  }, CLOSE_DELAY_MS);
1234
1245
  };
1235
- React7.useEffect(() => cancelClose, [cancelClose]);
1246
+ React8.useEffect(() => cancelClose, [cancelClose]);
1236
1247
  const pos = usePopoverPosition(open, anchorRef, {
1237
1248
  placement: placement === "bottom" ? "bottom-center" : "top-center",
1238
1249
  offset: 8,
@@ -1242,7 +1253,7 @@ function Tooltip({ label, placement = "top", children, className = "" }) {
1242
1253
  onDetach: hide
1243
1254
  });
1244
1255
  const zIndex = useOverlayLayer(open);
1245
- React7.useEffect(() => {
1256
+ React8.useEffect(() => {
1246
1257
  if (!open) return;
1247
1258
  const key = (e) => {
1248
1259
  if (e.key === "Escape") hide();
@@ -1270,7 +1281,7 @@ function Tooltip({ label, placement = "top", children, className = "" }) {
1270
1281
  }
1271
1282
  if (keyboard) show("focus");
1272
1283
  };
1273
- const described = React7.isValidElement(children) ? React7.cloneElement(children, {
1284
+ const described = React8.isValidElement(children) ? React8.cloneElement(children, {
1274
1285
  "aria-describedby": [children.props["aria-describedby"], descId].filter(Boolean).join(" ")
1275
1286
  }) : children;
1276
1287
  return /* @__PURE__ */ jsxs20(
@@ -1278,7 +1289,7 @@ function Tooltip({ label, placement = "top", children, className = "" }) {
1278
1289
  {
1279
1290
  ref: anchorRef,
1280
1291
  className: "fd-tooltip-anchor",
1281
- "aria-describedby": React7.isValidElement(children) ? void 0 : descId,
1292
+ "aria-describedby": React8.isValidElement(children) ? void 0 : descId,
1282
1293
  onPointerEnter: (e) => {
1283
1294
  if (isMouse(e)) hover();
1284
1295
  },
@@ -1322,7 +1333,7 @@ function Tooltip({ label, placement = "top", children, className = "" }) {
1322
1333
  }
1323
1334
 
1324
1335
  // src/components/data/Clamp.tsx
1325
- import * as React8 from "react";
1336
+ import * as React9 from "react";
1326
1337
  import { jsx as jsx25, jsxs as jsxs21 } from "react/jsx-runtime";
1327
1338
  function Clamp({
1328
1339
  children,
@@ -1334,16 +1345,16 @@ function Clamp({
1334
1345
  onToggle,
1335
1346
  className = ""
1336
1347
  }) {
1337
- const [open, setOpen] = React8.useState(false);
1338
- const [overflows, setOverflows] = React8.useState(false);
1339
- const bodyRef = React8.useRef(null);
1340
- const charClamped = React8.useMemo(() => {
1348
+ const [open, setOpen] = React9.useState(false);
1349
+ const [overflows, setOverflows] = React9.useState(false);
1350
+ const bodyRef = React9.useRef(null);
1351
+ const charClamped = React9.useMemo(() => {
1341
1352
  if (open || !maxChars || typeof text !== "string" || text.length <= maxChars) return null;
1342
1353
  const cut = text.slice(0, maxChars);
1343
1354
  const at = cut.lastIndexOf(" ");
1344
1355
  return (at > maxChars * 0.6 ? cut.slice(0, at) : cut).trimEnd() + "\u2026";
1345
1356
  }, [text, maxChars, open]);
1346
- React8.useLayoutEffect(() => {
1357
+ React9.useLayoutEffect(() => {
1347
1358
  const node = bodyRef.current;
1348
1359
  if (maxChars || !node || !maxHeight) return;
1349
1360
  const measure = () => {
@@ -1482,18 +1493,18 @@ function DataTable({
1482
1493
  }
1483
1494
 
1484
1495
  // src/components/data/FilterBar.tsx
1485
- import * as React9 from "react";
1496
+ import * as React10 from "react";
1486
1497
  import { jsx as jsx28, jsxs as jsxs24 } from "react/jsx-runtime";
1487
1498
  var isSet = (v) => !(v == null || v === "" || v === false);
1488
1499
  function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align = "left", className = "", ...rest }) {
1489
- const [panel, setPanel] = React9.useState(null);
1500
+ const [panel, setPanel] = React10.useState(null);
1490
1501
  const zIndex = useOverlayLayer(!!panel);
1491
- const rootRef = React9.useRef(null);
1492
- const textInputRef = React9.useRef(null);
1493
- const optionsRef = React9.useRef(null);
1502
+ const rootRef = React10.useRef(null);
1503
+ const textInputRef = React10.useRef(null);
1504
+ const optionsRef = React10.useRef(null);
1494
1505
  const editingField = panel && panel.kind === "edit" ? fields.find((f) => f.key === panel.key) : void 0;
1495
1506
  const dismissible = !!panel && (panel.kind === "add" || panel.kind === "edit" && editingField?.type === "select");
1496
- React9.useEffect(() => {
1507
+ React10.useEffect(() => {
1497
1508
  if (!dismissible) return;
1498
1509
  const away = (e) => {
1499
1510
  if (rootRef.current && !rootRef.current.contains(e.target)) setPanel(null);
@@ -1508,7 +1519,7 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
1508
1519
  document.removeEventListener("keydown", esc);
1509
1520
  };
1510
1521
  }, [dismissible]);
1511
- React9.useEffect(() => {
1522
+ React10.useEffect(() => {
1512
1523
  if (!panel || panel.kind !== "edit") return;
1513
1524
  if (editingField?.type === "text") {
1514
1525
  textInputRef.current?.focus();
@@ -1674,7 +1685,7 @@ function StatTile({ label, value, sub, delta, loading = false, compact: compact3
1674
1685
  }
1675
1686
 
1676
1687
  // src/components/data/CodeBlock.tsx
1677
- import * as React10 from "react";
1688
+ import * as React11 from "react";
1678
1689
  import { Fragment as Fragment5, jsx as jsx30, jsxs as jsxs26 } from "react/jsx-runtime";
1679
1690
  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)$/;
1680
1691
  var RULES = {
@@ -1830,13 +1841,13 @@ function tokenize(src, lang) {
1830
1841
  return out;
1831
1842
  }
1832
1843
  function Highlighted({ code, lang }) {
1833
- const toks = React10.useMemo(() => tokenize(code, lang), [code, lang]);
1844
+ const toks = React11.useMemo(() => tokenize(code, lang), [code, lang]);
1834
1845
  return /* @__PURE__ */ jsx30(Fragment5, { children: toks.map((tk, i) => tk.c ? /* @__PURE__ */ jsx30("span", { className: "tok-" + tk.c, children: tk.t }, i) : tk.t) });
1835
1846
  }
1836
1847
  function useCopy() {
1837
- const [done, setDone] = React10.useState(false);
1838
- const timer = React10.useRef(null);
1839
- React10.useEffect(() => () => {
1848
+ const [done, setDone] = React11.useState(false);
1849
+ const timer = React11.useRef(null);
1850
+ React11.useEffect(() => () => {
1840
1851
  if (timer.current) clearTimeout(timer.current);
1841
1852
  }, []);
1842
1853
  const copy = (text) => {
@@ -1872,8 +1883,8 @@ function CodeBlock({
1872
1883
  actions,
1873
1884
  className = ""
1874
1885
  }) {
1875
- const [wrap, setWrap] = React10.useState(!!wrapDefault);
1876
- const [open, setOpen] = React10.useState(false);
1886
+ const [wrap, setWrap] = React11.useState(!!wrapDefault);
1887
+ const [open, setOpen] = React11.useState(false);
1877
1888
  const [copied, copy] = useCopy();
1878
1889
  const body = String(code).replace(/\n$/, "");
1879
1890
  const lines = body.split("\n");
@@ -1902,7 +1913,7 @@ function CodeBlock({
1902
1913
  ] });
1903
1914
  }
1904
1915
  function DiffBlock({ diff = "", filename, collapseAfter = 34, className = "" }) {
1905
- const [open, setOpen] = React10.useState(false);
1916
+ const [open, setOpen] = React11.useState(false);
1906
1917
  const [copied, copy] = useCopy();
1907
1918
  const raw = String(diff).replace(/\n$/, "");
1908
1919
  const all = raw.split("\n").map((line) => {
@@ -1948,7 +1959,7 @@ function DiffBlock({ diff = "", filename, collapseAfter = 34, className = "" })
1948
1959
  }
1949
1960
 
1950
1961
  // src/components/data/Markdown.tsx
1951
- import * as React11 from "react";
1962
+ import * as React12 from "react";
1952
1963
  import { jsx as jsx31, jsxs as jsxs27 } from "react/jsx-runtime";
1953
1964
  var SAFE = /^(?:https?:|mailto:|tel:|#|\/|\.{1,2}\/)/i;
1954
1965
  var safeHref = (h) => SAFE.test(String(h || "").trim()) ? String(h).trim() : null;
@@ -1987,7 +1998,7 @@ function inlineNodes(src, ctx, keyBase) {
1987
1998
  const href = safeHref(g(2));
1988
1999
  out.push(href ? /* @__PURE__ */ jsx31("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__ */ jsx31("span", { children: g(1) }, key()));
1989
2000
  } else if (kind === "cite") {
1990
- out.push(ctx.renderCitation ? /* @__PURE__ */ jsx31(React11.Fragment, { children: ctx.renderCitation(g(1)) }, key()) : /* @__PURE__ */ jsx31("sup", { className: "fd-md-cite", children: g(1) }, key()));
2001
+ out.push(ctx.renderCitation ? /* @__PURE__ */ jsx31(React12.Fragment, { children: ctx.renderCitation(g(1)) }, key()) : /* @__PURE__ */ jsx31("sup", { className: "fd-md-cite", children: g(1) }, key()));
1991
2002
  } else if (kind === "strongem") out.push(/* @__PURE__ */ jsx31("strong", { children: /* @__PURE__ */ jsx31("em", { children: inlineNodes(g(1), ctx, key()) }) }, key()));
1992
2003
  else if (kind === "strong") out.push(/* @__PURE__ */ jsx31("strong", { children: inlineNodes(g(1) || g(2), ctx, key()) }, key()));
1993
2004
  else if (kind === "em") out.push(/* @__PURE__ */ jsx31("em", { children: inlineNodes(g(1) || g(2), ctx, key()) }, key()));
@@ -2152,7 +2163,7 @@ function Markdown({
2152
2163
  onLinkClick
2153
2164
  }) {
2154
2165
  const ctx = { allowImages, headingOffset, renderCitation, onLinkClick, codeProps };
2155
- const blocks = React11.useMemo(() => parseBlocks(source), [source]);
2166
+ const blocks = React12.useMemo(() => parseBlocks(source), [source]);
2156
2167
  return /* @__PURE__ */ jsx31("div", { className: ["fd-md", className].filter(Boolean).join(" "), children: renderBlocks(blocks, ctx, "b") });
2157
2168
  }
2158
2169
  function MarkdownInline({ source = "", className = "", onLinkClick }) {
@@ -2266,7 +2277,7 @@ function Pagination({
2266
2277
  }
2267
2278
 
2268
2279
  // src/components/data/RelativeTime.tsx
2269
- import * as React12 from "react";
2280
+ import * as React13 from "react";
2270
2281
  import { jsxs as jsxs30 } from "react/jsx-runtime";
2271
2282
  var MIN = 6e4;
2272
2283
  var HOUR = 36e5;
@@ -2318,9 +2329,9 @@ function formatDuration(ms) {
2318
2329
  return h + "h" + (m % 60 ? " " + m % 60 + "m" : "");
2319
2330
  }
2320
2331
  function RelativeTime({ value, prefix, className = "", refresh: refresh2 = true, ...rest }) {
2321
- const [, force] = React12.useState(0);
2332
+ const [, force] = React13.useState(0);
2322
2333
  const t = value instanceof Date ? value.getTime() : Date.parse(value);
2323
- React12.useEffect(() => {
2334
+ React13.useEffect(() => {
2324
2335
  if (!refresh2 || !t || isNaN(t)) return;
2325
2336
  const abs = Math.abs(t - Date.now());
2326
2337
  if (abs > DAY) return;
@@ -2336,10 +2347,10 @@ function RelativeTime({ value, prefix, className = "", refresh: refresh2 = true,
2336
2347
  }
2337
2348
 
2338
2349
  // src/components/data/ScoreMeter.tsx
2339
- import * as React14 from "react";
2350
+ import * as React15 from "react";
2340
2351
 
2341
2352
  // src/components/data/SegmentedMeter.tsx
2342
- import * as React13 from "react";
2353
+ import * as React14 from "react";
2343
2354
 
2344
2355
  // src/kits/files.ts
2345
2356
  function formatBytes(n) {
@@ -2544,7 +2555,7 @@ function SegmentedMeter({
2544
2555
  const pct = safeTotal ? Math.min(100, Math.round(used / safeTotal * 100)) : 0;
2545
2556
  const w = (v) => safeTotal ? Math.max(0, Math.min(100, Number(v) / safeTotal * 100)) : 0;
2546
2557
  const fill = (s, i) => s.color || (s.variant ? VARIANT_FILL[s.variant] : void 0) || CHART[i % CHART.length];
2547
- const generatedId = React13.useId();
2558
+ const generatedId = React14.useId();
2548
2559
  const uid2 = id || generatedId;
2549
2560
  return /* @__PURE__ */ jsxs31("div", { className: ["fd-segmeter", className].filter(Boolean).join(" "), onClick, children: [
2550
2561
  label || showTotal ? /* @__PURE__ */ jsxs31("div", { className: "fd-segmeter-head", children: [
@@ -2628,10 +2639,10 @@ function ScoreMeter({
2628
2639
  breakdownWidth = 280,
2629
2640
  className = ""
2630
2641
  }) {
2631
- const [open, setOpen] = React14.useState(false);
2632
- const ref = React14.useRef(null);
2642
+ const [open, setOpen] = React15.useState(false);
2643
+ const ref = React15.useRef(null);
2633
2644
  const value = Math.max(0, Math.min(100, Math.round(Number(score) || 0)));
2634
- const body = /* @__PURE__ */ jsxs32(React14.Fragment, { children: [
2645
+ const body = /* @__PURE__ */ jsxs32(React15.Fragment, { children: [
2635
2646
  /* @__PURE__ */ jsx35("span", { className: "fd-scoremeter-rail", style: { width }, "aria-hidden": "true", children: /* @__PURE__ */ jsx35(SegmentedMeter, { total: 100, segments, height, showTotal: false, showPercent: false, legend: false }) }),
2636
2647
  showScore ? /* @__PURE__ */ jsx35("span", { className: "fd-scoremeter-value fd-tabular", "aria-hidden": "true", children: value }) : null
2637
2648
  ] });
@@ -2650,7 +2661,7 @@ function ScoreMeter({
2650
2661
  }
2651
2662
  );
2652
2663
  }
2653
- return /* @__PURE__ */ jsxs32(React14.Fragment, { children: [
2664
+ return /* @__PURE__ */ jsxs32(React15.Fragment, { children: [
2654
2665
  /* @__PURE__ */ jsx35(
2655
2666
  "button",
2656
2667
  {
@@ -2681,13 +2692,13 @@ function ScoreMeter({
2681
2692
  }
2682
2693
 
2683
2694
  // src/components/data/SortMenu.tsx
2684
- import * as React15 from "react";
2695
+ import * as React16 from "react";
2685
2696
  import { jsx as jsx36, jsxs as jsxs33 } from "react/jsx-runtime";
2686
2697
  function SortMenu({ fields = [], sort, onSort, align = "right", size = "md", className = "", ...rest }) {
2687
- const [open, setOpen] = React15.useState(false);
2698
+ const [open, setOpen] = React16.useState(false);
2688
2699
  const zIndex = useOverlayLayer(open);
2689
- const ref = React15.useRef(null);
2690
- React15.useEffect(() => {
2700
+ const ref = React16.useRef(null);
2701
+ React16.useEffect(() => {
2691
2702
  if (!open) return;
2692
2703
  const away = (e) => {
2693
2704
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -2749,7 +2760,7 @@ function SortMenu({ fields = [], sort, onSort, align = "right", size = "md", cla
2749
2760
  }
2750
2761
 
2751
2762
  // src/components/data/StepList.tsx
2752
- import * as React16 from "react";
2763
+ import * as React17 from "react";
2753
2764
  import { jsx as jsx37, jsxs as jsxs34 } from "react/jsx-runtime";
2754
2765
  var KIND_ICON = { tool: "wrench", search: "magnifying-glass", read: "file-text", write: "pencil-simple", reasoning: "brain", run: "terminal", fetch: "cloud-arrow-down" };
2755
2766
  function StepList({
@@ -2762,9 +2773,9 @@ function StepList({
2762
2773
  dense = false,
2763
2774
  className = ""
2764
2775
  }) {
2765
- const [openState, setOpenState] = React16.useState(defaultOpen);
2776
+ const [openState, setOpenState] = React17.useState(defaultOpen);
2766
2777
  const open = openProp == null ? openState : openProp;
2767
- const [expanded, setExpanded] = React16.useState({});
2778
+ const [expanded, setExpanded] = React17.useState({});
2768
2779
  if (!steps.length) return null;
2769
2780
  const running = steps.some((s) => s.status === "running");
2770
2781
  const failed = steps.filter((s) => s.status === "error").length;
@@ -2822,7 +2833,7 @@ function StepList({
2822
2833
  }
2823
2834
 
2824
2835
  // src/components/data/VirtualList.tsx
2825
- import * as React17 from "react";
2836
+ import * as React18 from "react";
2826
2837
  import { jsx as jsx38, jsxs as jsxs35 } from "react/jsx-runtime";
2827
2838
  var VIRTUAL_LIST_BUFFER_ROWS = 20;
2828
2839
  var VIRTUAL_LIST_ROW_GAP = 6;
@@ -2874,35 +2885,35 @@ function VirtualList({
2874
2885
  className = "",
2875
2886
  style
2876
2887
  }) {
2877
- const [scrollTop, setScrollTop] = React17.useState(0);
2878
- const loaded = React17.useMemo(
2888
+ const [scrollTop, setScrollTop] = React18.useState(0);
2889
+ const loaded = React18.useMemo(
2879
2890
  () => ({ firstItemIndex, count: items.length }),
2880
2891
  [firstItemIndex, items.length]
2881
2892
  );
2882
2893
  const total = Math.max(totalCount == null ? 0 : totalCount, firstItemIndex + items.length);
2883
2894
  const bandKey = firstItemIndex + ":" + items.length;
2884
- const win = React17.useMemo(
2895
+ const win = React18.useMemo(
2885
2896
  () => computeVirtualWindow({ scrollTop, viewportHeight: height, itemHeight, itemCount: total, loaded }),
2886
2897
  [scrollTop, height, itemHeight, total, loaded]
2887
2898
  );
2888
- const need = React17.useMemo(() => computeNeedMore(win, loaded), [win, loaded]);
2899
+ const need = React18.useMemo(() => computeNeedMore(win, loaded), [win, loaded]);
2889
2900
  const wantStart = need.start && hasMore?.start !== false;
2890
2901
  const wantEnd = need.end && hasMore?.end !== false;
2891
2902
  const bucket2 = Math.floor(win.visibleStart / VIRTUAL_LIST_BUFFER_ROWS);
2892
2903
  const askKey = bandKey + "@" + bucket2;
2893
- const asked = React17.useRef({ start: null, end: null });
2894
- React17.useEffect(() => {
2904
+ const asked = React18.useRef({ start: null, end: null });
2905
+ React18.useEffect(() => {
2895
2906
  if (loading || !wantStart || asked.current.start === askKey) return;
2896
2907
  asked.current.start = askKey;
2897
2908
  onNeedMore("start", win);
2898
2909
  }, [wantStart, loading, askKey, onNeedMore, win]);
2899
- React17.useEffect(() => {
2910
+ React18.useEffect(() => {
2900
2911
  if (loading || !wantEnd || asked.current.end === askKey) return;
2901
2912
  asked.current.end = askKey;
2902
2913
  onNeedMore("end", win);
2903
2914
  }, [wantEnd, loading, askKey, onNeedMore, win]);
2904
- const lastKeysRef = React17.useRef(null);
2905
- const newKeys = React17.useMemo(() => {
2915
+ const lastKeysRef = React18.useRef(null);
2916
+ const newKeys = React18.useMemo(() => {
2906
2917
  const prev = lastKeysRef.current;
2907
2918
  if (!prev) return /* @__PURE__ */ new Set();
2908
2919
  const fresh = /* @__PURE__ */ new Set();
@@ -2912,7 +2923,7 @@ function VirtualList({
2912
2923
  }
2913
2924
  return fresh;
2914
2925
  }, [items]);
2915
- React17.useEffect(() => {
2926
+ React18.useEffect(() => {
2916
2927
  lastKeysRef.current = new Set(items.map(keyOf));
2917
2928
  }, [items]);
2918
2929
  const gapStyle = { "--fd-vlist-gap": rowGap + "px" };
@@ -2957,7 +2968,7 @@ function VirtualList({
2957
2968
  }
2958
2969
 
2959
2970
  // src/components/data/EntityRow.tsx
2960
- import * as React18 from "react";
2971
+ import * as React19 from "react";
2961
2972
  import { jsx as jsx39, jsxs as jsxs36 } from "react/jsx-runtime";
2962
2973
  var ENTITY_ROW_METRIC_WIDTH = 74;
2963
2974
  var METRIC_TONE_COLOR = {
@@ -2970,7 +2981,7 @@ function EntityRowMetrics({ metrics, className = "", style }) {
2970
2981
  const key = m.id || m.label || i;
2971
2982
  const className2 = ["fd-erow-metric", m.tone && m.tone !== "default" ? "is-" + m.tone : "", m.tooltip != null ? "is-described" : ""].filter(Boolean).join(" ");
2972
2983
  const tone = { color: METRIC_TONE_COLOR[m.tone || "default"] };
2973
- const content = /* @__PURE__ */ jsxs36(React18.Fragment, { children: [
2984
+ const content = /* @__PURE__ */ jsxs36(React19.Fragment, { children: [
2974
2985
  m.icon ? /* @__PURE__ */ jsx39("i", { className: "ph ph-" + m.icon, "aria-hidden": "true" }) : null,
2975
2986
  /* @__PURE__ */ jsx39("span", { className: "fd-erow-metric-value fd-tabular", children: m.value }),
2976
2987
  /* @__PURE__ */ jsx39("span", { className: "fd-sr", children: " " + m.label })
@@ -3062,14 +3073,14 @@ function EntityRow({
3062
3073
  }
3063
3074
 
3064
3075
  // src/components/data/TransferList.tsx
3065
- import * as React20 from "react";
3076
+ import * as React21 from "react";
3066
3077
  import { createPortal as createPortal4 } from "react-dom";
3067
3078
 
3068
3079
  // src/components/forms/field.tsx
3069
- import * as React19 from "react";
3080
+ import * as React20 from "react";
3070
3081
  import { jsx as jsx40, jsxs as jsxs37 } from "react/jsx-runtime";
3071
3082
  function useFieldId(id) {
3072
- const generated = React19.useId();
3083
+ const generated = React20.useId();
3073
3084
  return id || generated;
3074
3085
  }
3075
3086
  function FieldLabel({ htmlFor, id, required = false, onClick, children }) {
@@ -3187,12 +3198,12 @@ function TransferList({
3187
3198
  listHeight = 440,
3188
3199
  className = ""
3189
3200
  }) {
3190
- const [drag, setDrag] = React20.useState(null);
3191
- const [dragOverSide, setDragOverSide] = React20.useState(null);
3192
- const dragRef = React20.useRef(null);
3193
- const armedRef = React20.useRef(null);
3194
- const sideRefs = React20.useRef({ left: null, right: null });
3195
- const hitTestSide = React20.useCallback((x, y) => {
3201
+ const [drag, setDrag] = React21.useState(null);
3202
+ const [dragOverSide, setDragOverSide] = React21.useState(null);
3203
+ const dragRef = React21.useRef(null);
3204
+ const armedRef = React21.useRef(null);
3205
+ const sideRefs = React21.useRef({ left: null, right: null });
3206
+ const hitTestSide = React21.useCallback((x, y) => {
3196
3207
  for (const side of ["left", "right"]) {
3197
3208
  const el = sideRefs.current[side];
3198
3209
  if (!el) continue;
@@ -3201,7 +3212,7 @@ function TransferList({
3201
3212
  }
3202
3213
  return null;
3203
3214
  }, []);
3204
- const findItem = React20.useCallback(
3215
+ const findItem = React21.useCallback(
3205
3216
  (side, key) => (side === "left" ? left.items : right.items).find((it) => keyOf(it) === key),
3206
3217
  [left.items, right.items, keyOf]
3207
3218
  );
@@ -3223,7 +3234,7 @@ function TransferList({
3223
3234
  height: rect.height
3224
3235
  };
3225
3236
  };
3226
- React20.useEffect(() => {
3237
+ React21.useEffect(() => {
3227
3238
  const onMovePointer = (e) => {
3228
3239
  const armed = armedRef.current;
3229
3240
  if (!armed || e.pointerId !== armed.pointerId) return;
@@ -3276,7 +3287,7 @@ function TransferList({
3276
3287
  window.removeEventListener("pointercancel", endDrag);
3277
3288
  };
3278
3289
  }, [hitTestSide, findItem, onMove]);
3279
- React20.useEffect(() => {
3290
+ React21.useEffect(() => {
3280
3291
  if (!drag || typeof document === "undefined") return;
3281
3292
  const prev = document.body.style.userSelect;
3282
3293
  document.body.style.userSelect = "none";
@@ -3384,11 +3395,11 @@ function TransferList({
3384
3395
  }
3385
3396
 
3386
3397
  // src/components/forms/Checkbox.tsx
3387
- import * as React21 from "react";
3398
+ import * as React22 from "react";
3388
3399
  import { jsx as jsx44, jsxs as jsxs40 } from "react/jsx-runtime";
3389
3400
  function Checkbox({ label, description, card = false, indeterminate = false, className = "", ...rest }) {
3390
- const ref = React21.useRef(null);
3391
- React21.useEffect(() => {
3401
+ const ref = React22.useRef(null);
3402
+ React22.useEffect(() => {
3392
3403
  if (ref.current) ref.current.indeterminate = indeterminate;
3393
3404
  }, [indeterminate]);
3394
3405
  return /* @__PURE__ */ jsxs40("label", { className: ["fd-choice", card ? "fd-choice-card" : "", className].filter(Boolean).join(" "), children: [
@@ -3447,7 +3458,7 @@ function Textarea({ label, help, error, required = false, rows = 4, disabled = f
3447
3458
  }
3448
3459
 
3449
3460
  // src/components/forms/NumberInput.tsx
3450
- import * as React22 from "react";
3461
+ import * as React23 from "react";
3451
3462
  import { jsx as jsx48, jsxs as jsxs44 } from "react/jsx-runtime";
3452
3463
  function NumberInput({
3453
3464
  label,
@@ -3475,10 +3486,10 @@ function NumberInput({
3475
3486
  const n = Number(String(v == null ? "" : v).replace(/[^0-9.-]/g, ""));
3476
3487
  return isNaN(n) ? null : n;
3477
3488
  };
3478
- const [text, setText] = React22.useState(value == null || value === "" ? "" : String(value));
3479
- const [editing, setEditing] = React22.useState(false);
3480
- const timer = React22.useRef(null);
3481
- React22.useEffect(() => {
3489
+ const [text, setText] = React23.useState(value == null || value === "" ? "" : String(value));
3490
+ const [editing, setEditing] = React23.useState(false);
3491
+ const timer = React23.useRef(null);
3492
+ React23.useEffect(() => {
3482
3493
  if (!editing) setText(value == null || value === "" ? "" : String(value));
3483
3494
  }, [value, editing]);
3484
3495
  const clamp = (n) => Math.min(max, Math.max(min, n));
@@ -3502,7 +3513,7 @@ function NumberInput({
3502
3513
  const release = () => {
3503
3514
  if (timer.current) clearTimeout(timer.current);
3504
3515
  };
3505
- React22.useEffect(() => () => {
3516
+ React23.useEffect(() => () => {
3506
3517
  if (timer.current) clearTimeout(timer.current);
3507
3518
  }, []);
3508
3519
  const shown = editing ? text : (() => {
@@ -3589,7 +3600,7 @@ function NumberInput({
3589
3600
  }
3590
3601
 
3591
3602
  // src/components/forms/Select.tsx
3592
- import * as React23 from "react";
3603
+ import * as React24 from "react";
3593
3604
  import { jsx as jsx49, jsxs as jsxs45 } from "react/jsx-runtime";
3594
3605
  var norm = (o) => typeof o === "string" ? { value: o, label: o } : o;
3595
3606
  function Select({
@@ -3616,13 +3627,13 @@ function Select({
3616
3627
  const isOn = (v) => multiple ? vals.includes(v) : v === value;
3617
3628
  const hasSearch = searchable === void 0 ? opts.length > 8 : searchable;
3618
3629
  const { fieldId, labelId, ariaLabelledBy } = useTriggerLabelling(label, id);
3619
- const [open, setOpen] = React23.useState(false);
3620
- const [q, setQ] = React23.useState("");
3621
- const [active, setActive] = React23.useState(-1);
3622
- const rootRef = React23.useRef(null);
3623
- const boxRef = React23.useRef(null);
3624
- const listRef = React23.useRef(null);
3625
- const typeBuf = React23.useRef({ s: "", t: 0 });
3630
+ const [open, setOpen] = React24.useState(false);
3631
+ const [q, setQ] = React24.useState("");
3632
+ const [active, setActive] = React24.useState(-1);
3633
+ const rootRef = React24.useRef(null);
3634
+ const boxRef = React24.useRef(null);
3635
+ const listRef = React24.useRef(null);
3636
+ const typeBuf = React24.useRef({ s: "", t: 0 });
3626
3637
  const selected = multiple ? null : opts.find((o) => o.value === value);
3627
3638
  const chosen = multiple ? opts.filter((o) => vals.includes(o.value)) : [];
3628
3639
  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;
@@ -3647,7 +3658,7 @@ function Select({
3647
3658
  }
3648
3659
  setOpen(!open);
3649
3660
  };
3650
- React23.useEffect(() => {
3661
+ React24.useEffect(() => {
3651
3662
  if (!open || active < 0 || !listRef.current) return;
3652
3663
  const el = listRef.current.querySelector('[data-i="' + active + '"]');
3653
3664
  if (el) {
@@ -3768,7 +3779,7 @@ function Select({
3768
3779
  ),
3769
3780
  q ? /* @__PURE__ */ jsx49("span", { className: "fd-body-sm fd-muted", children: visible.length }) : null
3770
3781
  ] }) : void 0,
3771
- footer: multiple ? /* @__PURE__ */ jsxs45(React23.Fragment, { children: [
3782
+ footer: multiple ? /* @__PURE__ */ jsxs45(React24.Fragment, { children: [
3772
3783
  /* @__PURE__ */ jsx49(
3773
3784
  "button",
3774
3785
  {
@@ -3799,7 +3810,7 @@ function Select({
3799
3810
  'Nothing matches "',
3800
3811
  q,
3801
3812
  '".'
3802
- ] }) : groups.map((grp) => /* @__PURE__ */ jsxs45(React23.Fragment, { children: [
3813
+ ] }) : groups.map((grp) => /* @__PURE__ */ jsxs45(React24.Fragment, { children: [
3803
3814
  grp.g ? /* @__PURE__ */ jsx49("div", { className: "fd-pop-group", children: grp.g }) : null,
3804
3815
  grp.items.map(({ o, i }) => /* @__PURE__ */ jsxs45(
3805
3816
  "button",
@@ -3836,7 +3847,7 @@ function Select({
3836
3847
  }
3837
3848
 
3838
3849
  // src/components/forms/DatePicker.tsx
3839
- import * as React24 from "react";
3850
+ import * as React25 from "react";
3840
3851
  import { jsx as jsx50, jsxs as jsxs46 } from "react/jsx-runtime";
3841
3852
  var MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
3842
3853
  var DOW = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
@@ -3854,10 +3865,10 @@ function Calendar({ value, range = false, onPick, initialMonth, months = 1 }) {
3854
3865
  const today = /* @__PURE__ */ new Date();
3855
3866
  const sel = range ? value || {} : { start: value, end: value };
3856
3867
  const anchor = parse(sel.start) || parse(initialMonth) || today;
3857
- const [vy, setVy] = React24.useState(anchor.getFullYear());
3858
- const [vm, setVm] = React24.useState(anchor.getMonth());
3859
- const [mode2, setMode] = React24.useState("days");
3860
- const [hover, setHover] = React24.useState(null);
3868
+ const [vy, setVy] = React25.useState(anchor.getFullYear());
3869
+ const [vm, setVm] = React25.useState(anchor.getMonth());
3870
+ const [mode2, setMode] = React25.useState("days");
3871
+ const [hover, setHover] = React25.useState(null);
3861
3872
  const s = parse(sel.start), e = parse(sel.end);
3862
3873
  const hoverEnd = range && s && !e && hover ? parse(hover) : null;
3863
3874
  const inRange = (d) => {
@@ -3973,9 +3984,9 @@ function Calendar({ value, range = false, onPick, initialMonth, months = 1 }) {
3973
3984
  }
3974
3985
  function DatePicker({ label, help, error, required = false, disabled = false, range = false, value, onChange, placeholder, id, className = "", style, ...rest }) {
3975
3986
  const { fieldId, labelId, ariaLabelledBy } = useTriggerLabelling(label, id);
3976
- const [open, setOpen] = React24.useState(false);
3977
- const rootRef = React24.useRef(null);
3978
- const boxRef = React24.useRef(null);
3987
+ const [open, setOpen] = React25.useState(false);
3988
+ const rootRef = React25.useRef(null);
3989
+ const boxRef = React25.useRef(null);
3979
3990
  const display = range ? value && value.start ? fmt(value.start) + (value.end ? " \u2192 " + fmt(value.end) : " \u2192 \u2026") : "" : fmt(value);
3980
3991
  const toggle = () => {
3981
3992
  if (!disabled) setOpen(!open);
@@ -4038,7 +4049,7 @@ function DatePicker({ label, help, error, required = false, disabled = false, ra
4038
4049
  }
4039
4050
 
4040
4051
  // src/components/forms/TimePicker.tsx
4041
- import * as React25 from "react";
4052
+ import * as React26 from "react";
4042
4053
  import { jsx as jsx51, jsxs as jsxs47 } from "react/jsx-runtime";
4043
4054
  var pad = (n) => String(n).padStart(2, "0");
4044
4055
  function ClockFace({ value = "09:00", onChange }) {
@@ -4047,9 +4058,9 @@ function ClockFace({ value = "09:00", onChange }) {
4047
4058
  if (isNaN(m)) m = 0;
4048
4059
  const pm = h24 >= 12;
4049
4060
  const h12 = h24 % 12 === 0 ? 12 : h24 % 12;
4050
- const [mode2, setMode] = React25.useState("h");
4051
- const faceRef = React25.useRef(null);
4052
- const dragging = React25.useRef(false);
4061
+ const [mode2, setMode] = React26.useState("h");
4062
+ const faceRef = React26.useRef(null);
4063
+ const dragging = React26.useRef(false);
4053
4064
  const set = (h, mm, isPm) => onChange((isPm ? h % 12 + 12 : h % 12) + ":" + pad(mm));
4054
4065
  const R = 108, NR = 80;
4055
4066
  const nums = mode2 === "h" ? Array.from({ length: 12 }, (_, i) => i + 1) : Array.from({ length: 12 }, (_, i) => i * 5);
@@ -4122,9 +4133,9 @@ function ClockFace({ value = "09:00", onChange }) {
4122
4133
  }
4123
4134
  function TimePicker({ label, help, error, required = false, disabled = false, value = "", onChange, placeholder = "Pick a time", id, className = "", style }) {
4124
4135
  const { fieldId, labelId, ariaLabelledBy } = useTriggerLabelling(label, id);
4125
- const [open, setOpen] = React25.useState(false);
4126
- const rootRef = React25.useRef(null);
4127
- const boxRef = React25.useRef(null);
4136
+ const [open, setOpen] = React26.useState(false);
4137
+ const rootRef = React26.useRef(null);
4138
+ const boxRef = React26.useRef(null);
4128
4139
  const disp = () => {
4129
4140
  if (!value) return "";
4130
4141
  const [h, m] = value.split(":").map(Number);
@@ -4167,7 +4178,7 @@ function TimePicker({ label, help, error, required = false, disabled = false, va
4167
4178
  offset: 6,
4168
4179
  width: 262,
4169
4180
  role: "presentation",
4170
- footer: /* @__PURE__ */ jsxs47(React25.Fragment, { children: [
4181
+ footer: /* @__PURE__ */ jsxs47(React26.Fragment, { children: [
4171
4182
  /* @__PURE__ */ jsx51(
4172
4183
  "button",
4173
4184
  {
@@ -4195,7 +4206,7 @@ function TimePicker({ label, help, error, required = false, disabled = false, va
4195
4206
  }
4196
4207
 
4197
4208
  // src/components/forms/Slider.tsx
4198
- import * as React26 from "react";
4209
+ import * as React27 from "react";
4199
4210
  import { jsx as jsx52, jsxs as jsxs48 } from "react/jsx-runtime";
4200
4211
  function Slider({
4201
4212
  label,
@@ -4213,7 +4224,7 @@ function Slider({
4213
4224
  ...rest
4214
4225
  }) {
4215
4226
  const fieldId = useFieldId(id);
4216
- const [dragging, setDragging] = React26.useState(false);
4227
+ const [dragging, setDragging] = React27.useState(false);
4217
4228
  const v = value === void 0 ? min : Number(value);
4218
4229
  const pct = max === min ? 0 : (v - min) / (max - min) * 100;
4219
4230
  return /* @__PURE__ */ jsxs48("div", { className: ["fd-field", className].filter(Boolean).join(" "), children: [
@@ -4247,7 +4258,7 @@ function Slider({
4247
4258
  }
4248
4259
 
4249
4260
  // src/components/forms/RangeSlider.tsx
4250
- import * as React27 from "react";
4261
+ import * as React28 from "react";
4251
4262
  import { jsx as jsx53, jsxs as jsxs49 } from "react/jsx-runtime";
4252
4263
  function RangeSlider({
4253
4264
  label,
@@ -4267,9 +4278,9 @@ function RangeSlider({
4267
4278
  }) {
4268
4279
  const fmt2 = format || ((v) => String(v));
4269
4280
  const [a, b] = value || [min, max];
4270
- const [drag, setDrag] = React27.useState(null);
4271
- const [focus, setFocus] = React27.useState(null);
4272
- const railRef = React27.useRef(null);
4281
+ const [drag, setDrag] = React28.useState(null);
4282
+ const [focus, setFocus] = React28.useState(null);
4283
+ const railRef = React28.useRef(null);
4273
4284
  const pct = (v) => max === min ? 0 : (v - min) / (max - min) * 100;
4274
4285
  const clampPair = (i, v) => {
4275
4286
  v = Math.min(max, Math.max(min, Math.round(v / step) * step));
@@ -4284,7 +4295,7 @@ function RangeSlider({
4284
4295
  const r = railRef.current.getBoundingClientRect();
4285
4296
  return min + Math.min(1, Math.max(0, (e.clientX - r.left) / r.width)) * (max - min);
4286
4297
  };
4287
- React27.useEffect(() => {
4298
+ React28.useEffect(() => {
4288
4299
  if (drag === null) return;
4289
4300
  const mv = (e) => onChange && onChange(clampPair(drag, fromEvent(e)));
4290
4301
  const upH = () => setDrag(null);
@@ -4365,7 +4376,7 @@ function RangeSlider({
4365
4376
  /* @__PURE__ */ jsxs49("div", { className: "fd-range" + (hasLabels ? " has-labels" : ""), onPointerDown: onRailDown, children: [
4366
4377
  /* @__PURE__ */ jsx53("span", { className: "fd-range-rail", ref: railRef }),
4367
4378
  /* @__PURE__ */ jsx53("span", { className: "fd-range-fill", style: { left: pct(a) + "%", width: pct(b) - pct(a) + "%" } }),
4368
- marks.map((m) => /* @__PURE__ */ jsxs49(React27.Fragment, { children: [
4379
+ marks.map((m) => /* @__PURE__ */ jsxs49(React28.Fragment, { children: [
4369
4380
  /* @__PURE__ */ jsx53("span", { className: "fd-range-mark" + (m.value >= a && m.value <= b ? " is-in" : ""), style: { left: pct(m.value) + "%" } }),
4370
4381
  m.label ? /* @__PURE__ */ jsx53("span", { className: "fd-range-mark-label", style: { left: pct(m.value) + "%" }, children: m.label }) : null
4371
4382
  ] }, m.value)),
@@ -4394,7 +4405,7 @@ function RangeSlider({
4394
4405
  }
4395
4406
 
4396
4407
  // src/components/forms/Dropzone.tsx
4397
- import * as React28 from "react";
4408
+ import * as React29 from "react";
4398
4409
  import { jsx as jsx54, jsxs as jsxs50 } from "react/jsx-runtime";
4399
4410
  function Dropzone({
4400
4411
  onFiles,
@@ -4409,8 +4420,8 @@ function Dropzone({
4409
4420
  className = "",
4410
4421
  style
4411
4422
  }) {
4412
- const [over, setOver] = React28.useState(false);
4413
- const depth = React28.useRef(0);
4423
+ const [over, setOver] = React29.useState(false);
4424
+ const depth = React29.useRef(0);
4414
4425
  const has = (e) => {
4415
4426
  const dt = e.dataTransfer;
4416
4427
  if (!dt) return false;
@@ -4472,8 +4483,8 @@ function FilePickButton({
4472
4483
  className = "",
4473
4484
  children
4474
4485
  }) {
4475
- const ref = React28.useRef(null);
4476
- return /* @__PURE__ */ jsxs50(React28.Fragment, { children: [
4486
+ const ref = React29.useRef(null);
4487
+ return /* @__PURE__ */ jsxs50(React29.Fragment, { children: [
4477
4488
  /* @__PURE__ */ jsx54(
4478
4489
  "button",
4479
4490
  {
@@ -4506,10 +4517,10 @@ function FilePickButton({
4506
4517
  }
4507
4518
  function useStagedFiles(upload, opts) {
4508
4519
  const o = opts || {};
4509
- const [items, setItems] = React28.useState([]);
4510
- const controllers = React28.useRef({});
4520
+ const [items, setItems] = React29.useState([]);
4521
+ const controllers = React29.useRef({});
4511
4522
  const patch = (id, next) => setItems((list) => list.map((f) => f.id === id ? Object.assign({}, f, next) : f));
4512
- const run = React28.useCallback((att) => {
4523
+ const run = React29.useCallback((att) => {
4513
4524
  if (!upload) return;
4514
4525
  const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
4515
4526
  controllers.current[att.id] = ac;
@@ -4526,14 +4537,14 @@ function useStagedFiles(upload, opts) {
4526
4537
  delete controllers.current[att.id];
4527
4538
  });
4528
4539
  }, [upload]);
4529
- const add = React28.useCallback((files) => {
4540
+ const add = React29.useCallback((files) => {
4530
4541
  if (!upload) return [];
4531
4542
  const atts = Array.from(files).map((f) => toAttachment(f));
4532
4543
  setItems((list) => list.concat(atts));
4533
4544
  atts.forEach(run);
4534
4545
  return atts;
4535
4546
  }, [upload, run]);
4536
- const remove = React28.useCallback((att) => {
4547
+ const remove = React29.useCallback((att) => {
4537
4548
  const ac = controllers.current[att.id];
4538
4549
  if (ac) {
4539
4550
  try {
@@ -4544,10 +4555,10 @@ function useStagedFiles(upload, opts) {
4544
4555
  }
4545
4556
  setItems((list) => list.filter((f) => f.id !== att.id));
4546
4557
  }, []);
4547
- const retry = React28.useCallback((att) => {
4558
+ const retry = React29.useCallback((att) => {
4548
4559
  run(att);
4549
4560
  }, [run]);
4550
- const clear = React28.useCallback(() => {
4561
+ const clear = React29.useCallback(() => {
4551
4562
  Object.values(controllers.current).forEach((ac) => {
4552
4563
  try {
4553
4564
  ac && ac.abort();
@@ -4671,7 +4682,7 @@ function FileGrid({ files = [], onOpen, onRemove, onRetry, tiles = true, maxHeig
4671
4682
  }
4672
4683
 
4673
4684
  // src/components/forms/MarkdownEditor.tsx
4674
- import * as React29 from "react";
4685
+ import * as React30 from "react";
4675
4686
  import { jsx as jsx56, jsxs as jsxs52 } from "react/jsx-runtime";
4676
4687
  var isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent || "");
4677
4688
  var INLINE_RE = /(\*\*\*[^*\n]+\*\*\*|\*\*[^*\n]+\*\*|__[^_\n]+__|~~[^~\n]+~~|`[^`\n]+`|\*[^*\s][^*\n]*\*|(?<![A-Za-z0-9_])_[^_\s][^_\n]*_|\[[^\]\n]*\]\([^)\s\n]*\)|https?:\/\/\S+)/g;
@@ -4885,7 +4896,7 @@ function syncDom(root, value) {
4885
4896
  while (root.children.length > lines.length) root.removeChild(root.lastChild);
4886
4897
  }
4887
4898
  var LIST_CONT = RE_LI;
4888
- var MarkdownEditor = React29.forwardRef(function MarkdownEditor2({
4899
+ var MarkdownEditor = React30.forwardRef(function MarkdownEditor2({
4889
4900
  value = "",
4890
4901
  onChange,
4891
4902
  onSubmit,
@@ -4904,10 +4915,10 @@ var MarkdownEditor = React29.forwardRef(function MarkdownEditor2({
4904
4915
  className = "",
4905
4916
  id
4906
4917
  }, ref) {
4907
- const boxRef = React29.useRef(null);
4908
- const composing = React29.useRef(false);
4909
- const pendingCaret = React29.useRef(null);
4910
- React29.useLayoutEffect(() => {
4918
+ const boxRef = React30.useRef(null);
4919
+ const composing = React30.useRef(false);
4920
+ const pendingCaret = React30.useRef(null);
4921
+ React30.useLayoutEffect(() => {
4911
4922
  const root = boxRef.current;
4912
4923
  if (!root || composing.current) return;
4913
4924
  const active = document.activeElement === root || root.contains(document.activeElement);
@@ -4916,7 +4927,7 @@ var MarkdownEditor = React29.forwardRef(function MarkdownEditor2({
4916
4927
  pendingCaret.current = null;
4917
4928
  if (active && caret != null) placeCaret(root, caret);
4918
4929
  }, [value]);
4919
- React29.useEffect(() => {
4930
+ React30.useEffect(() => {
4920
4931
  if (autoFocus && boxRef.current) boxRef.current.focus();
4921
4932
  }, [autoFocus]);
4922
4933
  const caretNow = () => {
@@ -4983,7 +4994,7 @@ var MarkdownEditor = React29.forwardRef(function MarkdownEditor2({
4983
4994
  api.replaceRange(from, to, next, from + next.length);
4984
4995
  }
4985
4996
  };
4986
- React29.useImperativeHandle(ref, () => api);
4997
+ React30.useImperativeHandle(ref, () => api);
4987
4998
  function detect(text, caret) {
4988
4999
  if (!onTrigger) return;
4989
5000
  const upto = text.slice(0, caret);
@@ -5135,16 +5146,16 @@ var MarkdownEditor = React29.forwardRef(function MarkdownEditor2({
5135
5146
  });
5136
5147
 
5137
5148
  // src/components/platform/AccountMenu.tsx
5138
- import * as React31 from "react";
5149
+ import * as React32 from "react";
5139
5150
 
5140
5151
  // src/kits/sessionContext.tsx
5141
- import * as React30 from "react";
5142
- var SessionContext = React30.createContext(null);
5152
+ import * as React31 from "react";
5153
+ var SessionContext = React31.createContext(null);
5143
5154
  function SessionProvider({ value, children }) {
5144
- return React30.createElement(SessionContext.Provider, { value }, children);
5155
+ return React31.createElement(SessionContext.Provider, { value }, children);
5145
5156
  }
5146
5157
  function useSessionContext() {
5147
- const ctx = React30.useContext(SessionContext);
5158
+ const ctx = React31.useContext(SessionContext);
5148
5159
  if (!ctx) {
5149
5160
  throw new Error(
5150
5161
  "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."
@@ -5155,20 +5166,8 @@ function useSessionContext() {
5155
5166
 
5156
5167
  // src/components/platform/AccountMenu.tsx
5157
5168
  import { Fragment as Fragment14, jsx as jsx57, jsxs as jsxs53 } from "react/jsx-runtime";
5158
- var DEFAULT_LINKS = [
5159
- { id: "profile", label: "Your profile", icon: "user-circle", href: "../admin/index.html#profile" },
5160
- { id: "preferences", label: "Preferences", icon: "sliders-horizontal", href: "../admin/index.html#preferences" }
5161
- ];
5162
- var DEFAULT_GROUPS = [
5163
- {
5164
- label: "Administration",
5165
- links: [
5166
- { id: "users", label: "Users", icon: "users-three", href: "../admin/index.html#users", perm: "users.view" },
5167
- { id: "roles", label: "Roles & permissions", icon: "shield-check", href: "../admin/index.html#roles", perm: "roles.manage" },
5168
- { id: "flags", label: "Feature flags", icon: "toggle-right", href: "../admin/index.html#flags", perm: "flags.manage" }
5169
- ]
5170
- }
5171
- ];
5169
+ var DEFAULT_LINKS = [];
5170
+ var DEFAULT_GROUPS = [];
5172
5171
  function Item({ item, onPick }) {
5173
5172
  return /* @__PURE__ */ jsxs53(
5174
5173
  "button",
@@ -5196,10 +5195,10 @@ function AccountMenu({
5196
5195
  ...rest
5197
5196
  }) {
5198
5197
  const { user, roles, can: can2, signOut: ctxSignOut, demo } = useSessionContext();
5199
- const [open, setOpen] = React31.useState(false);
5200
- const [switching, setSwitching] = React31.useState(false);
5201
- const [signingOut, setSigningOut] = React31.useState(false);
5202
- const ref = React31.useRef(null);
5198
+ const [open, setOpen] = React32.useState(false);
5199
+ const [switching, setSwitching] = React32.useState(false);
5200
+ const [signingOut, setSigningOut] = React32.useState(false);
5201
+ const ref = React32.useRef(null);
5203
5202
  const visibleLinks = links.filter((l) => !l.perm || can2(l.perm));
5204
5203
  const visibleGroups = groups.map((g) => ({ label: g.label, links: g.links.filter((l) => !l.perm || can2(l.perm)) })).filter((g) => g.links.length);
5205
5204
  const pick = (item) => {
@@ -5302,13 +5301,13 @@ function AccountMenu({
5302
5301
  }
5303
5302
 
5304
5303
  // src/components/platform/AppSwitcher.tsx
5305
- import * as React32 from "react";
5304
+ import * as React33 from "react";
5306
5305
  import { jsx as jsx58, jsxs as jsxs54 } from "react/jsx-runtime";
5307
5306
  function AppSwitcher({ apps: appsProp, onNavigate, className = "" }) {
5308
5307
  const { apps: contextApps } = useSessionContext();
5309
5308
  const apps = appsProp ?? contextApps ?? [];
5310
- const [open, setOpen] = React32.useState(false);
5311
- const anchorRef = React32.useRef(null);
5309
+ const [open, setOpen] = React33.useState(false);
5310
+ const anchorRef = React33.useRef(null);
5312
5311
  if (!apps.length) return null;
5313
5312
  const items = apps.map((app) => ({
5314
5313
  id: app.key,
@@ -5322,7 +5321,7 @@ function AppSwitcher({ apps: appsProp, onNavigate, className = "" }) {
5322
5321
  window.location.href = app.url;
5323
5322
  }
5324
5323
  }));
5325
- return /* @__PURE__ */ jsxs54(React32.Fragment, { children: [
5324
+ return /* @__PURE__ */ jsxs54(React33.Fragment, { children: [
5326
5325
  /* @__PURE__ */ jsx58("span", { ref: anchorRef, className: ["fd-appswitch-anchor", className].filter(Boolean).join(" "), style: { display: "inline-flex" }, children: /* @__PURE__ */ jsx58(
5327
5326
  IconButton,
5328
5327
  {
@@ -5351,10 +5350,10 @@ function AppSwitcher({ apps: appsProp, onNavigate, className = "" }) {
5351
5350
  }
5352
5351
 
5353
5352
  // src/components/platform/AppearanceMenu.tsx
5354
- import * as React34 from "react";
5353
+ import * as React35 from "react";
5355
5354
 
5356
5355
  // src/kits/appearance.ts
5357
- import * as React33 from "react";
5356
+ import * as React34 from "react";
5358
5357
  var DEFAULT_STORAGE_KEY = "fd-appearance";
5359
5358
  var DEFAULT_PREFS = {
5360
5359
  theme: "system",
@@ -5403,6 +5402,43 @@ function savePrefs(prefs, opts) {
5403
5402
  } catch {
5404
5403
  }
5405
5404
  }
5405
+ function createLocalStorageAppearanceStore(opts) {
5406
+ const key = opts?.storageKey ?? DEFAULT_STORAGE_KEY;
5407
+ const storage = resolveStorage(opts);
5408
+ let snapshot = loadPrefs(opts);
5409
+ const listeners3 = /* @__PURE__ */ new Set();
5410
+ const notify = () => listeners3.forEach((l) => l());
5411
+ const onStorageEvent = (e) => {
5412
+ if (e.key !== null && e.key !== key) return;
5413
+ snapshot = loadPrefs(opts);
5414
+ notify();
5415
+ };
5416
+ return {
5417
+ getSnapshot: () => snapshot,
5418
+ write(prefs) {
5419
+ snapshot = prefs;
5420
+ if (storage) {
5421
+ try {
5422
+ storage.setItem(key, JSON.stringify(prefs));
5423
+ } catch {
5424
+ }
5425
+ }
5426
+ notify();
5427
+ },
5428
+ subscribe(onChange) {
5429
+ listeners3.add(onChange);
5430
+ if (listeners3.size === 1 && typeof window !== "undefined") {
5431
+ window.addEventListener("storage", onStorageEvent);
5432
+ }
5433
+ return () => {
5434
+ listeners3.delete(onChange);
5435
+ if (listeners3.size === 0 && typeof window !== "undefined") {
5436
+ window.removeEventListener("storage", onStorageEvent);
5437
+ }
5438
+ };
5439
+ }
5440
+ };
5441
+ }
5406
5442
  function applyPrefs(prefs, root = document.documentElement) {
5407
5443
  root.setAttribute("data-theme", resolveTheme(prefs.theme));
5408
5444
  root.setAttribute("data-text-scale", prefs.textScale);
@@ -5411,13 +5447,12 @@ function applyPrefs(prefs, root = document.documentElement) {
5411
5447
  if (prefs.reduceMotion) root.setAttribute("data-motion", "reduce");
5412
5448
  else root.removeAttribute("data-motion");
5413
5449
  }
5414
- function useAppearancePrefs(opts) {
5415
- const [prefs, setPrefs] = React33.useState(() => loadPrefs(opts));
5416
- React33.useEffect(() => {
5450
+ function useAppearancePrefs(store) {
5451
+ const prefs = React34.useSyncExternalStore(store.subscribe, store.getSnapshot);
5452
+ React34.useEffect(() => {
5417
5453
  applyPrefs(prefs);
5418
- savePrefs(prefs, opts);
5419
5454
  }, [prefs]);
5420
- React33.useEffect(() => {
5455
+ React34.useEffect(() => {
5421
5456
  if (prefs.theme !== "system" || typeof window === "undefined" || typeof window.matchMedia !== "function") {
5422
5457
  return;
5423
5458
  }
@@ -5426,9 +5461,12 @@ function useAppearancePrefs(opts) {
5426
5461
  mq.addEventListener("change", onChange);
5427
5462
  return () => mq.removeEventListener("change", onChange);
5428
5463
  }, [prefs]);
5429
- const update = React33.useCallback((patch) => {
5430
- setPrefs((p) => ({ ...p, ...patch }));
5431
- }, []);
5464
+ const update = React34.useCallback(
5465
+ (patch) => {
5466
+ store.write({ ...store.getSnapshot(), ...patch });
5467
+ },
5468
+ [store]
5469
+ );
5432
5470
  return [prefs, update];
5433
5471
  }
5434
5472
 
@@ -5445,12 +5483,16 @@ var TEXT_SCALE_OPTIONS = [
5445
5483
  { value: "lg", label: "L" },
5446
5484
  { value: "xl", label: "XL" }
5447
5485
  ];
5448
- function AppearanceMenu({ className = "", ...storeOptions }) {
5449
- const [prefs, update] = useAppearancePrefs(storeOptions);
5450
- const [open, setOpen] = React34.useState(false);
5451
- const anchorRef = React34.useRef(null);
5486
+ function AppearanceMenu({ className = "", store: storeProp, storageKey, storage, defaults }) {
5487
+ const store = React35.useMemo(
5488
+ () => storeProp ?? createLocalStorageAppearanceStore({ storageKey, storage, defaults }),
5489
+ [storeProp, storageKey, storage, defaults]
5490
+ );
5491
+ const [prefs, update] = useAppearancePrefs(store);
5492
+ const [open, setOpen] = React35.useState(false);
5493
+ const anchorRef = React35.useRef(null);
5452
5494
  const patch = (p) => update(p);
5453
- return /* @__PURE__ */ jsxs55(React34.Fragment, { children: [
5495
+ return /* @__PURE__ */ jsxs55(React35.Fragment, { children: [
5454
5496
  /* @__PURE__ */ jsx59("span", { ref: anchorRef, className: ["fd-appearance-anchor", className].filter(Boolean).join(" "), style: { display: "inline-flex" }, children: /* @__PURE__ */ jsx59(
5455
5497
  IconButton,
5456
5498
  {
@@ -5519,14 +5561,14 @@ function AppearanceMenu({ className = "", ...storeOptions }) {
5519
5561
  }
5520
5562
 
5521
5563
  // src/components/platform/ApiSpecBrowser.tsx
5522
- import * as React35 from "react";
5564
+ import * as React36 from "react";
5523
5565
  import { jsx as jsx60, jsxs as jsxs56 } from "react/jsx-runtime";
5524
5566
  var METHOD_TONE = { GET: "success", POST: "info", PATCH: "warning", PUT: "warning", DELETE: "danger" };
5525
5567
  function Json({ obj }) {
5526
5568
  return /* @__PURE__ */ jsx60("pre", { className: "fd-json", children: JSON.stringify(obj, null, 2) });
5527
5569
  }
5528
5570
  function Endpoint({ s, onRequest }) {
5529
- const [tried, setTried] = React35.useState(null);
5571
+ const [tried, setTried] = React36.useState(null);
5530
5572
  const run = async () => {
5531
5573
  setTried("busy");
5532
5574
  const t0 = (window.performance || Date).now();
@@ -5599,10 +5641,10 @@ function ApiSpecBrowser({
5599
5641
  className = "",
5600
5642
  ...rest
5601
5643
  }) {
5602
- const [q, setQ] = React35.useState("");
5603
- const [method, setMethod] = React35.useState(null);
5604
- const [mod, setMod] = React35.useState(null);
5605
- const [listOnly, setListOnly] = React35.useState(false);
5644
+ const [q, setQ] = React36.useState("");
5645
+ const [method, setMethod] = React36.useState(null);
5646
+ const [mod, setMod] = React36.useState(null);
5647
+ const [listOnly, setListOnly] = React36.useState(false);
5606
5648
  const activeModule = modules && modules.find((m) => m.id === mod);
5607
5649
  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())));
5608
5650
  const effGroups = groups && groups.length ? groups : [["All endpoints", spec.map((s) => s.id)]];
@@ -5681,139 +5723,174 @@ function ApiSpecBrowser({
5681
5723
  }
5682
5724
 
5683
5725
  // src/components/platform/ProfilePage.tsx
5684
- import * as React36 from "react";
5726
+ import * as React37 from "react";
5685
5727
  import { jsx as jsx61, jsxs as jsxs57 } from "react/jsx-runtime";
5686
- var TIMEZONES = ["America/New_York", "America/Chicago", "America/Denver", "America/Los_Angeles", "America/Anchorage", "Pacific/Honolulu", "Europe/London", "Europe/Berlin"];
5687
- var NOTIFY = [
5688
- { key: "planShared", label: "A plan is shared with me", detail: "Someone sends you a plan or a client link." },
5689
- { key: "planChanged", label: "A plan I own changes", detail: "Budget moves, campuses added or removed." },
5690
- { key: "goalMissed", label: "The reviewer flags a missed criterion", detail: "After every solve, if a criterion is short." },
5691
- { key: "flagChanged", label: "A feature flag affecting me changes", detail: "Someone toggles a feature you can see." },
5692
- { key: "weekly", label: "Weekly digest", detail: "Monday summary of plan activity." }
5693
- ];
5694
- function ProfilePage({ user: userProp, onSave, onNavigate, sessions, showSessions = true, className = "", ...rest }) {
5728
+ function fieldInitialValue(field, user) {
5729
+ if (field.value) return field.value(user);
5730
+ const raw = user[field.key];
5731
+ return raw == null ? "" : String(raw);
5732
+ }
5733
+ function groupFields(fields) {
5734
+ const order = [];
5735
+ const byGroup = /* @__PURE__ */ new Map();
5736
+ for (const field of fields) {
5737
+ const group = field.group ?? "Identity";
5738
+ if (!byGroup.has(group)) {
5739
+ byGroup.set(group, { group, description: field.groupDescription, fields: [] });
5740
+ order.push(group);
5741
+ }
5742
+ const entry = byGroup.get(group);
5743
+ if (entry.description === void 0) entry.description = field.groupDescription;
5744
+ entry.fields.push(field);
5745
+ }
5746
+ return order.map((g) => byGroup.get(g));
5747
+ }
5748
+ function ProfilePage({
5749
+ user: userProp,
5750
+ fields = [],
5751
+ notifications = [],
5752
+ extra,
5753
+ onChangePhoto,
5754
+ onSave,
5755
+ onNavigate,
5756
+ sessions,
5757
+ onSignOutEverywhere,
5758
+ className = ""
5759
+ }) {
5695
5760
  const { user: sessionUser, roles, permissions, saveProfile: saveProfile2 } = useSessionContext();
5696
5761
  const user = userProp || sessionUser;
5697
- const [draft, setDraft] = React36.useState(() => ({
5698
- name: user.name || "",
5699
- title: user.title || "",
5700
- email: user.email || "",
5701
- phone: user.phone || "",
5702
- timezone: user.timezone || TIMEZONES[2],
5703
- bio: user.bio || "",
5704
- notify: user.notify || { planShared: true, planChanged: true, goalMissed: true, flagChanged: false, weekly: true }
5705
- }));
5706
- const [saving, setSaving] = React36.useState(false);
5707
- const [saved, setSaved] = React36.useState(false);
5708
- const set = (k, v) => {
5709
- setDraft((d) => Object.assign({}, d, { [k]: v }));
5762
+ const initialFieldValues = React37.useMemo(() => {
5763
+ const values2 = {};
5764
+ for (const field of fields) values2[field.key] = fieldInitialValue(field, user);
5765
+ return values2;
5766
+ }, [user.id, fields]);
5767
+ const initialNotify = React37.useMemo(() => {
5768
+ const values2 = {};
5769
+ for (const n of notifications) values2[n.key] = !!user.notify?.[n.key];
5770
+ return values2;
5771
+ }, [user.id, notifications]);
5772
+ const [values, setValues] = React37.useState(initialFieldValues);
5773
+ const [notify, setNotify] = React37.useState(initialNotify);
5774
+ const [saving, setSaving] = React37.useState(false);
5775
+ const [saved, setSaved] = React37.useState(false);
5776
+ const [prevUserId, setPrevUserId] = React37.useState(user.id);
5777
+ if (user.id !== prevUserId) {
5778
+ setPrevUserId(user.id);
5779
+ setValues(initialFieldValues);
5780
+ setNotify(initialNotify);
5781
+ setSaved(false);
5782
+ }
5783
+ const setField = (key, v) => {
5784
+ setValues((d) => ({ ...d, [key]: v }));
5710
5785
  setSaved(false);
5711
5786
  };
5712
- const setNotify = (k) => {
5713
- setDraft((d) => Object.assign({}, d, { notify: Object.assign({}, d.notify, { [k]: !d.notify[k] }) }));
5787
+ const toggleNotify = (key) => {
5788
+ setNotify((d) => ({ ...d, [key]: !d[key] }));
5714
5789
  setSaved(false);
5715
5790
  };
5716
- 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 });
5717
- const canSave = !!(onSave || saveProfile2);
5791
+ const dirty = fields.some((f) => values[f.key] !== initialFieldValues[f.key]) || notifications.some((n) => notify[n.key] !== initialNotify[n.key]);
5792
+ const canSave = !!(onSave || saveProfile2) && (fields.length > 0 || notifications.length > 0);
5718
5793
  const save = async () => {
5719
5794
  if (!canSave) return;
5720
5795
  setSaving(true);
5721
- if (onSave) await onSave(draft);
5722
- else await saveProfile2(draft);
5723
- setSaving(false);
5724
- setSaved(true);
5796
+ const draft = { ...values };
5797
+ if (notifications.length > 0) draft.notify = notify;
5798
+ try {
5799
+ if (onSave) await onSave(draft);
5800
+ else await saveProfile2(draft);
5801
+ setSaved(true);
5802
+ } finally {
5803
+ setSaving(false);
5804
+ }
5725
5805
  };
5726
- const liveSessions = sessions || [
5727
- { id: "s1", device: "MacBook Pro \xB7 Chrome", where: "Denver, CO", when: "Active now", current: true },
5728
- { id: "s2", device: "iPhone 15 \xB7 Safari", where: "Denver, CO", when: "2 hours ago" },
5729
- { id: "s3", device: "Windows \xB7 Edge", where: "Chicago, IL", when: "Aug 12" }
5730
- ];
5731
- return /* @__PURE__ */ jsxs57("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 20, maxWidth: 860 }, ...rest, children: [
5806
+ const discard = () => {
5807
+ setValues(initialFieldValues);
5808
+ setNotify(initialNotify);
5809
+ setSaved(false);
5810
+ };
5811
+ const displayName = fields.some((f) => f.key === "name") ? values.name || user.name : user.name;
5812
+ const fieldGroups = groupFields(fields);
5813
+ return /* @__PURE__ */ jsxs57("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 20, maxWidth: 860 }, children: [
5732
5814
  /* @__PURE__ */ jsxs57("div", { className: "fd-stack", style: { gap: 10 }, children: [
5733
5815
  /* @__PURE__ */ jsx61("span", { className: "fd-overline fd-muted", children: "Account" }),
5734
5816
  /* @__PURE__ */ jsx61("h1", { className: "fd-h1", style: { margin: 0 }, children: "Your profile" }),
5735
5817
  /* @__PURE__ */ jsx61("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." })
5736
5818
  ] }),
5737
5819
  /* @__PURE__ */ jsx61(Card, { elevation: "flat", children: /* @__PURE__ */ jsxs57("div", { className: "fd-row", style: { gap: 16, flexWrap: "wrap", alignItems: "flex-start" }, children: [
5738
- /* @__PURE__ */ jsx61(Avatar, { name: draft.name || user.name, size: "lg" }),
5820
+ /* @__PURE__ */ jsx61(Avatar, { name: displayName, size: "lg" }),
5739
5821
  /* @__PURE__ */ jsxs57("div", { className: "fd-stack", style: { gap: 4, flex: 1, minWidth: 200 }, children: [
5740
- /* @__PURE__ */ jsx61("span", { className: "fd-h3", style: { margin: 0 }, children: draft.name || user.name }),
5741
- /* @__PURE__ */ jsxs57("span", { className: "fd-body-sm fd-muted", children: [
5742
- draft.title || "No title set",
5743
- " \xB7 ",
5744
- user.team || "No team"
5745
- ] }),
5822
+ /* @__PURE__ */ jsx61("span", { className: "fd-h3", style: { margin: 0 }, children: displayName }),
5746
5823
  /* @__PURE__ */ jsxs57("span", { className: "fd-row", style: { gap: 6, flexWrap: "wrap", paddingTop: 4 }, children: [
5747
5824
  roles.map((r) => /* @__PURE__ */ jsx61(Badge, { tone: r.id === "owner" ? "success" : "neutral", children: r.name }, r.id)),
5748
5825
  user.sso ? /* @__PURE__ */ jsx61(Badge, { tone: "info", icon: "shield-check", children: "SSO" }) : null
5749
5826
  ] })
5750
5827
  ] }),
5751
- /* @__PURE__ */ jsx61(Button, { variant: "secondary", size: "sm", icon: "image", children: "Change photo" })
5828
+ onChangePhoto ? /* @__PURE__ */ jsx61(Button, { variant: "secondary", size: "sm", icon: "image", onClick: onChangePhoto, children: "Change photo" }) : null
5752
5829
  ] }) }),
5753
- /* @__PURE__ */ jsx61(
5830
+ fieldGroups.map(({ group, description, fields: groupFieldDefs }) => /* @__PURE__ */ jsx61(
5754
5831
  Card,
5755
5832
  {
5756
- title: /* @__PURE__ */ jsxs57("span", { className: "fd-stack", style: { gap: 2 }, children: [
5757
- /* @__PURE__ */ jsx61("span", { children: "Identity" }),
5758
- /* @__PURE__ */ jsx61("span", { className: "fd-body-sm fd-muted", style: { fontWeight: 400 }, children: "Name and title appear on plans you share" })
5759
- ] }),
5833
+ title: description ? /* @__PURE__ */ jsxs57("span", { className: "fd-stack", style: { gap: 2 }, children: [
5834
+ /* @__PURE__ */ jsx61("span", { children: group }),
5835
+ /* @__PURE__ */ jsx61("span", { className: "fd-body-sm fd-muted", style: { fontWeight: 400 }, children: description })
5836
+ ] }) : group,
5760
5837
  elevation: "flat",
5761
5838
  children: /* @__PURE__ */ jsxs57("div", { className: "fd-stack", style: { gap: 14 }, children: [
5762
- /* @__PURE__ */ jsxs57("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: [
5763
- /* @__PURE__ */ jsx61(Input, { label: "Full name", value: draft.name, onChange: (e) => set("name", e.target.value) }),
5764
- /* @__PURE__ */ jsx61(Input, { label: "Job title", value: draft.title, onChange: (e) => set("title", e.target.value), placeholder: "e.g. Senior media planner" })
5765
- ] }),
5766
- /* @__PURE__ */ jsxs57("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: [
5767
- /* @__PURE__ */ jsx61(
5839
+ /* @__PURE__ */ jsx61("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: groupFieldDefs.filter((f) => f.type !== "textarea").map((field) => {
5840
+ const help = typeof field.help === "function" ? field.help(user) : field.help;
5841
+ const readOnly = field.readOnly || field.type === "readonly";
5842
+ if (field.type === "select") {
5843
+ return /* @__PURE__ */ jsx61(
5844
+ Select,
5845
+ {
5846
+ label: field.label,
5847
+ value: values[field.key],
5848
+ onChange: readOnly ? void 0 : (e) => setField(field.key, e.target.value),
5849
+ options: field.options ?? [],
5850
+ placeholder: field.placeholder,
5851
+ help,
5852
+ disabled: readOnly
5853
+ },
5854
+ field.key
5855
+ );
5856
+ }
5857
+ return /* @__PURE__ */ jsx61(
5768
5858
  Input,
5769
5859
  {
5770
- label: "Work email",
5771
- value: draft.email,
5772
- readOnly: true,
5773
- disabled: true,
5774
- help: user.sso ? "Managed by your identity provider \u2014 change it there." : "Contact an administrator to change this."
5775
- }
5776
- ),
5777
- /* @__PURE__ */ jsx61(Input, { label: "Phone", value: draft.phone, onChange: (e) => set("phone", e.target.value), placeholder: "Optional" })
5778
- ] }),
5779
- /* @__PURE__ */ jsx61(
5780
- Textarea,
5781
- {
5782
- label: "Short bio",
5783
- value: draft.bio,
5784
- onChange: (e) => set("bio", e.target.value),
5785
- rows: 3,
5786
- placeholder: "Optional \u2014 shown to colleagues on shared plans.",
5787
- help: "Never shown to clients."
5788
- }
5789
- )
5860
+ label: field.label,
5861
+ value: values[field.key],
5862
+ onChange: readOnly ? void 0 : (e) => setField(field.key, e.target.value),
5863
+ readOnly,
5864
+ disabled: readOnly,
5865
+ placeholder: field.placeholder,
5866
+ help
5867
+ },
5868
+ field.key
5869
+ );
5870
+ }) }),
5871
+ groupFieldDefs.filter((f) => f.type === "textarea").map((field) => {
5872
+ const help = typeof field.help === "function" ? field.help(user) : field.help;
5873
+ return /* @__PURE__ */ jsx61(
5874
+ Textarea,
5875
+ {
5876
+ label: field.label,
5877
+ value: values[field.key],
5878
+ onChange: field.readOnly ? void 0 : (e) => setField(field.key, e.target.value),
5879
+ readOnly: field.readOnly,
5880
+ disabled: field.readOnly,
5881
+ rows: field.rows ?? 3,
5882
+ placeholder: field.placeholder,
5883
+ help
5884
+ },
5885
+ field.key
5886
+ );
5887
+ })
5790
5888
  ] })
5791
- }
5792
- ),
5793
- /* @__PURE__ */ jsx61(Card, { title: "Working preferences", elevation: "flat", children: /* @__PURE__ */ jsxs57("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: [
5794
- /* @__PURE__ */ jsx61(
5795
- Select,
5796
- {
5797
- label: "Time zone",
5798
- value: draft.timezone,
5799
- onChange: (e) => set("timezone", e.target.value),
5800
- options: TIMEZONES,
5801
- help: "Flight dates and schedules render in this zone."
5802
- }
5803
- ),
5804
- /* @__PURE__ */ jsx61(Select, { label: "Start of week", options: ["Monday", "Sunday"], placeholder: "Monday" })
5805
- ] }) }),
5806
- /* @__PURE__ */ jsx61(
5807
- Card,
5808
- {
5809
- title: /* @__PURE__ */ jsxs57("span", { className: "fd-stack", style: { gap: 2 }, children: [
5810
- /* @__PURE__ */ jsx61("span", { children: "Notifications" }),
5811
- /* @__PURE__ */ jsx61("span", { className: "fd-body-sm fd-muted", style: { fontWeight: 400 }, children: "Email only for now \u2014 in-app notifications are on the roadmap" })
5812
- ] }),
5813
- elevation: "flat",
5814
- children: /* @__PURE__ */ jsx61("div", { className: "fd-stack", style: { gap: 14 }, children: NOTIFY.map((n) => /* @__PURE__ */ jsx61(Switch, { checked: !!draft.notify[n.key], onChange: () => setNotify(n.key), label: n.label, description: n.detail }, n.key)) })
5815
- }
5816
- ),
5889
+ },
5890
+ group
5891
+ )),
5892
+ extra,
5893
+ notifications.length > 0 ? /* @__PURE__ */ jsx61(Card, { title: "Notifications", elevation: "flat", children: /* @__PURE__ */ jsx61("div", { className: "fd-stack", style: { gap: 14 }, children: notifications.map((n) => /* @__PURE__ */ jsx61(Switch, { checked: !!notify[n.key], onChange: () => toggleNotify(n.key), label: n.label, description: n.description }, n.key)) }) }) : null,
5817
5894
  /* @__PURE__ */ jsxs57(
5818
5895
  Card,
5819
5896
  {
@@ -5845,13 +5922,13 @@ function ProfilePage({ user: userProp, onSave, onNavigate, sessions, showSession
5845
5922
  ]
5846
5923
  }
5847
5924
  ),
5848
- showSessions ? /* @__PURE__ */ jsx61(
5925
+ sessions ? /* @__PURE__ */ jsx61(
5849
5926
  Card,
5850
5927
  {
5851
5928
  title: "Signed-in devices",
5852
5929
  elevation: "flat",
5853
- action: /* @__PURE__ */ jsx61(Button, { size: "sm", variant: "ghost", icon: "sign-out", children: "Sign out everywhere" }),
5854
- children: /* @__PURE__ */ jsx61("div", { className: "fd-stack", style: { gap: 0 }, children: liveSessions.map((s) => /* @__PURE__ */ jsxs57("div", { className: "fd-row", style: { gap: 12, padding: "10px 0", borderTop: "1px solid var(--border)", flexWrap: "wrap" }, children: [
5930
+ action: /* @__PURE__ */ jsx61(Button, { size: "sm", variant: "ghost", icon: "sign-out", onClick: onSignOutEverywhere, children: "Sign out everywhere" }),
5931
+ children: /* @__PURE__ */ jsx61("div", { className: "fd-stack", style: { gap: 0 }, children: sessions.length === 0 ? /* @__PURE__ */ jsx61("p", { className: "fd-body-sm fd-muted", style: { margin: 0 }, children: "No other signed-in devices." }) : sessions.map((s) => /* @__PURE__ */ jsxs57("div", { className: "fd-row", style: { gap: 12, padding: "10px 0", borderTop: "1px solid var(--border)", flexWrap: "wrap" }, children: [
5855
5932
  /* @__PURE__ */ jsx61("i", { className: "ph ph-device-mobile", style: { fontSize: 16, color: "var(--text-muted)" }, "aria-hidden": "true" }),
5856
5933
  /* @__PURE__ */ jsxs57("span", { className: "fd-stack", style: { gap: 1, flex: 1, minWidth: 160 }, children: [
5857
5934
  /* @__PURE__ */ jsx61("span", { className: "fd-body-sm", style: { fontWeight: 600 }, children: s.device }),
@@ -5877,29 +5954,17 @@ function ProfilePage({ user: userProp, onSave, onNavigate, sessions, showSession
5877
5954
  boxShadow: "0 -4px 16px rgba(11,13,17,.06)"
5878
5955
  }, children: [
5879
5956
  /* @__PURE__ */ jsx61("span", { className: "fd-body-sm", style: { fontWeight: 600, flex: 1 }, children: saved ? "Saved." : dirty ? "Unsaved changes" : "No pending changes" }),
5880
- /* @__PURE__ */ jsx61(
5881
- Button,
5882
- {
5883
- size: "sm",
5884
- variant: "ghost",
5885
- disabled: !dirty || saving,
5886
- onClick: () => {
5887
- 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 } });
5888
- setSaved(false);
5889
- },
5890
- children: "Discard"
5891
- }
5892
- ),
5957
+ /* @__PURE__ */ jsx61(Button, { size: "sm", variant: "ghost", disabled: !dirty || saving, onClick: discard, children: "Discard" }),
5893
5958
  /* @__PURE__ */ jsx61(Button, { size: "sm", icon: "check", disabled: !dirty || !canSave, loading: saving, onClick: save, children: "Save changes" })
5894
5959
  ] })
5895
5960
  ] });
5896
5961
  }
5897
5962
 
5898
5963
  // src/components/platform/RoadmapTimeline.tsx
5899
- import * as React39 from "react";
5964
+ import * as React40 from "react";
5900
5965
 
5901
5966
  // src/kits/session.ts
5902
- import * as React37 from "react";
5967
+ import * as React38 from "react";
5903
5968
  var PERMISSION_CATALOG = [
5904
5969
  { group: "Plans", items: [
5905
5970
  { key: "plan.view", label: "View plans", detail: "Read any plan in the workspace." },
@@ -7466,8 +7531,8 @@ function roadmap(overrides) {
7466
7531
  };
7467
7532
  }
7468
7533
  function useSession() {
7469
- const [s, setS] = React37.useState(getSession);
7470
- React37.useEffect(() => subscribe(setS), []);
7534
+ const [s, setS] = React38.useState(getSession);
7535
+ React38.useEffect(() => subscribe(setS), []);
7471
7536
  return s;
7472
7537
  }
7473
7538
  var SessionKit = {
@@ -7500,7 +7565,7 @@ var SessionKit = {
7500
7565
  };
7501
7566
  function DemoSessionProvider({ children }) {
7502
7567
  const session = useSession();
7503
- const value = React37.useMemo(
7568
+ const value = React38.useMemo(
7504
7569
  () => ({
7505
7570
  user: session.user,
7506
7571
  roles: session.roles,
@@ -7516,11 +7581,11 @@ function DemoSessionProvider({ children }) {
7516
7581
  }),
7517
7582
  [session]
7518
7583
  );
7519
- return React37.createElement(SessionProvider, { value }, children);
7584
+ return React38.createElement(SessionProvider, { value }, children);
7520
7585
  }
7521
7586
 
7522
7587
  // src/kits/runtime.ts
7523
- import * as React38 from "react";
7588
+ import * as React39 from "react";
7524
7589
  var WIRED_ENDPOINTS = [
7525
7590
  // Nothing yet. Every id below would come from a real service:
7526
7591
  // "plan.get", "placements.list", …
@@ -7691,8 +7756,8 @@ var RuntimeKit = {
7691
7756
  };
7692
7757
  RuntimeKit.declare(FEATURE_NEEDS);
7693
7758
  function useRuntimeMode() {
7694
- const [m, setM] = React38.useState(RuntimeKit.getMode());
7695
- React38.useEffect(() => RuntimeKit.subscribe(setM), []);
7759
+ const [m, setM] = React39.useState(RuntimeKit.getMode());
7760
+ React39.useEffect(() => RuntimeKit.subscribe(setM), []);
7696
7761
  return m;
7697
7762
  }
7698
7763
  function useFeatureStatus(key) {
@@ -7775,12 +7840,12 @@ function RoadmapCard({ item, byKey, onOpenFlag }) {
7775
7840
  }
7776
7841
  function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
7777
7842
  const session = SessionKit.useSession();
7778
- const [project, setProject] = React39.useState("");
7779
- const [q, setQ] = React39.useState("");
7780
- const scrollRef = React39.useRef(null);
7781
- const nowRef = React39.useRef(null);
7782
- const rm = React39.useMemo(() => SessionKit.roadmap({ isComplete: RuntimeKit.isComplete }), [session]);
7783
- const byKey = React39.useMemo(() => {
7843
+ const [project, setProject] = React40.useState("");
7844
+ const [q, setQ] = React40.useState("");
7845
+ const scrollRef = React40.useRef(null);
7846
+ const nowRef = React40.useRef(null);
7847
+ const rm = React40.useMemo(() => SessionKit.roadmap({ isComplete: RuntimeKit.isComplete }), [session]);
7848
+ const byKey = React40.useMemo(() => {
7784
7849
  const m = {};
7785
7850
  rm.items.forEach((i) => {
7786
7851
  m[i.key] = i;
@@ -7789,7 +7854,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
7789
7854
  }, [rm]);
7790
7855
  const items = rm.items.filter((i) => (!project || i.project === project) && (!q || (i.label + " " + i.description + " " + (i.backend || "") + " " + i.key).toLowerCase().includes(q.toLowerCase())));
7791
7856
  const projects = [...new Set(rm.items.map((i) => i.project))];
7792
- React39.useEffect(() => {
7857
+ React40.useEffect(() => {
7793
7858
  let raf1 = 0, raf2 = 0;
7794
7859
  const place = () => {
7795
7860
  const box = scrollRef.current, mark = nowRef.current;
@@ -7863,7 +7928,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
7863
7928
  lastPhase = item.phase;
7864
7929
  const inPhase = items.filter((i) => i.phase === item.phase).length;
7865
7930
  const isBoundary = n === firstPending;
7866
- return /* @__PURE__ */ jsxs58(React39.Fragment, { children: [
7931
+ return /* @__PURE__ */ jsxs58(React40.Fragment, { children: [
7867
7932
  showPhase ? /* @__PURE__ */ jsxs58("div", { className: "fd-rm-era", children: [
7868
7933
  /* @__PURE__ */ jsxs58("span", { className: "fd-row", style: { gap: 8, flexWrap: "wrap", alignItems: "baseline" }, children: [
7869
7934
  /* @__PURE__ */ jsx62("span", { style: { fontWeight: 700 }, children: item.phase === 0 ? "Shipped" : "Phase " + item.phase + " \u2014 " + item.phaseName }),
@@ -7907,7 +7972,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
7907
7972
  }
7908
7973
 
7909
7974
  // src/components/platform/ComingSoon.tsx
7910
- import * as React40 from "react";
7975
+ import * as React41 from "react";
7911
7976
  import { createPortal as createPortal5 } from "react-dom";
7912
7977
  import { Fragment as Fragment18, jsx as jsx63, jsxs as jsxs59 } from "react/jsx-runtime";
7913
7978
  var BYPASS_STORE = "fd.soon.bypass.v1";
@@ -7925,8 +7990,8 @@ function writeBypassed(list) {
7925
7990
  }
7926
7991
  }
7927
7992
  function useBypass(key) {
7928
- const [on, setOn] = React40.useState(() => !!key && readBypassed().indexOf(key) >= 0);
7929
- React40.useEffect(() => {
7993
+ const [on, setOn] = React41.useState(() => !!key && readBypassed().indexOf(key) >= 0);
7994
+ React41.useEffect(() => {
7930
7995
  setOn(!!key && readBypassed().indexOf(key) >= 0);
7931
7996
  }, [key]);
7932
7997
  const set = (next) => {
@@ -7973,9 +8038,9 @@ function SoonCard({ label, detail, backend, eta, effort, onRoadmap, allowed, onB
7973
8038
  ] });
7974
8039
  }
7975
8040
  function useHoverCard(open) {
7976
- const anchor = React40.useRef(null);
7977
- const [pos, setPos] = React40.useState(null);
7978
- React40.useLayoutEffect(() => {
8041
+ const anchor = React41.useRef(null);
8042
+ const [pos, setPos] = React41.useState(null);
8043
+ React41.useLayoutEffect(() => {
7979
8044
  if (!open || !anchor.current) {
7980
8045
  setPos(null);
7981
8046
  return;
@@ -8102,10 +8167,10 @@ function ComingSoon({
8102
8167
  ] });
8103
8168
  }
8104
8169
  function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, onBypass, blur, tip, className, rest, children }) {
8105
- const [open, setOpen] = React40.useState(false);
8170
+ const [open, setOpen] = React41.useState(false);
8106
8171
  const [anchor, pos] = useHoverCard(open);
8107
8172
  const zIndex = useOverlayLayer(open);
8108
- const close = React40.useRef(null);
8173
+ const close = React41.useRef(null);
8109
8174
  const show = () => {
8110
8175
  if (close.current) {
8111
8176
  clearTimeout(close.current);
@@ -8121,7 +8186,7 @@ function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, o
8121
8186
  setOpen(false);
8122
8187
  }, 140);
8123
8188
  };
8124
- React40.useEffect(() => () => {
8189
+ React41.useEffect(() => () => {
8125
8190
  if (close.current) clearTimeout(close.current);
8126
8191
  }, []);
8127
8192
  return /* @__PURE__ */ jsxs59(
@@ -8292,14 +8357,14 @@ function PermissionHint({ perm, children }) {
8292
8357
  }
8293
8358
 
8294
8359
  // src/components/platform/ModeSwitch.tsx
8295
- import * as React41 from "react";
8360
+ import * as React42 from "react";
8296
8361
  import { Fragment as Fragment19, jsx as jsx65, jsxs as jsxs61 } from "react/jsx-runtime";
8297
8362
  function ModeSwitch({ canToggle = false, requestCount = 0, onClearLog, renderLog, onOpenSpec, summary }) {
8298
8363
  const mode2 = useRuntimeMode();
8299
- const [open, setOpen] = React41.useState(false);
8364
+ const [open, setOpen] = React42.useState(false);
8300
8365
  const zIndex = useOverlayLayer(open);
8301
- const ref = React41.useRef(null);
8302
- React41.useEffect(() => {
8366
+ const ref = React42.useRef(null);
8367
+ React42.useEffect(() => {
8303
8368
  if (!open) return;
8304
8369
  const away = (e) => {
8305
8370
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -8555,7 +8620,7 @@ function SaturationDistribution({ campuses = [], floorBand, floorScore, onSelect
8555
8620
  }
8556
8621
 
8557
8622
  // src/components/planner/MixGap.tsx
8558
- import * as React42 from "react";
8623
+ import * as React43 from "react";
8559
8624
  import { Fragment as Fragment20, jsx as jsx68, jsxs as jsxs64 } from "react/jsx-runtime";
8560
8625
  var MIX_GAP_LAYOUT = {
8561
8626
  trackHeight: 30,
@@ -8570,7 +8635,7 @@ function mixGapOffset(value, max) {
8570
8635
  }
8571
8636
  function MixGap({ rows = [], loading = false, className = "" }) {
8572
8637
  const max = Math.max(1, ...rows.flatMap((r) => [r.target, r.realized]));
8573
- const [hover, setHover] = React42.useState(null);
8638
+ const [hover, setHover] = React43.useState(null);
8574
8639
  const toneOf = (gap) => gap >= -1 ? "ok" : gap >= -4 ? "warn" : "danger";
8575
8640
  const TONE = { ok: "var(--ok-solid)", warn: "var(--warn-solid)", danger: "var(--danger-solid)" };
8576
8641
  const { trackHeight, barHeight, targetWidth, labelGap } = MIX_GAP_LAYOUT;
@@ -8713,7 +8778,7 @@ function ChannelContribution({
8713
8778
  }
8714
8779
 
8715
8780
  // src/components/planner/BudgetReallocator.tsx
8716
- import * as React43 from "react";
8781
+ import * as React44 from "react";
8717
8782
  import { Fragment as Fragment21, jsx as jsx70, jsxs as jsxs66 } from "react/jsx-runtime";
8718
8783
  var bandFor = (crp) => crp >= 200 ? "dominant" : crp >= 100 ? "strong" : crp >= 50 ? "adequate" : "weak";
8719
8784
  function BudgetReallocator({
@@ -8729,8 +8794,8 @@ function BudgetReallocator({
8729
8794
  onCancel,
8730
8795
  className = ""
8731
8796
  }) {
8732
- const [draft, setDraft] = React43.useState(spend);
8733
- React43.useEffect(() => setDraft(spend), [spend]);
8797
+ const [draft, setDraft] = React44.useState(spend);
8798
+ React44.useEffect(() => setDraft(spend), [spend]);
8734
8799
  const dirty = draft !== spend;
8735
8800
  const nextCrp = scoreFor ? scoreFor(draft) : crp;
8736
8801
  const nextBand = bandFor(nextCrp);
@@ -8745,10 +8810,10 @@ function BudgetReallocator({
8745
8810
  setDraft(spend);
8746
8811
  if (onCancel) onCancel();
8747
8812
  };
8748
- const trackRef = React43.useRef(null);
8749
- const chipRef = React43.useRef(null);
8750
- const [chipLeft, setChipLeft] = React43.useState(null);
8751
- React43.useLayoutEffect(() => {
8813
+ const trackRef = React44.useRef(null);
8814
+ const chipRef = React44.useRef(null);
8815
+ const [chipLeft, setChipLeft] = React44.useState(null);
8816
+ React44.useLayoutEffect(() => {
8752
8817
  const track = trackRef.current;
8753
8818
  if (!dirty || !track) {
8754
8819
  setChipLeft(null);
@@ -8935,7 +9000,7 @@ function ImportanceTag({ level, showLabel = true, size = "sm", className = "" })
8935
9000
  }
8936
9001
 
8937
9002
  // src/components/planner/PreferenceMeter.tsx
8938
- import * as React44 from "react";
9003
+ import * as React45 from "react";
8939
9004
  import { jsx as jsx73, jsxs as jsxs69 } from "react/jsx-runtime";
8940
9005
  function preferenceScore(criteria) {
8941
9006
  const earned = criteria.reduce((n, c) => n + (Number(c.earned) || 0), 0);
@@ -8965,7 +9030,7 @@ function PreferenceMeter({
8965
9030
  className = ""
8966
9031
  }) {
8967
9032
  const value = score == null ? preferenceScore(criteria) : score;
8968
- const segments = React44.useMemo(() => preferenceSegments(criteria), [criteria]);
9033
+ const segments = React45.useMemo(() => preferenceSegments(criteria), [criteria]);
8969
9034
  return /* @__PURE__ */ jsx73(
8970
9035
  ScoreMeter,
8971
9036
  {
@@ -9004,10 +9069,10 @@ function PreferenceMeter({
9004
9069
  }
9005
9070
 
9006
9071
  // src/components/chat/AgentChatPanel.tsx
9007
- import * as React50 from "react";
9072
+ import * as React51 from "react";
9008
9073
 
9009
9074
  // src/components/chat/chatEngine.ts
9010
- import * as React45 from "react";
9075
+ import * as React46 from "react";
9011
9076
  var CHAT_UNAVAILABLE = "chat_unavailable";
9012
9077
  var JOB_PENDING = ["queued", "running"];
9013
9078
  var JOB_SUCCESS = ["completed", "recovered"];
@@ -9061,22 +9126,22 @@ function useChatEngine(opts) {
9061
9126
  onClear,
9062
9127
  onFeedback
9063
9128
  } = opts || {};
9064
- const [status, setStatus] = React45.useState("idle");
9065
- const [threadId, setThreadId] = React45.useState(null);
9066
- const [messages, setMessages] = React45.useState([]);
9067
- const [queue, setQueue] = React45.useState([]);
9068
- const [fatal, setFatal] = React45.useState(null);
9069
- const [busy, setBusy] = React45.useState(false);
9070
- const [turnStartedAt, setTurnStartedAt] = React45.useState(null);
9071
- const listRef = React45.useRef([]);
9072
- const queueRef = React45.useRef([]);
9073
- const busyRef = React45.useRef(false);
9074
- const stoppedRef = React45.useRef(false);
9075
- const abortRef = React45.useRef(null);
9076
- const serverCount = React45.useRef(0);
9077
- const threadRef = React45.useRef(null);
9078
- const mounted = React45.useRef(true);
9079
- React45.useEffect(() => {
9129
+ const [status, setStatus] = React46.useState("idle");
9130
+ const [threadId, setThreadId] = React46.useState(null);
9131
+ const [messages, setMessages] = React46.useState([]);
9132
+ const [queue, setQueue] = React46.useState([]);
9133
+ const [fatal, setFatal] = React46.useState(null);
9134
+ const [busy, setBusy] = React46.useState(false);
9135
+ const [turnStartedAt, setTurnStartedAt] = React46.useState(null);
9136
+ const listRef = React46.useRef([]);
9137
+ const queueRef = React46.useRef([]);
9138
+ const busyRef = React46.useRef(false);
9139
+ const stoppedRef = React46.useRef(false);
9140
+ const abortRef = React46.useRef(null);
9141
+ const serverCount = React46.useRef(0);
9142
+ const threadRef = React46.useRef(null);
9143
+ const mounted = React46.useRef(true);
9144
+ React46.useEffect(() => {
9080
9145
  mounted.current = true;
9081
9146
  return () => {
9082
9147
  mounted.current = false;
@@ -9098,14 +9163,14 @@ function useChatEngine(opts) {
9098
9163
  }
9099
9164
  return false;
9100
9165
  };
9101
- const loadThread = React45.useCallback(async (id) => {
9166
+ const loadThread = React46.useCallback(async (id) => {
9102
9167
  const data = await apiAdapter.getThread(id);
9103
9168
  const list = [...data && data.messages || []];
9104
9169
  serverCount.current = list.length;
9105
9170
  commit(list);
9106
9171
  return list;
9107
9172
  }, [apiAdapter]);
9108
- React45.useEffect(() => {
9173
+ React46.useEffect(() => {
9109
9174
  if (!apiAdapter) {
9110
9175
  setStatus("idle");
9111
9176
  setFatal(null);
@@ -9318,7 +9383,7 @@ function useChatEngine(opts) {
9318
9383
  await dispatchTurn(turn);
9319
9384
  }
9320
9385
  }
9321
- const send = React45.useCallback((text, attachments) => {
9386
+ const send = React46.useCallback((text, attachments) => {
9322
9387
  const body = (text || "").trim();
9323
9388
  if (!body && !(attachments && attachments.length)) return;
9324
9389
  if (status === "disconnected") return;
@@ -9328,7 +9393,7 @@ function useChatEngine(opts) {
9328
9393
  stoppedRef.current = false;
9329
9394
  drain();
9330
9395
  }, [status]);
9331
- const stop = React45.useCallback(() => {
9396
+ const stop = React46.useCallback(() => {
9332
9397
  stoppedRef.current = true;
9333
9398
  const ac = abortRef.current;
9334
9399
  if (ac) {
@@ -9347,11 +9412,11 @@ function useChatEngine(opts) {
9347
9412
  store.del(STORAGE_PREFIX + threadRef.current);
9348
9413
  }
9349
9414
  }, [apiAdapter]);
9350
- const removeQueued = React45.useCallback((id) => {
9415
+ const removeQueued = React46.useCallback((id) => {
9351
9416
  queueRef.current = queueRef.current.filter((t) => t.id !== id);
9352
9417
  setQueue(queueRef.current.slice());
9353
9418
  }, []);
9354
- const retry = React45.useCallback(() => {
9419
+ const retry = React46.useCallback(() => {
9355
9420
  const list = listRef.current;
9356
9421
  let at = -1;
9357
9422
  for (let i = list.length - 1; i >= 0; i--) if (list[i].role === "user") {
@@ -9367,19 +9432,19 @@ function useChatEngine(opts) {
9367
9432
  setQueue(queueRef.current.slice());
9368
9433
  drain();
9369
9434
  }, []);
9370
- const clear = React45.useCallback(() => {
9435
+ const clear = React46.useCallback(() => {
9371
9436
  commit([]);
9372
9437
  serverCount.current = 0;
9373
9438
  queueRef.current = [];
9374
9439
  setQueue([]);
9375
9440
  onClear && onClear();
9376
9441
  }, [onClear]);
9377
- const setFeedback = React45.useCallback((id, value) => {
9442
+ const setFeedback = React46.useCallback((id, value) => {
9378
9443
  patch(id, (m) => ({ feedback: m.feedback === value ? null : value }));
9379
9444
  const msg = listRef.current.find((m) => m.id === id);
9380
9445
  onFeedback && onFeedback({ message: msg, feedback: msg ? msg.feedback : value });
9381
9446
  }, [onFeedback]);
9382
- const reload = React45.useCallback(async () => {
9447
+ const reload = React46.useCallback(async () => {
9383
9448
  if (!threadRef.current) return;
9384
9449
  setStatus("loading");
9385
9450
  try {
@@ -9417,10 +9482,10 @@ var ChatKit = {
9417
9482
  };
9418
9483
 
9419
9484
  // src/components/chat/ChatTranscript.tsx
9420
- import * as React47 from "react";
9485
+ import * as React48 from "react";
9421
9486
 
9422
9487
  // src/components/chat/ChatTurn.tsx
9423
- import * as React46 from "react";
9488
+ import * as React47 from "react";
9424
9489
  import { jsx as jsx74, jsxs as jsxs70 } from "react/jsx-runtime";
9425
9490
  function JsonView({ value }) {
9426
9491
  let text;
@@ -9457,7 +9522,7 @@ function PacketCard({ packet, schema, render, onApply, applied }) {
9457
9522
  ] });
9458
9523
  }
9459
9524
  function ThinkingBlock({ text, durationMs, streaming, defaultOpen = false }) {
9460
- const [open, setOpen] = React46.useState(defaultOpen);
9525
+ const [open, setOpen] = React47.useState(defaultOpen);
9461
9526
  if (!text) return null;
9462
9527
  return /* @__PURE__ */ jsxs70("div", { className: "fdc-think" + (open ? " is-open" : ""), children: [
9463
9528
  /* @__PURE__ */ jsxs70("button", { type: "button", className: "fdc-think-head", onClick: () => setOpen((v) => !v), "aria-expanded": open, children: [
@@ -9474,7 +9539,7 @@ function ThinkingBlock({ text, durationMs, streaming, defaultOpen = false }) {
9474
9539
  ] });
9475
9540
  }
9476
9541
  function Citations({ items = [], onOpen }) {
9477
- const [open, setOpen] = React46.useState(false);
9542
+ const [open, setOpen] = React47.useState(false);
9478
9543
  if (!items.length) return null;
9479
9544
  return /* @__PURE__ */ jsxs70("div", { className: "fdc-cites", children: [
9480
9545
  /* @__PURE__ */ jsxs70("button", { type: "button", className: "fdc-cites-head", onClick: () => setOpen((v) => !v), "aria-expanded": open, children: [
@@ -9500,7 +9565,7 @@ function clampText(text, max) {
9500
9565
  return (at > max * 0.6 ? cut.slice(0, at) : cut).trimEnd() + "\u2026";
9501
9566
  }
9502
9567
  function MessageBody({ message: m, ctx }) {
9503
- const [expanded, setExpanded] = React46.useState(false);
9568
+ const [expanded, setExpanded] = React47.useState(false);
9504
9569
  const isUser = m.role === "user";
9505
9570
  const raw = m.text || "";
9506
9571
  const clamped = !expanded && !m.streaming ? clampText(raw, ctx.maxVisibleChars) : null;
@@ -9567,9 +9632,9 @@ function MessageBody({ message: m, ctx }) {
9567
9632
  ] });
9568
9633
  }
9569
9634
  function useCopyRun() {
9570
- const [done, setDone] = React46.useState(false);
9571
- const t = React46.useRef(null);
9572
- React46.useEffect(() => () => {
9635
+ const [done, setDone] = React47.useState(false);
9636
+ const t = React47.useRef(null);
9637
+ React47.useEffect(() => () => {
9573
9638
  if (t.current) clearTimeout(t.current);
9574
9639
  }, []);
9575
9640
  return [done, (text) => {
@@ -9596,7 +9661,7 @@ function RunActions({ group, ctx }) {
9596
9661
  ] }),
9597
9662
  isAssistant && ctx.onRetry ? /* @__PURE__ */ jsx74("button", { type: "button", className: "fdc-act", onClick: ctx.onRetry, "aria-label": "Retry this turn", children: /* @__PURE__ */ jsx74("i", { className: "ph ph-arrow-clockwise", "aria-hidden": "true" }) }) : null,
9598
9663
  group.role === "user" && ctx.onEdit ? /* @__PURE__ */ jsx74("button", { type: "button", className: "fdc-act", onClick: () => ctx.onEdit(last), "aria-label": "Edit and resend", children: /* @__PURE__ */ jsx74("i", { className: "ph ph-pencil-simple", "aria-hidden": "true" }) }) : null,
9599
- isAssistant && ctx.onFeedback ? /* @__PURE__ */ jsxs70(React46.Fragment, { children: [
9664
+ isAssistant && ctx.onFeedback ? /* @__PURE__ */ jsxs70(React47.Fragment, { children: [
9600
9665
  /* @__PURE__ */ jsx74("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__ */ jsx74("i", { className: "ph ph-thumbs-up", "aria-hidden": "true" }) }),
9601
9666
  /* @__PURE__ */ jsx74("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__ */ jsx74("i", { className: "ph ph-thumbs-down", "aria-hidden": "true" }) })
9602
9667
  ] }) : null,
@@ -9691,10 +9756,10 @@ function ChatTranscript({
9691
9756
  renderEmpty,
9692
9757
  className = ""
9693
9758
  }) {
9694
- const scroller = React47.useRef(null);
9695
- const stick = React47.useRef(true);
9696
- const [pill, setPill] = React47.useState(0);
9697
- const seen = React47.useRef(0);
9759
+ const scroller = React48.useRef(null);
9760
+ const stick = React48.useRef(true);
9761
+ const [pill, setPill] = React48.useState(0);
9762
+ const seen = React48.useRef(0);
9698
9763
  const toBottom = (smooth) => {
9699
9764
  const el = scroller.current;
9700
9765
  if (!el) return;
@@ -9713,7 +9778,7 @@ function ChatTranscript({
9713
9778
  seen.current = messages.length;
9714
9779
  }
9715
9780
  };
9716
- React47.useLayoutEffect(() => {
9781
+ React48.useLayoutEffect(() => {
9717
9782
  const el = scroller.current;
9718
9783
  if (!el) return;
9719
9784
  if (stick.current) {
@@ -9721,7 +9786,7 @@ function ChatTranscript({
9721
9786
  seen.current = messages.length;
9722
9787
  } else setPill(Math.max(0, messages.length - seen.current));
9723
9788
  }, [messages]);
9724
- React47.useEffect(() => {
9789
+ React48.useEffect(() => {
9725
9790
  const el = scroller.current;
9726
9791
  const inner = el && el.firstChild;
9727
9792
  if (!el || !inner || typeof ResizeObserver === "undefined") return;
@@ -9731,7 +9796,7 @@ function ChatTranscript({
9731
9796
  ro.observe(inner);
9732
9797
  return () => ro.disconnect();
9733
9798
  }, []);
9734
- const groups = React47.useMemo(() => groupMessages(messages), [messages]);
9799
+ const groups = React48.useMemo(() => groupMessages(messages), [messages]);
9735
9800
  const empty = !messages.length && status === "ready";
9736
9801
  return /* @__PURE__ */ jsxs71("div", { className: ["fdc-scroll", className].filter(Boolean).join(" "), ref: scroller, onScroll, children: [
9737
9802
  /* @__PURE__ */ jsxs71("div", { className: "fdc-log", role: "log", "aria-label": "Conversation", children: [
@@ -9755,7 +9820,7 @@ function ChatTranscript({
9755
9820
  const prev = groups[i - 1];
9756
9821
  const k = dayKey(g.messages[0].timestamp);
9757
9822
  const showDay = !!k && (!prev || dayKey(prev.messages[0].timestamp) !== k);
9758
- return /* @__PURE__ */ jsxs71(React47.Fragment, { children: [
9823
+ return /* @__PURE__ */ jsxs71(React48.Fragment, { children: [
9759
9824
  showDay ? /* @__PURE__ */ jsx75("div", { className: "fdc-day", children: /* @__PURE__ */ jsx75("span", { children: dayLabel(k) }) }) : null,
9760
9825
  /* @__PURE__ */ jsx75(ChatTurn, { group: g, ctx })
9761
9826
  ] }, g.key || i);
@@ -9772,7 +9837,7 @@ function ChatTranscript({
9772
9837
  var TranscriptKit = { groupMessages };
9773
9838
 
9774
9839
  // src/components/chat/ChatComposer.tsx
9775
- import * as React48 from "react";
9840
+ import * as React49 from "react";
9776
9841
  import { jsx as jsx76, jsxs as jsxs72 } from "react/jsx-runtime";
9777
9842
  function ChatComposer({
9778
9843
  onSubmit,
@@ -9800,17 +9865,17 @@ function ChatComposer({
9800
9865
  onReject,
9801
9866
  onOpenAttachment
9802
9867
  }) {
9803
- const [text, setText] = React48.useState(draft || "");
9804
- const [trigger, setTrigger] = React48.useState(null);
9805
- const [mentionItems, setMentionItems] = React48.useState([]);
9806
- const [listening, setListening] = React48.useState(false);
9807
- const [notice, setNotice] = React48.useState(null);
9808
- const editor = React48.useRef(null);
9809
- const wrap = React48.useRef(null);
9810
- const stopVoice = React48.useRef(null);
9868
+ const [text, setText] = React49.useState(draft || "");
9869
+ const [trigger, setTrigger] = React49.useState(null);
9870
+ const [mentionItems, setMentionItems] = React49.useState([]);
9871
+ const [listening, setListening] = React49.useState(false);
9872
+ const [notice, setNotice] = React49.useState(null);
9873
+ const editor = React49.useRef(null);
9874
+ const wrap = React49.useRef(null);
9875
+ const stopVoice = React49.useRef(null);
9811
9876
  const staged = useStagedFiles(fileUploadHandler, { onError: () => {
9812
9877
  } });
9813
- React48.useEffect(() => {
9878
+ React49.useEffect(() => {
9814
9879
  if (draft != null && draft !== text) setText(draft);
9815
9880
  }, [draft]);
9816
9881
  const change = (v) => {
@@ -9846,7 +9911,7 @@ function ChatComposer({
9846
9911
  e.preventDefault();
9847
9912
  staged.add(found.files);
9848
9913
  };
9849
- React48.useEffect(() => {
9914
+ React49.useEffect(() => {
9850
9915
  if (!trigger || trigger.type !== "mention" || !mentionSources) {
9851
9916
  setMentionItems([]);
9852
9917
  return;
@@ -9864,7 +9929,7 @@ function ChatComposer({
9864
9929
  const q = (trigger.query || "").toLowerCase();
9865
9930
  setMentionItems(mentionSources.filter((m) => !q || (m.label + " " + (m.description || "")).toLowerCase().includes(q)));
9866
9931
  }, [trigger, mentionSources]);
9867
- const slashItems = React48.useMemo(() => {
9932
+ const slashItems = React49.useMemo(() => {
9868
9933
  if (!trigger || trigger.type !== "slash" || !slashCommands) return [];
9869
9934
  const q = (trigger.query || "").toLowerCase();
9870
9935
  return slashCommands.filter((c) => !q || (c.id + " " + c.label + " " + (c.description || "")).toLowerCase().includes(q));
@@ -10038,12 +10103,12 @@ function ChatComposer({
10038
10103
  }
10039
10104
 
10040
10105
  // src/components/chat/ChatSessionBar.tsx
10041
- import * as React49 from "react";
10106
+ import * as React50 from "react";
10042
10107
  import { jsx as jsx77, jsxs as jsxs73 } from "react/jsx-runtime";
10043
10108
  var compact2 = meterFormats.compact;
10044
10109
  function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extras }) {
10045
- const [open, setOpen] = React49.useState(false);
10046
- const anchor = React49.useRef(null);
10110
+ const [open, setOpen] = React50.useState(false);
10111
+ const anchor = React50.useRef(null);
10047
10112
  const stats = sessionStats || null;
10048
10113
  const cu = contextUsage || null;
10049
10114
  const limits = usageLimits || null;
@@ -10058,7 +10123,7 @@ function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extr
10058
10123
  if (stats && stats.runningTasks) bits.push(stats.runningTasks + " running task" + (stats.runningTasks === 1 ? "" : "s"));
10059
10124
  if (cu) bits.push(pct + "% context");
10060
10125
  const expandable = !!(cu || limits);
10061
- return /* @__PURE__ */ jsxs73(React49.Fragment, { children: [
10126
+ return /* @__PURE__ */ jsxs73(React50.Fragment, { children: [
10062
10127
  /* @__PURE__ */ jsxs73("div", { className: "fdc-bar", children: [
10063
10128
  /* @__PURE__ */ jsxs73(
10064
10129
  "button",
@@ -10199,7 +10264,7 @@ function ModelControls({
10199
10264
  ] })
10200
10265
  });
10201
10266
  }
10202
- return /* @__PURE__ */ jsxs73(React49.Fragment, { children: [
10267
+ return /* @__PURE__ */ jsxs73(React50.Fragment, { children: [
10203
10268
  /* @__PURE__ */ jsx77(
10204
10269
  MenuButton,
10205
10270
  {
@@ -10316,11 +10381,11 @@ function AgentChatPanel({
10316
10381
  onFeedback,
10317
10382
  onEditMessage
10318
10383
  }) {
10319
- const [panelWidth, setPanelWidth] = React50.useState(width);
10320
- const [applied, setApplied] = React50.useState({});
10321
- const [draft, setDraft] = React50.useState("");
10322
- const dragging = React50.useRef(null);
10323
- React50.useEffect(() => setPanelWidth(width), [width]);
10384
+ const [panelWidth, setPanelWidth] = React51.useState(width);
10385
+ const [applied, setApplied] = React51.useState({});
10386
+ const [draft, setDraft] = React51.useState("");
10387
+ const dragging = React51.useRef(null);
10388
+ React51.useEffect(() => setPanelWidth(width), [width]);
10324
10389
  const engine = useChatEngine({
10325
10390
  contextType,
10326
10391
  contextId,
@@ -10524,7 +10589,7 @@ function AgentChatPanel({
10524
10589
  }
10525
10590
 
10526
10591
  // src/components/files/FileViewer.tsx
10527
- import * as React51 from "react";
10592
+ import * as React52 from "react";
10528
10593
  import { jsx as jsx79, jsxs as jsxs75 } from "react/jsx-runtime";
10529
10594
  function previewKindOf(file) {
10530
10595
  const mime = (file.mime || "").toLowerCase();
@@ -10581,13 +10646,13 @@ function parseDelimited(text, delimiter) {
10581
10646
  return rows;
10582
10647
  }
10583
10648
  function usePreview(file, kind, resolveUrl) {
10584
- const [state, setState] = React51.useState({ status: "loading" });
10585
- const [attempt, setAttempt] = React51.useState(0);
10586
- const resolveRef = React51.useRef(resolveUrl);
10587
- React51.useEffect(() => {
10649
+ const [state, setState] = React52.useState({ status: "loading" });
10650
+ const [attempt, setAttempt] = React52.useState(0);
10651
+ const resolveRef = React52.useRef(resolveUrl);
10652
+ React52.useEffect(() => {
10588
10653
  resolveRef.current = resolveUrl;
10589
10654
  }, [resolveUrl]);
10590
- React51.useEffect(() => {
10655
+ React52.useEffect(() => {
10591
10656
  if (kind === "none") {
10592
10657
  setState({ status: "ready" });
10593
10658
  return;
@@ -10617,7 +10682,7 @@ function usePreview(file, kind, resolveUrl) {
10617
10682
  if (ac) ac.abort();
10618
10683
  };
10619
10684
  }, [file.id, kind, attempt]);
10620
- return { ...state, retry: React51.useCallback(() => setAttempt((a) => a + 1), []) };
10685
+ return { ...state, retry: React52.useCallback(() => setAttempt((a) => a + 1), []) };
10621
10686
  }
10622
10687
  function PreviewBody({ file, kind, state }) {
10623
10688
  if (kind === "image") return /* @__PURE__ */ jsx79("img", { className: "fd-fv-image", src: state.url, alt: file.name || "Preview" });
@@ -10662,13 +10727,13 @@ function PreviewBody({ file, kind, state }) {
10662
10727
  ] });
10663
10728
  }
10664
10729
  function FileViewer({ file, resolveUrl, sidePanel, sidePanelLabel = "Details", onClose, actions, className = "" }) {
10665
- const kind = React51.useMemo(() => previewKindOf(file), [file]);
10730
+ const kind = React52.useMemo(() => previewKindOf(file), [file]);
10666
10731
  const preview = usePreview(file, kind, resolveUrl);
10667
- const [tab, setTab] = React51.useState("preview");
10668
- const [downloading, setDownloading] = React51.useState(false);
10669
- const [downloadError, setDownloadError] = React51.useState(null);
10732
+ const [tab, setTab] = React52.useState("preview");
10733
+ const [downloading, setDownloading] = React52.useState(false);
10734
+ const [downloadError, setDownloadError] = React52.useState(null);
10670
10735
  const hasSide = sidePanel != null;
10671
- React51.useEffect(() => {
10736
+ React52.useEffect(() => {
10672
10737
  setTab("preview");
10673
10738
  }, [file.id]);
10674
10739
  const download = () => {
@@ -10735,7 +10800,7 @@ function FileViewer({ file, resolveUrl, sidePanel, sidePanelLabel = "Details", o
10735
10800
  }
10736
10801
 
10737
10802
  // src/components/files/FileBrowser.tsx
10738
- import * as React52 from "react";
10803
+ import * as React53 from "react";
10739
10804
  import { Fragment as Fragment25, jsx as jsx80, jsxs as jsxs76 } from "react/jsx-runtime";
10740
10805
  function childrenOf(folders, parentId) {
10741
10806
  return folders.filter((f) => f.parentId === parentId).sort((a, b) => a.name.localeCompare(b.name));
@@ -10806,10 +10871,10 @@ function FolderPickerMenu({
10806
10871
  destinations,
10807
10872
  onPick
10808
10873
  }) {
10809
- const [open, setOpen] = React52.useState(false);
10874
+ const [open, setOpen] = React53.useState(false);
10810
10875
  const zIndex = useOverlayLayer(open);
10811
- const ref = React52.useRef(null);
10812
- React52.useEffect(() => {
10876
+ const ref = React53.useRef(null);
10877
+ React53.useEffect(() => {
10813
10878
  if (!open) return;
10814
10879
  const away = (e) => {
10815
10880
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -10861,8 +10926,8 @@ function FolderPickerMenu({
10861
10926
  ] });
10862
10927
  }
10863
10928
  function RowShell({ icon, name, meta, onOpen, draggableId, isDropTarget, onDropPayload, actions, className = "" }) {
10864
- const [over, setOver] = React52.useState(false);
10865
- const dragCounter = React52.useRef(0);
10929
+ const [over, setOver] = React53.useState(false);
10930
+ const dragCounter = React53.useRef(0);
10866
10931
  const dropHandlers = isDropTarget ? {
10867
10932
  onDragEnter: (e) => {
10868
10933
  if (!hasInternalPayload(e)) return;
@@ -10910,8 +10975,8 @@ function RowShell({ icon, name, meta, onOpen, draggableId, isDropTarget, onDropP
10910
10975
  }
10911
10976
  function TreeNode({ folder, depth, folders, currentFolderId, onNavigate, onMoveInto, canMove }) {
10912
10977
  const kids = childrenOf(folders, folder.id);
10913
- const [expanded, setExpanded] = React52.useState(() => currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId));
10914
- React52.useEffect(() => {
10978
+ const [expanded, setExpanded] = React53.useState(() => currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId));
10979
+ React53.useEffect(() => {
10915
10980
  if (currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId)) setExpanded(true);
10916
10981
  }, [currentFolderId, folder.id, folders]);
10917
10982
  const isCurrent = folder.id === currentFolderId;
@@ -10977,15 +11042,15 @@ function FileBrowser({
10977
11042
  const canUpload = perm.canUpload !== false && !!onUpload;
10978
11043
  const canRemoveFile = perm.canRemoveFile !== false && !!onRemoveFile;
10979
11044
  const canDeleteFolder = perm.canDeleteFolder !== false && !!onDeleteFolder;
10980
- const [createOpen, setCreateOpen] = React52.useState(false);
10981
- const [createName, setCreateName] = React52.useState("");
10982
- const [renameTarget, setRenameTarget] = React52.useState(null);
10983
- const [renameValue, setRenameValue] = React52.useState("");
10984
- const [deleteTarget, setDeleteTarget] = React52.useState(null);
10985
- const uploadInputRef = React52.useRef(null);
10986
- const crumbFolders = React52.useMemo(() => ancestorsOf(folders, currentFolderId), [folders, currentFolderId]);
10987
- const subfolders = React52.useMemo(() => childrenOf(folders, currentFolderId), [folders, currentFolderId]);
10988
- const currentFiles = React52.useMemo(
11045
+ const [createOpen, setCreateOpen] = React53.useState(false);
11046
+ const [createName, setCreateName] = React53.useState("");
11047
+ const [renameTarget, setRenameTarget] = React53.useState(null);
11048
+ const [renameValue, setRenameValue] = React53.useState("");
11049
+ const [deleteTarget, setDeleteTarget] = React53.useState(null);
11050
+ const uploadInputRef = React53.useRef(null);
11051
+ const crumbFolders = React53.useMemo(() => ancestorsOf(folders, currentFolderId), [folders, currentFolderId]);
11052
+ const subfolders = React53.useMemo(() => childrenOf(folders, currentFolderId), [folders, currentFolderId]);
11053
+ const currentFiles = React53.useMemo(
10989
11054
  () => files.filter((f) => f.folderId === currentFolderId).sort((a, b) => a.name.localeCompare(b.name)),
10990
11055
  [files, currentFolderId]
10991
11056
  );
@@ -11217,8 +11282,8 @@ function BreadcrumbCrumb({
11217
11282
  isDropTarget,
11218
11283
  onDropPayload
11219
11284
  }) {
11220
- const [over, setOver] = React52.useState(false);
11221
- const dragCounter = React52.useRef(0);
11285
+ const [over, setOver] = React53.useState(false);
11286
+ const dragCounter = React53.useRef(0);
11222
11287
  const dropHandlers = isDropTarget ? {
11223
11288
  onDragEnter: (e) => {
11224
11289
  if (!hasInternalPayload(e)) return;
@@ -11247,7 +11312,7 @@ function BreadcrumbCrumb({
11247
11312
  }
11248
11313
 
11249
11314
  // src/kits/query.ts
11250
- import * as React53 from "react";
11315
+ import * as React54 from "react";
11251
11316
  function eqFilter(get2) {
11252
11317
  return (row, value) => Array.isArray(value) ? value.includes(get2(row)) : get2(row) === value;
11253
11318
  }
@@ -11279,22 +11344,22 @@ function compare(a, b, dir) {
11279
11344
  var API = null;
11280
11345
  var PREFS = null;
11281
11346
  function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
11282
- const [query, setQuery] = React53.useState(() => {
11347
+ const [query, setQuery] = React54.useState(() => {
11283
11348
  const store = PREFS || window.PlannerPrefs;
11284
11349
  const saved = prefsKey && store ? store.getTable(prefsKey) : {};
11285
11350
  return { ...DEFAULTS, ...defaults || {}, ...saved.pageSize ? { pageSize: saved.pageSize } : {}, ...saved.sort ? { sort: saved.sort, dir: saved.dir || "desc" } : {} };
11286
11351
  });
11287
- const savePref = React53.useCallback((patch2) => {
11352
+ const savePref = React54.useCallback((patch2) => {
11288
11353
  const store = PREFS || window.PlannerPrefs;
11289
11354
  if (prefsKey && store) store.setTable(prefsKey, patch2);
11290
11355
  }, [prefsKey]);
11291
- const [res, setRes] = React53.useState(null);
11292
- const [loading, setLoading] = React53.useState(true);
11293
- const seq2 = React53.useRef(0);
11356
+ const [res, setRes] = React54.useState(null);
11357
+ const [loading, setLoading] = React54.useState(true);
11358
+ const seq2 = React54.useRef(0);
11294
11359
  const depKey = (deps || []).join("|");
11295
11360
  const paramKey = JSON.stringify(params || {});
11296
11361
  const queryKey = JSON.stringify(query);
11297
- React53.useEffect(() => {
11362
+ React54.useEffect(() => {
11298
11363
  const id = ++seq2.current;
11299
11364
  setLoading(true);
11300
11365
  const t = setTimeout(() => {
@@ -11591,6 +11656,92 @@ function createVersionStore(initialState, options) {
11591
11656
  }
11592
11657
  };
11593
11658
  }
11659
+
11660
+ // src/kits/useResponsiveRail.ts
11661
+ import * as React55 from "react";
11662
+ function matchesBreakpoint(breakpoint) {
11663
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(`(max-width: ${breakpoint}px)`).matches;
11664
+ }
11665
+ var FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
11666
+ function getFocusable(container) {
11667
+ return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR));
11668
+ }
11669
+ function useResponsiveRail(options = {}) {
11670
+ const { breakpoint = 1024, initialOpen = true, onPersist } = options;
11671
+ const navRef = React55.useRef(null);
11672
+ const [overlay, setOverlay] = React55.useState(() => matchesBreakpoint(breakpoint));
11673
+ const [open, setOpenRaw] = React55.useState(() => matchesBreakpoint(breakpoint) ? false : initialOpen);
11674
+ const setOpen = React55.useCallback(
11675
+ (value) => {
11676
+ setOpenRaw(value);
11677
+ if (!matchesBreakpoint(breakpoint)) onPersist?.(value);
11678
+ },
11679
+ [breakpoint, onPersist]
11680
+ );
11681
+ const toggle = React55.useCallback(() => setOpen(!open), [open, setOpen]);
11682
+ const close = React55.useCallback(() => setOpenRaw(false), []);
11683
+ React55.useEffect(() => {
11684
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
11685
+ const mq = window.matchMedia(`(max-width: ${breakpoint}px)`);
11686
+ const handler = (e) => {
11687
+ setOverlay(e.matches);
11688
+ setOpenRaw(!e.matches);
11689
+ };
11690
+ mq.addEventListener("change", handler);
11691
+ return () => mq.removeEventListener("change", handler);
11692
+ }, [breakpoint]);
11693
+ React55.useEffect(() => {
11694
+ if (!overlay || !open) return;
11695
+ const handler = (e) => {
11696
+ if (e.key === "Escape") close();
11697
+ };
11698
+ document.addEventListener("keydown", handler);
11699
+ return () => document.removeEventListener("keydown", handler);
11700
+ }, [overlay, open, close]);
11701
+ React55.useEffect(() => {
11702
+ if (!overlay || !open) return;
11703
+ const previousOverflow = document.body.style.overflow;
11704
+ document.body.style.overflow = "hidden";
11705
+ return () => {
11706
+ document.body.style.overflow = previousOverflow;
11707
+ };
11708
+ }, [overlay, open]);
11709
+ React55.useEffect(() => {
11710
+ if (!overlay || !open) return;
11711
+ const container = navRef.current;
11712
+ if (!container) return;
11713
+ const previouslyFocused = document.activeElement;
11714
+ const focusable = getFocusable(container);
11715
+ (focusable[0] ?? container).focus();
11716
+ const handler = (e) => {
11717
+ if (e.key !== "Tab") return;
11718
+ const items = getFocusable(container);
11719
+ if (items.length === 0) {
11720
+ e.preventDefault();
11721
+ return;
11722
+ }
11723
+ const first = items[0];
11724
+ const last = items[items.length - 1];
11725
+ const active = document.activeElement;
11726
+ const outside = !(active instanceof Node) || !container.contains(active);
11727
+ if (e.shiftKey) {
11728
+ if (outside || active === first) {
11729
+ e.preventDefault();
11730
+ last.focus();
11731
+ }
11732
+ } else if (outside || active === last) {
11733
+ e.preventDefault();
11734
+ first.focus();
11735
+ }
11736
+ };
11737
+ document.addEventListener("keydown", handler);
11738
+ return () => {
11739
+ document.removeEventListener("keydown", handler);
11740
+ previouslyFocused?.focus?.();
11741
+ };
11742
+ }, [overlay, open]);
11743
+ return { open, overlay, navRef, toggle, close, setOpen };
11744
+ }
11594
11745
  export {
11595
11746
  AccountMenu,
11596
11747
  AgentChatPanel,
@@ -11734,6 +11885,7 @@ export {
11734
11885
  channelWeightOf,
11735
11886
  computeNeedMore,
11736
11887
  computeVirtualWindow,
11888
+ createLocalStorageAppearanceStore,
11737
11889
  createVersionStore,
11738
11890
  eqFilter,
11739
11891
  extensionOf,
@@ -11773,6 +11925,7 @@ export {
11773
11925
  useChatEngine,
11774
11926
  useFeatureStatus,
11775
11927
  usePopoverPosition,
11928
+ useResponsiveRail,
11776
11929
  useRuntimeMode,
11777
11930
  useServerTable,
11778
11931
  useSessionContext,