@estiva-app/ui 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -299,10 +299,27 @@ import { createContext, useCallback, useContext, useEffect, useLayoutEffect, use
299
299
  import { IconChevronRight } from "@tabler/icons-react";
300
300
  import { createPortal } from "react-dom";
301
301
 
302
- // src/SectionLabel.tsx
302
+ // src/Kbd.tsx
303
303
  import { jsx as jsx8 } from "react/jsx-runtime";
304
- function SectionLabel({ children, className }) {
304
+ function Kbd({ children, className }) {
305
305
  return /* @__PURE__ */ jsx8(
306
+ "kbd",
307
+ {
308
+ className: cn(
309
+ "inline-flex shrink-0 items-center justify-center whitespace-nowrap rounded-sm border border-border-strong bg-bg-inset px-1 py-px font-sans text-[12px] leading-[120%] font-normal text-text-secondary",
310
+ "signal:border-b-2 signal:pt-[2px] signal:pb-px signal:bg-[rgba(255,255,255,.05)] signal:font-mono signal:text-[10px]",
311
+ "ship:border-b-2 ship:pt-[2px] ship:pb-px ship:bg-[rgba(255,255,255,.05)] ship:font-mono ship:text-[10px]",
312
+ className
313
+ ),
314
+ children
315
+ }
316
+ );
317
+ }
318
+
319
+ // src/SectionLabel.tsx
320
+ import { jsx as jsx9 } from "react/jsx-runtime";
321
+ function SectionLabel({ children, className }) {
322
+ return /* @__PURE__ */ jsx9(
306
323
  "span",
307
324
  {
308
325
  className: cn(
@@ -315,8 +332,18 @@ function SectionLabel({ children, className }) {
315
332
  }
316
333
 
317
334
  // src/Menu.tsx
318
- import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
335
+ import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
319
336
  var MenuHoverContext = createContext(null);
337
+ function MenuPanel({ children, className, ...props }) {
338
+ return /* @__PURE__ */ jsx10(
339
+ "div",
340
+ {
341
+ className: cn("flex flex-col rounded-lg border border-border-default bg-bg-elevated p-2 shadow-lg", className),
342
+ ...props,
343
+ children
344
+ }
345
+ );
346
+ }
320
347
  function Menu({ onClose, anchor, align = "left", position, closeOnLeave = false, children, className }) {
321
348
  const ref = useRef(null);
322
349
  const leaveTimer = useRef(void 0);
@@ -382,14 +409,14 @@ function Menu({ onClose, anchor, align = "left", position, closeOnLeave = false,
382
409
  // The provisional render: measured by the layout effect, never seen.
383
410
  { left: 0, top: 0, visibility: "hidden" }
384
411
  ) : void 0;
385
- const node = /* @__PURE__ */ jsx9(MenuHoverContext.Provider, { value: { hold, release }, children: /* @__PURE__ */ jsx9(
386
- "div",
412
+ const node = /* @__PURE__ */ jsx10(MenuHoverContext.Provider, { value: { hold, release }, children: /* @__PURE__ */ jsx10(
413
+ MenuPanel,
387
414
  {
388
415
  ref,
389
416
  role: "menu",
390
417
  "data-interactive": true,
391
418
  className: cn(
392
- "z-50 flex min-w-[180px] flex-col rounded-lg border border-border-default bg-bg-elevated p-2 shadow-lg",
419
+ "z-50 min-w-[180px]",
393
420
  portalled ? "fixed overflow-y-auto" : "absolute right-0 top-full mt-1",
394
421
  className
395
422
  ),
@@ -434,15 +461,15 @@ function MenuSub({ label, leading, selected, children, className }) {
434
461
  );
435
462
  }, [open]);
436
463
  return /* @__PURE__ */ jsxs5("div", { ref: rowRef, onMouseEnter: enter, onMouseLeave: leave, children: [
437
- /* @__PURE__ */ jsx9(MenuItem, { label, leading, selected, submenu: true }),
464
+ /* @__PURE__ */ jsx10(MenuItem, { label, leading, selected, submenu: true }),
438
465
  open && createPortal(
439
- /* @__PURE__ */ jsx9(
440
- "div",
466
+ /* @__PURE__ */ jsx10(
467
+ MenuPanel,
441
468
  {
442
469
  ref: panelRef,
443
470
  role: "menu",
444
471
  "data-interactive": true,
445
- className: cn("fixed z-50 flex w-[160px] flex-col rounded-lg border border-border-default bg-bg-elevated p-2 shadow-lg", className),
472
+ className: cn("fixed z-50 w-[160px]", className),
446
473
  style: placed ?? { left: 0, top: 0, visibility: "hidden" },
447
474
  onMouseEnter: enter,
448
475
  onMouseLeave: leave,
@@ -453,15 +480,29 @@ function MenuSub({ label, leading, selected, children, className }) {
453
480
  )
454
481
  ] });
455
482
  }
456
- function MenuItem({ label, children, size = "default", description, leading, trailing, shortcut, submenu, destructive, selected, className, ...props }) {
457
- const edge = trailing ?? (shortcut ? /* @__PURE__ */ jsx9("kbd", { className: "inline-flex shrink-0 items-center justify-center rounded-sm border border-border-strong bg-bg-inset px-1 py-px text-caption text-text-secondary", children: shortcut }) : submenu ? /* @__PURE__ */ jsx9(IconChevronRight, { size: 16, stroke: 1.5, className: "shrink-0 text-text-muted" }) : null);
483
+ function MenuItem({ label, children, size = "default", description, leading, trailing, hint, shortcut, submenu, destructive, selected, className, ...props }) {
484
+ const edge = trailing ?? (shortcut ? /* @__PURE__ */ jsx10(Kbd, { children: shortcut }) : submenu ? /* @__PURE__ */ jsx10(IconChevronRight, { size: 16, stroke: 1.5, className: "shrink-0 text-text-muted" }) : null);
458
485
  return /* @__PURE__ */ jsxs5(
459
486
  "button",
460
487
  {
461
488
  type: "button",
462
489
  role: "menuitem",
463
490
  className: cn(
464
- "flex w-full cursor-pointer items-center rounded-lg text-left hover:bg-bg-hover transition-colors",
491
+ // shrink-0: a menu is a flex column that scrolls at its max height,
492
+ // and a flex child shrinks before its container does — so every row
493
+ // in an overflowing menu was squashed to its `min-h`, and a row given
494
+ // an explicit height silently lost it (Peek's `[` menu: h-12 rows
495
+ // measured 40px). The same fix NavItem took on 2026-09-02.
496
+ // `group`: the `hint` slot reveals itself from this row's own :hover,
497
+ // so the hint and the fill are one CSS state change, not two engines.
498
+ //
499
+ // No transition on the fill (Katerina, 2026-09-05). It faded over
500
+ // 150ms, and anything appearing with it had to fade too or arrive
501
+ // ahead of it — which, sweeping a pointer down a list, read as the
502
+ // hint flickering in and out. Both are instant now: they still change
503
+ // on exactly the same :hover, so they cannot come apart, and a row
504
+ // lights and unlights crisply as the pointer crosses it.
505
+ "group flex w-full shrink-0 cursor-pointer items-center rounded-lg text-left hover:bg-bg-hover",
465
506
  // tall: as tall as its content, never shorter than 40px (Katerina,
466
507
  // 2026-09-01) — a single-line picker row sits at 40, a row with a
467
508
  // 32px face and a role line comes out at its natural 48. One rule,
@@ -472,34 +513,41 @@ function MenuItem({ label, children, size = "default", description, leading, tra
472
513
  ),
473
514
  ...props,
474
515
  children: [
475
- leading && /* @__PURE__ */ jsx9("span", { className: "flex shrink-0 items-center", children: leading }),
516
+ leading && /* @__PURE__ */ jsx10("span", { className: "flex shrink-0 items-center", children: leading }),
476
517
  children ?? /* @__PURE__ */ jsxs5("span", { className: cn("flex min-w-0 flex-1 flex-col", size === "tall" && "gap-[2px]"), children: [
477
- /* @__PURE__ */ jsx9("span", { className: cn("truncate text-[14px] leading-[140%]", destructive ? "text-error-default" : "text-text-primary"), children: label }),
478
- description && /* @__PURE__ */ jsx9("span", { className: "truncate text-[12px] leading-[120%] text-text-secondary", children: description })
518
+ /* @__PURE__ */ jsx10("span", { className: cn("truncate text-[14px] leading-[140%]", destructive ? "text-error-default" : "text-text-primary"), children: label }),
519
+ description && /* @__PURE__ */ jsx10("span", { className: "truncate text-[12px] leading-[120%] text-text-secondary", children: description })
479
520
  ] }),
480
- edge && /* @__PURE__ */ jsx9("span", { className: "flex shrink-0 items-center", children: edge })
521
+ (edge || hint) && // One grid cell holding both, right-aligned: the slot is as wide as
522
+ // the wider of the two and never changes, so revealing the hint moves
523
+ // nothing. No transition here either — the hint switches on the same
524
+ // :hover / `selected` as the fill, in the same frame.
525
+ /* @__PURE__ */ jsxs5("span", { className: "grid shrink-0 items-center justify-items-end [&>*]:col-start-1 [&>*]:row-start-1", children: [
526
+ edge && /* @__PURE__ */ jsx10("span", { className: cn("flex items-center", hint && "group-hover:opacity-0", hint && selected && "opacity-0"), children: edge }),
527
+ hint && /* @__PURE__ */ jsx10("span", { className: cn("flex items-center opacity-0 group-hover:opacity-100", selected && "opacity-100"), children: hint })
528
+ ] })
481
529
  ]
482
530
  }
483
531
  );
484
532
  }
485
- function EnterHint({ label = "Enter" }) {
486
- return /* @__PURE__ */ jsxs5("span", { className: "flex shrink-0 items-center gap-2 text-text-muted", children: [
487
- /* @__PURE__ */ jsx9("span", { className: "text-[12px] leading-[120%]", children: "\u21A9" }),
488
- /* @__PURE__ */ jsx9("span", { className: "text-[9px] font-medium leading-[115%] signal:font-mono signal:text-[9.5px] signal:tracking-[0.04em]", children: label })
533
+ function EnterHint({ target }) {
534
+ return /* @__PURE__ */ jsxs5("span", { className: "flex shrink-0 items-center gap-1.5 text-text-muted", children: [
535
+ /* @__PURE__ */ jsx10(Kbd, { children: "\u21A9 Enter" }),
536
+ target && /* @__PURE__ */ jsx10("span", { className: "text-[9px] font-medium leading-[115%] signal:font-mono signal:text-[9.5px] signal:tracking-[0.04em]", children: target })
489
537
  ] });
490
538
  }
491
539
  function MenuSection({ label, children, className }) {
492
540
  return /* @__PURE__ */ jsxs5("div", { className: "flex flex-col", children: [
493
- /* @__PURE__ */ jsx9("div", { className: cn("flex h-8 items-center px-2", className), children: /* @__PURE__ */ jsx9(SectionLabel, { className: "text-text-secondary", children: label }) }),
541
+ /* @__PURE__ */ jsx10("div", { className: cn("flex h-8 items-center px-2", className), children: /* @__PURE__ */ jsx10(SectionLabel, { className: "text-text-secondary", children: label }) }),
494
542
  children
495
543
  ] });
496
544
  }
497
545
  function MenuRow({ children, className }) {
498
- return /* @__PURE__ */ jsx9("div", { className: cn("flex items-center gap-2 px-2 py-1.5", className), children });
546
+ return /* @__PURE__ */ jsx10("div", { className: cn("flex items-center gap-2 px-2 py-1.5", className), children });
499
547
  }
500
548
 
501
549
  // src/ChipInput.tsx
502
- import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
550
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
503
551
  function InputChip({ label, leading, onRemove, className }) {
504
552
  return /* @__PURE__ */ jsxs6(
505
553
  "div",
@@ -517,9 +565,9 @@ function InputChip({ label, leading, onRemove, className }) {
517
565
  className
518
566
  ),
519
567
  children: [
520
- leading && /* @__PURE__ */ jsx10("span", { className: "flex shrink-0 items-center", children: leading }),
521
- /* @__PURE__ */ jsx10("span", { className: "text-caption font-medium text-text-primary", children: label }),
522
- onRemove && /* @__PURE__ */ jsx10(
568
+ leading && /* @__PURE__ */ jsx11("span", { className: "flex shrink-0 items-center", children: leading }),
569
+ /* @__PURE__ */ jsx11("span", { className: "text-caption font-medium text-text-primary", children: label }),
570
+ onRemove && /* @__PURE__ */ jsx11(
523
571
  "button",
524
572
  {
525
573
  type: "button",
@@ -529,7 +577,7 @@ function InputChip({ label, leading, onRemove, className }) {
529
577
  },
530
578
  className: "size-4 flex items-center justify-center rounded-full hover:bg-bg-hover text-text-secondary",
531
579
  "aria-label": `Remove ${label}`,
532
- children: /* @__PURE__ */ jsx10(IconX, { size: 10, stroke: 1.5 })
580
+ children: /* @__PURE__ */ jsx11(IconX, { size: 10, stroke: 1.5 })
533
581
  }
534
582
  )
535
583
  ]
@@ -625,8 +673,8 @@ function ChipInput({
625
673
  className: "bg-bg-inset border border-border-default hover:border-border-strong focus-within:border-border-focus focus-within:hover:border-border-focus rounded-lg px-3 py-1.5 flex flex-wrap items-center gap-1.5 transition-colors min-h-[38px] cursor-text signal:transition-shadow signal:focus-within:shadow-focus-ring",
626
674
  onClick: () => inputRef.current?.focus(),
627
675
  children: [
628
- value.map((o) => /* @__PURE__ */ jsx10(InputChip, { label: o.label, leading: chipLeading?.(o), onRemove: () => removeOption(o.id) }, o.id)),
629
- /* @__PURE__ */ jsx10(
676
+ value.map((o) => /* @__PURE__ */ jsx11(InputChip, { label: o.label, leading: chipLeading?.(o), onRemove: () => removeOption(o.id) }, o.id)),
677
+ /* @__PURE__ */ jsx11(
630
678
  "input",
631
679
  {
632
680
  ref: inputRef,
@@ -647,7 +695,7 @@ function ChipInput({
647
695
  }
648
696
  ),
649
697
  showDropdown && anchorRect && createPortal2(
650
- /* @__PURE__ */ jsx10(
698
+ /* @__PURE__ */ jsx11(
651
699
  "div",
652
700
  {
653
701
  className: "fixed z-[60] overflow-y-auto bg-bg-elevated border border-border-default rounded-lg shadow-lg",
@@ -660,7 +708,7 @@ function ChipInput({
660
708
  }),
661
709
  width: anchorRect.width
662
710
  },
663
- children: matches.map((o, i) => /* @__PURE__ */ jsx10(
711
+ children: matches.map((o, i) => /* @__PURE__ */ jsx11(
664
712
  MenuItem,
665
713
  {
666
714
  size: "tall",
@@ -687,13 +735,16 @@ function ChipInput({
687
735
  // src/Tooltip.tsx
688
736
  import { useCallback as useCallback2, useLayoutEffect as useLayoutEffect3, useRef as useRef3, useState as useState4 } from "react";
689
737
  import { createPortal as createPortal3 } from "react-dom";
690
- import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
691
- function Tooltip({ label, className }) {
692
- return /* @__PURE__ */ jsx11("div", { role: "tooltip", className: cn("bg-bg-elevated border border-border-default rounded-lg h-[30px] flex items-center justify-center px-2 shadow-lg", className), children: /* @__PURE__ */ jsx11("span", { className: "text-caption text-text-primary whitespace-nowrap", children: label }) });
738
+ import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
739
+ function Tooltip({ label, shortcut, className }) {
740
+ return /* @__PURE__ */ jsxs7("div", { role: "tooltip", className: cn("bg-bg-elevated border border-border-default rounded-lg h-[30px] flex items-center justify-center gap-1.5 px-2 shadow-lg", className), children: [
741
+ /* @__PURE__ */ jsx12("span", { className: "text-caption text-text-primary whitespace-nowrap", children: label }),
742
+ shortcut && /* @__PURE__ */ jsx12(Kbd, { children: shortcut })
743
+ ] });
693
744
  }
694
745
  var GAP2 = 6;
695
746
  var VIEWPORT_PAD = 8;
696
- function WithTooltip({ label, placement = "top", wrapperClassName, children }) {
747
+ function WithTooltip({ label, shortcut, placement = "top", wrapperClassName, children }) {
697
748
  const [show, setShow] = useState4(false);
698
749
  const ref = useRef3(null);
699
750
  const tooltipRef = useRef3(null);
@@ -718,25 +769,26 @@ function WithTooltip({ label, placement = "top", wrapperClassName, children }) {
718
769
  return /* @__PURE__ */ jsxs7("div", { ref, className: cn("inline-flex shrink-0", wrapperClassName), onMouseEnter: () => setShow(true), onMouseLeave: () => setShow(false), children: [
719
770
  children,
720
771
  show && createPortal3(
721
- /* @__PURE__ */ jsx11("div", { ref: tooltipRef, style, children: /* @__PURE__ */ jsx11(Tooltip, { label }) }),
772
+ /* @__PURE__ */ jsx12("div", { ref: tooltipRef, style, children: /* @__PURE__ */ jsx12(Tooltip, { label, shortcut }) }),
722
773
  document.body
723
774
  )
724
775
  ] });
725
776
  }
726
777
 
727
778
  // src/IconButton.tsx
728
- import { jsx as jsx12 } from "react/jsx-runtime";
779
+ import { jsx as jsx13 } from "react/jsx-runtime";
729
780
  function IconButton({
730
781
  variant = "muted",
731
782
  className,
732
783
  children,
733
784
  disabled,
734
785
  tooltip,
786
+ tooltipShortcut,
735
787
  tooltipPlacement,
736
788
  type = "button",
737
789
  ...props
738
790
  }) {
739
- const button = /* @__PURE__ */ jsx12(
791
+ const button = /* @__PURE__ */ jsx13(
740
792
  "button",
741
793
  {
742
794
  type,
@@ -757,7 +809,7 @@ function IconButton({
757
809
  }
758
810
  );
759
811
  if (tooltip) {
760
- return /* @__PURE__ */ jsx12(WithTooltip, { label: tooltip, placement: tooltipPlacement, children: button });
812
+ return /* @__PURE__ */ jsx13(WithTooltip, { label: tooltip, shortcut: tooltipShortcut, placement: tooltipPlacement, children: button });
761
813
  }
762
814
  return button;
763
815
  }
@@ -766,38 +818,38 @@ function IconButton({
766
818
  import { useRef as useRef4, useState as useState5 } from "react";
767
819
 
768
820
  // src/Divider.tsx
769
- import { jsx as jsx13 } from "react/jsx-runtime";
821
+ import { jsx as jsx14 } from "react/jsx-runtime";
770
822
  function Divider({ orientation = "horizontal", className }) {
771
- return /* @__PURE__ */ jsx13(
823
+ return /* @__PURE__ */ jsx14(
772
824
  "div",
773
825
  {
774
826
  role: "separator",
775
827
  "aria-orientation": orientation,
776
- className: cn("bg-border-subtle", orientation === "horizontal" ? "h-px mx-3" : "w-px self-stretch shrink-0", className)
828
+ className: cn("shrink-0 bg-border-subtle", orientation === "horizontal" ? "h-px mx-3" : "w-px self-stretch", className)
777
829
  }
778
830
  );
779
831
  }
780
832
 
781
833
  // src/Person.tsx
782
- import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
834
+ import { jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
783
835
  function Person({ name, picture, fallback = "\u2014", size = 20, className }) {
784
836
  return (
785
837
  // gap-2: the face-to-name distance is one rule (8px) wherever a person
786
838
  // appears — Katerina, 2026-09-01, set on Person so PersonTrigger and every
787
839
  // row agree by construction.
788
840
  /* @__PURE__ */ jsxs8("span", { className: cn("flex min-w-0 items-center gap-2", className), children: [
789
- /* @__PURE__ */ jsx14(Avatar, { name, src: picture, size }),
790
- /* @__PURE__ */ jsx14("span", { className: cn("truncate", !name && "text-text-muted"), children: name ?? fallback })
841
+ /* @__PURE__ */ jsx15(Avatar, { name, src: picture, size }),
842
+ /* @__PURE__ */ jsx15("span", { className: cn("truncate", !name && "text-text-muted"), children: name ?? fallback })
791
843
  ] })
792
844
  );
793
845
  }
794
846
 
795
847
  // src/PersonTrigger.tsx
796
848
  import { IconChevronDown } from "@tabler/icons-react";
797
- import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
849
+ import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
798
850
  function PersonTrigger({ name, picture, fallback, size, open = false, compact = false, className, ...props }) {
799
851
  if (compact) {
800
- return /* @__PURE__ */ jsx15(
852
+ return /* @__PURE__ */ jsx16(
801
853
  "button",
802
854
  {
803
855
  type: "button",
@@ -805,7 +857,7 @@ function PersonTrigger({ name, picture, fallback, size, open = false, compact =
805
857
  "aria-expanded": open,
806
858
  className: cn("cursor-pointer rounded-full focus:outline-none", className),
807
859
  ...props,
808
- children: /* @__PURE__ */ jsx15(Avatar, { name, src: picture, size: size ?? 36 })
860
+ children: /* @__PURE__ */ jsx16(Avatar, { name, src: picture, size: size ?? 36 })
809
861
  }
810
862
  );
811
863
  }
@@ -825,15 +877,15 @@ function PersonTrigger({ name, picture, fallback, size, open = false, compact =
825
877
  ),
826
878
  ...props,
827
879
  children: [
828
- /* @__PURE__ */ jsx15(Person, { name, picture, fallback, size: size ?? 22 }),
829
- /* @__PURE__ */ jsx15(IconChevronDown, { size: 14, stroke: 1.5, className: "shrink-0 text-text-muted" })
880
+ /* @__PURE__ */ jsx16(Person, { name, picture, fallback, size: size ?? 22 }),
881
+ /* @__PURE__ */ jsx16(IconChevronDown, { size: 14, stroke: 1.5, className: "shrink-0 text-text-muted" })
830
882
  ]
831
883
  }
832
884
  );
833
885
  }
834
886
 
835
887
  // src/IdentityMenu.tsx
836
- import { Fragment, jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
888
+ import { Fragment, jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
837
889
  function IdentityPanel({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, onClose, anchor, className, children }) {
838
890
  const act = (action) => () => {
839
891
  onClose();
@@ -842,41 +894,41 @@ function IdentityPanel({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, o
842
894
  const appRows = typeof children === "function" ? children(onClose) : children;
843
895
  return /* @__PURE__ */ jsxs10(Menu, { onClose, anchor, align: "right", className: cn("w-72", className), children: [
844
896
  /* @__PURE__ */ jsxs10(MenuSection, { label: signedIn ? "Signed in as" : "Acting as", children: [
845
- /* @__PURE__ */ jsx16(MenuRow, { children: /* @__PURE__ */ jsx16(Person, { name: me.name, picture: me.picture, fallback: "Anonymous", size: 28, className: "text-body-2-strong" }) }),
846
- /* @__PURE__ */ jsx16(Note, { children: signedIn ? "Your Estiva ID. The same person you are in every Estiva app." : "A key held by this browser only. Nobody else knows who you are." }),
897
+ /* @__PURE__ */ jsx17(MenuRow, { children: /* @__PURE__ */ jsx17(Person, { name: me.name, picture: me.picture, fallback: "Anonymous", size: 28, className: "text-body-2-strong" }) }),
898
+ /* @__PURE__ */ jsx17(Note, { children: signedIn ? "Your Estiva ID. The same person you are in every Estiva app." : "A key held by this browser only. Nobody else knows who you are." }),
847
899
  me.email && /* @__PURE__ */ jsxs10(Note, { children: [
848
900
  me.email,
849
901
  " \xB7 known to Estiva, never published to the relay"
850
902
  ] })
851
903
  ] }),
852
904
  relayUrl && /* @__PURE__ */ jsxs10(Fragment, { children: [
853
- /* @__PURE__ */ jsx16(Divider, { className: "mx-0 my-2" }),
905
+ /* @__PURE__ */ jsx17(Divider, { className: "mx-0 my-2" }),
854
906
  /* @__PURE__ */ jsxs10(MenuSection, { label: "Workspace", children: [
855
- /* @__PURE__ */ jsx16(MenuRow, { children: /* @__PURE__ */ jsx16("span", { className: "break-all font-mono text-caption text-text-primary", children: relayUrl }) }),
856
- /* @__PURE__ */ jsx16(Note, { children: "Everyone on this relay shares this workspace, in every app." })
907
+ /* @__PURE__ */ jsx17(MenuRow, { children: /* @__PURE__ */ jsx17("span", { className: "break-all font-mono text-caption text-text-primary", children: relayUrl }) }),
908
+ /* @__PURE__ */ jsx17(Note, { children: "Everyone on this relay shares this workspace, in every app." })
857
909
  ] })
858
910
  ] }),
859
911
  appRows && /* @__PURE__ */ jsxs10(Fragment, { children: [
860
- /* @__PURE__ */ jsx16(Divider, { className: "mx-0 my-2" }),
912
+ /* @__PURE__ */ jsx17(Divider, { className: "mx-0 my-2" }),
861
913
  appRows
862
914
  ] }),
863
- signedIn && idBase || onCopyKey || idBase && onSignOut ? /* @__PURE__ */ jsx16(Divider, { className: "mx-0 my-2" }) : null,
864
- signedIn && idBase && /* @__PURE__ */ jsx16(
915
+ signedIn && idBase || onCopyKey || idBase && onSignOut ? /* @__PURE__ */ jsx17(Divider, { className: "mx-0 my-2" }) : null,
916
+ signedIn && idBase && /* @__PURE__ */ jsx17(
865
917
  MenuItem,
866
918
  {
867
919
  label: "Edit your profile in Estiva ID",
868
920
  onClick: act(() => window.open(idBase, "_blank", "noopener,noreferrer"))
869
921
  }
870
922
  ),
871
- onCopyKey && /* @__PURE__ */ jsx16(MenuItem, { label: "Copy public key", onClick: act(onCopyKey) }),
872
- idBase && onSignOut && /* @__PURE__ */ jsx16(MenuItem, { label: "Sign out", onClick: act(onSignOut) })
923
+ onCopyKey && /* @__PURE__ */ jsx17(MenuItem, { label: "Copy public key", onClick: act(onCopyKey) }),
924
+ idBase && onSignOut && /* @__PURE__ */ jsx17(MenuItem, { label: "Sign out", onClick: act(onSignOut) })
873
925
  ] });
874
926
  }
875
927
  function IdentityMenu({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, compact = false, className, children }) {
876
928
  const [open, setOpen] = useState5(false);
877
929
  const anchorRef = useRef4(null);
878
930
  return /* @__PURE__ */ jsxs10("div", { ref: anchorRef, className: cn("relative", className), children: [
879
- /* @__PURE__ */ jsx16(
931
+ /* @__PURE__ */ jsx17(
880
932
  PersonTrigger,
881
933
  {
882
934
  name: me.name,
@@ -890,7 +942,7 @@ function IdentityMenu({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, co
890
942
  "aria-label": compact ? "Account menu" : void 0
891
943
  }
892
944
  ),
893
- open && /* @__PURE__ */ jsx16(
945
+ open && /* @__PURE__ */ jsx17(
894
946
  IdentityPanel,
895
947
  {
896
948
  me,
@@ -907,12 +959,12 @@ function IdentityMenu({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, co
907
959
  ] });
908
960
  }
909
961
  function Note({ children }) {
910
- return /* @__PURE__ */ jsx16("span", { className: "px-2 pb-1.5 text-caption text-text-secondary", children });
962
+ return /* @__PURE__ */ jsx17("span", { className: "px-2 pb-1.5 text-caption text-text-secondary", children });
911
963
  }
912
964
 
913
965
  // src/Field.tsx
914
966
  import { createContext as createContext2, useContext as useContext2, useId } from "react";
915
- import { jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
967
+ import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
916
968
  var FieldControlIdContext = createContext2(void 0);
917
969
  function useFieldControlId(ownId) {
918
970
  const fromField = useContext2(FieldControlIdContext);
@@ -929,11 +981,11 @@ function Field({ label, required = false, htmlFor, children }) {
929
981
  className: `text-input-label text-text-primary${required ? " flex items-center" : ""}`,
930
982
  children: [
931
983
  label,
932
- required && /* @__PURE__ */ jsx17("span", { className: "text-error-default ml-0.5", children: "*" })
984
+ required && /* @__PURE__ */ jsx18("span", { className: "text-error-default ml-0.5", children: "*" })
933
985
  ]
934
986
  }
935
987
  ),
936
- /* @__PURE__ */ jsx17(FieldControlIdContext.Provider, { value: id, children })
988
+ /* @__PURE__ */ jsx18(FieldControlIdContext.Provider, { value: id, children })
937
989
  ] });
938
990
  }
939
991
 
@@ -941,7 +993,7 @@ function Field({ label, required = false, htmlFor, children }) {
941
993
  import { IconCheck as IconCheck2, IconChevronDown as IconChevronDown2 } from "@tabler/icons-react";
942
994
  import { useCallback as useCallback3, useEffect as useEffect3, useId as useId2, useLayoutEffect as useLayoutEffect4, useMemo as useMemo2, useRef as useRef5, useState as useState6 } from "react";
943
995
  import { createPortal as createPortal4 } from "react-dom";
944
- import { Fragment as Fragment2, jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
996
+ import { Fragment as Fragment2, jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
945
997
  function Select({ value, onChange, options, size = "default", ariaLabel, placeholder = "Select\u2026", disabled, className }) {
946
998
  const id = useId2();
947
999
  const triggerRef = useRef5(null);
@@ -1081,15 +1133,15 @@ function Select({ value, onChange, options, size = "default", ariaLabel, placeho
1081
1133
  ),
1082
1134
  children: [
1083
1135
  /* @__PURE__ */ jsxs12("span", { className: cn("flex min-w-0 items-center gap-2", !selected && "text-text-muted"), children: [
1084
- selected?.leading && /* @__PURE__ */ jsx18("span", { className: "flex shrink-0 items-center", children: selected.leading }),
1085
- /* @__PURE__ */ jsx18("span", { className: "truncate", children: selected?.label ?? placeholder })
1136
+ selected?.leading && /* @__PURE__ */ jsx19("span", { className: "flex shrink-0 items-center", children: selected.leading }),
1137
+ /* @__PURE__ */ jsx19("span", { className: "truncate", children: selected?.label ?? placeholder })
1086
1138
  ] }),
1087
- /* @__PURE__ */ jsx18(IconChevronDown2, { size: size === "small" ? 14 : 16, stroke: 1.5, className: "shrink-0 text-text-secondary" })
1139
+ /* @__PURE__ */ jsx19(IconChevronDown2, { size: size === "small" ? 14 : 16, stroke: 1.5, className: "shrink-0 text-text-secondary" })
1088
1140
  ]
1089
1141
  }
1090
1142
  ),
1091
1143
  open && rect && createPortal4(
1092
- /* @__PURE__ */ jsx18(
1144
+ /* @__PURE__ */ jsx19(
1093
1145
  "div",
1094
1146
  {
1095
1147
  ref: menuRef,
@@ -1122,10 +1174,10 @@ function Select({ value, onChange, options, size = "default", ariaLabel, placeho
1122
1174
  ),
1123
1175
  children: [
1124
1176
  /* @__PURE__ */ jsxs12("span", { className: "flex min-w-0 items-center gap-2", children: [
1125
- option.leading && /* @__PURE__ */ jsx18("span", { className: "flex shrink-0 items-center", children: option.leading }),
1126
- /* @__PURE__ */ jsx18("span", { className: "truncate", children: option.label })
1177
+ option.leading && /* @__PURE__ */ jsx19("span", { className: "flex shrink-0 items-center", children: option.leading }),
1178
+ /* @__PURE__ */ jsx19("span", { className: "truncate", children: option.label })
1127
1179
  ] }),
1128
- option.value === value && /* @__PURE__ */ jsx18(IconCheck2, { size: 16, stroke: 1.5, className: "shrink-0 text-text-secondary" })
1180
+ option.value === value && /* @__PURE__ */ jsx19(IconCheck2, { size: 16, stroke: 1.5, className: "shrink-0 text-text-secondary" })
1129
1181
  ]
1130
1182
  },
1131
1183
  option.value
@@ -1139,10 +1191,10 @@ function Select({ value, onChange, options, size = "default", ariaLabel, placeho
1139
1191
 
1140
1192
  // src/TextInput.tsx
1141
1193
  import { forwardRef } from "react";
1142
- import { jsx as jsx19 } from "react/jsx-runtime";
1194
+ import { jsx as jsx20 } from "react/jsx-runtime";
1143
1195
  var TextInput = forwardRef(function TextInput2({ className, type = "text", id, ...props }, ref) {
1144
1196
  const controlId = useFieldControlId(id);
1145
- return /* @__PURE__ */ jsx19(
1197
+ return /* @__PURE__ */ jsx20(
1146
1198
  "input",
1147
1199
  {
1148
1200
  ref,
@@ -1163,10 +1215,10 @@ var TextInput = forwardRef(function TextInput2({ className, type = "text", id, .
1163
1215
 
1164
1216
  // src/Textarea.tsx
1165
1217
  import { forwardRef as forwardRef2 } from "react";
1166
- import { jsx as jsx20 } from "react/jsx-runtime";
1218
+ import { jsx as jsx21 } from "react/jsx-runtime";
1167
1219
  var Textarea = forwardRef2(function Textarea2({ className, id, ...props }, ref) {
1168
1220
  const controlId = useFieldControlId(id);
1169
- return /* @__PURE__ */ jsx20(
1221
+ return /* @__PURE__ */ jsx21(
1170
1222
  "textarea",
1171
1223
  {
1172
1224
  ref,
@@ -1191,7 +1243,7 @@ import { useState as useState7 } from "react";
1191
1243
  import { useEffect as useEffect4 } from "react";
1192
1244
  import { createPortal as createPortal5 } from "react-dom";
1193
1245
  import { IconX as IconX2 } from "@tabler/icons-react";
1194
- import { Fragment as Fragment3, jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
1246
+ import { Fragment as Fragment3, jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
1195
1247
  function DialogShell({ title, onClose, headerContent, footer, children, bodyClassName, width = 502 }) {
1196
1248
  useEffect4(() => {
1197
1249
  const onKey = (event) => {
@@ -1202,8 +1254,8 @@ function DialogShell({ title, onClose, headerContent, footer, children, bodyClas
1202
1254
  }, [onClose]);
1203
1255
  return createPortal5(
1204
1256
  /* @__PURE__ */ jsxs13(Fragment3, { children: [
1205
- /* @__PURE__ */ jsx21("div", { className: "fixed inset-0 z-40 bg-black/50", onClick: onClose }),
1206
- /* @__PURE__ */ jsx21("div", { className: "fixed inset-0 z-50 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxs13(
1257
+ /* @__PURE__ */ jsx22("div", { className: "fixed inset-0 z-40 bg-black/50", onClick: onClose }),
1258
+ /* @__PURE__ */ jsx22("div", { className: "fixed inset-0 z-50 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxs13(
1207
1259
  "div",
1208
1260
  {
1209
1261
  role: "dialog",
@@ -1213,11 +1265,11 @@ function DialogShell({ title, onClose, headerContent, footer, children, bodyClas
1213
1265
  style: { width },
1214
1266
  children: [
1215
1267
  /* @__PURE__ */ jsxs13("div", { className: "h-12 flex items-center justify-between pl-5 pr-4 border-b border-border-subtle shrink-0", children: [
1216
- headerContent ?? /* @__PURE__ */ jsx21("span", { className: "text-h4 text-text-primary", children: title }),
1217
- /* @__PURE__ */ jsx21(IconButton, { tooltip: "Close", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx21(IconX2, { size: 16, stroke: 1.5 }) })
1268
+ headerContent ?? /* @__PURE__ */ jsx22("span", { className: "text-h4 text-text-primary", children: title }),
1269
+ /* @__PURE__ */ jsx22(IconButton, { tooltip: "Close", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx22(IconX2, { size: 16, stroke: 1.5 }) })
1218
1270
  ] }),
1219
- /* @__PURE__ */ jsx21("div", { className: cn("pl-5 pr-4 py-4", footer != null && "border-b border-border-subtle", bodyClassName), children }),
1220
- footer != null && /* @__PURE__ */ jsx21("div", { className: "h-12 flex items-center justify-end gap-2 pl-5 pr-4 shrink-0", children: footer })
1271
+ /* @__PURE__ */ jsx22("div", { className: cn("pl-5 pr-4 py-4", footer != null && "border-b border-border-subtle", bodyClassName), children }),
1272
+ footer != null && /* @__PURE__ */ jsx22("div", { className: "h-12 flex items-center justify-end gap-2 pl-5 pr-4 shrink-0", children: footer })
1221
1273
  ]
1222
1274
  }
1223
1275
  ) })
@@ -1227,7 +1279,7 @@ function DialogShell({ title, onClose, headerContent, footer, children, bodyClas
1227
1279
  }
1228
1280
 
1229
1281
  // src/ConfirmDialog.tsx
1230
- import { Fragment as Fragment4, jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
1282
+ import { Fragment as Fragment4, jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
1231
1283
  function ConfirmDialog({ title, children, confirmLabel, destructive = false, onConfirm, onClose }) {
1232
1284
  const [busy, setBusy] = useState7(false);
1233
1285
  const confirm = async () => {
@@ -1239,15 +1291,15 @@ function ConfirmDialog({ title, children, confirmLabel, destructive = false, onC
1239
1291
  setBusy(false);
1240
1292
  }
1241
1293
  };
1242
- return /* @__PURE__ */ jsx22(
1294
+ return /* @__PURE__ */ jsx23(
1243
1295
  DialogShell,
1244
1296
  {
1245
1297
  title,
1246
1298
  onClose,
1247
1299
  bodyClassName: "flex flex-col gap-3 text-body-2 text-text-primary",
1248
1300
  footer: /* @__PURE__ */ jsxs14(Fragment4, { children: [
1249
- /* @__PURE__ */ jsx22(Button, { variant: "muted", onClick: onClose, disabled: busy, children: "Cancel" }),
1250
- /* @__PURE__ */ jsx22(Button, { variant: destructive ? "destructive" : "primary", onClick: () => void confirm(), disabled: busy, children: confirmLabel })
1301
+ /* @__PURE__ */ jsx23(Button, { variant: "muted", onClick: onClose, disabled: busy, children: "Cancel" }),
1302
+ /* @__PURE__ */ jsx23(Button, { variant: destructive ? "destructive" : "primary", onClick: () => void confirm(), disabled: busy, children: confirmLabel })
1251
1303
  ] }),
1252
1304
  children
1253
1305
  }
@@ -1256,7 +1308,7 @@ function ConfirmDialog({ title, children, confirmLabel, destructive = false, onC
1256
1308
 
1257
1309
  // src/EditableText.tsx
1258
1310
  import { useEffect as useEffect5, useRef as useRef6, useState as useState8 } from "react";
1259
- import { jsx as jsx23 } from "react/jsx-runtime";
1311
+ import { jsx as jsx24 } from "react/jsx-runtime";
1260
1312
  function EditableText({ value, display, displayNode, placeholder, onCommit, multiline = false, className, label, readOnly = false }) {
1261
1313
  const [editing, setEditing] = useState8(false);
1262
1314
  const [draft, setDraft] = useState8(value);
@@ -1307,7 +1359,7 @@ function EditableText({ value, display, displayNode, placeholder, onCommit, mult
1307
1359
  className
1308
1360
  );
1309
1361
  if (readOnly) {
1310
- return /* @__PURE__ */ jsx23(
1362
+ return /* @__PURE__ */ jsx24(
1311
1363
  "div",
1312
1364
  {
1313
1365
  "aria-label": label,
@@ -1317,7 +1369,7 @@ function EditableText({ value, display, displayNode, placeholder, onCommit, mult
1317
1369
  );
1318
1370
  }
1319
1371
  if (editing) {
1320
- return multiline ? /* @__PURE__ */ jsx23(
1372
+ return multiline ? /* @__PURE__ */ jsx24(
1321
1373
  "textarea",
1322
1374
  {
1323
1375
  ref: fieldRef,
@@ -1330,7 +1382,7 @@ function EditableText({ value, display, displayNode, placeholder, onCommit, mult
1330
1382
  onBlur: () => void commit(),
1331
1383
  className: fieldClass
1332
1384
  }
1333
- ) : /* @__PURE__ */ jsx23(
1385
+ ) : /* @__PURE__ */ jsx24(
1334
1386
  "input",
1335
1387
  {
1336
1388
  ref: fieldRef,
@@ -1344,7 +1396,7 @@ function EditableText({ value, display, displayNode, placeholder, onCommit, mult
1344
1396
  }
1345
1397
  );
1346
1398
  }
1347
- return /* @__PURE__ */ jsx23(
1399
+ return /* @__PURE__ */ jsx24(
1348
1400
  "button",
1349
1401
  {
1350
1402
  type: "button",
@@ -1364,33 +1416,33 @@ function EditableText({ value, display, displayNode, placeholder, onCommit, mult
1364
1416
 
1365
1417
  // src/EmptyState.tsx
1366
1418
  import { IconMessage2 } from "@tabler/icons-react";
1367
- import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
1419
+ import { jsx as jsx25, jsxs as jsxs15 } from "react/jsx-runtime";
1368
1420
  function EmptyState({ icon, message, className }) {
1369
1421
  return /* @__PURE__ */ jsxs15("div", { className: cn("flex flex-col gap-2 items-center", className), children: [
1370
- /* @__PURE__ */ jsx24("span", { className: "text-text-secondary", children: icon ?? /* @__PURE__ */ jsx24(IconMessage2, { size: 16, stroke: 1.5 }) }),
1371
- /* @__PURE__ */ jsx24("p", { className: "text-body-2 text-text-secondary text-center", children: message })
1422
+ /* @__PURE__ */ jsx25("span", { className: "text-text-secondary", children: icon ?? /* @__PURE__ */ jsx25(IconMessage2, { size: 16, stroke: 1.5 }) }),
1423
+ /* @__PURE__ */ jsx25("p", { className: "text-body-2 text-text-secondary text-center", children: message })
1372
1424
  ] });
1373
1425
  }
1374
1426
 
1375
1427
  // src/Skeleton.tsx
1376
- import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
1428
+ import { jsx as jsx26, jsxs as jsxs16 } from "react/jsx-runtime";
1377
1429
  function SkeletonBar({ className, style }) {
1378
- return /* @__PURE__ */ jsx25("div", { className: cn("bg-bg-inset rounded-sm animate-pulse", className), style });
1430
+ return /* @__PURE__ */ jsx26("div", { className: cn("bg-bg-inset rounded-sm animate-pulse", className), style });
1379
1431
  }
1380
1432
  var ROW_BAR_WIDTHS = [150, 100, 170, 120, 90, 140, 110, 160];
1381
1433
  function SkeletonRow({ barWidth = 130 }) {
1382
1434
  return /* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-2 px-2 h-[32px] rounded-lg", children: [
1383
- /* @__PURE__ */ jsx25(SkeletonBar, { className: "w-4 h-4 shrink-0" }),
1384
- /* @__PURE__ */ jsx25(SkeletonBar, { className: "h-3.5", style: { width: barWidth } })
1435
+ /* @__PURE__ */ jsx26(SkeletonBar, { className: "w-4 h-4 shrink-0" }),
1436
+ /* @__PURE__ */ jsx26(SkeletonBar, { className: "h-3.5", style: { width: barWidth } })
1385
1437
  ] });
1386
1438
  }
1387
1439
  function SkeletonList({ rows = 8, className }) {
1388
- return /* @__PURE__ */ jsx25("div", { className: cn("flex flex-col gap-0.5 animate-skeleton-in", className), "aria-hidden": true, children: Array.from({ length: rows }, (_, i) => /* @__PURE__ */ jsx25(SkeletonRow, { barWidth: ROW_BAR_WIDTHS[i % ROW_BAR_WIDTHS.length] }, i)) });
1440
+ return /* @__PURE__ */ jsx26("div", { className: cn("flex flex-col gap-0.5 animate-skeleton-in", className), "aria-hidden": true, children: Array.from({ length: rows }, (_, i) => /* @__PURE__ */ jsx26(SkeletonRow, { barWidth: ROW_BAR_WIDTHS[i % ROW_BAR_WIDTHS.length] }, i)) });
1389
1441
  }
1390
1442
 
1391
1443
  // src/Breadcrumb.tsx
1392
1444
  import { Fragment as Fragment5, useCallback as useCallback4, useLayoutEffect as useLayoutEffect5, useRef as useRef7, useState as useState9 } from "react";
1393
- import { jsx as jsx26, jsxs as jsxs17 } from "react/jsx-runtime";
1445
+ import { jsx as jsx27, jsxs as jsxs17 } from "react/jsx-runtime";
1394
1446
  function Breadcrumb({ items, className }) {
1395
1447
  const navRef = useRef7(null);
1396
1448
  const labelRefs = useRef7([]);
@@ -1410,7 +1462,7 @@ function Breadcrumb({ items, className }) {
1410
1462
  observer.observe(nav);
1411
1463
  return () => observer.disconnect();
1412
1464
  }, [measure, items]);
1413
- return /* @__PURE__ */ jsx26("nav", { ref: navRef, "aria-label": "Breadcrumb", className: cn("flex min-w-0 items-center gap-1.5 text-[14px] leading-[140%]", className), children: items.map((item, index) => {
1465
+ return /* @__PURE__ */ jsx27("nav", { ref: navRef, "aria-label": "Breadcrumb", className: cn("flex min-w-0 items-center gap-1.5 text-[14px] leading-[140%]", className), children: items.map((item, index) => {
1414
1466
  const last = index === items.length - 1;
1415
1467
  const text = cn(
1416
1468
  "truncate",
@@ -1423,21 +1475,21 @@ function Breadcrumb({ items, className }) {
1423
1475
  const setLabelRef = (el) => {
1424
1476
  labelRefs.current[index] = el;
1425
1477
  };
1426
- const crumb = item.href ? /* @__PURE__ */ jsx26("a", { ref: setLabelRef, href: item.href, className: cn(text, "hover:text-text-primary"), children: item.label }) : /* @__PURE__ */ jsx26("span", { ref: setLabelRef, className: text, "aria-current": last ? "page" : void 0, children: item.label });
1478
+ const crumb = item.href ? /* @__PURE__ */ jsx27("a", { ref: setLabelRef, href: item.href, className: cn(text, "hover:text-text-primary"), children: item.label }) : /* @__PURE__ */ jsx27("span", { ref: setLabelRef, className: text, "aria-current": last ? "page" : void 0, children: item.label });
1427
1479
  return /* @__PURE__ */ jsxs17(Fragment5, { children: [
1428
- index > 0 && /* @__PURE__ */ jsx26("span", { "aria-hidden": "true", className: "shrink-0 text-text-muted", children: "/" }),
1480
+ index > 0 && /* @__PURE__ */ jsx27("span", { "aria-hidden": "true", className: "shrink-0 text-text-muted", children: "/" }),
1429
1481
  truncated.has(index) ? (
1430
1482
  // `min-w-0 shrink` undoes the wrapper's own shrink-0 — the crumb
1431
1483
  // must keep truncating inside it, or wrapping it would widen the
1432
1484
  // trail and the tooltip would never be needed again.
1433
- /* @__PURE__ */ jsx26(WithTooltip, { label: item.label, wrapperClassName: "min-w-0 shrink", children: crumb })
1485
+ /* @__PURE__ */ jsx27(WithTooltip, { label: item.label, wrapperClassName: "min-w-0 shrink", children: crumb })
1434
1486
  ) : crumb
1435
1487
  ] }, `${item.label}-${index}`);
1436
1488
  }) });
1437
1489
  }
1438
1490
 
1439
1491
  // src/NavItem.tsx
1440
- import { jsx as jsx27, jsxs as jsxs18 } from "react/jsx-runtime";
1492
+ import { jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
1441
1493
  function NavItem({ label, href, count, countLabel, active = false, icon, className, ...props }) {
1442
1494
  return /* @__PURE__ */ jsxs18(
1443
1495
  "a",
@@ -1454,22 +1506,22 @@ function NavItem({ label, href, count, countLabel, active = false, icon, classNa
1454
1506
  ),
1455
1507
  ...props,
1456
1508
  children: [
1457
- icon && /* @__PURE__ */ jsx27("span", { className: "flex shrink-0 items-center", children: icon }),
1458
- /* @__PURE__ */ jsx27("span", { className: "flex-1 truncate", children: label }),
1459
- count ? /* @__PURE__ */ jsx27(WithTooltip, { label: countLabel ?? `${count} open`, children: /* @__PURE__ */ jsx27("span", { className: "shrink-0 font-mono text-caption tabular-nums text-text-muted", children: count }) }) : null
1509
+ icon && /* @__PURE__ */ jsx28("span", { className: "flex shrink-0 items-center", children: icon }),
1510
+ /* @__PURE__ */ jsx28("span", { className: "flex-1 truncate", children: label }),
1511
+ count ? /* @__PURE__ */ jsx28(WithTooltip, { label: countLabel ?? `${count} open`, children: /* @__PURE__ */ jsx28("span", { className: "shrink-0 font-mono text-caption tabular-nums text-text-muted", children: count }) }) : null
1460
1512
  ]
1461
1513
  }
1462
1514
  );
1463
1515
  }
1464
1516
 
1465
1517
  // src/Rail.tsx
1466
- import { jsx as jsx28 } from "react/jsx-runtime";
1518
+ import { jsx as jsx29 } from "react/jsx-runtime";
1467
1519
  function Rail({ "aria-label": ariaLabel = "Navigation", children, className }) {
1468
- return /* @__PURE__ */ jsx28("nav", { "aria-label": ariaLabel, className: cn("flex w-16 shrink-0 flex-col items-start gap-2 px-2 py-3", className), children });
1520
+ return /* @__PURE__ */ jsx29("nav", { "aria-label": ariaLabel, className: cn("flex w-16 shrink-0 flex-col items-start gap-2 px-2 py-3", className), children });
1469
1521
  }
1470
1522
 
1471
1523
  // src/RailItem.tsx
1472
- import { jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
1524
+ import { jsx as jsx30, jsxs as jsxs19 } from "react/jsx-runtime";
1473
1525
  function RailItem({ href, icon, label, active = false, className, ...props }) {
1474
1526
  return /* @__PURE__ */ jsxs19(
1475
1527
  "a",
@@ -1479,14 +1531,14 @@ function RailItem({ href, icon, label, active = false, className, ...props }) {
1479
1531
  className: cn("group flex w-full shrink-0 flex-col items-center gap-0.5 px-2 py-0.5", className),
1480
1532
  ...props,
1481
1533
  children: [
1482
- /* @__PURE__ */ jsx29(
1534
+ /* @__PURE__ */ jsx30(
1483
1535
  "div",
1484
1536
  {
1485
1537
  className: cn(
1486
1538
  "flex items-center justify-center rounded-lg p-2 transition-colors",
1487
1539
  active ? "bg-bg-selected" : "group-hover:bg-bg-hover"
1488
1540
  ),
1489
- children: /* @__PURE__ */ jsx29(
1541
+ children: /* @__PURE__ */ jsx30(
1490
1542
  "span",
1491
1543
  {
1492
1544
  className: cn(
@@ -1498,16 +1550,16 @@ function RailItem({ href, icon, label, active = false, className, ...props }) {
1498
1550
  )
1499
1551
  }
1500
1552
  ),
1501
- /* @__PURE__ */ jsx29("span", { className: cn("text-center text-[9px] font-medium leading-[115%]", active ? "text-text-primary" : "text-text-secondary"), children: label })
1553
+ /* @__PURE__ */ jsx30("span", { className: cn("text-center text-[9px] font-medium leading-[115%]", active ? "text-text-primary" : "text-text-secondary"), children: label })
1502
1554
  ]
1503
1555
  }
1504
1556
  );
1505
1557
  }
1506
1558
 
1507
1559
  // src/Sidebar.tsx
1508
- import { jsx as jsx30 } from "react/jsx-runtime";
1560
+ import { jsx as jsx31 } from "react/jsx-runtime";
1509
1561
  function Sidebar({ "aria-label": ariaLabel = "Workspace", children, className }) {
1510
- return /* @__PURE__ */ jsx30(
1562
+ return /* @__PURE__ */ jsx31(
1511
1563
  "nav",
1512
1564
  {
1513
1565
  "aria-label": ariaLabel,
@@ -1521,7 +1573,7 @@ function Sidebar({ "aria-label": ariaLabel = "Workspace", children, className })
1521
1573
  }
1522
1574
 
1523
1575
  // src/Property.tsx
1524
- import { jsx as jsx31, jsxs as jsxs20 } from "react/jsx-runtime";
1576
+ import { jsx as jsx32, jsxs as jsxs20 } from "react/jsx-runtime";
1525
1577
  function Property({ label, layout = "row", children, className }) {
1526
1578
  if (layout === "stacked") {
1527
1579
  return (
@@ -1529,22 +1581,48 @@ function Property({ label, layout = "row", children, className }) {
1529
1581
  // Peek's topic-details sections already sit at 12px, Ship's rail was
1530
1582
  // 6px, and unifying means the bigger, calmer one.
1531
1583
  /* @__PURE__ */ jsxs20("div", { className: cn("flex flex-col gap-3", className), children: [
1532
- /* @__PURE__ */ jsx31("span", { className: "text-menu uppercase tracking-[0.08em] text-text-secondary", children: label }),
1584
+ /* @__PURE__ */ jsx32("span", { className: "text-menu uppercase tracking-[0.08em] text-text-secondary", children: label }),
1533
1585
  children
1534
1586
  ] })
1535
1587
  );
1536
1588
  }
1537
1589
  return /* @__PURE__ */ jsxs20("div", { className: cn("flex items-center gap-2", className), children: [
1538
- /* @__PURE__ */ jsx31("span", { className: "w-[68px] shrink-0 text-caption text-text-secondary", children: label }),
1590
+ /* @__PURE__ */ jsx32("span", { className: "w-[68px] shrink-0 text-caption text-text-secondary", children: label }),
1539
1591
  children
1540
1592
  ] });
1541
1593
  }
1542
1594
 
1595
+ // src/Reaction.tsx
1596
+ import { jsx as jsx33, jsxs as jsxs21 } from "react/jsx-runtime";
1597
+ function Reaction({ emoji, count, pressed = false, className, type, ...props }) {
1598
+ return /* @__PURE__ */ jsxs21(
1599
+ "button",
1600
+ {
1601
+ type: type ?? "button",
1602
+ "aria-pressed": pressed,
1603
+ className: cn(
1604
+ // Chip's pill at a control's height, so a reaction and a status chip
1605
+ // read as the same family — 24px matches Button `small`.
1606
+ "inline-flex h-6 items-center justify-center gap-1.5 rounded-full px-2",
1607
+ "border transition-colors",
1608
+ "disabled:cursor-not-allowed disabled:opacity-50",
1609
+ pressed ? "border-accent-primary bg-accent-muted text-accent-primary hover:border-accent-hover" : "border-border-default bg-bg-inset text-text-primary hover:border-border-strong hover:bg-bg-hover",
1610
+ className
1611
+ ),
1612
+ ...props,
1613
+ children: [
1614
+ /* @__PURE__ */ jsx33("span", { "aria-hidden": "true", className: "shrink-0 text-[16px] leading-none", children: emoji }),
1615
+ /* @__PURE__ */ jsx33("span", { className: "text-chip signal:font-mono signal:text-[10px] signal:font-semibold signal:tabular-nums", children: count })
1616
+ ]
1617
+ }
1618
+ );
1619
+ }
1620
+
1543
1621
  // src/SearchInput.tsx
1544
1622
  import "react";
1545
- import { jsx as jsx32, jsxs as jsxs21 } from "react/jsx-runtime";
1623
+ import { jsx as jsx34, jsxs as jsxs22 } from "react/jsx-runtime";
1546
1624
  function SearchInput({ shortcut, className, placeholder = "Search\u2026", ...props }) {
1547
- return /* @__PURE__ */ jsxs21(
1625
+ return /* @__PURE__ */ jsxs22(
1548
1626
  "div",
1549
1627
  {
1550
1628
  className: cn(
@@ -1554,7 +1632,7 @@ function SearchInput({ shortcut, className, placeholder = "Search\u2026", ...pro
1554
1632
  className
1555
1633
  ),
1556
1634
  children: [
1557
- /* @__PURE__ */ jsx32(
1635
+ /* @__PURE__ */ jsx34(
1558
1636
  "input",
1559
1637
  {
1560
1638
  className: "flex-1 min-w-0 bg-transparent text-input-value text-text-primary placeholder:text-text-muted outline-none",
@@ -1562,7 +1640,7 @@ function SearchInput({ shortcut, className, placeholder = "Search\u2026", ...pro
1562
1640
  ...props
1563
1641
  }
1564
1642
  ),
1565
- shortcut && /* @__PURE__ */ jsx32("div", { className: "flex items-center justify-center px-1 py-px rounded-sm bg-bg-inset border border-border-strong shrink-0 signal:bg-[rgba(255,255,255,.05)] signal:border-b-2", children: /* @__PURE__ */ jsx32("span", { className: "text-caption text-text-secondary whitespace-nowrap signal:font-mono signal:text-[10px]", children: shortcut }) })
1643
+ shortcut && /* @__PURE__ */ jsx34(Kbd, { children: shortcut })
1566
1644
  ]
1567
1645
  }
1568
1646
  );
@@ -1571,10 +1649,10 @@ function SearchInput({ shortcut, className, placeholder = "Search\u2026", ...pro
1571
1649
  // src/SectionHeader.tsx
1572
1650
  import { useState as useState10 } from "react";
1573
1651
  import { IconChevronRight as IconChevronRight2 } from "@tabler/icons-react";
1574
- import { jsx as jsx33, jsxs as jsxs22 } from "react/jsx-runtime";
1652
+ import { jsx as jsx35, jsxs as jsxs23 } from "react/jsx-runtime";
1575
1653
  function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, actions, showActions = "hover", className }) {
1576
1654
  const [isHovered, setIsHovered] = useState10(false);
1577
- return /* @__PURE__ */ jsxs22(
1655
+ return /* @__PURE__ */ jsxs23(
1578
1656
  "div",
1579
1657
  {
1580
1658
  className: cn(
@@ -1587,8 +1665,8 @@ function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, ac
1587
1665
  onMouseEnter: () => setIsHovered(true),
1588
1666
  onMouseLeave: () => setIsHovered(false),
1589
1667
  children: [
1590
- /* @__PURE__ */ jsxs22("div", { className: "flex shrink-0 items-center gap-1", children: [
1591
- chevron && /* @__PURE__ */ jsx33(
1668
+ /* @__PURE__ */ jsxs23("div", { className: "flex shrink-0 items-center gap-1", children: [
1669
+ chevron && /* @__PURE__ */ jsx35(
1592
1670
  IconChevronRight2,
1593
1671
  {
1594
1672
  size: 12,
@@ -1596,9 +1674,9 @@ function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, ac
1596
1674
  className: cn("text-text-secondary transition-transform duration-150", isExpanded && "rotate-90")
1597
1675
  }
1598
1676
  ),
1599
- /* @__PURE__ */ jsx33(SectionLabel, { children: title })
1677
+ /* @__PURE__ */ jsx35(SectionLabel, { children: title })
1600
1678
  ] }),
1601
- (showActions === "always" || isHovered) && actions && actions.length > 0 && /* @__PURE__ */ jsx33("div", { className: "flex items-center gap-1", children: actions.map((action) => /* @__PURE__ */ jsx33(
1679
+ (showActions === "always" || isHovered) && actions && actions.length > 0 && /* @__PURE__ */ jsx35("div", { className: "flex items-center gap-1", children: actions.map((action) => /* @__PURE__ */ jsx35(
1602
1680
  IconButton,
1603
1681
  {
1604
1682
  tooltip: action.tooltip,
@@ -1617,9 +1695,9 @@ function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, ac
1617
1695
  }
1618
1696
 
1619
1697
  // src/Tabs.tsx
1620
- import { jsx as jsx34, jsxs as jsxs23 } from "react/jsx-runtime";
1698
+ import { jsx as jsx36, jsxs as jsxs24 } from "react/jsx-runtime";
1621
1699
  function Tabs({ tabs, active, onChange, size = "default", className }) {
1622
- return /* @__PURE__ */ jsx34("div", { role: "tablist", className: cn("flex items-center gap-2", className), children: tabs.map((tab) => /* @__PURE__ */ jsxs23(
1700
+ return /* @__PURE__ */ jsx36("div", { role: "tablist", className: cn("flex items-center gap-2", className), children: tabs.map((tab) => /* @__PURE__ */ jsxs24(
1623
1701
  "button",
1624
1702
  {
1625
1703
  type: "button",
@@ -1638,9 +1716,9 @@ function Tabs({ tabs, active, onChange, size = "default", className }) {
1638
1716
  ),
1639
1717
  children: [
1640
1718
  tab.icon,
1641
- /* @__PURE__ */ jsxs23("span", { className: "flex items-baseline", children: [
1719
+ /* @__PURE__ */ jsxs24("span", { className: "flex items-baseline", children: [
1642
1720
  tab.label,
1643
- tab.count !== void 0 ? /* @__PURE__ */ jsx34("span", { className: "ml-2.5 font-mono text-caption tabular-nums text-text-secondary", children: tab.count }) : null
1721
+ tab.count !== void 0 ? /* @__PURE__ */ jsx36("span", { className: "ml-2.5 font-mono text-caption tabular-nums text-text-secondary", children: tab.count }) : null
1644
1722
  ] })
1645
1723
  ]
1646
1724
  },
@@ -1652,7 +1730,7 @@ function Tabs({ tabs, active, onChange, size = "default", className }) {
1652
1730
  import { createContext as createContext3, useCallback as useCallback5, useContext as useContext3, useEffect as useEffect6, useMemo as useMemo3, useState as useState11 } from "react";
1653
1731
  import { createPortal as createPortal6 } from "react-dom";
1654
1732
  import { IconCircleCheck } from "@tabler/icons-react";
1655
- import { jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
1733
+ import { jsx as jsx37, jsxs as jsxs25 } from "react/jsx-runtime";
1656
1734
  var SURFACE_BY_TYPE = {
1657
1735
  success: "bg-success-muted signal:bg-bg-inset signal:border signal:border-border-default signal:shadow-[shadow:var(--shadow-md)]",
1658
1736
  brand: "bg-accent-muted signal:bg-bg-inset signal:border signal:border-border-default signal:shadow-[shadow:var(--shadow-md)]",
@@ -1670,7 +1748,7 @@ var ACTION_BORDER_BY_TYPE = {
1670
1748
  };
1671
1749
  function Toast({ label, type = "neutral", leadingIcon = true, actionLabel, onAction, className }) {
1672
1750
  const hasAction = !!(actionLabel && onAction);
1673
- return /* @__PURE__ */ jsxs24(
1751
+ return /* @__PURE__ */ jsxs25(
1674
1752
  "div",
1675
1753
  {
1676
1754
  className: cn(
@@ -1682,11 +1760,11 @@ function Toast({ label, type = "neutral", leadingIcon = true, actionLabel, onAct
1682
1760
  className
1683
1761
  ),
1684
1762
  children: [
1685
- /* @__PURE__ */ jsxs24("div", { className: "flex items-center gap-2 shrink-0", children: [
1686
- leadingIcon && /* @__PURE__ */ jsx35(IconCircleCheck, { size: 16, stroke: 1.5, className: cn("text-text-primary shrink-0", ICON_BY_TYPE[type]) }),
1687
- /* @__PURE__ */ jsx35("span", { className: "font-normal text-[14px] leading-[1.4] text-text-primary whitespace-nowrap", children: label })
1763
+ /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-2 shrink-0", children: [
1764
+ leadingIcon && /* @__PURE__ */ jsx37(IconCircleCheck, { size: 16, stroke: 1.5, className: cn("text-text-primary shrink-0", ICON_BY_TYPE[type]) }),
1765
+ /* @__PURE__ */ jsx37("span", { className: "font-normal text-[14px] leading-[1.4] text-text-primary whitespace-nowrap", children: label })
1688
1766
  ] }),
1689
- hasAction && /* @__PURE__ */ jsx35(
1767
+ hasAction && /* @__PURE__ */ jsx37(
1690
1768
  "button",
1691
1769
  {
1692
1770
  type: "button",
@@ -1696,7 +1774,7 @@ function Toast({ label, type = "neutral", leadingIcon = true, actionLabel, onAct
1696
1774
  ACTION_BORDER_BY_TYPE[type],
1697
1775
  type === "neutral" ? "hover:border-border-strong" : "hover:opacity-80"
1698
1776
  ),
1699
- children: /* @__PURE__ */ jsx35("span", { className: "font-medium text-[12px] leading-[12px] text-text-primary whitespace-nowrap", children: actionLabel })
1777
+ children: /* @__PURE__ */ jsx37("span", { className: "font-medium text-[12px] leading-[12px] text-text-primary whitespace-nowrap", children: actionLabel })
1700
1778
  }
1701
1779
  )
1702
1780
  ]
@@ -1723,10 +1801,10 @@ function ToastProvider({ children }) {
1723
1801
  return () => clearTimeout(timer);
1724
1802
  }, [toast]);
1725
1803
  const value = useMemo3(() => ({ showToast, dismissToast }), [showToast, dismissToast]);
1726
- return /* @__PURE__ */ jsxs24(ToastContext.Provider, { value, children: [
1804
+ return /* @__PURE__ */ jsxs25(ToastContext.Provider, { value, children: [
1727
1805
  children,
1728
1806
  toast && createPortal6(
1729
- /* @__PURE__ */ jsx35("div", { className: "fixed bottom-4 left-4 z-[100] pointer-events-none", children: /* @__PURE__ */ jsx35(
1807
+ /* @__PURE__ */ jsx37("div", { className: "fixed bottom-4 left-4 z-[100] pointer-events-none", children: /* @__PURE__ */ jsx37(
1730
1808
  Toast,
1731
1809
  {
1732
1810
  className: "pointer-events-auto",
@@ -1770,8 +1848,10 @@ export {
1770
1848
  IdentityMenu,
1771
1849
  IdentityPanel,
1772
1850
  InputChip,
1851
+ Kbd,
1773
1852
  Menu,
1774
1853
  MenuItem,
1854
+ MenuPanel,
1775
1855
  MenuRow,
1776
1856
  MenuSection,
1777
1857
  MenuSub,
@@ -1781,6 +1861,7 @@ export {
1781
1861
  Property,
1782
1862
  Rail,
1783
1863
  RailItem,
1864
+ Reaction,
1784
1865
  SearchInput,
1785
1866
  SectionHeader,
1786
1867
  SectionLabel,