@flytedan/flytebot-design-system 0.28.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -438,6 +438,15 @@ var ROOT_LAYER = 0;
438
438
  var LAYER_STEP = 10;
439
439
  var ROOT_STEP = 100;
440
440
  var OVERLAY_CEILING = 1e4;
441
+ var openEscapeIds = [];
442
+ var registerEscapeLayer = (id) => {
443
+ if (openEscapeIds.includes(id)) return;
444
+ openEscapeIds = [...openEscapeIds, id];
445
+ };
446
+ var unregisterEscapeLayer = (id) => {
447
+ if (!openEscapeIds.includes(id)) return;
448
+ openEscapeIds = openEscapeIds.filter((existing) => existing !== id);
449
+ };
441
450
  var LayerContext = React2.createContext(ROOT_LAYER);
442
451
  var openRootIds = [];
443
452
  var rootListeners = /* @__PURE__ */ new Set();
@@ -515,6 +524,12 @@ function Popover({
515
524
  });
516
525
  const restore = React2.useRef(null);
517
526
  const zIndex = useOverlayLayer(!!open);
527
+ const escapeId = React2.useId();
528
+ React2.useLayoutEffect(() => {
529
+ if (!open) return;
530
+ registerEscapeLayer(escapeId);
531
+ return () => unregisterEscapeLayer(escapeId);
532
+ }, [open, escapeId]);
518
533
  React2.useEffect(() => {
519
534
  if (!open) return;
520
535
  restore.current = typeof document !== "undefined" ? document.activeElement : null;
@@ -539,10 +554,10 @@ function Popover({
539
554
  onClose && onClose();
540
555
  };
541
556
  const key = (e) => {
542
- if (closeOnEscape && e.key === "Escape") {
543
- e.stopPropagation();
544
- onClose && onClose();
545
- }
557
+ if (!closeOnEscape || e.key !== "Escape") return;
558
+ if (openEscapeIds[openEscapeIds.length - 1] !== escapeId) return;
559
+ e.stopPropagation();
560
+ onClose && onClose();
546
561
  };
547
562
  document.addEventListener("pointerdown", away, true);
548
563
  document.addEventListener("keydown", key);
@@ -550,7 +565,7 @@ function Popover({
550
565
  document.removeEventListener("pointerdown", away, true);
551
566
  document.removeEventListener("keydown", key);
552
567
  };
553
- }, [open, closeOnOutside, closeOnEscape, onClose, anchorRef, triggerRef]);
568
+ }, [open, closeOnOutside, closeOnEscape, onClose, anchorRef, triggerRef, escapeId]);
554
569
  if (!open || !pos) return null;
555
570
  return portal(
556
571
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LayerProvider, { zIndex, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
@@ -1739,11 +1754,13 @@ var BANNER_TONE_STYLE = {
1739
1754
  text: "var(--danger-text)"
1740
1755
  }
1741
1756
  };
1742
- function Banner({ tone = "info", title, detail, actions }) {
1757
+ function Banner({ tone = "info", title, detail, actions, role }) {
1743
1758
  const t = BANNER_TONE_STYLE[tone];
1759
+ const liveRole = role || (tone === "danger" || tone === "warn" ? "alert" : "status");
1744
1760
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
1745
1761
  "div",
1746
1762
  {
1763
+ role: liveRole,
1747
1764
  style: {
1748
1765
  display: "flex",
1749
1766
  alignItems: "flex-start",
@@ -2953,7 +2970,7 @@ function DataTable({
2953
2970
  }
2954
2971
 
2955
2972
  // src/components/data/FilterBar.tsx
2956
- var React23 = __toESM(require("react"), 1);
2973
+ var React24 = __toESM(require("react"), 1);
2957
2974
 
2958
2975
  // src/components/forms/NumberInput.tsx
2959
2976
  var React21 = __toESM(require("react"), 1);
@@ -3001,8 +3018,11 @@ function NumberInput({
3001
3018
  "aria-label": ariaLabel
3002
3019
  }) {
3003
3020
  const fieldId = useFieldId(id);
3021
+ const errorId = fieldId + "-error";
3004
3022
  const num = (v) => {
3005
- const n = Number(String(v == null ? "" : v).replace(/[^0-9.-]/g, ""));
3023
+ const s = String(v == null ? "" : v).replace(/[^0-9.-]/g, "");
3024
+ if (s === "" || s === "-") return null;
3025
+ const n = Number(s);
3006
3026
  return isNaN(n) ? null : n;
3007
3027
  };
3008
3028
  const [text, setText] = React21.useState(value == null || value === "" ? "" : String(value));
@@ -3055,9 +3075,15 @@ function NumberInput({
3055
3075
  disabled,
3056
3076
  placeholder,
3057
3077
  value: shown,
3078
+ "aria-invalid": error ? "true" : void 0,
3079
+ "aria-describedby": error ? errorId : void 0,
3058
3080
  onFocus: () => setEditing(true),
3059
3081
  onBlur: () => {
3060
3082
  setEditing(false);
3083
+ if (text.trim() === "") {
3084
+ fire(void 0);
3085
+ return;
3086
+ }
3061
3087
  const n = num(text);
3062
3088
  if (n != null) {
3063
3089
  const c = clamp(n);
@@ -3067,6 +3093,10 @@ function NumberInput({
3067
3093
  },
3068
3094
  onChange: (e) => {
3069
3095
  setText(e.target.value);
3096
+ if (e.target.value.trim() === "") {
3097
+ fire(void 0);
3098
+ return;
3099
+ }
3070
3100
  const n = num(e.target.value);
3071
3101
  if (n != null) fire(n);
3072
3102
  },
@@ -3111,7 +3141,7 @@ function NumberInput({
3111
3141
  )
3112
3142
  ] })
3113
3143
  ] }),
3114
- error ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "fd-field-error", children: [
3144
+ error ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { id: errorId, className: "fd-field-error", children: [
3115
3145
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("i", { className: "ph ph-warning-circle", "aria-hidden": "true" }),
3116
3146
  error
3117
3147
  ] }) : help ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "fd-field-help", children: help }) : null
@@ -3335,16 +3365,48 @@ function Textarea({ label, help, error, required = false, rows = 4, disabled = f
3335
3365
  ] });
3336
3366
  }
3337
3367
 
3368
+ // src/components/forms/listbox.ts
3369
+ var React23 = __toESM(require("react"), 1);
3370
+ function useListboxNav({ count, onSelect }) {
3371
+ const [active, setActive] = React23.useState(-1);
3372
+ return {
3373
+ active,
3374
+ setActive,
3375
+ move: (dir) => setActive((a) => Math.min(count - 1, Math.max(0, a + dir))),
3376
+ first: () => setActive(0),
3377
+ last: () => setActive(count - 1),
3378
+ activate: () => {
3379
+ if (active >= 0 && active < count) onSelect(active);
3380
+ }
3381
+ };
3382
+ }
3383
+ function useListboxScrollIntoView(active, listRef, enabled) {
3384
+ React23.useEffect(() => {
3385
+ if (!enabled || active < 0 || !listRef.current) return;
3386
+ const list = listRef.current;
3387
+ const el = list.querySelector('[data-i="' + active + '"]');
3388
+ if (!el) return;
3389
+ const top = el.offsetTop - list.offsetTop;
3390
+ if (top < list.scrollTop) list.scrollTop = top - 4;
3391
+ else if (top + el.offsetHeight > list.scrollTop + list.clientHeight) {
3392
+ list.scrollTop = top + el.offsetHeight - list.clientHeight + 4;
3393
+ }
3394
+ }, [active, enabled]);
3395
+ }
3396
+
3338
3397
  // src/components/data/FilterBar.tsx
3339
3398
  var import_jsx_runtime45 = require("react/jsx-runtime");
3399
+ var PANEL_WIDTH = { multiSelect: 280, range: 240, date: 300 };
3340
3400
  var isSet = (v) => !(v == null || v === "" || v === false);
3401
+ function operatorsFor(field) {
3402
+ return field.operators && field.operators.length ? field.operators : ["in", "notIn"];
3403
+ }
3341
3404
  function isFieldSet(field, value) {
3342
3405
  switch (field.type) {
3343
3406
  case "text":
3344
3407
  case "select":
3345
3408
  return isSet(value);
3346
- case "multiSelect":
3347
- case "pasteList": {
3409
+ case "multiSelect": {
3348
3410
  const v = value;
3349
3411
  return !!v && Array.isArray(v.values) && v.values.length > 0;
3350
3412
  }
@@ -3355,7 +3417,7 @@ function isFieldSet(field, value) {
3355
3417
  case "date": {
3356
3418
  const v = value;
3357
3419
  if (!v) return false;
3358
- return v.kind === "withinDays" ? v.days != null : !!(v.from || v.to);
3420
+ return v.kind === "withinDays" ? v.days != null : !!(v.from && v.to);
3359
3421
  }
3360
3422
  }
3361
3423
  }
@@ -3367,8 +3429,7 @@ function chipLabel(field, value) {
3367
3429
  const chosen = field.options.find((o) => o.value === value);
3368
3430
  return chosen ? chosen.label : String(value ?? "");
3369
3431
  }
3370
- case "multiSelect":
3371
- case "pasteList": {
3432
+ case "multiSelect": {
3372
3433
  const v = value;
3373
3434
  if (!v || !v.values.length) return "";
3374
3435
  const prefix = v.operator === "notIn" ? "Not " : "";
@@ -3386,7 +3447,7 @@ function chipLabel(field, value) {
3386
3447
  const v = value;
3387
3448
  if (!v) return "";
3388
3449
  if (v.kind === "withinDays") return "Last " + v.days + " day" + (v.days === 1 ? "" : "s");
3389
- return (v.from || "\u2026") + " \u2192 " + (v.to || "\u2026");
3450
+ return v.from + " \u2192 " + v.to;
3390
3451
  }
3391
3452
  }
3392
3453
  }
@@ -3408,338 +3469,374 @@ function OperatorToggle({
3408
3469
  }
3409
3470
  );
3410
3471
  }
3411
- function EditPopover({ zIndex, width, children }) {
3412
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(LayerProvider, { zIndex, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3413
- "span",
3414
- {
3415
- className: "fd-view-enter",
3416
- style: {
3417
- position: "absolute",
3418
- top: "calc(100% + 6px)",
3419
- left: 0,
3420
- zIndex,
3421
- width,
3422
- maxWidth: "calc(100vw - 32px)",
3423
- display: "block",
3424
- background: "var(--surface)",
3425
- border: "1px solid var(--border)",
3426
- borderRadius: 10,
3427
- boxShadow: "0 16px 44px rgba(11,13,17,.18)",
3428
- padding: "var(--s-8)"
3429
- },
3430
- children
3431
- }
3432
- ) });
3433
- }
3434
3472
  function MultiSelectPanel({
3435
3473
  field,
3436
3474
  value,
3475
+ anchorRef,
3437
3476
  onChange,
3477
+ onRemove,
3438
3478
  onClose
3439
3479
  }) {
3440
- const operator = value?.operator ?? "in";
3480
+ const allowedOps = operatorsFor(field);
3481
+ const operator = allowedOps.length === 1 ? allowedOps[0] : value?.operator ?? allowedOps[0];
3441
3482
  const selected = value?.values ?? [];
3442
- const [query, setQuery] = React23.useState("");
3443
- const [asyncOptions, setAsyncOptions] = React23.useState(null);
3444
- const [loading, setLoading] = React23.useState(!!field.loadOptions);
3445
- const [active, setActive] = React23.useState(0);
3446
- const inputRef = React23.useRef(null);
3447
- const listId = React23.useId();
3448
- React23.useEffect(() => {
3483
+ const selectedRef = React24.useRef(selected);
3484
+ selectedRef.current = selected;
3485
+ const commit = (nextValues, nextOperator = operator) => {
3486
+ if (nextValues.length === 0) onRemove();
3487
+ else onChange({ operator: nextOperator, values: nextValues });
3488
+ };
3489
+ const [mode2, setMode] = React24.useState("search");
3490
+ const [query, setQuery] = React24.useState("");
3491
+ const [asyncState, setAsyncState] = React24.useState(
3492
+ { options: null, loading: !!field.loadOptions, error: false }
3493
+ );
3494
+ const [retryTick, setRetryTick] = React24.useState(0);
3495
+ const inputRef = React24.useRef(null);
3496
+ const listRef = React24.useRef(null);
3497
+ const listId = React24.useId();
3498
+ const loadOptionsRef = React24.useRef(field.loadOptions);
3499
+ loadOptionsRef.current = field.loadOptions;
3500
+ React24.useEffect(() => {
3449
3501
  inputRef.current?.focus();
3450
3502
  }, []);
3451
- React23.useEffect(() => {
3452
- if (!field.loadOptions) return;
3453
- let cancelled = false;
3454
- setLoading(true);
3455
- const loadOptions = field.loadOptions;
3503
+ React24.useEffect(() => {
3504
+ const load = loadOptionsRef.current;
3505
+ if (!load) return;
3506
+ const controller = new AbortController();
3507
+ setAsyncState((s) => ({ options: s.options, loading: true, error: false }));
3456
3508
  const t = setTimeout(() => {
3457
- loadOptions(query).then((opts) => {
3458
- if (!cancelled) {
3459
- setAsyncOptions(opts);
3460
- setLoading(false);
3461
- }
3462
- }).catch(() => {
3463
- if (!cancelled) {
3464
- setAsyncOptions([]);
3465
- setLoading(false);
3466
- }
3509
+ load(query, controller.signal).then((opts) => {
3510
+ if (controller.signal.aborted) return;
3511
+ setAsyncState({ options: opts, loading: false, error: false });
3512
+ }).catch((err) => {
3513
+ if (controller.signal.aborted || err && err.name === "AbortError") return;
3514
+ setAsyncState({ options: null, loading: false, error: true });
3467
3515
  });
3468
3516
  }, 300);
3469
3517
  return () => {
3470
- cancelled = true;
3471
3518
  clearTimeout(t);
3519
+ controller.abort();
3472
3520
  };
3473
- }, [query, field.loadOptions]);
3474
- const options = field.loadOptions ? asyncOptions ?? [] : (field.options ?? []).filter((o) => o.label.toLowerCase().includes(query.toLowerCase()));
3475
- React23.useEffect(() => {
3476
- setActive(0);
3477
- }, [query, options.length]);
3521
+ }, [query, retryTick]);
3522
+ const options = field.loadOptions ? asyncState.options ?? [] : (field.options ?? []).filter((o) => o.label.toLowerCase().includes(query.toLowerCase()));
3523
+ const showLoading = !!field.loadOptions && asyncState.loading;
3524
+ const showError = !!field.loadOptions && asyncState.error;
3525
+ const showListbox = !showLoading && !showError;
3478
3526
  const isSelected = (o) => selected.some((s) => s.value === o.value);
3479
3527
  const toggle = (o) => {
3480
3528
  const next = isSelected(o) ? selected.filter((s) => s.value !== o.value) : [...selected, o];
3481
- onChange({ operator, values: next });
3529
+ commit(next);
3530
+ };
3531
+ const nav = useListboxNav({ count: options.length, onSelect: (i) => toggle(options[i]) });
3532
+ useListboxScrollIntoView(nav.active, listRef, showListbox);
3533
+ React24.useEffect(() => {
3534
+ nav.setActive(0);
3535
+ }, [query, options.length]);
3536
+ const activeId = showListbox && options[nav.active] ? listId + "-opt-" + nav.active : void 0;
3537
+ const [pasteText, setPasteText] = React24.useState("");
3538
+ const [pasteLoading, setPasteLoading] = React24.useState(false);
3539
+ const [unmatchedTokens, setUnmatchedTokens] = React24.useState([]);
3540
+ const [pasteError, setPasteError] = React24.useState(null);
3541
+ const pasteWrapRef = React24.useRef(null);
3542
+ React24.useEffect(() => {
3543
+ if (mode2 === "paste") pasteWrapRef.current?.querySelector("textarea")?.focus();
3544
+ }, [mode2]);
3545
+ const doResolve = () => {
3546
+ if (!field.resolve) return;
3547
+ const tokens = Array.from(new Set(pasteText.split(/[\n,]/).map((t) => t.trim()).filter(Boolean)));
3548
+ if (!tokens.length) return;
3549
+ setPasteLoading(true);
3550
+ setPasteError(null);
3551
+ field.resolve(tokens).then((result) => {
3552
+ setPasteLoading(false);
3553
+ setUnmatchedTokens(result.unmatched);
3554
+ const merged = new Map(selectedRef.current.map((s) => [s.value, s]));
3555
+ for (const m of result.matched) merged.set(m.value, { value: m.value, label: m.label });
3556
+ commit(Array.from(merged.values()));
3557
+ setPasteText("");
3558
+ }).catch((err) => {
3559
+ setPasteLoading(false);
3560
+ setPasteError(err instanceof Error ? err.message : "Could not resolve the pasted list.");
3561
+ });
3482
3562
  };
3483
- const activeId = options[active] ? listId + "-opt-" + active : void 0;
3484
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-stack", style: { gap: "var(--s-8)", width: 260 }, children: [
3485
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(OperatorToggle, { operator, onChange: (op) => onChange({ operator: op, values: selected }), label: field.label }),
3563
+ const header = /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-stack fd-gap-sm", children: [
3564
+ allowedOps.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(OperatorToggle, { operator, onChange: (op) => commit(selected, op), label: field.label }) : null,
3565
+ field.resolve ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3566
+ SegmentedControl,
3567
+ {
3568
+ "aria-label": field.label + " \u2014 search or paste a list",
3569
+ options: [{ value: "search", label: "Search" }, { value: "paste", label: "Paste a list" }],
3570
+ value: mode2,
3571
+ onChange: (m) => setMode(m)
3572
+ }
3573
+ ) : null,
3574
+ mode2 === "search" ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-pop-search", children: [
3575
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("i", { className: "ph ph-magnifying-glass", "aria-hidden": "true" }),
3576
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3577
+ "input",
3578
+ {
3579
+ ref: inputRef,
3580
+ role: "combobox",
3581
+ "aria-expanded": showListbox,
3582
+ "aria-controls": showListbox ? listId : void 0,
3583
+ "aria-autocomplete": "list",
3584
+ "aria-activedescendant": activeId,
3585
+ "aria-label": field.label,
3586
+ value: query,
3587
+ placeholder: field.placeholder || "Search\u2026",
3588
+ onChange: (e) => setQuery(e.target.value),
3589
+ onKeyDown: (e) => {
3590
+ if (e.key === "ArrowDown") {
3591
+ e.preventDefault();
3592
+ nav.move(1);
3593
+ } else if (e.key === "ArrowUp") {
3594
+ e.preventDefault();
3595
+ nav.move(-1);
3596
+ } else if (e.key === "Home") {
3597
+ e.preventDefault();
3598
+ nav.first();
3599
+ } else if (e.key === "End") {
3600
+ e.preventDefault();
3601
+ nav.last();
3602
+ } else if (e.key === "Enter") {
3603
+ e.preventDefault();
3604
+ nav.activate();
3605
+ }
3606
+ }
3607
+ }
3608
+ )
3609
+ ] }) : null
3610
+ ] });
3611
+ const body = mode2 === "paste" ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-stack fd-gap-sm", ref: pasteWrapRef, children: [
3486
3612
  /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3487
- "input",
3613
+ Textarea,
3488
3614
  {
3489
- ref: inputRef,
3490
- role: "combobox",
3491
- "aria-expanded": "true",
3492
- "aria-controls": listId,
3493
- "aria-autocomplete": "list",
3494
- "aria-activedescendant": activeId,
3495
- "aria-label": field.label,
3496
- className: "fd-input",
3497
- style: { width: "100%", padding: "6px 10px" },
3498
- value: query,
3499
- placeholder: field.placeholder || "Search\u2026",
3500
- onChange: (e) => setQuery(e.target.value),
3615
+ "aria-label": field.label + " \u2014 paste a list",
3616
+ rows: 4,
3617
+ placeholder: field.pastePlaceholder || field.placeholder || "Paste names, one per line or comma-separated",
3618
+ value: pasteText,
3619
+ disabled: pasteLoading,
3620
+ onChange: (e) => setPasteText(e.target.value),
3501
3621
  onKeyDown: (e) => {
3502
- if (e.key === "ArrowDown") {
3503
- e.preventDefault();
3504
- setActive((a) => Math.min(a + 1, options.length - 1));
3505
- } else if (e.key === "ArrowUp") {
3506
- e.preventDefault();
3507
- setActive((a) => Math.max(a - 1, 0));
3508
- } else if (e.key === "Enter") {
3622
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
3509
3623
  e.preventDefault();
3510
- if (options[active]) toggle(options[active]);
3511
- } else if (e.key === "Escape") {
3512
- e.stopPropagation();
3513
- onClose();
3624
+ doResolve();
3514
3625
  }
3515
3626
  }
3516
3627
  }
3517
3628
  ),
3518
- loading ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-row fd-body-sm fd-muted", style: { gap: "var(--s-4)" }, children: [
3519
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-spinner", "aria-hidden": "true" }),
3520
- " Loading\u2026"
3521
- ] }) : options.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-body-sm fd-muted", children: "No matches" }) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3522
- "span",
3523
- {
3524
- id: listId,
3525
- role: "listbox",
3526
- "aria-label": field.label,
3527
- "aria-multiselectable": "true",
3528
- className: "fd-stack",
3529
- style: { gap: 2, maxHeight: 220, overflowY: "auto" },
3530
- children: options.map((o, i) => {
3531
- const on = isSelected(o);
3532
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
3533
- "button",
3534
- {
3535
- type: "button",
3536
- id: listId + "-opt-" + i,
3537
- role: "option",
3538
- "aria-selected": on,
3539
- className: "fd-row",
3540
- onMouseEnter: () => setActive(i),
3541
- onClick: () => toggle(o),
3542
- style: {
3543
- gap: "var(--s-8)",
3544
- padding: "4px 8px",
3545
- borderRadius: 7,
3546
- border: 0,
3547
- width: "100%",
3548
- textAlign: "left",
3549
- cursor: "pointer",
3550
- background: i === active ? "var(--surface-brand)" : "transparent"
3551
- },
3552
- children: [
3553
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("i", { className: on ? "ph ph-check-square" : "ph ph-square", "aria-hidden": "true" }),
3554
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-body-sm", style: { flex: 1, color: "var(--text)" }, children: o.label })
3555
- ]
3556
- },
3557
- o.value
3558
- );
3559
- })
3560
- }
3561
- ),
3562
- selected.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-body-sm fd-muted", children: [
3563
- selected.length,
3564
- " selected"
3629
+ pasteError ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Banner, { tone: "danger", title: "Couldn't resolve the list", detail: pasteError }) : null,
3630
+ unmatchedTokens.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Banner, { tone: "warn", title: unmatchedTokens.length + " didn't match", detail: unmatchedTokens.join(", ") }) : null,
3631
+ selected.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-stack fd-gap-xs", children: [
3632
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-body-sm fd-muted", children: [
3633
+ selected.length,
3634
+ " matched"
3635
+ ] }),
3636
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-row fd-wrap fd-gap-xs", children: selected.map((v) => /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Tag, { onRemove: () => commit(selected.filter((s) => s.value !== v.value)), children: v.label }, v.value)) })
3565
3637
  ] }) : null
3566
- ] });
3638
+ ] }) : showLoading ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-row fd-gap-xs", role: "status", children: [
3639
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-spinner", "aria-hidden": "true" }),
3640
+ " Loading\u2026"
3641
+ ] }) : showError ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-stack fd-gap-sm", children: [
3642
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-body-sm", role: "alert", children: "Couldn't load options." }),
3643
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Button, { size: "sm", variant: "secondary", onClick: () => setRetryTick((n) => n + 1), children: "Retry" })
3644
+ ] }) : options.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-body-sm fd-muted", role: "status", children: "No matches" }) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { id: listId, role: "listbox", "aria-label": field.label, "aria-multiselectable": "true", ref: listRef, className: "fd-pop-list", children: options.map((o, i) => {
3645
+ const on = isSelected(o);
3646
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
3647
+ "div",
3648
+ {
3649
+ id: listId + "-opt-" + i,
3650
+ "data-i": i,
3651
+ role: "option",
3652
+ tabIndex: -1,
3653
+ "aria-selected": on,
3654
+ className: ["fd-opt", i === nav.active ? "is-active" : "", on ? "is-selected" : ""].filter(Boolean).join(" "),
3655
+ onMouseEnter: () => nav.setActive(i),
3656
+ onClick: () => toggle(o),
3657
+ children: [
3658
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("i", { className: "fd-filterbar-opt-check ph " + (on ? "ph-check-square is-checked" : "ph-square"), "aria-hidden": "true" }),
3659
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-opt-label", children: o.label })
3660
+ ]
3661
+ },
3662
+ o.value
3663
+ );
3664
+ }) });
3665
+ const footer = mode2 === "paste" ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Button, { size: "sm", variant: "secondary", onClick: doResolve, disabled: pasteLoading || !pasteText.trim(), children: pasteLoading ? "Resolving\u2026" : "Resolve" }) : void 0;
3666
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3667
+ Popover,
3668
+ {
3669
+ open: true,
3670
+ anchorRef,
3671
+ onClose,
3672
+ placement: "bottom-start",
3673
+ offset: 6,
3674
+ width: PANEL_WIDTH.multiSelect,
3675
+ label: field.label,
3676
+ header,
3677
+ footer,
3678
+ children: body
3679
+ }
3680
+ );
3567
3681
  }
3568
3682
  function RangePanel({
3569
3683
  field,
3570
3684
  value,
3685
+ anchorRef,
3571
3686
  onChange,
3687
+ onRemove,
3572
3688
  onClose
3573
3689
  }) {
3574
- const min = value?.min;
3575
- const max = value?.max;
3576
- const rangeError = min != null && max != null && min > max ? "Must be \u2265 " + (field.minLabel || "min").toLowerCase() : void 0;
3577
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-stack", style: { gap: "var(--s-8)", width: 240 }, children: [
3578
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3579
- NumberInput,
3580
- {
3581
- label: field.minLabel || "At least",
3582
- value: min ?? "",
3583
- min: field.min,
3584
- max: field.max,
3585
- step: field.step,
3586
- placeholder: field.minPlaceholder,
3587
- onChange: (e) => onChange({ min: e.target.value, max })
3588
- }
3589
- ),
3590
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3591
- NumberInput,
3592
- {
3593
- label: field.maxLabel || "At most",
3594
- value: max ?? "",
3595
- min: field.min,
3596
- max: field.max,
3597
- step: field.step,
3598
- error: rangeError,
3599
- placeholder: field.maxPlaceholder,
3600
- onChange: (e) => onChange({ min, max: e.target.value })
3601
- }
3602
- ),
3603
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Button, { size: "sm", variant: "secondary", onClick: onClose, children: "Done" })
3604
- ] });
3690
+ const [draftMin, setDraftMin] = React24.useState(value?.min);
3691
+ const [draftMax, setDraftMax] = React24.useState(value?.max);
3692
+ const invalid = draftMin != null && draftMax != null && draftMin > draftMax;
3693
+ const apply = (nextMin, nextMax) => {
3694
+ setDraftMin(nextMin);
3695
+ setDraftMax(nextMax);
3696
+ if (nextMin == null && nextMax == null) {
3697
+ onRemove();
3698
+ return;
3699
+ }
3700
+ if (nextMin != null && nextMax != null && nextMin > nextMax) return;
3701
+ onChange({ min: nextMin, max: nextMax });
3702
+ };
3703
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3704
+ Popover,
3705
+ {
3706
+ open: true,
3707
+ anchorRef,
3708
+ onClose,
3709
+ placement: "bottom-start",
3710
+ offset: 6,
3711
+ width: PANEL_WIDTH.range,
3712
+ label: field.label,
3713
+ footer: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Button, { size: "sm", variant: "secondary", onClick: onClose, children: "Done" }),
3714
+ children: /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-stack fd-gap-sm", children: [
3715
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3716
+ NumberInput,
3717
+ {
3718
+ label: field.minLabel || "At least",
3719
+ value: draftMin ?? "",
3720
+ min: field.min,
3721
+ max: field.max,
3722
+ step: field.step,
3723
+ placeholder: field.minPlaceholder,
3724
+ onChange: (e) => apply(e.target.value, draftMax)
3725
+ }
3726
+ ),
3727
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3728
+ NumberInput,
3729
+ {
3730
+ label: field.maxLabel || "At most",
3731
+ value: draftMax ?? "",
3732
+ min: field.min,
3733
+ max: field.max,
3734
+ step: field.step,
3735
+ error: invalid ? "Must be \u2265 " + draftMin : void 0,
3736
+ placeholder: field.maxPlaceholder,
3737
+ onChange: (e) => apply(draftMin, e.target.value)
3738
+ }
3739
+ )
3740
+ ] })
3741
+ }
3742
+ );
3605
3743
  }
3606
3744
  function DatePanel({
3607
3745
  field,
3608
3746
  value,
3747
+ anchorRef,
3609
3748
  onChange,
3749
+ onRemove,
3610
3750
  onClose
3611
3751
  }) {
3612
- const kind = value?.kind ?? "withinDays";
3613
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-stack", style: { gap: "var(--s-8)", width: 300 }, children: [
3614
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3615
- SegmentedControl,
3616
- {
3617
- "aria-label": field.label + " \u2014 relative or between dates",
3618
- options: [
3619
- { value: "withinDays", label: "Within last N days" },
3620
- { value: "between", label: "Between dates" }
3621
- ],
3622
- value: kind,
3623
- onChange: (k) => {
3624
- if (k === "withinDays") {
3625
- onChange({ kind: "withinDays", days: value?.kind === "withinDays" ? value.days : field.defaultDays ?? 30 });
3626
- } else {
3627
- onChange({ kind: "between", from: value?.kind === "between" ? value.from : "", to: value?.kind === "between" ? value.to : "" });
3628
- }
3629
- }
3630
- }
3631
- ),
3632
- kind === "withinDays" ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3633
- NumberInput,
3634
- {
3635
- label: "Days",
3636
- min: 1,
3637
- value: value?.kind === "withinDays" ? value.days : field.defaultDays ?? 30,
3638
- onChange: (e) => onChange({ kind: "withinDays", days: e.target.value })
3639
- }
3640
- ) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3641
- DatePicker,
3642
- {
3643
- range: true,
3644
- label: "Date range",
3645
- value: value?.kind === "between" ? { start: value.from, end: value.to } : void 0,
3646
- onChange: (e) => onChange({ kind: "between", from: e.target.value.start || "", to: e.target.value.end || "" })
3752
+ const defaultDays = field.defaultDays ?? 30;
3753
+ const [draft, setDraft] = React24.useState(value ?? { kind: "withinDays", days: defaultDays });
3754
+ const committedRef = React24.useRef(!!value);
3755
+ const applyWithinDays = (days) => {
3756
+ const next = { kind: "withinDays", days: days ?? defaultDays };
3757
+ setDraft(next);
3758
+ if (days != null) {
3759
+ committedRef.current = true;
3760
+ onChange(next);
3761
+ }
3762
+ };
3763
+ const applyBetween = (from, to) => {
3764
+ const next = { kind: "between", from, to };
3765
+ setDraft(next);
3766
+ if (from && to) {
3767
+ committedRef.current = true;
3768
+ onChange(next);
3769
+ } else if (committedRef.current) {
3770
+ committedRef.current = false;
3771
+ onRemove();
3772
+ }
3773
+ };
3774
+ const switchKind = (kind) => {
3775
+ if (kind === "withinDays") {
3776
+ const days = draft.kind === "withinDays" ? draft.days : defaultDays;
3777
+ setDraft({ kind: "withinDays", days });
3778
+ } else {
3779
+ setDraft({ kind: "between", from: "", to: "" });
3780
+ if (committedRef.current) {
3781
+ committedRef.current = false;
3782
+ onRemove();
3647
3783
  }
3648
- ),
3649
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Button, { size: "sm", variant: "secondary", onClick: onClose, children: "Done" })
3650
- ] });
3651
- }
3652
- function PasteListPanel({
3653
- field,
3654
- value,
3655
- onChange,
3656
- onClose
3657
- }) {
3658
- const operator = value?.operator ?? "in";
3659
- const selected = value?.values ?? [];
3660
- const [text, setText] = React23.useState("");
3661
- const [loading, setLoading] = React23.useState(false);
3662
- const [unmatched, setUnmatched] = React23.useState([]);
3663
- const [error, setError] = React23.useState(null);
3664
- const wrapRef = React23.useRef(null);
3665
- React23.useEffect(() => {
3666
- wrapRef.current?.querySelector("textarea")?.focus();
3667
- }, []);
3668
- const doResolve = () => {
3669
- const tokens = text.split(/[\n,]/).map((t) => t.trim()).filter(Boolean);
3670
- if (!tokens.length) return;
3671
- setLoading(true);
3672
- setError(null);
3673
- field.resolve(tokens).then((result) => {
3674
- setLoading(false);
3675
- setUnmatched(result.unmatched);
3676
- const merged = new Map(selected.map((s) => [s.value, s]));
3677
- for (const m of result.matched) merged.set(m.value, { value: m.value, label: m.label });
3678
- onChange({ operator, values: Array.from(merged.values()) });
3679
- setText("");
3680
- }).catch((err) => {
3681
- setLoading(false);
3682
- setError(err instanceof Error ? err.message : "Could not resolve the pasted list.");
3683
- });
3784
+ }
3684
3785
  };
3685
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-stack", style: { gap: "var(--s-8)", width: 320 }, children: [
3686
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(OperatorToggle, { operator, onChange: (op) => onChange({ operator: op, values: selected }), label: field.label }),
3687
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { ref: wrapRef, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3688
- Textarea,
3689
- {
3690
- "aria-label": field.label + " \u2014 paste a list",
3691
- rows: 4,
3692
- placeholder: field.placeholder || "Paste names, one per line or comma-separated",
3693
- value: text,
3694
- disabled: loading,
3695
- onChange: (e) => setText(e.target.value),
3696
- onKeyDown: (e) => {
3697
- if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
3698
- e.preventDefault();
3699
- doResolve();
3786
+ const confirm = () => {
3787
+ if (draft.kind === "withinDays") {
3788
+ committedRef.current = true;
3789
+ onChange(draft);
3790
+ }
3791
+ onClose();
3792
+ };
3793
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3794
+ Popover,
3795
+ {
3796
+ open: true,
3797
+ anchorRef,
3798
+ onClose,
3799
+ placement: "bottom-start",
3800
+ offset: 6,
3801
+ width: PANEL_WIDTH.date,
3802
+ label: field.label,
3803
+ footer: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Button, { size: "sm", variant: "secondary", onClick: confirm, children: "Done" }),
3804
+ children: /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-stack fd-gap-sm", children: [
3805
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3806
+ SegmentedControl,
3807
+ {
3808
+ "aria-label": field.label + " \u2014 relative or between dates",
3809
+ options: [
3810
+ { value: "withinDays", label: "Within last N days" },
3811
+ { value: "between", label: "Between dates" }
3812
+ ],
3813
+ value: draft.kind,
3814
+ onChange: (k) => switchKind(k)
3700
3815
  }
3701
- }
3702
- }
3703
- ) }),
3704
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Button, { size: "sm", variant: "secondary", onClick: doResolve, disabled: loading || !text.trim(), children: loading ? "Resolving\u2026" : "Resolve" }),
3705
- error ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Banner, { tone: "danger", title: "Couldn't resolve the list", detail: error }) : null,
3706
- unmatched.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Banner, { tone: "warn", title: unmatched.length + " didn't match", detail: unmatched.join(", ") }) : null,
3707
- selected.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-stack", style: { gap: "var(--s-4)" }, children: [
3708
- /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-body-sm fd-muted", children: [
3709
- selected.length,
3710
- " matched"
3711
- ] }),
3712
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-row", style: { flexWrap: "wrap", gap: "var(--s-4)" }, children: selected.map((v) => /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Tag, { onRemove: () => onChange({ operator, values: selected.filter((s) => s.value !== v.value) }), children: v.label }, v.value)) })
3713
- ] }) : null,
3714
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Button, { size: "sm", variant: "secondary", onClick: onClose, children: "Done" })
3715
- ] });
3816
+ ),
3817
+ draft.kind === "withinDays" ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(NumberInput, { label: "Days", min: 1, value: draft.days, onChange: (e) => applyWithinDays(e.target.value) }) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3818
+ DatePicker,
3819
+ {
3820
+ range: true,
3821
+ label: "Date range",
3822
+ value: { start: draft.from, end: draft.to },
3823
+ onChange: (e) => applyBetween(e.target.value.start || "", e.target.value.end || "")
3824
+ }
3825
+ )
3826
+ ] })
3827
+ }
3828
+ );
3716
3829
  }
3717
3830
  function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align = "left", className = "", ...rest }) {
3718
- const [panel, setPanel] = React23.useState(null);
3719
- const zIndex = useOverlayLayer(!!panel);
3720
- const rootRef = React23.useRef(null);
3721
- const textInputRef = React23.useRef(null);
3722
- const optionsRef = React23.useRef(null);
3831
+ const [panel, setPanel] = React24.useState(null);
3832
+ const editAnchorRef = React24.useRef(null);
3833
+ const addAnchorRef = React24.useRef(null);
3834
+ const textInputRef = React24.useRef(null);
3835
+ const optionsRef = React24.useRef(null);
3723
3836
  const editingField = panel && panel.kind === "edit" ? fields.find((f) => f.key === panel.key) : void 0;
3724
- const dismissible = !!panel && (panel.kind === "add" || panel.kind === "edit" && editingField?.type !== "text");
3725
- React23.useEffect(() => {
3726
- if (!dismissible) return;
3727
- const away = (e) => {
3728
- const target = e.target;
3729
- if (target.closest && target.closest(".fd-pop")) return;
3730
- if (rootRef.current && !rootRef.current.contains(e.target)) setPanel(null);
3731
- };
3732
- const esc = (e) => {
3733
- if (e.key === "Escape") setPanel(null);
3734
- };
3735
- document.addEventListener("pointerdown", away);
3736
- document.addEventListener("keydown", esc);
3737
- return () => {
3738
- document.removeEventListener("pointerdown", away);
3739
- document.removeEventListener("keydown", esc);
3740
- };
3741
- }, [dismissible]);
3742
- React23.useEffect(() => {
3837
+ const startEditing = (key) => setPanel({ kind: "edit", key });
3838
+ const closeEditing = () => setPanel(null);
3839
+ React24.useEffect(() => {
3743
3840
  if (!panel || panel.kind !== "edit") return;
3744
3841
  if (editingField?.type === "text") {
3745
3842
  textInputRef.current?.focus();
@@ -3749,72 +3846,77 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
3749
3846
  }
3750
3847
  }, [panel]);
3751
3848
  const addableFields = fields.filter((f) => !isFieldSet(f, filters[f.key]));
3752
- const startEditing = (key) => setPanel({ kind: "edit", key });
3753
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className, style: { position: "relative", display: "inline-flex", flexWrap: "wrap", alignItems: "center", gap: 8 }, ref: rootRef, ...rest, children: [
3849
+ const addMenuItems = [
3850
+ { kind: "section", label: "Add filter" },
3851
+ ...addableFields.map((f) => ({ id: f.key, label: f.label, onSelect: () => startEditing(f.key) }))
3852
+ ];
3853
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: ["fd-filterbar", className].filter(Boolean).join(" "), ...rest, children: [
3754
3854
  fields.map((field) => {
3755
3855
  const value = filters[field.key];
3756
3856
  const editing = panel?.kind === "edit" && panel.key === field.key;
3757
3857
  if (editing && field.type === "text") {
3758
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
3759
- "span",
3760
- {
3761
- className: "fd-tag",
3762
- style: { background: "var(--surface-brand)", borderColor: "var(--border-brand)", cursor: "text" },
3763
- children: [
3764
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3765
- "input",
3766
- {
3767
- ref: textInputRef,
3768
- value: value ?? "",
3769
- placeholder: field.placeholder || field.label,
3770
- "aria-label": field.label,
3771
- onChange: (e) => onFilterChange(field.key, e.target.value),
3772
- onKeyDown: (e) => {
3773
- if (e.key === "Enter") setPanel(null);
3774
- else if (e.key === "Escape") {
3775
- e.stopPropagation();
3776
- setPanel(null);
3777
- }
3778
- },
3779
- onBlur: () => setPanel(null),
3780
- style: { all: "unset", minWidth: 90, color: "var(--text-on-surface-brand)" }
3781
- }
3782
- ),
3783
- isSet(value) ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3784
- "span",
3785
- {
3786
- className: "fd-tag-remove",
3787
- role: "button",
3788
- "aria-label": "Remove " + field.label,
3789
- onClick: (e) => {
3790
- e.stopPropagation();
3791
- onFilterRemove(field.key);
3792
- setPanel(null);
3793
- },
3794
- children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("i", { className: "ph ph-x", "aria-hidden": "true" })
3858
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: ["fd-tag", "fd-filterbar-text"].join(" "), children: [
3859
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3860
+ "input",
3861
+ {
3862
+ ref: textInputRef,
3863
+ className: "fd-filterbar-text-input",
3864
+ value: value ?? "",
3865
+ placeholder: field.placeholder || field.label,
3866
+ "aria-label": field.label,
3867
+ onChange: (e) => onFilterChange(field.key, e.target.value),
3868
+ onKeyDown: (e) => {
3869
+ if (e.key === "Enter") setPanel(null);
3870
+ else if (e.key === "Escape") {
3871
+ e.stopPropagation();
3872
+ setPanel(null);
3795
3873
  }
3796
- ) : null
3797
- ]
3798
- },
3799
- field.key
3800
- );
3801
- }
3802
- if (editing && field.type === "select") {
3803
- const chosen = field.options.find((o) => o.value === value);
3804
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { style: { position: "relative", display: "inline-flex" }, children: [
3805
- /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(Tag, { selected: true, onClick: () => setPanel(null), onRemove: () => {
3806
- if (isSet(value)) onFilterRemove(field.key);
3807
- setPanel(null);
3808
- }, children: [
3809
- field.label,
3810
- chosen ? ": " + chosen.label : ""
3811
- ] }),
3812
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(LayerProvider, { zIndex, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3874
+ },
3875
+ onBlur: () => setPanel(null)
3876
+ }
3877
+ ),
3878
+ isSet(value) ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3813
3879
  "span",
3814
3880
  {
3815
- className: "fd-view-enter",
3816
- style: { position: "absolute", top: "calc(100% + 6px)", left: 0, zIndex, minWidth: 200, maxWidth: "calc(100vw - 32px)", display: "block", background: "var(--surface)", border: "1px solid var(--border)", borderRadius: 10, boxShadow: "0 16px 44px rgba(11,13,17,.18)", padding: 8 },
3817
- children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-stack", role: "listbox", "aria-label": field.label, ref: optionsRef, style: { gap: 2 }, children: field.options.map((o) => {
3881
+ className: "fd-tag-remove",
3882
+ role: "button",
3883
+ "aria-label": "Remove " + field.label,
3884
+ onClick: (e) => {
3885
+ e.stopPropagation();
3886
+ onFilterRemove(field.key);
3887
+ setPanel(null);
3888
+ },
3889
+ children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("i", { className: "ph ph-x", "aria-hidden": "true" })
3890
+ }
3891
+ ) : null
3892
+ ] }, field.key);
3893
+ }
3894
+ const set = isFieldSet(field, value);
3895
+ if (!editing && !set) return null;
3896
+ const chipRef = editing ? editAnchorRef : void 0;
3897
+ const toggleOrClose = () => editing ? closeEditing() : startEditing(field.key);
3898
+ const remove = () => {
3899
+ if (set) onFilterRemove(field.key);
3900
+ closeEditing();
3901
+ };
3902
+ const chipText = field.label + (set ? ": " + chipLabel(field, value) : "");
3903
+ if (field.type === "text") {
3904
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Tag, { selected: true, onClick: toggleOrClose, onRemove: remove, children: chipText }, field.key);
3905
+ }
3906
+ if (field.type === "select") {
3907
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { children: [
3908
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { ref: chipRef, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Tag, { selected: true, onClick: toggleOrClose, onRemove: remove, children: chipText }) }),
3909
+ editing ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3910
+ Popover,
3911
+ {
3912
+ open: true,
3913
+ anchorRef: editAnchorRef,
3914
+ onClose: closeEditing,
3915
+ placement: align === "right" ? "bottom-end" : "bottom-start",
3916
+ offset: 6,
3917
+ minWidth: 200,
3918
+ label: field.label,
3919
+ children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { role: "listbox", "aria-label": field.label, ref: optionsRef, className: "fd-pop-list", children: field.options.map((o) => {
3818
3920
  const on = o.value === value;
3819
3921
  return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3820
3922
  "button",
@@ -3822,84 +3924,72 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
3822
3924
  type: "button",
3823
3925
  role: "option",
3824
3926
  "aria-selected": on,
3825
- className: "fd-row",
3927
+ className: ["fd-opt", on ? "is-selected" : ""].filter(Boolean).join(" "),
3826
3928
  onClick: () => {
3827
3929
  onFilterChange(field.key, o.value);
3828
3930
  setPanel(null);
3829
3931
  },
3830
- style: { gap: 8, padding: "4px 8px", borderRadius: 7, border: 0, width: "100%", textAlign: "left", cursor: "pointer", background: on ? "var(--surface-brand)" : "transparent" },
3831
- children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-body-sm", style: { flex: 1, fontWeight: on ? 700 : 500, color: on ? "var(--text-on-surface-brand)" : "var(--text)" }, children: o.label })
3932
+ children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-opt-label", children: o.label })
3832
3933
  },
3833
3934
  o.value
3834
3935
  );
3835
3936
  }) })
3836
3937
  }
3837
- ) })
3838
- ] }, field.key);
3839
- }
3840
- if (editing && field.type === "multiSelect") {
3841
- const mv = value;
3842
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { style: { position: "relative", display: "inline-flex" }, children: [
3843
- /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(Tag, { selected: true, onClick: () => setPanel(null), onRemove: () => {
3844
- if (isFieldSet(field, value)) onFilterRemove(field.key);
3845
- setPanel(null);
3846
- }, children: [
3847
- field.label,
3848
- isFieldSet(field, value) ? ": " + chipLabel(field, value) : ""
3849
- ] }),
3850
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(EditPopover, { zIndex, width: 260, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(MultiSelectPanel, { field, value: mv, onChange: (v) => onFilterChange(field.key, v), onClose: () => setPanel(null) }) })
3938
+ ) : null
3851
3939
  ] }, field.key);
3852
3940
  }
3853
- if (editing && field.type === "range") {
3854
- const rv = value;
3855
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { style: { position: "relative", display: "inline-flex" }, children: [
3856
- /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(Tag, { selected: true, onClick: () => setPanel(null), onRemove: () => {
3857
- if (isFieldSet(field, value)) onFilterRemove(field.key);
3858
- setPanel(null);
3859
- }, children: [
3860
- field.label,
3861
- isFieldSet(field, value) ? ": " + chipLabel(field, value) : ""
3862
- ] }),
3863
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(EditPopover, { zIndex, width: 240, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(RangePanel, { field, value: rv, onChange: (v) => onFilterChange(field.key, v), onClose: () => setPanel(null) }) })
3864
- ] }, field.key);
3865
- }
3866
- if (editing && field.type === "date") {
3867
- const dv = value;
3868
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { style: { position: "relative", display: "inline-flex" }, children: [
3869
- /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(Tag, { selected: true, onClick: () => setPanel(null), onRemove: () => {
3870
- if (isFieldSet(field, value)) onFilterRemove(field.key);
3871
- setPanel(null);
3872
- }, children: [
3873
- field.label,
3874
- isFieldSet(field, value) ? ": " + chipLabel(field, value) : ""
3875
- ] }),
3876
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(EditPopover, { zIndex, width: 300, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(DatePanel, { field, value: dv, onChange: (v) => onFilterChange(field.key, v), onClose: () => setPanel(null) }) })
3941
+ if (field.type === "multiSelect") {
3942
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { children: [
3943
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { ref: chipRef, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Tag, { selected: true, onClick: toggleOrClose, onRemove: remove, children: chipText }) }),
3944
+ editing ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3945
+ MultiSelectPanel,
3946
+ {
3947
+ field,
3948
+ value,
3949
+ anchorRef: editAnchorRef,
3950
+ onChange: (v) => onFilterChange(field.key, v),
3951
+ onRemove: () => onFilterRemove(field.key),
3952
+ onClose: closeEditing
3953
+ }
3954
+ ) : null
3877
3955
  ] }, field.key);
3878
3956
  }
3879
- if (editing && field.type === "pasteList") {
3880
- const pv = value;
3881
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { style: { position: "relative", display: "inline-flex" }, children: [
3882
- /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(Tag, { selected: true, onClick: () => setPanel(null), onRemove: () => {
3883
- if (isFieldSet(field, value)) onFilterRemove(field.key);
3884
- setPanel(null);
3885
- }, children: [
3886
- field.label,
3887
- isFieldSet(field, value) ? ": " + chipLabel(field, value) : ""
3888
- ] }),
3889
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(EditPopover, { zIndex, width: 320, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(PasteListPanel, { field, value: pv, onChange: (v) => onFilterChange(field.key, v), onClose: () => setPanel(null) }) })
3957
+ if (field.type === "range") {
3958
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { children: [
3959
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { ref: chipRef, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Tag, { selected: true, onClick: toggleOrClose, onRemove: remove, children: chipText }) }),
3960
+ editing ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3961
+ RangePanel,
3962
+ {
3963
+ field,
3964
+ value,
3965
+ anchorRef: editAnchorRef,
3966
+ onChange: (v) => onFilterChange(field.key, v),
3967
+ onRemove: () => onFilterRemove(field.key),
3968
+ onClose: closeEditing
3969
+ }
3970
+ ) : null
3890
3971
  ] }, field.key);
3891
3972
  }
3892
- if (isFieldSet(field, value)) {
3893
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(Tag, { selected: true, onClick: () => startEditing(field.key), onRemove: () => onFilterRemove(field.key), children: [
3894
- field.label,
3895
- ": ",
3896
- chipLabel(field, value)
3973
+ if (field.type === "date") {
3974
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { children: [
3975
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { ref: chipRef, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Tag, { selected: true, onClick: toggleOrClose, onRemove: remove, children: chipText }) }),
3976
+ editing ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3977
+ DatePanel,
3978
+ {
3979
+ field,
3980
+ value,
3981
+ anchorRef: editAnchorRef,
3982
+ onChange: (v) => onFilterChange(field.key, v),
3983
+ onRemove: () => onFilterRemove(field.key),
3984
+ onClose: closeEditing
3985
+ }
3986
+ ) : null
3897
3987
  ] }, field.key);
3898
3988
  }
3899
3989
  return null;
3900
3990
  }),
3901
- addableFields.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { style: { position: "relative", display: "inline-flex" }, children: [
3902
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3991
+ addableFields.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { children: [
3992
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { ref: addAnchorRef, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3903
3993
  Button,
3904
3994
  {
3905
3995
  size: "sm",
@@ -3910,29 +4000,20 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
3910
4000
  onClick: () => setPanel(panel?.kind === "add" ? null : { kind: "add" }),
3911
4001
  children: "Filter"
3912
4002
  }
3913
- ),
3914
- panel?.kind === "add" ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(LayerProvider, { zIndex, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3915
- "span",
4003
+ ) }),
4004
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
4005
+ Popover,
3916
4006
  {
3917
- className: "fd-view-enter",
3918
- style: { position: "absolute", top: "calc(100% + 6px)", [align]: 0, zIndex, width: 220, maxWidth: "calc(100vw - 32px)", display: "block", background: "var(--surface)", border: "1px solid var(--border)", borderRadius: 10, boxShadow: "0 16px 44px rgba(11,13,17,.18)", padding: 8 },
3919
- children: /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("span", { className: "fd-stack", role: "menu", "aria-label": "Add filter", style: { gap: 2 }, children: [
3920
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-overline fd-muted", style: { padding: "4px 8px 6px" }, children: "Add filter" }),
3921
- addableFields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3922
- "button",
3923
- {
3924
- type: "button",
3925
- role: "menuitem",
3926
- className: "fd-row",
3927
- onClick: () => startEditing(f.key),
3928
- style: { gap: 8, padding: "4px 8px", borderRadius: 7, border: 0, width: "100%", textAlign: "left", cursor: "pointer", background: "transparent" },
3929
- children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "fd-body-sm", style: { flex: 1, color: "var(--text)" }, children: f.label })
3930
- },
3931
- f.key
3932
- ))
3933
- ] })
4007
+ open: panel?.kind === "add",
4008
+ anchorRef: addAnchorRef,
4009
+ onClose: closeEditing,
4010
+ placement: align === "right" ? "bottom-end" : "bottom-start",
4011
+ minWidth: 200,
4012
+ role: "menu",
4013
+ label: "Add filter",
4014
+ children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Menu, { items: addMenuItems })
3934
4015
  }
3935
- ) }) : null
4016
+ )
3936
4017
  ] }) : null
3937
4018
  ] });
3938
4019
  }
@@ -3964,7 +4045,7 @@ function StatTile({ label, value, sub, delta, invert = false, loading = false, c
3964
4045
  }
3965
4046
 
3966
4047
  // src/components/data/CodeBlock.tsx
3967
- var React24 = __toESM(require("react"), 1);
4048
+ var React25 = __toESM(require("react"), 1);
3968
4049
  var import_jsx_runtime47 = require("react/jsx-runtime");
3969
4050
  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)$/;
3970
4051
  var RULES = {
@@ -4120,13 +4201,13 @@ function tokenize(src, lang) {
4120
4201
  return out;
4121
4202
  }
4122
4203
  function Highlighted({ code, lang }) {
4123
- const toks = React24.useMemo(() => tokenize(code, lang), [code, lang]);
4204
+ const toks = React25.useMemo(() => tokenize(code, lang), [code, lang]);
4124
4205
  return /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_jsx_runtime47.Fragment, { children: toks.map((tk, i) => tk.c ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { className: "tok-" + tk.c, children: tk.t }, i) : tk.t) });
4125
4206
  }
4126
4207
  function useCopy() {
4127
- const [done, setDone] = React24.useState(false);
4128
- const timer = React24.useRef(null);
4129
- React24.useEffect(() => () => {
4208
+ const [done, setDone] = React25.useState(false);
4209
+ const timer = React25.useRef(null);
4210
+ React25.useEffect(() => () => {
4130
4211
  if (timer.current) clearTimeout(timer.current);
4131
4212
  }, []);
4132
4213
  const copy = (text) => {
@@ -4162,8 +4243,8 @@ function CodeBlock({
4162
4243
  actions,
4163
4244
  className = ""
4164
4245
  }) {
4165
- const [wrap, setWrap] = React24.useState(!!wrapDefault);
4166
- const [open, setOpen] = React24.useState(false);
4246
+ const [wrap, setWrap] = React25.useState(!!wrapDefault);
4247
+ const [open, setOpen] = React25.useState(false);
4167
4248
  const [copied, copy] = useCopy();
4168
4249
  const body = String(code).replace(/\n$/, "");
4169
4250
  const lines = body.split("\n");
@@ -4192,7 +4273,7 @@ function CodeBlock({
4192
4273
  ] });
4193
4274
  }
4194
4275
  function DiffBlock({ diff = "", filename, collapseAfter = 34, className = "" }) {
4195
- const [open, setOpen] = React24.useState(false);
4276
+ const [open, setOpen] = React25.useState(false);
4196
4277
  const [copied, copy] = useCopy();
4197
4278
  const raw = String(diff).replace(/\n$/, "");
4198
4279
  const all = raw.split("\n").map((line) => {
@@ -4238,7 +4319,7 @@ function DiffBlock({ diff = "", filename, collapseAfter = 34, className = "" })
4238
4319
  }
4239
4320
 
4240
4321
  // src/components/data/Markdown.tsx
4241
- var React25 = __toESM(require("react"), 1);
4322
+ var React26 = __toESM(require("react"), 1);
4242
4323
  var import_jsx_runtime48 = require("react/jsx-runtime");
4243
4324
  var SAFE = /^(?:https?:|mailto:|tel:|#|\/|\.{1,2}\/)/i;
4244
4325
  var safeHref = (h) => SAFE.test(String(h || "").trim()) ? String(h).trim() : null;
@@ -4277,7 +4358,7 @@ function inlineNodes(src, ctx, keyBase) {
4277
4358
  const href = safeHref(g(2));
4278
4359
  out.push(href ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("a", { href, title: g(3) || void 0, target: /^https?:/i.test(href) ? "_blank" : void 0, rel: "noopener noreferrer", onClick: ctx.onLinkClick, children: inlineNodes(g(1), ctx, key()) }, key()) : /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { children: g(1) }, key()));
4279
4360
  } else if (kind === "cite") {
4280
- out.push(ctx.renderCitation ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(React25.Fragment, { children: ctx.renderCitation(g(1)) }, key()) : /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("sup", { className: "fd-md-cite", children: g(1) }, key()));
4361
+ out.push(ctx.renderCitation ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(React26.Fragment, { children: ctx.renderCitation(g(1)) }, key()) : /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("sup", { className: "fd-md-cite", children: g(1) }, key()));
4281
4362
  } else if (kind === "strongem") out.push(/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("strong", { children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("em", { children: inlineNodes(g(1), ctx, key()) }) }, key()));
4282
4363
  else if (kind === "strong") out.push(/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("strong", { children: inlineNodes(g(1) || g(2), ctx, key()) }, key()));
4283
4364
  else if (kind === "em") out.push(/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("em", { children: inlineNodes(g(1) || g(2), ctx, key()) }, key()));
@@ -4442,7 +4523,7 @@ function Markdown({
4442
4523
  onLinkClick
4443
4524
  }) {
4444
4525
  const ctx = { allowImages, headingOffset, renderCitation, onLinkClick, codeProps };
4445
- const blocks = React25.useMemo(() => parseBlocks(source), [source]);
4526
+ const blocks = React26.useMemo(() => parseBlocks(source), [source]);
4446
4527
  return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: ["fd-md", className].filter(Boolean).join(" "), children: renderBlocks(blocks, ctx, "b") });
4447
4528
  }
4448
4529
  function MarkdownInline({ source = "", className = "", onLinkClick }) {
@@ -4556,7 +4637,7 @@ function Pagination({
4556
4637
  }
4557
4638
 
4558
4639
  // src/components/data/RelativeTime.tsx
4559
- var React26 = __toESM(require("react"), 1);
4640
+ var React27 = __toESM(require("react"), 1);
4560
4641
  var import_jsx_runtime51 = require("react/jsx-runtime");
4561
4642
  var MIN = 6e4;
4562
4643
  var HOUR = 36e5;
@@ -4608,9 +4689,9 @@ function formatDuration(ms) {
4608
4689
  return h + "h" + (m % 60 ? " " + m % 60 + "m" : "");
4609
4690
  }
4610
4691
  function RelativeTime({ value, prefix, className = "", refresh: refresh2 = true, ...rest }) {
4611
- const [, force] = React26.useState(0);
4692
+ const [, force] = React27.useState(0);
4612
4693
  const t = value instanceof Date ? value.getTime() : Date.parse(value);
4613
- React26.useEffect(() => {
4694
+ React27.useEffect(() => {
4614
4695
  if (!refresh2 || !t || isNaN(t)) return;
4615
4696
  const abs = Math.abs(t - Date.now());
4616
4697
  if (abs > DAY) return;
@@ -4626,7 +4707,7 @@ function RelativeTime({ value, prefix, className = "", refresh: refresh2 = true,
4626
4707
  }
4627
4708
 
4628
4709
  // src/components/data/ScoreMeter.tsx
4629
- var React27 = __toESM(require("react"), 1);
4710
+ var React28 = __toESM(require("react"), 1);
4630
4711
  var import_jsx_runtime52 = require("react/jsx-runtime");
4631
4712
  function ScoreMeter({
4632
4713
  score,
@@ -4641,10 +4722,10 @@ function ScoreMeter({
4641
4722
  breakdownWidth = 280,
4642
4723
  className = ""
4643
4724
  }) {
4644
- const [open, setOpen] = React27.useState(false);
4645
- const ref = React27.useRef(null);
4725
+ const [open, setOpen] = React28.useState(false);
4726
+ const ref = React28.useRef(null);
4646
4727
  const value = Math.max(0, Math.min(100, Math.round(Number(score) || 0)));
4647
- const body = /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(React27.Fragment, { children: [
4728
+ const body = /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(React28.Fragment, { children: [
4648
4729
  /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("span", { className: "fd-scoremeter-rail", style: { width }, "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(SegmentedMeter, { total: 100, segments, height, showTotal: false, showPercent: false, legend: false }) }),
4649
4730
  showScore ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("span", { className: "fd-scoremeter-value fd-tabular", "aria-hidden": "true", children: value }) : null
4650
4731
  ] });
@@ -4663,7 +4744,7 @@ function ScoreMeter({
4663
4744
  }
4664
4745
  );
4665
4746
  }
4666
- return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(React27.Fragment, { children: [
4747
+ return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(React28.Fragment, { children: [
4667
4748
  /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
4668
4749
  "button",
4669
4750
  {
@@ -4694,13 +4775,13 @@ function ScoreMeter({
4694
4775
  }
4695
4776
 
4696
4777
  // src/components/data/SortMenu.tsx
4697
- var React28 = __toESM(require("react"), 1);
4778
+ var React29 = __toESM(require("react"), 1);
4698
4779
  var import_jsx_runtime53 = require("react/jsx-runtime");
4699
4780
  function SortMenu({ fields = [], sort, onSort, align = "right", size = "md", className = "", ...rest }) {
4700
- const [open, setOpen] = React28.useState(false);
4781
+ const [open, setOpen] = React29.useState(false);
4701
4782
  const zIndex = useOverlayLayer(open);
4702
- const ref = React28.useRef(null);
4703
- React28.useEffect(() => {
4783
+ const ref = React29.useRef(null);
4784
+ React29.useEffect(() => {
4704
4785
  if (!open) return;
4705
4786
  const away = (e) => {
4706
4787
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -4762,7 +4843,7 @@ function SortMenu({ fields = [], sort, onSort, align = "right", size = "md", cla
4762
4843
  }
4763
4844
 
4764
4845
  // src/components/data/StepList.tsx
4765
- var React29 = __toESM(require("react"), 1);
4846
+ var React30 = __toESM(require("react"), 1);
4766
4847
  var import_jsx_runtime54 = require("react/jsx-runtime");
4767
4848
  var KIND_ICON = { tool: "wrench", search: "magnifying-glass", read: "file-text", write: "pencil-simple", reasoning: "brain", run: "terminal", fetch: "cloud-arrow-down" };
4768
4849
  function StepList({
@@ -4775,9 +4856,9 @@ function StepList({
4775
4856
  dense = false,
4776
4857
  className = ""
4777
4858
  }) {
4778
- const [openState, setOpenState] = React29.useState(defaultOpen);
4859
+ const [openState, setOpenState] = React30.useState(defaultOpen);
4779
4860
  const open = openProp == null ? openState : openProp;
4780
- const [expanded, setExpanded] = React29.useState({});
4861
+ const [expanded, setExpanded] = React30.useState({});
4781
4862
  if (!steps.length) return null;
4782
4863
  const running = steps.some((s) => s.status === "running");
4783
4864
  const failed = steps.filter((s) => s.status === "error").length;
@@ -4835,7 +4916,7 @@ function StepList({
4835
4916
  }
4836
4917
 
4837
4918
  // src/components/data/VirtualList.tsx
4838
- var React30 = __toESM(require("react"), 1);
4919
+ var React31 = __toESM(require("react"), 1);
4839
4920
  var import_jsx_runtime55 = require("react/jsx-runtime");
4840
4921
  var VIRTUAL_LIST_BUFFER_ROWS = 20;
4841
4922
  var VIRTUAL_LIST_ROW_GAP = 6;
@@ -4887,35 +4968,35 @@ function VirtualList({
4887
4968
  className = "",
4888
4969
  style
4889
4970
  }) {
4890
- const [scrollTop, setScrollTop] = React30.useState(0);
4891
- const loaded = React30.useMemo(
4971
+ const [scrollTop, setScrollTop] = React31.useState(0);
4972
+ const loaded = React31.useMemo(
4892
4973
  () => ({ firstItemIndex, count: items.length }),
4893
4974
  [firstItemIndex, items.length]
4894
4975
  );
4895
4976
  const total = Math.max(totalCount == null ? 0 : totalCount, firstItemIndex + items.length);
4896
4977
  const bandKey = firstItemIndex + ":" + items.length;
4897
- const win = React30.useMemo(
4978
+ const win = React31.useMemo(
4898
4979
  () => computeVirtualWindow({ scrollTop, viewportHeight: height, itemHeight, itemCount: total, loaded }),
4899
4980
  [scrollTop, height, itemHeight, total, loaded]
4900
4981
  );
4901
- const need = React30.useMemo(() => computeNeedMore(win, loaded), [win, loaded]);
4982
+ const need = React31.useMemo(() => computeNeedMore(win, loaded), [win, loaded]);
4902
4983
  const wantStart = need.start && hasMore?.start !== false;
4903
4984
  const wantEnd = need.end && hasMore?.end !== false;
4904
4985
  const bucket2 = Math.floor(win.visibleStart / VIRTUAL_LIST_BUFFER_ROWS);
4905
4986
  const askKey = bandKey + "@" + bucket2;
4906
- const asked = React30.useRef({ start: null, end: null });
4907
- React30.useEffect(() => {
4987
+ const asked = React31.useRef({ start: null, end: null });
4988
+ React31.useEffect(() => {
4908
4989
  if (loading || !wantStart || asked.current.start === askKey) return;
4909
4990
  asked.current.start = askKey;
4910
4991
  onNeedMore("start", win);
4911
4992
  }, [wantStart, loading, askKey, onNeedMore, win]);
4912
- React30.useEffect(() => {
4993
+ React31.useEffect(() => {
4913
4994
  if (loading || !wantEnd || asked.current.end === askKey) return;
4914
4995
  asked.current.end = askKey;
4915
4996
  onNeedMore("end", win);
4916
4997
  }, [wantEnd, loading, askKey, onNeedMore, win]);
4917
- const lastKeysRef = React30.useRef(null);
4918
- const newKeys = React30.useMemo(() => {
4998
+ const lastKeysRef = React31.useRef(null);
4999
+ const newKeys = React31.useMemo(() => {
4919
5000
  const prev = lastKeysRef.current;
4920
5001
  if (!prev) return /* @__PURE__ */ new Set();
4921
5002
  const fresh = /* @__PURE__ */ new Set();
@@ -4925,7 +5006,7 @@ function VirtualList({
4925
5006
  }
4926
5007
  return fresh;
4927
5008
  }, [items]);
4928
- React30.useEffect(() => {
5009
+ React31.useEffect(() => {
4929
5010
  lastKeysRef.current = new Set(items.map(keyOf));
4930
5011
  }, [items]);
4931
5012
  const gapStyle = { "--fd-vlist-gap": rowGap + "px" };
@@ -4970,7 +5051,7 @@ function VirtualList({
4970
5051
  }
4971
5052
 
4972
5053
  // src/components/data/EntityRow.tsx
4973
- var React31 = __toESM(require("react"), 1);
5054
+ var React32 = __toESM(require("react"), 1);
4974
5055
  var import_jsx_runtime56 = require("react/jsx-runtime");
4975
5056
  var ENTITY_ROW_METRIC_WIDTH = 74;
4976
5057
  var METRIC_TONE_COLOR = {
@@ -4983,7 +5064,7 @@ function EntityRowMetrics({ metrics, className = "", style }) {
4983
5064
  const key = m.id || m.label || i;
4984
5065
  const className2 = ["fd-erow-metric", m.tone && m.tone !== "default" ? "is-" + m.tone : "", m.tooltip != null ? "is-described" : ""].filter(Boolean).join(" ");
4985
5066
  const tone = { color: METRIC_TONE_COLOR[m.tone || "default"] };
4986
- const content = /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(React31.Fragment, { children: [
5067
+ const content = /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(React32.Fragment, { children: [
4987
5068
  m.icon ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("i", { className: "ph ph-" + m.icon, "aria-hidden": "true" }) : null,
4988
5069
  /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("span", { className: "fd-erow-metric-value fd-tabular", children: m.value }),
4989
5070
  /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("span", { className: "fd-sr", children: " " + m.label })
@@ -5075,7 +5156,7 @@ function EntityRow({
5075
5156
  }
5076
5157
 
5077
5158
  // src/components/data/TransferList.tsx
5078
- var React32 = __toESM(require("react"), 1);
5159
+ var React33 = __toESM(require("react"), 1);
5079
5160
  var import_react_dom5 = require("react-dom");
5080
5161
 
5081
5162
  // src/components/forms/Input.tsx
@@ -5181,12 +5262,12 @@ function TransferList({
5181
5262
  listHeight = 440,
5182
5263
  className = ""
5183
5264
  }) {
5184
- const [drag, setDrag] = React32.useState(null);
5185
- const [dragOverSide, setDragOverSide] = React32.useState(null);
5186
- const dragRef = React32.useRef(null);
5187
- const armedRef = React32.useRef(null);
5188
- const sideRefs = React32.useRef({ left: null, right: null });
5189
- const hitTestSide = React32.useCallback((x, y) => {
5265
+ const [drag, setDrag] = React33.useState(null);
5266
+ const [dragOverSide, setDragOverSide] = React33.useState(null);
5267
+ const dragRef = React33.useRef(null);
5268
+ const armedRef = React33.useRef(null);
5269
+ const sideRefs = React33.useRef({ left: null, right: null });
5270
+ const hitTestSide = React33.useCallback((x, y) => {
5190
5271
  for (const side of ["left", "right"]) {
5191
5272
  const el = sideRefs.current[side];
5192
5273
  if (!el) continue;
@@ -5195,7 +5276,7 @@ function TransferList({
5195
5276
  }
5196
5277
  return null;
5197
5278
  }, []);
5198
- const findItem = React32.useCallback(
5279
+ const findItem = React33.useCallback(
5199
5280
  (side, key) => (side === "left" ? left.items : right.items).find((it) => keyOf(it) === key),
5200
5281
  [left.items, right.items, keyOf]
5201
5282
  );
@@ -5217,7 +5298,7 @@ function TransferList({
5217
5298
  height: rect.height
5218
5299
  };
5219
5300
  };
5220
- React32.useEffect(() => {
5301
+ React33.useEffect(() => {
5221
5302
  const onMovePointer = (e) => {
5222
5303
  const armed = armedRef.current;
5223
5304
  if (!armed || e.pointerId !== armed.pointerId) return;
@@ -5270,7 +5351,7 @@ function TransferList({
5270
5351
  window.removeEventListener("pointercancel", endDrag);
5271
5352
  };
5272
5353
  }, [hitTestSide, findItem, onMove]);
5273
- React32.useEffect(() => {
5354
+ React33.useEffect(() => {
5274
5355
  if (!drag || typeof document === "undefined") return;
5275
5356
  const prev = document.body.style.userSelect;
5276
5357
  document.body.style.userSelect = "none";
@@ -5378,11 +5459,11 @@ function TransferList({
5378
5459
  }
5379
5460
 
5380
5461
  // src/components/forms/Checkbox.tsx
5381
- var React33 = __toESM(require("react"), 1);
5462
+ var React34 = __toESM(require("react"), 1);
5382
5463
  var import_jsx_runtime60 = require("react/jsx-runtime");
5383
5464
  function Checkbox({ label, description, card = false, indeterminate = false, className = "", ...rest }) {
5384
- const ref = React33.useRef(null);
5385
- React33.useEffect(() => {
5465
+ const ref = React34.useRef(null);
5466
+ React34.useEffect(() => {
5386
5467
  if (ref.current) ref.current.indeterminate = indeterminate;
5387
5468
  }, [indeterminate]);
5388
5469
  return /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)("label", { className: ["fd-choice", card ? "fd-choice-card" : "", className].filter(Boolean).join(" "), children: [
@@ -5426,7 +5507,7 @@ function Switch({ label, description, className = "", ...rest }) {
5426
5507
  }
5427
5508
 
5428
5509
  // src/components/forms/Select.tsx
5429
- var React34 = __toESM(require("react"), 1);
5510
+ var React35 = __toESM(require("react"), 1);
5430
5511
  var import_jsx_runtime63 = require("react/jsx-runtime");
5431
5512
  var norm = (o) => typeof o === "string" ? { value: o, label: o } : o;
5432
5513
  function Select({
@@ -5453,13 +5534,12 @@ function Select({
5453
5534
  const isOn = (v) => multiple ? vals.includes(v) : v === value;
5454
5535
  const hasSearch = searchable === void 0 ? opts.length > 8 : searchable;
5455
5536
  const { fieldId, labelId, ariaLabelledBy } = useTriggerLabelling(label, id);
5456
- const [open, setOpen] = React34.useState(false);
5457
- const [q, setQ] = React34.useState("");
5458
- const [active, setActive] = React34.useState(-1);
5459
- const rootRef = React34.useRef(null);
5460
- const boxRef = React34.useRef(null);
5461
- const listRef = React34.useRef(null);
5462
- const typeBuf = React34.useRef({ s: "", t: 0 });
5537
+ const [open, setOpen] = React35.useState(false);
5538
+ const [q, setQ] = React35.useState("");
5539
+ const rootRef = React35.useRef(null);
5540
+ const boxRef = React35.useRef(null);
5541
+ const listRef = React35.useRef(null);
5542
+ const typeBuf = React35.useRef({ s: "", t: 0 });
5463
5543
  const selected = multiple ? null : opts.find((o) => o.value === value);
5464
5544
  const chosen = multiple ? opts.filter((o) => vals.includes(o.value)) : [];
5465
5545
  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;
@@ -5476,23 +5556,16 @@ function Select({
5476
5556
  fire(o.value);
5477
5557
  setOpen(false);
5478
5558
  };
5559
+ const nav = useListboxNav({ count: visible.length, onSelect: (i) => pick(visible[i]) });
5560
+ useListboxScrollIntoView(nav.active, listRef, open);
5479
5561
  const toggle = () => {
5480
5562
  if (disabled || loading) return;
5481
5563
  if (!open) {
5482
5564
  setQ("");
5483
- setActive(Math.max(0, opts.findIndex((o) => isOn(o.value))));
5565
+ nav.setActive(Math.max(0, opts.findIndex((o) => isOn(o.value))));
5484
5566
  }
5485
5567
  setOpen(!open);
5486
5568
  };
5487
- React34.useEffect(() => {
5488
- if (!open || active < 0 || !listRef.current) return;
5489
- const el = listRef.current.querySelector('[data-i="' + active + '"]');
5490
- if (el) {
5491
- const L = listRef.current, top = el.offsetTop - L.offsetTop;
5492
- if (top < L.scrollTop) L.scrollTop = top - 4;
5493
- else if (top + el.offsetHeight > L.scrollTop + L.clientHeight) L.scrollTop = top + el.offsetHeight - L.clientHeight + 4;
5494
- }
5495
- }, [active, open]);
5496
5569
  const onKey = (e) => {
5497
5570
  if (disabled) return;
5498
5571
  if (!open && (e.key === "Enter" || e.key === " " || e.key === "ArrowDown" || e.key === "ArrowUp")) {
@@ -5514,19 +5587,19 @@ function Select({
5514
5587
  setOpen(false);
5515
5588
  } else if (e.key === "ArrowDown") {
5516
5589
  e.preventDefault();
5517
- setActive((a) => Math.min(visible.length - 1, a + 1));
5590
+ nav.move(1);
5518
5591
  } else if (e.key === "ArrowUp") {
5519
5592
  e.preventDefault();
5520
- setActive((a) => Math.max(0, a - 1));
5593
+ nav.move(-1);
5521
5594
  } else if (e.key === "Home") {
5522
5595
  e.preventDefault();
5523
- setActive(0);
5596
+ nav.first();
5524
5597
  } else if (e.key === "End") {
5525
5598
  e.preventDefault();
5526
- setActive(visible.length - 1);
5599
+ nav.last();
5527
5600
  } else if (e.key === "Enter") {
5528
5601
  e.preventDefault();
5529
- if (visible[active]) pick(visible[active]);
5602
+ nav.activate();
5530
5603
  } else if (e.key === "Tab") setOpen(false);
5531
5604
  };
5532
5605
  const groups = [];
@@ -5599,13 +5672,13 @@ function Select({
5599
5672
  onKeyDown: onKey,
5600
5673
  onChange: (e) => {
5601
5674
  setQ(e.target.value);
5602
- setActive(0);
5675
+ nav.setActive(0);
5603
5676
  }
5604
5677
  }
5605
5678
  ),
5606
5679
  q ? /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { className: "fd-body-sm fd-muted", children: visible.length }) : null
5607
5680
  ] }) : void 0,
5608
- footer: multiple ? /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)(React34.Fragment, { children: [
5681
+ footer: multiple ? /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)(React35.Fragment, { children: [
5609
5682
  /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(
5610
5683
  "button",
5611
5684
  {
@@ -5636,7 +5709,7 @@ function Select({
5636
5709
  'Nothing matches "',
5637
5710
  q,
5638
5711
  '".'
5639
- ] }) : groups.map((grp) => /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)(React34.Fragment, { children: [
5712
+ ] }) : groups.map((grp) => /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)(React35.Fragment, { children: [
5640
5713
  grp.g ? /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("div", { className: "fd-pop-group", children: grp.g }) : null,
5641
5714
  grp.items.map(({ o, i }) => /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)(
5642
5715
  "button",
@@ -5646,8 +5719,8 @@ function Select({
5646
5719
  role: "option",
5647
5720
  "aria-selected": isOn(o.value),
5648
5721
  "aria-disabled": o.disabled || void 0,
5649
- className: ["fd-opt", i === active ? "is-active" : "", isOn(o.value) ? "is-selected" : ""].filter(Boolean).join(" "),
5650
- onMouseEnter: () => setActive(i),
5722
+ className: ["fd-opt", i === nav.active ? "is-active" : "", isOn(o.value) ? "is-selected" : ""].filter(Boolean).join(" "),
5723
+ onMouseEnter: () => nav.setActive(i),
5651
5724
  onClick: () => pick(o),
5652
5725
  children: [
5653
5726
  multiple ? /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { "aria-hidden": "true", style: { flex: "none", display: "grid", placeItems: "center", width: 16, height: 16, borderRadius: 4, border: "1.5px solid " + (isOn(o.value) ? "var(--brand)" : "var(--border-strong, var(--border))"), background: isOn(o.value) ? "var(--brand)" : "var(--surface)", color: "#fff" }, children: isOn(o.value) ? /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("i", { className: "ph ph-check", style: { fontSize: 11 } }) : null }) : null,
@@ -5673,7 +5746,7 @@ function Select({
5673
5746
  }
5674
5747
 
5675
5748
  // src/components/forms/TimePicker.tsx
5676
- var React35 = __toESM(require("react"), 1);
5749
+ var React36 = __toESM(require("react"), 1);
5677
5750
  var import_jsx_runtime64 = require("react/jsx-runtime");
5678
5751
  var pad = (n) => String(n).padStart(2, "0");
5679
5752
  function ClockFace({ value = "09:00", onChange }) {
@@ -5682,9 +5755,9 @@ function ClockFace({ value = "09:00", onChange }) {
5682
5755
  if (isNaN(m)) m = 0;
5683
5756
  const pm = h24 >= 12;
5684
5757
  const h12 = h24 % 12 === 0 ? 12 : h24 % 12;
5685
- const [mode2, setMode] = React35.useState("h");
5686
- const faceRef = React35.useRef(null);
5687
- const dragging = React35.useRef(false);
5758
+ const [mode2, setMode] = React36.useState("h");
5759
+ const faceRef = React36.useRef(null);
5760
+ const dragging = React36.useRef(false);
5688
5761
  const set = (h, mm, isPm) => onChange((isPm ? h % 12 + 12 : h % 12) + ":" + pad(mm));
5689
5762
  const R = 108, NR = 80;
5690
5763
  const nums = mode2 === "h" ? Array.from({ length: 12 }, (_, i) => i + 1) : Array.from({ length: 12 }, (_, i) => i * 5);
@@ -5757,9 +5830,9 @@ function ClockFace({ value = "09:00", onChange }) {
5757
5830
  }
5758
5831
  function TimePicker({ label, help, error, required = false, disabled = false, value = "", onChange, placeholder = "Pick a time", id, className = "", style }) {
5759
5832
  const { fieldId, labelId, ariaLabelledBy } = useTriggerLabelling(label, id);
5760
- const [open, setOpen] = React35.useState(false);
5761
- const rootRef = React35.useRef(null);
5762
- const boxRef = React35.useRef(null);
5833
+ const [open, setOpen] = React36.useState(false);
5834
+ const rootRef = React36.useRef(null);
5835
+ const boxRef = React36.useRef(null);
5763
5836
  const disp = () => {
5764
5837
  if (!value) return "";
5765
5838
  const [h, m] = value.split(":").map(Number);
@@ -5802,7 +5875,7 @@ function TimePicker({ label, help, error, required = false, disabled = false, va
5802
5875
  offset: 6,
5803
5876
  width: 262,
5804
5877
  role: "presentation",
5805
- footer: /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)(React35.Fragment, { children: [
5878
+ footer: /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)(React36.Fragment, { children: [
5806
5879
  /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
5807
5880
  "button",
5808
5881
  {
@@ -5830,7 +5903,7 @@ function TimePicker({ label, help, error, required = false, disabled = false, va
5830
5903
  }
5831
5904
 
5832
5905
  // src/components/forms/Slider.tsx
5833
- var React36 = __toESM(require("react"), 1);
5906
+ var React37 = __toESM(require("react"), 1);
5834
5907
  var import_jsx_runtime65 = require("react/jsx-runtime");
5835
5908
  function Slider({
5836
5909
  label,
@@ -5848,7 +5921,7 @@ function Slider({
5848
5921
  ...rest
5849
5922
  }) {
5850
5923
  const fieldId = useFieldId(id);
5851
- const [dragging, setDragging] = React36.useState(false);
5924
+ const [dragging, setDragging] = React37.useState(false);
5852
5925
  const v = value === void 0 ? min : Number(value);
5853
5926
  const pct = max === min ? 0 : (v - min) / (max - min) * 100;
5854
5927
  return /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { className: ["fd-field", className].filter(Boolean).join(" "), children: [
@@ -5882,7 +5955,7 @@ function Slider({
5882
5955
  }
5883
5956
 
5884
5957
  // src/components/forms/RangeSlider.tsx
5885
- var React37 = __toESM(require("react"), 1);
5958
+ var React38 = __toESM(require("react"), 1);
5886
5959
  var import_jsx_runtime66 = require("react/jsx-runtime");
5887
5960
  function RangeSlider({
5888
5961
  label,
@@ -5902,9 +5975,9 @@ function RangeSlider({
5902
5975
  }) {
5903
5976
  const fmt2 = format || ((v) => String(v));
5904
5977
  const [a, b] = value || [min, max];
5905
- const [drag, setDrag] = React37.useState(null);
5906
- const [focus, setFocus] = React37.useState(null);
5907
- const railRef = React37.useRef(null);
5978
+ const [drag, setDrag] = React38.useState(null);
5979
+ const [focus, setFocus] = React38.useState(null);
5980
+ const railRef = React38.useRef(null);
5908
5981
  const pct = (v) => max === min ? 0 : (v - min) / (max - min) * 100;
5909
5982
  const clampPair = (i, v) => {
5910
5983
  v = Math.min(max, Math.max(min, Math.round(v / step) * step));
@@ -5919,7 +5992,7 @@ function RangeSlider({
5919
5992
  const r = railRef.current.getBoundingClientRect();
5920
5993
  return min + Math.min(1, Math.max(0, (e.clientX - r.left) / r.width)) * (max - min);
5921
5994
  };
5922
- React37.useEffect(() => {
5995
+ React38.useEffect(() => {
5923
5996
  if (drag === null) return;
5924
5997
  const mv = (e) => onChange && onChange(clampPair(drag, fromEvent(e)));
5925
5998
  const upH = () => setDrag(null);
@@ -6000,7 +6073,7 @@ function RangeSlider({
6000
6073
  /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "fd-range" + (hasLabels ? " has-labels" : ""), onPointerDown: onRailDown, children: [
6001
6074
  /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { className: "fd-range-rail", ref: railRef }),
6002
6075
  /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { className: "fd-range-fill", style: { left: pct(a) + "%", width: pct(b) - pct(a) + "%" } }),
6003
- marks.map((m) => /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(React37.Fragment, { children: [
6076
+ marks.map((m) => /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(React38.Fragment, { children: [
6004
6077
  /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { className: "fd-range-mark" + (m.value >= a && m.value <= b ? " is-in" : ""), style: { left: pct(m.value) + "%" } }),
6005
6078
  m.label ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { className: "fd-range-mark-label", style: { left: pct(m.value) + "%" }, children: m.label }) : null
6006
6079
  ] }, m.value)),
@@ -6029,7 +6102,7 @@ function RangeSlider({
6029
6102
  }
6030
6103
 
6031
6104
  // src/components/forms/Dropzone.tsx
6032
- var React38 = __toESM(require("react"), 1);
6105
+ var React39 = __toESM(require("react"), 1);
6033
6106
  var import_jsx_runtime67 = require("react/jsx-runtime");
6034
6107
  function Dropzone({
6035
6108
  onFiles,
@@ -6044,8 +6117,8 @@ function Dropzone({
6044
6117
  className = "",
6045
6118
  style
6046
6119
  }) {
6047
- const [over, setOver] = React38.useState(false);
6048
- const depth = React38.useRef(0);
6120
+ const [over, setOver] = React39.useState(false);
6121
+ const depth = React39.useRef(0);
6049
6122
  const has = (e) => {
6050
6123
  const dt = e.dataTransfer;
6051
6124
  if (!dt) return false;
@@ -6107,8 +6180,8 @@ function FilePickButton({
6107
6180
  className = "",
6108
6181
  children
6109
6182
  }) {
6110
- const ref = React38.useRef(null);
6111
- return /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)(React38.Fragment, { children: [
6183
+ const ref = React39.useRef(null);
6184
+ return /* @__PURE__ */ (0, import_jsx_runtime67.jsxs)(React39.Fragment, { children: [
6112
6185
  /* @__PURE__ */ (0, import_jsx_runtime67.jsx)(
6113
6186
  "button",
6114
6187
  {
@@ -6141,10 +6214,10 @@ function FilePickButton({
6141
6214
  }
6142
6215
  function useStagedFiles(upload, opts) {
6143
6216
  const o = opts || {};
6144
- const [items, setItems] = React38.useState([]);
6145
- const controllers = React38.useRef({});
6217
+ const [items, setItems] = React39.useState([]);
6218
+ const controllers = React39.useRef({});
6146
6219
  const patch = (id, next) => setItems((list) => list.map((f) => f.id === id ? Object.assign({}, f, next) : f));
6147
- const run = React38.useCallback((att) => {
6220
+ const run = React39.useCallback((att) => {
6148
6221
  if (!upload) return;
6149
6222
  const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
6150
6223
  controllers.current[att.id] = ac;
@@ -6161,14 +6234,14 @@ function useStagedFiles(upload, opts) {
6161
6234
  delete controllers.current[att.id];
6162
6235
  });
6163
6236
  }, [upload]);
6164
- const add = React38.useCallback((files) => {
6237
+ const add = React39.useCallback((files) => {
6165
6238
  if (!upload) return [];
6166
6239
  const atts = Array.from(files).map((f) => toAttachment(f));
6167
6240
  setItems((list) => list.concat(atts));
6168
6241
  atts.forEach(run);
6169
6242
  return atts;
6170
6243
  }, [upload, run]);
6171
- const remove = React38.useCallback((att) => {
6244
+ const remove = React39.useCallback((att) => {
6172
6245
  const ac = controllers.current[att.id];
6173
6246
  if (ac) {
6174
6247
  try {
@@ -6179,10 +6252,10 @@ function useStagedFiles(upload, opts) {
6179
6252
  }
6180
6253
  setItems((list) => list.filter((f) => f.id !== att.id));
6181
6254
  }, []);
6182
- const retry = React38.useCallback((att) => {
6255
+ const retry = React39.useCallback((att) => {
6183
6256
  run(att);
6184
6257
  }, [run]);
6185
- const clear = React38.useCallback(() => {
6258
+ const clear = React39.useCallback(() => {
6186
6259
  Object.values(controllers.current).forEach((ac) => {
6187
6260
  try {
6188
6261
  ac && ac.abort();
@@ -6306,7 +6379,7 @@ function FileGrid({ files = [], onOpen, onRemove, onRetry, tiles = true, maxHeig
6306
6379
  }
6307
6380
 
6308
6381
  // src/components/forms/MarkdownEditor.tsx
6309
- var React39 = __toESM(require("react"), 1);
6382
+ var React40 = __toESM(require("react"), 1);
6310
6383
  var import_jsx_runtime69 = require("react/jsx-runtime");
6311
6384
  var isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent || "");
6312
6385
  var INLINE_RE = /(\*\*\*[^*\n]+\*\*\*|\*\*[^*\n]+\*\*|__[^_\n]+__|~~[^~\n]+~~|`[^`\n]+`|\*[^*\s][^*\n]*\*|(?<![A-Za-z0-9_])_[^_\s][^_\n]*_|\[[^\]\n]*\]\([^)\s\n]*\)|https?:\/\/\S+)/g;
@@ -6520,7 +6593,7 @@ function syncDom(root, value) {
6520
6593
  while (root.children.length > lines.length) root.removeChild(root.lastChild);
6521
6594
  }
6522
6595
  var LIST_CONT = RE_LI;
6523
- var MarkdownEditor = React39.forwardRef(function MarkdownEditor2({
6596
+ var MarkdownEditor = React40.forwardRef(function MarkdownEditor2({
6524
6597
  value = "",
6525
6598
  onChange,
6526
6599
  onSubmit,
@@ -6539,10 +6612,10 @@ var MarkdownEditor = React39.forwardRef(function MarkdownEditor2({
6539
6612
  className = "",
6540
6613
  id
6541
6614
  }, ref) {
6542
- const boxRef = React39.useRef(null);
6543
- const composing = React39.useRef(false);
6544
- const pendingCaret = React39.useRef(null);
6545
- React39.useLayoutEffect(() => {
6615
+ const boxRef = React40.useRef(null);
6616
+ const composing = React40.useRef(false);
6617
+ const pendingCaret = React40.useRef(null);
6618
+ React40.useLayoutEffect(() => {
6546
6619
  const root = boxRef.current;
6547
6620
  if (!root || composing.current) return;
6548
6621
  const active = document.activeElement === root || root.contains(document.activeElement);
@@ -6551,7 +6624,7 @@ var MarkdownEditor = React39.forwardRef(function MarkdownEditor2({
6551
6624
  pendingCaret.current = null;
6552
6625
  if (active && caret != null) placeCaret(root, caret);
6553
6626
  }, [value]);
6554
- React39.useEffect(() => {
6627
+ React40.useEffect(() => {
6555
6628
  if (autoFocus && boxRef.current) boxRef.current.focus();
6556
6629
  }, [autoFocus]);
6557
6630
  const caretNow = () => {
@@ -6618,7 +6691,7 @@ var MarkdownEditor = React39.forwardRef(function MarkdownEditor2({
6618
6691
  api.replaceRange(from, to, next, from + next.length);
6619
6692
  }
6620
6693
  };
6621
- React39.useImperativeHandle(ref, () => api);
6694
+ React40.useImperativeHandle(ref, () => api);
6622
6695
  function detect(text, caret) {
6623
6696
  if (!onTrigger) return;
6624
6697
  const upto = text.slice(0, caret);
@@ -6770,16 +6843,16 @@ var MarkdownEditor = React39.forwardRef(function MarkdownEditor2({
6770
6843
  });
6771
6844
 
6772
6845
  // src/components/platform/AccountMenu.tsx
6773
- var React41 = __toESM(require("react"), 1);
6846
+ var React42 = __toESM(require("react"), 1);
6774
6847
 
6775
6848
  // src/kits/sessionContext.tsx
6776
- var React40 = __toESM(require("react"), 1);
6777
- var SessionContext = React40.createContext(null);
6849
+ var React41 = __toESM(require("react"), 1);
6850
+ var SessionContext = React41.createContext(null);
6778
6851
  function SessionProvider({ value, children }) {
6779
- return React40.createElement(SessionContext.Provider, { value }, children);
6852
+ return React41.createElement(SessionContext.Provider, { value }, children);
6780
6853
  }
6781
6854
  function useSessionContext() {
6782
- const ctx = React40.useContext(SessionContext);
6855
+ const ctx = React41.useContext(SessionContext);
6783
6856
  if (!ctx) {
6784
6857
  throw new Error(
6785
6858
  "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."
@@ -6819,11 +6892,11 @@ function AccountMenu({
6819
6892
  ...rest
6820
6893
  }) {
6821
6894
  const { user, roles, can: can2, signOut: ctxSignOut, demo } = useSessionContext();
6822
- const [open, setOpen] = React41.useState(false);
6823
- const [switching, setSwitching] = React41.useState(false);
6824
- const [signingOut, setSigningOut] = React41.useState(false);
6825
- const [signOutError, setSignOutError] = React41.useState(null);
6826
- const ref = React41.useRef(null);
6895
+ const [open, setOpen] = React42.useState(false);
6896
+ const [switching, setSwitching] = React42.useState(false);
6897
+ const [signingOut, setSigningOut] = React42.useState(false);
6898
+ const [signOutError, setSignOutError] = React42.useState(null);
6899
+ const ref = React42.useRef(null);
6827
6900
  const visibleLinks = links.filter((l) => !l.perm || can2(l.perm));
6828
6901
  const visibleGroups = groups.map((g) => ({ label: g.label, links: g.links.filter((l) => !l.perm || can2(l.perm)) })).filter((g) => g.links.length);
6829
6902
  const closeMenu = () => {
@@ -6948,13 +7021,13 @@ function AccountMenu({
6948
7021
  }
6949
7022
 
6950
7023
  // src/components/platform/AppSwitcher.tsx
6951
- var React42 = __toESM(require("react"), 1);
7024
+ var React43 = __toESM(require("react"), 1);
6952
7025
  var import_jsx_runtime71 = require("react/jsx-runtime");
6953
7026
  function AppSwitcher({ apps: appsProp, onNavigate, className = "" }) {
6954
7027
  const { apps: contextApps } = useSessionContext();
6955
7028
  const apps = appsProp ?? contextApps ?? [];
6956
- const [open, setOpen] = React42.useState(false);
6957
- const anchorRef = React42.useRef(null);
7029
+ const [open, setOpen] = React43.useState(false);
7030
+ const anchorRef = React43.useRef(null);
6958
7031
  if (!apps.length) return null;
6959
7032
  const items = apps.map((app) => ({
6960
7033
  id: app.key,
@@ -6968,7 +7041,7 @@ function AppSwitcher({ apps: appsProp, onNavigate, className = "" }) {
6968
7041
  window.location.href = app.url;
6969
7042
  }
6970
7043
  }));
6971
- return /* @__PURE__ */ (0, import_jsx_runtime71.jsxs)(React42.Fragment, { children: [
7044
+ return /* @__PURE__ */ (0, import_jsx_runtime71.jsxs)(React43.Fragment, { children: [
6972
7045
  /* @__PURE__ */ (0, import_jsx_runtime71.jsx)("span", { ref: anchorRef, className: ["fd-appswitch-anchor", className].filter(Boolean).join(" "), style: { display: "inline-flex" }, children: /* @__PURE__ */ (0, import_jsx_runtime71.jsx)(
6973
7046
  IconButton,
6974
7047
  {
@@ -6997,10 +7070,10 @@ function AppSwitcher({ apps: appsProp, onNavigate, className = "" }) {
6997
7070
  }
6998
7071
 
6999
7072
  // src/components/platform/AppearanceMenu.tsx
7000
- var React44 = __toESM(require("react"), 1);
7073
+ var React45 = __toESM(require("react"), 1);
7001
7074
 
7002
7075
  // src/kits/appearance.ts
7003
- var React43 = __toESM(require("react"), 1);
7076
+ var React44 = __toESM(require("react"), 1);
7004
7077
  var DEFAULT_STORAGE_KEY = "fd-appearance";
7005
7078
  var DEFAULT_PREFS = {
7006
7079
  theme: "system",
@@ -7095,11 +7168,11 @@ function applyPrefs(prefs, root = document.documentElement) {
7095
7168
  else root.removeAttribute("data-motion");
7096
7169
  }
7097
7170
  function useAppearancePrefs(store) {
7098
- const prefs = React43.useSyncExternalStore(store.subscribe, store.getSnapshot);
7099
- React43.useEffect(() => {
7171
+ const prefs = React44.useSyncExternalStore(store.subscribe, store.getSnapshot);
7172
+ React44.useEffect(() => {
7100
7173
  applyPrefs(prefs);
7101
7174
  }, [prefs]);
7102
- React43.useEffect(() => {
7175
+ React44.useEffect(() => {
7103
7176
  if (prefs.theme !== "system" || typeof window === "undefined" || typeof window.matchMedia !== "function") {
7104
7177
  return;
7105
7178
  }
@@ -7108,7 +7181,7 @@ function useAppearancePrefs(store) {
7108
7181
  mq.addEventListener("change", onChange);
7109
7182
  return () => mq.removeEventListener("change", onChange);
7110
7183
  }, [prefs]);
7111
- const update = React43.useCallback(
7184
+ const update = React44.useCallback(
7112
7185
  (patch) => {
7113
7186
  store.write({ ...store.getSnapshot(), ...patch });
7114
7187
  },
@@ -7131,15 +7204,15 @@ var TEXT_SCALE_OPTIONS = [
7131
7204
  { value: "xl", label: "XL" }
7132
7205
  ];
7133
7206
  function AppearanceMenu({ className = "", store: storeProp, storageKey, storage, defaults }) {
7134
- const store = React44.useMemo(
7207
+ const store = React45.useMemo(
7135
7208
  () => storeProp ?? createLocalStorageAppearanceStore({ storageKey, storage, defaults }),
7136
7209
  [storeProp, storageKey, storage, defaults]
7137
7210
  );
7138
7211
  const [prefs, update] = useAppearancePrefs(store);
7139
- const [open, setOpen] = React44.useState(false);
7140
- const anchorRef = React44.useRef(null);
7212
+ const [open, setOpen] = React45.useState(false);
7213
+ const anchorRef = React45.useRef(null);
7141
7214
  const patch = (p) => update(p);
7142
- return /* @__PURE__ */ (0, import_jsx_runtime72.jsxs)(React44.Fragment, { children: [
7215
+ return /* @__PURE__ */ (0, import_jsx_runtime72.jsxs)(React45.Fragment, { children: [
7143
7216
  /* @__PURE__ */ (0, import_jsx_runtime72.jsx)("span", { ref: anchorRef, className: ["fd-appearance-anchor", className].filter(Boolean).join(" "), style: { display: "inline-flex" }, children: /* @__PURE__ */ (0, import_jsx_runtime72.jsx)(
7144
7217
  IconButton,
7145
7218
  {
@@ -7208,14 +7281,14 @@ function AppearanceMenu({ className = "", store: storeProp, storageKey, storage,
7208
7281
  }
7209
7282
 
7210
7283
  // src/components/platform/ApiSpecBrowser.tsx
7211
- var React45 = __toESM(require("react"), 1);
7284
+ var React46 = __toESM(require("react"), 1);
7212
7285
  var import_jsx_runtime73 = require("react/jsx-runtime");
7213
7286
  var METHOD_TONE = { GET: "success", POST: "info", PATCH: "warning", PUT: "warning", DELETE: "danger" };
7214
7287
  function Json({ obj }) {
7215
7288
  return /* @__PURE__ */ (0, import_jsx_runtime73.jsx)("pre", { className: "fd-json", children: JSON.stringify(obj, null, 2) });
7216
7289
  }
7217
7290
  function Endpoint({ s, onRequest }) {
7218
- const [tried, setTried] = React45.useState(null);
7291
+ const [tried, setTried] = React46.useState(null);
7219
7292
  const run = async () => {
7220
7293
  setTried("busy");
7221
7294
  const t0 = (window.performance || Date).now();
@@ -7288,10 +7361,10 @@ function ApiSpecBrowser({
7288
7361
  className = "",
7289
7362
  ...rest
7290
7363
  }) {
7291
- const [q, setQ] = React45.useState("");
7292
- const [method, setMethod] = React45.useState(null);
7293
- const [mod, setMod] = React45.useState(null);
7294
- const [listOnly, setListOnly] = React45.useState(false);
7364
+ const [q, setQ] = React46.useState("");
7365
+ const [method, setMethod] = React46.useState(null);
7366
+ const [mod, setMod] = React46.useState(null);
7367
+ const [listOnly, setListOnly] = React46.useState(false);
7295
7368
  const activeModule = modules && modules.find((m) => m.id === mod);
7296
7369
  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())));
7297
7370
  const effGroups = groups && groups.length ? groups : [["All endpoints", spec.map((s) => s.id)]];
@@ -7370,7 +7443,7 @@ function ApiSpecBrowser({
7370
7443
  }
7371
7444
 
7372
7445
  // src/components/platform/ProfilePage.tsx
7373
- var React46 = __toESM(require("react"), 1);
7446
+ var React47 = __toESM(require("react"), 1);
7374
7447
  var import_jsx_runtime74 = require("react/jsx-runtime");
7375
7448
  function fieldInitialValue(field, user) {
7376
7449
  if (field.value) return field.value(user);
@@ -7406,21 +7479,21 @@ function ProfilePage({
7406
7479
  }) {
7407
7480
  const { user: sessionUser, roles, permissions, saveProfile: saveProfile2 } = useSessionContext();
7408
7481
  const user = userProp || sessionUser;
7409
- const initialFieldValues = React46.useMemo(() => {
7482
+ const initialFieldValues = React47.useMemo(() => {
7410
7483
  const values2 = {};
7411
7484
  for (const field of fields) values2[field.key] = fieldInitialValue(field, user);
7412
7485
  return values2;
7413
7486
  }, [user.id, fields]);
7414
- const initialNotify = React46.useMemo(() => {
7487
+ const initialNotify = React47.useMemo(() => {
7415
7488
  const values2 = {};
7416
7489
  for (const n of notifications) values2[n.key] = !!user.notify?.[n.key];
7417
7490
  return values2;
7418
7491
  }, [user.id, notifications]);
7419
- const [values, setValues] = React46.useState(initialFieldValues);
7420
- const [notify, setNotify] = React46.useState(initialNotify);
7421
- const [saving, setSaving] = React46.useState(false);
7422
- const [saved, setSaved] = React46.useState(false);
7423
- const [prevUserId, setPrevUserId] = React46.useState(user.id);
7492
+ const [values, setValues] = React47.useState(initialFieldValues);
7493
+ const [notify, setNotify] = React47.useState(initialNotify);
7494
+ const [saving, setSaving] = React47.useState(false);
7495
+ const [saved, setSaved] = React47.useState(false);
7496
+ const [prevUserId, setPrevUserId] = React47.useState(user.id);
7424
7497
  if (user.id !== prevUserId) {
7425
7498
  setPrevUserId(user.id);
7426
7499
  setValues(initialFieldValues);
@@ -7608,11 +7681,11 @@ function ProfilePage({
7608
7681
  }
7609
7682
 
7610
7683
  // src/components/platform/SignInPage.tsx
7611
- var React47 = __toESM(require("react"), 1);
7684
+ var React48 = __toESM(require("react"), 1);
7612
7685
  var import_jsx_runtime75 = require("react/jsx-runtime");
7613
7686
  function SignInPage({ productName, onSignIn, continueAs, error }) {
7614
- const [signingIn, setSigningIn] = React47.useState(false);
7615
- const [resuming, setResuming] = React47.useState(false);
7687
+ const [signingIn, setSigningIn] = React48.useState(false);
7688
+ const [resuming, setResuming] = React48.useState(false);
7616
7689
  function handleSignIn() {
7617
7690
  setSigningIn(true);
7618
7691
  onSignIn();
@@ -7801,10 +7874,10 @@ function GoogleGlyph() {
7801
7874
  }
7802
7875
 
7803
7876
  // src/components/platform/RoadmapTimeline.tsx
7804
- var React50 = __toESM(require("react"), 1);
7877
+ var React51 = __toESM(require("react"), 1);
7805
7878
 
7806
7879
  // src/kits/session.ts
7807
- var React48 = __toESM(require("react"), 1);
7880
+ var React49 = __toESM(require("react"), 1);
7808
7881
  var PERMISSION_CATALOG = [
7809
7882
  { group: "Plans", items: [
7810
7883
  { key: "plan.view", label: "View plans", detail: "Read any plan in the workspace." },
@@ -9371,8 +9444,8 @@ function roadmap(overrides) {
9371
9444
  };
9372
9445
  }
9373
9446
  function useSession() {
9374
- const [s, setS] = React48.useState(getSession);
9375
- React48.useEffect(() => subscribe(setS), []);
9447
+ const [s, setS] = React49.useState(getSession);
9448
+ React49.useEffect(() => subscribe(setS), []);
9376
9449
  return s;
9377
9450
  }
9378
9451
  var SessionKit = {
@@ -9405,7 +9478,7 @@ var SessionKit = {
9405
9478
  };
9406
9479
  function DemoSessionProvider({ children }) {
9407
9480
  const session = useSession();
9408
- const value = React48.useMemo(
9481
+ const value = React49.useMemo(
9409
9482
  () => ({
9410
9483
  user: session.user,
9411
9484
  roles: session.roles,
@@ -9421,11 +9494,11 @@ function DemoSessionProvider({ children }) {
9421
9494
  }),
9422
9495
  [session]
9423
9496
  );
9424
- return React48.createElement(SessionProvider, { value }, children);
9497
+ return React49.createElement(SessionProvider, { value }, children);
9425
9498
  }
9426
9499
 
9427
9500
  // src/kits/runtime.ts
9428
- var React49 = __toESM(require("react"), 1);
9501
+ var React50 = __toESM(require("react"), 1);
9429
9502
  var WIRED_ENDPOINTS = [
9430
9503
  // Nothing yet. Every id below would come from a real service:
9431
9504
  // "plan.get", "placements.list", …
@@ -9596,8 +9669,8 @@ var RuntimeKit = {
9596
9669
  };
9597
9670
  RuntimeKit.declare(FEATURE_NEEDS);
9598
9671
  function useRuntimeMode() {
9599
- const [m, setM] = React49.useState(RuntimeKit.getMode());
9600
- React49.useEffect(() => RuntimeKit.subscribe(setM), []);
9672
+ const [m, setM] = React50.useState(RuntimeKit.getMode());
9673
+ React50.useEffect(() => RuntimeKit.subscribe(setM), []);
9601
9674
  return m;
9602
9675
  }
9603
9676
  function useFeatureStatus(key) {
@@ -9680,12 +9753,12 @@ function RoadmapCard({ item, byKey, onOpenFlag }) {
9680
9753
  }
9681
9754
  function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
9682
9755
  const session = SessionKit.useSession();
9683
- const [project, setProject] = React50.useState("");
9684
- const [q, setQ] = React50.useState("");
9685
- const scrollRef = React50.useRef(null);
9686
- const nowRef = React50.useRef(null);
9687
- const rm = React50.useMemo(() => SessionKit.roadmap({ isComplete: RuntimeKit.isComplete }), [session]);
9688
- const byKey = React50.useMemo(() => {
9756
+ const [project, setProject] = React51.useState("");
9757
+ const [q, setQ] = React51.useState("");
9758
+ const scrollRef = React51.useRef(null);
9759
+ const nowRef = React51.useRef(null);
9760
+ const rm = React51.useMemo(() => SessionKit.roadmap({ isComplete: RuntimeKit.isComplete }), [session]);
9761
+ const byKey = React51.useMemo(() => {
9689
9762
  const m = {};
9690
9763
  rm.items.forEach((i) => {
9691
9764
  m[i.key] = i;
@@ -9694,7 +9767,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
9694
9767
  }, [rm]);
9695
9768
  const items = rm.items.filter((i) => (!project || i.project === project) && (!q || (i.label + " " + i.description + " " + (i.backend || "") + " " + i.key).toLowerCase().includes(q.toLowerCase())));
9696
9769
  const projects = [...new Set(rm.items.map((i) => i.project))];
9697
- React50.useEffect(() => {
9770
+ React51.useEffect(() => {
9698
9771
  let raf1 = 0, raf2 = 0;
9699
9772
  const place = () => {
9700
9773
  const box = scrollRef.current, mark = nowRef.current;
@@ -9768,7 +9841,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
9768
9841
  lastPhase = item.phase;
9769
9842
  const inPhase = items.filter((i) => i.phase === item.phase).length;
9770
9843
  const isBoundary = n === firstPending;
9771
- return /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)(React50.Fragment, { children: [
9844
+ return /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)(React51.Fragment, { children: [
9772
9845
  showPhase ? /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "fd-rm-era", children: [
9773
9846
  /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("span", { className: "fd-row", style: { gap: 8, flexWrap: "wrap", alignItems: "baseline" }, children: [
9774
9847
  /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("span", { style: { fontWeight: 700 }, children: item.phase === 0 ? "Shipped" : "Phase " + item.phase + " \u2014 " + item.phaseName }),
@@ -9812,7 +9885,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
9812
9885
  }
9813
9886
 
9814
9887
  // src/components/platform/ComingSoon.tsx
9815
- var React51 = __toESM(require("react"), 1);
9888
+ var React52 = __toESM(require("react"), 1);
9816
9889
  var import_react_dom6 = require("react-dom");
9817
9890
  var import_jsx_runtime77 = require("react/jsx-runtime");
9818
9891
  var BYPASS_STORE = "fd.soon.bypass.v1";
@@ -9830,8 +9903,8 @@ function writeBypassed(list) {
9830
9903
  }
9831
9904
  }
9832
9905
  function useBypass(key) {
9833
- const [on, setOn] = React51.useState(() => !!key && readBypassed().indexOf(key) >= 0);
9834
- React51.useEffect(() => {
9906
+ const [on, setOn] = React52.useState(() => !!key && readBypassed().indexOf(key) >= 0);
9907
+ React52.useEffect(() => {
9835
9908
  setOn(!!key && readBypassed().indexOf(key) >= 0);
9836
9909
  }, [key]);
9837
9910
  const set = (next) => {
@@ -9878,9 +9951,9 @@ function SoonCard({ label, detail, backend, eta, effort, onRoadmap, allowed, onB
9878
9951
  ] });
9879
9952
  }
9880
9953
  function useHoverCard(open) {
9881
- const anchor = React51.useRef(null);
9882
- const [pos, setPos] = React51.useState(null);
9883
- React51.useLayoutEffect(() => {
9954
+ const anchor = React52.useRef(null);
9955
+ const [pos, setPos] = React52.useState(null);
9956
+ React52.useLayoutEffect(() => {
9884
9957
  if (!open || !anchor.current) {
9885
9958
  setPos(null);
9886
9959
  return;
@@ -10007,10 +10080,10 @@ function ComingSoon({
10007
10080
  ] });
10008
10081
  }
10009
10082
  function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, onBypass, blur, tip, className, rest, children }) {
10010
- const [open, setOpen] = React51.useState(false);
10083
+ const [open, setOpen] = React52.useState(false);
10011
10084
  const [anchor, pos] = useHoverCard(open);
10012
10085
  const zIndex = useOverlayLayer(open);
10013
- const close = React51.useRef(null);
10086
+ const close = React52.useRef(null);
10014
10087
  const show = () => {
10015
10088
  if (close.current) {
10016
10089
  clearTimeout(close.current);
@@ -10026,7 +10099,7 @@ function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, o
10026
10099
  setOpen(false);
10027
10100
  }, 140);
10028
10101
  };
10029
- React51.useEffect(() => () => {
10102
+ React52.useEffect(() => () => {
10030
10103
  if (close.current) clearTimeout(close.current);
10031
10104
  }, []);
10032
10105
  return /* @__PURE__ */ (0, import_jsx_runtime77.jsxs)(
@@ -10197,14 +10270,14 @@ function PermissionHint({ perm, children }) {
10197
10270
  }
10198
10271
 
10199
10272
  // src/components/platform/ModeSwitch.tsx
10200
- var React52 = __toESM(require("react"), 1);
10273
+ var React53 = __toESM(require("react"), 1);
10201
10274
  var import_jsx_runtime79 = require("react/jsx-runtime");
10202
10275
  function ModeSwitch({ canToggle = false, requestCount = 0, onClearLog, renderLog, onOpenSpec, summary }) {
10203
10276
  const mode2 = useRuntimeMode();
10204
- const [open, setOpen] = React52.useState(false);
10277
+ const [open, setOpen] = React53.useState(false);
10205
10278
  const zIndex = useOverlayLayer(open);
10206
- const ref = React52.useRef(null);
10207
- React52.useEffect(() => {
10279
+ const ref = React53.useRef(null);
10280
+ React53.useEffect(() => {
10208
10281
  if (!open) return;
10209
10282
  const away = (e) => {
10210
10283
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -10460,7 +10533,7 @@ function SaturationDistribution({ campuses = [], floorBand, floorScore, onSelect
10460
10533
  }
10461
10534
 
10462
10535
  // src/components/planner/MixGap.tsx
10463
- var React53 = __toESM(require("react"), 1);
10536
+ var React54 = __toESM(require("react"), 1);
10464
10537
  var import_jsx_runtime82 = require("react/jsx-runtime");
10465
10538
  var MIX_GAP_LAYOUT = {
10466
10539
  trackHeight: 30,
@@ -10475,7 +10548,7 @@ function mixGapOffset(value, max) {
10475
10548
  }
10476
10549
  function MixGap({ rows = [], loading = false, className = "" }) {
10477
10550
  const max = Math.max(1, ...rows.flatMap((r) => [r.target, r.realized]));
10478
- const [hover, setHover] = React53.useState(null);
10551
+ const [hover, setHover] = React54.useState(null);
10479
10552
  const toneOf = (gap) => gap >= -1 ? "ok" : gap >= -4 ? "warn" : "danger";
10480
10553
  const TONE = { ok: "var(--ok-solid)", warn: "var(--warn-solid)", danger: "var(--danger-solid)" };
10481
10554
  const { trackHeight, barHeight, targetWidth, labelGap } = MIX_GAP_LAYOUT;
@@ -10618,7 +10691,7 @@ function ChannelContribution({
10618
10691
  }
10619
10692
 
10620
10693
  // src/components/planner/BudgetReallocator.tsx
10621
- var React54 = __toESM(require("react"), 1);
10694
+ var React55 = __toESM(require("react"), 1);
10622
10695
  var import_jsx_runtime84 = require("react/jsx-runtime");
10623
10696
  var bandFor = (crp) => crp >= 200 ? "dominant" : crp >= 100 ? "strong" : crp >= 50 ? "adequate" : "weak";
10624
10697
  function BudgetReallocator({
@@ -10634,8 +10707,8 @@ function BudgetReallocator({
10634
10707
  onCancel,
10635
10708
  className = ""
10636
10709
  }) {
10637
- const [draft, setDraft] = React54.useState(spend);
10638
- React54.useEffect(() => setDraft(spend), [spend]);
10710
+ const [draft, setDraft] = React55.useState(spend);
10711
+ React55.useEffect(() => setDraft(spend), [spend]);
10639
10712
  const dirty = draft !== spend;
10640
10713
  const nextCrp = scoreFor ? scoreFor(draft) : crp;
10641
10714
  const nextBand = bandFor(nextCrp);
@@ -10650,10 +10723,10 @@ function BudgetReallocator({
10650
10723
  setDraft(spend);
10651
10724
  if (onCancel) onCancel();
10652
10725
  };
10653
- const trackRef = React54.useRef(null);
10654
- const chipRef = React54.useRef(null);
10655
- const [chipLeft, setChipLeft] = React54.useState(null);
10656
- React54.useLayoutEffect(() => {
10726
+ const trackRef = React55.useRef(null);
10727
+ const chipRef = React55.useRef(null);
10728
+ const [chipLeft, setChipLeft] = React55.useState(null);
10729
+ React55.useLayoutEffect(() => {
10657
10730
  const track = trackRef.current;
10658
10731
  if (!dirty || !track) {
10659
10732
  setChipLeft(null);
@@ -10840,7 +10913,7 @@ function ImportanceTag({ level, showLabel = true, size = "sm", className = "" })
10840
10913
  }
10841
10914
 
10842
10915
  // src/components/planner/PreferenceMeter.tsx
10843
- var React55 = __toESM(require("react"), 1);
10916
+ var React56 = __toESM(require("react"), 1);
10844
10917
  var import_jsx_runtime87 = require("react/jsx-runtime");
10845
10918
  function preferenceScore(criteria) {
10846
10919
  const earned = criteria.reduce((n, c) => n + (Number(c.earned) || 0), 0);
@@ -10870,7 +10943,7 @@ function PreferenceMeter({
10870
10943
  className = ""
10871
10944
  }) {
10872
10945
  const value = score == null ? preferenceScore(criteria) : score;
10873
- const segments = React55.useMemo(() => preferenceSegments(criteria), [criteria]);
10946
+ const segments = React56.useMemo(() => preferenceSegments(criteria), [criteria]);
10874
10947
  return /* @__PURE__ */ (0, import_jsx_runtime87.jsx)(
10875
10948
  ScoreMeter,
10876
10949
  {
@@ -10909,10 +10982,10 @@ function PreferenceMeter({
10909
10982
  }
10910
10983
 
10911
10984
  // src/components/chat/AgentChatPanel.tsx
10912
- var React61 = __toESM(require("react"), 1);
10985
+ var React62 = __toESM(require("react"), 1);
10913
10986
 
10914
10987
  // src/components/chat/chatEngine.ts
10915
- var React56 = __toESM(require("react"), 1);
10988
+ var React57 = __toESM(require("react"), 1);
10916
10989
  var CHAT_UNAVAILABLE = "chat_unavailable";
10917
10990
  var JOB_PENDING = ["queued", "running"];
10918
10991
  var JOB_SUCCESS = ["completed", "recovered"];
@@ -10966,22 +11039,22 @@ function useChatEngine(opts) {
10966
11039
  onClear,
10967
11040
  onFeedback
10968
11041
  } = opts || {};
10969
- const [status, setStatus] = React56.useState("idle");
10970
- const [threadId, setThreadId] = React56.useState(null);
10971
- const [messages, setMessages] = React56.useState([]);
10972
- const [queue, setQueue] = React56.useState([]);
10973
- const [fatal, setFatal] = React56.useState(null);
10974
- const [busy, setBusy] = React56.useState(false);
10975
- const [turnStartedAt, setTurnStartedAt] = React56.useState(null);
10976
- const listRef = React56.useRef([]);
10977
- const queueRef = React56.useRef([]);
10978
- const busyRef = React56.useRef(false);
10979
- const stoppedRef = React56.useRef(false);
10980
- const abortRef = React56.useRef(null);
10981
- const serverCount = React56.useRef(0);
10982
- const threadRef = React56.useRef(null);
10983
- const mounted = React56.useRef(true);
10984
- React56.useEffect(() => {
11042
+ const [status, setStatus] = React57.useState("idle");
11043
+ const [threadId, setThreadId] = React57.useState(null);
11044
+ const [messages, setMessages] = React57.useState([]);
11045
+ const [queue, setQueue] = React57.useState([]);
11046
+ const [fatal, setFatal] = React57.useState(null);
11047
+ const [busy, setBusy] = React57.useState(false);
11048
+ const [turnStartedAt, setTurnStartedAt] = React57.useState(null);
11049
+ const listRef = React57.useRef([]);
11050
+ const queueRef = React57.useRef([]);
11051
+ const busyRef = React57.useRef(false);
11052
+ const stoppedRef = React57.useRef(false);
11053
+ const abortRef = React57.useRef(null);
11054
+ const serverCount = React57.useRef(0);
11055
+ const threadRef = React57.useRef(null);
11056
+ const mounted = React57.useRef(true);
11057
+ React57.useEffect(() => {
10985
11058
  mounted.current = true;
10986
11059
  return () => {
10987
11060
  mounted.current = false;
@@ -11003,14 +11076,14 @@ function useChatEngine(opts) {
11003
11076
  }
11004
11077
  return false;
11005
11078
  };
11006
- const loadThread = React56.useCallback(async (id) => {
11079
+ const loadThread = React57.useCallback(async (id) => {
11007
11080
  const data = await apiAdapter.getThread(id);
11008
11081
  const list = [...data && data.messages || []];
11009
11082
  serverCount.current = list.length;
11010
11083
  commit(list);
11011
11084
  return list;
11012
11085
  }, [apiAdapter]);
11013
- React56.useEffect(() => {
11086
+ React57.useEffect(() => {
11014
11087
  if (!apiAdapter) {
11015
11088
  setStatus("idle");
11016
11089
  setFatal(null);
@@ -11223,7 +11296,7 @@ function useChatEngine(opts) {
11223
11296
  await dispatchTurn(turn);
11224
11297
  }
11225
11298
  }
11226
- const send = React56.useCallback((text, attachments) => {
11299
+ const send = React57.useCallback((text, attachments) => {
11227
11300
  const body = (text || "").trim();
11228
11301
  if (!body && !(attachments && attachments.length)) return;
11229
11302
  if (status === "disconnected") return;
@@ -11233,7 +11306,7 @@ function useChatEngine(opts) {
11233
11306
  stoppedRef.current = false;
11234
11307
  drain();
11235
11308
  }, [status]);
11236
- const stop = React56.useCallback(() => {
11309
+ const stop = React57.useCallback(() => {
11237
11310
  stoppedRef.current = true;
11238
11311
  const ac = abortRef.current;
11239
11312
  if (ac) {
@@ -11252,11 +11325,11 @@ function useChatEngine(opts) {
11252
11325
  store.del(STORAGE_PREFIX + threadRef.current);
11253
11326
  }
11254
11327
  }, [apiAdapter]);
11255
- const removeQueued = React56.useCallback((id) => {
11328
+ const removeQueued = React57.useCallback((id) => {
11256
11329
  queueRef.current = queueRef.current.filter((t) => t.id !== id);
11257
11330
  setQueue(queueRef.current.slice());
11258
11331
  }, []);
11259
- const retry = React56.useCallback(() => {
11332
+ const retry = React57.useCallback(() => {
11260
11333
  const list = listRef.current;
11261
11334
  let at = -1;
11262
11335
  for (let i = list.length - 1; i >= 0; i--) if (list[i].role === "user") {
@@ -11272,19 +11345,19 @@ function useChatEngine(opts) {
11272
11345
  setQueue(queueRef.current.slice());
11273
11346
  drain();
11274
11347
  }, []);
11275
- const clear = React56.useCallback(() => {
11348
+ const clear = React57.useCallback(() => {
11276
11349
  commit([]);
11277
11350
  serverCount.current = 0;
11278
11351
  queueRef.current = [];
11279
11352
  setQueue([]);
11280
11353
  onClear && onClear();
11281
11354
  }, [onClear]);
11282
- const setFeedback = React56.useCallback((id, value) => {
11355
+ const setFeedback = React57.useCallback((id, value) => {
11283
11356
  patch(id, (m) => ({ feedback: m.feedback === value ? null : value }));
11284
11357
  const msg = listRef.current.find((m) => m.id === id);
11285
11358
  onFeedback && onFeedback({ message: msg, feedback: msg ? msg.feedback : value });
11286
11359
  }, [onFeedback]);
11287
- const reload = React56.useCallback(async () => {
11360
+ const reload = React57.useCallback(async () => {
11288
11361
  if (!threadRef.current) return;
11289
11362
  setStatus("loading");
11290
11363
  try {
@@ -11322,10 +11395,10 @@ var ChatKit = {
11322
11395
  };
11323
11396
 
11324
11397
  // src/components/chat/ChatTranscript.tsx
11325
- var React58 = __toESM(require("react"), 1);
11398
+ var React59 = __toESM(require("react"), 1);
11326
11399
 
11327
11400
  // src/components/chat/ChatTurn.tsx
11328
- var React57 = __toESM(require("react"), 1);
11401
+ var React58 = __toESM(require("react"), 1);
11329
11402
  var import_jsx_runtime88 = require("react/jsx-runtime");
11330
11403
  function JsonView({ value }) {
11331
11404
  let text;
@@ -11362,7 +11435,7 @@ function PacketCard({ packet, schema, render, onApply, applied }) {
11362
11435
  ] });
11363
11436
  }
11364
11437
  function ThinkingBlock({ text, durationMs, streaming, defaultOpen = false }) {
11365
- const [open, setOpen] = React57.useState(defaultOpen);
11438
+ const [open, setOpen] = React58.useState(defaultOpen);
11366
11439
  if (!text) return null;
11367
11440
  return /* @__PURE__ */ (0, import_jsx_runtime88.jsxs)("div", { className: "fdc-think" + (open ? " is-open" : ""), children: [
11368
11441
  /* @__PURE__ */ (0, import_jsx_runtime88.jsxs)("button", { type: "button", className: "fdc-think-head", onClick: () => setOpen((v) => !v), "aria-expanded": open, children: [
@@ -11379,7 +11452,7 @@ function ThinkingBlock({ text, durationMs, streaming, defaultOpen = false }) {
11379
11452
  ] });
11380
11453
  }
11381
11454
  function Citations({ items = [], onOpen }) {
11382
- const [open, setOpen] = React57.useState(false);
11455
+ const [open, setOpen] = React58.useState(false);
11383
11456
  if (!items.length) return null;
11384
11457
  return /* @__PURE__ */ (0, import_jsx_runtime88.jsxs)("div", { className: "fdc-cites", children: [
11385
11458
  /* @__PURE__ */ (0, import_jsx_runtime88.jsxs)("button", { type: "button", className: "fdc-cites-head", onClick: () => setOpen((v) => !v), "aria-expanded": open, children: [
@@ -11405,7 +11478,7 @@ function clampText(text, max) {
11405
11478
  return (at > max * 0.6 ? cut.slice(0, at) : cut).trimEnd() + "\u2026";
11406
11479
  }
11407
11480
  function MessageBody({ message: m, ctx }) {
11408
- const [expanded, setExpanded] = React57.useState(false);
11481
+ const [expanded, setExpanded] = React58.useState(false);
11409
11482
  const isUser = m.role === "user";
11410
11483
  const raw = m.text || "";
11411
11484
  const clamped = !expanded && !m.streaming ? clampText(raw, ctx.maxVisibleChars) : null;
@@ -11472,9 +11545,9 @@ function MessageBody({ message: m, ctx }) {
11472
11545
  ] });
11473
11546
  }
11474
11547
  function useCopyRun() {
11475
- const [done, setDone] = React57.useState(false);
11476
- const t = React57.useRef(null);
11477
- React57.useEffect(() => () => {
11548
+ const [done, setDone] = React58.useState(false);
11549
+ const t = React58.useRef(null);
11550
+ React58.useEffect(() => () => {
11478
11551
  if (t.current) clearTimeout(t.current);
11479
11552
  }, []);
11480
11553
  return [done, (text) => {
@@ -11501,7 +11574,7 @@ function RunActions({ group, ctx }) {
11501
11574
  ] }),
11502
11575
  isAssistant && ctx.onRetry ? /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("button", { type: "button", className: "fdc-act", onClick: ctx.onRetry, "aria-label": "Retry this turn", children: /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("i", { className: "ph ph-arrow-clockwise", "aria-hidden": "true" }) }) : null,
11503
11576
  group.role === "user" && ctx.onEdit ? /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("button", { type: "button", className: "fdc-act", onClick: () => ctx.onEdit(last), "aria-label": "Edit and resend", children: /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("i", { className: "ph ph-pencil-simple", "aria-hidden": "true" }) }) : null,
11504
- isAssistant && ctx.onFeedback ? /* @__PURE__ */ (0, import_jsx_runtime88.jsxs)(React57.Fragment, { children: [
11577
+ isAssistant && ctx.onFeedback ? /* @__PURE__ */ (0, import_jsx_runtime88.jsxs)(React58.Fragment, { children: [
11505
11578
  /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("button", { type: "button", className: "fdc-act" + (fb === "up" ? " is-on" : ""), onClick: () => ctx.onFeedback(last.id, "up"), "aria-pressed": fb === "up", "aria-label": "Good response", children: /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("i", { className: "ph ph-thumbs-up", "aria-hidden": "true" }) }),
11506
11579
  /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("button", { type: "button", className: "fdc-act" + (fb === "down" ? " is-on" : ""), onClick: () => ctx.onFeedback(last.id, "down"), "aria-pressed": fb === "down", "aria-label": "Bad response", children: /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("i", { className: "ph ph-thumbs-down", "aria-hidden": "true" }) })
11507
11580
  ] }) : null,
@@ -11596,10 +11669,10 @@ function ChatTranscript({
11596
11669
  renderEmpty,
11597
11670
  className = ""
11598
11671
  }) {
11599
- const scroller = React58.useRef(null);
11600
- const stick = React58.useRef(true);
11601
- const [pill, setPill] = React58.useState(0);
11602
- const seen = React58.useRef(0);
11672
+ const scroller = React59.useRef(null);
11673
+ const stick = React59.useRef(true);
11674
+ const [pill, setPill] = React59.useState(0);
11675
+ const seen = React59.useRef(0);
11603
11676
  const toBottom = (smooth) => {
11604
11677
  const el = scroller.current;
11605
11678
  if (!el) return;
@@ -11618,7 +11691,7 @@ function ChatTranscript({
11618
11691
  seen.current = messages.length;
11619
11692
  }
11620
11693
  };
11621
- React58.useLayoutEffect(() => {
11694
+ React59.useLayoutEffect(() => {
11622
11695
  const el = scroller.current;
11623
11696
  if (!el) return;
11624
11697
  if (stick.current) {
@@ -11626,7 +11699,7 @@ function ChatTranscript({
11626
11699
  seen.current = messages.length;
11627
11700
  } else setPill(Math.max(0, messages.length - seen.current));
11628
11701
  }, [messages]);
11629
- React58.useEffect(() => {
11702
+ React59.useEffect(() => {
11630
11703
  const el = scroller.current;
11631
11704
  const inner = el && el.firstChild;
11632
11705
  if (!el || !inner || typeof ResizeObserver === "undefined") return;
@@ -11636,7 +11709,7 @@ function ChatTranscript({
11636
11709
  ro.observe(inner);
11637
11710
  return () => ro.disconnect();
11638
11711
  }, []);
11639
- const groups = React58.useMemo(() => groupMessages(messages), [messages]);
11712
+ const groups = React59.useMemo(() => groupMessages(messages), [messages]);
11640
11713
  const empty = !messages.length && status === "ready";
11641
11714
  return /* @__PURE__ */ (0, import_jsx_runtime89.jsxs)("div", { className: ["fdc-scroll", className].filter(Boolean).join(" "), ref: scroller, onScroll, children: [
11642
11715
  /* @__PURE__ */ (0, import_jsx_runtime89.jsxs)("div", { className: "fdc-log", role: "log", "aria-label": "Conversation", children: [
@@ -11660,7 +11733,7 @@ function ChatTranscript({
11660
11733
  const prev = groups[i - 1];
11661
11734
  const k = dayKey(g.messages[0].timestamp);
11662
11735
  const showDay = !!k && (!prev || dayKey(prev.messages[0].timestamp) !== k);
11663
- return /* @__PURE__ */ (0, import_jsx_runtime89.jsxs)(React58.Fragment, { children: [
11736
+ return /* @__PURE__ */ (0, import_jsx_runtime89.jsxs)(React59.Fragment, { children: [
11664
11737
  showDay ? /* @__PURE__ */ (0, import_jsx_runtime89.jsx)("div", { className: "fdc-day", children: /* @__PURE__ */ (0, import_jsx_runtime89.jsx)("span", { children: dayLabel(k) }) }) : null,
11665
11738
  /* @__PURE__ */ (0, import_jsx_runtime89.jsx)(ChatTurn, { group: g, ctx })
11666
11739
  ] }, g.key || i);
@@ -11677,7 +11750,7 @@ function ChatTranscript({
11677
11750
  var TranscriptKit = { groupMessages };
11678
11751
 
11679
11752
  // src/components/chat/ChatComposer.tsx
11680
- var React59 = __toESM(require("react"), 1);
11753
+ var React60 = __toESM(require("react"), 1);
11681
11754
  var import_jsx_runtime90 = require("react/jsx-runtime");
11682
11755
  function ChatComposer({
11683
11756
  onSubmit,
@@ -11705,17 +11778,17 @@ function ChatComposer({
11705
11778
  onReject,
11706
11779
  onOpenAttachment
11707
11780
  }) {
11708
- const [text, setText] = React59.useState(draft || "");
11709
- const [trigger, setTrigger] = React59.useState(null);
11710
- const [mentionItems, setMentionItems] = React59.useState([]);
11711
- const [listening, setListening] = React59.useState(false);
11712
- const [notice, setNotice] = React59.useState(null);
11713
- const editor = React59.useRef(null);
11714
- const wrap = React59.useRef(null);
11715
- const stopVoice = React59.useRef(null);
11781
+ const [text, setText] = React60.useState(draft || "");
11782
+ const [trigger, setTrigger] = React60.useState(null);
11783
+ const [mentionItems, setMentionItems] = React60.useState([]);
11784
+ const [listening, setListening] = React60.useState(false);
11785
+ const [notice, setNotice] = React60.useState(null);
11786
+ const editor = React60.useRef(null);
11787
+ const wrap = React60.useRef(null);
11788
+ const stopVoice = React60.useRef(null);
11716
11789
  const staged = useStagedFiles(fileUploadHandler, { onError: () => {
11717
11790
  } });
11718
- React59.useEffect(() => {
11791
+ React60.useEffect(() => {
11719
11792
  if (draft != null && draft !== text) setText(draft);
11720
11793
  }, [draft]);
11721
11794
  const change = (v) => {
@@ -11751,7 +11824,7 @@ function ChatComposer({
11751
11824
  e.preventDefault();
11752
11825
  staged.add(found.files);
11753
11826
  };
11754
- React59.useEffect(() => {
11827
+ React60.useEffect(() => {
11755
11828
  if (!trigger || trigger.type !== "mention" || !mentionSources) {
11756
11829
  setMentionItems([]);
11757
11830
  return;
@@ -11769,7 +11842,7 @@ function ChatComposer({
11769
11842
  const q = (trigger.query || "").toLowerCase();
11770
11843
  setMentionItems(mentionSources.filter((m) => !q || (m.label + " " + (m.description || "")).toLowerCase().includes(q)));
11771
11844
  }, [trigger, mentionSources]);
11772
- const slashItems = React59.useMemo(() => {
11845
+ const slashItems = React60.useMemo(() => {
11773
11846
  if (!trigger || trigger.type !== "slash" || !slashCommands) return [];
11774
11847
  const q = (trigger.query || "").toLowerCase();
11775
11848
  return slashCommands.filter((c) => !q || (c.id + " " + c.label + " " + (c.description || "")).toLowerCase().includes(q));
@@ -11943,12 +12016,12 @@ function ChatComposer({
11943
12016
  }
11944
12017
 
11945
12018
  // src/components/chat/ChatSessionBar.tsx
11946
- var React60 = __toESM(require("react"), 1);
12019
+ var React61 = __toESM(require("react"), 1);
11947
12020
  var import_jsx_runtime91 = require("react/jsx-runtime");
11948
12021
  var compact2 = meterFormats.compact;
11949
12022
  function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extras }) {
11950
- const [open, setOpen] = React60.useState(false);
11951
- const anchor = React60.useRef(null);
12023
+ const [open, setOpen] = React61.useState(false);
12024
+ const anchor = React61.useRef(null);
11952
12025
  const stats = sessionStats || null;
11953
12026
  const cu = contextUsage || null;
11954
12027
  const limits = usageLimits || null;
@@ -11963,7 +12036,7 @@ function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extr
11963
12036
  if (stats && stats.runningTasks) bits.push(stats.runningTasks + " running task" + (stats.runningTasks === 1 ? "" : "s"));
11964
12037
  if (cu) bits.push(pct + "% context");
11965
12038
  const expandable = !!(cu || limits);
11966
- return /* @__PURE__ */ (0, import_jsx_runtime91.jsxs)(React60.Fragment, { children: [
12039
+ return /* @__PURE__ */ (0, import_jsx_runtime91.jsxs)(React61.Fragment, { children: [
11967
12040
  /* @__PURE__ */ (0, import_jsx_runtime91.jsxs)("div", { className: "fdc-bar", children: [
11968
12041
  /* @__PURE__ */ (0, import_jsx_runtime91.jsxs)(
11969
12042
  "button",
@@ -12104,7 +12177,7 @@ function ModelControls({
12104
12177
  ] })
12105
12178
  });
12106
12179
  }
12107
- return /* @__PURE__ */ (0, import_jsx_runtime91.jsxs)(React60.Fragment, { children: [
12180
+ return /* @__PURE__ */ (0, import_jsx_runtime91.jsxs)(React61.Fragment, { children: [
12108
12181
  /* @__PURE__ */ (0, import_jsx_runtime91.jsx)(
12109
12182
  MenuButton,
12110
12183
  {
@@ -12221,11 +12294,11 @@ function AgentChatPanel({
12221
12294
  onFeedback,
12222
12295
  onEditMessage
12223
12296
  }) {
12224
- const [panelWidth, setPanelWidth] = React61.useState(width);
12225
- const [applied, setApplied] = React61.useState({});
12226
- const [draft, setDraft] = React61.useState("");
12227
- const dragging = React61.useRef(null);
12228
- React61.useEffect(() => setPanelWidth(width), [width]);
12297
+ const [panelWidth, setPanelWidth] = React62.useState(width);
12298
+ const [applied, setApplied] = React62.useState({});
12299
+ const [draft, setDraft] = React62.useState("");
12300
+ const dragging = React62.useRef(null);
12301
+ React62.useEffect(() => setPanelWidth(width), [width]);
12229
12302
  const engine = useChatEngine({
12230
12303
  contextType,
12231
12304
  contextId,
@@ -12429,7 +12502,7 @@ function AgentChatPanel({
12429
12502
  }
12430
12503
 
12431
12504
  // src/components/files/FileViewer.tsx
12432
- var React62 = __toESM(require("react"), 1);
12505
+ var React63 = __toESM(require("react"), 1);
12433
12506
  var import_jsx_runtime93 = require("react/jsx-runtime");
12434
12507
  function previewKindOf(file) {
12435
12508
  const mime = (file.mime || "").toLowerCase();
@@ -12486,13 +12559,13 @@ function parseDelimited(text, delimiter) {
12486
12559
  return rows;
12487
12560
  }
12488
12561
  function usePreview(file, kind, resolveUrl) {
12489
- const [state, setState] = React62.useState({ status: "loading" });
12490
- const [attempt, setAttempt] = React62.useState(0);
12491
- const resolveRef = React62.useRef(resolveUrl);
12492
- React62.useEffect(() => {
12562
+ const [state, setState] = React63.useState({ status: "loading" });
12563
+ const [attempt, setAttempt] = React63.useState(0);
12564
+ const resolveRef = React63.useRef(resolveUrl);
12565
+ React63.useEffect(() => {
12493
12566
  resolveRef.current = resolveUrl;
12494
12567
  }, [resolveUrl]);
12495
- React62.useEffect(() => {
12568
+ React63.useEffect(() => {
12496
12569
  if (kind === "none") {
12497
12570
  setState({ status: "ready" });
12498
12571
  return;
@@ -12522,7 +12595,7 @@ function usePreview(file, kind, resolveUrl) {
12522
12595
  if (ac) ac.abort();
12523
12596
  };
12524
12597
  }, [file.id, kind, attempt]);
12525
- return { ...state, retry: React62.useCallback(() => setAttempt((a) => a + 1), []) };
12598
+ return { ...state, retry: React63.useCallback(() => setAttempt((a) => a + 1), []) };
12526
12599
  }
12527
12600
  function PreviewBody({ file, kind, state }) {
12528
12601
  if (kind === "image") return /* @__PURE__ */ (0, import_jsx_runtime93.jsx)("img", { className: "fd-fv-image", src: state.url, alt: file.name || "Preview" });
@@ -12567,13 +12640,13 @@ function PreviewBody({ file, kind, state }) {
12567
12640
  ] });
12568
12641
  }
12569
12642
  function FileViewer({ file, resolveUrl, sidePanel, sidePanelLabel = "Details", onClose, actions, className = "" }) {
12570
- const kind = React62.useMemo(() => previewKindOf(file), [file]);
12643
+ const kind = React63.useMemo(() => previewKindOf(file), [file]);
12571
12644
  const preview = usePreview(file, kind, resolveUrl);
12572
- const [tab, setTab] = React62.useState("preview");
12573
- const [downloading, setDownloading] = React62.useState(false);
12574
- const [downloadError, setDownloadError] = React62.useState(null);
12645
+ const [tab, setTab] = React63.useState("preview");
12646
+ const [downloading, setDownloading] = React63.useState(false);
12647
+ const [downloadError, setDownloadError] = React63.useState(null);
12575
12648
  const hasSide = sidePanel != null;
12576
- React62.useEffect(() => {
12649
+ React63.useEffect(() => {
12577
12650
  setTab("preview");
12578
12651
  }, [file.id]);
12579
12652
  const download = () => {
@@ -12640,7 +12713,7 @@ function FileViewer({ file, resolveUrl, sidePanel, sidePanelLabel = "Details", o
12640
12713
  }
12641
12714
 
12642
12715
  // src/components/files/FileBrowser.tsx
12643
- var React63 = __toESM(require("react"), 1);
12716
+ var React64 = __toESM(require("react"), 1);
12644
12717
  var import_jsx_runtime94 = require("react/jsx-runtime");
12645
12718
  function childrenOf(folders, parentId) {
12646
12719
  return folders.filter((f) => f.parentId === parentId).sort((a, b) => a.name.localeCompare(b.name));
@@ -12711,10 +12784,10 @@ function FolderPickerMenu({
12711
12784
  destinations,
12712
12785
  onPick
12713
12786
  }) {
12714
- const [open, setOpen] = React63.useState(false);
12787
+ const [open, setOpen] = React64.useState(false);
12715
12788
  const zIndex = useOverlayLayer(open);
12716
- const ref = React63.useRef(null);
12717
- React63.useEffect(() => {
12789
+ const ref = React64.useRef(null);
12790
+ React64.useEffect(() => {
12718
12791
  if (!open) return;
12719
12792
  const away = (e) => {
12720
12793
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -12766,8 +12839,8 @@ function FolderPickerMenu({
12766
12839
  ] });
12767
12840
  }
12768
12841
  function RowShell({ icon, name, meta, onOpen, draggableId, isDropTarget, onDropPayload, actions, className = "" }) {
12769
- const [over, setOver] = React63.useState(false);
12770
- const dragCounter = React63.useRef(0);
12842
+ const [over, setOver] = React64.useState(false);
12843
+ const dragCounter = React64.useRef(0);
12771
12844
  const dropHandlers = isDropTarget ? {
12772
12845
  onDragEnter: (e) => {
12773
12846
  if (!hasInternalPayload(e)) return;
@@ -12815,8 +12888,8 @@ function RowShell({ icon, name, meta, onOpen, draggableId, isDropTarget, onDropP
12815
12888
  }
12816
12889
  function TreeNode({ folder, depth, folders, currentFolderId, onNavigate, onMoveInto, canMove }) {
12817
12890
  const kids = childrenOf(folders, folder.id);
12818
- const [expanded, setExpanded] = React63.useState(() => currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId));
12819
- React63.useEffect(() => {
12891
+ const [expanded, setExpanded] = React64.useState(() => currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId));
12892
+ React64.useEffect(() => {
12820
12893
  if (currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId)) setExpanded(true);
12821
12894
  }, [currentFolderId, folder.id, folders]);
12822
12895
  const isCurrent = folder.id === currentFolderId;
@@ -12882,15 +12955,15 @@ function FileBrowser({
12882
12955
  const canUpload = perm.canUpload !== false && !!onUpload;
12883
12956
  const canRemoveFile = perm.canRemoveFile !== false && !!onRemoveFile;
12884
12957
  const canDeleteFolder = perm.canDeleteFolder !== false && !!onDeleteFolder;
12885
- const [createOpen, setCreateOpen] = React63.useState(false);
12886
- const [createName, setCreateName] = React63.useState("");
12887
- const [renameTarget, setRenameTarget] = React63.useState(null);
12888
- const [renameValue, setRenameValue] = React63.useState("");
12889
- const [deleteTarget, setDeleteTarget] = React63.useState(null);
12890
- const uploadInputRef = React63.useRef(null);
12891
- const crumbFolders = React63.useMemo(() => ancestorsOf(folders, currentFolderId), [folders, currentFolderId]);
12892
- const subfolders = React63.useMemo(() => childrenOf(folders, currentFolderId), [folders, currentFolderId]);
12893
- const currentFiles = React63.useMemo(
12958
+ const [createOpen, setCreateOpen] = React64.useState(false);
12959
+ const [createName, setCreateName] = React64.useState("");
12960
+ const [renameTarget, setRenameTarget] = React64.useState(null);
12961
+ const [renameValue, setRenameValue] = React64.useState("");
12962
+ const [deleteTarget, setDeleteTarget] = React64.useState(null);
12963
+ const uploadInputRef = React64.useRef(null);
12964
+ const crumbFolders = React64.useMemo(() => ancestorsOf(folders, currentFolderId), [folders, currentFolderId]);
12965
+ const subfolders = React64.useMemo(() => childrenOf(folders, currentFolderId), [folders, currentFolderId]);
12966
+ const currentFiles = React64.useMemo(
12894
12967
  () => files.filter((f) => f.folderId === currentFolderId).sort((a, b) => a.name.localeCompare(b.name)),
12895
12968
  [files, currentFolderId]
12896
12969
  );
@@ -13122,8 +13195,8 @@ function BreadcrumbCrumb({
13122
13195
  isDropTarget,
13123
13196
  onDropPayload
13124
13197
  }) {
13125
- const [over, setOver] = React63.useState(false);
13126
- const dragCounter = React63.useRef(0);
13198
+ const [over, setOver] = React64.useState(false);
13199
+ const dragCounter = React64.useRef(0);
13127
13200
  const dropHandlers = isDropTarget ? {
13128
13201
  onDragEnter: (e) => {
13129
13202
  if (!hasInternalPayload(e)) return;
@@ -13152,9 +13225,27 @@ function BreadcrumbCrumb({
13152
13225
  }
13153
13226
 
13154
13227
  // src/kits/query.ts
13155
- var React64 = __toESM(require("react"), 1);
13228
+ var React65 = __toESM(require("react"), 1);
13229
+ var isMultiSelectShaped = (v) => !!v && typeof v === "object" && Array.isArray(v.values);
13230
+ var isRangeShaped = (v) => !!v && typeof v === "object" && ("min" in v || "max" in v);
13231
+ var isDateShaped = (v) => !!v && typeof v === "object" && "kind" in v;
13232
+ function isActiveFilterValue(v) {
13233
+ if (v == null || v === "" || v === false) return false;
13234
+ if (Array.isArray(v)) return v.length > 0;
13235
+ if (isMultiSelectShaped(v)) return v.values.length > 0;
13236
+ if (isRangeShaped(v)) return v.min != null || v.max != null;
13237
+ if (isDateShaped(v)) return v.kind === "withinDays" ? v.days != null : !!(v.from && v.to);
13238
+ return true;
13239
+ }
13156
13240
  function eqFilter(get2) {
13157
- return (row, value) => Array.isArray(value) ? value.includes(get2(row)) : get2(row) === value;
13241
+ return (row, value) => {
13242
+ if (Array.isArray(value)) return value.includes(get2(row));
13243
+ if (isMultiSelectShaped(value)) {
13244
+ const hit = value.values.some((o) => o.value === get2(row));
13245
+ return value.operator === "notIn" ? !hit : hit;
13246
+ }
13247
+ return get2(row) === value;
13248
+ };
13158
13249
  }
13159
13250
  function anyOfFilter(get2) {
13160
13251
  return (row, value) => {
@@ -13165,10 +13256,15 @@ function anyOfFilter(get2) {
13165
13256
  function rangeFilter(get2) {
13166
13257
  return (row, value) => {
13167
13258
  const n = get2(row);
13168
- const bounds = Array.isArray(value) ? value : [0, Number.POSITIVE_INFINITY];
13169
- const lo = Number(bounds[0] ?? 0);
13170
- const hiRaw = bounds[1];
13171
- const hi = hiRaw == null ? Number.POSITIVE_INFINITY : Number(hiRaw);
13259
+ let lo = 0;
13260
+ let hi = Number.POSITIVE_INFINITY;
13261
+ if (Array.isArray(value)) {
13262
+ lo = Number(value[0] ?? 0);
13263
+ hi = value[1] == null ? Number.POSITIVE_INFINITY : Number(value[1]);
13264
+ } else if (isRangeShaped(value)) {
13265
+ lo = value.min ?? 0;
13266
+ hi = value.max ?? Number.POSITIVE_INFINITY;
13267
+ }
13172
13268
  return n >= lo && (hi === Number.POSITIVE_INFINITY || n <= hi);
13173
13269
  };
13174
13270
  }
@@ -13184,22 +13280,22 @@ function compare(a, b, dir) {
13184
13280
  var API = null;
13185
13281
  var PREFS = null;
13186
13282
  function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
13187
- const [query, setQuery] = React64.useState(() => {
13283
+ const [query, setQuery] = React65.useState(() => {
13188
13284
  const store = PREFS || window.PlannerPrefs;
13189
13285
  const saved = prefsKey && store ? store.getTable(prefsKey) : {};
13190
13286
  return { ...DEFAULTS, ...defaults || {}, ...saved.pageSize ? { pageSize: saved.pageSize } : {}, ...saved.sort ? { sort: saved.sort, dir: saved.dir || "desc" } : {} };
13191
13287
  });
13192
- const savePref = React64.useCallback((patch2) => {
13288
+ const savePref = React65.useCallback((patch2) => {
13193
13289
  const store = PREFS || window.PlannerPrefs;
13194
13290
  if (prefsKey && store) store.setTable(prefsKey, patch2);
13195
13291
  }, [prefsKey]);
13196
- const [res, setRes] = React64.useState(null);
13197
- const [loading, setLoading] = React64.useState(true);
13198
- const seq2 = React64.useRef(0);
13292
+ const [res, setRes] = React65.useState(null);
13293
+ const [loading, setLoading] = React65.useState(true);
13294
+ const seq2 = React65.useRef(0);
13199
13295
  const depKey = (deps || []).join("|");
13200
13296
  const paramKey = JSON.stringify(params || {});
13201
13297
  const queryKey = JSON.stringify(query);
13202
- React64.useEffect(() => {
13298
+ React65.useEffect(() => {
13203
13299
  const id = ++seq2.current;
13204
13300
  setLoading(true);
13205
13301
  const t = setTimeout(() => {
@@ -13235,7 +13331,7 @@ function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
13235
13331
  loading,
13236
13332
  /** True only on the very first load — later loads keep showing stale rows. */
13237
13333
  initialLoading: loading && !res,
13238
- activeFilterCount: Object.values(query.filters).filter((v) => !(v == null || v === "" || v === false)).length,
13334
+ activeFilterCount: Object.values(query.filters).filter(isActiveFilterValue).length,
13239
13335
  setPage: (page) => patch({ page }, false),
13240
13336
  setPageSize: (pageSize) => {
13241
13337
  savePref({ pageSize });
@@ -13298,7 +13394,6 @@ var QueryKit = {
13298
13394
  const Q = { ...DEFAULTS, ...query || {} };
13299
13395
  const c = cfg;
13300
13396
  const filters = Q.filters || {};
13301
- const active = (v) => !(v == null || v === "" || v === false || Array.isArray(v) && !v.length);
13302
13397
  let out = rows;
13303
13398
  const term = (Q.q || "").trim().toLowerCase();
13304
13399
  if (term && c.searchKeys) {
@@ -13308,7 +13403,7 @@ var QueryKit = {
13308
13403
  const searched = out;
13309
13404
  Object.entries(filters).forEach(([id, val]) => {
13310
13405
  const fn = c.filters && c.filters[id];
13311
- if (!active(val) || !fn) return;
13406
+ if (!isActiveFilterValue(val) || !fn) return;
13312
13407
  out = out.filter((r) => fn(r, val));
13313
13408
  });
13314
13409
  const matched = out;
@@ -13355,9 +13450,30 @@ var QueryKit = {
13355
13450
  if (Q.sort) parts.push("sort=" + Q.sort + "&dir=" + Q.dir);
13356
13451
  if (Q.q) parts.push("q=" + encodeURIComponent(Q.q));
13357
13452
  Object.entries(Q.filters || {}).forEach(([k, v]) => {
13358
- if (v == null || v === "" || v === false || Array.isArray(v) && !v.length) return;
13359
- if (Array.isArray(v)) v.forEach((x) => parts.push(k + "=" + encodeURIComponent(x === Infinity ? "*" : x)));
13360
- else parts.push(k + "=" + encodeURIComponent(v));
13453
+ if (!isActiveFilterValue(v)) return;
13454
+ if (Array.isArray(v)) {
13455
+ v.forEach((x) => parts.push(k + "=" + encodeURIComponent(x === Infinity ? "*" : x)));
13456
+ return;
13457
+ }
13458
+ if (isMultiSelectShaped(v)) {
13459
+ const prefix = v.operator === "notIn" ? "notIn:" : "";
13460
+ v.values.forEach((o) => parts.push(k + "=" + encodeURIComponent(prefix + String(o.value))));
13461
+ return;
13462
+ }
13463
+ if (isRangeShaped(v)) {
13464
+ if (v.min != null) parts.push(k + ".min=" + encodeURIComponent(String(v.min)));
13465
+ if (v.max != null) parts.push(k + ".max=" + encodeURIComponent(String(v.max)));
13466
+ return;
13467
+ }
13468
+ if (isDateShaped(v)) {
13469
+ if (v.kind === "withinDays") parts.push(k + "=withinDays:" + encodeURIComponent(String(v.days)));
13470
+ else {
13471
+ parts.push(k + ".from=" + encodeURIComponent(String(v.from)));
13472
+ parts.push(k + ".to=" + encodeURIComponent(String(v.to)));
13473
+ }
13474
+ return;
13475
+ }
13476
+ parts.push(k + "=" + encodeURIComponent(v));
13361
13477
  });
13362
13478
  return parts.join("&");
13363
13479
  },
@@ -13498,12 +13614,12 @@ function createVersionStore(initialState, options) {
13498
13614
  }
13499
13615
 
13500
13616
  // src/kits/documentTitle.ts
13501
- var React65 = __toESM(require("react"), 1);
13617
+ var React66 = __toESM(require("react"), 1);
13502
13618
  function formatDocumentTitle(routeTitle, appName) {
13503
13619
  return [routeTitle, appName, "Flytedesk"].filter((part) => !!part && part.length > 0).join(" \xB7 ");
13504
13620
  }
13505
13621
  function useDocumentTitle(routeTitle, appName) {
13506
- React65.useEffect(() => {
13622
+ React66.useEffect(() => {
13507
13623
  if (typeof document === "undefined") return;
13508
13624
  document.title = formatDocumentTitle(routeTitle, appName);
13509
13625
  }, [routeTitle, appName]);