@q9labsai/chalk-react 5.1.0 → 5.2.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/dist/bindings/hooks.d.ts +2 -0
  3. package/dist/{chunk-QDEKMRH2.js → chunk-CYC2G42K.js} +66 -58
  4. package/dist/{chunk-XOHNIYSR.js → chunk-G62BKRNX.js} +1534 -948
  5. package/dist/{chunk-ZFWIMKUC.js → chunk-HWQLW4JR.js} +55 -7
  6. package/dist/components/board/BoardMenu.d.ts +4 -1
  7. package/dist/components/board/board-draw-policy.d.ts +15 -0
  8. package/dist/components/board/board-model.d.ts +5 -1
  9. package/dist/components/board/board-view-versions.d.ts +1 -7
  10. package/dist/components/chalk/Chalk.d.ts +4 -0
  11. package/dist/components/chalk/ReportProblemDialog.d.ts +12 -0
  12. package/dist/components/chalk/StatusSurface.d.ts +3 -2
  13. package/dist/components/chalk-ui/ChalkSheet.d.ts +1 -1
  14. package/dist/components/chalk-ui/ChalkToast.d.ts +7 -1
  15. package/dist/components/control-bar/command-failure.d.ts +105 -1
  16. package/dist/components/control-bar/control-bar-behavior.d.ts +5 -1
  17. package/dist/components/index.js +3 -3
  18. package/dist/components/offline-actions/OfflineActions.d.ts +2 -1
  19. package/dist/components/space-header/CallDetails.d.ts +4 -2
  20. package/dist/components/space-header/space-header-contract.d.ts +5 -0
  21. package/dist/components/space-view/SpaceView.d.ts +8 -3
  22. package/dist/components/stage/Stage.d.ts +1 -1
  23. package/dist/components/stage/stage-items.d.ts +4 -0
  24. package/dist/components/stage/stage-layout.d.ts +6 -2
  25. package/dist/components/toast-stack/CallEventIcon.d.ts +1 -1
  26. package/dist/components/toast-stack/NoticeButton.d.ts +7 -0
  27. package/dist/components/toast-stack/ToastStack.d.ts +13 -2
  28. package/dist/components/toast-stack/call-events.d.ts +2 -1
  29. package/dist/components/toast-stack/notice-announcement.d.ts +4 -0
  30. package/dist/components/toast-stack/toast-layout-snapshot.d.ts +4 -0
  31. package/dist/components/toast-stack/useToastCapacity.d.ts +7 -0
  32. package/dist/headless.js +1 -1
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.js +24 -3
  35. package/dist/internal/whiteboard-view-limits.d.ts +1 -0
  36. package/dist/public-surface.contract.d.ts +1 -1
  37. package/dist/test-support/index.js +3 -3
  38. package/package.json +5 -5
@@ -3,14 +3,17 @@ import {
3
3
  DEFAULT_SPOTLIGHT_OPTIONS,
4
4
  EMPTY_GEOMETRY,
5
5
  MORE_TILE_ID,
6
+ WHITEBOARD_ITEM_ID,
6
7
  buildStageItems,
7
8
  choosePrimary,
8
9
  fitGrid,
9
10
  fitSpotlight,
11
+ floatingSpeakerId,
10
12
  gridOrder,
11
13
  isContentItem,
14
+ speakingParticipantId,
12
15
  stabilizeOrder
13
- } from "./chunk-ZFWIMKUC.js";
16
+ } from "./chunk-HWQLW4JR.js";
14
17
  import {
15
18
  ArrowLeft01Icon,
16
19
  ArrowRight01Icon,
@@ -102,6 +105,13 @@ function useWhiteboard() {
102
105
  function useCan(capability) {
103
106
  return useSelf().can(capability);
104
107
  }
108
+ function useCanDrawWhiteboard() {
109
+ const whiteboard = useWhiteboard();
110
+ const roleDefault = useCan("drawWhiteboard");
111
+ if (whiteboard.engine.status === "unsubscribed") return roleDefault;
112
+ if (whiteboard.access.policy === null) return roleDefault && whiteboard.access.canDraw;
113
+ return whiteboard.access.canDraw;
114
+ }
105
115
  function useScreenShare() {
106
116
  const client = useSpaceClient();
107
117
  const { screenShare } = useMedia();
@@ -385,7 +395,7 @@ function ChalkTooltip({ trigger, children }) {
385
395
 
386
396
  // src/components/chalk-ui/ChalkPopover.tsx
387
397
  import { Popover } from "@base-ui/react/popover";
388
- import { useRef as useRef3 } from "react";
398
+ import { useState as useState3 } from "react";
389
399
  import { jsx as jsx17, jsxs as jsxs5 } from "react/jsx-runtime";
390
400
  function ChalkPopover({
391
401
  trigger,
@@ -397,20 +407,12 @@ function ChalkPopover({
397
407
  open,
398
408
  onOpenChange
399
409
  }) {
400
- const container = useRef3(null);
401
- return /* @__PURE__ */ jsx17(
402
- "span",
403
- {
404
- ref: (element) => {
405
- container.current = element?.closest("[data-chalk]") ?? element;
406
- },
407
- style: { display: "contents" },
408
- children: /* @__PURE__ */ jsxs5(Popover.Root, { open, onOpenChange, children: [
409
- tooltip ? /* @__PURE__ */ jsx17(ChalkTooltip, { trigger: /* @__PURE__ */ jsx17(Popover.Trigger, { render: trigger }), children: tooltip }) : /* @__PURE__ */ jsx17(Popover.Trigger, { render: trigger }),
410
- /* @__PURE__ */ jsx17(Popover.Portal, { container, children: /* @__PURE__ */ jsx17(Popover.Positioner, { side: "bottom", align: "start", alignOffset, sideOffset: 8, collisionPadding: 8, className: "chalk-popover-positioner", children: /* @__PURE__ */ jsx17(Popover.Popup, { "aria-label": label, "data-part": "popover", className: cn("chalk-menu chalk-popover", className), children }) }) })
411
- ] })
412
- }
413
- );
410
+ const [holder, setHolder] = useState3(null);
411
+ const root = holder?.closest("[data-chalk]") ?? null;
412
+ return /* @__PURE__ */ jsx17("span", { ref: setHolder, style: { display: "contents" }, children: /* @__PURE__ */ jsxs5(Popover.Root, { open, onOpenChange, children: [
413
+ tooltip ? /* @__PURE__ */ jsx17(ChalkTooltip, { trigger: /* @__PURE__ */ jsx17(Popover.Trigger, { render: trigger }), children: tooltip }) : /* @__PURE__ */ jsx17(Popover.Trigger, { render: trigger }),
414
+ /* @__PURE__ */ jsx17(Popover.Portal, { container: root ?? holder, children: /* @__PURE__ */ jsx17(Popover.Positioner, { side: "bottom", align: "start", alignOffset, sideOffset: 8, collisionPadding: 8, collisionBoundary: root ?? void 0, className: "chalk-popover-positioner", children: /* @__PURE__ */ jsx17(Popover.Popup, { "aria-label": label, "data-part": "popover", className: cn("chalk-menu chalk-popover", className), children }) }) })
415
+ ] }) });
414
416
  }
415
417
 
416
418
  // src/components/chalk-ui/ChalkSegmentedControl.tsx
@@ -437,12 +439,12 @@ function ChalkSegmentedControl({
437
439
  }
438
440
 
439
441
  // src/components/chalk-ui/ChalkCopyButton.tsx
440
- import { useEffect as useEffect2, useRef as useRef4, useState as useState3 } from "react";
442
+ import { useEffect as useEffect2, useRef as useRef3, useState as useState4 } from "react";
441
443
  import { Fragment as Fragment3, jsx as jsx19, jsxs as jsxs6 } from "react/jsx-runtime";
442
444
  function ChalkCopyButton({ text, label = "Copy link", announcement = "Link copied", showLabel = false }) {
443
- const [copied, setCopied] = useState3(false);
444
- const [error, setError] = useState3(false);
445
- const timer = useRef4(void 0);
445
+ const [copied, setCopied] = useState4(false);
446
+ const [error, setError] = useState4(false);
447
+ const timer = useRef3(void 0);
446
448
  useEffect2(() => () => clearTimeout(timer.current), []);
447
449
  const copy = async () => {
448
450
  try {
@@ -472,10 +474,10 @@ function ChalkCopyButton({ text, label = "Copy link", announcement = "Link copie
472
474
 
473
475
  // src/components/chalk-ui/ChalkSheet.tsx
474
476
  import { Dialog } from "@base-ui/react/dialog";
475
- import { useCallback as useCallback2, useState as useState4 } from "react";
477
+ import { useCallback as useCallback2, useState as useState5 } from "react";
476
478
  import { jsx as jsx20, jsxs as jsxs7 } from "react/jsx-runtime";
477
479
  function ChalkSheet({ open, onOpenChange, title, accessibleLabel, children, variant = "default" }) {
478
- const [container, setContainer] = useState4(null);
480
+ const [container, setContainer] = useState5(null);
479
481
  const attach = useCallback2((element) => setContainer(element?.closest("[data-chalk]") ?? element), []);
480
482
  return /* @__PURE__ */ jsx20("span", { ref: attach, style: { display: "contents" }, children: /* @__PURE__ */ jsx20(Dialog.Root, { open, onOpenChange, children: container ? /* @__PURE__ */ jsxs7(Dialog.Portal, { container, children: [
481
483
  /* @__PURE__ */ jsx20(Dialog.Backdrop, { "data-part": "sheet-scrim", className: "chalk-sheet-scrim" }),
@@ -503,32 +505,53 @@ function ChalkSheet({ open, onOpenChange, title, accessibleLabel, children, vari
503
505
  }
504
506
 
505
507
  // src/components/chalk-ui/ChalkToast.tsx
506
- import { useEffect as useEffect3, useRef as useRef5, useState as useState5 } from "react";
508
+ import { useEffect as useEffect3, useRef as useRef4, useState as useState6 } from "react";
509
+
510
+ // src/components/toast-stack/notice-announcement.ts
511
+ import { Children, Fragment as Fragment4, isValidElement } from "react";
512
+ function literalText(node) {
513
+ if (typeof node === "string" || typeof node === "number" || typeof node === "bigint") return String(node);
514
+ if (node == null || typeof node === "boolean") return "";
515
+ if (isValidElement(node)) return typeof node.type === "string" || node.type === Fragment4 ? literalText(node.props.children) : "";
516
+ if (typeof node === "object" && "then" in node) return "";
517
+ return typeof node === "object" && Symbol.iterator in node ? Children.toArray(node).map(literalText).join("") : "";
518
+ }
519
+ function noticeAnnouncement(toast) {
520
+ return toast.announcementText ?? literalText(toast.message);
521
+ }
522
+ function noticeIdentity(toast) {
523
+ return JSON.stringify([noticeAnnouncement(toast), toast.announcementKey, toast.type ?? "info"]);
524
+ }
525
+
526
+ // src/components/chalk-ui/ChalkToast.tsx
507
527
  import { jsx as jsx21, jsxs as jsxs8 } from "react/jsx-runtime";
508
- function ChalkToast({ message, type = "info", duration = type === "info" ? 5e3 : Infinity, action, icon, onDismiss }) {
509
- const dismissRef = useRef5(onDismiss);
528
+ function ChalkToast({ message, announcementKey, announcementText, type = "info", duration = type === "info" ? 5e3 : Infinity, action, icon, announce = true, onDismiss }) {
529
+ const dismissRef = useRef4(onDismiss);
510
530
  dismissRef.current = onDismiss;
511
- const [exiting, setExiting] = useState5(false);
531
+ const identity = noticeIdentity({ message, announcementText, announcementKey, type });
532
+ const [lifetime, setLifetime] = useState6({ identity, exiting: false });
533
+ if (lifetime.identity !== identity) setLifetime({ identity, exiting: false });
534
+ const exiting = lifetime.identity === identity && lifetime.exiting;
512
535
  useEffect3(() => {
513
536
  if (duration === Infinity) return;
514
- const timer = setTimeout(() => setExiting(true), duration);
537
+ const timer = setTimeout(() => setLifetime({ identity, exiting: true }), duration);
515
538
  return () => clearTimeout(timer);
516
- }, [duration]);
539
+ }, [duration, identity]);
517
540
  useEffect3(() => {
518
541
  if (!exiting) return;
519
542
  const timer = setTimeout(() => dismissRef.current(), 160);
520
543
  return () => clearTimeout(timer);
521
- }, [exiting]);
522
- return /* @__PURE__ */ jsxs8("div", { "data-part": "toast", "data-exiting": exiting || void 0, className: "chalk-toast", style: type === "error" ? { maxWidth: "min(560px, calc(100% - 32px))" } : void 0, role: type === "error" ? "alert" : "status", "aria-live": type === "error" ? "assertive" : "polite", "aria-atomic": "true", children: [
544
+ }, [exiting, identity]);
545
+ return /* @__PURE__ */ jsxs8("div", { "data-part": "toast", "data-type": type, "data-exiting": exiting || void 0, className: "chalk-toast", role: announce ? type === "error" ? "alert" : "status" : void 0, "aria-live": announce ? type === "error" ? "assertive" : "polite" : void 0, "aria-atomic": announce || void 0, children: [
523
546
  icon,
524
- /* @__PURE__ */ jsx21("span", { style: type === "error" ? { whiteSpace: "normal", lineHeight: 1.35, padding: "4px 0" } : void 0, children: message }),
547
+ /* @__PURE__ */ jsx21("span", { children: message }),
525
548
  action ? /* @__PURE__ */ jsx21(ChalkButton, { onClick: action.onClick, children: action.label }) : null,
526
- type === "error" ? /* @__PURE__ */ jsx21("button", { type: "button", "data-part": "toast-dismiss", className: "chalk-dismiss", "aria-label": "Dismiss", onClick: () => setExiting(true), children: /* @__PURE__ */ jsx21("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx21("path", { d: "m6 6 12 12M6 18 18 6" }) }) }) : null
549
+ type === "error" ? /* @__PURE__ */ jsx21("button", { type: "button", "data-part": "toast-dismiss", className: "chalk-dismiss", "aria-label": "Dismiss", onClick: () => setLifetime({ identity, exiting: true }), children: /* @__PURE__ */ jsx21("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx21("path", { d: "m6 6 12 12M6 18 18 6" }) }) }) : null
527
550
  ] });
528
551
  }
529
552
 
530
553
  // src/components/call-limbo/CallLimbo.tsx
531
- import { useEffect as useEffect4, useRef as useRef6, useState as useState6 } from "react";
554
+ import { useEffect as useEffect4, useRef as useRef5, useState as useState7 } from "react";
532
555
 
533
556
  // src/theme.ts
534
557
  var LIGHT_CHALK_THEME_TOKENS = {
@@ -612,28 +635,29 @@ function chalkThemeStyle(theme, colorScheme = theme?.colorScheme ?? "dark") {
612
635
  }
613
636
 
614
637
  // src/components/toast-stack/CallEventIcon.tsx
615
- import { Fragment as Fragment4, jsx as jsx22, jsxs as jsxs9 } from "react/jsx-runtime";
638
+ import { Fragment as Fragment5, jsx as jsx22, jsxs as jsxs9 } from "react/jsx-runtime";
616
639
  function CallEventIcon({ kind, error = false }) {
617
640
  const paths2 = {
641
+ speaker: /* @__PURE__ */ jsx22("path", { d: "M11 5 6 9H3v6h3l5 4V5ZM15 8a6 6 0 0 1 0 8M18 5a10 10 0 0 1 0 14" }),
618
642
  microphone: /* @__PURE__ */ jsx22("path", { d: "M2 2l20 20M15 9.3V5a3 3 0 0 0-5.7-1.3M9 9v2a3 3 0 0 0 5.1 2.1M17 17a7 7 0 0 1-12-6v-1M19 10v1a7 7 0 0 1-.1 1.2M12 18v4" }),
619
- "microphone-on": /* @__PURE__ */ jsxs9(Fragment4, { children: [
643
+ "microphone-on": /* @__PURE__ */ jsxs9(Fragment5, { children: [
620
644
  /* @__PURE__ */ jsx22("rect", { x: "9", y: "2", width: "6", height: "12", rx: "3" }),
621
645
  /* @__PURE__ */ jsx22("path", { d: "M19 10v1a7 7 0 0 1-14 0v-1M12 18v4" })
622
646
  ] }),
623
647
  camera: /* @__PURE__ */ jsx22("path", { d: "M2 2l20 20M10.7 6H14a2 2 0 0 1 2 2v3.3l1 1 4-2.8v8M16 16a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2" }),
624
- screen: /* @__PURE__ */ jsxs9(Fragment4, { children: [
648
+ screen: /* @__PURE__ */ jsxs9(Fragment5, { children: [
625
649
  /* @__PURE__ */ jsx22("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }),
626
650
  /* @__PURE__ */ jsx22("path", { d: "M8 21h8M12 17v4M9.5 7.5l5 5M14.5 7.5l-5 5" })
627
651
  ] }),
628
- recording: /* @__PURE__ */ jsxs9(Fragment4, { children: [
652
+ recording: /* @__PURE__ */ jsxs9(Fragment5, { children: [
629
653
  /* @__PURE__ */ jsx22("circle", { cx: "12", cy: "12", r: "9" }),
630
654
  /* @__PURE__ */ jsx22("circle", { cx: "12", cy: "12", r: "3.5", fill: "currentColor" })
631
655
  ] }),
632
- hand: /* @__PURE__ */ jsxs9(Fragment4, { children: [
656
+ hand: /* @__PURE__ */ jsxs9(Fragment5, { children: [
633
657
  /* @__PURE__ */ jsx22("path", { d: "M18 11V6a2 2 0 0 0-4 0v5M14 10V4a2 2 0 0 0-4 0v6M10 10.5V6a2 2 0 0 0-4 0v8" }),
634
658
  /* @__PURE__ */ jsx22("path", { d: "M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.9-6-2.3l-3.6-3.6a2 2 0 0 1 2.8-2.8L7 15" })
635
659
  ] }),
636
- host: /* @__PURE__ */ jsxs9(Fragment4, { children: [
660
+ host: /* @__PURE__ */ jsxs9(Fragment5, { children: [
637
661
  /* @__PURE__ */ jsx22("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }),
638
662
  /* @__PURE__ */ jsx22("circle", { cx: "9", cy: "7", r: "4" }),
639
663
  /* @__PURE__ */ jsx22("path", { d: "M22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8" })
@@ -645,9 +669,9 @@ function CallEventIcon({ kind, error = false }) {
645
669
  // src/components/call-limbo/CallLimbo.tsx
646
670
  import { jsx as jsx23, jsxs as jsxs10 } from "react/jsx-runtime";
647
671
  function CallLimbo({ spaceName = "Call", message, detail, action, children, overlay = false, active = true, initiallyCovered = false, theme, recorded = false, blocking = true, className }) {
648
- const layerRef = useRef6(null);
672
+ const layerRef = useRef5(null);
649
673
  const reducedMotion = usePrefersReducedMotion();
650
- const [covered, setCovered] = useState6(active || initiallyCovered);
674
+ const [covered, setCovered] = useState7(active || initiallyCovered);
651
675
  useEffect4(() => {
652
676
  let nextFrame = 0;
653
677
  const frame = requestAnimationFrame(() => {
@@ -724,11 +748,112 @@ function CallLimbo({ spaceName = "Call", message, detail, action, children, over
724
748
  ] });
725
749
  }
726
750
 
727
- // src/components/media-request-dialog/media-request-dialog-state.ts
728
- import { useEffect as useEffect5, useMemo as useMemo2, useState as useState7 } from "react";
729
- function errorMessage(cause) {
730
- return cause instanceof Error ? cause.message : "This media request could not be completed.";
751
+ // src/components/control-bar/command-failure.ts
752
+ import { SpaceClientError } from "@q9labsai/chalk-client";
753
+ function commandFailure(cause) {
754
+ return cause instanceof SpaceClientError ? cause : new SpaceClientError({
755
+ code: "client.internal_error",
756
+ recoverable: false,
757
+ message: cause instanceof Error ? cause.message : "This command could not be completed."
758
+ });
759
+ }
760
+ var again = ({ recoverable }) => recoverable ? " Try again." : "";
761
+ var device = ({ source }) => source ?? "device";
762
+ var settings = (context) => `Couldn\u2019t change ${context.source === "screen" ? "screen sharing" : `your ${device(context)} settings`}.`;
763
+ var stop = (context) => context.source === "screen" ? "Couldn\u2019t stop screen sharing." : settings(context);
764
+ var start = (context) => context.source === "screen" ? "Your screen couldn\u2019t be shared." : `Your ${device(context)} couldn\u2019t start.`;
765
+ var test = () => "Couldn\u2019t play the speaker test. Check your browser\u2019s audio settings and system volume.";
766
+ var operationCopy = {
767
+ start: (context) => `${start(context)}${again(context)}`,
768
+ stop: (context) => `${stop(context)}${again(context)}`,
769
+ select: (context) => `Couldn\u2019t select your ${device(context)}.${again(context)}`,
770
+ test: (context) => `${test(context)}${again(context)}`,
771
+ request: (context) => `Couldn\u2019t respond to the ${device(context)} request.${again(context)}`,
772
+ change: (context) => `${settings(context)}${again(context)}`
773
+ };
774
+ var generic = { copy: operationCopy, retry: true };
775
+ var unavailable = { copy: operationCopy, retry: false };
776
+ var specific = (copy) => ({ copy: { ...operationCopy, start: copy, select: copy, request: copy, change: copy }, retry: false });
777
+ var capture = (context) => `${settings(context)} Check that it is connected and available${context.recoverable ? ", then try again" : ""}.`;
778
+ var captureStart = (context) => context.source === "screen" ? `${start(context)}${again(context)}` : `Your ${device(context)} couldn\u2019t start. Check that it is connected and available${context.recoverable ? ", then try again" : ""}.`;
779
+ var MEDIA_FAILURE_POLICY = {
780
+ "access.invalid": specific(() => "Your call access is unavailable. Rejoin the call."),
781
+ "access.unavailable": specific(() => "Your call access is unavailable. Rejoin the call."),
782
+ "chat.cursor_reset_required": unavailable,
783
+ "chat.payload_invalid": unavailable,
784
+ "client.internal_error": generic,
785
+ "collaboration.unavailable": unavailable,
786
+ "command.rate_limited": generic,
787
+ "command.rejected": unavailable,
788
+ "connection.invalid_state": {
789
+ copy: {
790
+ ...operationCopy,
791
+ start: (context) => `That change was interrupted.${again(context)}`,
792
+ stop: (context) => `That change was interrupted.${again(context)}`,
793
+ select: (context) => `That change was interrupted.${again(context)}`,
794
+ change: (context) => `That change was interrupted.${again(context)}`
795
+ },
796
+ retry: true
797
+ },
798
+ "connection.join_cleanup_unconfirmed": unavailable,
799
+ "connection.leave_unconfirmed": unavailable,
800
+ "connection.media_recovery_exhausted": unavailable,
801
+ "connection.media_start_failed": generic,
802
+ "connection.state_digest_recovery_failed": unavailable,
803
+ "connection.sync_recovery_exhausted": unavailable,
804
+ "connection.sync_start_failed": unavailable,
805
+ "connection.sync_hello_timeout": unavailable,
806
+ "environment.unsupported": {
807
+ copy: {
808
+ ...operationCopy,
809
+ start: () => "Media capture is unavailable in this environment. Use a supported browser over HTTPS or the native app.",
810
+ request: () => "Media capture is unavailable in this environment. Use a supported browser over HTTPS or the native app.",
811
+ select: (context) => context.source === "speaker" ? "This browser can\u2019t change your speaker. Use your system sound settings to choose an output device." : "Media capture is unavailable in this environment. Use a supported browser over HTTPS or the native app.",
812
+ test: () => "This browser can\u2019t play a speaker test. Check your browser\u2019s audio settings or use another supported browser.",
813
+ change: (context) => context.source === "speaker" ? "This browser can\u2019t change your speaker. Use your system sound settings to choose an output device." : "Media capture is unavailable in this environment. Use a supported browser over HTTPS or the native app."
814
+ },
815
+ retry: false
816
+ },
817
+ "episode.ended": { copy: { start: () => "This call has ended.", stop: () => "This call has ended.", select: () => "This call has ended.", test: () => "This call has ended.", request: () => "This call has ended.", change: () => "This call has ended." }, retry: false },
818
+ "media.permission_denied": specific(
819
+ (context) => context.source === "screen" ? `Your screen couldn\u2019t be shared. Allow screen recording for your browser in System Settings${context.recoverable ? ", then try again" : ""}.` : context.source === "speaker" ? "Chalk can\u2019t use your speaker. Check your browser\u2019s audio permissions and system sound settings." : `Chalk can\u2019t use your ${device(context)}. Click the camera icon in the address bar and allow ${context.source === "camera" ? "Camera" : context.source === "microphone" ? "Microphone" : "Camera and Microphone"}.`
820
+ ),
821
+ "media.device_not_found": specific((context) => `No ${device(context)} found. Connect one${context.recoverable ? ", then try again" : ""}.`),
822
+ "media.device_busy": { ...specific((context) => `Your ${device(context)} is being used by another app. Close it there${context.recoverable ? ", then try again" : ""}.`), retry: true },
823
+ "media.device_constraint_invalid": specific((context) => `Your ${device(context)} doesn\u2019t support these settings. Choose another ${device(context)}${context.recoverable ? ", then try again" : ""}.`),
824
+ "media.capture_failed": { copy: { ...operationCopy, start: captureStart, request: captureStart, change: (context) => context.source === "screen" ? `${settings(context)}${again(context)}` : capture(context) }, retry: true },
825
+ "media.request_invalid": {
826
+ copy: {
827
+ ...operationCopy,
828
+ test: () => "The speaker test is no longer available.",
829
+ request: () => "The media request is no longer active.",
830
+ select: (context) => `Choose a valid ${device(context)} from the device list.`,
831
+ start: () => "This media action is no longer available.",
832
+ stop: () => "This media action is no longer available.",
833
+ change: () => "This media action is no longer available."
834
+ },
835
+ retry: false
836
+ },
837
+ "media.timeout": generic,
838
+ "offline.discarded": unavailable,
839
+ "participant.invalid": unavailable,
840
+ "reaction.invalid": unavailable,
841
+ "whiteboard.unavailable": unavailable
842
+ };
843
+ function commandFailurePresentation(failure, source, operation = "change", hasExactRetry = false) {
844
+ if (!Object.hasOwn(MEDIA_FAILURE_POLICY, failure.code)) return { message: "This media action couldn\u2019t be completed.", retry: false };
845
+ if (!source && operation === "change" && !failure.code.startsWith("media.")) return { message: failure.message, retry: false };
846
+ const rule = MEDIA_FAILURE_POLICY[failure.code];
847
+ const context = { source, recoverable: failure.recoverable };
848
+ const message = rule.copy[operation](context);
849
+ return { message, retry: failure.recoverable && rule.retry && hasExactRetry && source !== void 0 && source !== "speaker" && (operation === "start" || operation === "stop") };
850
+ }
851
+ function commandFailureMessage(failure, source, operation) {
852
+ return commandFailurePresentation(failure, source, operation).message;
731
853
  }
854
+
855
+ // src/components/media-request-dialog/media-request-dialog-state.ts
856
+ import { useEffect as useEffect5, useMemo as useMemo2, useState as useState8 } from "react";
732
857
  function formatExpiryLabel(expiresAtMs, now) {
733
858
  if (!Number.isFinite(expiresAtMs)) return null;
734
859
  const remainingSeconds = Math.max(0, Math.ceil((expiresAtMs - now) / 1e3));
@@ -739,9 +864,9 @@ function formatExpiryLabel(expiresAtMs, now) {
739
864
  }
740
865
  function useMediaRequestDialogState({ request, onDecline, onAllow, onActionError }) {
741
866
  const expiresAtMs = useMemo2(() => Date.parse(request.expiresAt), [request.expiresAt]);
742
- const [now, setNow] = useState7(() => Date.now());
743
- const [pendingAction, setPendingAction] = useState7(null);
744
- const [errorMessageState, setErrorMessage] = useState7(null);
867
+ const [now, setNow] = useState8(() => Date.now());
868
+ const [pendingAction, setPendingAction] = useState8(null);
869
+ const [errorMessageState, setErrorMessage] = useState8(null);
745
870
  useEffect5(() => {
746
871
  setNow(Date.now());
747
872
  setPendingAction(null);
@@ -760,7 +885,7 @@ function useMediaRequestDialogState({ request, onDecline, onAllow, onActionError
760
885
  setPendingAction(action);
761
886
  setErrorMessage(null);
762
887
  Promise.resolve().then(handler).then(() => onActionError?.(null, action)).catch((cause) => {
763
- const message = errorMessage(cause);
888
+ const message = commandFailureMessage(commandFailure(cause), request.kind === "start_camera" ? "camera" : "microphone", "request");
764
889
  setErrorMessage(message);
765
890
  onActionError?.(message, action);
766
891
  }).finally(() => setPendingAction(null));
@@ -771,7 +896,7 @@ function useMediaRequestDialogState({ request, onDecline, onAllow, onActionError
771
896
  // src/components/media-request-dialog/MediaRequestDialog.tsx
772
897
  import { jsx as jsx24, jsxs as jsxs11 } from "react/jsx-runtime";
773
898
  function MediaRequestDialog({ request, onDecline, onAllow, onActionError }) {
774
- const { isExpired, expiryLabel, pendingAction, errorMessage: errorMessage2, runAction } = useMediaRequestDialogState({ request, onDecline, onAllow, onActionError });
899
+ const { isExpired, expiryLabel, pendingAction, errorMessage, runAction } = useMediaRequestDialogState({ request, onDecline, onAllow, onActionError });
775
900
  const RequestIcon = request.kind === "unmute" ? Microphone01Icon : Video01Icon;
776
901
  return /* @__PURE__ */ jsxs11(
777
902
  "div",
@@ -794,7 +919,7 @@ function MediaRequestDialog({ request, onDecline, onAllow, onActionError }) {
794
919
  expiryLabel ? /* @__PURE__ */ jsx24("p", { className: "mt-1 text-xs text-[var(--chalk-app-text-muted)]", role: "status", children: expiryLabel }) : null
795
920
  ] })
796
921
  ] }),
797
- errorMessage2 ? /* @__PURE__ */ jsx24("p", { className: "mt-3 rounded-md border border-[var(--chalk-app-danger)]/35 bg-[var(--chalk-app-danger)]/10 px-3 py-2 text-sm text-[var(--chalk-app-danger)]", role: "alert", children: errorMessage2 }) : null,
922
+ errorMessage ? /* @__PURE__ */ jsx24("p", { className: "mt-3 rounded-md border border-[var(--chalk-app-danger)]/35 bg-[var(--chalk-app-danger)]/10 px-3 py-2 text-sm text-[var(--chalk-app-danger)]", role: "alert", children: errorMessage }) : null,
798
923
  /* @__PURE__ */ jsxs11("div", { className: "mt-4 flex justify-end gap-2", children: [
799
924
  /* @__PURE__ */ jsx24("button", { type: "button", className: "rounded-lg px-3 py-2 text-sm text-[var(--chalk-app-text-muted)] hover:bg-[var(--chalk-app-control-hover)] disabled:cursor-not-allowed disabled:opacity-55", onClick: () => runAction("decline"), disabled: isExpired || pendingAction !== null, children: "Not now" }),
800
925
  /* @__PURE__ */ jsx24(
@@ -814,11 +939,11 @@ function MediaRequestDialog({ request, onDecline, onAllow, onActionError }) {
814
939
  }
815
940
 
816
941
  // src/components/admission-panel/AdmissionPanel.tsx
817
- import React8, { useEffect as useEffect10, useState as useState14 } from "react";
942
+ import React8, { useEffect as useEffect10, useState as useState15 } from "react";
818
943
  import { IconButton, Spinner } from "@q9labsai/chalk-ui";
819
944
 
820
945
  // src/components/audio-output/AudioOutput.tsx
821
- import { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo4, useRef as useRef7, useState as useState9 } from "react";
946
+ import { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo4, useRef as useRef6, useState as useState10 } from "react";
822
947
 
823
948
  // src/selectors/space-selectors.ts
824
949
  function toVideoParticipants(participants, remoteMedia, localId, displayName, localMedia) {
@@ -879,11 +1004,11 @@ function toAudioParticipants(remoteMedia) {
879
1004
  }
880
1005
 
881
1006
  // src/components/participants-panel/participant-volume-context.tsx
882
- import { createContext as createContext2, useContext as useContext2, useMemo as useMemo3, useState as useState8 } from "react";
1007
+ import { createContext as createContext2, useContext as useContext2, useMemo as useMemo3, useState as useState9 } from "react";
883
1008
  import { jsx as jsx25 } from "react/jsx-runtime";
884
1009
  var ParticipantVolumeContext = createContext2(null);
885
1010
  function ParticipantVolumeProvider({ children }) {
886
- const [volumes, setVolumes] = useState8(/* @__PURE__ */ new Map());
1011
+ const [volumes, setVolumes] = useState9(/* @__PURE__ */ new Map());
887
1012
  const value = useMemo3(
888
1013
  () => ({
889
1014
  volumes,
@@ -924,11 +1049,11 @@ function AudioOutput({ participants, volume = 1, audioOutputDeviceId, getPartici
924
1049
  }, [participants, contextParticipants, participantState.roster]);
925
1050
  const effectiveAudioOutputDeviceId = audioOutputDeviceId ?? media.selection.speaker ?? void 0;
926
1051
  const effectiveGetParticipantVolume = getParticipantVolume ?? ((participantId) => (volumeContext?.volumes.get(participantId) ?? 100) / 100);
927
- const audioElementsRef = useRef7(/* @__PURE__ */ new Map());
928
- const screenShareAudioRef = useRef7(/* @__PURE__ */ new Map());
929
- const pendingAutoplayRetryRef = useRef7(/* @__PURE__ */ new Set());
930
- const [needsAutoplayUnlock, setNeedsAutoplayUnlock] = useState9(false);
931
- const resumeAudioRef = useRef7(null);
1052
+ const audioElementsRef = useRef6(/* @__PURE__ */ new Map());
1053
+ const screenShareAudioRef = useRef6(/* @__PURE__ */ new Map());
1054
+ const pendingAutoplayRetryRef = useRef6(/* @__PURE__ */ new Set());
1055
+ const [needsAutoplayUnlock, setNeedsAutoplayUnlock] = useState10(false);
1056
+ const resumeAudioRef = useRef6(null);
932
1057
  const markAutoplayBlocked = (el) => {
933
1058
  pendingAutoplayRetryRef.current.add(el);
934
1059
  setNeedsAutoplayUnlock(true);
@@ -1174,7 +1299,7 @@ function useHaptics({ enabled = true } = {}) {
1174
1299
 
1175
1300
  // src/components/participant-tile/ParticipantTile.tsx
1176
1301
  import { Facehash as Facehash2 } from "@q9labsai/facehash/react";
1177
- import React5, { useMemo as useMemo6, useRef as useRef9, useState as useState12 } from "react";
1302
+ import React5, { useMemo as useMemo6, useRef as useRef8, useState as useState13 } from "react";
1178
1303
 
1179
1304
  // src/internal/useMirrorSetting.ts
1180
1305
  import { useSyncExternalStore as useSyncExternalStore2 } from "react";
@@ -1215,33 +1340,33 @@ function setMirrored(next) {
1215
1340
 
1216
1341
  // src/components/stage/StageIcon.tsx
1217
1342
  import "react";
1218
- import { Fragment as Fragment5, jsx as jsx27, jsxs as jsxs12 } from "react/jsx-runtime";
1343
+ import { Fragment as Fragment6, jsx as jsx27, jsxs as jsxs12 } from "react/jsx-runtime";
1219
1344
  var paths = {
1220
- share: /* @__PURE__ */ jsxs12(Fragment5, { children: [
1345
+ share: /* @__PURE__ */ jsxs12(Fragment6, { children: [
1221
1346
  /* @__PURE__ */ jsx27("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }),
1222
1347
  /* @__PURE__ */ jsx27("path", { d: "M8 21h8M12 17v4M9 10l3-3 3 3M12 7v6" })
1223
1348
  ] }),
1224
- fill: /* @__PURE__ */ jsxs12(Fragment5, { children: [
1349
+ fill: /* @__PURE__ */ jsxs12(Fragment6, { children: [
1225
1350
  /* @__PURE__ */ jsx27("rect", { x: "3", y: "5", width: "18", height: "14", rx: "2" }),
1226
1351
  /* @__PURE__ */ jsx27("path", { d: "M7 12h10M7 12l2.5-2.5M7 12l2.5 2.5M17 12l-2.5-2.5M17 12l-2.5 2.5" })
1227
1352
  ] }),
1228
- fit: /* @__PURE__ */ jsxs12(Fragment5, { children: [
1353
+ fit: /* @__PURE__ */ jsxs12(Fragment6, { children: [
1229
1354
  /* @__PURE__ */ jsx27("rect", { x: "3", y: "5", width: "18", height: "14", rx: "2" }),
1230
1355
  /* @__PURE__ */ jsx27("rect", { x: "7.5", y: "9", width: "9", height: "6", rx: "1" })
1231
1356
  ] }),
1232
- "zoom-in": /* @__PURE__ */ jsxs12(Fragment5, { children: [
1357
+ "zoom-in": /* @__PURE__ */ jsxs12(Fragment6, { children: [
1233
1358
  /* @__PURE__ */ jsx27("circle", { cx: "11", cy: "11", r: "7" }),
1234
1359
  /* @__PURE__ */ jsx27("path", { d: "M21 21l-4.3-4.3M8 11h6M11 8v6" })
1235
1360
  ] }),
1236
- "zoom-out": /* @__PURE__ */ jsxs12(Fragment5, { children: [
1361
+ "zoom-out": /* @__PURE__ */ jsxs12(Fragment6, { children: [
1237
1362
  /* @__PURE__ */ jsx27("circle", { cx: "11", cy: "11", r: "7" }),
1238
1363
  /* @__PURE__ */ jsx27("path", { d: "M21 21l-4.3-4.3M8 11h6" })
1239
1364
  ] }),
1240
- pip: /* @__PURE__ */ jsxs12(Fragment5, { children: [
1365
+ pip: /* @__PURE__ */ jsxs12(Fragment6, { children: [
1241
1366
  /* @__PURE__ */ jsx27("rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }),
1242
1367
  /* @__PURE__ */ jsx27("rect", { x: "12", y: "12", width: "7", height: "5", rx: "1", fill: "currentColor" })
1243
1368
  ] }),
1244
- volume: /* @__PURE__ */ jsxs12(Fragment5, { children: [
1369
+ volume: /* @__PURE__ */ jsxs12(Fragment6, { children: [
1245
1370
  /* @__PURE__ */ jsx27("path", { d: "M11 5L6 9H2v6h4l5 4z" }),
1246
1371
  /* @__PURE__ */ jsx27("path", { d: "M15.5 8.5a5 5 0 0 1 0 7M19 5a10 10 0 0 1 0 14" })
1247
1372
  ] }),
@@ -1249,12 +1374,12 @@ var paths = {
1249
1374
  pause: /* @__PURE__ */ jsx27("path", { d: "M9 5v14M15 5v14", strokeWidth: "2.5" }),
1250
1375
  more: /* @__PURE__ */ jsx27("path", { d: "M5 12h.01M12 12h.01M19 12h.01", strokeWidth: "3" }),
1251
1376
  mute: /* @__PURE__ */ jsx27("path", { d: "M2 2l20 20M15 9.3V5a3 3 0 0 0-5.7-1.3M9 9v2a3 3 0 0 0 5.1 2.1M17 17a7 7 0 0 1-12-6v-1M19 10v1a7 7 0 0 1-.1 1.2M12 18v4" }),
1252
- hand: /* @__PURE__ */ jsxs12(Fragment5, { children: [
1377
+ hand: /* @__PURE__ */ jsxs12(Fragment6, { children: [
1253
1378
  /* @__PURE__ */ jsx27("path", { d: "M18 11V6a2 2 0 0 0-4 0v5M14 10V4a2 2 0 0 0-4 0v6M10 10.5V6a2 2 0 0 0-4 0v8" }),
1254
1379
  /* @__PURE__ */ jsx27("path", { d: "M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.9-6-2.3l-3.6-3.6a2 2 0 0 1 2.8-2.8L7 15" })
1255
1380
  ] }),
1256
1381
  pin: /* @__PURE__ */ jsx27("path", { d: "M12 17v5M9 10.8a2 2 0 0 1-1.1 1.8l-1.8.9A2 2 0 0 0 5 15.2V17h14v-1.8a2 2 0 0 0-1.1-1.8l-1.8-.9a2 2 0 0 1-1.1-1.8V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z" }),
1257
- pen: /* @__PURE__ */ jsxs12(Fragment5, { children: [
1382
+ pen: /* @__PURE__ */ jsxs12(Fragment6, { children: [
1258
1383
  /* @__PURE__ */ jsx27("path", { d: "M17 3a2.85 2.85 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5z" }),
1259
1384
  /* @__PURE__ */ jsx27("path", { d: "M15 5l4 4" })
1260
1385
  ] })
@@ -1264,7 +1389,7 @@ function StageIcon({ name, size = 16, ...props }) {
1264
1389
  }
1265
1390
 
1266
1391
  // src/components/atomic/Avatar.tsx
1267
- import React3, { useEffect as useEffect7, useMemo as useMemo5, useState as useState10 } from "react";
1392
+ import React3, { useEffect as useEffect7, useMemo as useMemo5, useState as useState11 } from "react";
1268
1393
  import { Facehash } from "@q9labsai/facehash/react";
1269
1394
  import { jsx as jsx28, jsxs as jsxs13 } from "react/jsx-runtime";
1270
1395
  var GeneratedFacehash = Facehash;
@@ -1283,7 +1408,7 @@ var statusColorMap = {
1283
1408
  offline: "var(--chalk-muted-text)"
1284
1409
  };
1285
1410
  var Avatar = React3.memo(({ name, src, size = "md", status, className, style, gradientPreference, generated = true, animated = true }) => {
1286
- const [imageError, setImageError] = useState10(false);
1411
+ const [imageError, setImageError] = useState11(false);
1287
1412
  const hasUploadedImage = Boolean(src) && !imageError;
1288
1413
  const shouldShowGeneratedAvatar = generated && Boolean(name) && !hasUploadedImage;
1289
1414
  useEffect7(() => {
@@ -1360,10 +1485,10 @@ function TileShell({ label, accentColor, pinned = false, ring = false, speaking
1360
1485
  }
1361
1486
 
1362
1487
  // src/components/participant-tile/useVideoTrack.ts
1363
- import { useEffect as useEffect8, useRef as useRef8, useState as useState11 } from "react";
1488
+ import { useEffect as useEffect8, useRef as useRef7, useState as useState12 } from "react";
1364
1489
  function useVideoTrack(videoRef, track, enabled) {
1365
- const [status, setStatus] = useState11("idle");
1366
- const loadedRef = useRef8(false);
1490
+ const [status, setStatus] = useState12("idle");
1491
+ const loadedRef = useRef7(false);
1367
1492
  useEffect8(() => {
1368
1493
  const video = videoRef.current;
1369
1494
  loadedRef.current = false;
@@ -1403,7 +1528,7 @@ function useVideoTrack(videoRef, track, enabled) {
1403
1528
  }
1404
1529
 
1405
1530
  // src/components/participant-tile/ParticipantTile.tsx
1406
- import { Fragment as Fragment6, jsx as jsx30, jsxs as jsxs15 } from "react/jsx-runtime";
1531
+ import { Fragment as Fragment7, jsx as jsx30, jsxs as jsxs15 } from "react/jsx-runtime";
1407
1532
  var aspectRatioClasses = {
1408
1533
  "16:9": "aspect-video",
1409
1534
  "4:3": "aspect-[4/3]",
@@ -1411,7 +1536,7 @@ var aspectRatioClasses = {
1411
1536
  fill: ""
1412
1537
  };
1413
1538
  var ParticipantTile = React5.memo(function ParticipantTile2({ videoTrack, ...props }) {
1414
- const videoRef = useRef9(null);
1539
+ const videoRef = useRef8(null);
1415
1540
  const status = useVideoTrack(videoRef, videoTrack, props.participant.isVideoEnabled === true);
1416
1541
  return /* @__PURE__ */ jsx30(ParticipantTileSurface, { ...props, media: /* @__PURE__ */ jsx30("video", { ref: videoRef, autoPlay: true, playsInline: true, muted: true, className: "h-full w-full object-cover" }), mediaVisible: status === "playing" });
1417
1542
  });
@@ -1440,8 +1565,8 @@ var ParticipantTileSurface = React5.memo(function ParticipantTileSurface2({
1440
1565
  }) {
1441
1566
  const prefersReducedMotion = usePrefersReducedMotion();
1442
1567
  const phone = useMediaQuery("(max-width:640px)");
1443
- const [optionsOpen, setOptionsOpen] = useState12(false);
1444
- const returnFocus = useRef9(null);
1568
+ const [optionsOpen, setOptionsOpen] = useState13(false);
1569
+ const returnFocus = useRef8(null);
1445
1570
  const openOptions = () => {
1446
1571
  returnFocus.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
1447
1572
  setOptionsOpen(true);
@@ -1473,9 +1598,9 @@ var ParticipantTileSurface = React5.memo(function ParticipantTileSurface2({
1473
1598
  style,
1474
1599
  dataTour: participant.isLocal ? "local-video" : "video-grid",
1475
1600
  hidden,
1476
- corner: /* @__PURE__ */ jsxs15(Fragment6, { children: [
1477
- showStatus && hasPoorConnection ? /* @__PURE__ */ jsx30("span", { role: "img", "aria-label": `${participant.displayName} has a weak connection`, className: "pointer-events-none absolute top-2 left-2 z-30 flex h-6 items-center rounded-[6px] bg-[rgba(12,14,18,.75)] px-[7px]", children: /* @__PURE__ */ jsx30(SignalBars, {}) }) : null,
1478
- onClick && !phone ? /* @__PURE__ */ jsx30("span", { className: "absolute top-2 right-2 z-30 opacity-0 transition-opacity duration-150 group-hover/tile:opacity-100 focus-within:opacity-100", children: /* @__PURE__ */ jsx30(
1601
+ corner: /* @__PURE__ */ jsxs15(Fragment7, { children: [
1602
+ showStatus && hasPoorConnection ? /* @__PURE__ */ jsx30("span", { role: "img", "data-part": "tile-signal", "aria-label": `${participant.displayName} has a weak connection`, className: "pointer-events-none absolute top-2 left-2 z-30 flex h-6 items-center rounded-[6px] bg-[rgba(12,14,18,.75)] px-[7px]", children: /* @__PURE__ */ jsx30(SignalBars, {}) }) : null,
1603
+ onClick && !phone ? /* @__PURE__ */ jsx30("span", { "data-part": "tile-menu", className: "absolute top-2 right-2 z-30 opacity-0 transition-opacity duration-150 group-hover/tile:opacity-100 focus-within:opacity-100", children: /* @__PURE__ */ jsx30(
1479
1604
  ChalkMenu,
1480
1605
  {
1481
1606
  trigger: /* @__PURE__ */ jsx30("button", { type: "button", "aria-label": `More options for ${participant.displayName}`, className: "grid h-8 w-8 place-items-center rounded-full bg-[rgba(12,14,18,.75)] text-white outline-none hover:bg-[rgba(40,42,48,.95)] focus-visible:ring-2 focus-visible:ring-[var(--chalk-focus)]", children: /* @__PURE__ */ jsx30(StageIcon, { name: "more", size: 18 }) }),
@@ -1483,13 +1608,16 @@ var ParticipantTileSurface = React5.memo(function ParticipantTileSurface2({
1483
1608
  }
1484
1609
  ) }) : null
1485
1610
  ] }),
1486
- chip: showName || showStatus ? /* @__PURE__ */ jsxs15(Fragment6, { children: [
1611
+ chip: showName || showStatus ? /* @__PURE__ */ jsxs15(Fragment7, { children: [
1487
1612
  pinned && /* @__PURE__ */ jsx30(StageIcon, { name: "pin", size: 14, "aria-label": "Pinned" }),
1488
- showName && /* @__PURE__ */ jsxs15("span", { dir: "auto", className: "min-w-0 truncate [unicode-bidi:plaintext]", title: participant.displayName, children: [
1489
- participant.displayName,
1490
- participant.isLocal && participant.displayName !== "You" && /* @__PURE__ */ jsx30("span", { className: "text-white", children: " (You)" })
1613
+ showName && /* @__PURE__ */ jsxs15(Fragment7, { children: [
1614
+ /* @__PURE__ */ jsxs15("span", { dir: "auto", "data-part": "tile-full-name", className: "min-w-0 truncate [unicode-bidi:plaintext]", title: participant.displayName, children: [
1615
+ participant.displayName,
1616
+ participant.isLocal && participant.displayName !== "You" && /* @__PURE__ */ jsx30("span", { className: "text-white", children: " (You)" })
1617
+ ] }),
1618
+ /* @__PURE__ */ jsx30("span", { dir: "auto", "data-part": "tile-short-name", className: "hidden min-w-0 truncate [unicode-bidi:plaintext]", children: participant.isLocal ? "You" : participant.displayName.split(" ")[0] })
1491
1619
  ] }),
1492
- following ? /* @__PURE__ */ jsx30("span", { className: "shrink-0 font-semibold opacity-80", children: "following" }) : null,
1620
+ following ? /* @__PURE__ */ jsx30("span", { "data-part": "tile-following", className: "shrink-0 font-semibold opacity-80", children: "following" }) : null,
1493
1621
  showStatus && participant.isMuted && /* @__PURE__ */ jsx30(StageIcon, { name: "mute", size: 14, className: "shrink-0 text-[#ff6b6b]", "aria-label": "Muted" }),
1494
1622
  showStatus && participant.isHandRaised && /* @__PURE__ */ jsxs15("span", { className: "flex shrink-0 items-center text-[#e2bd3a]", role: "img", "aria-label": `Hand raised${participant.handRaiseOrder ? `, number ${participant.handRaiseOrder}` : ""}`, children: [
1495
1623
  /* @__PURE__ */ jsx30(StageIcon, { name: "hand", size: 14 }),
@@ -1560,7 +1688,7 @@ function SignalBars() {
1560
1688
  }
1561
1689
 
1562
1690
  // src/components/atomic/ReactionBubble.tsx
1563
- import React6, { useEffect as useEffect9, useState as useState13, useMemo as useMemo7 } from "react";
1691
+ import React6, { useEffect as useEffect9, useState as useState14, useMemo as useMemo7 } from "react";
1564
1692
  import { CELEBRATION_EMOJIS } from "@q9labsai/chalk-ui/reactions";
1565
1693
  import { jsx as jsx31, jsxs as jsxs16 } from "react/jsx-runtime";
1566
1694
  var BASE_PARTICLE_COLORS = ["#14b8a6", "#f59e0b", "#ec4899", "#8b5cf6", "#06b6d4", "#22c55e"];
@@ -1604,7 +1732,7 @@ function getParticleStyle(particle) {
1604
1732
  };
1605
1733
  }
1606
1734
  var ReactionBubble = React6.memo(({ emoji, participantName, onComplete, duration: baseDuration = 3e3, animated = true, className }) => {
1607
- const [isVisible, setIsVisible] = useState13(true);
1735
+ const [isVisible, setIsVisible] = useState14(true);
1608
1736
  const prefersReducedMotion = usePrefersReducedMotion();
1609
1737
  const participantColors = useMemo7(() => getParticipantColor(participantName || "unknown"), [participantName]);
1610
1738
  const isCelebration = CELEBRATION_EMOJIS.includes(emoji);
@@ -1681,7 +1809,7 @@ var VolumeSlider = React7.memo(({ value, onChange, muted = false, onMuteToggle,
1681
1809
  VolumeSlider.displayName = "VolumeSlider";
1682
1810
 
1683
1811
  // src/components/admission-panel/AdmissionPanel.tsx
1684
- import { Fragment as Fragment7, jsx as jsx33, jsxs as jsxs18 } from "react/jsx-runtime";
1812
+ import { Fragment as Fragment8, jsx as jsx33, jsxs as jsxs18 } from "react/jsx-runtime";
1685
1813
  var TEXT_BUTTON_CLASS = "inline-flex h-7 items-center rounded-[6px] px-2 text-xs font-semibold text-[var(--chalk-app-text-muted)] transition-colors hover:bg-[var(--chalk-app-control-hover)] hover:text-[var(--chalk-app-text)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--chalk-app-control-active-line)]";
1686
1814
  var ADMIT_BUTTON_CLASS = "inline-flex h-8 items-center rounded-[7px] border border-transparent bg-[var(--chalk-app-control-active)] px-3 text-[13px] font-semibold text-[var(--chalk-app-control-active-text)] transition-colors hover:border-[var(--chalk-app-control-active-line)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--chalk-app-control-active-line)]";
1687
1815
  var DENY_BUTTON_CLASS = "inline-flex h-8 items-center rounded-[7px] px-2.5 text-[13px] font-semibold text-[var(--chalk-app-text-muted)] transition-colors hover:bg-[var(--chalk-app-control-hover)] hover:text-[var(--chalk-app-danger)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--chalk-app-control-active-line)]";
@@ -1692,13 +1820,13 @@ function getWaitingLabel(joinedAt) {
1692
1820
  return `Waiting ${minutes} min`;
1693
1821
  }
1694
1822
  var AdmissionPanelSurface = React8.memo(({ participants, onAdmit, onDeny, onAdmitAll, onDenyAll, loading = false, className, onClose, inline = false }) => {
1695
- const [, setTick] = useState14(0);
1823
+ const [, setTick] = useState15(0);
1696
1824
  useEffect10(() => {
1697
1825
  const timer = window.setInterval(() => setTick((t) => t + 1), 6e4);
1698
1826
  return () => window.clearInterval(timer);
1699
1827
  }, []);
1700
- const [busy, setBusy] = useState14(false);
1701
- const [error, setError] = useState14(null);
1828
+ const [busy, setBusy] = useState15(false);
1829
+ const [error, setError] = useState15(null);
1702
1830
  const perform = async (action) => {
1703
1831
  if (busy) return;
1704
1832
  setBusy(true);
@@ -1712,7 +1840,7 @@ var AdmissionPanelSurface = React8.memo(({ participants, onAdmit, onDeny, onAdmi
1712
1840
  }
1713
1841
  };
1714
1842
  const pending = participants.length;
1715
- const list = /* @__PURE__ */ jsxs18(Fragment7, { children: [
1843
+ const list = /* @__PURE__ */ jsxs18(Fragment8, { children: [
1716
1844
  error ? /* @__PURE__ */ jsx33("p", { role: "alert", className: "text-sm text-[var(--chalk-danger-text)]", children: error }) : null,
1717
1845
  pending > 0 ? /* @__PURE__ */ jsxs18("div", { className: "flex h-9 items-center justify-between gap-2", children: [
1718
1846
  /* @__PURE__ */ jsx33("span", { className: "text-xs font-semibold uppercase tracking-[0.08em] text-[var(--chalk-app-text-muted)]", "aria-live": "polite", children: pending === 1 ? "1 waiting" : `${pending} waiting` }),
@@ -1771,7 +1899,7 @@ AdmissionPanel.displayName = "AdmissionPanel";
1771
1899
 
1772
1900
  // src/components/composite/ChatPanel.tsx
1773
1901
  import { CHALK_CHAT_ATTACHMENT_LIMITS, CHALK_CHAT_ATTACHMENT_MIME_TYPES } from "@q9labsai/chalk-client";
1774
- import React10, { useEffect as useEffect13, useLayoutEffect, useMemo as useMemo8, useRef as useRef12, useState as useState17 } from "react";
1902
+ import React10, { useEffect as useEffect13, useLayoutEffect, useMemo as useMemo8, useRef as useRef11, useState as useState18 } from "react";
1775
1903
  import { Button } from "@q9labsai/chalk-ui";
1776
1904
 
1777
1905
  // src/components/call-card/CallCardStyles.tsx
@@ -2634,21 +2762,21 @@ function CallCardStyles() {
2634
2762
 
2635
2763
  // src/components/composite/MessageBubble.tsx
2636
2764
  import { CHALK_REACTIONS } from "@q9labsai/chalk-client";
2637
- import React9, { useEffect as useEffect12, useRef as useRef11, useState as useState16 } from "react";
2765
+ import React9, { useEffect as useEffect12, useRef as useRef10, useState as useState17 } from "react";
2638
2766
 
2639
2767
  // src/components/composite/use-chat-attachment-previews.ts
2640
- import { useCallback as useCallback4, useEffect as useEffect11, useRef as useRef10, useState as useState15 } from "react";
2768
+ import { useCallback as useCallback4, useEffect as useEffect11, useRef as useRef9, useState as useState16 } from "react";
2641
2769
  function useChatAttachmentPreviews(attachments, resolveUrl) {
2642
- const [resolvedUrls, setResolvedUrls] = useState15(() => /* @__PURE__ */ new Map());
2643
- const [failedAttachmentIds, setFailedAttachmentIds] = useState15(() => /* @__PURE__ */ new Set());
2644
- const [retryGeneration, setRetryGeneration] = useState15(0);
2645
- const resolvingAttachmentIds = useRef10(/* @__PURE__ */ new Set());
2646
- const resolvedUrlsRef = useRef10(resolvedUrls);
2647
- const failedAttachmentIdsRef = useRef10(failedAttachmentIds);
2770
+ const [resolvedUrls, setResolvedUrls] = useState16(() => /* @__PURE__ */ new Map());
2771
+ const [failedAttachmentIds, setFailedAttachmentIds] = useState16(() => /* @__PURE__ */ new Set());
2772
+ const [retryGeneration, setRetryGeneration] = useState16(0);
2773
+ const resolvingAttachmentIds = useRef9(/* @__PURE__ */ new Set());
2774
+ const resolvedUrlsRef = useRef9(resolvedUrls);
2775
+ const failedAttachmentIdsRef = useRef9(failedAttachmentIds);
2648
2776
  const currentAttachmentIds = new Set(attachments.map((attachment) => attachment.attachmentId));
2649
2777
  const currentImageIds = new Set(attachments.filter((attachment) => attachment.mimeType.startsWith("image/")).map((attachment) => attachment.attachmentId));
2650
- const currentAttachmentIdsRef = useRef10(currentAttachmentIds);
2651
- const currentImageIdsRef = useRef10(currentImageIds);
2778
+ const currentAttachmentIdsRef = useRef9(currentAttachmentIds);
2779
+ const currentImageIdsRef = useRef9(currentImageIds);
2652
2780
  resolvedUrlsRef.current = resolvedUrls;
2653
2781
  failedAttachmentIdsRef.current = failedAttachmentIds;
2654
2782
  currentAttachmentIdsRef.current = currentAttachmentIds;
@@ -2697,7 +2825,7 @@ function useChatAttachmentPreviews(attachments, resolveUrl) {
2697
2825
  }
2698
2826
 
2699
2827
  // src/components/composite/MessageBubble.tsx
2700
- import { Fragment as Fragment8, jsx as jsx35, jsxs as jsxs19 } from "react/jsx-runtime";
2828
+ import { Fragment as Fragment9, jsx as jsx35, jsxs as jsxs19 } from "react/jsx-runtime";
2701
2829
  var MessageBubble = React9.memo(
2702
2830
  ({
2703
2831
  content,
@@ -2724,11 +2852,11 @@ var MessageBubble = React9.memo(
2724
2852
  className
2725
2853
  }) => {
2726
2854
  const { resolvedUrls, failedAttachmentIds, markFailed, retry } = useChatAttachmentPreviews(attachments, onResolveAttachmentUrl);
2727
- const [reactionError, setReactionError] = useState16(null);
2728
- const [reacting, setReacting] = useState16(false);
2729
- const [reactionBarOpen, setReactionBarOpen] = useState16(false);
2730
- const messageRef = useRef11(null);
2731
- const press = useRef11(null);
2855
+ const [reactionError, setReactionError] = useState17(null);
2856
+ const [reacting, setReacting] = useState17(false);
2857
+ const [reactionBarOpen, setReactionBarOpen] = useState17(false);
2858
+ const messageRef = useRef10(null);
2859
+ const press = useRef10(null);
2732
2860
  const cancelPress = () => {
2733
2861
  if (press.current) clearTimeout(press.current.timer);
2734
2862
  press.current = null;
@@ -2743,10 +2871,10 @@ var MessageBubble = React9.memo(
2743
2871
  document.addEventListener("pointerdown", dismiss);
2744
2872
  return () => document.removeEventListener("pointerdown", dismiss);
2745
2873
  }, [reactionBarOpen]);
2746
- const [photo, setPhoto] = useState16(null);
2747
- const [downloading, setDownloading] = useState16(false);
2748
- const [downloadError, setDownloadError] = useState16(null);
2749
- const downloadController = useRef11(null);
2874
+ const [photo, setPhoto] = useState17(null);
2875
+ const [downloading, setDownloading] = useState17(false);
2876
+ const [downloadError, setDownloadError] = useState17(null);
2877
+ const downloadController = useRef10(null);
2750
2878
  useEffect12(() => () => downloadController.current?.abort(), []);
2751
2879
  const downloadPhoto = async () => {
2752
2880
  if (!photo || downloading) return;
@@ -2777,8 +2905,8 @@ var MessageBubble = React9.memo(
2777
2905
  downloadController.current = null;
2778
2906
  }
2779
2907
  };
2780
- const [photoClosing, setPhotoClosing] = useState16(false);
2781
- const closeTimer = useRef11(null);
2908
+ const [photoClosing, setPhotoClosing] = useState17(false);
2909
+ const closeTimer = useRef10(null);
2782
2910
  useEffect12(
2783
2911
  () => () => {
2784
2912
  if (closeTimer.current) clearTimeout(closeTimer.current);
@@ -2801,7 +2929,7 @@ var MessageBubble = React9.memo(
2801
2929
  const changePhoto = (step) => {
2802
2930
  if (photos.length > 1) setPhoto(photos[(photoIndex + step + photos.length) % photos.length]);
2803
2931
  };
2804
- const viewer = useRef11(null);
2932
+ const viewer = useRef10(null);
2805
2933
  const react = async (emoji) => {
2806
2934
  if (!onReact || reacting) return;
2807
2935
  setReactionBarOpen(false);
@@ -2996,7 +3124,7 @@ var MessageBubble = React9.memo(
2996
3124
  },
2997
3125
  children: [
2998
3126
  photo ? /* @__PURE__ */ jsx35("img", { src: resolvedUrls.get(photo.attachmentId), alt: photo.fileName }) : null,
2999
- photos.length > 1 ? /* @__PURE__ */ jsxs19(Fragment8, { children: [
3127
+ photos.length > 1 ? /* @__PURE__ */ jsxs19(Fragment9, { children: [
3000
3128
  /* @__PURE__ */ jsx35("button", { type: "button", className: "call-photo-viewer-control call-photo-previous", "aria-label": "Previous image", onClick: () => changePhoto(-1), children: /* @__PURE__ */ jsx35(ArrowLeft01Icon, {}) }),
3001
3129
  /* @__PURE__ */ jsx35("button", { type: "button", className: "call-photo-viewer-control call-photo-next", "aria-label": "Next image", onClick: () => changePhoto(1), children: /* @__PURE__ */ jsx35(ArrowRight01Icon, {}) })
3002
3130
  ] }) : null
@@ -3099,7 +3227,7 @@ function describeChatUploadFile(file) {
3099
3227
  }
3100
3228
 
3101
3229
  // src/components/composite/ChatPanel.tsx
3102
- import { Fragment as Fragment9, jsx as jsx36, jsxs as jsxs20 } from "react/jsx-runtime";
3230
+ import { Fragment as Fragment10, jsx as jsx36, jsxs as jsxs20 } from "react/jsx-runtime";
3103
3231
  var ChatPanelPresentation = React10.memo((props) => {
3104
3232
  return /* @__PURE__ */ jsx36(ChatPanelSurface, { ...props });
3105
3233
  });
@@ -3133,12 +3261,12 @@ var ChatPanelSurface = React10.memo(
3133
3261
  autoScrollBehavior,
3134
3262
  className
3135
3263
  }) => {
3136
- const [draft, setDraft] = useState17("");
3137
- const [stagedFiles, setStagedFiles] = useState17([]);
3138
- const uploads = useRef12(/* @__PURE__ */ new Map());
3139
- const [failedUploads, setFailedUploads] = useState17(/* @__PURE__ */ new Set());
3140
- const [uploading, setUploading] = useState17(/* @__PURE__ */ new Set());
3141
- const [previews, setPreviews] = useState17(/* @__PURE__ */ new Map());
3264
+ const [draft, setDraft] = useState18("");
3265
+ const [stagedFiles, setStagedFiles] = useState18([]);
3266
+ const uploads = useRef11(/* @__PURE__ */ new Map());
3267
+ const [failedUploads, setFailedUploads] = useState18(/* @__PURE__ */ new Set());
3268
+ const [uploading, setUploading] = useState18(/* @__PURE__ */ new Set());
3269
+ const [previews, setPreviews] = useState18(/* @__PURE__ */ new Map());
3142
3270
  useEffect13(() => {
3143
3271
  const urls = new Map(stagedFiles.flatMap((file) => file instanceof File && file.type.startsWith("image/") ? [[file, URL.createObjectURL(file)]] : []));
3144
3272
  setPreviews(urls);
@@ -3146,15 +3274,15 @@ var ChatPanelSurface = React10.memo(
3146
3274
  for (const url of urls.values()) URL.revokeObjectURL(url);
3147
3275
  };
3148
3276
  }, [stagedFiles]);
3149
- const [sending, setSending] = useState17(false);
3150
- const [pickingFiles, setPickingFiles] = useState17(false);
3151
- const [composerError, setComposerError] = useState17(null);
3152
- const scrollRef = useRef12(null);
3153
- const textareaRef = useRef12(null);
3154
- const fileInputRef = useRef12(null);
3155
- const isAtBottomRef = useRef12(true);
3156
- const lastMarkedSequenceRef = useRef12(localReadThroughSequence);
3157
- const restoreScrollRef = useRef12(null);
3277
+ const [sending, setSending] = useState18(false);
3278
+ const [pickingFiles, setPickingFiles] = useState18(false);
3279
+ const [composerError, setComposerError] = useState18(null);
3280
+ const scrollRef = useRef11(null);
3281
+ const textareaRef = useRef11(null);
3282
+ const fileInputRef = useRef11(null);
3283
+ const isAtBottomRef = useRef11(true);
3284
+ const lastMarkedSequenceRef = useRef11(localReadThroughSequence);
3285
+ const restoreScrollRef = useRef11(null);
3158
3286
  const grouped = useMemo8(() => groupChatMessages(messages), [messages]);
3159
3287
  const latestSequence = messages.at(-1)?.sequence;
3160
3288
  useLayoutEffect(() => {
@@ -3398,7 +3526,7 @@ var ChatPanelSurface = React10.memo(
3398
3526
  /* @__PURE__ */ jsx36("button", { type: "button", disabled: sending || pickingFiles, "aria-label": `Remove ${describeChatUploadFile(file).fileName}`, onClick: () => removeFile(file), children: /* @__PURE__ */ jsx36(Cancel01Icon, { className: "h-3.5 w-3.5" }) })
3399
3527
  ] }, `${describeChatUploadFile(file).fileName}-${describeChatUploadFile(file).byteLength}-${index}`)) }) : null,
3400
3528
  /* @__PURE__ */ jsx36("div", { className: "call-composer", children: /* @__PURE__ */ jsxs20("div", { className: "call-composer-field", children: [
3401
- onUploadAttachment ? /* @__PURE__ */ jsxs20(Fragment9, { children: [
3529
+ onUploadAttachment ? /* @__PURE__ */ jsxs20(Fragment10, { children: [
3402
3530
  !pickChatFiles ? /* @__PURE__ */ jsx36("input", { ref: fileInputRef, type: "file", multiple: true, className: "sr-only", "aria-label": "Choose attachments", accept: CHALK_CHAT_ATTACHMENT_MIME_TYPES.join(","), onChange: (event) => selectFiles(event.target.files) }) : null,
3403
3531
  /* @__PURE__ */ jsx36(Button, { type: "button", variant: "ghost", size: "icon", className: "call-attach-button", disabled: disabled || sending || pickingFiles || stagedFiles.length >= CHALK_CHAT_ATTACHMENT_LIMITS.maximumPerMessage, onClick: () => void chooseFiles(), "aria-label": "Attach files", children: /* @__PURE__ */ jsx36("svg", { className: "size-5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx36("path", { d: "M20.5 11.2l-8.4 8.4a5 5 0 0 1-7.1-7.1l8.4-8.4a3.3 3.3 0 0 1 4.7 4.7l-8.4 8.4a1.7 1.7 0 0 1-2.4-2.4l7.7-7.7" }) }) })
3404
3532
  ] }) : null,
@@ -3464,7 +3592,7 @@ var ChatPanel = React10.memo((props) => {
3464
3592
  ChatPanel.displayName = "ChatPanel";
3465
3593
 
3466
3594
  // src/components/composite/ReactionsOverlay.tsx
3467
- import { useEffect as useEffect14, useRef as useRef13, useState as useState18 } from "react";
3595
+ import { useEffect as useEffect14, useRef as useRef12, useState as useState19 } from "react";
3468
3596
  import { createPortal } from "react-dom";
3469
3597
 
3470
3598
  // src/components/composite/reaction-float.ts
@@ -3495,7 +3623,7 @@ function reactionPresentationStyle(eventId, progress) {
3495
3623
  }
3496
3624
 
3497
3625
  // src/components/composite/ReactionsOverlay.tsx
3498
- import { Fragment as Fragment10, jsx as jsx37, jsxs as jsxs21 } from "react/jsx-runtime";
3626
+ import { Fragment as Fragment11, jsx as jsx37, jsxs as jsxs21 } from "react/jsx-runtime";
3499
3627
  function ReactionsOverlayPresentation({ reactions, elapsedMs, maxVisible = 6, className }) {
3500
3628
  return /* @__PURE__ */ jsx37("div", { className: cn("pointer-events-none absolute inset-0 z-30 overflow-hidden", className), "aria-live": "off", children: reactions.slice(-maxVisible).map((reaction) => {
3501
3629
  const duration = Math.max(1, reaction.expiresAtMs - reaction.occurredAtMs);
@@ -3504,12 +3632,12 @@ function ReactionsOverlayPresentation({ reactions, elapsedMs, maxVisible = 6, cl
3504
3632
  }) });
3505
3633
  }
3506
3634
  function ReactionsOverlaySurface({ reactions, maxVisible = 6, className }) {
3507
- const root = useRef13(null);
3635
+ const root = useRef12(null);
3508
3636
  return /* @__PURE__ */ jsx37("div", { ref: root, className: cn("pointer-events-none absolute inset-0 z-30", className), "aria-live": "polite", "aria-atomic": "false", children: reactions.slice(-maxVisible).map((reaction) => /* @__PURE__ */ jsx37(TileReaction, { reaction, root }, reaction.eventId)) });
3509
3637
  }
3510
3638
  function TileReaction({ reaction, root }) {
3511
- const [host, setHost] = useState18(null);
3512
- const emoji = useRef13(null);
3639
+ const [host, setHost] = useState19(null);
3640
+ const emoji = useRef12(null);
3513
3641
  const reduced = usePrefersReducedMotion();
3514
3642
  useEffect14(() => {
3515
3643
  const frame = root.current?.closest("[data-part=call-frame]");
@@ -3529,7 +3657,7 @@ function TileReaction({ reaction, root }) {
3529
3657
  );
3530
3658
  return () => animation.cancel();
3531
3659
  }, [host, reduced]);
3532
- return /* @__PURE__ */ jsxs21(Fragment10, { children: [
3660
+ return /* @__PURE__ */ jsxs21(Fragment11, { children: [
3533
3661
  /* @__PURE__ */ jsxs21("span", { className: "sr-only", children: [
3534
3662
  reaction.displayName,
3535
3663
  " reacted ",
@@ -3554,68 +3682,68 @@ function CommandErrorAlert({ message }) {
3554
3682
  }
3555
3683
 
3556
3684
  // src/components/control-bar/ControlBar.tsx
3557
- import React12, { useEffect as useEffect17, useRef as useRef15, useState as useState23 } from "react";
3685
+ import React12, { useEffect as useEffect17, useRef as useRef14, useState as useState24 } from "react";
3558
3686
 
3559
3687
  // src/components/device-popover/DevicePopover.tsx
3560
3688
  import { Menu as Menu2 } from "@base-ui/react/menu";
3561
- import { useEffect as useEffect15, useRef as useRef14, useState as useState19 } from "react";
3689
+ import { useEffect as useEffect15, useRef as useRef13, useState as useState20 } from "react";
3562
3690
 
3563
3691
  // src/components/control-bar/ShellIcon.tsx
3564
- import { Fragment as Fragment11, jsx as jsx39, jsxs as jsxs22 } from "react/jsx-runtime";
3692
+ import { Fragment as Fragment12, jsx as jsx39, jsxs as jsxs22 } from "react/jsx-runtime";
3565
3693
  var GLYPHS = {
3566
- flip: /* @__PURE__ */ jsxs22(Fragment11, { children: [
3694
+ flip: /* @__PURE__ */ jsxs22(Fragment12, { children: [
3567
3695
  /* @__PURE__ */ jsx39("path", { d: "M3 8h3l2-3h8l2 3h3v12H3z" }),
3568
3696
  /* @__PURE__ */ jsx39("path", { d: "M8 14a4 4 0 0 1 7-2l1 1M16 10v3h-3M16 15a4 4 0 0 1-7 2l-1-1M8 19v-3h3" })
3569
3697
  ] }),
3570
- mic: /* @__PURE__ */ jsxs22(Fragment11, { children: [
3698
+ mic: /* @__PURE__ */ jsxs22(Fragment12, { children: [
3571
3699
  /* @__PURE__ */ jsx39("rect", { x: "9", y: "2", width: "6", height: "12", rx: "3" }),
3572
3700
  /* @__PURE__ */ jsx39("path", { d: "M19 10v1a7 7 0 0 1-14 0v-1M12 18v4" })
3573
3701
  ] }),
3574
- micOff: /* @__PURE__ */ jsx39(Fragment11, { children: /* @__PURE__ */ jsx39("path", { d: "M2 2l20 20M15 9.3V5a3 3 0 0 0-5.7-1.3M9 9v2a3 3 0 0 0 5.1 2.1M17 17a7 7 0 0 1-12-6v-1M19 10v1a7 7 0 0 1-.1 1.2M12 18v4" }) }),
3575
- cam: /* @__PURE__ */ jsxs22(Fragment11, { children: [
3702
+ micOff: /* @__PURE__ */ jsx39(Fragment12, { children: /* @__PURE__ */ jsx39("path", { d: "M2 2l20 20M15 9.3V5a3 3 0 0 0-5.7-1.3M9 9v2a3 3 0 0 0 5.1 2.1M17 17a7 7 0 0 1-12-6v-1M19 10v1a7 7 0 0 1-.1 1.2M12 18v4" }) }),
3703
+ cam: /* @__PURE__ */ jsxs22(Fragment12, { children: [
3576
3704
  /* @__PURE__ */ jsx39("path", { d: "M16 10.5l5-3v9l-5-3z" }),
3577
3705
  /* @__PURE__ */ jsx39("rect", { x: "2", y: "6", width: "14", height: "12", rx: "2" })
3578
3706
  ] }),
3579
- camOff: /* @__PURE__ */ jsx39(Fragment11, { children: /* @__PURE__ */ jsx39("path", { d: "M2 2l20 20M10.7 6H14a2 2 0 0 1 2 2v3.3l1 1 4-2.8v8M16 16a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2" }) }),
3580
- hang: /* @__PURE__ */ jsx39(Fragment11, { children: /* @__PURE__ */ jsx39("path", { transform: "rotate(135 12 12)", d: "M22 16.9v3a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3.1 19.5 19.5 0 0 1-6-6A19.8 19.8 0 0 1 2.1 4.2 2 2 0 0 1 4.1 2h3a2 2 0 0 1 2 1.7c.1 1 .4 1.9.7 2.8a2 2 0 0 1-.5 2.1L8.1 9.9a16 16 0 0 0 6 6l1.3-1.3a2 2 0 0 1 2.1-.4c.9.3 1.8.6 2.8.7a2 2 0 0 1 1.7 2z" }) }),
3581
- down: /* @__PURE__ */ jsx39(Fragment11, { children: /* @__PURE__ */ jsx39("path", { d: "M6 9l6 6 6-6" }) }),
3582
- up2: /* @__PURE__ */ jsx39(Fragment11, { children: /* @__PURE__ */ jsx39("path", { d: "M18 15l-6-6-6 6" }) }),
3583
- right: /* @__PURE__ */ jsx39(Fragment11, { children: /* @__PURE__ */ jsx39("path", { d: "M9 18l6-6-6-6" }) }),
3584
- grid: /* @__PURE__ */ jsxs22(Fragment11, { children: [
3707
+ camOff: /* @__PURE__ */ jsx39(Fragment12, { children: /* @__PURE__ */ jsx39("path", { d: "M2 2l20 20M10.7 6H14a2 2 0 0 1 2 2v3.3l1 1 4-2.8v8M16 16a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2" }) }),
3708
+ hang: /* @__PURE__ */ jsx39(Fragment12, { children: /* @__PURE__ */ jsx39("path", { transform: "rotate(135 12 12)", d: "M22 16.9v3a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3.1 19.5 19.5 0 0 1-6-6A19.8 19.8 0 0 1 2.1 4.2 2 2 0 0 1 4.1 2h3a2 2 0 0 1 2 1.7c.1 1 .4 1.9.7 2.8a2 2 0 0 1-.5 2.1L8.1 9.9a16 16 0 0 0 6 6l1.3-1.3a2 2 0 0 1 2.1-.4c.9.3 1.8.6 2.8.7a2 2 0 0 1 1.7 2z" }) }),
3709
+ down: /* @__PURE__ */ jsx39(Fragment12, { children: /* @__PURE__ */ jsx39("path", { d: "M6 9l6 6 6-6" }) }),
3710
+ up2: /* @__PURE__ */ jsx39(Fragment12, { children: /* @__PURE__ */ jsx39("path", { d: "M18 15l-6-6-6 6" }) }),
3711
+ right: /* @__PURE__ */ jsx39(Fragment12, { children: /* @__PURE__ */ jsx39("path", { d: "M9 18l6-6-6-6" }) }),
3712
+ grid: /* @__PURE__ */ jsxs22(Fragment12, { children: [
3585
3713
  /* @__PURE__ */ jsx39("rect", { x: "3", y: "3", width: "7.5", height: "7.5", rx: "1.5" }),
3586
3714
  /* @__PURE__ */ jsx39("rect", { x: "13.5", y: "3", width: "7.5", height: "7.5", rx: "1.5" }),
3587
3715
  /* @__PURE__ */ jsx39("rect", { x: "3", y: "13.5", width: "7.5", height: "7.5", rx: "1.5" }),
3588
3716
  /* @__PURE__ */ jsx39("rect", { x: "13.5", y: "13.5", width: "7.5", height: "7.5", rx: "1.5" })
3589
3717
  ] }),
3590
- spot: /* @__PURE__ */ jsxs22(Fragment11, { children: [
3718
+ spot: /* @__PURE__ */ jsxs22(Fragment12, { children: [
3591
3719
  /* @__PURE__ */ jsx39("rect", { x: "3", y: "3", width: "18", height: "12.5", rx: "2" }),
3592
3720
  /* @__PURE__ */ jsx39("path", { d: "M4 20.5h3M10.5 20.5h3M17 20.5h3" })
3593
3721
  ] }),
3594
- smile: /* @__PURE__ */ jsxs22(Fragment11, { children: [
3722
+ smile: /* @__PURE__ */ jsxs22(Fragment12, { children: [
3595
3723
  /* @__PURE__ */ jsx39("circle", { cx: "12", cy: "12", r: "9" }),
3596
3724
  /* @__PURE__ */ jsx39("path", { d: "M8.5 14.5s1.3 1.8 3.5 1.8 3.5-1.8 3.5-1.8M9 9.5h.01M15 9.5h.01" })
3597
3725
  ] }),
3598
- pen: /* @__PURE__ */ jsxs22(Fragment11, { children: [
3726
+ pen: /* @__PURE__ */ jsxs22(Fragment12, { children: [
3599
3727
  /* @__PURE__ */ jsx39("path", { d: "M17 3a2.85 2.85 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5z" }),
3600
3728
  /* @__PURE__ */ jsx39("path", { d: "M15 5l4 4" })
3601
3729
  ] }),
3602
- rec: /* @__PURE__ */ jsxs22(Fragment11, { children: [
3730
+ rec: /* @__PURE__ */ jsxs22(Fragment12, { children: [
3603
3731
  /* @__PURE__ */ jsx39("circle", { cx: "12", cy: "12", r: "9" }),
3604
3732
  /* @__PURE__ */ jsx39("circle", { cx: "12", cy: "12", r: "3.5", fill: "currentColor" })
3605
3733
  ] }),
3606
- max: /* @__PURE__ */ jsx39(Fragment11, { children: /* @__PURE__ */ jsx39("path", { d: "M8 3H5a2 2 0 0 0-2 2v3M21 8V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3M16 21h3a2 2 0 0 0 2-2v-3" }) }),
3607
- min: /* @__PURE__ */ jsx39(Fragment11, { children: /* @__PURE__ */ jsx39("path", { d: "M8 3v3a2 2 0 0 1-2 2H3M21 8h-3a2 2 0 0 1-2-2V3M3 16h3a2 2 0 0 1 2 2v3M16 21v-3a2 2 0 0 1 2-2h3" }) }),
3608
- chat: /* @__PURE__ */ jsx39(Fragment11, { children: /* @__PURE__ */ jsx39("path", { d: "M7.9 20A9 9 0 1 0 4 16.1L2 22z" }) }),
3609
- users: /* @__PURE__ */ jsxs22(Fragment11, { children: [
3734
+ max: /* @__PURE__ */ jsx39(Fragment12, { children: /* @__PURE__ */ jsx39("path", { d: "M8 3H5a2 2 0 0 0-2 2v3M21 8V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3M16 21h3a2 2 0 0 0 2-2v-3" }) }),
3735
+ min: /* @__PURE__ */ jsx39(Fragment12, { children: /* @__PURE__ */ jsx39("path", { d: "M8 3v3a2 2 0 0 1-2 2H3M21 8h-3a2 2 0 0 1-2-2V3M3 16h3a2 2 0 0 1 2 2v3M16 21v-3a2 2 0 0 1 2-2h3" }) }),
3736
+ chat: /* @__PURE__ */ jsx39(Fragment12, { children: /* @__PURE__ */ jsx39("path", { d: "M7.9 20A9 9 0 1 0 4 16.1L2 22z" }) }),
3737
+ users: /* @__PURE__ */ jsxs22(Fragment12, { children: [
3610
3738
  /* @__PURE__ */ jsx39("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }),
3611
3739
  /* @__PURE__ */ jsx39("circle", { cx: "9", cy: "7", r: "4" }),
3612
3740
  /* @__PURE__ */ jsx39("path", { d: "M22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8" })
3613
3741
  ] }),
3614
- share: /* @__PURE__ */ jsxs22(Fragment11, { children: [
3742
+ share: /* @__PURE__ */ jsxs22(Fragment12, { children: [
3615
3743
  /* @__PURE__ */ jsx39("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }),
3616
3744
  /* @__PURE__ */ jsx39("path", { d: "M8 21h8M12 17v4M9 10l3-3 3 3M12 7v6" })
3617
3745
  ] }),
3618
- shareOff: /* @__PURE__ */ jsxs22(Fragment11, { children: [
3746
+ shareOff: /* @__PURE__ */ jsxs22(Fragment12, { children: [
3619
3747
  /* @__PURE__ */ jsx39("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }),
3620
3748
  /* @__PURE__ */ jsx39("path", { d: "M8 21h8M12 17v4M9.5 7.5l5 5M14.5 7.5l-5 5" })
3621
3749
  ] })
@@ -4215,9 +4343,9 @@ var CALL_SHELL_CSS = `.chalk-shell-dock {
4215
4343
  `;
4216
4344
 
4217
4345
  // src/components/device-popover/DevicePopover.tsx
4218
- import { Fragment as Fragment12, jsx as jsx41, jsxs as jsxs23 } from "react/jsx-runtime";
4346
+ import { Fragment as Fragment13, jsx as jsx41, jsxs as jsxs23 } from "react/jsx-runtime";
4219
4347
  function InputLevel({ track }) {
4220
- const [level, setLevel] = useState19(0);
4348
+ const [level, setLevel] = useState20(0);
4221
4349
  useEffect15(() => {
4222
4350
  if (!track || track.readyState !== "live") return;
4223
4351
  const context = new AudioContext();
@@ -4240,8 +4368,8 @@ function InputLevel({ track }) {
4240
4368
  return /* @__PURE__ */ jsx41("span", { className: "chalk-shell-level", role: "meter", "aria-label": "Input level", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": level, children: /* @__PURE__ */ jsx41("i", { style: { transform: `scaleX(${level / 100})` } }) });
4241
4369
  }
4242
4370
  function CameraPreview({ track, mirrored, open, selectedDeviceId, captureCameraPreview }) {
4243
- const video = useRef14(null);
4244
- const [unavailable, setUnavailable] = useState19(false);
4371
+ const video = useRef13(null);
4372
+ const [unavailable2, setUnavailable] = useState20(false);
4245
4373
  const trackEnabled = track?.enabled;
4246
4374
  useEffect15(() => {
4247
4375
  const element = video.current;
@@ -4276,7 +4404,7 @@ function CameraPreview({ track, mirrored, open, selectedDeviceId, captureCameraP
4276
4404
  }, [track, trackEnabled, open, selectedDeviceId, captureCameraPreview]);
4277
4405
  return /* @__PURE__ */ jsxs23("div", { className: "chalk-shell-preview", children: [
4278
4406
  /* @__PURE__ */ jsx41("video", { ref: video, autoPlay: true, muted: true, playsInline: true, style: { transform: mirrored ? "scaleX(-1)" : void 0 } }),
4279
- /* @__PURE__ */ jsx41("small", { children: unavailable ? "Camera preview unavailable. Check camera access." : "Only you see this preview" })
4407
+ /* @__PURE__ */ jsx41("small", { children: unavailable2 ? "Camera preview unavailable. Check camera access." : "Only you see this preview" })
4280
4408
  ] });
4281
4409
  }
4282
4410
  function DeviceSwitch({ label, checked, onChange }) {
@@ -4310,7 +4438,7 @@ function DevicePopover({
4310
4438
  toggleDisabled,
4311
4439
  busy
4312
4440
  }) {
4313
- const [open, setOpen] = useState19(false);
4441
+ const [open, setOpen] = useState20(false);
4314
4442
  const mic = type === "mic";
4315
4443
  const label = mic ? isActive ? "Mute microphone" : "Unmute microphone" : isActive ? "Turn off camera" : "Turn on camera";
4316
4444
  return /* @__PURE__ */ jsxs23("div", { "data-part": `${type}-split`, className: cn("chalk-shell-split", className), "data-off": !isActive, children: [
@@ -4332,20 +4460,20 @@ function DevicePopover({
4332
4460
  children: [
4333
4461
  !mic ? /* @__PURE__ */ jsx41(CameraPreview, { track, mirrored, open, selectedDeviceId, captureCameraPreview }) : null,
4334
4462
  /* @__PURE__ */ jsx41(ChalkMenuLabel, { children: mic ? "Microphone" : "Camera" }),
4335
- devices.length ? devices.map((device) => /* @__PURE__ */ jsx41(ChalkMenuItem, { checked: selectedDeviceId === device.deviceId, onClick: () => onDeviceChange(device.deviceId), closeOnClick: false, children: /* @__PURE__ */ jsxs23("span", { className: "chalk-shell-device-label", children: [
4336
- device.label || (mic ? "Microphone" : "Camera"),
4337
- mic && selectedDeviceId === device.deviceId ? /* @__PURE__ */ jsx41(InputLevel, { track }) : null
4338
- ] }) }, device.deviceId)) : /* @__PURE__ */ jsx41(ChalkMenuItem, { disabled: true, children: "No devices found" }),
4339
- mic ? /* @__PURE__ */ jsxs23(Fragment12, { children: [
4463
+ devices.length ? devices.map((device2) => /* @__PURE__ */ jsx41(ChalkMenuItem, { checked: selectedDeviceId === device2.deviceId, onClick: () => onDeviceChange(device2.deviceId), closeOnClick: false, children: /* @__PURE__ */ jsxs23("span", { className: "chalk-shell-device-label", children: [
4464
+ device2.label || (mic ? "Microphone" : "Camera"),
4465
+ mic && selectedDeviceId === device2.deviceId ? /* @__PURE__ */ jsx41(InputLevel, { track }) : null
4466
+ ] }) }, device2.deviceId)) : /* @__PURE__ */ jsx41(ChalkMenuItem, { disabled: true, children: "No devices found" }),
4467
+ mic ? /* @__PURE__ */ jsxs23(Fragment13, { children: [
4340
4468
  /* @__PURE__ */ jsx41(DeviceSwitch, { label: "Noise suppression", checked: noiseSuppression, onChange: onNoiseSuppressionChange }),
4341
4469
  /* @__PURE__ */ jsx41(Menu2.Separator, { className: "chalk-shell-menu-separator" }),
4342
4470
  /* @__PURE__ */ jsx41(ChalkMenuLabel, { children: "Speaker" }),
4343
- secondaryDevices?.map((device) => /* @__PURE__ */ jsx41(ChalkMenuItem, { checked: selectedSecondaryDeviceId === device.deviceId, onClick: () => onSecondaryDeviceChange?.(device.deviceId), closeOnClick: false, children: device.label || "Speaker" }, device.deviceId)),
4471
+ secondaryDevices?.map((device2) => /* @__PURE__ */ jsx41(ChalkMenuItem, { checked: selectedSecondaryDeviceId === device2.deviceId, onClick: () => onSecondaryDeviceChange?.(device2.deviceId), closeOnClick: false, children: device2.label || "Speaker" }, device2.deviceId)),
4344
4472
  /* @__PURE__ */ jsxs23(ChalkMenuItem, { disabled: !onTestSpeaker, onClick: onTestSpeaker, closeOnClick: false, children: [
4345
4473
  /* @__PURE__ */ jsx41(VolumeHighIcon, {}),
4346
4474
  "Test speaker"
4347
4475
  ] })
4348
- ] }) : /* @__PURE__ */ jsxs23(Fragment12, { children: [
4476
+ ] }) : /* @__PURE__ */ jsxs23(Fragment13, { children: [
4349
4477
  /* @__PURE__ */ jsx41(Menu2.Separator, { className: "chalk-shell-menu-separator" }),
4350
4478
  /* @__PURE__ */ jsx41(DeviceSwitch, { label: "Mirror my video", checked: mirrored, onChange: onMirrorChange })
4351
4479
  ] })
@@ -4378,11 +4506,11 @@ function LeaveMenu({ onLeave, onEndForEveryone, renderButton }) {
4378
4506
  }
4379
4507
 
4380
4508
  // src/components/control-bar/DockLayout.tsx
4381
- import { useState as useState20 } from "react";
4382
- import { Fragment as Fragment13, jsx as jsx44, jsxs as jsxs26 } from "react/jsx-runtime";
4509
+ import { useState as useState21 } from "react";
4510
+ import { Fragment as Fragment14, jsx as jsx44, jsxs as jsxs26 } from "react/jsx-runtime";
4383
4511
  function DockLayout({ layout, onLayoutChange, children }) {
4384
- const [open, setOpen] = useState20(false);
4385
- return /* @__PURE__ */ jsxs26(Fragment13, { children: [
4512
+ const [open, setOpen] = useState21(false);
4513
+ return /* @__PURE__ */ jsxs26(Fragment14, { children: [
4386
4514
  /* @__PURE__ */ jsx44(ChalkIconButton, { size: "lg", "aria-label": "More options", "aria-haspopup": "dialog", "aria-expanded": open, onClick: () => setOpen(true), children: /* @__PURE__ */ jsx44("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx44("path", { "data-part": "dock-more-glyph", d: "M5 12h.01M12 12h.01M19 12h.01", strokeWidth: "3" }) }) }),
4387
4515
  /* @__PURE__ */ jsx44(ChalkSheet, { title: "More options", open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs26(
4388
4516
  "div",
@@ -4423,25 +4551,25 @@ function DockLayout({ layout, onLayoutChange, children }) {
4423
4551
 
4424
4552
  // src/components/control-bar/ShareControl.tsx
4425
4553
  import { Menu as Menu4 } from "@base-ui/react/menu";
4426
- import { useState as useState21 } from "react";
4427
- import { Fragment as Fragment14, jsx as jsx45, jsxs as jsxs27 } from "react/jsx-runtime";
4554
+ import { useState as useState22 } from "react";
4555
+ import { Fragment as Fragment15, jsx as jsx45, jsxs as jsxs27 } from "react/jsx-runtime";
4428
4556
  function SourceIcon({ source }) {
4429
- return /* @__PURE__ */ jsx45("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: source === "monitor" ? /* @__PURE__ */ jsxs27(Fragment14, { children: [
4557
+ return /* @__PURE__ */ jsx45("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: source === "monitor" ? /* @__PURE__ */ jsxs27(Fragment15, { children: [
4430
4558
  /* @__PURE__ */ jsx45("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }),
4431
4559
  /* @__PURE__ */ jsx45("path", { d: "M8 21h8M12 17v4" })
4432
- ] }) : source === "window" ? /* @__PURE__ */ jsxs27(Fragment14, { children: [
4560
+ ] }) : source === "window" ? /* @__PURE__ */ jsxs27(Fragment15, { children: [
4433
4561
  /* @__PURE__ */ jsx45("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }),
4434
4562
  /* @__PURE__ */ jsx45("path", { d: "M3 9h18M6.5 6.5h.01M9 6.5h.01" })
4435
- ] }) : /* @__PURE__ */ jsxs27(Fragment14, { children: [
4563
+ ] }) : /* @__PURE__ */ jsxs27(Fragment15, { children: [
4436
4564
  /* @__PURE__ */ jsx45("path", { d: "M3 20V7a2 2 0 0 1 2-2h5l2 3h7a2 2 0 0 1 2 2v10z" }),
4437
4565
  /* @__PURE__ */ jsx45("path", { d: "M3 11h18" })
4438
4566
  ] }) });
4439
4567
  }
4440
4568
  function ShareControl({ sharing, onToggle, onStart, disabled = false, startDisabled = false }) {
4441
- const [surface, setSurface] = useState21("window");
4442
- const [audio, setAudio] = useState21(true);
4443
- const [replace, setReplace] = useState21(false);
4444
- const start = (next) => {
4569
+ const [surface, setSurface] = useState22("window");
4570
+ const [audio, setAudio] = useState22(true);
4571
+ const [replace, setReplace] = useState22(false);
4572
+ const start2 = (next) => {
4445
4573
  if (disabled || startDisabled) return;
4446
4574
  setSurface(next);
4447
4575
  setReplace(false);
@@ -4451,7 +4579,7 @@ function ShareControl({ sharing, onToggle, onStart, disabled = false, startDisab
4451
4579
  /* @__PURE__ */ jsx45(
4452
4580
  ChalkTooltip,
4453
4581
  {
4454
- trigger: /* @__PURE__ */ jsx45("button", { type: "button", className: "chalk-shell-main", disabled, "aria-label": sharing ? "Stop sharing" : "Share screen", "aria-pressed": sharing, onClick: () => sharing || !onStart ? onToggle?.() : start(surface), children: /* @__PURE__ */ jsx45(ShellIcon, { name: sharing ? "shareOff" : "share" }) }),
4582
+ trigger: /* @__PURE__ */ jsx45("button", { type: "button", className: "chalk-shell-main", disabled, "aria-label": sharing ? "Stop sharing" : "Share screen", "aria-pressed": sharing, onClick: () => sharing || !onStart ? onToggle?.() : start2(surface), children: /* @__PURE__ */ jsx45(ShellIcon, { name: sharing ? "shareOff" : "share" }) }),
4455
4583
  children: sharing ? "Stop sharing" : "Share screen"
4456
4584
  }
4457
4585
  ),
@@ -4461,16 +4589,16 @@ function ShareControl({ sharing, onToggle, onStart, disabled = false, startDisab
4461
4589
  side: "top",
4462
4590
  className: "chalk-device-menu",
4463
4591
  trigger: /* @__PURE__ */ jsx45("button", { type: "button", className: "chalk-shell-caret", disabled: disabled || startDisabled, "aria-label": "Share options", children: /* @__PURE__ */ jsx45(ShellIcon, { name: "down" }) }),
4464
- children: sharing && !replace ? /* @__PURE__ */ jsxs27(Fragment14, { children: [
4592
+ children: sharing && !replace ? /* @__PURE__ */ jsxs27(Fragment15, { children: [
4465
4593
  /* @__PURE__ */ jsx45(ChalkMenuLabel, { children: "Sharing your screen" }),
4466
4594
  /* @__PURE__ */ jsx45(Menu4.Item, { className: "chalk-menu-item", disabled: disabled || startDisabled, closeOnClick: false, onClick: () => setReplace(true), children: "Share something else" }),
4467
4595
  /* @__PURE__ */ jsx45(ChalkMenuItem, { onClick: onToggle, children: "Stop sharing" })
4468
- ] }) : /* @__PURE__ */ jsxs27(Fragment14, { children: [
4596
+ ] }) : /* @__PURE__ */ jsxs27(Fragment15, { children: [
4469
4597
  [
4470
4598
  { value: "monitor", label: "Entire screen", description: "Everything on one display" },
4471
4599
  { value: "window", label: "Window", description: "One app, like Slides or Figma" },
4472
4600
  { value: "browser", label: "Browser tab", description: "One tab, and its sound if you like" }
4473
- ].map((option) => /* @__PURE__ */ jsxs27(Menu4.Item, { className: "chalk-menu-item chalk-shell-source", disabled: disabled || startDisabled, role: "menuitemradio", "aria-label": option.label, "aria-checked": surface === option.value, onClick: () => start(option.value), children: [
4601
+ ].map((option) => /* @__PURE__ */ jsxs27(Menu4.Item, { className: "chalk-menu-item chalk-shell-source", disabled: disabled || startDisabled, role: "menuitemradio", "aria-label": option.label, "aria-checked": surface === option.value, onClick: () => start2(option.value), children: [
4474
4602
  /* @__PURE__ */ jsx45(SourceIcon, { source: option.value }),
4475
4603
  /* @__PURE__ */ jsxs27("span", { className: "chalk-shell-device-label", children: [
4476
4604
  /* @__PURE__ */ jsx45("span", { children: option.label }),
@@ -4478,7 +4606,7 @@ function ShareControl({ sharing, onToggle, onStart, disabled = false, startDisab
4478
4606
  ] }),
4479
4607
  /* @__PURE__ */ jsx45("span", { className: "chalk-menu-check", "aria-hidden": "true", children: surface === option.value ? /* @__PURE__ */ jsx45("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx45("path", { d: "M20 6L9 17l-5-5" }) }) : null })
4480
4608
  ] }, option.value)),
4481
- surface === "browser" ? /* @__PURE__ */ jsxs27(Fragment14, { children: [
4609
+ surface === "browser" ? /* @__PURE__ */ jsxs27(Fragment15, { children: [
4482
4610
  /* @__PURE__ */ jsx45(Menu4.Separator, { className: "chalk-shell-menu-separator" }),
4483
4611
  /* @__PURE__ */ jsxs27(Menu4.CheckboxItem, { className: "chalk-menu-item", checked: audio, onCheckedChange: setAudio, closeOnClick: false, children: [
4484
4612
  /* @__PURE__ */ jsxs27("span", { className: "chalk-shell-device-label", children: [
@@ -4495,19 +4623,7 @@ function ShareControl({ sharing, onToggle, onStart, disabled = false, startDisab
4495
4623
  }
4496
4624
 
4497
4625
  // src/components/control-bar/control-bar-behavior.ts
4498
- import { useCallback as useCallback5, useEffect as useEffect16, useMemo as useMemo9, useState as useState22 } from "react";
4499
-
4500
- // src/components/control-bar/command-failure.ts
4501
- import { SpaceClientError } from "@q9labsai/chalk-client";
4502
- function commandFailure(cause) {
4503
- return cause instanceof SpaceClientError ? cause : new SpaceClientError({
4504
- code: "client.internal_error",
4505
- recoverable: false,
4506
- message: cause instanceof Error ? cause.message : "This command could not be completed."
4507
- });
4508
- }
4509
-
4510
- // src/components/control-bar/control-bar-behavior.ts
4626
+ import { useCallback as useCallback5, useEffect as useEffect16, useMemo as useMemo9, useState as useState23 } from "react";
4511
4627
  var DEFAULT_CONTROL_BAR_BUTTONS = ["mic", "video", "screenshare", "record", "whiteboard", "handraise", "leave", "participants", "chat", "reactions"];
4512
4628
  var EMPTY_DETECTED_DEVICES = {
4513
4629
  audioinput: [],
@@ -4517,10 +4633,10 @@ var EMPTY_DETECTED_DEVICES = {
4517
4633
  function mergeDevices(...deviceGroups) {
4518
4634
  const devicesById = /* @__PURE__ */ new Map();
4519
4635
  for (const deviceGroup of deviceGroups) {
4520
- for (const device of deviceGroup ?? []) {
4521
- const existing = devicesById.get(device.deviceId);
4522
- if (!existing || !existing.label && device.label) {
4523
- devicesById.set(device.deviceId, device);
4636
+ for (const device2 of deviceGroup ?? []) {
4637
+ const existing = devicesById.get(device2.deviceId);
4638
+ if (!existing || !existing.label && device2.label) {
4639
+ devicesById.set(device2.deviceId, device2);
4524
4640
  }
4525
4641
  }
4526
4642
  }
@@ -4544,7 +4660,7 @@ function useControlBarSurfaceBehavior({
4544
4660
  detectDevices = true
4545
4661
  }) {
4546
4662
  const themeVariables = useMemo9(() => getParticipantThemeVariables(participantColorSeed, participantGradientPreference), [participantColorSeed, participantGradientPreference]);
4547
- const [detectedDevices, setDetectedDevices] = useState22(EMPTY_DETECTED_DEVICES);
4663
+ const [detectedDevices, setDetectedDevices] = useState23(EMPTY_DETECTED_DEVICES);
4548
4664
  const buttonsToRender = buttons ?? DEFAULT_CONTROL_BAR_BUTTONS;
4549
4665
  useEffect16(() => {
4550
4666
  if (!detectDevices || density !== "comfortable" || !navigator.mediaDevices?.enumerateDevices) return;
@@ -4554,9 +4670,9 @@ function useControlBarSurfaceBehavior({
4554
4670
  const devices = await navigator.mediaDevices.enumerateDevices();
4555
4671
  if (!cancelled) {
4556
4672
  setDetectedDevices({
4557
- audioinput: devices.filter((device) => device.kind === "audioinput"),
4558
- audiooutput: devices.filter((device) => device.kind === "audiooutput"),
4559
- videoinput: devices.filter((device) => device.kind === "videoinput")
4673
+ audioinput: devices.filter((device2) => device2.kind === "audioinput"),
4674
+ audiooutput: devices.filter((device2) => device2.kind === "audiooutput"),
4675
+ videoinput: devices.filter((device2) => device2.kind === "videoinput")
4560
4676
  });
4561
4677
  }
4562
4678
  } catch {
@@ -4594,7 +4710,7 @@ function useConnectedControlBarBehavior(props) {
4594
4710
  const canPublishScreen = useCan("publishScreen");
4595
4711
  const canSendReaction = useCan("sendReaction");
4596
4712
  const canRaiseHand = useCan("raiseHand");
4597
- const canDrawWhiteboard = useCan("drawWhiteboard");
4713
+ const canDrawWhiteboard = useCanDrawWhiteboard();
4598
4714
  const canManageRecording = useCan("manageRecording");
4599
4715
  const live = useConnection().status === "live";
4600
4716
  const microphoneEnabled = media.local.microphone.state === "enabled";
@@ -4603,18 +4719,26 @@ function useConnectedControlBarBehavior(props) {
4603
4719
  const recordingStatus = recording.current?.status;
4604
4720
  const isRecording = recordingStatus === "recording" || recordingStatus === "stopping";
4605
4721
  const isRecordingPending = recordingStatus === "starting" || recordingStatus === "stopping";
4606
- const [noiseSuppression, setNoiseSuppression] = useState22(true);
4607
- const [commandError, setCommandError] = useState22(null);
4722
+ const [noiseSuppression, setNoiseSuppression] = useState23(true);
4723
+ const [commandError, setCommandError] = useState23(null);
4724
+ const [commandSource, setCommandSource] = useState23();
4725
+ const [commandAction, setCommandAction] = useState23();
4608
4726
  const run = useCallback5(
4609
- async (command, source) => {
4727
+ async (command, source, action) => {
4610
4728
  setCommandError(null);
4611
- props.onCommandError?.(null, source);
4729
+ setCommandSource(void 0);
4730
+ setCommandAction(void 0);
4731
+ props.onCommandError?.(null, source === "speaker" ? void 0 : source, source, action);
4612
4732
  try {
4613
4733
  await command();
4614
4734
  } catch (cause) {
4615
4735
  const failure = commandFailure(cause);
4616
- if (!props.onCommandError) setCommandError(failure);
4617
- props.onCommandError?.(failure, source);
4736
+ if (!props.onCommandError) {
4737
+ setCommandError(failure);
4738
+ setCommandSource(source);
4739
+ setCommandAction(action);
4740
+ }
4741
+ props.onCommandError?.(failure, source === "speaker" ? void 0 : source, source, action, command);
4618
4742
  }
4619
4743
  },
4620
4744
  [props.onCommandError]
@@ -4637,9 +4761,9 @@ function useConnectedControlBarBehavior(props) {
4637
4761
  if (button === "record") return canManageRecording;
4638
4762
  return true;
4639
4763
  });
4640
- const audioInputDevices = media.devices.microphones.map((device) => ({ ...device, kind: "audioinput" }));
4641
- const audioOutputDevices = media.devices.speakers.map((device) => ({ ...device, kind: "audiooutput" }));
4642
- const videoInputDevices = media.devices.cameras.map((device) => ({ ...device, kind: "videoinput" }));
4764
+ const audioInputDevices = media.devices.microphones.map((device2) => ({ ...device2, kind: "audioinput" }));
4765
+ const audioOutputDevices = media.devices.speakers.map((device2) => ({ ...device2, kind: "audiooutput" }));
4766
+ const videoInputDevices = media.devices.cameras.map((device2) => ({ ...device2, kind: "videoinput" }));
4643
4767
  const surfaceProps = {
4644
4768
  ...props,
4645
4769
  detectDevices: false,
@@ -4656,9 +4780,13 @@ function useConnectedControlBarBehavior(props) {
4656
4780
  await media.local.microphone.track?.applyConstraints({ noiseSuppression: enabled });
4657
4781
  setNoiseSuppression(enabled);
4658
4782
  }, "microphone") : void 0,
4659
- onStartScreenShare: (options) => void run(async () => {
4660
- await client.media.setScreenShareEnabled(true, options);
4661
- }, "screen"),
4783
+ onStartScreenShare: (options) => void run(
4784
+ async () => {
4785
+ await client.media.setScreenShareEnabled(true, options);
4786
+ },
4787
+ "screen",
4788
+ "start"
4789
+ ),
4662
4790
  shareUrl: props.shareUrl,
4663
4791
  isMuted: !microphoneEnabled,
4664
4792
  microphonePending: media.local.microphone.state === "requesting",
@@ -4680,39 +4808,43 @@ function useConnectedControlBarBehavior(props) {
4680
4808
  selectedAudioInput: media.selection.microphone ?? void 0,
4681
4809
  selectedAudioOutput: media.selection.speaker ?? void 0,
4682
4810
  selectedVideoInput: media.selection.camera ?? void 0,
4683
- onTestSpeaker: () => void run(async () => {
4684
- const context = new AudioContext();
4685
- const oscillator = context.createOscillator();
4686
- const gain = context.createGain();
4687
- const output = context.createMediaStreamDestination();
4688
- const audio = new Audio();
4689
- try {
4690
- if (media.selection.speaker && audio.setSinkId) await audio.setSinkId(media.selection.speaker);
4691
- audio.srcObject = output.stream;
4692
- oscillator.connect(gain).connect(output);
4693
- gain.gain.value = 0.15;
4694
- oscillator.frequency.value = 440;
4695
- oscillator.start();
4696
- await audio.play();
4697
- await new Promise((resolve) => {
4698
- oscillator.onended = () => resolve();
4699
- oscillator.stop(context.currentTime + 0.4);
4700
- });
4701
- } finally {
4702
- audio.pause();
4703
- audio.srcObject = null;
4704
- oscillator.disconnect();
4705
- gain.disconnect();
4706
- output.stream.getTracks().forEach((track) => track.stop());
4707
- await context.close();
4708
- }
4709
- }),
4710
- onToggleMute: () => void run(() => client.media.setMicrophoneEnabled(!microphoneEnabled), "microphone"),
4711
- onToggleVideo: () => void run(() => client.media.setCameraEnabled(!cameraEnabled), "camera"),
4712
- onAudioInputChange: (deviceId) => void run(() => client.media.selectMicrophone(deviceId), "microphone"),
4713
- onAudioOutputChange: (deviceId) => void run(() => client.media.selectSpeaker(deviceId)),
4714
- onVideoInputChange: (deviceId) => void run(() => client.media.selectCamera(deviceId), "camera"),
4715
- onToggleScreenShare: () => void run(() => client.media.setScreenShareEnabled(!screenSharing), "screen"),
4811
+ onTestSpeaker: () => void run(
4812
+ async () => {
4813
+ const context = new AudioContext();
4814
+ const oscillator = context.createOscillator();
4815
+ const gain = context.createGain();
4816
+ const output = context.createMediaStreamDestination();
4817
+ const audio = new Audio();
4818
+ try {
4819
+ if (media.selection.speaker && audio.setSinkId) await audio.setSinkId(media.selection.speaker);
4820
+ audio.srcObject = output.stream;
4821
+ oscillator.connect(gain).connect(output);
4822
+ gain.gain.value = 0.15;
4823
+ oscillator.frequency.value = 440;
4824
+ oscillator.start();
4825
+ await audio.play();
4826
+ await new Promise((resolve) => {
4827
+ oscillator.onended = () => resolve();
4828
+ oscillator.stop(context.currentTime + 0.4);
4829
+ });
4830
+ } finally {
4831
+ audio.pause();
4832
+ audio.srcObject = null;
4833
+ oscillator.disconnect();
4834
+ gain.disconnect();
4835
+ output.stream.getTracks().forEach((track) => track.stop());
4836
+ await context.close();
4837
+ }
4838
+ },
4839
+ "speaker",
4840
+ "test"
4841
+ ),
4842
+ onToggleMute: () => void run(() => client.media.setMicrophoneEnabled(!microphoneEnabled), "microphone", microphoneEnabled ? "stop" : "start"),
4843
+ onToggleVideo: () => void run(() => client.media.setCameraEnabled(!cameraEnabled), "camera", cameraEnabled ? "stop" : "start"),
4844
+ onAudioInputChange: (deviceId) => void run(() => client.media.selectMicrophone(deviceId), "microphone", "select"),
4845
+ onAudioOutputChange: (deviceId) => void run(() => client.media.selectSpeaker(deviceId), "speaker", "select"),
4846
+ onVideoInputChange: (deviceId) => void run(() => client.media.selectCamera(deviceId), "camera", "select"),
4847
+ onToggleScreenShare: () => void run(() => client.media.setScreenShareEnabled(!screenSharing), "screen", screenSharing ? "stop" : "start"),
4716
4848
  onToggleRecording: () => void run(() => recordingStatus === "recording" ? client.recording.stop() : client.recording.start()),
4717
4849
  onToggleChat: props.onToggleChat,
4718
4850
  onToggleParticipants: props.onToggleParticipants,
@@ -4722,7 +4854,7 @@ function useConnectedControlBarBehavior(props) {
4722
4854
  onLeft: leave,
4723
4855
  onEndForEveryone: props.onEndForEveryone
4724
4856
  };
4725
- return { surfaceProps, commandError };
4857
+ return { surfaceProps, commandError, commandSource, commandAction };
4726
4858
  }
4727
4859
 
4728
4860
  // src/components/control-bar/next-camera.ts
@@ -4732,26 +4864,26 @@ function facing(label) {
4732
4864
  return void 0;
4733
4865
  }
4734
4866
  function nextCamera(devices, selectedId, track) {
4735
- const settings = track?.getSettings();
4736
- const currentId = selectedId ?? settings?.deviceId;
4737
- const index = devices.findIndex((device) => device.deviceId === currentId);
4867
+ const settings2 = track?.getSettings();
4868
+ const currentId = selectedId ?? settings2?.deviceId;
4869
+ const index = devices.findIndex((device2) => device2.deviceId === currentId);
4738
4870
  const current = devices[index];
4739
4871
  const capabilities = track?.getCapabilities?.().facingMode;
4740
- const currentFacing = (current && facing(current.label)) ?? settings?.facingMode ?? (capabilities?.length === 1 ? capabilities[0] : void 0);
4872
+ const currentFacing = (current && facing(current.label)) ?? settings2?.facingMode ?? (capabilities?.length === 1 ? capabilities[0] : void 0);
4741
4873
  const opposite = currentFacing === "user" ? "environment" : currentFacing === "environment" ? "user" : void 0;
4742
- return opposite && devices.find((device) => device.deviceId !== currentId && facing(device.label) === opposite) || devices[(index + 1) % devices.length];
4874
+ return opposite && devices.find((device2) => device2.deviceId !== currentId && facing(device2.label) === opposite) || devices[(index + 1) % devices.length];
4743
4875
  }
4744
4876
 
4745
4877
  // src/components/control-bar/ControlBar.tsx
4746
- import { Fragment as Fragment15, jsx as jsx46, jsxs as jsxs28 } from "react/jsx-runtime";
4878
+ import { Fragment as Fragment16, jsx as jsx46, jsxs as jsxs28 } from "react/jsx-runtime";
4747
4879
  function ControlBarPresentation(props) {
4748
4880
  return /* @__PURE__ */ jsx46(ControlBarSurface, { ...props, detectDevices: props.displayOnly ? false : props.detectDevices });
4749
4881
  }
4750
4882
  var ControlBarSurface = React12.memo((props) => {
4751
4883
  const { buttonsToRender, effectiveAudioInputDevices, effectiveAudioOutputDevices, effectiveVideoInputDevices, showLeave } = useControlBarSurfaceBehavior({ ...props, density: props.density ?? "comfortable", detectDevices: props.detectDevices ?? true });
4752
- const dock = useRef15(null);
4753
- const [fullscreen, setFullscreen] = useState23(false);
4754
- const [canCaptureDisplay, setCanCaptureDisplay] = useState23(false);
4884
+ const dock = useRef14(null);
4885
+ const [fullscreen, setFullscreen] = useState24(false);
4886
+ const [canCaptureDisplay, setCanCaptureDisplay] = useState24(false);
4755
4887
  useEffect17(() => setCanCaptureDisplay(typeof navigator.mediaDevices?.getDisplayMedia === "function"), []);
4756
4888
  useEffect17(() => {
4757
4889
  const changed = () => setFullscreen(Boolean(document.fullscreenElement));
@@ -4894,19 +5026,19 @@ var ControlBarSurface = React12.memo((props) => {
4894
5026
  });
4895
5027
  ControlBarSurface.displayName = "ControlBarSurface";
4896
5028
  function ControlBar(props) {
4897
- const { surfaceProps, commandError } = useConnectedControlBarBehavior(props);
4898
- return /* @__PURE__ */ jsxs28(Fragment15, { children: [
5029
+ const { surfaceProps, commandError, commandSource, commandAction } = useConnectedControlBarBehavior(props);
5030
+ return /* @__PURE__ */ jsxs28(Fragment16, { children: [
4899
5031
  /* @__PURE__ */ jsx46(ControlBarSurface, { ...surfaceProps }),
4900
- !props.onCommandError ? /* @__PURE__ */ jsx46(CommandErrorAlert, { message: commandError?.message }) : null
5032
+ !props.onCommandError ? /* @__PURE__ */ jsx46(CommandErrorAlert, { message: commandError ? commandFailureMessage(commandError, commandSource, commandAction) : void 0 }) : null
4901
5033
  ] });
4902
5034
  }
4903
5035
 
4904
5036
  // src/components/participants-panel/ParticipantsPanel.tsx
4905
5037
  import { Popover as Popover3 } from "@base-ui/react/popover";
4906
- import React13, { useMemo as useMemo10, useState as useState25 } from "react";
5038
+ import React13, { useMemo as useMemo10, useState as useState26 } from "react";
4907
5039
 
4908
5040
  // src/components/participants-panel/participant-row.tsx
4909
- import { useState as useState24, useRef as useRef16, useEffect as useEffect18 } from "react";
5041
+ import { useState as useState25, useRef as useRef15, useEffect as useEffect18 } from "react";
4910
5042
  import { Popover as Popover2 } from "@base-ui/react/popover";
4911
5043
 
4912
5044
  // src/components/participants-panel/participant-options-menu-contract.ts
@@ -4928,7 +5060,7 @@ function runParticipantAction(action, onClose, onCommandError) {
4928
5060
  }
4929
5061
 
4930
5062
  // src/components/participants-panel/participant-options-menu.tsx
4931
- import { Fragment as Fragment16, jsx as jsx47, jsxs as jsxs29 } from "react/jsx-runtime";
5063
+ import { Fragment as Fragment17, jsx as jsx47, jsxs as jsxs29 } from "react/jsx-runtime";
4932
5064
  function ParticipantOptionsMenu({
4933
5065
  participant,
4934
5066
  variant,
@@ -4953,7 +5085,7 @@ function ParticipantOptionsMenu({
4953
5085
  const volumeMuted = volume <= 0;
4954
5086
  const menuItemClassName = cn("flex w-full items-center gap-3 rounded-[8px] px-3 py-2.5 text-left text-sm font-medium text-[var(--chalk-app-text)] transition-colors hover:bg-[var(--chalk-app-control-hover)]");
4955
5087
  const dividerClassName = "my-1.5 h-px bg-[var(--chalk-app-line)]";
4956
- return /* @__PURE__ */ jsxs29(Fragment16, { children: [
5088
+ return /* @__PURE__ */ jsxs29(Fragment17, { children: [
4957
5089
  hasLocalActions ? /* @__PURE__ */ jsxs29("button", { type: "button", onClick: onEditName, className: menuItemClassName, children: [
4958
5090
  /* @__PURE__ */ jsx47(Edit02Icon, { className: "h-4 w-4" }),
4959
5091
  "Edit Name"
@@ -4978,7 +5110,7 @@ function ParticipantOptionsMenu({
4978
5110
  )
4979
5111
  ] }) : null,
4980
5112
  hasVolumeControl && hasManageActions ? /* @__PURE__ */ jsx47("div", { className: dividerClassName }) : null,
4981
- hasManageActions ? /* @__PURE__ */ jsxs29(Fragment16, { children: [
5113
+ hasManageActions ? /* @__PURE__ */ jsxs29(Fragment17, { children: [
4982
5114
  !participant.isMuted && onMuteParticipant ? /* @__PURE__ */ jsxs29(
4983
5115
  "button",
4984
5116
  {
@@ -5081,10 +5213,10 @@ function ParticipantRow({
5081
5213
  onMenuToggle,
5082
5214
  onMenuClose
5083
5215
  }) {
5084
- const [isEditing, setIsEditing] = useState24(false);
5085
- const [editName, setEditName] = useState24(participant.displayName);
5086
- const inputRef = useRef16(null);
5087
- const rootRef = useRef16(null);
5216
+ const [isEditing, setIsEditing] = useState25(false);
5217
+ const [editName, setEditName] = useState25(participant.displayName);
5218
+ const inputRef = useRef15(null);
5219
+ const rootRef = useRef15(null);
5088
5220
  const hasVolumeControl = !!participantVolumes && !!onParticipantVolumeChange;
5089
5221
  const showMenuButton = canManageParticipants || hasVolumeControl || participant.isLocal && !!onUpdateDisplayName;
5090
5222
  useEffect18(() => {
@@ -5182,7 +5314,7 @@ function ParticipantRow({
5182
5314
  }
5183
5315
 
5184
5316
  // src/components/participants-panel/ParticipantsPanel.tsx
5185
- import { Fragment as Fragment17, jsx as jsx49, jsxs as jsxs31 } from "react/jsx-runtime";
5317
+ import { Fragment as Fragment18, jsx as jsx49, jsxs as jsxs31 } from "react/jsx-runtime";
5186
5318
  var ParticipantsPanelSurface = React13.memo(
5187
5319
  ({
5188
5320
  participants,
@@ -5208,10 +5340,10 @@ var ParticipantsPanelSurface = React13.memo(
5208
5340
  participantGradientPreference,
5209
5341
  generatedAvatars = true
5210
5342
  }) => {
5211
- const [query, setQuery] = useState25("");
5212
- const [activeMenuId, setActiveMenuId] = useState25(null);
5213
- const [muting, setMuting] = useState25(false);
5214
- const [error, setError] = useState25(null);
5343
+ const [query, setQuery] = useState26("");
5344
+ const [activeMenuId, setActiveMenuId] = useState26(null);
5345
+ const [muting, setMuting] = useState26(false);
5346
+ const [error, setError] = useState26(null);
5215
5347
  const filtered = participants.filter((person) => person.displayName.toLocaleLowerCase().includes(query.trim().toLocaleLowerCase()));
5216
5348
  const raised = participants.filter((person) => person.isHandRaised);
5217
5349
  const unmutedCount = participants.filter((person) => !person.isLocal && !person.isMuted).length;
@@ -5257,11 +5389,11 @@ var ParticipantsPanelSurface = React13.memo(
5257
5389
  /* @__PURE__ */ jsx49(Search01Icon, { "aria-hidden": "true" }),
5258
5390
  /* @__PURE__ */ jsx49("input", { "aria-label": "Search people", placeholder: "Search people", value: query, onChange: (event) => setQuery(event.target.value) })
5259
5391
  ] }) : null,
5260
- canManageAdmission && admissionEnabled && admissionCount > 0 ? /* @__PURE__ */ jsxs31(Fragment17, { children: [
5392
+ canManageAdmission && admissionEnabled && admissionCount > 0 ? /* @__PURE__ */ jsxs31(Fragment18, { children: [
5261
5393
  /* @__PURE__ */ jsx49(AdmissionPanel, { inline: true }),
5262
5394
  /* @__PURE__ */ jsx49("h3", { className: "call-people-section", children: "In the call" })
5263
5395
  ] }) : null,
5264
- !query && raised.length > 1 ? /* @__PURE__ */ jsxs31(Fragment17, { children: [
5396
+ !query && raised.length > 1 ? /* @__PURE__ */ jsxs31(Fragment18, { children: [
5265
5397
  /* @__PURE__ */ jsx49("h3", { className: "call-people-section", children: "Raised hands" }),
5266
5398
  raised.map((person) => /* @__PURE__ */ jsxs31("div", { className: "call-raised-row", children: [
5267
5399
  person.handOrder !== void 0 ? /* @__PURE__ */ jsx49("span", { className: "call-hand-number", children: person.handOrder }) : null,
@@ -5314,7 +5446,7 @@ var ParticipantsPanel = React13.memo((props) => {
5314
5446
  const canRemoveParticipants = useCan("removeParticipant");
5315
5447
  const canManageAdmission = useCan("manageAdmission");
5316
5448
  const contextVolumeState = useParticipantVolumeContext();
5317
- const [localVolumes, setLocalVolumes] = useState25(/* @__PURE__ */ new Map());
5449
+ const [localVolumes, setLocalVolumes] = useState26(/* @__PURE__ */ new Map());
5318
5450
  const isControlled = props.participantVolumes !== void 0 && props.onParticipantVolumeChange !== void 0;
5319
5451
  const participantVolumes = isControlled ? props.participantVolumes : contextVolumeState?.volumes ?? localVolumes;
5320
5452
  const onParticipantVolumeChange = isControlled ? props.onParticipantVolumeChange : contextVolumeState?.setVolume ?? ((participantId, volume) => setLocalVolumes((current) => {
@@ -5365,15 +5497,9 @@ var ParticipantsPanel = React13.memo((props) => {
5365
5497
  });
5366
5498
  ParticipantsPanel.displayName = "ParticipantsPanel";
5367
5499
 
5368
- // src/components/toast-stack/ToastStack.tsx
5369
- import { memo } from "react";
5370
- import { jsx as jsx50 } from "react/jsx-runtime";
5371
- var ToastStack = memo(function ToastStack2({ toasts, onDismiss, maxVisible = 3, className }) {
5372
- return /* @__PURE__ */ jsx50("div", { "data-part": "toast-stack", className: cn("chalk-toasts", className), children: toasts.slice(-maxVisible).map((toast) => /* @__PURE__ */ jsx50(ChalkToast, { ...toast, onDismiss: () => onDismiss(toast.id) }, toast.id)) });
5373
- });
5374
-
5375
5500
  // src/components/chalk/StatusSurface.tsx
5376
- import { Fragment as Fragment18, jsx as jsx51, jsxs as jsxs32 } from "react/jsx-runtime";
5501
+ import { formatSyncConnectDiagnostic } from "@q9labsai/chalk-client";
5502
+ import { Fragment as Fragment19, jsx as jsx50, jsxs as jsxs32 } from "react/jsx-runtime";
5377
5503
  var DEFAULT_FAILURE_DETAIL = "Something went wrong while joining. Try again.";
5378
5504
  var FAILURE_DETAILS = {
5379
5505
  "access.invalid": "Access to this call was rejected. Refresh the page and try again.",
@@ -5386,7 +5512,7 @@ var FAILURE_DETAILS = {
5386
5512
  "environment.unsupported": "This browser cannot start the call. Use an up-to-date browser over HTTPS.",
5387
5513
  "client.internal_error": DEFAULT_FAILURE_DETAIL
5388
5514
  };
5389
- function StatusSurface({ phase, callName = "the call", reason, errorCode, theme, onRejoin, onHome, onRetry, onReportProblem, rejoinDisabled = false, retryPending = false }) {
5515
+ function StatusSurface({ phase, callName = "the call", reason, errorCode, sync, theme, onRejoin, onHome, onRetry, onReportProblem, rejoinDisabled = false, retryPending = false }) {
5390
5516
  const copy = {
5391
5517
  entering: { title: `Joining ${callName}\u2026`, detail: "" },
5392
5518
  left: { title: `You left ${callName}`, detail: "" },
@@ -5407,31 +5533,34 @@ function StatusSurface({ phase, callName = "the call", reason, errorCode, theme,
5407
5533
  style: { ...chalkThemeStyle(theme), fontSize: 14, lineHeight: 1.4 },
5408
5534
  className: "chalk-root relative flex h-full min-h-0 w-full flex-col items-center justify-center gap-3 bg-[var(--chalk-canvas)] p-6 text-center text-[var(--chalk-text)]",
5409
5535
  children: [
5410
- /* @__PURE__ */ jsx51("h1", { role: phase === "entering" ? "status" : void 0, style: { fontSize: 22, fontWeight: 600, lineHeight: 1.4, fontFamily: "inherit", letterSpacing: "normal", textWrap: "wrap" }, children: copy.title }),
5411
- reason || copy.detail ? /* @__PURE__ */ jsx51("p", { className: "text-[14px] text-[var(--chalk-muted-text)]", children: reason ?? copy.detail }) : null,
5536
+ /* @__PURE__ */ jsx50("h1", { role: phase === "entering" ? "status" : void 0, style: { fontSize: 22, fontWeight: 600, lineHeight: 1.4, fontFamily: "inherit", letterSpacing: "normal", textWrap: "wrap" }, children: copy.title }),
5537
+ reason || copy.detail ? /* @__PURE__ */ jsx50("p", { className: "text-[14px] text-[var(--chalk-muted-text)]", children: reason ?? copy.detail }) : null,
5412
5538
  phase === "failed" && errorCode ? /* @__PURE__ */ jsxs32("p", { className: "text-[13px] text-[var(--chalk-muted-text)]", children: [
5413
5539
  "Reference:",
5414
5540
  " ",
5415
- /* @__PURE__ */ jsx51("code", { dir: "ltr", className: "break-all", children: errorCode })
5541
+ /* @__PURE__ */ jsxs32("code", { dir: "ltr", className: "break-all", children: [
5542
+ errorCode,
5543
+ sync ? ` ${formatSyncConnectDiagnostic(sync)}` : ""
5544
+ ] })
5416
5545
  ] }) : null,
5417
5546
  /* @__PURE__ */ jsxs32("div", { "data-part": "status-actions", className: "mt-5 flex gap-7", children: [
5418
- canRetry && retry ? /* @__PURE__ */ jsx51(StatusAction, { icon: /* @__PURE__ */ jsx51(StatusIcon, { retry: true }), label: phase === "elsewhere" ? "Use here" : phase === "left" ? "Rejoin" : "Try again", onClick: retry, disabled: rejoinDisabled || retryPending }) : null,
5419
- phase !== "entering" && onHome ? /* @__PURE__ */ jsx51(StatusAction, { icon: /* @__PURE__ */ jsx51(StatusIcon, {}), label: "Home", onClick: onHome }) : null
5547
+ canRetry && retry ? /* @__PURE__ */ jsx50(StatusAction, { icon: /* @__PURE__ */ jsx50(StatusIcon, { retry: true }), label: phase === "elsewhere" ? "Use here" : phase === "left" ? "Rejoin" : "Try again", onClick: retry, disabled: rejoinDisabled || retryPending }) : null,
5548
+ phase !== "entering" && onHome ? /* @__PURE__ */ jsx50(StatusAction, { icon: /* @__PURE__ */ jsx50(StatusIcon, {}), label: "Home", onClick: onHome }) : null
5420
5549
  ] }),
5421
- onReportProblem && phase !== "entering" ? /* @__PURE__ */ jsx51("button", { type: "button", onClick: onReportProblem, className: "mt-[18px] px-2.5 py-2 text-[13px] text-[var(--chalk-muted-text)] underline underline-offset-4 focus-visible:outline-2 focus-visible:outline-offset-2", children: "Report a problem" }) : null
5550
+ onReportProblem && phase !== "entering" ? /* @__PURE__ */ jsx50("button", { type: "button", onClick: onReportProblem, className: "mt-[18px] px-2.5 py-2 text-[13px] text-[var(--chalk-muted-text)] underline underline-offset-4 focus-visible:outline-2 focus-visible:outline-offset-2", children: "Report a problem" }) : null
5422
5551
  ]
5423
5552
  }
5424
5553
  );
5425
5554
  }
5426
5555
  function StatusIcon({ retry = false }) {
5427
- return /* @__PURE__ */ jsx51("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", children: retry ? /* @__PURE__ */ jsxs32(Fragment18, { children: [
5428
- /* @__PURE__ */ jsx51("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }),
5429
- /* @__PURE__ */ jsx51("path", { d: "M3 3v5h5" })
5430
- ] }) : /* @__PURE__ */ jsx51("path", { d: "M3 10.5L12 3l9 7.5V20a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z" }) });
5556
+ return /* @__PURE__ */ jsx50("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", children: retry ? /* @__PURE__ */ jsxs32(Fragment19, { children: [
5557
+ /* @__PURE__ */ jsx50("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }),
5558
+ /* @__PURE__ */ jsx50("path", { d: "M3 3v5h5" })
5559
+ ] }) : /* @__PURE__ */ jsx50("path", { d: "M3 10.5L12 3l9 7.5V20a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z" }) });
5431
5560
  }
5432
5561
  function StatusAction({ icon, label, onClick, disabled }) {
5433
5562
  return /* @__PURE__ */ jsxs32("button", { type: "button", onClick, disabled, style: { fontSize: 13, fontWeight: 500, lineHeight: 1.4 }, className: "group flex flex-col items-center gap-2 text-[var(--chalk-soft-text)] outline-none disabled:pointer-events-none disabled:opacity-50", children: [
5434
- /* @__PURE__ */ jsx51(
5563
+ /* @__PURE__ */ jsx50(
5435
5564
  "span",
5436
5565
  {
5437
5566
  "aria-hidden": "true",
@@ -5446,7 +5575,7 @@ function StatusAction({ icon, label, onClick, disabled }) {
5446
5575
  }
5447
5576
 
5448
5577
  // src/components/call-limbo/CallReady.tsx
5449
- import { useEffect as useEffect19, useId, useRef as useRef17, useState as useState26 } from "react";
5578
+ import { useEffect as useEffect19, useId, useRef as useRef16, useState as useState27 } from "react";
5450
5579
 
5451
5580
  // src/components/call-limbo/qr-code.ts
5452
5581
  function inviteQR(text) {
@@ -5587,15 +5716,15 @@ function inviteQR(text) {
5587
5716
  }
5588
5717
 
5589
5718
  // src/components/call-limbo/CallReady.tsx
5590
- import { Fragment as Fragment19, jsx as jsx52, jsxs as jsxs33 } from "react/jsx-runtime";
5719
+ import { Fragment as Fragment20, jsx as jsx51, jsxs as jsxs33 } from "react/jsx-runtime";
5591
5720
  function CallReady({ open, onClose, link = typeof location === "undefined" ? "" : location.href }) {
5592
- const [showQR, setShowQR] = useState26(false);
5593
- const qrRef = useRef17(null);
5594
- const cardRef = useRef17(null);
5721
+ const [showQR, setShowQR] = useState27(false);
5722
+ const qrRef = useRef16(null);
5723
+ const cardRef = useRef16(null);
5595
5724
  const phone = useIsMobile();
5596
5725
  const reducedMotion = usePrefersReducedMotion();
5597
- const [entered, setEntered] = useState26(false);
5598
- const [present, setPresent] = useState26(open);
5726
+ const [entered, setEntered] = useState27(false);
5727
+ const [present, setPresent] = useState27(open);
5599
5728
  const id = useId();
5600
5729
  const qr = showQR ? inviteQR(link) : null;
5601
5730
  const close = () => {
@@ -5624,31 +5753,31 @@ function CallReady({ open, onClose, link = typeof location === "undefined" ? ""
5624
5753
  clearTimeout(timer);
5625
5754
  };
5626
5755
  }, [open, reducedMotion]);
5627
- const content = /* @__PURE__ */ jsxs33(Fragment19, { children: [
5756
+ const content = /* @__PURE__ */ jsxs33(Fragment20, { children: [
5628
5757
  /* @__PURE__ */ jsxs33("div", { className: "-mt-1.5 mb-0.5 flex items-center justify-between", children: [
5629
- /* @__PURE__ */ jsx52("h2", { id, "data-part": "ready-title", tabIndex: -1, style: { fontSize: 15, fontWeight: 600, lineHeight: 1.4, fontFamily: "inherit", letterSpacing: "normal", textWrap: "wrap", outline: "none" }, children: "Your call is ready" }),
5630
- /* @__PURE__ */ jsx52("button", { type: "button", "data-part": "ready-dismiss", className: "chalk-dismiss max-sm:!h-11 max-sm:!w-11", "aria-label": "Close call link", onClick: close, children: /* @__PURE__ */ jsx52("svg", { width: "20", height: "20", style: { width: 20, height: 20 }, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx52("path", { d: "m6 6 12 12M6 18 18 6" }) }) })
5758
+ /* @__PURE__ */ jsx51("h2", { id, "data-part": "ready-title", tabIndex: -1, style: { fontSize: 15, fontWeight: 600, lineHeight: 1.4, fontFamily: "inherit", letterSpacing: "normal", textWrap: "wrap", outline: "none" }, children: "Your call is ready" }),
5759
+ /* @__PURE__ */ jsx51("button", { type: "button", "data-part": "ready-dismiss", className: "chalk-dismiss max-sm:!h-11 max-sm:!w-11", "aria-label": "Close call link", onClick: close, children: /* @__PURE__ */ jsx51("svg", { width: "20", height: "20", style: { width: 20, height: 20 }, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx51("path", { d: "m6 6 12 12M6 18 18 6" }) }) })
5631
5760
  ] }),
5632
- /* @__PURE__ */ jsx52("p", { className: "mr-1 text-[13px] text-[var(--chalk-muted-text)]", children: "Share this link with people you want in the call" }),
5761
+ /* @__PURE__ */ jsx51("p", { className: "mr-1 text-[13px] text-[var(--chalk-muted-text)]", children: "Share this link with people you want in the call" }),
5633
5762
  /* @__PURE__ */ jsxs33("div", { "data-part": "ready-link", style: { borderRadius: 999 }, className: "mt-3 flex h-11 items-center justify-between gap-2 bg-[var(--chalk-raised)] pl-3.5 pr-1 font-medium", children: [
5634
- /* @__PURE__ */ jsx52("span", { className: "min-w-0 truncate", children: link.replace(/^https?:\/\//, "") }),
5763
+ /* @__PURE__ */ jsx51("span", { className: "min-w-0 truncate", children: link.replace(/^https?:\/\//, "") }),
5635
5764
  /* @__PURE__ */ jsxs33("span", { className: "flex gap-0.5", children: [
5636
- /* @__PURE__ */ jsx52("button", { type: "button", className: "chalk-copy", "aria-label": showQR ? "Hide QR code" : "Show QR code", "aria-expanded": showQR, "aria-controls": `${id}-qr`, onClick: () => setShowQR(!showQR), children: /* @__PURE__ */ jsxs33("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", "aria-hidden": "true", children: [
5637
- /* @__PURE__ */ jsx52("rect", { x: "3", y: "3", width: "7", height: "7", rx: "1.5" }),
5638
- /* @__PURE__ */ jsx52("rect", { x: "14", y: "3", width: "7", height: "7", rx: "1.5" }),
5639
- /* @__PURE__ */ jsx52("rect", { x: "3", y: "14", width: "7", height: "7", rx: "1.5" }),
5640
- /* @__PURE__ */ jsx52("path", { d: "M14 14h3v3M21 14v.01M14 21h3M21 17v4h-1" })
5765
+ /* @__PURE__ */ jsx51("button", { type: "button", className: "chalk-copy", "aria-label": showQR ? "Hide QR code" : "Show QR code", "aria-expanded": showQR, "aria-controls": `${id}-qr`, onClick: () => setShowQR(!showQR), children: /* @__PURE__ */ jsxs33("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", "aria-hidden": "true", children: [
5766
+ /* @__PURE__ */ jsx51("rect", { x: "3", y: "3", width: "7", height: "7", rx: "1.5" }),
5767
+ /* @__PURE__ */ jsx51("rect", { x: "14", y: "3", width: "7", height: "7", rx: "1.5" }),
5768
+ /* @__PURE__ */ jsx51("rect", { x: "3", y: "14", width: "7", height: "7", rx: "1.5" }),
5769
+ /* @__PURE__ */ jsx51("path", { d: "M14 14h3v3M21 14v.01M14 21h3M21 17v4h-1" })
5641
5770
  ] }) }),
5642
- /* @__PURE__ */ jsx52(ChalkCopyButton, { text: link, announcement: "Copied" })
5771
+ /* @__PURE__ */ jsx51(ChalkCopyButton, { text: link, announcement: "Copied" })
5643
5772
  ] })
5644
5773
  ] }),
5645
- /* @__PURE__ */ jsx52("div", { ref: qrRef, id: `${id}-qr`, hidden: !showQR, children: /* @__PURE__ */ jsxs33("div", { className: "mt-3 flex items-center gap-3.5", children: [
5646
- qr ? /* @__PURE__ */ jsx52("svg", { role: "img", "aria-label": "Scan to join this call", width: "104", height: "104", viewBox: `-4 -4 ${qr.length + 8} ${qr.length + 8}`, className: "shrink-0 rounded-lg bg-white p-1", shapeRendering: "crispEdges", children: /* @__PURE__ */ jsx52("path", { fill: "#000", d: qr.flatMap((row, y) => row.flatMap((dark, x) => dark ? [`M${x} ${y}h1v1h-1z`] : [])).join("") }) }) : null,
5647
- /* @__PURE__ */ jsx52("p", { className: "text-[13px] text-[var(--chalk-soft-text)]", children: qr ? "Scan with a phone camera to join this call." : "This link is too long for a QR code. Copy the link to share it." })
5774
+ /* @__PURE__ */ jsx51("div", { ref: qrRef, id: `${id}-qr`, hidden: !showQR, children: /* @__PURE__ */ jsxs33("div", { className: "mt-3 flex items-center gap-3.5", children: [
5775
+ qr ? /* @__PURE__ */ jsx51("svg", { role: "img", "aria-label": "Scan to join this call", width: "104", height: "104", viewBox: `-4 -4 ${qr.length + 8} ${qr.length + 8}`, className: "shrink-0 rounded-lg bg-white p-1", shapeRendering: "crispEdges", children: /* @__PURE__ */ jsx51("path", { fill: "#000", d: qr.flatMap((row, y) => row.flatMap((dark, x) => dark ? [`M${x} ${y}h1v1h-1z`] : [])).join("") }) }) : null,
5776
+ /* @__PURE__ */ jsx51("p", { className: "text-[13px] text-[var(--chalk-soft-text)]", children: qr ? "Scan with a phone camera to join this call." : "This link is too long for a QR code. Copy the link to share it." })
5648
5777
  ] }) })
5649
5778
  ] });
5650
5779
  if (phone)
5651
- return /* @__PURE__ */ jsx52(
5780
+ return /* @__PURE__ */ jsx51(
5652
5781
  ChalkSheet,
5653
5782
  {
5654
5783
  variant: "ready",
@@ -5657,10 +5786,10 @@ function CallReady({ open, onClose, link = typeof location === "undefined" ? ""
5657
5786
  if (!value) onClose();
5658
5787
  },
5659
5788
  title: "Your call is ready",
5660
- children: /* @__PURE__ */ jsx52("section", { ref: cardRef, "aria-labelledby": id, children: content })
5789
+ children: /* @__PURE__ */ jsx51("section", { ref: cardRef, "aria-labelledby": id, children: content })
5661
5790
  }
5662
5791
  );
5663
- return present ? /* @__PURE__ */ jsx52(
5792
+ return present ? /* @__PURE__ */ jsx51(
5664
5793
  "section",
5665
5794
  {
5666
5795
  ref: cardRef,
@@ -5675,8 +5804,228 @@ function CallReady({ open, onClose, link = typeof location === "undefined" ? ""
5675
5804
  ) : null;
5676
5805
  }
5677
5806
 
5807
+ // src/components/toast-stack/ToastStack.tsx
5808
+ import { memo, useLayoutEffect as useLayoutEffect3, useRef as useRef17, useState as useState29 } from "react";
5809
+ import { flushSync as flushSync2 } from "react-dom";
5810
+
5811
+ // src/components/toast-stack/useToastCapacity.ts
5812
+ import { isValidElement as isValidElement2, useLayoutEffect as useLayoutEffect2, useState as useState28 } from "react";
5813
+ import { flushSync } from "react-dom";
5814
+
5815
+ // src/components/toast-stack/toast-layout-snapshot.ts
5816
+ function captureToastLayout(toast) {
5817
+ const snapshot = document.createElement("div");
5818
+ copyAttributes(toast, snapshot);
5819
+ for (const child of toast.childNodes) snapshot.appendChild(copyLayout(child));
5820
+ return snapshot;
5821
+ }
5822
+ function copyAttributes(source, target) {
5823
+ for (const { name, value } of source.attributes) {
5824
+ if (!/^(?:on|src|href|xlink:href|id$|name$|action$|formaction$|autoplay$|autofocus$|role$|aria-live$|aria-atomic$|tabindex$|contenteditable$)/i.test(name)) target.setAttribute(name, value);
5825
+ }
5826
+ }
5827
+ function copyLayout(node) {
5828
+ if (!(node instanceof Element)) return document.createTextNode(node instanceof Text ? node.data : "");
5829
+ const opaque = node.localName.includes("-") || /^(?:img|video|audio|canvas|iframe|object|embed|script|style|link|image|use|animate|animateMotion|animateTransform|set)$/.test(node.localName);
5830
+ if (opaque) {
5831
+ const placeholder = document.createElement("span");
5832
+ const size = node.getBoundingClientRect();
5833
+ placeholder.style.cssText = `display:inline-block;width:${size.width}px;height:${size.height}px;flex-shrink:0`;
5834
+ return placeholder;
5835
+ }
5836
+ const copy = document.createElementNS(node.namespaceURI, node.localName);
5837
+ copyAttributes(node, copy);
5838
+ for (const child of node.childNodes) copy.appendChild(copyLayout(child));
5839
+ return copy;
5840
+ }
5841
+ function measureToastLayout(stack, snapshot) {
5842
+ const probe = captureToastLayout(snapshot);
5843
+ probe.dataset.part = "toast-measurement";
5844
+ probe.inert = true;
5845
+ probe.setAttribute("aria-hidden", "true");
5846
+ Object.assign(probe.style, { position: "absolute", left: "0", top: "0", width: "max-content", visibility: "hidden", pointerEvents: "none", animation: "none", transition: "none", transform: "none" });
5847
+ stack.appendChild(probe);
5848
+ try {
5849
+ return Math.max(probe.offsetHeight, Math.ceil(probe.getBoundingClientRect().height));
5850
+ } finally {
5851
+ probe.remove();
5852
+ }
5853
+ }
5854
+
5855
+ // src/components/toast-stack/useToastCapacity.ts
5856
+ function useToastCapacity(stack, candidates, enabled, ownedIds) {
5857
+ const [fit, setFit] = useState28(null);
5858
+ let count = candidates.length;
5859
+ if (enabled) {
5860
+ count = 0;
5861
+ let used = 0;
5862
+ if (fit)
5863
+ for (const toast of candidates.slice().reverse()) {
5864
+ const measured = fit.measurements.get(toast.id);
5865
+ const height = measured && sameLayout(measured.toast, toast) ? measured.height : 48;
5866
+ const next = used + (count ? fit.gap : 0) + height;
5867
+ if (fit.width > 0 && next > fit.available) break;
5868
+ used = next;
5869
+ count += 1;
5870
+ }
5871
+ }
5872
+ const selected = candidates.slice(candidates.length - count);
5873
+ const ready = fit !== null && selected.every((toast) => {
5874
+ const measured = fit.measurements.get(toast.id);
5875
+ return measured !== void 0 && sameLayout(measured.toast, toast);
5876
+ });
5877
+ useLayoutEffect2(() => {
5878
+ const node = stack.current;
5879
+ if (!enabled || !node) return;
5880
+ const elements = [...node.children].filter((child) => child instanceof HTMLElement);
5881
+ const measure = () => {
5882
+ const style = getComputedStyle(node);
5883
+ const padding = (parseFloat(style.paddingTop) || 0) + (parseFloat(style.paddingBottom) || 0);
5884
+ const width = node.clientWidth;
5885
+ const available = Math.max(0, node.clientHeight - padding);
5886
+ const gap = parseFloat(style.rowGap) || 0;
5887
+ const mounted = selected.filter((toast) => ownedIds.includes(toast.id));
5888
+ const updates = /* @__PURE__ */ new Map();
5889
+ mounted.forEach((toast, index) => {
5890
+ const element = elements[index];
5891
+ if (!element) return;
5892
+ const height = Math.max(element.offsetHeight, Math.ceil(element.getBoundingClientRect().height));
5893
+ const old = fit?.measurements.get(toast.id);
5894
+ const markup = element.innerHTML;
5895
+ if (!old || old.height !== height || old.width !== width || old.markup !== markup || !sameLayout(old.toast, toast)) {
5896
+ updates.set(toast.id, { toast, height, width, markup, snapshot: captureToastLayout(element) });
5897
+ }
5898
+ });
5899
+ for (const toast of candidates) {
5900
+ const old = fit?.measurements.get(toast.id);
5901
+ if (old && old.width !== width && !mounted.includes(toast) && sameLayout(old.toast, toast)) {
5902
+ updates.set(toast.id, { ...old, width, height: measureToastLayout(node, old.snapshot) });
5903
+ }
5904
+ }
5905
+ setFit((previous) => {
5906
+ const measurements = new Map(previous?.measurements);
5907
+ const present = new Set(candidates.map((toast) => toast.id));
5908
+ for (const id of measurements.keys()) if (!present.has(id)) measurements.delete(id);
5909
+ const changed = measurements.size !== previous?.measurements.size || updates.size > 0;
5910
+ for (const [id, measurement] of updates) measurements.set(id, measurement);
5911
+ return !changed && previous?.width === width && previous.available === available && previous.gap === gap ? previous : { width, available, gap, measurements };
5912
+ });
5913
+ };
5914
+ measure();
5915
+ if (typeof ResizeObserver === "undefined") return;
5916
+ let active = true;
5917
+ const observer = new ResizeObserver(() => {
5918
+ if (active) flushSync(measure);
5919
+ });
5920
+ observer.observe(node);
5921
+ for (const element of elements) observer.observe(element);
5922
+ return () => {
5923
+ active = false;
5924
+ observer.disconnect();
5925
+ };
5926
+ });
5927
+ return { count, ready: !enabled || ready };
5928
+ }
5929
+ function sameLayout(previous, next) {
5930
+ return previous.id === next.id && noticeIdentity(previous) === noticeIdentity(next) && previous.action?.label === next.action?.label && sameLayoutValue(previous.message, next.message) && sameLayoutValue(previous.icon, next.icon);
5931
+ }
5932
+ function sameLayoutValue(previous, next, seen = /* @__PURE__ */ new WeakMap()) {
5933
+ if (Object.is(previous, next)) return true;
5934
+ if (typeof previous === "function" && typeof next === "function") return true;
5935
+ if (previous === null || next === null || typeof previous !== "object" || typeof next !== "object") return false;
5936
+ if (isValidElement2(previous) || isValidElement2(next)) return isValidElement2(previous) && isValidElement2(next) && previous.type === next.type && sameLayoutValue(previous.props, next.props, seen);
5937
+ if (Object.getPrototypeOf(previous) !== Object.getPrototypeOf(next)) return false;
5938
+ if (seen.get(previous) === next) return true;
5939
+ seen.set(previous, next);
5940
+ const keys = Object.keys(previous).filter((key) => key !== "ref");
5941
+ return keys.length === Object.keys(next).filter((key) => key !== "ref").length && keys.every((key) => Object.hasOwn(next, key) && sameLayoutValue(Reflect.get(previous, key), Reflect.get(next, key), seen));
5942
+ }
5943
+
5944
+ // src/components/toast-stack/ToastStack.tsx
5945
+ import { Fragment as Fragment21, jsx as jsx52, jsxs as jsxs34 } from "react/jsx-runtime";
5946
+ var FOLD_BELOW_PX = 620;
5947
+ var ToastStack = memo(function ToastStack2({ toasts, onDismiss, maxVisible = 3, className, onFold }) {
5948
+ const stack = useRef17(null);
5949
+ const short = useShortCall(stack, onFold !== void 0);
5950
+ const candidates = maxVisible > 0 ? toasts.slice(-maxVisible) : [];
5951
+ const [ownedIds, setOwnedIds] = useState29([]);
5952
+ const fold = useRef17(onFold);
5953
+ const capacity = useToastCapacity(stack, candidates, onFold !== void 0 && short === false, ownedIds);
5954
+ const selected = onFold !== void 0 && short !== false ? [] : candidates.slice(candidates.length - capacity.count);
5955
+ const shown = onFold || fold.current ? selected.filter((toast) => ownedIds.includes(toast.id)) : selected;
5956
+ const folded = onFold !== void 0 && short !== null ? toasts.filter((toast) => !selected.includes(toast)) : [];
5957
+ const foldedIds = folded.map((toast) => toast.id);
5958
+ const announced = useRef17(/* @__PURE__ */ new Map());
5959
+ const wasAnnounced = (toast) => announced.current.get(toast.id) === noticeIdentity(toast);
5960
+ const [unheard, setUnheard] = useState29([]);
5961
+ const reported = useRef17([]);
5962
+ const reporting = useRef17(false);
5963
+ useLayoutEffect3(() => {
5964
+ const replacement = fold.current !== onFold;
5965
+ if (replacement) {
5966
+ if (!onFold) fold.current?.([]);
5967
+ fold.current = onFold;
5968
+ }
5969
+ const selectedIds = selected.map((toast) => toast.id);
5970
+ setOwnedIds((previous) => sameIds(previous, selectedIds) ? previous : selectedIds);
5971
+ const unchanged = sameIds(reported.current, foldedIds);
5972
+ if (!unchanged) reported.current = foldedIds;
5973
+ if (!unchanged || replacement && !reporting.current) {
5974
+ reporting.current = true;
5975
+ queueMicrotask(() => {
5976
+ reporting.current = false;
5977
+ });
5978
+ fold.current?.(reported.current);
5979
+ }
5980
+ if (!capacity.ready) return;
5981
+ const present = new Set(toasts.map((toast) => toast.id));
5982
+ for (const id of announced.current.keys()) if (!present.has(id)) announced.current.delete(id);
5983
+ const eligible = onFold ? toasts : shown;
5984
+ const fresh = eligible.filter((toast) => !wasAnnounced(toast)).map((toast) => toast.id);
5985
+ for (const toast of eligible) announced.current.set(toast.id, noticeIdentity(toast));
5986
+ const kept = unheard.filter((id) => eligible.some((toast) => toast.id === id));
5987
+ if (fresh.length) setUnheard(fresh);
5988
+ else if (kept.length < unheard.length) setUnheard(kept);
5989
+ });
5990
+ useLayoutEffect3(
5991
+ () => () => {
5992
+ reported.current = [];
5993
+ fold.current?.([]);
5994
+ },
5995
+ []
5996
+ );
5997
+ const announcing = (onFold ? toasts : shown).filter((toast) => unheard.includes(toast.id));
5998
+ return /* @__PURE__ */ jsxs34(Fragment21, { children: [
5999
+ /* @__PURE__ */ jsx52("div", { ref: stack, "data-part": "toast-stack", className: cn("chalk-toasts", className), children: shown.map((toast) => /* @__PURE__ */ jsx52(ChalkToast, { ...toast, announce: toast.announcementText === void 0 && noticeAnnouncement(toast) === "", onDismiss: () => onDismiss(toast.id) }, toast.id)) }),
6000
+ /* @__PURE__ */ jsxs34("div", { "data-part": "notice-announcer", className: "sr-only", children: [
6001
+ /* @__PURE__ */ jsx52("div", { "aria-live": "polite", children: announcing.filter((toast) => toast.type !== "error").map((toast) => /* @__PURE__ */ jsx52("div", { children: noticeAnnouncement(toast) }, JSON.stringify([toast.id, noticeIdentity(toast)]))) }),
6002
+ /* @__PURE__ */ jsx52("div", { "aria-live": "assertive", children: announcing.filter((toast) => toast.type === "error").map((toast) => /* @__PURE__ */ jsx52("div", { children: noticeAnnouncement(toast) }, JSON.stringify([toast.id, noticeIdentity(toast)]))) })
6003
+ ] })
6004
+ ] });
6005
+ });
6006
+ function useShortCall(stack, enabled) {
6007
+ const [short, setShort] = useState29(null);
6008
+ useLayoutEffect3(() => {
6009
+ if (!enabled) {
6010
+ setShort(null);
6011
+ return;
6012
+ }
6013
+ const call = stack.current?.closest("[data-part='call-frame']");
6014
+ const read3 = () => setShort(Boolean(call && call.clientHeight > 0 && call.clientHeight < FOLD_BELOW_PX));
6015
+ read3();
6016
+ if (!call || typeof ResizeObserver === "undefined") return;
6017
+ const observer = new ResizeObserver(() => flushSync2(read3));
6018
+ observer.observe(call);
6019
+ return () => observer.disconnect();
6020
+ }, [stack, enabled]);
6021
+ return short;
6022
+ }
6023
+ function sameIds(a, b) {
6024
+ return a.length === b.length && a.every((id, index) => id === b[index]);
6025
+ }
6026
+
5678
6027
  // src/internal/call-motion.ts
5679
- import { useEffect as useEffect20, useState as useState27 } from "react";
6028
+ import { useEffect as useEffect20, useState as useState30 } from "react";
5680
6029
  var moves = /* @__PURE__ */ new WeakMap();
5681
6030
  function captureFlip(elements) {
5682
6031
  const previous = Array.from(elements, (element) => ({ element, box: element.getBoundingClientRect() }));
@@ -5702,8 +6051,9 @@ function playFlip(previous) {
5702
6051
  }
5703
6052
  return next.map(({ element, next: box }) => ({ element, box }));
5704
6053
  }
6054
+ var ACTIVE_LAYERS = '[role="menu"],[role="dialog"],[data-part="toast"],[data-part="notice-button"],[data-chalk-drawer]';
5705
6055
  function useIdleChrome(root, enabled) {
5706
- const [idle, setIdle] = useState27(false);
6056
+ const [idle, setIdle] = useState30(false);
5707
6057
  const fine = useMediaQuery("(pointer:fine) and (min-width:641px)");
5708
6058
  useEffect20(() => {
5709
6059
  const element = root.current;
@@ -5713,7 +6063,7 @@ function useIdleChrome(root, enabled) {
5713
6063
  setIdle(false);
5714
6064
  clearTimeout(timer);
5715
6065
  timer = setTimeout(() => {
5716
- const layer = element.querySelector('[role="menu"],[role="dialog"],[data-part="toast"],[data-chalk-drawer]');
6066
+ const layer = element.querySelector(ACTIVE_LAYERS);
5717
6067
  const focused = element.querySelector(".chalk-frame-header :focus-visible,.chalk-frame-dock :focus-visible");
5718
6068
  if (!layer && !focused) setIdle(true);
5719
6069
  else poke();
@@ -5721,8 +6071,7 @@ function useIdleChrome(root, enabled) {
5721
6071
  };
5722
6072
  for (const event of ["pointermove", "pointerdown", "keydown", "focusin"]) element.addEventListener(event, poke);
5723
6073
  const observer = new MutationObserver((records) => {
5724
- const selector = '[role="menu"],[role="dialog"],[data-part="toast"],[data-chalk-drawer]';
5725
- if (records.some((record) => [...record.addedNodes, ...record.removedNodes].some((node) => node instanceof Element && (node.matches(selector) || node.querySelector(selector))))) poke();
6074
+ if (records.some((record) => [...record.addedNodes, ...record.removedNodes].some((node) => node instanceof Element && (node.matches(ACTIVE_LAYERS) || node.querySelector(ACTIVE_LAYERS))))) poke();
5726
6075
  });
5727
6076
  observer.observe(element, { childList: true, subtree: true });
5728
6077
  poke();
@@ -5736,7 +6085,7 @@ function useIdleChrome(root, enabled) {
5736
6085
  }
5737
6086
 
5738
6087
  // src/components/space-header/SpaceHeader.tsx
5739
- import React14, { useCallback as useCallback6, useRef as useRef20, useState as useState29 } from "react";
6088
+ import React14, { useCallback as useCallback6, useRef as useRef20, useState as useState32 } from "react";
5740
6089
 
5741
6090
  // src/internal/useSoundsSetting.ts
5742
6091
  import { useSyncExternalStore as useSyncExternalStore3 } from "react";
@@ -5788,7 +6137,7 @@ function LayoutMenu({ layout, onLayoutChange }) {
5788
6137
 
5789
6138
  // src/components/space-header/CallDetails.tsx
5790
6139
  import { Dialog as Dialog2 } from "@base-ui/react/dialog";
5791
- import { useEffect as useEffect21, useRef as useRef18, useState as useState28 } from "react";
6140
+ import { useEffect as useEffect21, useRef as useRef18, useState as useState31 } from "react";
5792
6141
 
5793
6142
  // src/components/space-header/share-qr.ts
5794
6143
  function shareQr(text) {
@@ -5918,7 +6267,7 @@ function shareQr(text) {
5918
6267
  }
5919
6268
 
5920
6269
  // src/components/space-header/CallDetails.tsx
5921
- import { Fragment as Fragment20, jsx as jsx54, jsxs as jsxs34 } from "react/jsx-runtime";
6270
+ import { Fragment as Fragment22, jsx as jsx54, jsxs as jsxs35 } from "react/jsx-runtime";
5922
6271
  var CALL_SHORTCUTS = [
5923
6272
  ["Mute or unmute", "\u2318 D"],
5924
6273
  ["Turn camera on or off", "\u2318 E"],
@@ -5936,17 +6285,21 @@ function CallDialog({
5936
6285
  onClose,
5937
6286
  onReportProblem,
5938
6287
  diagnostics,
5939
- returnFocus
6288
+ returnFocus,
6289
+ focusScope,
6290
+ unavailableReason
5940
6291
  }) {
5941
6292
  const container = useRef18(null);
5942
6293
  const reportInput = useRef18(null);
5943
- const [text, setText] = useState28("");
5944
- const [includeDiagnostics, setIncludeDiagnostics] = useState28(true);
5945
- const [status, setStatus] = useState28("idle");
5946
- const [error, setError] = useState28("");
6294
+ const reportOpener = useRef18(null);
6295
+ const [text, setText] = useState31("");
6296
+ const [includeDiagnostics, setIncludeDiagnostics] = useState31(false);
6297
+ const [status, setStatus] = useState31("idle");
6298
+ const [error, setError] = useState31("");
5947
6299
  useEffect21(() => {
5948
6300
  if (open && kind === "report") {
5949
6301
  setText("");
6302
+ setIncludeDiagnostics(false);
5950
6303
  setError("");
5951
6304
  setStatus("idle");
5952
6305
  }
@@ -5962,68 +6315,107 @@ function CallDialog({
5962
6315
  Dialog2.Root,
5963
6316
  {
5964
6317
  open,
5965
- onOpenChange: (open2) => {
6318
+ onOpenChange: (open2, event) => {
6319
+ if (!open2 && status === "sending") {
6320
+ event.cancel();
6321
+ return;
6322
+ }
5966
6323
  if (!open2) onClose();
5967
6324
  },
5968
- children: /* @__PURE__ */ jsxs34(Dialog2.Portal, { container, children: [
6325
+ children: /* @__PURE__ */ jsxs35(Dialog2.Portal, { container, children: [
5969
6326
  /* @__PURE__ */ jsx54(Dialog2.Backdrop, { className: "chalk-shell-modal-backdrop" }),
5970
- /* @__PURE__ */ jsxs34(Dialog2.Popup, { initialFocus: kind === "report" ? reportInput : void 0, finalFocus: returnFocus, className: "chalk-shell-modal", children: [
5971
- kind === "report" && status === "sent" ? null : /* @__PURE__ */ jsxs34("div", { className: "chalk-shell-modal-heading", children: [
5972
- /* @__PURE__ */ jsx54(Dialog2.Title, { children: kind === "shortcuts" ? "Keyboard shortcuts" : "Report a problem" }),
5973
- /* @__PURE__ */ jsx54(
5974
- Dialog2.Close,
5975
- {
5976
- render: /* @__PURE__ */ jsx54(ChalkIconButton, { "aria-label": "Close", size: "sm", children: /* @__PURE__ */ jsx54("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx54("path", { d: "m6 6 12 12M6 18 18 6" }) }) })
5977
- }
5978
- )
5979
- ] }),
5980
- kind === "shortcuts" ? /* @__PURE__ */ jsx54("dl", { className: "chalk-shell-shortcuts", children: CALL_SHORTCUTS.map(([label, keys]) => /* @__PURE__ */ jsxs34("div", { style: { display: "contents" }, children: [
5981
- /* @__PURE__ */ jsx54("dt", { children: label }),
5982
- /* @__PURE__ */ jsx54("dd", { children: keys.split(" ").map((key, index) => /* @__PURE__ */ jsx54("kbd", { children: key }, `${key}${index}`)) })
5983
- ] }, label)) }) : status === "sent" ? /* @__PURE__ */ jsxs34("div", { className: "chalk-shell-report-sent", role: "status", children: [
5984
- /* @__PURE__ */ jsx54("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx54("path", { d: "m5 12 4 4L19 6" }) }),
5985
- /* @__PURE__ */ jsx54(Dialog2.Title, { children: "Sent" }),
5986
- /* @__PURE__ */ jsx54("p", { children: "Thanks. We\u2019ll look into it." }),
5987
- /* @__PURE__ */ jsx54(ChalkButton, { variant: "solid", onClick: onClose, children: "Done" })
5988
- ] }) : /* @__PURE__ */ jsxs34(
5989
- "form",
5990
- {
5991
- className: "chalk-shell-report",
5992
- onSubmit: (event) => {
5993
- event.preventDefault();
5994
- if (!text.trim() || !onReportProblem || status === "sending") return;
5995
- setStatus("sending");
5996
- setError("");
5997
- void Promise.resolve().then(() => onReportProblem({ description: text.trim(), diagnostics: includeDiagnostics ? diagnostics : void 0 })).then(
5998
- () => setStatus("sent"),
5999
- () => {
6000
- setStatus("idle");
6001
- setError("Your report could not be sent. Try again.");
6327
+ /* @__PURE__ */ jsxs35(
6328
+ Dialog2.Popup,
6329
+ {
6330
+ initialFocus: kind === "report" ? () => {
6331
+ const opener = reportInput.current?.ownerDocument.activeElement;
6332
+ reportOpener.current = opener instanceof HTMLElement && focusScope.current?.contains(opener) ? opener : null;
6333
+ return reportInput.current;
6334
+ } : void 0,
6335
+ finalFocus: kind === "report" ? () => reportFocusTarget(focusScope.current, reportOpener.current) : returnFocus,
6336
+ className: "chalk-shell-modal",
6337
+ children: [
6338
+ kind === "report" && status === "sent" ? null : /* @__PURE__ */ jsxs35("div", { className: "chalk-shell-modal-heading", children: [
6339
+ /* @__PURE__ */ jsx54(Dialog2.Title, { children: kind === "shortcuts" ? "Keyboard shortcuts" : "Report a problem" }),
6340
+ /* @__PURE__ */ jsx54(
6341
+ Dialog2.Close,
6342
+ {
6343
+ disabled: status === "sending",
6344
+ render: /* @__PURE__ */ jsx54(ChalkIconButton, { "aria-label": "Close", size: "sm", children: /* @__PURE__ */ jsx54("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx54("path", { d: "m6 6 12 12M6 18 18 6" }) }) })
6002
6345
  }
6003
- );
6004
- },
6005
- children: [
6006
- /* @__PURE__ */ jsx54("label", { htmlFor: "chalk-problem", children: "What went wrong?" }),
6007
- /* @__PURE__ */ jsx54("textarea", { ref: reportInput, id: "chalk-problem", rows: 4, required: true, disabled: status === "sending", value: text, onChange: (event) => setText(event.target.value), placeholder: "Tell us what happened\u2026" }),
6008
- /* @__PURE__ */ jsxs34("label", { className: "chalk-shell-report-check", children: [
6009
- /* @__PURE__ */ jsx54("input", { type: "checkbox", disabled: status === "sending", checked: includeDiagnostics, onChange: (event) => setIncludeDiagnostics(event.target.checked) }),
6010
- " Include diagnostics"
6011
- ] }),
6012
- error ? /* @__PURE__ */ jsx54("p", { role: "alert", children: error }) : null,
6013
- /* @__PURE__ */ jsxs34("div", { className: "chalk-shell-report-actions", children: [
6014
- /* @__PURE__ */ jsx54(ChalkButton, { onClick: onClose, disabled: status === "sending", children: "Cancel" }),
6015
- /* @__PURE__ */ jsx54(ChalkButton, { type: "submit", variant: "solid", disabled: !text.trim() || status === "sending", children: status === "sending" ? "Sending\u2026" : "Send" })
6016
- ] })
6017
- ]
6018
- }
6019
- )
6020
- ] })
6346
+ )
6347
+ ] }),
6348
+ kind === "shortcuts" ? /* @__PURE__ */ jsx54("dl", { className: "chalk-shell-shortcuts", children: CALL_SHORTCUTS.map(([label, keys]) => /* @__PURE__ */ jsxs35("div", { style: { display: "contents" }, children: [
6349
+ /* @__PURE__ */ jsx54("dt", { children: label }),
6350
+ /* @__PURE__ */ jsx54("dd", { children: keys.split(" ").map((key, index) => /* @__PURE__ */ jsx54("kbd", { children: key }, `${key}${index}`)) })
6351
+ ] }, label)) }) : status === "sent" ? /* @__PURE__ */ jsxs35("div", { className: "chalk-shell-report-sent", role: "status", children: [
6352
+ /* @__PURE__ */ jsx54("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx54("path", { d: "m5 12 4 4L19 6" }) }),
6353
+ /* @__PURE__ */ jsx54(Dialog2.Title, { children: "Sent" }),
6354
+ /* @__PURE__ */ jsx54("p", { children: "Thanks. We\u2019ll look into it." }),
6355
+ /* @__PURE__ */ jsx54(ChalkButton, { variant: "solid", onClick: onClose, children: "Done" })
6356
+ ] }) : /* @__PURE__ */ jsxs35(
6357
+ "form",
6358
+ {
6359
+ className: "chalk-shell-report",
6360
+ onSubmit: (event) => {
6361
+ event.preventDefault();
6362
+ if (!text.trim() || !onReportProblem || status === "sending" || unavailableReason) return;
6363
+ setStatus("sending");
6364
+ setError("");
6365
+ void Promise.resolve().then(() => onReportProblem({ description: text.trim(), diagnostics: includeDiagnostics && diagnostics.trim() ? diagnostics : void 0 })).then(
6366
+ () => setStatus("sent"),
6367
+ () => {
6368
+ setStatus("idle");
6369
+ setError("Your report could not be sent. Try again.");
6370
+ }
6371
+ );
6372
+ },
6373
+ children: [
6374
+ /* @__PURE__ */ jsx54("label", { htmlFor: "chalk-problem", children: "What went wrong?" }),
6375
+ /* @__PURE__ */ jsx54("textarea", { ref: reportInput, id: "chalk-problem", rows: 4, required: true, disabled: status === "sending", value: text, onChange: (event) => setText(Array.from(event.target.value).slice(0, 1e3).join("")), placeholder: "Tell us what happened\u2026" }),
6376
+ diagnostics.trim() ? /* @__PURE__ */ jsxs35("label", { className: "chalk-shell-report-check", children: [
6377
+ /* @__PURE__ */ jsx54("input", { type: "checkbox", disabled: status === "sending", checked: includeDiagnostics, onChange: (event) => setIncludeDiagnostics(event.target.checked) }),
6378
+ " Include diagnostics"
6379
+ ] }) : null,
6380
+ error ? /* @__PURE__ */ jsx54("p", { role: "alert", children: error }) : null,
6381
+ unavailableReason ? /* @__PURE__ */ jsx54("p", { role: "alert", children: unavailableReason }) : null,
6382
+ /* @__PURE__ */ jsxs35("div", { className: "chalk-shell-report-actions", children: [
6383
+ /* @__PURE__ */ jsx54(ChalkButton, { onClick: onClose, disabled: status === "sending", children: "Cancel" }),
6384
+ /* @__PURE__ */ jsx54(ChalkButton, { type: "submit", variant: "solid", disabled: !text.trim() || status === "sending" || Boolean(unavailableReason), children: status === "sending" ? "Sending\u2026" : "Send" })
6385
+ ] })
6386
+ ]
6387
+ }
6388
+ )
6389
+ ]
6390
+ }
6391
+ )
6021
6392
  ] })
6022
6393
  }
6023
6394
  )
6024
6395
  }
6025
6396
  );
6026
6397
  }
6398
+ function reportFocusTarget(scope, opener) {
6399
+ if (usableReportFocusTarget(opener)) return opener;
6400
+ if (!scope?.isConnected) return false;
6401
+ for (const selector of ['button[data-part="call-name"]', '[data-part="status-actions"] button', 'main, [role="main"]']) {
6402
+ for (const target of [scope, ...scope.querySelectorAll(selector)]) {
6403
+ if (!target.matches(selector) || !usableReportFocusTarget(target)) continue;
6404
+ if (target.matches('main, [role="main"]')) target.tabIndex = -1;
6405
+ return target;
6406
+ }
6407
+ }
6408
+ return false;
6409
+ }
6410
+ function usableReportFocusTarget(target) {
6411
+ if (!target?.isConnected || target.matches(':disabled, [aria-disabled="true"]') || target.closest("[inert]") || target.getClientRects().length === 0) return false;
6412
+ const visibility = getComputedStyle(target).visibility;
6413
+ if (visibility === "hidden" || visibility === "collapse") return false;
6414
+ for (let element = target; element; element = element.parentElement) {
6415
+ if (getComputedStyle(element).opacity === "0") return false;
6416
+ }
6417
+ return true;
6418
+ }
6027
6419
  function CallDetails({
6028
6420
  shareUrl,
6029
6421
  spaceName,
@@ -6038,46 +6430,46 @@ function CallDetails({
6038
6430
  }) {
6039
6431
  const link = shareUrl ?? (typeof location === "undefined" ? "" : location.href);
6040
6432
  const qr = shareQr(link);
6041
- const [details, setDetails] = useState28(false);
6042
- return /* @__PURE__ */ jsxs34("div", { className: "chalk-details", children: [
6043
- /* @__PURE__ */ jsxs34("div", { className: "chalk-shell-details-heading", children: [
6433
+ const [details, setDetails] = useState31(false);
6434
+ return /* @__PURE__ */ jsxs35("div", { className: "chalk-details", children: [
6435
+ /* @__PURE__ */ jsxs35("div", { className: "chalk-shell-details-heading", children: [
6044
6436
  /* @__PURE__ */ jsx54("h2", { children: spaceName }),
6045
6437
  /* @__PURE__ */ jsx54(ChalkIconButton, { "aria-label": "Close", size: "sm", onClick: onClose, children: /* @__PURE__ */ jsx54("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx54("path", { d: "m6 6 12 12M6 18 18 6" }) }) })
6046
6438
  ] }),
6047
- /* @__PURE__ */ jsxs34("section", { className: "chalk-shell-section", children: [
6439
+ /* @__PURE__ */ jsxs35("section", { className: "chalk-shell-section", children: [
6048
6440
  /* @__PURE__ */ jsx54("h3", { className: "chalk-shell-label", children: "Share" }),
6049
- /* @__PURE__ */ jsxs34("div", { className: "chalk-shell-link", children: [
6441
+ /* @__PURE__ */ jsxs35("div", { className: "chalk-shell-link", children: [
6050
6442
  /* @__PURE__ */ jsx54("span", { dir: "ltr", children: link.replace(/^https?:\/\//, "") }),
6051
6443
  /* @__PURE__ */ jsx54(ChalkCopyButton, { text: link, label: "Copy link" })
6052
6444
  ] }),
6053
- qr ? /* @__PURE__ */ jsxs34("div", { className: "chalk-shell-qr-row", children: [
6054
- /* @__PURE__ */ jsxs34("svg", { "data-part": "share-qr", className: "chalk-shell-qr", viewBox: `0 0 ${qr.length + 8} ${qr.length + 8}`, role: "img", "aria-label": "Scan to join this call", shapeRendering: "crispEdges", children: [
6445
+ qr ? /* @__PURE__ */ jsxs35("div", { className: "chalk-shell-qr-row", children: [
6446
+ /* @__PURE__ */ jsxs35("svg", { "data-part": "share-qr", className: "chalk-shell-qr", viewBox: `0 0 ${qr.length + 8} ${qr.length + 8}`, role: "img", "aria-label": "Scan to join this call", shapeRendering: "crispEdges", children: [
6055
6447
  /* @__PURE__ */ jsx54("rect", { width: "100%", height: "100%", fill: "white" }),
6056
6448
  /* @__PURE__ */ jsx54("path", { d: qr.flatMap((row, y) => row.flatMap((dark, x) => dark ? [`M${x + 4},${y + 4}h1v1h-1z`] : [])).join("") })
6057
6449
  ] }),
6058
6450
  /* @__PURE__ */ jsx54("p", { children: "Scan with a phone camera to join this call." })
6059
6451
  ] }) : null
6060
6452
  ] }),
6061
- diagnostics.length ? /* @__PURE__ */ jsxs34("section", { className: "chalk-shell-section", children: [
6062
- /* @__PURE__ */ jsxs34("button", { type: "button", className: "chalk-shell-detail-action chalk-shell-connection", "aria-expanded": details, onClick: () => setDetails(!details), children: [
6453
+ diagnostics.length ? /* @__PURE__ */ jsxs35("section", { className: "chalk-shell-section", children: [
6454
+ /* @__PURE__ */ jsxs35("button", { type: "button", className: "chalk-shell-detail-action chalk-shell-connection", "aria-expanded": details, onClick: () => setDetails(!details), children: [
6063
6455
  /* @__PURE__ */ jsx54("span", { className: "chalk-shell-connection-bars", "aria-hidden": "true", children: [6, 9, 12, 15].map((height) => /* @__PURE__ */ jsx54("i", { style: { height } }, height)) }),
6064
- /* @__PURE__ */ jsxs34("span", { children: [
6456
+ /* @__PURE__ */ jsxs35("span", { children: [
6065
6457
  "Connection: ",
6066
6458
  diagnostics[0]?.value.toLowerCase()
6067
6459
  ] }),
6068
6460
  /* @__PURE__ */ jsx54("span", { className: "chalk-shell-connection-details", children: details ? "Hide details" : "Details" }),
6069
6461
  /* @__PURE__ */ jsx54(ShellIcon, { name: details ? "up2" : "down", size: 18 })
6070
6462
  ] }),
6071
- details ? /* @__PURE__ */ jsxs34(Fragment20, { children: [
6072
- /* @__PURE__ */ jsx54("dl", { className: "chalk-shell-diagnostics", children: diagnostics.map(({ label, value }) => /* @__PURE__ */ jsxs34("div", { style: { display: "contents" }, children: [
6463
+ details ? /* @__PURE__ */ jsxs35(Fragment22, { children: [
6464
+ /* @__PURE__ */ jsx54("dl", { className: "chalk-shell-diagnostics", children: diagnostics.map(({ label, value }) => /* @__PURE__ */ jsxs35("div", { style: { display: "contents" }, children: [
6073
6465
  /* @__PURE__ */ jsx54("dt", { children: label }),
6074
6466
  /* @__PURE__ */ jsx54("dd", { children: value })
6075
6467
  ] }, label)) }),
6076
6468
  /* @__PURE__ */ jsx54("div", { className: "chalk-shell-diagnostics-copy", children: /* @__PURE__ */ jsx54(ChalkCopyButton, { text: diagnostics.map(({ label, value }) => `${label}: ${value}`).join("\n"), label: "Copy diagnostics", announcement: "Diagnostics copied", showLabel: true }) })
6077
6469
  ] }) : null
6078
6470
  ] }) : null,
6079
- /* @__PURE__ */ jsxs34("section", { className: "chalk-shell-section", children: [
6080
- /* @__PURE__ */ jsxs34("div", { className: "chalk-details-row", children: [
6471
+ /* @__PURE__ */ jsxs35("section", { className: "chalk-shell-section", children: [
6472
+ /* @__PURE__ */ jsxs35("div", { className: "chalk-details-row", children: [
6081
6473
  /* @__PURE__ */ jsx54("span", { children: "Appearance" }),
6082
6474
  /* @__PURE__ */ jsx54(
6083
6475
  ChalkSegmentedControl,
@@ -6093,15 +6485,15 @@ function CallDetails({
6093
6485
  }
6094
6486
  )
6095
6487
  ] }),
6096
- /* @__PURE__ */ jsxs34("button", { type: "button", role: "switch", "aria-label": "Sounds", "aria-checked": sounds, className: "chalk-shell-detail-action", onClick: () => onSoundsChange(!sounds), children: [
6488
+ /* @__PURE__ */ jsxs35("button", { type: "button", role: "switch", "aria-label": "Sounds", "aria-checked": sounds, className: "chalk-shell-detail-action", onClick: () => onSoundsChange(!sounds), children: [
6097
6489
  /* @__PURE__ */ jsx54("span", { children: "Sounds" }),
6098
6490
  /* @__PURE__ */ jsx54("span", { className: "chalk-switch", "aria-checked": sounds, "aria-hidden": "true", children: /* @__PURE__ */ jsx54("span", {}) })
6099
6491
  ] }),
6100
- /* @__PURE__ */ jsxs34("button", { type: "button", className: "chalk-shell-detail-action", "aria-haspopup": "dialog", onClick: () => onDialog("shortcuts"), children: [
6492
+ /* @__PURE__ */ jsxs35("button", { type: "button", className: "chalk-shell-detail-action", "aria-haspopup": "dialog", onClick: () => onDialog("shortcuts"), children: [
6101
6493
  "Keyboard shortcuts",
6102
6494
  /* @__PURE__ */ jsx54(ShellIcon, { name: "right", size: 18 })
6103
6495
  ] }),
6104
- onReportProblem ? /* @__PURE__ */ jsxs34("button", { type: "button", className: "chalk-shell-detail-action", "aria-haspopup": "dialog", onClick: () => onDialog("report"), children: [
6496
+ onReportProblem ? /* @__PURE__ */ jsxs35("button", { type: "button", className: "chalk-shell-detail-action", "aria-haspopup": "dialog", onClick: () => onDialog("report"), children: [
6105
6497
  "Report a problem",
6106
6498
  /* @__PURE__ */ jsx54(ShellIcon, { name: "right", size: 18 })
6107
6499
  ] }) : null
@@ -6181,18 +6573,19 @@ function formatDuration(seconds) {
6181
6573
  }
6182
6574
 
6183
6575
  // src/components/space-header/SpaceHeader.tsx
6184
- import { Fragment as Fragment21, jsx as jsx55, jsxs as jsxs35 } from "react/jsx-runtime";
6576
+ import { Fragment as Fragment23, jsx as jsx55, jsxs as jsxs36 } from "react/jsx-runtime";
6185
6577
  var SpaceHeader = React14.memo((props) => {
6186
6578
  const { spaceName, duration = 0, isRecording = false, layout = "focus", onLayoutChange, className, weakConnection } = props;
6187
6579
  const [sounds, setSounds] = useSoundsSetting();
6188
6580
  const triggerRef = useRef20(null);
6189
- const [open, setOpen] = useState29(false);
6190
- const [dialog, setDialog] = useState29({ kind: "shortcuts", open: false });
6581
+ const focusScope = useRef20(null);
6582
+ const [open, setOpen] = useState32(false);
6583
+ const [dialog, setDialog] = useState32({ kind: "shortcuts", open: false });
6191
6584
  const showShortcuts = useCallback6(() => setDialog({ kind: "shortcuts", open: true }), []);
6192
6585
  useCallShortcuts(props.shortcuts, showShortcuts);
6193
6586
  const phone = useMediaQuery("(max-width: 640px)");
6194
6587
  const chevron = /* @__PURE__ */ jsx55("svg", { width: phone ? "16" : "14", height: phone ? "16" : "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", "aria-hidden": "true", children: /* @__PURE__ */ jsx55("path", { d: "m6 9 6 6 6-6" }) });
6195
- const trigger = /* @__PURE__ */ jsxs35("button", { ref: triggerRef, type: "button", "data-part": "call-name", "aria-label": "Call details", className: "chalk-call-name", children: [
6588
+ const trigger = /* @__PURE__ */ jsxs36("button", { ref: triggerRef, type: "button", "data-part": "call-name", "aria-label": "Call details", className: "chalk-call-name", children: [
6196
6589
  /* @__PURE__ */ jsx55("span", { dir: "auto", children: spaceName }),
6197
6590
  /* @__PURE__ */ jsx55("span", { "data-part": "call-timer", className: "chalk-call-timer", children: formatDuration(duration) }),
6198
6591
  chevron
@@ -6201,26 +6594,28 @@ var SpaceHeader = React14.memo((props) => {
6201
6594
  CallDetails,
6202
6595
  {
6203
6596
  ...props,
6597
+ onReportProblem: props.reportUnavailableReason ? void 0 : props.onReportProblem,
6204
6598
  onClose: () => setOpen(false),
6205
6599
  sounds,
6206
6600
  onSoundsChange: setSounds,
6207
6601
  onDialog: (kind) => {
6208
6602
  setOpen(false);
6209
- setDialog({ kind, open: true });
6603
+ if (kind === "report" && props.onOpenReportProblem) props.onOpenReportProblem();
6604
+ else setDialog({ kind, open: true });
6210
6605
  }
6211
6606
  }
6212
6607
  );
6213
- return /* @__PURE__ */ jsxs35("header", { "data-part": "header-region", className: cn("chalk-header", className), role: "banner", children: [
6608
+ return /* @__PURE__ */ jsxs36("header", { ref: focusScope, "data-part": "header-region", className: cn("chalk-header", className), role: "banner", children: [
6214
6609
  /* @__PURE__ */ jsx55(ShellStyles, {}),
6215
- /* @__PURE__ */ jsxs35("div", { className: "chalk-header-title flex min-w-0 items-center gap-3", children: [
6216
- phone ? /* @__PURE__ */ jsxs35(Fragment21, { children: [
6217
- /* @__PURE__ */ jsxs35("button", { ref: triggerRef, type: "button", "data-call-link-trigger": true, "data-part": "call-name", className: "chalk-call-name chalk-call-name-phone", "aria-label": "Call details", "aria-haspopup": "dialog", "aria-expanded": open, onClick: () => setOpen(true), children: [
6610
+ /* @__PURE__ */ jsxs36("div", { className: "chalk-header-title flex min-w-0 items-center gap-3", children: [
6611
+ phone ? /* @__PURE__ */ jsxs36(Fragment23, { children: [
6612
+ /* @__PURE__ */ jsxs36("button", { ref: triggerRef, type: "button", "data-call-link-trigger": true, "data-part": "call-name", className: "chalk-call-name chalk-call-name-phone", "aria-label": "Call details", "aria-haspopup": "dialog", "aria-expanded": open, onClick: () => setOpen(true), children: [
6218
6613
  /* @__PURE__ */ jsx55("span", { dir: "auto", children: spaceName }),
6219
6614
  chevron
6220
6615
  ] }),
6221
6616
  /* @__PURE__ */ jsx55("span", { "data-part": "call-timer", className: "chalk-call-timer", children: formatDuration(duration) }),
6222
- /* @__PURE__ */ jsxs35(ChalkSheet, { variant: "details", open, onOpenChange: setOpen, title: spaceName, accessibleLabel: "Call details", children: [
6223
- props.onCallLinkToggle ? /* @__PURE__ */ jsxs35(
6617
+ /* @__PURE__ */ jsxs36(ChalkSheet, { variant: "details", open, onOpenChange: setOpen, title: spaceName, accessibleLabel: "Call details", children: [
6618
+ props.onCallLinkToggle ? /* @__PURE__ */ jsxs36(
6224
6619
  "button",
6225
6620
  {
6226
6621
  type: "button",
@@ -6240,46 +6635,59 @@ var SpaceHeader = React14.memo((props) => {
6240
6635
  ] })
6241
6636
  ] }) : /* @__PURE__ */ jsx55(ChalkPopover, { open, onOpenChange: setOpen, tooltip: "Call details", trigger, label: "Call details", className: "chalk-call-details", alignOffset: -2, children: content }),
6242
6637
  weakConnection ? /* @__PURE__ */ jsx55("span", { className: "chalk-shell-quality", role: "img", "aria-label": "Your connection is weak", children: [5, 8, 11, 15].map((height) => /* @__PURE__ */ jsx55("i", { style: { height } }, height)) }) : null,
6243
- isRecording ? /* @__PURE__ */ jsxs35("span", { className: "chalk-shell-recording", children: [
6638
+ isRecording ? /* @__PURE__ */ jsxs36("span", { className: "chalk-shell-recording", children: [
6244
6639
  /* @__PURE__ */ jsx55("i", {}),
6245
6640
  "Rec"
6246
6641
  ] }) : null
6247
6642
  ] }),
6248
- /* @__PURE__ */ jsxs35("div", { className: "chalk-header-layout flex items-center gap-2", children: [
6249
- props.onTogglePeople ? /* @__PURE__ */ jsxs35("button", { type: "button", className: "chalk-shell-people-pill", "aria-label": `People, ${props.people?.length ?? 0}`, onClick: props.onTogglePeople, children: [
6643
+ props.notices,
6644
+ /* @__PURE__ */ jsxs36("div", { className: "chalk-header-layout flex items-center gap-2", children: [
6645
+ props.onTogglePeople ? /* @__PURE__ */ jsxs36("button", { type: "button", className: "chalk-shell-people-pill", "aria-label": `People, ${props.people?.length ?? 0}`, onClick: props.onTogglePeople, children: [
6250
6646
  /* @__PURE__ */ jsx55("span", { className: "chalk-shell-faces", children: props.people?.slice(0, 3).map((person) => /* @__PURE__ */ jsx55(Avatar, { name: person.displayName, size: "xs" }, person.participantId)) }),
6251
6647
  /* @__PURE__ */ jsx55("span", { children: props.people?.length ?? 0 }),
6252
6648
  props.knockCount ? /* @__PURE__ */ jsx55("span", { "aria-label": `${props.knockCount} waiting to join`, children: props.knockCount }) : null
6253
6649
  ] }) : null,
6254
6650
  onLayoutChange ? /* @__PURE__ */ jsx55(LayoutMenu, { layout, onLayoutChange }) : null
6255
6651
  ] }),
6256
- /* @__PURE__ */ jsx55(CallDialog, { returnFocus: triggerRef, kind: dialog.kind, open: dialog.open, onClose: () => setDialog((current) => ({ ...current, open: false })), onReportProblem: props.onReportProblem, diagnostics: props.diagnostics?.map(({ label, value }) => `${label}: ${value}`).join("\n") ?? "" })
6652
+ /* @__PURE__ */ jsx55(
6653
+ CallDialog,
6654
+ {
6655
+ focusScope,
6656
+ returnFocus: triggerRef,
6657
+ kind: dialog.kind,
6658
+ open: dialog.open,
6659
+ onClose: () => setDialog((current) => ({ ...current, open: false })),
6660
+ onReportProblem: props.onReportProblem,
6661
+ unavailableReason: props.reportUnavailableReason,
6662
+ diagnostics: props.diagnostics?.map(({ label, value }) => `${label}: ${value}`).join("\n") ?? ""
6663
+ }
6664
+ )
6257
6665
  ] });
6258
6666
  });
6259
6667
  SpaceHeader.displayName = "SpaceHeader";
6260
6668
 
6261
6669
  // src/components/composite/ScreenShareView.tsx
6262
- import { useEffect as useEffect23, useMemo as useMemo11, useRef as useRef21, useState as useState30 } from "react";
6263
- import { Fragment as Fragment22, jsx as jsx56, jsxs as jsxs36 } from "react/jsx-runtime";
6264
- function ScreenSharePresentation({ sharedByName, media, mediaVisible, state, isLocal = false, hasAudio = false, capture, primary = true, pinned, hidden, style, onClick, onDoubleClick, onStopSharing, onChangeShare, switcher, className }) {
6670
+ import { useEffect as useEffect23, useMemo as useMemo11, useRef as useRef21, useState as useState33 } from "react";
6671
+ import { Fragment as Fragment24, jsx as jsx56, jsxs as jsxs37 } from "react/jsx-runtime";
6672
+ function ScreenSharePresentation({ sharedByName, media, mediaVisible, state, isLocal = false, hasAudio = false, capture: capture2, primary = true, pinned, hidden, style, onClick, onDoubleClick, onStopSharing, onChangeShare, switcher, className }) {
6265
6673
  const root = useRef21(null);
6266
- const [fill, setFill] = useState30(false);
6267
- const [controlsOpen, setControlsOpen] = useState30(false);
6268
- const [poppedOut, setPoppedOut] = useState30(false);
6269
- const [zoom, setZoom] = useState30(false);
6270
- const [pending, setPending] = useState30(false);
6271
- const [error, setError] = useState30(null);
6674
+ const [fill, setFill] = useState33(false);
6675
+ const [controlsOpen, setControlsOpen] = useState33(false);
6676
+ const [poppedOut, setPoppedOut] = useState33(false);
6677
+ const [zoom, setZoom] = useState33(false);
6678
+ const [pending, setPending] = useState33(false);
6679
+ const [error, setError] = useState33(null);
6272
6680
  const status = state ?? (mediaVisible ? "live" : "connecting");
6273
6681
  const name = isLocal ? "Your screen" : `${sharedByName}'s screen`;
6274
- const source = capture?.surface === "window" ? "a window" : capture?.surface === "browser" ? "a tab" : capture?.surface === "monitor" ? "your screen" : null;
6275
- const run = async (command) => {
6682
+ const source = capture2?.surface === "window" ? "a window" : capture2?.surface === "browser" ? "a tab" : capture2?.surface === "monitor" ? "your screen" : null;
6683
+ const run = async (command, source2, action) => {
6276
6684
  if (pending) return;
6277
6685
  setPending(true);
6278
6686
  setError(null);
6279
6687
  try {
6280
6688
  await command();
6281
6689
  } catch (cause) {
6282
- setError(cause instanceof Error ? cause.message : "The share could not be changed.");
6690
+ setError(source2 ? commandFailureMessage(commandFailure(cause), source2, action) : cause instanceof Error ? cause.message : "The share could not be changed.");
6283
6691
  } finally {
6284
6692
  setPending(false);
6285
6693
  }
@@ -6317,12 +6725,12 @@ function ScreenSharePresentation({ sharedByName, media, mediaVisible, state, isL
6317
6725
  hidden,
6318
6726
  style,
6319
6727
  className: cn("h-full w-full", className),
6320
- chip: /* @__PURE__ */ jsxs36(Fragment22, { children: [
6728
+ chip: /* @__PURE__ */ jsxs37(Fragment24, { children: [
6321
6729
  /* @__PURE__ */ jsx56(StageIcon, { name: "share", size: 14, className: "shrink-0" }),
6322
6730
  /* @__PURE__ */ jsx56("span", { dir: "auto", className: "min-w-0 truncate [unicode-bidi:plaintext]", children: name }),
6323
6731
  !isLocal && hasAudio ? /* @__PURE__ */ jsx56(StageIcon, { name: "volume", size: 14, "aria-label": "With share audio", className: "shrink-0" }) : null
6324
6732
  ] }),
6325
- children: /* @__PURE__ */ jsxs36(
6733
+ children: /* @__PURE__ */ jsxs37(
6326
6734
  "div",
6327
6735
  {
6328
6736
  ref: root,
@@ -6339,43 +6747,43 @@ function ScreenSharePresentation({ sharedByName, media, mediaVisible, state, isL
6339
6747
  children: media
6340
6748
  }
6341
6749
  ) }),
6342
- poppedOut ? /* @__PURE__ */ jsxs36("div", { className: "absolute inset-0 flex flex-col items-center justify-center gap-2.5 p-4 text-center text-[var(--chalk-muted-text)]", children: [
6750
+ poppedOut ? /* @__PURE__ */ jsxs37("div", { className: "absolute inset-0 flex flex-col items-center justify-center gap-2.5 p-4 text-center text-[var(--chalk-muted-text)]", children: [
6343
6751
  /* @__PURE__ */ jsx56(StageIcon, { name: "pip", size: 32 }),
6344
- /* @__PURE__ */ jsxs36("p", { className: "font-medium text-[var(--chalk-text)]", children: [
6752
+ /* @__PURE__ */ jsxs37("p", { className: "font-medium text-[var(--chalk-text)]", children: [
6345
6753
  name,
6346
6754
  " is in a picture-in-picture window"
6347
6755
  ] }),
6348
6756
  /* @__PURE__ */ jsx56(ChalkButton, { className: "!h-8 !px-3", onClick: () => void popout(), children: "Bring it back" })
6349
6757
  ] }) : null,
6350
- status === "connecting" ? /* @__PURE__ */ jsxs36("div", { role: "status", className: "absolute inset-0 flex flex-col items-center justify-center gap-3 bg-[var(--chalk-tile)] text-[var(--chalk-muted-text)]", children: [
6758
+ status === "connecting" ? /* @__PURE__ */ jsxs37("div", { role: "status", className: "absolute inset-0 flex flex-col items-center justify-center gap-3 bg-[var(--chalk-tile)] text-[var(--chalk-muted-text)]", children: [
6351
6759
  /* @__PURE__ */ jsx56("span", { className: "h-[18px] w-[18px] rounded-[50%] border-2 border-[var(--chalk-line-strong)] border-t-[var(--chalk-text)] motion-safe:animate-spin motion-safe:[animation-duration:.9s] motion-reduce:border-t-[var(--chalk-muted-text)]" }),
6352
- /* @__PURE__ */ jsxs36("p", { className: "font-medium text-[var(--chalk-text)]", children: [
6760
+ /* @__PURE__ */ jsxs37("p", { className: "font-medium text-[var(--chalk-text)]", children: [
6353
6761
  "Connecting to ",
6354
6762
  sharedByName,
6355
6763
  "\u2019s screen\u2026"
6356
6764
  ] })
6357
6765
  ] }) : null,
6358
- status === "paused" ? /* @__PURE__ */ jsxs36("div", { role: "status", className: "pointer-events-none absolute inset-0 z-10 flex flex-col items-center justify-center gap-2 text-white", children: [
6766
+ status === "paused" ? /* @__PURE__ */ jsxs37("div", { role: "status", className: "pointer-events-none absolute inset-0 z-10 flex flex-col items-center justify-center gap-2 text-white", children: [
6359
6767
  /* @__PURE__ */ jsx56("span", { className: "grid h-12 w-12 place-items-center rounded-[50%] bg-[rgba(12,14,18,.82)]", children: /* @__PURE__ */ jsx56(StageIcon, { name: "pause", size: 24 }) }),
6360
- /* @__PURE__ */ jsxs36("p", { className: "rounded-[8px] bg-[rgba(12,14,18,.82)] px-3 py-1.5 font-semibold", children: [
6768
+ /* @__PURE__ */ jsxs37("p", { className: "rounded-[8px] bg-[rgba(12,14,18,.82)] px-3 py-1.5 font-semibold", children: [
6361
6769
  sharedByName,
6362
6770
  " paused sharing"
6363
6771
  ] })
6364
6772
  ] }) : null,
6365
- status === "low-quality" ? /* @__PURE__ */ jsxs36("div", { role: "status", "data-part": "share-low-quality", className: "absolute top-2 left-2 z-30 flex h-7 items-center gap-2 rounded-[6px] bg-[rgba(12,14,18,.82)] px-2.5 text-[12px] font-medium text-white", children: [
6773
+ status === "low-quality" ? /* @__PURE__ */ jsxs37("div", { role: "status", "data-part": "share-low-quality", className: "absolute top-2 left-2 z-30 flex h-7 items-center gap-2 rounded-[6px] bg-[rgba(12,14,18,.82)] px-2.5 text-[12px] font-medium text-white", children: [
6366
6774
  /* @__PURE__ */ jsx56(SignalBars, {}),
6367
- /* @__PURE__ */ jsxs36("span", { children: [
6775
+ /* @__PURE__ */ jsxs37("span", { children: [
6368
6776
  sharedByName,
6369
6777
  "\u2019s connection is slow"
6370
6778
  ] })
6371
6779
  ] }) : null,
6372
6780
  primary ? switcher : null,
6373
- isLocal && primary ? /* @__PURE__ */ jsxs36("div", { "data-part": "share-local-overlay", role: "group", "aria-label": "Your share", className: "absolute top-2.5 right-2.5 left-2.5 z-30 flex items-center gap-2 rounded-[12px] bg-[rgba(12,14,18,.86)] p-2 pl-3.5 text-white max-[640px]:flex-wrap", children: [
6374
- /* @__PURE__ */ jsxs36("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
6781
+ isLocal && primary ? /* @__PURE__ */ jsxs37("div", { "data-part": "share-local-overlay", role: "group", "aria-label": "Your share", className: "absolute top-2.5 right-2.5 left-2.5 z-30 flex items-center gap-2 rounded-[12px] bg-[rgba(12,14,18,.86)] p-2 pl-3.5 text-white max-[640px]:flex-wrap", children: [
6782
+ /* @__PURE__ */ jsxs37("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
6375
6783
  /* @__PURE__ */ jsx56("b", { className: "truncate font-semibold", children: source ? `You\u2019re sharing ${source}` : "You\u2019re screen sharing" }),
6376
- capture?.surface === "monitor" || capture?.surface === "browser" ? /* @__PURE__ */ jsxs36("span", { className: "text-[12px] text-[#c9cbd1]", children: [
6784
+ capture2?.surface === "monitor" || capture2?.surface === "browser" ? /* @__PURE__ */ jsxs37("span", { className: "text-[12px] text-[#c9cbd1]", children: [
6377
6785
  "Seeing a mirror effect? That\u2019s normal when you share ",
6378
- capture.surface === "browser" ? "this tab" : "your whole screen",
6786
+ capture2.surface === "browser" ? "this tab" : "your whole screen",
6379
6787
  "."
6380
6788
  ] }) : null
6381
6789
  ] }),
@@ -6387,12 +6795,12 @@ function ScreenSharePresentation({ sharedByName, media, mediaVisible, state, isL
6387
6795
  { surface: "monitor", label: "Entire screen" },
6388
6796
  { surface: "window", label: "Window" },
6389
6797
  { surface: "browser", label: "Browser tab" }
6390
- ].map((option) => /* @__PURE__ */ jsx56(ChalkMenuItem, { onClick: () => void run(() => onChangeShare({ surface: option.surface, audio: option.surface === "browser" })), children: option.label }, option.surface))
6798
+ ].map((option) => /* @__PURE__ */ jsx56(ChalkMenuItem, { onClick: () => void run(() => onChangeShare({ surface: option.surface, audio: option.surface === "browser" }), "screen", "start"), children: option.label }, option.surface))
6391
6799
  }
6392
6800
  ) : null,
6393
- onStopSharing ? /* @__PURE__ */ jsx56(ChalkButton, { disabled: pending, tone: "danger", className: "!h-8 !bg-[var(--chalk-danger)] !px-3 !text-white hover:!bg-[var(--chalk-danger-hover)]", onClick: () => void run(onStopSharing), children: "Stop sharing" }) : null
6801
+ onStopSharing ? /* @__PURE__ */ jsx56(ChalkButton, { disabled: pending, tone: "danger", className: "!h-8 !bg-[var(--chalk-danger)] !px-3 !text-white hover:!bg-[var(--chalk-danger-hover)]", onClick: () => void run(onStopSharing, "screen", "stop"), children: "Stop sharing" }) : null
6394
6802
  ] }) : null,
6395
- primary && !isLocal && status !== "connecting" ? /* @__PURE__ */ jsxs36("div", { role: "toolbar", "aria-label": "Share controls", className: cn("absolute right-2 bottom-2 z-30 flex gap-1.5 opacity-0 transition-opacity duration-150 group-hover/tile:opacity-100 focus-within:opacity-100", controlsOpen && "opacity-100"), children: [
6803
+ primary && !isLocal && status !== "connecting" ? /* @__PURE__ */ jsxs37("div", { role: "toolbar", "aria-label": "Share controls", className: cn("absolute right-2 bottom-2 z-30 flex gap-1.5 opacity-0 transition-opacity duration-150 group-hover/tile:opacity-100 focus-within:opacity-100", controlsOpen && "opacity-100"), children: [
6396
6804
  /* @__PURE__ */ jsx56(ShareControl2, { label: fill ? "Fit the whole screen" : "Fill the tile", pressed: fill, onClick: () => setFill(!fill), children: /* @__PURE__ */ jsx56(StageIcon, { name: fill ? "fit" : "fill", size: 16 }) }),
6397
6805
  /* @__PURE__ */ jsx56(ShareControl2, { label: zoom ? "Zoom out" : "Zoom in", pressed: zoom, onClick: () => setZoom(!zoom), children: /* @__PURE__ */ jsx56(StageIcon, { name: zoom ? "zoom-out" : "zoom-in", size: 16 }) }),
6398
6806
  /* @__PURE__ */ jsx56(ShareControl2, { label: "Pop out", onClick: () => void popout(), children: /* @__PURE__ */ jsx56(StageIcon, { name: "pip", size: 16 }) }),
@@ -6454,16 +6862,17 @@ function ScreenShareView(props) {
6454
6862
  }
6455
6863
 
6456
6864
  // src/components/stage/Stage.tsx
6457
- import { useCallback as useCallback7, useEffect as useEffect24, useMemo as useMemo12, useRef as useRef24, useState as useState32 } from "react";
6865
+ import { useCallback as useCallback7, useEffect as useEffect24, useLayoutEffect as useLayoutEffect5, useMemo as useMemo12, useRef as useRef24, useState as useState35 } from "react";
6866
+ import { createPortal as createPortal2 } from "react-dom";
6458
6867
 
6459
6868
  // src/components/stage/useStageDimensions.ts
6460
- import { useLayoutEffect as useLayoutEffect2, useRef as useRef22, useState as useState31 } from "react";
6461
- import { flushSync } from "react-dom";
6869
+ import { useLayoutEffect as useLayoutEffect4, useRef as useRef22, useState as useState34 } from "react";
6870
+ import { flushSync as flushSync3 } from "react-dom";
6462
6871
  function useStageDimensions(fallback3) {
6463
6872
  const ref = useRef22(null);
6464
6873
  const current = useRef22(fallback3);
6465
- const [dimensions, setDimensions] = useState31(fallback3);
6466
- useLayoutEffect2(() => {
6874
+ const [dimensions, setDimensions] = useState34(fallback3);
6875
+ useLayoutEffect4(() => {
6467
6876
  const element = ref.current;
6468
6877
  if (!element || typeof ResizeObserver === "undefined") return;
6469
6878
  const style = getComputedStyle(element);
@@ -6481,7 +6890,7 @@ function useStageDimensions(fallback3) {
6481
6890
  if (width <= 0 || height <= 0 || width === current.current.width && height === current.current.height) return;
6482
6891
  const next = { width, height };
6483
6892
  current.current = next;
6484
- flushSync(() => setDimensions(next));
6893
+ flushSync3(() => setDimensions(next));
6485
6894
  });
6486
6895
  observer.observe(element);
6487
6896
  return () => observer.disconnect();
@@ -6503,7 +6912,7 @@ var StageMotion = class extends Component {
6503
6912
  getSnapshotBeforeUpdate(previous) {
6504
6913
  if (!this.props.animate || !this.animationsSupported() || previous.motionKey === this.props.motionKey) return null;
6505
6914
  const elements = this.elements();
6506
- return { play: captureFlip(elements), previous: new Set(elements) };
6915
+ return { play: captureFlip(elements.filter((element) => element.dataset.stageItem !== WHITEBOARD_ITEM_ID)), previous: new Set(elements) };
6507
6916
  }
6508
6917
  componentDidUpdate(previous, _state, snapshot) {
6509
6918
  if (!this.animationsSupported()) return;
@@ -6534,7 +6943,7 @@ var StageMotion = class extends Component {
6534
6943
  } else if (entering.has(element)) {
6535
6944
  const transform = getComputedStyle(element).transform;
6536
6945
  const enter = element.animate(
6537
- [
6946
+ element.dataset.stageItem === WHITEBOARD_ITEM_ID ? [{ opacity: 0 }, { opacity: 1 }] : [
6538
6947
  { opacity: 0, transform: `${transform} scale(.97)` },
6539
6948
  { opacity: 1, transform }
6540
6949
  ],
@@ -6555,7 +6964,7 @@ var StageMotion = class extends Component {
6555
6964
 
6556
6965
  // src/components/stage/StageContentTile.tsx
6557
6966
  import React17, { useRef as useRef23 } from "react";
6558
- import { Fragment as Fragment23, jsx as jsx58, jsxs as jsxs37 } from "react/jsx-runtime";
6967
+ import { Fragment as Fragment25, jsx as jsx58, jsxs as jsxs38 } from "react/jsx-runtime";
6559
6968
  var BOARD_ACCENT = "var(--chalk-app-line-strong, currentColor)";
6560
6969
  function contentTileLabel(item) {
6561
6970
  return item.kind === "whiteboard" ? "Whiteboard tile" : `Screen share tile for ${item.participant.displayName}`;
@@ -6579,7 +6988,7 @@ var StageContentTile = React17.memo(function StageContentTile2({ item, active, .
6579
6988
  }
6580
6989
  );
6581
6990
  });
6582
- var StageContentTileSurface = React17.memo(function StageContentTileSurface2({ item, pinned, onClick, onDoubleClick, className, style, hidden, media, mediaVisible, primary, state, onStopSharing, onChangeShare, capture, switcher }) {
6991
+ var StageContentTileSurface = React17.memo(function StageContentTileSurface2({ item, pinned, onClick, onDoubleClick, className, style, hidden, media, mediaVisible, primary, state, onStopSharing, onChangeShare, capture: capture2, switcher }) {
6583
6992
  if (item.kind === "screen-share")
6584
6993
  return /* @__PURE__ */ jsx58(
6585
6994
  ScreenSharePresentation,
@@ -6599,12 +7008,12 @@ var StageContentTileSurface = React17.memo(function StageContentTileSurface2({ i
6599
7008
  hidden,
6600
7009
  onStopSharing,
6601
7010
  onChangeShare,
6602
- capture,
7011
+ capture: capture2,
6603
7012
  switcher
6604
7013
  }
6605
7014
  );
6606
7015
  const accentColor = BOARD_ACCENT;
6607
- return /* @__PURE__ */ jsxs37(
7016
+ return /* @__PURE__ */ jsxs38(
6608
7017
  TileShell,
6609
7018
  {
6610
7019
  label: contentTileLabel(item),
@@ -6616,7 +7025,7 @@ var StageContentTileSurface = React17.memo(function StageContentTileSurface2({ i
6616
7025
  style,
6617
7026
  dataTour: "video-grid",
6618
7027
  hidden,
6619
- chip: primary ? null : /* @__PURE__ */ jsxs37(Fragment23, { children: [
7028
+ chip: primary ? null : /* @__PURE__ */ jsxs38(Fragment25, { children: [
6620
7029
  /* @__PURE__ */ jsx58(StageIcon, { name: "pen", size: 14, className: "shrink-0" }),
6621
7030
  /* @__PURE__ */ jsx58("span", { className: "min-w-0 truncate", children: contentTileName(item) })
6622
7031
  ] }),
@@ -6631,9 +7040,49 @@ StageContentTile.displayName = "StageContentTile";
6631
7040
  StageContentTileSurface.displayName = "StageContentTileSurface";
6632
7041
 
6633
7042
  // src/components/stage/Stage.tsx
6634
- import { jsx as jsx59, jsxs as jsxs38 } from "react/jsx-runtime";
7043
+ import { Fragment as Fragment26, jsx as jsx59, jsxs as jsxs39 } from "react/jsx-runtime";
6635
7044
  var FALLBACK_BOX = { width: 960, height: 540 };
6636
7045
  var TILE_FILL = { width: "100%", height: "100%" };
7046
+ function createContentPlacement() {
7047
+ let host = null;
7048
+ let home = null;
7049
+ let overflow = null;
7050
+ const move = () => {
7051
+ const target = overflow ?? home;
7052
+ if (host && target && host.parentNode !== target) target.appendChild(host);
7053
+ };
7054
+ return {
7055
+ attachHost: (element) => {
7056
+ host = element;
7057
+ move();
7058
+ },
7059
+ attachHome: (element) => {
7060
+ home = element;
7061
+ move();
7062
+ },
7063
+ attachOverflow: (element) => {
7064
+ overflow = element;
7065
+ move();
7066
+ }
7067
+ };
7068
+ }
7069
+ function PersistentStageContent({ placement, children }) {
7070
+ const [host, setHost] = useState35(null);
7071
+ useLayoutEffect5(() => {
7072
+ const element = document.createElement("div");
7073
+ Object.assign(element.style, TILE_FILL);
7074
+ placement.attachHost(element);
7075
+ setHost(element);
7076
+ return () => {
7077
+ placement.attachHost(null);
7078
+ element.remove();
7079
+ };
7080
+ }, [placement]);
7081
+ return /* @__PURE__ */ jsxs39(Fragment26, { children: [
7082
+ /* @__PURE__ */ jsx59("div", { ref: placement.attachHome, style: TILE_FILL }),
7083
+ host && createPortal2(children, host)
7084
+ ] });
7085
+ }
6637
7086
  function frameStyle(frame) {
6638
7087
  return { position: "absolute", left: 0, top: 0, transform: `translate(${frame.x}px, ${frame.y}px)`, width: frame.width, height: frame.height, zIndex: frame.role === "primary" ? 1 : 2 };
6639
7088
  }
@@ -6662,14 +7111,15 @@ function Stage({
6662
7111
  }) {
6663
7112
  const prefersReducedMotion = usePrefersReducedMotion();
6664
7113
  const { ref: boxRef, dimensions: box } = useStageDimensions(FALLBACK_BOX);
6665
- const [overflowOpen, setOverflowOpen] = useState32(false);
6666
- const [shareAspects, setShareAspects] = useState32(/* @__PURE__ */ new Map());
6667
- const [uncontrolledPinnedId, setUncontrolledPinnedId] = useState32(null);
7114
+ const [overflowOpen, setOverflowOpen] = useState35(false);
7115
+ const [shareAspects, setShareAspects] = useState35(/* @__PURE__ */ new Map());
7116
+ const [uncontrolledPinnedId, setUncontrolledPinnedId] = useState35(null);
6668
7117
  const activePinnedId = pinnedId === void 0 ? uncontrolledPinnedId : pinnedId;
6669
7118
  const orderRef = useRef24([]);
6670
7119
  const seenAtRef = useRef24(/* @__PURE__ */ new Map());
6671
7120
  const nextSeenRef = useRef24(0);
6672
7121
  const lastSpeakerRef = useRef24(null);
7122
+ const lastRemoteSpeakerRef = useRef24(null);
6673
7123
  const ordered = useMemo12(() => {
6674
7124
  if (!interactive) {
6675
7125
  seenAtRef.current = new Map(items.map((item, index) => [item.id, index]));
@@ -6692,8 +7142,9 @@ function Stage({
6692
7142
  }, [ordered]);
6693
7143
  useEffect24(() => {
6694
7144
  orderRef.current = ordered.map((item) => item.id);
6695
- const speaking = ordered.find((item) => item.kind === "participant" && (item.participant.isActiveSpeaker || item.participant.isSpeaking));
6696
- if (speaking) lastSpeakerRef.current = speaking.id;
7145
+ const speaking = speakingParticipantId(ordered);
7146
+ if (speaking) lastSpeakerRef.current = speaking;
7147
+ lastRemoteSpeakerRef.current = floatingSpeakerId(ordered, lastRemoteSpeakerRef.current);
6697
7148
  }, [ordered]);
6698
7149
  const primary = useMemo12(() => layout === "grid" ? null : choosePrimary(ordered, { layout, pinnedId: activePinnedId, lastSpeakerId: interactive ? lastSpeakerRef.current : null, seenAt: seenAtRef.current }), [activePinnedId, interactive, layout, ordered]);
6699
7150
  const geometry = useMemo12(() => {
@@ -6711,21 +7162,39 @@ function Stage({
6711
7162
  );
6712
7163
  }
6713
7164
  const track = primary.kind === "screen-share" ? primary.participant.screenShareTrack : null;
6714
- const settings = track?.getSettings();
7165
+ const settings2 = track?.getSettings();
6715
7166
  const observedAspect = shareAspects.get(primary.id);
6716
- const aspect = primary.kind === "whiteboard" ? null : observedAspect ?? (primary.kind === "screen-share" && primary.participant.screenShareAspect ? primary.participant.screenShareAspect : settings?.width && settings.height ? settings.width / settings.height : 16 / 9);
7167
+ const aspect = primary.kind === "whiteboard" ? null : observedAspect ?? (primary.kind === "screen-share" && primary.participant.screenShareAspect ? primary.participant.screenShareAspect : settings2?.width && settings2.height ? settings2.width / settings2.height : 16 / 9);
6717
7168
  const remaining = ordered.filter((item) => item.id !== primary.id);
6718
7169
  const strip = [...remaining.filter(isContentItem), ...remaining.filter((item) => !isContentItem(item))];
7170
+ const speakerId = floatingSpeakerId(remaining, interactive ? lastRemoteSpeakerRef.current : null);
7171
+ const localId = remaining.find((item) => item.kind === "participant" && item.participant.isLocal)?.id ?? null;
6719
7172
  return fitSpotlight(
6720
7173
  box,
6721
7174
  primary.id,
6722
7175
  strip.map((item) => item.id),
6723
- { ...DEFAULT_SPOTLIGHT_OPTIONS, primaryAspect: aspect, content: primary.kind !== "participant", slimPhoneStrip: primary.kind === "whiteboard", wideIds: primary.kind === "screen-share" ? ordered.filter((item) => item.kind === "whiteboard").map((item) => item.id) : wideIds, maxPerPage }
7176
+ {
7177
+ ...DEFAULT_SPOTLIGHT_OPTIONS,
7178
+ primaryAspect: aspect,
7179
+ content: primary.kind !== "participant",
7180
+ floatingPhonePeople: primary.kind === "whiteboard",
7181
+ speakerId,
7182
+ localId,
7183
+ wideIds: primary.kind === "screen-share" ? ordered.filter((item) => item.kind === "whiteboard").map((item) => item.id) : wideIds,
7184
+ maxPerPage
7185
+ }
6724
7186
  );
6725
- }, [activePinnedId, box, layout, maxPerPage, minTileWidth, ordered, primary, shareAspects]);
7187
+ }, [activePinnedId, box, interactive, layout, maxPerPage, minTileWidth, ordered, primary, shareAspects]);
6726
7188
  const framesById = useMemo12(() => new Map(geometry.frames.map((frame) => [frame.id, frame])), [geometry.frames]);
7189
+ const foldedContent = ordered.filter((item) => item.kind !== "participant" && !framesById.has(item.id));
7190
+ const renderContent = (item, context, primaryRenderer) => {
7191
+ if (primaryRenderer && renderPrimaryContent) return /* @__PURE__ */ jsx59(StageContentTileSurface, { item, ...context, mediaVisible: true, media: renderPrimaryContent(item) });
7192
+ if (renderContentTile) return renderContentTile(item, context);
7193
+ if (renderPrimaryContent) return /* @__PURE__ */ jsx59(StageContentTileSurface, { item, ...context, mediaVisible: true, media: renderPrimaryContent(item) });
7194
+ return /* @__PURE__ */ jsx59(StageContentTile, { item, ...context });
7195
+ };
6727
7196
  const animate = animateOverride ?? !prefersReducedMotion;
6728
- const [displayed, setDisplayed] = useState32({
7197
+ const [displayed, setDisplayed] = useState35({
6729
7198
  source: ordered,
6730
7199
  geometry,
6731
7200
  tiles: ordered.map((item) => ({ item, frame: framesById.get(item.id), leaving: false }))
@@ -6735,6 +7204,19 @@ function Stage({
6735
7204
  const tiles = ordered.map((item) => ({ item, frame: framesById.get(item.id), leaving: false }));
6736
7205
  setDisplayed({ source: ordered, geometry, tiles: [...tiles, ...animate ? displayed.tiles.filter((tile) => !present.has(tile.item.id)).map((tile) => ({ ...tile, leaving: true, departedAt: tile.departedAt ?? performance.now() })) : []] });
6737
7206
  }
7207
+ const contentPlacements = useRef24(/* @__PURE__ */ new Map());
7208
+ const contentPlacement = (id) => {
7209
+ let placement = contentPlacements.current.get(id);
7210
+ if (!placement) {
7211
+ placement = createContentPlacement();
7212
+ contentPlacements.current.set(id, placement);
7213
+ }
7214
+ return placement;
7215
+ };
7216
+ useEffect24(() => {
7217
+ const present = new Set(displayed.tiles.map(({ item }) => item.id));
7218
+ for (const id of contentPlacements.current.keys()) if (!present.has(id)) contentPlacements.current.delete(id);
7219
+ }, [displayed.tiles]);
6738
7220
  useEffect24(() => {
6739
7221
  const departing = displayed.tiles.filter((tile) => tile.leaving);
6740
7222
  if (!departing.length) return;
@@ -6791,8 +7273,8 @@ function Stage({
6791
7273
  return /* @__PURE__ */ jsx59("div", { className: cn("flex h-full w-full items-center justify-center px-6 py-10", className), "data-tour": "video-grid", children: emptyState });
6792
7274
  }
6793
7275
  const participantCount = ordered.filter((item) => item.kind === "participant").length;
6794
- return /* @__PURE__ */ jsxs38("div", { className: cn("relative h-full w-full", className), "data-tour": "video-grid", children: [
6795
- /* @__PURE__ */ jsx59("div", { ref: boxRef, className: "relative h-full w-full outline-none", role: "group", "aria-label": `Stage with ${participantCount} ${participantCount === 1 ? "participant" : "participants"}`, "aria-disabled": interactive ? void 0 : true, "data-testid": "stage", children: /* @__PURE__ */ jsxs38(StageMotion, { rootRef: tilesRef, motionKey, animate, children: [
7276
+ return /* @__PURE__ */ jsxs39("div", { className: cn("relative h-full w-full", className), "data-tour": "video-grid", children: [
7277
+ /* @__PURE__ */ jsx59("div", { ref: boxRef, className: "relative h-full w-full outline-none", role: "group", "aria-label": `Stage with ${participantCount} ${participantCount === 1 ? "participant" : "participants"}`, "aria-disabled": interactive ? void 0 : true, "data-testid": "stage", children: /* @__PURE__ */ jsxs39(StageMotion, { rootRef: tilesRef, motionKey, animate, children: [
6796
7278
  displayed.tiles.map(({ item, frame, leaving }) => {
6797
7279
  const onPage = Boolean(frame) && !leaving;
6798
7280
  const style = TILE_FILL;
@@ -6821,14 +7303,10 @@ function Stage({
6821
7303
  gradientPreference
6822
7304
  }
6823
7305
  );
6824
- } else if (frame?.role === "primary" && renderPrimaryContent) {
6825
- content = /* @__PURE__ */ jsx59(StageContentTileSurface, { item, ...renderContext, mediaVisible: true, media: renderPrimaryContent(item) });
6826
- } else if (renderContentTile) {
6827
- content = renderContentTile(item, renderContext);
6828
- } else if (renderPrimaryContent) {
6829
- content = /* @__PURE__ */ jsx59(StageContentTileSurface, { item, ...renderContext, mediaVisible: true, media: renderPrimaryContent(item) });
6830
7306
  } else {
6831
- content = /* @__PURE__ */ jsx59(StageContentTile, { item, ...renderContext });
7307
+ const inOverflow = overflowOpen && foldedContent.some((folded) => folded.id === item.id);
7308
+ const context = inOverflow ? { ...renderContext, active: true, primary: true, hidden: false, onClick: void 0, onDoubleClick: void 0 } : renderContext;
7309
+ content = /* @__PURE__ */ jsx59(PersistentStageContent, { placement: contentPlacement(item.id), children: renderContent(item, context, renderContext.primary) });
6832
7310
  }
6833
7311
  return /* @__PURE__ */ jsx59(
6834
7312
  "div",
@@ -6845,15 +7323,16 @@ function Stage({
6845
7323
  item.id
6846
7324
  );
6847
7325
  }),
6848
- geometry.frames.filter((frame) => frame.id === MORE_TILE_ID).map((frame) => /* @__PURE__ */ jsxs38(
7326
+ geometry.frames.filter((frame) => frame.id === MORE_TILE_ID).map((frame) => /* @__PURE__ */ jsxs39(
6849
7327
  "button",
6850
7328
  {
6851
7329
  type: "button",
6852
7330
  "data-stage-item": MORE_TILE_ID,
7331
+ "data-stage-role": frame.role,
6853
7332
  style: frameStyle(frame),
6854
- onClick: onOverflowClick ?? (() => setOverflowOpen(true)),
7333
+ onClick: foldedContent.length ? () => setOverflowOpen(true) : onOverflowClick ?? (() => setOverflowOpen(true)),
6855
7334
  disabled: !interactive,
6856
- "aria-label": `Show ${geometry.overflowCount} more participants`,
7335
+ "aria-label": `Show ${geometry.overflowCount} more ${foldedContent.length ? "items" : "participants"}`,
6857
7336
  className: "grid place-items-center rounded-[12px] bg-[var(--chalk-raised)] text-[22px] font-semibold text-[var(--chalk-text)] hover:bg-[var(--chalk-hover)] max-[640px]:rounded-[14px]",
6858
7337
  children: [
6859
7338
  "+",
@@ -6863,23 +7342,26 @@ function Stage({
6863
7342
  frame.id
6864
7343
  ))
6865
7344
  ] }) }),
6866
- /* @__PURE__ */ jsx59(ChalkSheet, { open: overflowOpen, onOpenChange: setOverflowOpen, title: "People", children: ordered.filter((item) => item.kind === "participant").map((item) => /* @__PURE__ */ jsxs38(
6867
- "button",
6868
- {
6869
- type: "button",
6870
- className: "min-h-11 w-full rounded-xl px-4 text-left hover:bg-[var(--chalk-hover)]",
6871
- onClick: () => {
6872
- handlersById.get(item.id)?.onClick();
6873
- setOverflowOpen(false);
7345
+ /* @__PURE__ */ jsxs39(ChalkSheet, { variant: "stage", open: overflowOpen, onOpenChange: setOverflowOpen, title: foldedContent.length ? "People and shared content" : "People", children: [
7346
+ overflowOpen && foldedContent.map((item) => /* @__PURE__ */ jsx59("div", { ref: contentPlacement(item.id).attachOverflow, className: "relative mb-3 aspect-video max-h-full w-full" }, item.id)),
7347
+ ordered.filter((item) => item.kind === "participant").map((item) => /* @__PURE__ */ jsxs39(
7348
+ "button",
7349
+ {
7350
+ type: "button",
7351
+ className: "min-h-11 w-full rounded-xl px-4 text-left hover:bg-[var(--chalk-hover)]",
7352
+ onClick: () => {
7353
+ handlersById.get(item.id)?.onClick();
7354
+ setOverflowOpen(false);
7355
+ },
7356
+ children: [
7357
+ clickAction === "follow" && !item.participant.isLocal ? `${followingId === item.id ? "Stop following" : "Follow"} ` : "",
7358
+ item.participant.displayName,
7359
+ item.participant.isMuted ? " \xB7 Muted" : ""
7360
+ ]
6874
7361
  },
6875
- children: [
6876
- clickAction === "follow" && !item.participant.isLocal ? `${followingId === item.id ? "Stop following" : "Follow"} ` : "",
6877
- item.participant.displayName,
6878
- item.participant.isMuted ? " \xB7 Muted" : ""
6879
- ]
6880
- },
6881
- item.id
6882
- )) })
7362
+ item.id
7363
+ ))
7364
+ ] })
6883
7365
  ] });
6884
7366
  }
6885
7367
  Stage.displayName = "Stage";
@@ -6966,7 +7448,7 @@ WhiteboardView.displayName = "WhiteboardView";
6966
7448
 
6967
7449
  // src/components/call-card/CallCard.tsx
6968
7450
  import { useEffect as useEffect25, useId as useId2, useRef as useRef25 } from "react";
6969
- import { Fragment as Fragment24, jsx as jsx62, jsxs as jsxs39 } from "react/jsx-runtime";
7451
+ import { Fragment as Fragment27, jsx as jsx62, jsxs as jsxs40 } from "react/jsx-runtime";
6970
7452
  function CallCard({ state, onClose, interactive = true, children, panel, onPanelChange, peopleCount, chatEnabled = true, participantsEnabled = true }) {
6971
7453
  const phone = useIsMobile();
6972
7454
  const id = useId2();
@@ -6989,9 +7471,9 @@ function CallCard({ state, onClose, interactive = true, children, panel, onPanel
6989
7471
  openerRef.current = null;
6990
7472
  if (opener?.isConnected) opener.focus({ preventScroll: true });
6991
7473
  }, [interactive, state]);
6992
- const content = /* @__PURE__ */ jsxs39(Fragment24, { children: [
7474
+ const content = /* @__PURE__ */ jsxs40(Fragment27, { children: [
6993
7475
  /* @__PURE__ */ jsx62(CallCardStyles, {}),
6994
- onPanelChange ? /* @__PURE__ */ jsxs39(
7476
+ onPanelChange ? /* @__PURE__ */ jsxs40(
6995
7477
  "div",
6996
7478
  {
6997
7479
  className: "call-card-tabs",
@@ -7017,7 +7499,7 @@ function CallCard({ state, onClose, interactive = true, children, panel, onPanel
7017
7499
  "aria-selected": tab === "participants" ? panel !== "chat" : panel === "chat",
7018
7500
  tabIndex: (tab === "participants" ? panel !== "chat" : panel === "chat") ? 0 : -1,
7019
7501
  onClick: () => onPanelChange(tab),
7020
- children: tab === "participants" ? /* @__PURE__ */ jsxs39(Fragment24, { children: [
7502
+ children: tab === "participants" ? /* @__PURE__ */ jsxs40(Fragment27, { children: [
7021
7503
  "People",
7022
7504
  /* @__PURE__ */ jsx62("span", { className: "call-card-count", children: peopleCount })
7023
7505
  ] }) : "Chat"
@@ -7049,29 +7531,29 @@ function CallCard({ state, onClose, interactive = true, children, panel, onPanel
7049
7531
  event.preventDefault();
7050
7532
  onClose?.();
7051
7533
  };
7052
- return /* @__PURE__ */ jsx62(Fragment24, { children: /* @__PURE__ */ jsx62("div", { ref: drawerRef, "data-part": "panel-card", tabIndex: interactive ? -1 : void 0, "data-state": state, "data-chalk-drawer": true, onKeyDown: interactive ? handleKeyDown : void 0, "aria-disabled": interactive ? void 0 : true, className: "chalk-drawer min-h-0 outline-none focus-visible:outline-none", children: /* @__PURE__ */ jsx62("div", { "data-part": "panel-content", className: cn("chalk-drawer-content flex min-h-0 flex-col overflow-hidden bg-[var(--chalk-app-panel)] text-[var(--chalk-app-text)]"), children: content }) }) });
7534
+ return /* @__PURE__ */ jsx62(Fragment27, { children: /* @__PURE__ */ jsx62("div", { ref: drawerRef, "data-part": "panel-card", tabIndex: interactive ? -1 : void 0, "data-state": state, "data-chalk-drawer": true, onKeyDown: interactive ? handleKeyDown : void 0, "aria-disabled": interactive ? void 0 : true, className: "chalk-drawer min-h-0 outline-none focus-visible:outline-none", children: /* @__PURE__ */ jsx62("div", { "data-part": "panel-content", className: cn("chalk-drawer-content flex min-h-0 flex-col overflow-hidden bg-[var(--chalk-app-panel)] text-[var(--chalk-app-text)]"), children: content }) }) });
7053
7535
  }
7054
7536
 
7055
7537
  // src/components/space-view/SpacePresentation.tsx
7056
7538
  import { useRef as useRef26 } from "react";
7057
- import { jsx as jsx63, jsxs as jsxs40 } from "react/jsx-runtime";
7539
+ import { jsx as jsx63, jsxs as jsxs41 } from "react/jsx-runtime";
7058
7540
  function SpacePresentation({ colorScheme = "dark", stageBackground, header, stage, controls, compactControls, stageOverlay, sidebar, mediaOutput, toasts, overlays, dialogs, renderToken, interactive = true, className }) {
7059
7541
  const root = useRef26(null);
7060
7542
  const idle = useIdleChrome(root, interactive);
7061
- return /* @__PURE__ */ jsxs40("main", { ref: root, "data-chalk": true, "data-part": "call-frame", "data-idle": idle, "data-chalk-theme": colorScheme, "data-recording-space-render-token": renderToken, inert: interactive ? void 0 : true, className: cn("chalk-root chalk-frame", className), children: [
7543
+ return /* @__PURE__ */ jsxs41("main", { ref: root, "data-chalk": true, "data-part": "call-frame", "data-idle": idle, "data-chalk-theme": colorScheme, "data-recording-space-render-token": renderToken, inert: interactive ? void 0 : true, className: cn("chalk-root chalk-frame", className), children: [
7062
7544
  mediaOutput,
7063
7545
  /* @__PURE__ */ jsx63(SpaceHeader, { ...header, className: cn("chalk-frame-header", header.className) }),
7064
- /* @__PURE__ */ jsx63("section", { "data-part": "stage-region", "data-stage-background": stageBackground, className: "chalk-frame-stage", "aria-label": "Space stage", children: /* @__PURE__ */ jsxs40("div", { "data-part": "stage-layout", className: "chalk-stage-layout", children: [
7546
+ /* @__PURE__ */ jsx63("section", { "data-part": "stage-region", "data-stage-background": stageBackground, className: "chalk-frame-stage", "aria-label": "Space stage", children: /* @__PURE__ */ jsxs41("div", { "data-part": "stage-layout", className: "chalk-stage-layout", children: [
7065
7547
  stage,
7066
7548
  stageOverlay
7067
7549
  ] }) }),
7068
- /* @__PURE__ */ jsxs40("div", { "data-part": "dock-region", className: "chalk-frame-dock", children: [
7550
+ /* @__PURE__ */ jsxs41("div", { "data-part": "dock-region", className: "chalk-frame-dock", children: [
7069
7551
  /* @__PURE__ */ jsx63("div", { className: compactControls ? "chalk-frame-regular" : void 0, style: compactControls ? void 0 : { display: "contents" }, children: controls }),
7070
7552
  compactControls ? /* @__PURE__ */ jsx63("div", { className: "chalk-frame-compact", children: compactControls }) : null
7071
7553
  ] }),
7072
7554
  sidebar,
7073
7555
  toasts,
7074
- /* @__PURE__ */ jsxs40("div", { "data-part": "overlays-region", className: "chalk-frame-overlays", children: [
7556
+ /* @__PURE__ */ jsxs41("div", { "data-part": "overlays-region", className: "chalk-frame-overlays", children: [
7075
7557
  overlays,
7076
7558
  dialogs
7077
7559
  ] })
@@ -7079,9 +7561,9 @@ function SpacePresentation({ colorScheme = "dark", stageBackground, header, stag
7079
7561
  }
7080
7562
 
7081
7563
  // src/components/space-view/SpaceView.tsx
7082
- import { SpaceClientError as SpaceClientError2 } from "@q9labsai/chalk-client";
7564
+ import "@q9labsai/chalk-client";
7083
7565
  import { DEFAULT_QUICK_REACTIONS as DEFAULT_QUICK_REACTIONS2 } from "@q9labsai/chalk-ui/reactions";
7084
- import { useCallback as useCallback15, useEffect as useEffect40, useMemo as useMemo18, useRef as useRef34, useState as useState46 } from "react";
7566
+ import { useCallback as useCallback16, useEffect as useEffect40, useMemo as useMemo19, useRef as useRef34, useState as useState50 } from "react";
7085
7567
 
7086
7568
  // src/components/space-header/call-diagnostics.ts
7087
7569
  function callDiagnostics(connection, media) {
@@ -7095,19 +7577,19 @@ function callDiagnostics(connection, media) {
7095
7577
  ["Speaker", media.devices.speakers, media.selection.speaker],
7096
7578
  ["Camera", media.devices.cameras, media.selection.camera]
7097
7579
  ]) {
7098
- const device = devices.find((item) => item.deviceId === selected);
7099
- if (device?.label) rows.push({ label, value: device.label });
7580
+ const device2 = devices.find((item) => item.deviceId === selected);
7581
+ if (device2?.label) rows.push({ label, value: device2.label });
7100
7582
  }
7101
7583
  if (media.screenShare.capture?.label) rows.push({ label: "Sharing", value: media.screenShare.capture.label });
7102
7584
  return rows;
7103
7585
  }
7104
7586
 
7105
7587
  // src/internal/useEpisodeDuration.ts
7106
- import { useEffect as useEffect26, useState as useState33 } from "react";
7588
+ import { useEffect as useEffect26, useState as useState36 } from "react";
7107
7589
  function useEpisodeDuration() {
7108
7590
  const connection = useConnection();
7109
7591
  const episodeStart = connection.episode?.startedAt ? Date.parse(connection.episode.startedAt) : null;
7110
- const [now, setNow] = useState33(() => Date.now());
7592
+ const [now, setNow] = useState36(() => Date.now());
7111
7593
  useEffect26(() => {
7112
7594
  if (episodeStart === null || Number.isNaN(episodeStart)) return;
7113
7595
  setNow(Date.now());
@@ -7314,19 +7796,19 @@ function useSoundCues(enabled, createPlayer = createSoundPlayer) {
7314
7796
  }
7315
7797
 
7316
7798
  // src/components/composite/ReactionPicker.tsx
7317
- import React21, { useMemo as useMemo14, useState as useState35, useCallback as useCallback10, useEffect as useEffect29, useRef as useRef29 } from "react";
7799
+ import React21, { useMemo as useMemo14, useState as useState38, useCallback as useCallback10, useEffect as useEffect29, useRef as useRef29 } from "react";
7318
7800
  import { DEFAULT_QUICK_REACTIONS, EMOJI_CATEGORIES, EMOJI_KEYWORDS } from "@q9labsai/chalk-ui/reactions";
7319
7801
 
7320
7802
  // src/components/composite/ReactionTray.tsx
7321
- import { useCallback as useCallback9, useEffect as useEffect28, useLayoutEffect as useLayoutEffect3, useRef as useRef28, useState as useState34 } from "react";
7322
- import { Fragment as Fragment25, jsx as jsx64, jsxs as jsxs41 } from "react/jsx-runtime";
7803
+ import { useCallback as useCallback9, useEffect as useEffect28, useLayoutEffect as useLayoutEffect6, useRef as useRef28, useState as useState37 } from "react";
7804
+ import { Fragment as Fragment28, jsx as jsx64, jsxs as jsxs42 } from "react/jsx-runtime";
7323
7805
  function ReactionTray({ isHandRaised, onToggleHandRaise, reactions, onSelect, onClose, position = "top", className }) {
7324
- const [more, setMore] = useState34(false);
7806
+ const [more, setMore] = useState37(false);
7325
7807
  const returnFocus = useRef28(null);
7326
7808
  const prefersReducedMotion = usePrefersReducedMotion();
7327
7809
  const { trigger } = useHaptics();
7328
7810
  const listRef = useRef28(null);
7329
- useLayoutEffect3(() => {
7811
+ useLayoutEffect6(() => {
7330
7812
  returnFocus.current = document.activeElement;
7331
7813
  const tray = listRef.current;
7332
7814
  const frame = tray?.closest("[data-part='call-frame']");
@@ -7384,12 +7866,12 @@ function ReactionTray({ isHandRaised, onToggleHandRaise, reactions, onSelect, on
7384
7866
  };
7385
7867
  const buttons = (more ? reactions : reactions.slice(0, 6)).map((emoji) => /* @__PURE__ */ jsx64("button", { type: "button", onClick: () => handleSelect(emoji), className: "chalk-shell-emoji", "aria-label": `React with ${emoji}`, children: /* @__PURE__ */ jsx64("span", { "aria-hidden": "true", children: emoji }) }, emoji));
7386
7868
  const trayClassName = cn("absolute z-50", !prefersReducedMotion && "chalk-shell-reactions-enter", className);
7387
- return /* @__PURE__ */ jsxs41(Fragment25, { children: [
7869
+ return /* @__PURE__ */ jsxs42(Fragment28, { children: [
7388
7870
  /* @__PURE__ */ jsx64(ShellStyles, {}),
7389
7871
  /* @__PURE__ */ jsx64("div", { className: "fixed inset-0 z-20", onClick: onClose, "aria-hidden": "true" }),
7390
- /* @__PURE__ */ jsxs41("div", { ref: listRef, className: cn(trayClassName, "chalk-shell-reactions", more && "chalk-shell-reactions-expanded"), role: "toolbar", "aria-label": "Reactions", onKeyDown: handleKeyDown, children: [
7391
- onToggleHandRaise ? /* @__PURE__ */ jsxs41("button", { type: "button", className: "chalk-shell-raise", "aria-pressed": isHandRaised, onClick: onToggleHandRaise, children: [
7392
- /* @__PURE__ */ jsxs41("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: [
7872
+ /* @__PURE__ */ jsxs42("div", { ref: listRef, className: cn(trayClassName, "chalk-shell-reactions", more && "chalk-shell-reactions-expanded"), role: "toolbar", "aria-label": "Reactions", onKeyDown: handleKeyDown, children: [
7873
+ onToggleHandRaise ? /* @__PURE__ */ jsxs42("button", { type: "button", className: "chalk-shell-raise", "aria-pressed": isHandRaised, onClick: onToggleHandRaise, children: [
7874
+ /* @__PURE__ */ jsxs42("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: [
7393
7875
  /* @__PURE__ */ jsx64("path", { d: "M18 11V6a2 2 0 0 0-4 0v5M14 10V4a2 2 0 0 0-4 0v6M10 10.5V6a2 2 0 0 0-4 0v8" }),
7394
7876
  /* @__PURE__ */ jsx64("path", { d: "M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.9-6-2.3l-3.6-3.6a2 2 0 0 1 2.8-2.8L7 15" })
7395
7877
  ] }),
@@ -7402,12 +7884,12 @@ function ReactionTray({ isHandRaised, onToggleHandRaise, reactions, onSelect, on
7402
7884
  }
7403
7885
 
7404
7886
  // src/components/composite/ReactionPicker.tsx
7405
- import { Fragment as Fragment26, jsx as jsx65, jsxs as jsxs42 } from "react/jsx-runtime";
7887
+ import { Fragment as Fragment29, jsx as jsx65, jsxs as jsxs43 } from "react/jsx-runtime";
7406
7888
  var ReactionPicker = React21.memo(({ isHandRaised, onToggleHandRaise, isOpen, onClose, onSelect, recentReactions = [], allowedReactions, position = "top", participantColorSeed, participantGradientPreference, className, size = "default" }) => {
7407
7889
  const prefersReducedMotion = usePrefersReducedMotion();
7408
7890
  const { trigger } = useHaptics();
7409
- const [activeCategory, setActiveCategory] = useState35("smileys");
7410
- const [searchQuery, setSearchQuery] = useState35("");
7891
+ const [activeCategory, setActiveCategory] = useState38("smileys");
7892
+ const [searchQuery, setSearchQuery] = useState38("");
7411
7893
  const inputRef = useRef29(null);
7412
7894
  const themeVariables = useMemo14(() => getParticipantThemeVariables(participantColorSeed, participantGradientPreference), [participantColorSeed, participantGradientPreference]);
7413
7895
  useEffect29(() => {
@@ -7466,9 +7948,9 @@ var ReactionPicker = React21.memo(({ isHandRaised, onToggleHandRaise, isOpen, on
7466
7948
  }
7467
7949
  );
7468
7950
  }
7469
- return /* @__PURE__ */ jsxs42(Fragment26, { children: [
7951
+ return /* @__PURE__ */ jsxs43(Fragment29, { children: [
7470
7952
  /* @__PURE__ */ jsx65("div", { className: "fixed inset-0 z-20", onClick: onClose, "aria-hidden": "true" }),
7471
- /* @__PURE__ */ jsxs42(
7953
+ /* @__PURE__ */ jsxs43(
7472
7954
  "div",
7473
7955
  {
7474
7956
  className: cn(
@@ -7502,7 +7984,7 @@ var ReactionPicker = React21.memo(({ isHandRaised, onToggleHandRaise, isOpen, on
7502
7984
  },
7503
7985
  `quick-${emoji}-${index}`
7504
7986
  )) }) }),
7505
- !allowedReactions ? /* @__PURE__ */ jsxs42("div", { className: cn("flex items-center gap-2 border-b border-[var(--chalk-line)] bg-[var(--chalk-stage)] focus-within:bg-[var(--chalk-stage)] transition-colors", size === "mini" ? "px-1.5 py-1" : size === "compact" ? "px-2 py-1.5" : "px-3 py-2"), children: [
7987
+ !allowedReactions ? /* @__PURE__ */ jsxs43("div", { className: cn("flex items-center gap-2 border-b border-[var(--chalk-line)] bg-[var(--chalk-stage)] focus-within:bg-[var(--chalk-stage)] transition-colors", size === "mini" ? "px-1.5 py-1" : size === "compact" ? "px-2 py-1.5" : "px-3 py-2"), children: [
7506
7988
  /* @__PURE__ */ jsx65(Search01Icon, { size: 16, className: "text-[var(--chalk-muted-text)] shrink-0" }),
7507
7989
  /* @__PURE__ */ jsx65(
7508
7990
  "input",
@@ -7519,7 +8001,7 @@ var ReactionPicker = React21.memo(({ isHandRaised, onToggleHandRaise, isOpen, on
7519
8001
  ),
7520
8002
  /* @__PURE__ */ jsx65("kbd", { className: "hidden sm:inline-flex items-center gap-1 px-1.5 py-0.5 rounded border border-[var(--chalk-line)] bg-[var(--chalk-canvas)] text-[10px] font-medium text-[var(--chalk-muted-text)] uppercase", children: "Esc" })
7521
8003
  ] }) : null,
7522
- !allowedReactions ? /* @__PURE__ */ jsxs42("div", { className: cn("flex", size === "mini" ? "h-36" : size === "compact" ? "h-48" : "h-64"), children: [
8004
+ !allowedReactions ? /* @__PURE__ */ jsxs43("div", { className: cn("flex", size === "mini" ? "h-36" : size === "compact" ? "h-48" : "h-64"), children: [
7523
8005
  !isSearching && /* @__PURE__ */ jsx65("div", { className: cn("flex flex-col items-center py-2 gap-1 border-r border-[var(--chalk-line)] bg-[var(--chalk-stage)] overflow-y-auto [&::-webkit-scrollbar]:hidden [scrollbar-width:none]", size === "mini" ? "w-8" : size === "compact" ? "w-10" : "w-12"), children: categories.map(([key, category]) => /* @__PURE__ */ jsx65(
7524
8006
  "button",
7525
8007
  {
@@ -7564,7 +8046,7 @@ var ReactionPicker = React21.memo(({ isHandRaised, onToggleHandRaise, isOpen, on
7564
8046
  ReactionPicker.displayName = "ReactionPicker";
7565
8047
 
7566
8048
  // src/components/offline-actions/OfflineActions.tsx
7567
- import { useEffect as useEffect30, useState as useState36, useSyncExternalStore as useSyncExternalStore4 } from "react";
8049
+ import { useEffect as useEffect30, useState as useState39, useSyncExternalStore as useSyncExternalStore4 } from "react";
7568
8050
  import { jsx as jsx66 } from "react/jsx-runtime";
7569
8051
  var RECONNECTING_GRACE_MS = 2e3;
7570
8052
  function offlineNoticeText(pendingCount, online) {
@@ -7572,9 +8054,8 @@ function offlineNoticeText(pendingCount, online) {
7572
8054
  if (pendingCount === 0) return online ? "Reconnecting\u2026" : "You're offline. Reconnecting\u2026";
7573
8055
  return `${state}. ${pendingCount === 1 ? "1 action" : `${pendingCount} actions`} will send when you're back.`;
7574
8056
  }
7575
- function OfflineActions({ reconnecting: requestedReconnecting = false } = {}) {
8057
+ function OfflineActions({ reconnecting: requestedReconnecting = false, hasNotice = false } = {}) {
7576
8058
  const client = useSpaceClient();
7577
- const [dismissedNotice, setDismissedNotice] = useState36(null);
7578
8059
  const connection = useConnection();
7579
8060
  const offline = useOffline();
7580
8061
  const online = useSyncExternalStore4(subscribeToOnline, readOnline, () => true);
@@ -7587,13 +8068,13 @@ function OfflineActions({ reconnecting: requestedReconnecting = false } = {}) {
7587
8068
  const messagesOnly = offline.pending.every((action) => action.kind === "chat_message");
7588
8069
  const pendingDetail = count ? `${count} ${messagesOnly ? count === 1 ? "message" : "messages" : count === 1 ? "action" : "actions"} will send when you\u2019re back` : void 0;
7589
8070
  const notice = reconnecting ? reconnectingNoticeDue ? `${offlineNoticeText(0, online)}${pendingDetail ? ` ${pendingDetail}` : ""}` : null : connection.syncHealth === "degraded" ? "Connection is slow" : null;
7590
- if (offline.notice && offline.notice !== dismissedNotice) return /* @__PURE__ */ jsx66(ToastStack, { toasts: [{ id: "offline-notice", message: offline.notice }], onDismiss: () => setDismissedNotice(offline.notice ?? null) });
8071
+ if (hasNotice) return null;
7591
8072
  if (notice)
7592
8073
  return /* @__PURE__ */ jsx66("div", { role: "status", "data-part": "connection-notice", style: { pointerEvents: "none" }, className: "absolute inset-x-4 top-16 z-50 mx-auto w-fit max-w-[calc(100%_-_2rem)] rounded-full bg-[var(--chalk-surface)] px-3 py-1 text-center text-sm text-[var(--chalk-text)]", children: notice });
7593
8074
  return null;
7594
8075
  }
7595
8076
  function useTrueFor(value, milliseconds) {
7596
- const [elapsed, setElapsed] = useState36(false);
8077
+ const [elapsed, setElapsed] = useState39(false);
7597
8078
  useEffect30(() => {
7598
8079
  setElapsed(false);
7599
8080
  if (!value) return;
@@ -7615,12 +8096,12 @@ function readOnline() {
7615
8096
  }
7616
8097
 
7617
8098
  // src/components/reconnecting-overlay/ReconnectingOverlay.tsx
7618
- import { memo as memo3, useEffect as useEffect31, useState as useState37 } from "react";
8099
+ import { memo as memo3, useEffect as useEffect31, useState as useState40 } from "react";
7619
8100
  import { jsx as jsx67 } from "react/jsx-runtime";
7620
8101
  var RECONNECTING_NOTICE_MS = 2e3;
7621
8102
  var ReconnectingOverlay = memo3(function ReconnectingOverlay2({ isVisible, status, onRetry, onLeft, message, showNotice }) {
7622
8103
  const reconnecting = isVisible && status === "reconnecting";
7623
- const [elapsed, setElapsed] = useState37(false);
8104
+ const [elapsed, setElapsed] = useState40(false);
7624
8105
  useEffect31(() => {
7625
8106
  setElapsed(false);
7626
8107
  if (!reconnecting) return;
@@ -7635,55 +8116,14 @@ var ReconnectingOverlay = memo3(function ReconnectingOverlay2({ isVisible, statu
7635
8116
  });
7636
8117
 
7637
8118
  // src/components/toast-stack/useCallToasts.ts
7638
- import { useCallback as useCallback11, useEffect as useEffect32, useRef as useRef30, useState as useState38 } from "react";
8119
+ import { useCallback as useCallback11, useEffect as useEffect32, useRef as useRef30, useState as useState41 } from "react";
7639
8120
 
7640
8121
  // src/components/toast-stack/call-events.tsx
7641
- import { Fragment as Fragment27, jsx as jsx68, jsxs as jsxs43 } from "react/jsx-runtime";
7642
- function mediaFailureToast(failure, source) {
8122
+ import { Fragment as Fragment30, jsx as jsx68, jsxs as jsxs44 } from "react/jsx-runtime";
8123
+ function mediaFailureToast(failure, source, action, onRetry) {
7643
8124
  if (!failure) return null;
7644
- const device = source ?? "device";
7645
- const icon = /* @__PURE__ */ jsx68(CallEventIcon, { kind: source ?? "microphone", error: true });
7646
- if (source === "screen" && failure.code.startsWith("media."))
7647
- return {
7648
- icon,
7649
- type: "error",
7650
- message: /* @__PURE__ */ jsxs43(Fragment27, { children: [
7651
- /* @__PURE__ */ jsx68("b", { style: { fontWeight: 600 }, children: "Your screen couldn\u2019t be shared." }),
7652
- " Allow screen recording for your browser in System Settings, then try again."
7653
- ] })
7654
- };
7655
- if (failure.code === "media.permission_denied")
7656
- return {
7657
- icon,
7658
- type: "error",
7659
- message: /* @__PURE__ */ jsxs43(Fragment27, { children: [
7660
- /* @__PURE__ */ jsxs43("b", { style: { fontWeight: 600 }, children: [
7661
- "Chalk can\u2019t use your ",
7662
- device,
7663
- "."
7664
- ] }),
7665
- " Click the camera icon in the address bar and allow ",
7666
- source === "camera" ? "Camera" : source === "microphone" ? "Microphone" : "Camera and Microphone",
7667
- "."
7668
- ] })
7669
- };
7670
- if (failure.code === "media.device_not_found")
7671
- return {
7672
- icon,
7673
- type: "error",
7674
- message: /* @__PURE__ */ jsxs43(Fragment27, { children: [
7675
- /* @__PURE__ */ jsxs43("b", { style: { fontWeight: 600 }, children: [
7676
- "No ",
7677
- device,
7678
- " found."
7679
- ] }),
7680
- " Plug one in, or check that your Mac can see it in System Settings."
7681
- ] })
7682
- };
7683
- if (failure.code === "media.device_busy") return { icon, type: "error", message: `Your ${device} is being used by another app. Close it there, then try again.` };
7684
- if (failure.code === "media.device_constraint_invalid") return { icon, type: "error", message: `Your ${device} doesn\u2019t support these settings. Choose another ${device}, then try again.` };
7685
- if (failure.code === "media.capture_failed") return { icon, type: "error", message: `Your ${device} couldn\u2019t start. Check that it is connected and available, then try again.` };
7686
- return { icon, type: "error", message: failure.message };
8125
+ const presentation = commandFailurePresentation(failure, source, action, onRetry !== void 0);
8126
+ return { icon: /* @__PURE__ */ jsx68(CallEventIcon, { kind: source ?? "microphone", error: true }), type: "error", message: presentation.message, action: presentation.retry && onRetry ? { label: "Try again", onClick: onRetry } : void 0 };
7687
8127
  }
7688
8128
  function callEventToasts(previous, next) {
7689
8129
  if (previous.connection.status !== "live" || next.connection.status !== "live") return [];
@@ -7695,16 +8135,16 @@ function callEventToasts(previous, next) {
7695
8135
  if (!old && person.participantId !== next.self.participantId)
7696
8136
  events.push({
7697
8137
  icon: /* @__PURE__ */ jsx68(Avatar, { name: person.displayName, size: "xs", style: { width: 28, height: 28 }, generated: true, animated: false }),
7698
- message: /* @__PURE__ */ jsxs43(Fragment27, { children: [
8138
+ message: /* @__PURE__ */ jsxs44(Fragment30, { children: [
7699
8139
  /* @__PURE__ */ jsx68("b", { style: { fontWeight: 600 }, dir: "auto", children: person.displayName }),
7700
8140
  " ",
7701
8141
  "joined"
7702
8142
  ] })
7703
8143
  });
7704
- if (old && !old.handRaised && person.handRaised)
8144
+ if (old && !old.handRaised && person.handRaised && person.participantId !== next.self.participantId)
7705
8145
  events.push({
7706
8146
  icon: /* @__PURE__ */ jsx68(CallEventIcon, { kind: "hand" }),
7707
- message: /* @__PURE__ */ jsxs43(Fragment27, { children: [
8147
+ message: /* @__PURE__ */ jsxs44(Fragment30, { children: [
7708
8148
  /* @__PURE__ */ jsx68("b", { style: { fontWeight: 600 }, dir: "auto", children: person.displayName }),
7709
8149
  " ",
7710
8150
  "raised a hand"
@@ -7713,7 +8153,7 @@ function callEventToasts(previous, next) {
7713
8153
  if (old?.media.screenShare === "active" && person.media.screenShare === "inactive")
7714
8154
  events.push({
7715
8155
  icon: /* @__PURE__ */ jsx68(CallEventIcon, { kind: "screen" }),
7716
- message: /* @__PURE__ */ jsxs43(Fragment27, { children: [
8156
+ message: /* @__PURE__ */ jsxs44(Fragment30, { children: [
7717
8157
  /* @__PURE__ */ jsx68("b", { style: { fontWeight: 600 }, dir: "auto", children: person.displayName }),
7718
8158
  " ",
7719
8159
  "stopped sharing"
@@ -7725,7 +8165,7 @@ function callEventToasts(previous, next) {
7725
8165
  if (becameHost)
7726
8166
  events.push({
7727
8167
  icon: /* @__PURE__ */ jsx68(CallEventIcon, { kind: "host" }),
7728
- message: departedHost ? /* @__PURE__ */ jsxs43(Fragment27, { children: [
8168
+ message: departedHost ? /* @__PURE__ */ jsxs44(Fragment30, { children: [
7729
8169
  /* @__PURE__ */ jsx68("b", { style: { fontWeight: 600 }, dir: "auto", children: departedHost.displayName }),
7730
8170
  " left. You\u2019re now the host."
7731
8171
  ] }) : "You\u2019re now the host."
@@ -7734,7 +8174,7 @@ function callEventToasts(previous, next) {
7734
8174
  if (!after.has(person.participantId) && person.participantId !== next.self.participantId && !(person === departedHost && becameHost))
7735
8175
  events.push({
7736
8176
  icon: /* @__PURE__ */ jsx68(Avatar, { name: person.displayName, size: "xs", style: { width: 28, height: 28 }, generated: true, animated: false }),
7737
- message: /* @__PURE__ */ jsxs43(Fragment27, { children: [
8177
+ message: /* @__PURE__ */ jsxs44(Fragment30, { children: [
7738
8178
  /* @__PURE__ */ jsx68("b", { style: { fontWeight: 600 }, dir: "auto", children: person.displayName }),
7739
8179
  " ",
7740
8180
  "left"
@@ -7745,7 +8185,7 @@ function callEventToasts(previous, next) {
7745
8185
  if (wasRecording !== isRecording)
7746
8186
  events.push({
7747
8187
  icon: /* @__PURE__ */ jsx68(CallEventIcon, { kind: "recording", error: isRecording }),
7748
- message: isRecording ? /* @__PURE__ */ jsxs43(Fragment27, { children: [
8188
+ message: isRecording ? /* @__PURE__ */ jsxs44(Fragment30, { children: [
7749
8189
  /* @__PURE__ */ jsx68("b", { style: { fontWeight: 600 }, children: "Recording started." }),
7750
8190
  " Everyone in the call can see it."
7751
8191
  ] }) : "Recording stopped"
@@ -7754,13 +8194,13 @@ function callEventToasts(previous, next) {
7754
8194
  if (previous.media.selection[source] !== next.media.selection[source]) {
7755
8195
  const devices = source === "camera" ? next.media.devices.cameras : next.media.devices.microphones;
7756
8196
  const oldDevices = source === "camera" ? previous.media.devices.cameras : previous.media.devices.microphones;
7757
- const disconnected = oldDevices.find((device) => device.deviceId === previous.media.selection[source]);
7758
- if (!disconnected || devices.some((device) => device.deviceId === disconnected.deviceId)) continue;
7759
- const selected = devices.find((device) => device.deviceId === next.media.selection[source]);
8197
+ const disconnected = oldDevices.find((device2) => device2.deviceId === previous.media.selection[source]);
8198
+ if (!disconnected || devices.some((device2) => device2.deviceId === disconnected.deviceId)) continue;
8199
+ const selected = devices.find((device2) => device2.deviceId === next.media.selection[source]);
7760
8200
  if (selected)
7761
8201
  events.push({
7762
8202
  icon: /* @__PURE__ */ jsx68(CallEventIcon, { kind: source === "microphone" ? "microphone-on" : source }),
7763
- message: /* @__PURE__ */ jsxs43(Fragment27, { children: [
8203
+ message: /* @__PURE__ */ jsxs44(Fragment30, { children: [
7764
8204
  `${disconnected.label || source} disconnected. Switched to `,
7765
8205
  /* @__PURE__ */ jsx68("b", { style: { fontWeight: 600 }, children: selected.label || source })
7766
8206
  ] })
@@ -7773,7 +8213,7 @@ function callEventToasts(previous, next) {
7773
8213
  // src/components/toast-stack/useCallToasts.ts
7774
8214
  function useCallToasts() {
7775
8215
  const client = useSpaceClient();
7776
- const [toasts, setToasts] = useState38([]);
8216
+ const [toasts, setToasts] = useState41([]);
7777
8217
  const sequence = useRef30(0);
7778
8218
  useEffect32(() => {
7779
8219
  setToasts([]);
@@ -7797,18 +8237,7 @@ function useCallToasts() {
7797
8237
  const toast = mediaFailureToast(error, failedSource);
7798
8238
  if (toast) {
7799
8239
  const id = `call-event-${++sequence.current}`;
7800
- const action = failedSource === "screen" && error.recoverable ? {
7801
- label: "Try again",
7802
- onClick: () => {
7803
- void client.media.setScreenShareEnabled(true).then(
7804
- () => {
7805
- setToasts((current) => current.filter((event) => event.id !== id));
7806
- },
7807
- () => void 0
7808
- );
7809
- }
7810
- } : void 0;
7811
- setToasts((current) => [...current, { ...toast, action, failureCode: error.code, id }].slice(-20));
8240
+ setToasts((current) => [...current, { ...toast, failureCode: error.code, id }].slice(-20));
7812
8241
  }
7813
8242
  failedSource = void 0;
7814
8243
  });
@@ -7825,15 +8254,37 @@ function useCallToasts() {
7825
8254
  }
7826
8255
 
7827
8256
  // src/components/call-limbo/useCallLink.ts
7828
- import { useEffect as useEffect33, useState as useState39 } from "react";
8257
+ import { useEffect as useEffect33, useState as useState42 } from "react";
7829
8258
  function useCallLink(alone) {
7830
- const [open, setOpen] = useState39(alone);
8259
+ const [open, setOpen] = useState42(alone);
7831
8260
  useEffect33(() => {
7832
8261
  if (!alone) setOpen(false);
7833
8262
  }, [alone]);
7834
8263
  return { open, toggle: () => setOpen((current) => !current), close: () => setOpen(false) };
7835
8264
  }
7836
8265
 
8266
+ // src/components/toast-stack/NoticeButton.tsx
8267
+ import { useState as useState43 } from "react";
8268
+ import { jsx as jsx69, jsxs as jsxs45 } from "react/jsx-runtime";
8269
+ function NoticeButton({ toasts, onDismiss }) {
8270
+ const [open, setOpen] = useState43(false);
8271
+ if (toasts.length === 0) return null;
8272
+ const label = toasts.length === 1 ? "1 notice" : `${toasts.length} notices`;
8273
+ const trigger = /* @__PURE__ */ jsxs45("button", { type: "button", "data-part": "notice-button", className: "chalk-notice-button", "aria-label": `Show ${label}`, children: [
8274
+ /* @__PURE__ */ jsxs45("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
8275
+ /* @__PURE__ */ jsx69("circle", { cx: "12", cy: "12", r: "9" }),
8276
+ /* @__PURE__ */ jsx69("path", { d: "M12 7.5v5.5M12 16.5h.01" })
8277
+ ] }),
8278
+ toasts.length > 1 ? /* @__PURE__ */ jsx69("span", { children: toasts.length }) : null
8279
+ ] });
8280
+ return /* @__PURE__ */ jsx69(ChalkPopover, { open, onOpenChange: setOpen, tooltip: label, trigger, label: "Notices", className: "chalk-notice-list", children: toasts.map((toast) => /* @__PURE__ */ jsxs45("div", { "data-part": "notice", "data-type": toast.type ?? "info", className: "chalk-notice", children: [
8281
+ toast.icon,
8282
+ /* @__PURE__ */ jsx69("span", { children: toast.message }),
8283
+ toast.action ? /* @__PURE__ */ jsx69(ChalkButton, { onClick: toast.action.onClick, children: toast.action.label }) : null,
8284
+ /* @__PURE__ */ jsx69("button", { type: "button", className: "chalk-dismiss", "aria-label": "Dismiss", onClick: () => onDismiss(toast.id), children: /* @__PURE__ */ jsx69("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx69("path", { d: "m6 6 12 12M6 18 18 6" }) }) })
8285
+ ] }, toast.id)) });
8286
+ }
8287
+
7837
8288
  // src/components/board/board-toasts.ts
7838
8289
  import { createContext as createContext3, useCallback as useCallback12, useContext as useContext3, useEffect as useEffect34, useMemo as useMemo15 } from "react";
7839
8290
  var BoardToastContext = createContext3(() => void 0);
@@ -7856,13 +8307,16 @@ function useBoardToast(lifetime) {
7856
8307
  }
7857
8308
 
7858
8309
  // src/components/stage/SpaceStage.tsx
7859
- import { useCallback as useCallback14, useEffect as useEffect38, useMemo as useMemo17, useState as useState44 } from "react";
8310
+ import { useCallback as useCallback15, useEffect as useEffect38, useMemo as useMemo18, useState as useState48 } from "react";
8311
+
8312
+ // src/internal/whiteboard-view-limits.ts
8313
+ var WHITEBOARD_VIEW_PEOPLE_MAX = 500;
7860
8314
 
7861
8315
  // src/components/board/board-history.ts
7862
- import { useEffect as useEffect35, useState as useState40 } from "react";
8316
+ import { useEffect as useEffect35, useState as useState44 } from "react";
7863
8317
  import { observeEngineHistory } from "@q9labsai/chalk-whiteboard/react";
7864
8318
  function useEngineHistory(root, engine) {
7865
- const [history, setHistory] = useState40({ canUndo: false, canRedo: false });
8319
+ const [history, setHistory] = useState44({ canUndo: false, canRedo: false });
7866
8320
  useEffect35(() => {
7867
8321
  const canvas = root.current?.querySelector(".excalidraw");
7868
8322
  if (!engine || !canvas) return;
@@ -7872,27 +8326,11 @@ function useEngineHistory(root, engine) {
7872
8326
  }
7873
8327
 
7874
8328
  // src/components/board/board-view-versions.ts
7875
- var ViewVersions = class {
7876
- #versions = /* @__PURE__ */ new Map();
7877
- isNew(view) {
7878
- if (!view.originParticipantId) return true;
7879
- return view.originSequence > (this.#versions.get(`${view.originParticipantId}:${view.originEpoch ?? ""}`) ?? 0);
7880
- }
7881
- remember(view) {
7882
- if (!view.originParticipantId || !this.isNew(view)) return;
7883
- const origin = `${view.originParticipantId}:${view.originEpoch ?? ""}`;
7884
- this.#versions.delete(origin);
7885
- this.#versions.set(origin, view.originSequence);
7886
- if (this.#versions.size > 256) {
7887
- const oldest = this.#versions.keys().next().value;
7888
- if (oldest !== void 0) this.#versions.delete(oldest);
7889
- }
7890
- }
7891
- };
8329
+ import { ChalkWhiteboardViewVersions } from "@q9labsai/chalk-client";
7892
8330
 
7893
8331
  // src/components/board/Board.tsx
7894
8332
  import { WhiteboardCanvas as WhiteboardCanvas2 } from "@q9labsai/chalk-whiteboard/react";
7895
- import { useCallback as useCallback13, useEffect as useEffect37, useMemo as useMemo16, useRef as useRef33, useState as useState43 } from "react";
8333
+ import { useCallback as useCallback14, useEffect as useEffect37, useMemo as useMemo17, useRef as useRef33, useState as useState47 } from "react";
7896
8334
 
7897
8335
  // src/components/board/board-model.ts
7898
8336
  import { ChalkWhiteboardViewBounds } from "@q9labsai/chalk-client";
@@ -7906,6 +8344,11 @@ var ISLAND_PLACES = [
7906
8344
  { id: "bottom", label: "Bottom" },
7907
8345
  { id: "top", label: "Top" }
7908
8346
  ];
8347
+ var DRAW_POLICIES = [
8348
+ { id: "everyone", label: "Everyone" },
8349
+ { id: "hosts", label: "Only hosts" },
8350
+ { id: "picked", label: "Picked people" }
8351
+ ];
7909
8352
  var PEN_COLORS = [
7910
8353
  { id: "ink", label: "Ink" },
7911
8354
  { id: "red", label: "Red" },
@@ -8131,88 +8574,88 @@ function sceneSignature(engine) {
8131
8574
  }
8132
8575
 
8133
8576
  // src/components/board/BoardCursors.tsx
8134
- import { useEffect as useEffect36, useRef as useRef31, useState as useState41 } from "react";
8577
+ import { useEffect as useEffect36, useRef as useRef31, useState as useState45 } from "react";
8135
8578
 
8136
8579
  // src/components/board/BoardIcon.tsx
8137
- import { Fragment as Fragment28, jsx as jsx69, jsxs as jsxs44 } from "react/jsx-runtime";
8580
+ import { Fragment as Fragment31, jsx as jsx70, jsxs as jsxs46 } from "react/jsx-runtime";
8138
8581
  var GLYPHS2 = {
8139
- select: /* @__PURE__ */ jsx69("path", { d: "M5 3l14 7-6.5 2L10 19z" }),
8140
- hand: /* @__PURE__ */ jsx69("path", { d: "M18 11V6a2 2 0 0 0-4 0M14 10V4a2 2 0 0 0-4 0v2M10 10.5V6a2 2 0 0 0-4 0v8M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.9-6-2.3l-3.6-3.6a2 2 0 0 1 2.8-2.8L7 15" }),
8141
- pen: /* @__PURE__ */ jsx69("path", { d: "M12 20h9M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z" }),
8142
- highlighter: /* @__PURE__ */ jsxs44(Fragment28, { children: [
8143
- /* @__PURE__ */ jsx69("path", { d: "M9 11l-6 6v3h9l3-3" }),
8144
- /* @__PURE__ */ jsx69("path", { d: "M22 12l-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4" })
8582
+ select: /* @__PURE__ */ jsx70("path", { d: "M5 3l14 7-6.5 2L10 19z" }),
8583
+ hand: /* @__PURE__ */ jsx70("path", { d: "M18 11V6a2 2 0 0 0-4 0M14 10V4a2 2 0 0 0-4 0v2M10 10.5V6a2 2 0 0 0-4 0v8M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.9-6-2.3l-3.6-3.6a2 2 0 0 1 2.8-2.8L7 15" }),
8584
+ pen: /* @__PURE__ */ jsx70("path", { d: "M12 20h9M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z" }),
8585
+ highlighter: /* @__PURE__ */ jsxs46(Fragment31, { children: [
8586
+ /* @__PURE__ */ jsx70("path", { d: "M9 11l-6 6v3h9l3-3" }),
8587
+ /* @__PURE__ */ jsx70("path", { d: "M22 12l-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4" })
8145
8588
  ] }),
8146
- rectangle: /* @__PURE__ */ jsx69("rect", { x: "3", y: "5", width: "18", height: "14", rx: "2" }),
8147
- ellipse: /* @__PURE__ */ jsx69("ellipse", { cx: "12", cy: "12", rx: "9", ry: "6.5" }),
8148
- diamond: /* @__PURE__ */ jsx69("path", { d: "M12 3l9 9-9 9-9-9z" }),
8149
- arrow: /* @__PURE__ */ jsx69("path", { d: "M5 19L19 5M9 5h10v10" }),
8150
- line: /* @__PURE__ */ jsx69("path", { d: "M4 20L20 4" }),
8151
- text: /* @__PURE__ */ jsx69("path", { d: "M5 6V4h14v2M12 4v16M9 20h6" }),
8152
- note: /* @__PURE__ */ jsxs44(Fragment28, { children: [
8153
- /* @__PURE__ */ jsx69("path", { d: "M5 3h14a2 2 0 0 1 2 2v9l-7 7H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z" }),
8154
- /* @__PURE__ */ jsx69("path", { d: "M14 21v-5a2 2 0 0 1 2-2h5" })
8589
+ rectangle: /* @__PURE__ */ jsx70("rect", { x: "3", y: "5", width: "18", height: "14", rx: "2" }),
8590
+ ellipse: /* @__PURE__ */ jsx70("ellipse", { cx: "12", cy: "12", rx: "9", ry: "6.5" }),
8591
+ diamond: /* @__PURE__ */ jsx70("path", { d: "M12 3l9 9-9 9-9-9z" }),
8592
+ arrow: /* @__PURE__ */ jsx70("path", { d: "M5 19L19 5M9 5h10v10" }),
8593
+ line: /* @__PURE__ */ jsx70("path", { d: "M4 20L20 4" }),
8594
+ text: /* @__PURE__ */ jsx70("path", { d: "M5 6V4h14v2M12 4v16M9 20h6" }),
8595
+ note: /* @__PURE__ */ jsxs46(Fragment31, { children: [
8596
+ /* @__PURE__ */ jsx70("path", { d: "M5 3h14a2 2 0 0 1 2 2v9l-7 7H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z" }),
8597
+ /* @__PURE__ */ jsx70("path", { d: "M14 21v-5a2 2 0 0 1 2-2h5" })
8155
8598
  ] }),
8156
- eraser: /* @__PURE__ */ jsx69("path", { d: "M7 21h10M5.5 13.5l7-7a2 2 0 0 1 2.8 0l3.2 3.2a2 2 0 0 1 0 2.8L11 19H8l-2.5-2.5a2 2 0 0 1 0-3z" }),
8157
- laser: /* @__PURE__ */ jsxs44(Fragment28, { children: [
8158
- /* @__PURE__ */ jsx69("circle", { cx: "12", cy: "12", r: "2.5", fill: "currentColor" }),
8159
- /* @__PURE__ */ jsx69("path", { d: "M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M18.4 5.6l-2.1 2.1M7.7 16.3l-2.1 2.1" })
8599
+ eraser: /* @__PURE__ */ jsx70("path", { d: "M7 21h10M5.5 13.5l7-7a2 2 0 0 1 2.8 0l3.2 3.2a2 2 0 0 1 0 2.8L11 19H8l-2.5-2.5a2 2 0 0 1 0-3z" }),
8600
+ laser: /* @__PURE__ */ jsxs46(Fragment31, { children: [
8601
+ /* @__PURE__ */ jsx70("circle", { cx: "12", cy: "12", r: "2.5", fill: "currentColor" }),
8602
+ /* @__PURE__ */ jsx70("path", { d: "M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M18.4 5.6l-2.1 2.1M7.7 16.3l-2.1 2.1" })
8160
8603
  ] }),
8161
- undo: /* @__PURE__ */ jsxs44(Fragment28, { children: [
8162
- /* @__PURE__ */ jsx69("path", { d: "M9 14L4 9l5-5" }),
8163
- /* @__PURE__ */ jsx69("path", { d: "M4 9h10.5a5.5 5.5 0 0 1 0 11H11" })
8604
+ undo: /* @__PURE__ */ jsxs46(Fragment31, { children: [
8605
+ /* @__PURE__ */ jsx70("path", { d: "M9 14L4 9l5-5" }),
8606
+ /* @__PURE__ */ jsx70("path", { d: "M4 9h10.5a5.5 5.5 0 0 1 0 11H11" })
8164
8607
  ] }),
8165
- redo: /* @__PURE__ */ jsxs44(Fragment28, { children: [
8166
- /* @__PURE__ */ jsx69("path", { d: "M15 14l5-5-5-5" }),
8167
- /* @__PURE__ */ jsx69("path", { d: "M20 9H9.5a5.5 5.5 0 0 0 0 11H13" })
8608
+ redo: /* @__PURE__ */ jsxs46(Fragment31, { children: [
8609
+ /* @__PURE__ */ jsx70("path", { d: "M15 14l5-5-5-5" }),
8610
+ /* @__PURE__ */ jsx70("path", { d: "M20 9H9.5a5.5 5.5 0 0 0 0 11H13" })
8168
8611
  ] }),
8169
- minus: /* @__PURE__ */ jsx69("path", { d: "M5 12h14" }),
8170
- plus: /* @__PURE__ */ jsx69("path", { d: "M12 5v14M5 12h14" }),
8171
- fit: /* @__PURE__ */ jsx69("path", { d: "M4 9V5a1 1 0 0 1 1-1h4M20 9V5a1 1 0 0 0-1-1h-4M4 15v4a1 1 0 0 0 1 1h4M20 15v4a1 1 0 0 1-1 1h-4" }),
8172
- download: /* @__PURE__ */ jsx69("path", { d: "M12 3v12M7 10l5 5 5-5M4 21h16" }),
8173
- copy: /* @__PURE__ */ jsxs44(Fragment28, { children: [
8174
- /* @__PURE__ */ jsx69("rect", { x: "9", y: "9", width: "12", height: "12", rx: "2" }),
8175
- /* @__PURE__ */ jsx69("path", { d: "M5 15V5a2 2 0 0 1 2-2h10" })
8612
+ minus: /* @__PURE__ */ jsx70("path", { d: "M5 12h14" }),
8613
+ plus: /* @__PURE__ */ jsx70("path", { d: "M12 5v14M5 12h14" }),
8614
+ fit: /* @__PURE__ */ jsx70("path", { d: "M4 9V5a1 1 0 0 1 1-1h4M20 9V5a1 1 0 0 0-1-1h-4M4 15v4a1 1 0 0 0 1 1h4M20 15v4a1 1 0 0 1-1 1h-4" }),
8615
+ download: /* @__PURE__ */ jsx70("path", { d: "M12 3v12M7 10l5 5 5-5M4 21h16" }),
8616
+ copy: /* @__PURE__ */ jsxs46(Fragment31, { children: [
8617
+ /* @__PURE__ */ jsx70("rect", { x: "9", y: "9", width: "12", height: "12", rx: "2" }),
8618
+ /* @__PURE__ */ jsx70("path", { d: "M5 15V5a2 2 0 0 1 2-2h10" })
8176
8619
  ] }),
8177
- trash: /* @__PURE__ */ jsx69("path", { d: "M4 7h16M10 11v6M14 11v6M6 7l1 13h10l1-13M9 7V4h6v3" }),
8178
- close: /* @__PURE__ */ jsx69("path", { d: "M6 6l12 12M18 6L6 18" }),
8179
- check: /* @__PURE__ */ jsx69("path", { d: "M5 12l5 5 9-10" }),
8180
- target: /* @__PURE__ */ jsxs44(Fragment28, { children: [
8181
- /* @__PURE__ */ jsx69("circle", { cx: "12", cy: "12", r: "9" }),
8182
- /* @__PURE__ */ jsx69("circle", { cx: "12", cy: "12", r: "4" })
8620
+ trash: /* @__PURE__ */ jsx70("path", { d: "M4 7h16M10 11v6M14 11v6M6 7l1 13h10l1-13M9 7V4h6v3" }),
8621
+ close: /* @__PURE__ */ jsx70("path", { d: "M6 6l12 12M18 6L6 18" }),
8622
+ check: /* @__PURE__ */ jsx70("path", { d: "M5 12l5 5 9-10" }),
8623
+ target: /* @__PURE__ */ jsxs46(Fragment31, { children: [
8624
+ /* @__PURE__ */ jsx70("circle", { cx: "12", cy: "12", r: "9" }),
8625
+ /* @__PURE__ */ jsx70("circle", { cx: "12", cy: "12", r: "4" })
8183
8626
  ] }),
8184
- eye: /* @__PURE__ */ jsxs44(Fragment28, { children: [
8185
- /* @__PURE__ */ jsx69("path", { d: "M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z" }),
8186
- /* @__PURE__ */ jsx69("circle", { cx: "12", cy: "12", r: "3" })
8627
+ eye: /* @__PURE__ */ jsxs46(Fragment31, { children: [
8628
+ /* @__PURE__ */ jsx70("path", { d: "M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z" }),
8629
+ /* @__PURE__ */ jsx70("circle", { cx: "12", cy: "12", r: "3" })
8187
8630
  ] }),
8188
- wifiOff: /* @__PURE__ */ jsx69("path", { d: "M2 2l20 20M8.5 16.5a5 5 0 0 1 7 0M5 12.9a10 10 0 0 1 5-2.7M10.7 5.1A16 16 0 0 1 22 9M2 8.8a15 15 0 0 1 4.2-2.5M12 20h.01" }),
8189
- lock: /* @__PURE__ */ jsxs44(Fragment28, { children: [
8190
- /* @__PURE__ */ jsx69("rect", { x: "5", y: "11", width: "14", height: "9", rx: "2" }),
8191
- /* @__PURE__ */ jsx69("path", { d: "M8 11V8a4 4 0 0 1 8 0v3" })
8631
+ wifiOff: /* @__PURE__ */ jsx70("path", { d: "M2 2l20 20M8.5 16.5a5 5 0 0 1 7 0M5 12.9a10 10 0 0 1 5-2.7M10.7 5.1A16 16 0 0 1 22 9M2 8.8a15 15 0 0 1 4.2-2.5M12 20h.01" }),
8632
+ lock: /* @__PURE__ */ jsxs46(Fragment31, { children: [
8633
+ /* @__PURE__ */ jsx70("rect", { x: "5", y: "11", width: "14", height: "9", rx: "2" }),
8634
+ /* @__PURE__ */ jsx70("path", { d: "M8 11V8a4 4 0 0 1 8 0v3" })
8192
8635
  ] }),
8193
- more: /* @__PURE__ */ jsxs44(Fragment28, { children: [
8194
- /* @__PURE__ */ jsx69("circle", { cx: "5", cy: "12", r: "1.2" }),
8195
- /* @__PURE__ */ jsx69("circle", { cx: "12", cy: "12", r: "1.2" }),
8196
- /* @__PURE__ */ jsx69("circle", { cx: "19", cy: "12", r: "1.2" })
8636
+ more: /* @__PURE__ */ jsxs46(Fragment31, { children: [
8637
+ /* @__PURE__ */ jsx70("circle", { cx: "5", cy: "12", r: "1.2" }),
8638
+ /* @__PURE__ */ jsx70("circle", { cx: "12", cy: "12", r: "1.2" }),
8639
+ /* @__PURE__ */ jsx70("circle", { cx: "19", cy: "12", r: "1.2" })
8197
8640
  ] }),
8198
- chevron: /* @__PURE__ */ jsx69("path", { d: "M6 9l6 6 6-6" })
8641
+ chevron: /* @__PURE__ */ jsx70("path", { d: "M6 9l6 6 6-6" })
8199
8642
  };
8200
8643
  function BoardIcon({ name, size = 20, className }) {
8201
- return /* @__PURE__ */ jsx69("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", className, "aria-hidden": "true", children: GLYPHS2[name] });
8644
+ return /* @__PURE__ */ jsx70("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", className, "aria-hidden": "true", children: GLYPHS2[name] });
8202
8645
  }
8203
8646
  function GripIcon({ className }) {
8204
- return /* @__PURE__ */ jsx69("svg", { width: "10", height: "18", viewBox: "0 0 10 18", fill: "currentColor", className, "aria-hidden": "true", children: [3, 9, 15].flatMap((y) => [/* @__PURE__ */ jsx69("circle", { cx: "2.5", cy: y, r: "1.5" }, `l${y}`), /* @__PURE__ */ jsx69("circle", { cx: "7.5", cy: y, r: "1.5" }, `r${y}`)]) });
8647
+ return /* @__PURE__ */ jsx70("svg", { width: "10", height: "18", viewBox: "0 0 10 18", fill: "currentColor", className, "aria-hidden": "true", children: [3, 9, 15].flatMap((y) => [/* @__PURE__ */ jsx70("circle", { cx: "2.5", cy: y, r: "1.5" }, `l${y}`), /* @__PURE__ */ jsx70("circle", { cx: "7.5", cy: y, r: "1.5" }, `r${y}`)]) });
8205
8648
  }
8206
8649
  function CursorArrow({ color }) {
8207
8650
  const path = "M3.2 2.6V20.2L8 15.7L11.3 22.6L14.6 21L11.4 14.2L17.9 13.6Z";
8208
- return /* @__PURE__ */ jsxs44("svg", { className: "chalk-board-cursor-arrow", width: "22", height: "26", viewBox: "0 0 22 26", fill: "none", "aria-hidden": "true", children: [
8209
- /* @__PURE__ */ jsx69("path", { d: path, fill: "#fff", stroke: "#fff", strokeWidth: "5", strokeLinejoin: "round" }),
8210
- /* @__PURE__ */ jsx69("path", { d: path, fill: color, stroke: color, strokeWidth: "2.2", strokeLinejoin: "round" })
8651
+ return /* @__PURE__ */ jsxs46("svg", { className: "chalk-board-cursor-arrow", width: "22", height: "26", viewBox: "0 0 22 26", fill: "none", "aria-hidden": "true", children: [
8652
+ /* @__PURE__ */ jsx70("path", { d: path, fill: "#fff", stroke: "#fff", strokeWidth: "5", strokeLinejoin: "round" }),
8653
+ /* @__PURE__ */ jsx70("path", { d: path, fill: color, stroke: color, strokeWidth: "2.2", strokeLinejoin: "round" })
8211
8654
  ] });
8212
8655
  }
8213
8656
 
8214
8657
  // src/components/board/BoardCursors.tsx
8215
- import { jsx as jsx70, jsxs as jsxs45 } from "react/jsx-runtime";
8658
+ import { jsx as jsx71, jsxs as jsxs47 } from "react/jsx-runtime";
8216
8659
  var REST_RATE = 16;
8217
8660
  var STALE_MS = 1e4;
8218
8661
  var NAME_HOLD_MS = 700;
@@ -8230,7 +8673,7 @@ function rememberCursor(cursors, cursor) {
8230
8673
  }
8231
8674
  function BoardCursors({ cursorsRef, cameraRef, colorFor, hidden }) {
8232
8675
  const layer = useRef31(null);
8233
- const [people, setPeople] = useState41([]);
8676
+ const [people, setPeople] = useState45([]);
8234
8677
  const tracked = useRef31(/* @__PURE__ */ new Map());
8235
8678
  const pointer = useRef31(null);
8236
8679
  useEffect36(() => {
@@ -8308,18 +8751,18 @@ function BoardCursors({ cursorsRef, cameraRef, colorFor, hidden }) {
8308
8751
  board.removeEventListener("pointerleave", leave);
8309
8752
  };
8310
8753
  }, []);
8311
- return /* @__PURE__ */ jsx70("div", { ref: layer, className: "chalk-board-cursors", "aria-hidden": "true", "data-hidden": hidden || void 0, children: people.map((person) => /* @__PURE__ */ jsxs45("div", { className: "chalk-board-cursor", "data-cursor": person.id, children: [
8312
- /* @__PURE__ */ jsx70(CursorArrow, { color: colorFor(person.id) }),
8313
- /* @__PURE__ */ jsxs45("span", { className: "chalk-board-cursor-tag", children: [
8314
- /* @__PURE__ */ jsx70("span", { className: "chalk-board-cursor-name", style: { background: colorFor(person.id) }, children: person.name.split(" ")[0] }),
8315
- /* @__PURE__ */ jsx70(Avatar, { name: person.name, size: "xs", animated: false, className: "chalk-board-cursor-avatar" })
8754
+ return /* @__PURE__ */ jsx71("div", { ref: layer, className: "chalk-board-cursors", "aria-hidden": "true", "data-hidden": hidden || void 0, children: people.map((person) => /* @__PURE__ */ jsxs47("div", { className: "chalk-board-cursor", "data-cursor": person.id, children: [
8755
+ /* @__PURE__ */ jsx71(CursorArrow, { color: colorFor(person.id) }),
8756
+ /* @__PURE__ */ jsxs47("span", { className: "chalk-board-cursor-tag", children: [
8757
+ /* @__PURE__ */ jsx71("span", { className: "chalk-board-cursor-name", style: { background: colorFor(person.id) }, children: person.name.split(" ")[0] }),
8758
+ /* @__PURE__ */ jsx71(Avatar, { name: person.name, size: "xs", animated: false, className: "chalk-board-cursor-avatar" })
8316
8759
  ] })
8317
8760
  ] }, person.id)) });
8318
8761
  }
8319
8762
 
8320
8763
  // src/components/board/BoardIsland.tsx
8321
- import { useLayoutEffect as useLayoutEffect4, useRef as useRef32, useState as useState42 } from "react";
8322
- import { Fragment as Fragment29, jsx as jsx71, jsxs as jsxs46 } from "react/jsx-runtime";
8764
+ import { useLayoutEffect as useLayoutEffect7, useRef as useRef32, useState as useState46 } from "react";
8765
+ import { Fragment as Fragment32, jsx as jsx72, jsxs as jsxs48 } from "react/jsx-runtime";
8323
8766
  var TOOLS = [
8324
8767
  [
8325
8768
  { tool: "selection", label: "Select", key: "V", glyph: "select" },
@@ -8341,41 +8784,41 @@ var TOOLS = [
8341
8784
  var LONG_PRESS_MS = 500;
8342
8785
  var DRAG_START_PX = 4;
8343
8786
  function Tip({ label, keyName, children }) {
8344
- return /* @__PURE__ */ jsxs46(ChalkTooltip, { trigger: children, children: [
8787
+ return /* @__PURE__ */ jsxs48(ChalkTooltip, { trigger: children, children: [
8345
8788
  label,
8346
- keyName ? /* @__PURE__ */ jsx71("kbd", { className: "chalk-board-kbd", children: keyName }) : null
8789
+ keyName ? /* @__PURE__ */ jsx72("kbd", { className: "chalk-board-kbd", children: keyName }) : null
8347
8790
  ] });
8348
8791
  }
8349
8792
  function ToolButton({ entry, active, onTool }) {
8350
- return /* @__PURE__ */ jsx71(Tip, { label: entry.label, keyName: entry.key, children: /* @__PURE__ */ jsx71("button", { type: "button", className: "chalk-board-tool", "aria-label": entry.label, "aria-keyshortcuts": entry.key, "aria-pressed": active, onClick: () => onTool(entry.tool), children: /* @__PURE__ */ jsx71(BoardIcon, { name: entry.glyph }) }) });
8793
+ return /* @__PURE__ */ jsx72(Tip, { label: entry.label, keyName: entry.key, children: /* @__PURE__ */ jsx72("button", { type: "button", className: "chalk-board-tool", "aria-label": entry.label, "aria-keyshortcuts": entry.key, "aria-pressed": active, onClick: () => onTool(entry.tool), children: /* @__PURE__ */ jsx72(BoardIcon, { name: entry.glyph }) }) });
8351
8794
  }
8352
8795
  function ShapesButton({ tool, shape, onTool, side }) {
8353
8796
  const active = SHAPES.some((entry) => entry.id === tool);
8354
- return /* @__PURE__ */ jsx71(
8797
+ return /* @__PURE__ */ jsx72(
8355
8798
  ChalkMenu,
8356
8799
  {
8357
8800
  side,
8358
- trigger: /* @__PURE__ */ jsxs46("button", { type: "button", className: "chalk-board-tool", "aria-label": "Shapes", "aria-keyshortcuts": "R", "aria-pressed": active, children: [
8359
- /* @__PURE__ */ jsx71(BoardIcon, { name: shape }),
8360
- /* @__PURE__ */ jsx71(BoardIcon, { name: "chevron", size: 9, className: "chalk-board-tool-caret" })
8801
+ trigger: /* @__PURE__ */ jsxs48("button", { type: "button", className: "chalk-board-tool", "aria-label": "Shapes", "aria-keyshortcuts": "R", "aria-pressed": active, children: [
8802
+ /* @__PURE__ */ jsx72(BoardIcon, { name: shape }),
8803
+ /* @__PURE__ */ jsx72(BoardIcon, { name: "chevron", size: 9, className: "chalk-board-tool-caret" })
8361
8804
  ] }),
8362
- children: SHAPES.map((entry) => /* @__PURE__ */ jsxs46(ChalkMenuItem, { checked: tool === entry.id, onClick: () => onTool(entry.id), children: [
8363
- /* @__PURE__ */ jsx71(BoardIcon, { name: entry.id, size: 18 }),
8805
+ children: SHAPES.map((entry) => /* @__PURE__ */ jsxs48(ChalkMenuItem, { checked: tool === entry.id, onClick: () => onTool(entry.id), children: [
8806
+ /* @__PURE__ */ jsx72(BoardIcon, { name: entry.id, size: 18 }),
8364
8807
  entry.label,
8365
- /* @__PURE__ */ jsx71("kbd", { className: "chalk-board-menu-kbd", children: entry.key })
8808
+ /* @__PURE__ */ jsx72("kbd", { className: "chalk-board-menu-kbd", children: entry.key })
8366
8809
  ] }, entry.id))
8367
8810
  }
8368
8811
  );
8369
8812
  }
8370
8813
  function ColorPanel({ color, width, surface, theme, onColor, onWidth }) {
8371
- return /* @__PURE__ */ jsxs46("div", { className: "chalk-board-colors", children: [
8372
- /* @__PURE__ */ jsx71("div", { role: "radiogroup", "aria-label": "Colour", className: "chalk-board-swatches", children: PEN_COLORS.map((entry) => /* @__PURE__ */ jsx71("button", { type: "button", role: "radio", "aria-checked": color === entry.id, "aria-label": entry.label, className: "chalk-board-swatch", onClick: () => onColor(entry.id), children: /* @__PURE__ */ jsx71("i", { style: { background: penHex(entry.id, surface, theme) } }) }, entry.id)) }),
8373
- /* @__PURE__ */ jsx71("div", { role: "radiogroup", "aria-label": "Stroke width", className: "chalk-board-swatches", children: PEN_WIDTHS.map((entry) => /* @__PURE__ */ jsx71("button", { type: "button", role: "radio", "aria-checked": width === entry.id, "aria-label": entry.label, className: "chalk-board-width", onClick: () => onWidth(entry.id), children: /* @__PURE__ */ jsx71("i", { style: { height: entry.units } }) }, entry.id)) })
8814
+ return /* @__PURE__ */ jsxs48("div", { className: "chalk-board-colors", children: [
8815
+ /* @__PURE__ */ jsx72("div", { role: "radiogroup", "aria-label": "Colour", className: "chalk-board-swatches", children: PEN_COLORS.map((entry) => /* @__PURE__ */ jsx72("button", { type: "button", role: "radio", "aria-checked": color === entry.id, "aria-label": entry.label, className: "chalk-board-swatch", onClick: () => onColor(entry.id), children: /* @__PURE__ */ jsx72("i", { style: { background: penHex(entry.id, surface, theme) } }) }, entry.id)) }),
8816
+ /* @__PURE__ */ jsx72("div", { role: "radiogroup", "aria-label": "Stroke width", className: "chalk-board-swatches", children: PEN_WIDTHS.map((entry) => /* @__PURE__ */ jsx72("button", { type: "button", role: "radio", "aria-checked": width === entry.id, "aria-label": entry.label, className: "chalk-board-width", onClick: () => onWidth(entry.id), children: /* @__PURE__ */ jsx72("i", { style: { height: entry.units } }) }, entry.id)) })
8374
8817
  ] });
8375
8818
  }
8376
8819
  function ColorButton(props) {
8377
8820
  const swatch = penHex(props.color, props.surface, props.theme);
8378
- return /* @__PURE__ */ jsx71(
8821
+ return /* @__PURE__ */ jsx72(
8379
8822
  ChalkPopover,
8380
8823
  {
8381
8824
  label: "Colour and width",
@@ -8383,8 +8826,8 @@ function ColorButton(props) {
8383
8826
  open: props.open,
8384
8827
  onOpenChange: props.onOpenChange,
8385
8828
  className: "chalk-board-color-popover",
8386
- trigger: props.trigger ?? /* @__PURE__ */ jsx71("button", { type: "button", className: "chalk-board-tool", "aria-label": `Colour: ${PEN_COLORS.find((entry) => entry.id === props.color).label}`, children: /* @__PURE__ */ jsx71("span", { className: "chalk-board-tool-swatch", style: { background: swatch } }) }),
8387
- children: /* @__PURE__ */ jsx71(ColorPanel, { ...props })
8829
+ trigger: props.trigger ?? /* @__PURE__ */ jsx72("button", { type: "button", className: "chalk-board-tool", "aria-label": `Colour: ${PEN_COLORS.find((entry) => entry.id === props.color).label}`, children: /* @__PURE__ */ jsx72("span", { className: "chalk-board-tool-swatch", style: { background: swatch } }) }),
8830
+ children: /* @__PURE__ */ jsx72(ColorPanel, { ...props })
8388
8831
  }
8389
8832
  );
8390
8833
  }
@@ -8401,20 +8844,20 @@ function roveFocus(event, vertical) {
8401
8844
  buttons[next]?.focus();
8402
8845
  }
8403
8846
  function Separator() {
8404
- return /* @__PURE__ */ jsx71("span", { className: "chalk-board-separator", "aria-hidden": "true" });
8847
+ return /* @__PURE__ */ jsx72("span", { className: "chalk-board-separator", "aria-hidden": "true" });
8405
8848
  }
8406
8849
  function ToolRow({ tool, shape, onTool, place, canUndo, canRedo, onUndo, onRedo, ...colors }) {
8407
- const groups = TOOLS.map((group, index) => /* @__PURE__ */ jsxs46("span", { className: "chalk-board-group", children: [
8408
- group.map((entry) => /* @__PURE__ */ jsx71(ToolButton, { entry, active: tool === entry.tool, onTool }, entry.tool)),
8409
- index === 1 ? /* @__PURE__ */ jsx71(ShapesButton, { tool, shape, onTool, side: place === "top" ? "bottom" : "top" }) : null
8850
+ const groups = TOOLS.map((group, index) => /* @__PURE__ */ jsxs48("span", { className: "chalk-board-group", children: [
8851
+ group.map((entry) => /* @__PURE__ */ jsx72(ToolButton, { entry, active: tool === entry.tool, onTool }, entry.tool)),
8852
+ index === 1 ? /* @__PURE__ */ jsx72(ShapesButton, { tool, shape, onTool, side: place === "top" ? "bottom" : "top" }) : null
8410
8853
  ] }, index));
8411
- return /* @__PURE__ */ jsxs46(Fragment29, { children: [
8412
- groups.flatMap((group, index) => [index > 0 ? /* @__PURE__ */ jsx71(Separator, {}, `s${index}`) : null, group]),
8413
- /* @__PURE__ */ jsx71(Separator, {}),
8414
- /* @__PURE__ */ jsx71(Tip, { label: "Undo", keyName: "\u2318Z", children: /* @__PURE__ */ jsx71("button", { type: "button", className: "chalk-board-tool", "aria-label": "Undo", disabled: !canUndo, onClick: onUndo, children: /* @__PURE__ */ jsx71(BoardIcon, { name: "undo" }) }) }),
8415
- /* @__PURE__ */ jsx71(Tip, { label: "Redo", keyName: "\u21E7\u2318Z", children: /* @__PURE__ */ jsx71("button", { type: "button", className: "chalk-board-tool", "aria-label": "Redo", disabled: !canRedo, onClick: onRedo, children: /* @__PURE__ */ jsx71(BoardIcon, { name: "redo" }) }) }),
8416
- /* @__PURE__ */ jsx71(Separator, {}),
8417
- /* @__PURE__ */ jsx71(ColorButton, { ...colors })
8854
+ return /* @__PURE__ */ jsxs48(Fragment32, { children: [
8855
+ groups.flatMap((group, index) => [index > 0 ? /* @__PURE__ */ jsx72(Separator, {}, `s${index}`) : null, group]),
8856
+ /* @__PURE__ */ jsx72(Separator, {}),
8857
+ /* @__PURE__ */ jsx72(Tip, { label: "Undo", keyName: "\u2318Z", children: /* @__PURE__ */ jsx72("button", { type: "button", className: "chalk-board-tool", "aria-label": "Undo", disabled: !canUndo, onClick: onUndo, children: /* @__PURE__ */ jsx72(BoardIcon, { name: "undo" }) }) }),
8858
+ /* @__PURE__ */ jsx72(Tip, { label: "Redo", keyName: "\u21E7\u2318Z", children: /* @__PURE__ */ jsx72("button", { type: "button", className: "chalk-board-tool", "aria-label": "Redo", disabled: !canRedo, onClick: onRedo, children: /* @__PURE__ */ jsx72(BoardIcon, { name: "redo" }) }) }),
8859
+ /* @__PURE__ */ jsx72(Separator, {}),
8860
+ /* @__PURE__ */ jsx72(ColorButton, { ...colors })
8418
8861
  ] });
8419
8862
  }
8420
8863
  function nearestPlace(board, x, y) {
@@ -8426,18 +8869,18 @@ function BoardIsland(props) {
8426
8869
  const island = useRef32(null);
8427
8870
  const tabStop = useRef32(null);
8428
8871
  const reduced = usePrefersReducedMotion();
8429
- const [drag, setDrag] = useState42(null);
8872
+ const [drag, setDrag] = useState46(null);
8430
8873
  const dropRect = useRef32(null);
8431
- const [expanded, setExpanded] = useState42(false);
8432
- const [colorsOpen, setColorsOpen] = useState42(false);
8874
+ const [expanded, setExpanded] = useState46(false);
8875
+ const [colorsOpen, setColorsOpen] = useState46(false);
8433
8876
  const press = useRef32({ long: false });
8434
- useLayoutEffect4(() => {
8877
+ useLayoutEffect7(() => {
8435
8878
  const buttons = [...island.current?.querySelectorAll("button") ?? []];
8436
- const stop = buttons.find((button) => button === tabStop.current && !button.disabled) ?? buttons.find((button) => !button.disabled);
8437
- tabStop.current = stop ?? null;
8438
- for (const button of buttons) button.tabIndex = button === stop ? 0 : -1;
8879
+ const stop2 = buttons.find((button) => button === tabStop.current && !button.disabled) ?? buttons.find((button) => !button.disabled);
8880
+ tabStop.current = stop2 ?? null;
8881
+ for (const button of buttons) button.tabIndex = button === stop2 ? 0 : -1;
8439
8882
  });
8440
- useLayoutEffect4(() => {
8883
+ useLayoutEffect7(() => {
8441
8884
  const element = island.current;
8442
8885
  const from = dropRect.current;
8443
8886
  dropRect.current = null;
@@ -8454,13 +8897,13 @@ function BoardIsland(props) {
8454
8897
  const startDrag = (event) => {
8455
8898
  const board = island.current?.parentElement;
8456
8899
  if (!board || event.button !== 0) return;
8457
- const start = { x: event.clientX, y: event.clientY };
8900
+ const start2 = { x: event.clientX, y: event.clientY };
8458
8901
  let moved = false;
8459
8902
  const grip = event.currentTarget;
8460
8903
  grip.setPointerCapture(event.pointerId);
8461
8904
  const move = (next) => {
8462
- const dx = next.clientX - start.x;
8463
- const dy = next.clientY - start.y;
8905
+ const dx = next.clientX - start2.x;
8906
+ const dy = next.clientY - start2.y;
8464
8907
  if (!moved && Math.hypot(dx, dy) < DRAG_START_PX) return;
8465
8908
  moved = true;
8466
8909
  setDrag({ x: dx, y: dy, near: nearestPlace(board.getBoundingClientRect(), next.clientX, next.clientY) });
@@ -8493,7 +8936,7 @@ function BoardIsland(props) {
8493
8936
  };
8494
8937
  if (compact && !expanded) {
8495
8938
  const cancelPress = () => clearTimeout(press.current.timer);
8496
- return /* @__PURE__ */ jsx71("div", { ref: island, className: "chalk-board-island", "data-place": "compact", children: /* @__PURE__ */ jsx71(
8939
+ return /* @__PURE__ */ jsx72("div", { ref: island, className: "chalk-board-island", "data-place": "compact", children: /* @__PURE__ */ jsx72(
8497
8940
  ColorButton,
8498
8941
  {
8499
8942
  ...props,
@@ -8501,7 +8944,7 @@ function BoardIsland(props) {
8501
8944
  onOpenChange: (open) => {
8502
8945
  if (!open) setColorsOpen(false);
8503
8946
  },
8504
- trigger: /* @__PURE__ */ jsxs46(
8947
+ trigger: /* @__PURE__ */ jsxs48(
8505
8948
  "button",
8506
8949
  {
8507
8950
  type: "button",
@@ -8522,8 +8965,8 @@ function BoardIsland(props) {
8522
8965
  if (!press.current.long) setExpanded(true);
8523
8966
  },
8524
8967
  children: [
8525
- /* @__PURE__ */ jsx71(BoardIcon, { name: "pen" }),
8526
- /* @__PURE__ */ jsx71("span", { className: "chalk-board-tool-dot", style: { background: penHex(props.color, props.surface, props.theme) } })
8968
+ /* @__PURE__ */ jsx72(BoardIcon, { name: "pen" }),
8969
+ /* @__PURE__ */ jsx72("span", { className: "chalk-board-tool-dot", style: { background: penHex(props.color, props.surface, props.theme) } })
8527
8970
  ]
8528
8971
  }
8529
8972
  )
@@ -8531,9 +8974,9 @@ function BoardIsland(props) {
8531
8974
  ) });
8532
8975
  }
8533
8976
  const vertical = place === "left" && !compact;
8534
- return /* @__PURE__ */ jsxs46(Fragment29, { children: [
8535
- drag ? ISLAND_PLACES.map((entry) => /* @__PURE__ */ jsx71("div", { className: "chalk-board-dropzone", "data-zone": entry.id, "data-near": drag.near === entry.id || void 0, "aria-hidden": "true" }, entry.id)) : null,
8536
- /* @__PURE__ */ jsxs46(
8977
+ return /* @__PURE__ */ jsxs48(Fragment32, { children: [
8978
+ drag ? ISLAND_PLACES.map((entry) => /* @__PURE__ */ jsx72("div", { className: "chalk-board-dropzone", "data-zone": entry.id, "data-near": drag.near === entry.id || void 0, "aria-hidden": "true" }, entry.id)) : null,
8979
+ /* @__PURE__ */ jsxs48(
8537
8980
  "div",
8538
8981
  {
8539
8982
  ref: island,
@@ -8553,11 +8996,11 @@ function BoardIsland(props) {
8553
8996
  for (const button of event.currentTarget.querySelectorAll("button")) button.tabIndex = button === event.target ? 0 : -1;
8554
8997
  },
8555
8998
  children: [
8556
- compact ? /* @__PURE__ */ jsxs46(Fragment29, { children: [
8557
- /* @__PURE__ */ jsx71("button", { type: "button", className: "chalk-board-tool", "aria-label": "Close drawing tools", onClick: () => setExpanded(false), children: /* @__PURE__ */ jsx71(BoardIcon, { name: "close" }) }),
8558
- /* @__PURE__ */ jsx71(Separator, {})
8559
- ] }) : /* @__PURE__ */ jsx71(Tip, { label: "Move the tools", children: /* @__PURE__ */ jsx71("button", { type: "button", className: "chalk-board-grip", "aria-label": "Move the tools. Use Alt and the arrow keys to dock them left, bottom or top.", onPointerDown: startDrag, onKeyDown: gripKeys, children: /* @__PURE__ */ jsx71(GripIcon, {}) }) }),
8560
- /* @__PURE__ */ jsx71(ToolRow, { ...props, place: compact ? "bottom" : place })
8999
+ compact ? /* @__PURE__ */ jsxs48(Fragment32, { children: [
9000
+ /* @__PURE__ */ jsx72("button", { type: "button", className: "chalk-board-tool", "aria-label": "Close drawing tools", onClick: () => setExpanded(false), children: /* @__PURE__ */ jsx72(BoardIcon, { name: "close" }) }),
9001
+ /* @__PURE__ */ jsx72(Separator, {})
9002
+ ] }) : /* @__PURE__ */ jsx72(Tip, { label: "Move the tools", children: /* @__PURE__ */ jsx72("button", { type: "button", className: "chalk-board-grip", "aria-label": "Move the tools. Use Alt and the arrow keys to dock them left, bottom or top.", onPointerDown: startDrag, onKeyDown: gripKeys, children: /* @__PURE__ */ jsx72(GripIcon, {}) }) }),
9003
+ /* @__PURE__ */ jsx72(ToolRow, { ...props, place: compact ? "bottom" : place })
8561
9004
  ]
8562
9005
  }
8563
9006
  )
@@ -8566,54 +9009,76 @@ function BoardIsland(props) {
8566
9009
 
8567
9010
  // src/components/board/BoardMenu.tsx
8568
9011
  import { Menu as Menu5 } from "@base-ui/react/menu";
8569
- import { Fragment as Fragment30, jsx as jsx72, jsxs as jsxs47 } from "react/jsx-runtime";
9012
+ import { Fragment as Fragment33, jsx as jsx73, jsxs as jsxs49 } from "react/jsx-runtime";
8570
9013
  function SurfacePreview({ surface }) {
8571
- return /* @__PURE__ */ jsx72("span", { className: "chalk-board-tile-preview", "data-surface": surface, "aria-hidden": "true", children: surface === "dots" ? null : /* @__PURE__ */ jsx72("svg", { viewBox: "0 0 60 38", width: "100%", height: "100%", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: /* @__PURE__ */ jsx72("path", { d: "M14 22c5-8 9-8 12 0s8 6 12-2 6-6 8 0" }) }) });
9014
+ return /* @__PURE__ */ jsx73("span", { className: "chalk-board-tile-preview", "data-surface": surface, "aria-hidden": "true", children: surface === "dots" ? null : /* @__PURE__ */ jsx73("svg", { viewBox: "0 0 60 38", width: "100%", height: "100%", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: /* @__PURE__ */ jsx73("path", { d: "M14 22c5-8 9-8 12 0s8 6 12-2 6-6 8 0" }) }) });
8572
9015
  }
8573
9016
  function IslandPreview({ place }) {
8574
- return /* @__PURE__ */ jsx72("span", { className: "chalk-board-tile-preview", "data-island": place, "aria-hidden": "true", children: /* @__PURE__ */ jsx72("i", {}) });
9017
+ return /* @__PURE__ */ jsx73("span", { className: "chalk-board-tile-preview", "data-island": place, "aria-hidden": "true", children: /* @__PURE__ */ jsx73("i", {}) });
8575
9018
  }
8576
9019
  function Tiles({ label, value, options, onChange, preview }) {
8577
- return /* @__PURE__ */ jsxs47(Fragment30, { children: [
8578
- /* @__PURE__ */ jsx72("div", { className: "chalk-board-menu-sublabel", children: label }),
8579
- /* @__PURE__ */ jsx72(Menu5.RadioGroup, { value, onValueChange: (next) => onChange(next), className: "chalk-board-tiles", "aria-label": label, children: options.map((option) => /* @__PURE__ */ jsxs47(Menu5.RadioItem, { value: option.id, closeOnClick: false, className: "chalk-board-tile", children: [
9020
+ return /* @__PURE__ */ jsxs49(Fragment33, { children: [
9021
+ /* @__PURE__ */ jsx73("div", { className: "chalk-board-menu-sublabel", children: label }),
9022
+ /* @__PURE__ */ jsx73(Menu5.RadioGroup, { value, onValueChange: (next) => onChange(next), className: "chalk-board-tiles", "aria-label": label, children: options.map((option) => /* @__PURE__ */ jsxs49(Menu5.RadioItem, { value: option.id, closeOnClick: false, className: "chalk-board-tile", children: [
8580
9023
  preview(option.id),
8581
9024
  option.label
8582
9025
  ] }, option.id)) })
8583
9026
  ] });
8584
9027
  }
8585
- function BoardMenu({ surface, onSurface, place, onPlace, onDownload, onCopy, onBring, onClear, onClose }) {
8586
- return /* @__PURE__ */ jsxs47(
9028
+ function Check({ on }) {
9029
+ return /* @__PURE__ */ jsx73("span", { "data-part": "menu-check", className: "chalk-menu-check", "aria-hidden": "true", children: on ? /* @__PURE__ */ jsx73("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx73("path", { d: "M20 6L9 17l-5-5" }) }) : null });
9030
+ }
9031
+ function PickedPeople({ people, onPick }) {
9032
+ if (people.length === 0) return /* @__PURE__ */ jsx73("p", { className: "chalk-board-picked-note", children: "No one else is here yet. Hosts can always draw." });
9033
+ return /* @__PURE__ */ jsxs49(Fragment33, { children: [
9034
+ /* @__PURE__ */ jsx73("div", { className: "chalk-board-picked", role: "group", "aria-label": "Picked people", children: people.map((person) => /* @__PURE__ */ jsxs49(Menu5.CheckboxItem, { className: "chalk-menu-item", checked: person.picked, onCheckedChange: (picked) => onPick(person.id, picked), closeOnClick: false, children: [
9035
+ /* @__PURE__ */ jsx73(Check, { on: person.picked }),
9036
+ /* @__PURE__ */ jsx73("span", { className: "chalk-board-picked-name", children: person.name })
9037
+ ] }, person.id)) }),
9038
+ people.some((person) => person.picked) ? null : /* @__PURE__ */ jsx73("p", { className: "chalk-board-picked-note", children: "Only hosts can draw until you pick someone." })
9039
+ ] });
9040
+ }
9041
+ function WhoCanDrawSection({ policy, people, onPolicy, onPick }) {
9042
+ return /* @__PURE__ */ jsxs49(Fragment33, { children: [
9043
+ /* @__PURE__ */ jsx73(Menu5.Separator, { className: "chalk-board-menu-rule" }),
9044
+ /* @__PURE__ */ jsx73(ChalkMenuLabel, { children: "Who can draw" }),
9045
+ DRAW_POLICIES.map((option) => /* @__PURE__ */ jsx73(ChalkMenuItem, { checked: policy === option.id, closeOnClick: false, onClick: () => onPolicy(option.id), children: option.label }, option.id)),
9046
+ policy === "picked" ? /* @__PURE__ */ jsx73(PickedPeople, { people, onPick }) : null
9047
+ ] });
9048
+ }
9049
+ function BoardMenu({ surface, onSurface, place, onPlace, onDownload, onCopy, whoCanDraw, onBring, onClear, onClose }) {
9050
+ return /* @__PURE__ */ jsxs49(
8587
9051
  ChalkMenu,
8588
9052
  {
8589
9053
  className: "chalk-board-menu",
8590
- trigger: /* @__PURE__ */ jsx72("button", { type: "button", className: "chalk-board-menu-button", "aria-label": "Board menu", children: /* @__PURE__ */ jsx72(BoardIcon, { name: "more" }) }),
9054
+ trigger: /* @__PURE__ */ jsx73("button", { type: "button", className: "chalk-board-menu-button", "aria-label": "Board menu", children: /* @__PURE__ */ jsx73(BoardIcon, { name: "more" }) }),
8591
9055
  children: [
8592
- /* @__PURE__ */ jsxs47(ChalkMenuItem, { onClick: onDownload, children: [
8593
- /* @__PURE__ */ jsx72(BoardIcon, { name: "download", size: 18 }),
9056
+ /* @__PURE__ */ jsxs49(ChalkMenuItem, { onClick: onDownload, children: [
9057
+ /* @__PURE__ */ jsx73(BoardIcon, { name: "download", size: 18 }),
8594
9058
  "Download as PNG"
8595
9059
  ] }),
8596
- /* @__PURE__ */ jsxs47(ChalkMenuItem, { onClick: onCopy, children: [
8597
- /* @__PURE__ */ jsx72(BoardIcon, { name: "copy", size: 18 }),
9060
+ /* @__PURE__ */ jsxs49(ChalkMenuItem, { onClick: onCopy, children: [
9061
+ /* @__PURE__ */ jsx73(BoardIcon, { name: "copy", size: 18 }),
8598
9062
  "Copy as image"
8599
9063
  ] }),
8600
- /* @__PURE__ */ jsx72(Menu5.Separator, { className: "chalk-board-menu-rule" }),
8601
- /* @__PURE__ */ jsx72(ChalkMenuLabel, { children: "Board appearance" }),
8602
- /* @__PURE__ */ jsx72(Tiles, { label: "Surface", value: surface, options: SURFACES, onChange: onSurface, preview: (id) => /* @__PURE__ */ jsx72(SurfacePreview, { surface: id }) }),
8603
- place ? /* @__PURE__ */ jsx72(Tiles, { label: "Tool island", value: place, options: ISLAND_PLACES, onChange: onPlace, preview: (id) => /* @__PURE__ */ jsx72(IslandPreview, { place: id }) }) : null,
8604
- onBring || onClear ? /* @__PURE__ */ jsx72(Menu5.Separator, { className: "chalk-board-menu-rule" }) : null,
8605
- onBring ? /* @__PURE__ */ jsxs47(ChalkMenuItem, { onClick: onBring, children: [
8606
- /* @__PURE__ */ jsx72(BoardIcon, { name: "target", size: 18 }),
9064
+ /* @__PURE__ */ jsx73(Menu5.Separator, { className: "chalk-board-menu-rule" }),
9065
+ /* @__PURE__ */ jsx73(ChalkMenuLabel, { children: "Board appearance" }),
9066
+ /* @__PURE__ */ jsx73(Tiles, { label: "Surface", value: surface, options: SURFACES, onChange: onSurface, preview: (id) => /* @__PURE__ */ jsx73(SurfacePreview, { surface: id }) }),
9067
+ place ? /* @__PURE__ */ jsx73(Tiles, { label: "Tool island", value: place, options: ISLAND_PLACES, onChange: onPlace, preview: (id) => /* @__PURE__ */ jsx73(IslandPreview, { place: id }) }) : null,
9068
+ whoCanDraw ? /* @__PURE__ */ jsx73(WhoCanDrawSection, { ...whoCanDraw }) : null,
9069
+ onBring || onClear ? /* @__PURE__ */ jsx73(Menu5.Separator, { className: "chalk-board-menu-rule" }) : null,
9070
+ onBring ? /* @__PURE__ */ jsxs49(ChalkMenuItem, { onClick: onBring, children: [
9071
+ /* @__PURE__ */ jsx73(BoardIcon, { name: "target", size: 18 }),
8607
9072
  "Bring everyone here"
8608
9073
  ] }) : null,
8609
- onClear ? /* @__PURE__ */ jsx72(ChalkMenuItem, { onClick: onClear, children: /* @__PURE__ */ jsxs47("span", { className: "chalk-board-danger", children: [
8610
- /* @__PURE__ */ jsx72(BoardIcon, { name: "trash", size: 18 }),
9074
+ onClear ? /* @__PURE__ */ jsx73(ChalkMenuItem, { onClick: onClear, children: /* @__PURE__ */ jsxs49("span", { className: "chalk-board-danger", children: [
9075
+ /* @__PURE__ */ jsx73(BoardIcon, { name: "trash", size: 18 }),
8611
9076
  "Clear board"
8612
9077
  ] }) }) : null,
8613
- onClose ? /* @__PURE__ */ jsxs47(Fragment30, { children: [
8614
- /* @__PURE__ */ jsx72(Menu5.Separator, { className: "chalk-board-menu-rule" }),
8615
- /* @__PURE__ */ jsxs47(ChalkMenuItem, { onClick: onClose, children: [
8616
- /* @__PURE__ */ jsx72(BoardIcon, { name: "close", size: 18 }),
9078
+ onClose ? /* @__PURE__ */ jsxs49(Fragment33, { children: [
9079
+ /* @__PURE__ */ jsx73(Menu5.Separator, { className: "chalk-board-menu-rule" }),
9080
+ /* @__PURE__ */ jsxs49(ChalkMenuItem, { onClick: onClose, children: [
9081
+ /* @__PURE__ */ jsx73(BoardIcon, { name: "close", size: 18 }),
8617
9082
  "Close board"
8618
9083
  ] })
8619
9084
  ] }) : null
@@ -8622,10 +9087,43 @@ function BoardMenu({ surface, onSurface, place, onPlace, onDownload, onCopy, onB
8622
9087
  );
8623
9088
  }
8624
9089
 
9090
+ // src/components/board/board-draw-policy.ts
9091
+ import { useCallback as useCallback13, useMemo as useMemo16 } from "react";
9092
+ function useWhoCanDraw(toast) {
9093
+ const client = useSpaceClient();
9094
+ const isHost = useCan("manageWhiteboard");
9095
+ const self = useSelf();
9096
+ const { roster } = useParticipants();
9097
+ const { access } = useWhiteboard();
9098
+ const transport = client.whiteboard.transport();
9099
+ const people = useMemo16(
9100
+ () => roster.filter((person) => person.participantId !== self.participantId && !person.capabilities.includes("manageWhiteboard")).map((person) => ({
9101
+ id: person.participantId,
9102
+ name: person.displayName,
9103
+ picked: access.permissions.some((permission) => permission.participantId === person.participantId && permission.canDraw)
9104
+ })),
9105
+ [roster, self.participantId, access.permissions]
9106
+ );
9107
+ const onPolicy = useCallback13(
9108
+ (policy) => {
9109
+ transport?.setDrawPolicy?.(policy).catch(() => toast({ type: "error", message: "Could not change who can draw. Try again." }));
9110
+ },
9111
+ [transport, toast]
9112
+ );
9113
+ const onPick = useCallback13(
9114
+ (id, picked) => {
9115
+ transport?.setDrawPermission(id, picked).catch(() => toast({ type: "error", message: "Could not change who can draw. Try again." }));
9116
+ },
9117
+ [transport, toast]
9118
+ );
9119
+ if (!isHost || access.policy === null || !transport?.setDrawPolicy) return void 0;
9120
+ return { policy: access.policy, people, onPolicy, onPick };
9121
+ }
9122
+
8625
9123
  // src/components/board/BoardStyles.tsx
8626
- import { jsx as jsx73 } from "react/jsx-runtime";
9124
+ import { jsx as jsx74 } from "react/jsx-runtime";
8627
9125
  function BoardStyles() {
8628
- return /* @__PURE__ */ jsx73("style", { href: "chalk-board", precedence: "default", children: BOARD_CSS });
9126
+ return /* @__PURE__ */ jsx74("style", { href: "chalk-board", precedence: "default", children: BOARD_CSS });
8629
9127
  }
8630
9128
  var BOARD_CSS = `.chalk-board {
8631
9129
  --board-shadow: 0 0 0 1px var(--chalk-line-strong), 0 8px 24px rgba(0,0,0,.35);
@@ -8728,6 +9226,9 @@ body:has(.chalk-board) :is(.excalidraw-modal-container, .excalidraw-tooltip) {di
8728
9226
  .chalk-board-tile-preview[data-island="left"] i {left:5px; top:20%; bottom:20%; width:5px;}
8729
9227
  .chalk-board-menu-kbd {margin-left:auto; padding-left:18px; font:600 11px ui-monospace, SFMono-Regular, Menlo, monospace; color:var(--chalk-muted-text);}
8730
9228
  .chalk-board-danger {display:contents; color:var(--chalk-danger-text);}
9229
+ .chalk-board-picked {max-height:220px; overflow-y:auto; padding-left:16px;}
9230
+ .chalk-board-picked-name {min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
9231
+ .chalk-board-picked-note {margin:0; padding:4px 10px 8px 26px; font-size:12px; line-height:1.4; color:var(--chalk-muted-text);}
8731
9232
 
8732
9233
  .chalk-board-zoom {position:absolute; right:12px; bottom:12px; z-index:5; height:40px; display:flex; align-items:center; border-radius:999px; padding:0 4px; transition:translate .2s ease-out;}
8733
9234
  .chalk-board-zoom button {height:32px; min-width:32px; border-radius:999px; display:grid; place-items:center; padding:0 6px !important; font-size:13px; font-weight:600; font-variant-numeric:tabular-nums;}
@@ -8796,7 +9297,7 @@ body:has(.chalk-board) :is(.excalidraw-modal-container, .excalidraw-tooltip) {di
8796
9297
  `;
8797
9298
 
8798
9299
  // src/components/board/Board.tsx
8799
- import { Fragment as Fragment31, jsx as jsx74, jsxs as jsxs48 } from "react/jsx-runtime";
9300
+ import { Fragment as Fragment34, jsx as jsx75, jsxs as jsxs50 } from "react/jsx-runtime";
8800
9301
  var SURFACE_KEY = "chalk.board.surface";
8801
9302
  var ISLAND_KEY = "chalk.board.island";
8802
9303
  var COMPACT_BELOW_PX = 1060;
@@ -8814,15 +9315,15 @@ function fitContent(engine, animate) {
8814
9315
  function isTyping(target) {
8815
9316
  return target instanceof HTMLElement && (target.isContentEditable || target.tagName === "TEXTAREA" || target.tagName === "INPUT");
8816
9317
  }
8817
- function useCamera(engine, root, onManualMove, onMoved, onViewportReady) {
9318
+ function useCamera(engine, root, onLeadingMove, onMoved, onViewportReady) {
8818
9319
  const cameraRef = useRef33({ scrollX: 0, scrollY: 0, zoom: 1 });
8819
9320
  const viewportRef = useRef33({ width: 1, height: 1 });
8820
9321
  const expected = useRef33(null);
8821
9322
  const glide = useRef33(0);
8822
- const [zoom, setZoom] = useState43(1);
9323
+ const [zoom, setZoom] = useState47(1);
8823
9324
  const reduced = usePrefersReducedMotion();
8824
- const manual = useRef33(onManualMove);
8825
- manual.current = onManualMove;
9325
+ const leading = useRef33(onLeadingMove);
9326
+ leading.current = onLeadingMove;
8826
9327
  const moved = useRef33(onMoved);
8827
9328
  moved.current = onMoved;
8828
9329
  const resized = useRef33(onViewportReady);
@@ -8845,6 +9346,7 @@ function useCamera(engine, root, onManualMove, onMoved, onViewportReady) {
8845
9346
  return engine.api.onScrollChange((scrollX, scrollY, next) => {
8846
9347
  const camera = { scrollX, scrollY, zoom: next.value };
8847
9348
  if (camera.zoom !== clampZoom(camera.zoom)) {
9349
+ leading.current();
8848
9350
  expected.current = { ...camera, zoom: clampZoom(camera.zoom) };
8849
9351
  setCamera(engine, expected.current);
8850
9352
  return;
@@ -8853,7 +9355,7 @@ function useCamera(engine, root, onManualMove, onMoved, onViewportReady) {
8853
9355
  if (expected.current && sameCamera(camera, expected.current)) return;
8854
9356
  expected.current = null;
8855
9357
  cancelAnimationFrame(glide.current);
8856
- manual.current();
9358
+ leading.current();
8857
9359
  moved.current();
8858
9360
  });
8859
9361
  }, [engine, root]);
@@ -8870,10 +9372,16 @@ function useCamera(engine, root, onManualMove, onMoved, onViewportReady) {
8870
9372
  return () => observer.disconnect();
8871
9373
  }, [root]);
8872
9374
  useEffect37(() => () => cancelAnimationFrame(glide.current), []);
8873
- const move = useCallback13(
8874
- (target, animate) => {
9375
+ const move = useCallback14(
9376
+ (target, animate, source) => {
8875
9377
  if (!engine) return;
9378
+ if (source !== "follow") leading.current();
8876
9379
  cancelAnimationFrame(glide.current);
9380
+ if (sameCamera(target, cameraRef.current)) {
9381
+ expected.current = target;
9382
+ moved.current();
9383
+ return;
9384
+ }
8877
9385
  const set = (camera) => {
8878
9386
  expected.current = camera;
8879
9387
  setCamera(engine, camera);
@@ -8886,16 +9394,22 @@ function useCamera(engine, root, onManualMove, onMoved, onViewportReady) {
8886
9394
  const from = cameraRef.current;
8887
9395
  const started = performance.now();
8888
9396
  const step = (now) => {
8889
- const progress = Math.min(1, (now - started) / GLIDE_MS);
9397
+ const progress = Math.max(0, Math.min(1, (now - started) / GLIDE_MS));
8890
9398
  set(mixCamera(from, target, 1 - (1 - progress) ** 3));
8891
9399
  if (progress < 1) glide.current = requestAnimationFrame(step);
8892
- else moved.current();
9400
+ else {
9401
+ if (source !== "follow") leading.current();
9402
+ moved.current();
9403
+ }
8893
9404
  };
8894
9405
  glide.current = requestAnimationFrame(step);
8895
9406
  },
8896
9407
  [engine, reduced]
8897
9408
  );
8898
- return useMemo16(() => ({ cameraRef, viewportRef, zoom, move }), [move, zoom]);
9409
+ const fit = useCallback14(() => {
9410
+ if (engine) fitContent(engine, false);
9411
+ }, [engine]);
9412
+ return useMemo17(() => ({ cameraRef, viewportRef, zoom, move, fit }), [move, zoom, fit]);
8899
9413
  }
8900
9414
  function useBoardKeys(root, handlers) {
8901
9415
  const latest = useRef33(handlers);
@@ -8972,29 +9486,30 @@ function Board({ board, primary, visible, colorScheme, people, following, onFoll
8972
9486
  const boardSession = useWhiteboard().engine;
8973
9487
  const offline = connection.status === "reconnecting";
8974
9488
  const phone = useMediaQuery("(max-width: 640px)");
8975
- const [engine, setEngine] = useState43(null);
9489
+ const [engine, setEngine] = useState47(null);
8976
9490
  const toast = useBoardToast(engine);
8977
- const [surface, setSurface] = useState43(() => readStored(SURFACE_KEY, SURFACES, "chalkboard"));
8978
- const [place, setPlace] = useState43(() => readStored(ISLAND_KEY, ISLAND_PLACES, "bottom"));
8979
- const [tool, setTool] = useState43("pen");
8980
- const [shape, setShape] = useState43("rectangle");
8981
- const [color, setColor] = useState43("ink");
8982
- const [width, setWidth] = useState43("medium");
8983
- const [elementCount, setElementCount] = useState43(0);
9491
+ const whoCanDraw = useWhoCanDraw(toast);
9492
+ const [surface, setSurface] = useState47(() => readStored(SURFACE_KEY, SURFACES, "chalkboard"));
9493
+ const [place, setPlace] = useState47(() => readStored(ISLAND_KEY, ISLAND_PLACES, "bottom"));
9494
+ const [tool, setTool] = useState47("pen");
9495
+ const [shape, setShape] = useState47("rectangle");
9496
+ const [color, setColor] = useState47("ink");
9497
+ const [width, setWidth] = useState47("medium");
9498
+ const [elementCount, setElementCount] = useState47(0);
8984
9499
  const empty = elementCount === 0;
8985
- const [narrow, setNarrow] = useState43(false);
8986
- const [announcement, setAnnouncement] = useState43("");
9500
+ const [narrow, setNarrow] = useState47(false);
9501
+ const [announcement, setAnnouncement] = useState47("");
8987
9502
  const canDraw = primary && (board.collab?.canDraw ?? board.canDraw ?? true);
8988
9503
  const compact = phone || narrow;
8989
9504
  const history = useEngineHistory(root, engine);
8990
9505
  const touched = useRef33(false);
8991
9506
  const views = useRef33(/* @__PURE__ */ new Map());
8992
9507
  const cursors = useRef33(/* @__PURE__ */ new Map());
8993
- const pen = useMemo16(() => ({ color, width, surface, theme: colorScheme }), [color, width, surface, colorScheme]);
9508
+ const pen = useMemo17(() => ({ color, width, surface, theme: colorScheme }), [color, width, surface, colorScheme]);
8994
9509
  const followingRef = useRef33(following);
8995
9510
  followingRef.current = following;
8996
- const nameOf = useCallback13((id) => first(people.find((person) => person.id === id)?.displayName ?? "Someone"), [people]);
8997
- const colorFor = useCallback13(
9511
+ const nameOf = useCallback14((id) => first(people.find((person) => person.id === id)?.displayName ?? "Someone"), [people]);
9512
+ const colorFor = useCallback14(
8998
9513
  (id) => personColor(
8999
9514
  Math.max(
9000
9515
  0,
@@ -9003,7 +9518,7 @@ function Board({ board, primary, visible, colorScheme, people, following, onFoll
9003
9518
  ),
9004
9519
  [people]
9005
9520
  );
9006
- const stopFollowing = useCallback13(() => {
9521
+ const stopFollowing = useCallback14(() => {
9007
9522
  const current = followingRef.current;
9008
9523
  if (!current) return;
9009
9524
  followingRef.current = null;
@@ -9013,14 +9528,32 @@ function Board({ board, primary, visible, colorScheme, people, following, onFoll
9013
9528
  const shareView = useRef33(() => false);
9014
9529
  const viewSender = useRef33(sendView);
9015
9530
  viewSender.current = sendView;
9531
+ const selectedFollow = useRef33(following);
9016
9532
  const ownParticipantId = useSelf().participantId;
9017
- const appliedVersions = useRef33(new ViewVersions());
9533
+ const appliedVersions = useRef33(new ChalkWhiteboardViewVersions());
9534
+ const { roster } = useParticipants();
9535
+ const previousRoster = useRef33(new Set(roster.map((person) => person.participantId)));
9536
+ useEffect37(() => {
9537
+ const next = new Set(roster.map((person) => person.participantId));
9538
+ for (const id of previousRoster.current) {
9539
+ if (next.has(id)) continue;
9540
+ appliedVersions.current.forgetParticipant(id);
9541
+ for (const [publisher, view] of views.current) {
9542
+ if (publisher === id || view.originParticipantId === id) views.current.delete(publisher);
9543
+ }
9544
+ cursors.current.delete(id);
9545
+ }
9546
+ previousRoster.current = next;
9547
+ }, [roster]);
9018
9548
  const viewsAvailable = sendView !== void 0;
9019
9549
  const reapplyView = useRef33(() => void 0);
9020
9550
  const camera = useCamera(
9021
9551
  engine,
9022
9552
  root,
9023
- stopFollowing,
9553
+ () => {
9554
+ stopFollowing();
9555
+ shareView.current(false, true);
9556
+ },
9024
9557
  () => shareView.current(false),
9025
9558
  () => reapplyView.current()
9026
9559
  );
@@ -9028,21 +9561,35 @@ function Board({ board, primary, visible, colorScheme, people, following, onFoll
9028
9561
  if (!visible || !viewsAvailable || !engine) return;
9029
9562
  let last = 0;
9030
9563
  let pending;
9564
+ let pendingLead = false;
9031
9565
  const send = (bring2) => {
9566
+ const lead = pendingLead || bring2;
9567
+ pendingLead = false;
9032
9568
  const followedView = followingRef.current ? views.current.get(followingRef.current) : void 0;
9033
- if (followedView && !followedView.originParticipantId && !bring2) return true;
9569
+ const selected = followedView !== void 0 && selectedFollow.current === followingRef.current;
9570
+ if (followedView && !followedView.originParticipantId && !followedView.ownerId && !bring2) return true;
9034
9571
  const view = followedView ?? visibleView(camera.cameraRef.current, camera.viewportRef.current);
9035
9572
  last = performance.now();
9036
- const rectangle = { x: view.x, y: view.y, width: view.width, height: view.height, bring: bring2 };
9037
- const outgoing = followedView?.originParticipantId ? { ...rectangle, originParticipantId: followedView.originParticipantId, originSequence: followedView.originSequence, ...followedView.originEpoch ? { originEpoch: followedView.originEpoch } : {} } : rectangle;
9573
+ const rectangle = { x: view.x, y: view.y, width: view.width, height: view.height, bring: bring2, lead: lead || selected };
9574
+ let outgoing = rectangle;
9575
+ if (followedView && !lead) {
9576
+ if (followedView.ownerId) {
9577
+ outgoing = { ...rectangle, originParticipantId: followedView.participantId, originSequence: followedView.ownerSequence };
9578
+ } else if (followedView.originParticipantId && followedView.originSequence !== void 0) {
9579
+ outgoing = { ...rectangle, originParticipantId: followedView.originParticipantId, originSequence: followedView.originSequence, ...followedView.originEpoch ? { originEpoch: followedView.originEpoch } : {} };
9580
+ }
9581
+ }
9038
9582
  try {
9039
- return viewSender.current?.(outgoing) ?? false;
9583
+ const sent = viewSender.current?.(outgoing) ?? false;
9584
+ if (sent && selected) selectedFollow.current = null;
9585
+ return sent;
9040
9586
  } catch (error) {
9041
9587
  console.warn("Board view dropped.", { operation: "sendView", error });
9042
9588
  return false;
9043
9589
  }
9044
9590
  };
9045
- shareView.current = (bring2) => {
9591
+ shareView.current = (bring2, lead = false) => {
9592
+ pendingLead ||= lead;
9046
9593
  if (bring2) return send(true);
9047
9594
  clearTimeout(pending);
9048
9595
  pending = setTimeout(() => send(false), Math.max(0, VIEW_SEND_MS - (performance.now() - last)));
@@ -9059,13 +9606,13 @@ function Board({ board, primary, visible, colorScheme, people, following, onFoll
9059
9606
  };
9060
9607
  }, [boardSession.sceneId, boardSession.status, camera.cameraRef, camera.viewportRef, connection.status, engine, visible, viewsAvailable]);
9061
9608
  const { move, viewportRef } = camera;
9062
- const goTo = useCallback13(
9609
+ const goTo = useCallback14(
9063
9610
  (participantId, animate, selected = false) => {
9064
9611
  const view = views.current.get(participantId);
9065
9612
  if (!view || viewportRef.current.width <= 0 || viewportRef.current.height <= 0) return;
9066
- if (!selected && !appliedVersions.current.isNew(view)) return;
9067
- appliedVersions.current.remember(view);
9068
- move(cameraForView(view, viewportRef.current), animate);
9613
+ if (!selected && !appliedVersions.current.isNew(view, participantId)) return;
9614
+ appliedVersions.current.remember(view, participantId);
9615
+ move(cameraForView(view, viewportRef.current), animate, "follow");
9069
9616
  },
9070
9617
  [move, viewportRef]
9071
9618
  );
@@ -9074,17 +9621,23 @@ function Board({ board, primary, visible, colorScheme, people, following, onFoll
9074
9621
  };
9075
9622
  const subscribe3 = board.collab?.subscribe;
9076
9623
  const latestView = useRef33(() => void 0);
9624
+ useEffect37(
9625
+ () => subscribe3?.((event) => {
9626
+ if (event.type !== "snapshot") return;
9627
+ views.current.clear();
9628
+ appliedVersions.current.clear();
9629
+ }),
9630
+ [subscribe3]
9631
+ );
9077
9632
  latestView.current = (event) => {
9078
9633
  const previous = views.current.get(event.participantId);
9079
- if (!event.bring) {
9080
- if (event.originParticipantId === ownParticipantId && ownParticipantId) return;
9081
- if (!appliedVersions.current.isNew(event)) return;
9082
- if (event.originParticipantId && previous?.originParticipantId === event.originParticipantId && event.originEpoch === previous.originEpoch && event.originSequence <= previous.originSequence) return;
9083
- }
9634
+ if (!event.bring && (event.participantId === ownParticipantId || !event.ownerId && event.originParticipantId === ownParticipantId)) return;
9635
+ if (!event.bring && !appliedVersions.current.isNew(event, event.participantId)) return;
9636
+ if (!event.ownerId && !event.bring && event.originParticipantId && previous?.originParticipantId === event.originParticipantId && event.originEpoch === previous.originEpoch && event.originSequence <= previous.originSequence) return;
9084
9637
  const newPeer = !previous;
9085
9638
  views.current.delete(event.participantId);
9086
- views.current.set(event.participantId, event.bring ? { x: event.x, y: event.y, width: event.width, height: event.height, bring: false } : event);
9087
- if (views.current.size > 256) {
9639
+ views.current.set(event.participantId, { ...event, bring: false });
9640
+ if (views.current.size > WHITEBOARD_VIEW_PEOPLE_MAX) {
9088
9641
  const oldest = views.current.keys().next().value;
9089
9642
  if (oldest !== void 0) views.current.delete(oldest);
9090
9643
  }
@@ -9092,6 +9645,7 @@ function Board({ board, primary, visible, colorScheme, people, following, onFoll
9092
9645
  shareView.current(false);
9093
9646
  }
9094
9647
  if (event.bring) {
9648
+ appliedVersions.current.remember(event, event.participantId);
9095
9649
  followingRef.current = event.participantId;
9096
9650
  onFollow(event.participantId);
9097
9651
  toast({ message: `${nameOf(event.participantId)} brought everyone to this view` });
@@ -9108,7 +9662,11 @@ function Board({ board, primary, visible, colorScheme, people, following, onFoll
9108
9662
  });
9109
9663
  }, [subscribe3]);
9110
9664
  useEffect37(() => {
9111
- if (following) goTo(following, true, true);
9665
+ selectedFollow.current = following;
9666
+ if (following) {
9667
+ goTo(following, true, true);
9668
+ shareView.current(false);
9669
+ }
9112
9670
  }, [following, goTo]);
9113
9671
  useEffect37(() => {
9114
9672
  if (engine && canDraw) applyTool(engine, tool, pen);
@@ -9123,7 +9681,7 @@ function Board({ board, primary, visible, colorScheme, people, following, onFoll
9123
9681
  setElementCount(engine.api.getSceneElements().length);
9124
9682
  if (hasContent(engine)) {
9125
9683
  touched.current = true;
9126
- if (!followingRef.current) fitContent(engine, false);
9684
+ if (!followingRef.current) camera.fit();
9127
9685
  }
9128
9686
  const offDown = engine.api.onPointerDown(() => {
9129
9687
  touched.current = true;
@@ -9133,7 +9691,10 @@ function Board({ board, primary, visible, colorScheme, people, following, onFoll
9133
9691
  setElementCount(count);
9134
9692
  if (!touched.current && count > 0) {
9135
9693
  touched.current = true;
9136
- if (!followingRef.current) requestAnimationFrame(() => fitContent(engine, false));
9694
+ if (!followingRef.current)
9695
+ requestAnimationFrame(() => {
9696
+ if (!followingRef.current) camera.fit();
9697
+ });
9137
9698
  }
9138
9699
  const editing = appState.editingTextElement !== null;
9139
9700
  if (!wasEditing && editing) beforeEditing = sceneSignature(engine);
@@ -9147,7 +9708,7 @@ function Board({ board, primary, visible, colorScheme, people, following, onFoll
9147
9708
  offDown();
9148
9709
  offChange();
9149
9710
  };
9150
- }, [engine]);
9711
+ }, [engine, camera.fit]);
9151
9712
  useEffect37(() => {
9152
9713
  const element = root.current;
9153
9714
  if (!element) return;
@@ -9155,19 +9716,19 @@ function Board({ board, primary, visible, colorScheme, people, following, onFoll
9155
9716
  observer.observe(element);
9156
9717
  return () => observer.disconnect();
9157
9718
  }, []);
9158
- const chooseTool = useCallback13((next) => {
9719
+ const chooseTool = useCallback14((next) => {
9159
9720
  if (next === "rectangle" || next === "ellipse" || next === "diamond" || next === "arrow" || next === "line") setShape(next);
9160
9721
  setTool(next);
9161
9722
  }, []);
9162
- const undo = useCallback13(() => {
9723
+ const undo = useCallback14(() => {
9163
9724
  const container = root.current?.querySelector(".excalidraw");
9164
9725
  if (container) pressHistory(container, false);
9165
9726
  }, []);
9166
- const redo = useCallback13(() => {
9727
+ const redo = useCallback14(() => {
9167
9728
  const container = root.current?.querySelector(".excalidraw");
9168
9729
  if (container) pressHistory(container, true);
9169
9730
  }, []);
9170
- const zoomBy = useCallback13(
9731
+ const zoomBy = useCallback14(
9171
9732
  (factor) => {
9172
9733
  stopFollowing();
9173
9734
  camera.move(zoomAroundCentre(camera.cameraRef.current, camera.viewportRef.current, camera.cameraRef.current.zoom * factor), true);
@@ -9249,34 +9810,34 @@ function Board({ board, primary, visible, colorScheme, people, following, onFoll
9249
9810
  const surfaceVars = { "--board-selection": selectionColor(surface, colorScheme) };
9250
9811
  const followed = following ? nameOf(following) : null;
9251
9812
  const ready = engine !== null;
9252
- return /* @__PURE__ */ jsxs48("div", { ref: root, className: "chalk-board", "data-surface": surface, "data-theme": colorScheme, "data-ready": ready || void 0, "data-compact": compact || void 0, "data-primary": primary || void 0, "data-elements": elementCount, style: surfaceVars, hidden: !visible, children: [
9253
- /* @__PURE__ */ jsx74(BoardStyles, {}),
9254
- /* @__PURE__ */ jsx74(WhiteboardCanvas2, { ...board, headless: true, canDraw, collab: board.collab ? { ...board.collab, canDraw, canManage: isHost } : void 0, inkColor: ink, theme: "light", isVisible: visible, className: "chalk-board-engine", onEngineReady: setEngine }),
9255
- !ready ? /* @__PURE__ */ jsxs48("div", { className: "chalk-board-loading", role: "status", children: [
9256
- /* @__PURE__ */ jsx74("span", { className: "chalk-board-spinner", "aria-hidden": "true" }),
9813
+ return /* @__PURE__ */ jsxs50("div", { ref: root, className: "chalk-board", "data-surface": surface, "data-theme": colorScheme, "data-ready": ready || void 0, "data-compact": compact || void 0, "data-primary": primary || void 0, "data-elements": elementCount, style: surfaceVars, hidden: !visible, children: [
9814
+ /* @__PURE__ */ jsx75(BoardStyles, {}),
9815
+ /* @__PURE__ */ jsx75(WhiteboardCanvas2, { ...board, headless: true, canDraw, collab: board.collab ? { ...board.collab, canDraw, canManage: isHost } : void 0, inkColor: ink, theme: "light", isVisible: visible, className: "chalk-board-engine", onEngineReady: setEngine }),
9816
+ !ready ? /* @__PURE__ */ jsxs50("div", { className: "chalk-board-loading", role: "status", children: [
9817
+ /* @__PURE__ */ jsx75("span", { className: "chalk-board-spinner", "aria-hidden": "true" }),
9257
9818
  "Opening board"
9258
9819
  ] }) : null,
9259
- primary && ready && empty ? /* @__PURE__ */ jsx74(BoardHint, { canDraw }) : null,
9260
- primary && tool === "note" && canDraw ? /* @__PURE__ */ jsx74("div", { className: "chalk-board-note-layer", "aria-hidden": "true", onPointerDown: placeNoteAt }) : null,
9261
- primary ? /* @__PURE__ */ jsx74(BoardCursors, { cursorsRef: cursors, cameraRef: camera.cameraRef, colorFor, hidden: offline }) : null,
9262
- primary && ready ? /* @__PURE__ */ jsxs48(Fragment31, { children: [
9263
- /* @__PURE__ */ jsxs48("div", { className: "chalk-board-chips", "data-low": !compact && place === "top" && canDraw || void 0, children: [
9264
- followed ? /* @__PURE__ */ jsxs48("div", { className: "chalk-board-chip", role: "status", children: [
9265
- /* @__PURE__ */ jsx74(BoardIcon, { name: "eye", size: 16 }),
9820
+ primary && ready && empty ? /* @__PURE__ */ jsx75(BoardHint, { canDraw }) : null,
9821
+ primary && tool === "note" && canDraw ? /* @__PURE__ */ jsx75("div", { className: "chalk-board-note-layer", "aria-hidden": "true", onPointerDown: placeNoteAt }) : null,
9822
+ primary ? /* @__PURE__ */ jsx75(BoardCursors, { cursorsRef: cursors, cameraRef: camera.cameraRef, colorFor, hidden: offline }) : null,
9823
+ primary && ready ? /* @__PURE__ */ jsxs50(Fragment34, { children: [
9824
+ /* @__PURE__ */ jsxs50("div", { className: "chalk-board-chips", "data-low": !compact && place === "top" && canDraw || void 0, children: [
9825
+ followed ? /* @__PURE__ */ jsxs50("div", { className: "chalk-board-chip", role: "status", children: [
9826
+ /* @__PURE__ */ jsx75(BoardIcon, { name: "eye", size: 16 }),
9266
9827
  "Following ",
9267
9828
  followed,
9268
- /* @__PURE__ */ jsx74("button", { type: "button", className: "chalk-board-chip-button", onClick: stopFollowing, children: "Stop" })
9829
+ /* @__PURE__ */ jsx75("button", { type: "button", className: "chalk-board-chip-button", onClick: stopFollowing, children: "Stop" })
9269
9830
  ] }) : null,
9270
- offline ? /* @__PURE__ */ jsxs48("div", { className: "chalk-board-chip", "data-quiet": true, role: "status", children: [
9271
- /* @__PURE__ */ jsx74(BoardIcon, { name: "wifiOff", size: 16 }),
9831
+ offline ? /* @__PURE__ */ jsxs50("div", { className: "chalk-board-chip", "data-quiet": true, role: "status", children: [
9832
+ /* @__PURE__ */ jsx75(BoardIcon, { name: "wifiOff", size: 16 }),
9272
9833
  "Offline. Your drawing will sync."
9273
9834
  ] }) : null
9274
9835
  ] }),
9275
- !canDraw ? /* @__PURE__ */ jsxs48("div", { className: "chalk-board-view-only", children: [
9276
- /* @__PURE__ */ jsx74(BoardIcon, { name: "lock", size: 14 }),
9836
+ !canDraw ? /* @__PURE__ */ jsxs50("div", { className: "chalk-board-view-only", children: [
9837
+ /* @__PURE__ */ jsx75(BoardIcon, { name: "lock", size: 14 }),
9277
9838
  "View only"
9278
9839
  ] }) : null,
9279
- /* @__PURE__ */ jsx74("div", { className: "chalk-board-corner", children: /* @__PURE__ */ jsx74(
9840
+ /* @__PURE__ */ jsx75("div", { className: "chalk-board-corner", children: /* @__PURE__ */ jsx75(
9280
9841
  BoardMenu,
9281
9842
  {
9282
9843
  surface,
@@ -9285,22 +9846,23 @@ function Board({ board, primary, visible, colorScheme, people, following, onFoll
9285
9846
  onPlace: (next) => choosePlace(next, true),
9286
9847
  onDownload: () => void download(),
9287
9848
  onCopy: () => void copy(),
9849
+ whoCanDraw,
9288
9850
  onBring: isHost && sendView ? bring : void 0,
9289
9851
  onClear: isHost ? () => void clear() : void 0,
9290
9852
  onClose
9291
9853
  }
9292
9854
  ) }),
9293
- /* @__PURE__ */ jsxs48("div", { className: "chalk-board-zoom", role: "group", "aria-label": "Zoom", children: [
9294
- /* @__PURE__ */ jsx74("button", { type: "button", "aria-label": "Zoom out", onClick: () => zoomBy(1 / ZOOM_STEP), children: /* @__PURE__ */ jsx74(BoardIcon, { name: "minus", size: 16 }) }),
9295
- /* @__PURE__ */ jsxs48("button", { type: "button", "aria-label": `Zoom ${Math.round(camera.zoom * 100)}%, reset to 100%`, className: "chalk-board-zoom-value", onClick: () => zoomBy(1 / camera.cameraRef.current.zoom), children: [
9855
+ /* @__PURE__ */ jsxs50("div", { className: "chalk-board-zoom", role: "group", "aria-label": "Zoom", children: [
9856
+ /* @__PURE__ */ jsx75("button", { type: "button", "aria-label": "Zoom out", onClick: () => zoomBy(1 / ZOOM_STEP), children: /* @__PURE__ */ jsx75(BoardIcon, { name: "minus", size: 16 }) }),
9857
+ /* @__PURE__ */ jsxs50("button", { type: "button", "aria-label": `Zoom ${Math.round(camera.zoom * 100)}%, reset to 100%`, className: "chalk-board-zoom-value", onClick: () => zoomBy(1 / camera.cameraRef.current.zoom), children: [
9296
9858
  Math.round(camera.zoom * 100),
9297
9859
  "%"
9298
9860
  ] }),
9299
- /* @__PURE__ */ jsx74("button", { type: "button", "aria-label": "Zoom in", onClick: () => zoomBy(ZOOM_STEP), children: /* @__PURE__ */ jsx74(BoardIcon, { name: "plus", size: 16 }) }),
9300
- /* @__PURE__ */ jsx74("span", { className: "chalk-board-zoom-rule", "aria-hidden": "true" }),
9301
- /* @__PURE__ */ jsx74("button", { type: "button", "aria-label": "Fit to content", onClick: fit, children: /* @__PURE__ */ jsx74(BoardIcon, { name: "fit", size: 16 }) })
9861
+ /* @__PURE__ */ jsx75("button", { type: "button", "aria-label": "Zoom in", onClick: () => zoomBy(ZOOM_STEP), children: /* @__PURE__ */ jsx75(BoardIcon, { name: "plus", size: 16 }) }),
9862
+ /* @__PURE__ */ jsx75("span", { className: "chalk-board-zoom-rule", "aria-hidden": "true" }),
9863
+ /* @__PURE__ */ jsx75("button", { type: "button", "aria-label": "Fit to content", onClick: fit, children: /* @__PURE__ */ jsx75(BoardIcon, { name: "fit", size: 16 }) })
9302
9864
  ] }),
9303
- canDraw ? /* @__PURE__ */ jsx74(
9865
+ canDraw ? /* @__PURE__ */ jsx75(
9304
9866
  BoardIsland,
9305
9867
  {
9306
9868
  tool,
@@ -9321,26 +9883,26 @@ function Board({ board, primary, visible, colorScheme, people, following, onFoll
9321
9883
  compact
9322
9884
  }
9323
9885
  ) : null,
9324
- /* @__PURE__ */ jsx74("div", { className: "chalk-board-announcer", "aria-live": "polite", children: announcement })
9886
+ /* @__PURE__ */ jsx75("div", { className: "chalk-board-announcer", "aria-live": "polite", children: announcement })
9325
9887
  ] }) : null
9326
9888
  ] });
9327
9889
  }
9328
9890
  function BoardHint({ canDraw }) {
9329
9891
  if (!canDraw)
9330
- return /* @__PURE__ */ jsx74("div", { className: "chalk-board-hint", children: /* @__PURE__ */ jsx74("p", { children: "Nothing here yet. When someone draws, it shows up here." }) });
9331
- return /* @__PURE__ */ jsxs48("div", { className: "chalk-board-hint", children: [
9332
- /* @__PURE__ */ jsx74("p", { children: "An empty board. Draw, type or add a sticky note." }),
9333
- /* @__PURE__ */ jsxs48("ul", { children: [
9334
- /* @__PURE__ */ jsxs48("li", { children: [
9335
- /* @__PURE__ */ jsx74("kbd", { children: "P" }),
9892
+ return /* @__PURE__ */ jsx75("div", { className: "chalk-board-hint", children: /* @__PURE__ */ jsx75("p", { children: "Nothing here yet. When someone draws, it shows up here." }) });
9893
+ return /* @__PURE__ */ jsxs50("div", { className: "chalk-board-hint", children: [
9894
+ /* @__PURE__ */ jsx75("p", { children: "An empty board. Draw, type or add a sticky note." }),
9895
+ /* @__PURE__ */ jsxs50("ul", { children: [
9896
+ /* @__PURE__ */ jsxs50("li", { children: [
9897
+ /* @__PURE__ */ jsx75("kbd", { children: "P" }),
9336
9898
  " Pen"
9337
9899
  ] }),
9338
- /* @__PURE__ */ jsxs48("li", { children: [
9339
- /* @__PURE__ */ jsx74("kbd", { children: "T" }),
9900
+ /* @__PURE__ */ jsxs50("li", { children: [
9901
+ /* @__PURE__ */ jsx75("kbd", { children: "T" }),
9340
9902
  " Text"
9341
9903
  ] }),
9342
- /* @__PURE__ */ jsxs48("li", { children: [
9343
- /* @__PURE__ */ jsx74("kbd", { children: "N" }),
9904
+ /* @__PURE__ */ jsxs50("li", { children: [
9905
+ /* @__PURE__ */ jsx75("kbd", { children: "N" }),
9344
9906
  " Sticky note"
9345
9907
  ] })
9346
9908
  ] })
@@ -9348,12 +9910,12 @@ function BoardHint({ canDraw }) {
9348
9910
  }
9349
9911
 
9350
9912
  // src/components/stage/SpaceStage.tsx
9351
- import { jsx as jsx75 } from "react/jsx-runtime";
9913
+ import { jsx as jsx76 } from "react/jsx-runtime";
9352
9914
  function SpaceStage({ tiles, layout, onLayoutChange, generatedAvatars = true, whiteboard, onOverflowClick, shareCapture, onStopSharing, onChangeShare, colorScheme = "dark", onCloseBoard, className }) {
9353
- const items = useMemo17(() => buildStageItems(tiles, whiteboard?.isOpen === true), [tiles, whiteboard?.isOpen]);
9354
- const [selectedShare, setSelectedShare] = useState44(null);
9355
- const [pin, setPin] = useState44(null);
9356
- const [following, setFollowing] = useState44(null);
9915
+ const items = useMemo18(() => buildStageItems(tiles, whiteboard?.isOpen === true), [tiles, whiteboard?.isOpen]);
9916
+ const [selectedShare, setSelectedShare] = useState48(null);
9917
+ const [pin, setPin] = useState48(null);
9918
+ const [following, setFollowing] = useState48(null);
9357
9919
  const boardOpen = whiteboard?.isOpen === true && Boolean(whiteboard.props);
9358
9920
  useEffect38(() => {
9359
9921
  if (!boardOpen) setFollowing(null);
@@ -9367,17 +9929,17 @@ function SpaceStage({ tiles, layout, onLayoutChange, generatedAvatars = true, wh
9367
9929
  if (pin !== activePin) setPin(activePin);
9368
9930
  if (selectedShare !== activeShare) setSelectedShare(activeShare);
9369
9931
  }, [pin, activePin, selectedShare, activeShare]);
9370
- const follow = useCallback14((id) => {
9932
+ const follow = useCallback15((id) => {
9371
9933
  if (id) {
9372
9934
  setPin(null);
9373
9935
  setSelectedShare(null);
9374
9936
  }
9375
9937
  setFollowing(id);
9376
9938
  }, []);
9377
- const renderContentTile = useCallback14(
9939
+ const renderContentTile = useCallback15(
9378
9940
  (item, context) => {
9379
9941
  if (item.kind === "screen-share")
9380
- return /* @__PURE__ */ jsx75(
9942
+ return /* @__PURE__ */ jsx76(
9381
9943
  StageContentTile,
9382
9944
  {
9383
9945
  item,
@@ -9385,7 +9947,7 @@ function SpaceStage({ tiles, layout, onLayoutChange, generatedAvatars = true, wh
9385
9947
  capture: item.participant.isLocal ? shareCapture : void 0,
9386
9948
  onStopSharing: item.participant.isLocal ? onStopSharing : void 0,
9387
9949
  onChangeShare: item.participant.isLocal ? onChangeShare : void 0,
9388
- switcher: shares.length > 1 ? /* @__PURE__ */ jsx75("div", { role: "group", "aria-label": "Whose screen is big", className: cn("absolute left-2 z-30 flex rounded-[999px] bg-[rgba(12,14,18,.82)] p-[3px]", item.participant.screenShareState === "low-quality" ? "top-11" : "top-2"), children: shares.map((share) => /* @__PURE__ */ jsx75(
9950
+ switcher: shares.length > 1 ? /* @__PURE__ */ jsx76("div", { role: "group", "aria-label": "Whose screen is big", className: cn("absolute left-2 z-30 flex rounded-[999px] bg-[rgba(12,14,18,.82)] p-[3px]", item.participant.screenShareState === "low-quality" ? "top-11" : "top-2"), children: shares.map((share) => /* @__PURE__ */ jsx76(
9389
9951
  "button",
9390
9952
  {
9391
9953
  type: "button",
@@ -9402,25 +9964,25 @@ function SpaceStage({ tiles, layout, onLayoutChange, generatedAvatars = true, wh
9402
9964
  }
9403
9965
  );
9404
9966
  const boardProps = whiteboard?.props;
9405
- return /* @__PURE__ */ jsx75(
9967
+ return /* @__PURE__ */ jsx76(
9406
9968
  StageContentTileSurface,
9407
9969
  {
9408
9970
  item,
9409
9971
  ...context,
9410
9972
  mediaVisible: Boolean(boardProps),
9411
- media: boardProps ? /* @__PURE__ */ jsx75("div", { className: cn("h-full", !context.primary && "pointer-events-none"), inert: !context.primary || void 0, children: /* @__PURE__ */ jsx75(Board, { board: boardProps, primary: context.primary, visible: context.active, colorScheme, people: tiles, following: followed, onFollow: follow, sendView: canFollow ? whiteboard?.sendView : void 0, onClose: onCloseBoard }) }) : null
9973
+ media: boardProps ? /* @__PURE__ */ jsx76("div", { className: cn("h-full", !context.primary && "pointer-events-none"), inert: !context.primary || void 0, children: /* @__PURE__ */ jsx76(Board, { board: boardProps, primary: context.primary, visible: context.active, colorScheme, people: tiles, following: followed, onFollow: follow, sendView: canFollow ? whiteboard?.sendView : void 0, onClose: onCloseBoard }) }) : null
9412
9974
  }
9413
9975
  );
9414
9976
  },
9415
9977
  [follow, whiteboard, shares, shareCapture, onStopSharing, onChangeShare, colorScheme, tiles, followed, canFollow, onCloseBoard]
9416
9978
  );
9417
- const focusContent = useCallback14(
9979
+ const focusContent = useCallback15(
9418
9980
  (item) => {
9419
9981
  if (item.kind !== "participant") onLayoutChange?.("focus");
9420
9982
  },
9421
9983
  [onLayoutChange]
9422
9984
  );
9423
- return /* @__PURE__ */ jsx75(
9985
+ return /* @__PURE__ */ jsx76(
9424
9986
  Stage,
9425
9987
  {
9426
9988
  items,
@@ -9434,7 +9996,7 @@ function SpaceStage({ tiles, layout, onLayoutChange, generatedAvatars = true, wh
9434
9996
  onItemClick: focusContent,
9435
9997
  onOverflowClick: canFollow ? void 0 : onOverflowClick,
9436
9998
  generatedAvatars,
9437
- emptyState: /* @__PURE__ */ jsx75(QuietSpace, {}),
9999
+ emptyState: /* @__PURE__ */ jsx76(QuietSpace, {}),
9438
10000
  className
9439
10001
  }
9440
10002
  );
@@ -9442,11 +10004,11 @@ function SpaceStage({ tiles, layout, onLayoutChange, generatedAvatars = true, wh
9442
10004
  SpaceStage.displayName = "SpaceStage";
9443
10005
 
9444
10006
  // src/components/space-view/useDrawerPresence.ts
9445
- import { useEffect as useEffect39, useState as useState45 } from "react";
10007
+ import { useEffect as useEffect39, useState as useState49 } from "react";
9446
10008
  var DRAWER_EXIT_MS = 200;
9447
10009
  function useDrawerPresence(panel, exitMs) {
9448
- const [retained, setRetained] = useState45(panel);
9449
- const [state, setState] = useState45(panel === null ? "closed" : "open");
10010
+ const [retained, setRetained] = useState49(panel);
10011
+ const [state, setState] = useState49(panel === null ? "closed" : "open");
9450
10012
  useEffect39(() => {
9451
10013
  if (panel !== null) {
9452
10014
  setRetained(panel);
@@ -9464,7 +10026,7 @@ function useDrawerPresence(panel, exitMs) {
9464
10026
  }
9465
10027
 
9466
10028
  // src/components/space-view/SpaceView.tsx
9467
- import { Fragment as Fragment32, jsx as jsx76, jsxs as jsxs49 } from "react/jsx-runtime";
10029
+ import { Fragment as Fragment35, jsx as jsx77, jsxs as jsxs51 } from "react/jsx-runtime";
9468
10030
  function SpaceView({
9469
10031
  colorScheme = "dark",
9470
10032
  onColorSchemeChange,
@@ -9473,6 +10035,8 @@ function SpaceView({
9473
10035
  callLinkOpen,
9474
10036
  onCallLinkToggle,
9475
10037
  onReportProblem,
10038
+ reportUnavailableReason,
10039
+ onOpenReportProblem,
9476
10040
  inviteLink,
9477
10041
  pickChatFiles,
9478
10042
  stageBackground = true,
@@ -9482,6 +10046,8 @@ function SpaceView({
9482
10046
  features,
9483
10047
  generatedAvatars = true,
9484
10048
  commandError: externalCommandError,
10049
+ commandSource: externalCommandSource,
10050
+ commandAction: externalCommandAction,
9485
10051
  onDismissCommandError,
9486
10052
  onToggleWhiteboard,
9487
10053
  whiteboard,
@@ -9500,29 +10066,36 @@ function SpaceView({
9500
10066
  const callLink = useCallLink(participantsSlice.roster.length <= 1);
9501
10067
  const media = useMedia();
9502
10068
  const connection = useConnection();
10069
+ const offline = useOffline();
10070
+ const [dismissedOfflineNotice, setDismissedOfflineNotice] = useState50(null);
10071
+ const offlineNotice = offline.notice && offline.notice !== dismissedOfflineNotice ? offline.notice : null;
9503
10072
  const recording = useRecording();
9504
10073
  const canPublishScreen = useCan("publishScreen");
9505
10074
  const canSendReaction = useCan("sendReaction");
9506
10075
  const canRaiseHand = useCan("raiseHand");
9507
- const canDrawWhiteboard = useCan("drawWhiteboard");
10076
+ const canDrawWhiteboard = useCanDrawWhiteboard();
9508
10077
  const canManageAdmission = useCan("manageAdmission");
9509
10078
  const canManageRecording = useCan("manageRecording");
9510
10079
  const episodeDuration = useEpisodeDuration();
9511
- const [uncontrolledLayout, setUncontrolledLayout] = useState46(controlledLayout ?? "focus");
9512
- const [activePanel, setActivePanel] = useState46(initialPanel);
9513
- const [isReactionPickerOpen, setReactionPickerOpen] = useState46(false);
9514
- const [commandError, setCommandError] = useState46(null);
9515
- const [commandSource, setCommandSource] = useState46();
9516
- const onCommandError = (failure, source) => {
10080
+ const [uncontrolledLayout, setUncontrolledLayout] = useState50(controlledLayout ?? "focus");
10081
+ const [activePanel, setActivePanel] = useState50(initialPanel);
10082
+ const [isReactionPickerOpen, setReactionPickerOpen] = useState50(false);
10083
+ const [commandError, setCommandError] = useState50(null);
10084
+ const [commandSource, setCommandSource] = useState50();
10085
+ const [commandAction, setCommandAction] = useState50();
10086
+ const [retryCommand, setRetryCommand] = useState50();
10087
+ const onCommandError = (failure, source, mediaSource, action, command) => {
9517
10088
  if (!failure && commandError && typeof commandError === "object") callToasts.dismissFailure(commandError.code);
9518
10089
  setCommandError(failure);
9519
- setCommandSource(source);
10090
+ setCommandSource(mediaSource ?? source);
10091
+ setCommandAction(action);
10092
+ setRetryCommand(() => command);
9520
10093
  if (failure && typeof failure !== "string") playSound("error", failure);
9521
10094
  };
9522
10095
  const layout = onLayoutChange ? controlledLayout ?? uncontrolledLayout : uncontrolledLayout;
9523
10096
  const feature = (name) => features?.[name] !== false;
9524
10097
  const localId = self.participantId ?? "local";
9525
- const tiles = useMemo18(
10098
+ const tiles = useMemo19(
9526
10099
  () => toVideoParticipants(participantsSlice.roster, media.remote, localId, self.displayName ?? "You", media.local).map((tile) => ({
9527
10100
  ...tile,
9528
10101
  screenShareHasAudio: tile.isLocal ? media.screenShare.hasAudio : participantsSlice.roster.find((person) => person.participantId === tile.id)?.media.screenShareHasAudio
@@ -9552,37 +10125,41 @@ function SpaceView({
9552
10125
  const playSound = useSoundCues(feature("sounds") && soundsEnabled);
9553
10126
  const prefersReducedMotion = usePrefersReducedMotion();
9554
10127
  const drawer = useDrawerPresence(activePanel, prefersReducedMotion ? 0 : DRAWER_EXIT_MS);
9555
- const runCommand = useCallback15(async (command) => {
10128
+ const runCommand = useCallback16(async (command, source, action) => {
9556
10129
  try {
9557
10130
  await command();
9558
10131
  setCommandError(null);
10132
+ setCommandSource(void 0);
10133
+ setCommandAction(void 0);
10134
+ setRetryCommand(void 0);
9559
10135
  } catch (cause) {
9560
10136
  setCommandError(commandFailure(cause));
10137
+ setCommandSource(source);
10138
+ setCommandAction(action);
10139
+ setRetryCommand(() => command);
9561
10140
  }
9562
10141
  }, []);
9563
10142
  const leaveCall = onLeft ? () => void runCommand(async () => onLeft()) : void 0;
9564
10143
  const endCallForEveryone = onEndEpisode ? () => void runCommand(async () => onEndEpisode()) : void 0;
9565
10144
  const displayedCommandError = externalCommandError ?? commandError;
9566
- const mappedFailure = mediaFailureToast(typeof displayedCommandError === "object" ? displayedCommandError : null, commandSource);
10145
+ const displayedCommandSource = externalCommandError != null ? externalCommandSource : commandSource;
9567
10146
  const retryMedia = async () => {
10147
+ if (!retryCommand) return;
9568
10148
  try {
9569
- if (commandSource === "screen") await client.media.setScreenShareEnabled(true);
9570
- else if (commandSource === "camera") await client.media.setCameraEnabled(true);
9571
- else if (commandSource === "microphone") await client.media.setMicrophoneEnabled(true);
10149
+ await retryCommand();
9572
10150
  onCommandError(null);
9573
10151
  } catch (cause) {
9574
- if (cause instanceof SpaceClientError2 && cause.code === "connection.invalid_state") return;
9575
- onCommandError(cause instanceof SpaceClientError2 ? cause : { code: "client.internal_error", recoverable: true, message: "Your device couldn\u2019t start. Try again." }, commandSource);
10152
+ onCommandError(commandFailure(cause), commandSource, commandSource, commandAction, retryCommand);
9576
10153
  }
9577
10154
  };
9578
- const retryableMedia = commandSource && typeof displayedCommandError === "object" && displayedCommandError?.recoverable && (commandSource === "screen" || displayedCommandError.code === "media.device_busy");
9579
- const commandToasts = mappedFailure ? [{ id: "space-command-error", ...mappedFailure, action: retryableMedia ? { label: "Try again", onClick: () => void retryMedia() } : void 0 }] : typeof displayedCommandError === "string" ? [{ id: "space-command-error", message: displayedCommandError, type: "error" }] : [];
9580
- const [boardToasts, setBoardToasts] = useState46([]);
10155
+ const mappedFailure = mediaFailureToast(typeof displayedCommandError === "object" ? displayedCommandError : null, displayedCommandSource, externalCommandError != null ? externalCommandAction : commandAction, externalCommandError == null && retryCommand ? () => void retryMedia() : void 0);
10156
+ const commandToasts = mappedFailure ? [{ id: "space-command-error", ...mappedFailure }] : typeof displayedCommandError === "string" ? [{ id: "space-command-error", message: displayedCommandError, type: "error" }] : [];
10157
+ const [boardToasts, setBoardToasts] = useState50([]);
9581
10158
  const boardToastCount = useRef34(0);
9582
10159
  useEffect40(() => {
9583
10160
  if (!whiteboard?.isOpen) setBoardToasts([]);
9584
10161
  }, [whiteboard?.isOpen]);
9585
- const showBoardToast = useCallback15((toast) => {
10162
+ const showBoardToast = useCallback16((toast) => {
9586
10163
  if (!toast) {
9587
10164
  setBoardToasts([]);
9588
10165
  return;
@@ -9596,7 +10173,21 @@ function SpaceView({
9596
10173
  setCommandError(null);
9597
10174
  onDismissCommandError?.();
9598
10175
  };
9599
- return /* @__PURE__ */ jsx76(ParticipantVolumeProvider, { children: /* @__PURE__ */ jsx76(BoardToastContext.Provider, { value: showBoardToast, children: /* @__PURE__ */ jsx76(
10176
+ const toasts = [
10177
+ ...callToasts.toasts.filter((toast) => !(typeof displayedCommandError === "object" && displayedCommandError && toast.failureCode === displayedCommandError.code)),
10178
+ ...whiteboard?.isOpen ? boardToasts : [],
10179
+ ...commandToasts,
10180
+ ...offlineNotice ? [{ id: "offline-notice", message: offlineNotice }] : []
10181
+ ];
10182
+ const dismissToast = (id) => {
10183
+ if (id === "space-command-error") dismissCommandError();
10184
+ else if (id === "offline-notice") setDismissedOfflineNotice(offline.notice ?? null);
10185
+ else if (id.startsWith("board-")) setBoardToasts((current) => current.filter((toast) => toast.id !== id));
10186
+ else callToasts.dismiss(id);
10187
+ };
10188
+ const [foldedNotices, setFoldedNotices] = useState50([]);
10189
+ const folded = toasts.filter((toast) => foldedNotices.includes(toast.id));
10190
+ return /* @__PURE__ */ jsx77(ParticipantVolumeProvider, { children: /* @__PURE__ */ jsx77(BoardToastContext.Provider, { value: showBoardToast, children: /* @__PURE__ */ jsx77(
9600
10191
  SpacePresentation,
9601
10192
  {
9602
10193
  colorScheme,
@@ -9612,11 +10203,13 @@ function SpaceView({
9612
10203
  callLinkOpen: callLinkOpen ?? callLink.open,
9613
10204
  onCallLinkToggle: onCallLinkToggle ?? callLink.toggle,
9614
10205
  onReportProblem,
10206
+ onOpenReportProblem,
10207
+ reportUnavailableReason,
9615
10208
  diagnostics: callDiagnostics(connection, media),
9616
10209
  shortcuts: {
9617
10210
  isMuted: media.local.microphone.state !== "enabled",
9618
- onToggleMute: connection.status === "live" || media.local.microphone.state === "enabled" ? () => void runCommand(() => client.media.setMicrophoneEnabled(media.local.microphone.state !== "enabled")) : void 0,
9619
- onToggleVideo: connection.status === "live" || media.local.camera.state === "enabled" ? () => void runCommand(() => client.media.setCameraEnabled(media.local.camera.state !== "enabled")) : void 0,
10211
+ onToggleMute: connection.status === "live" || media.local.microphone.state === "enabled" ? () => void runCommand(() => client.media.setMicrophoneEnabled(media.local.microphone.state !== "enabled"), "microphone", media.local.microphone.state === "enabled" ? "stop" : "start") : void 0,
10212
+ onToggleVideo: connection.status === "live" || media.local.camera.state === "enabled" ? () => void runCommand(() => client.media.setCameraEnabled(media.local.camera.state !== "enabled"), "camera", media.local.camera.state === "enabled" ? "stop" : "start") : void 0,
9620
10213
  onToggleHand: feature("handRaise") && canRaiseHand ? () => void runCommand(() => self.handRaised ? client.participants.lowerHand() : client.participants.raiseHand()) : void 0,
9621
10214
  onToggleChat: feature("chat") ? () => setActivePanel((current) => current === "chat" ? null : "chat") : void 0,
9622
10215
  onTogglePeople: feature("participants") ? () => setActivePanel((current) => current === "participants" ? null : "participants") : void 0,
@@ -9624,15 +10217,16 @@ function SpaceView({
9624
10217
  onClosePanel: () => setActivePanel(null),
9625
10218
  onPushToTalk: (enabled) => {
9626
10219
  if (enabled && connection.status !== "live") return;
9627
- void runCommand(() => client.media.setMicrophoneEnabled(enabled));
10220
+ void runCommand(() => client.media.setMicrophoneEnabled(enabled), "microphone", enabled ? "start" : "stop");
9628
10221
  }
9629
10222
  },
9630
10223
  duration: episodeDuration,
9631
10224
  isRecording: recording.current?.status === "recording",
9632
10225
  layout,
9633
- onLayoutChange: updateLayout
10226
+ onLayoutChange: updateLayout,
10227
+ notices: folded.length ? /* @__PURE__ */ jsx77(NoticeButton, { toasts: folded, onDismiss: dismissToast }) : void 0
9634
10228
  },
9635
- stage: /* @__PURE__ */ jsx76(
10229
+ stage: /* @__PURE__ */ jsx77(
9636
10230
  SpaceStage,
9637
10231
  {
9638
10232
  tiles,
@@ -9653,7 +10247,7 @@ function SpaceView({
9653
10247
  className: "h-full"
9654
10248
  }
9655
10249
  ),
9656
- controls: /* @__PURE__ */ jsx76(
10250
+ controls: /* @__PURE__ */ jsx77(
9657
10251
  ControlBar,
9658
10252
  {
9659
10253
  density: "comfortable",
@@ -9673,9 +10267,9 @@ function SpaceView({
9673
10267
  onEndForEveryone: endCallForEveryone
9674
10268
  }
9675
10269
  ),
9676
- stageOverlay: /* @__PURE__ */ jsxs49(Fragment32, { children: [
9677
- feature("reactions") ? /* @__PURE__ */ jsx76(ReactionsOverlay, {}) : null,
9678
- feature("reactions") && canSendReaction && connection.status === "live" ? /* @__PURE__ */ jsx76("div", { className: "absolute bottom-24 left-1/2 z-50 -translate-x-1/2", children: /* @__PURE__ */ jsx76(
10270
+ stageOverlay: /* @__PURE__ */ jsxs51(Fragment35, { children: [
10271
+ feature("reactions") ? /* @__PURE__ */ jsx77(ReactionsOverlay, {}) : null,
10272
+ feature("reactions") && canSendReaction && connection.status === "live" ? /* @__PURE__ */ jsx77("div", { className: "absolute bottom-24 left-1/2 z-50 -translate-x-1/2", children: /* @__PURE__ */ jsx77(
9679
10273
  ReactionPicker,
9680
10274
  {
9681
10275
  isHandRaised: self.handRaised,
@@ -9688,28 +10282,18 @@ function SpaceView({
9688
10282
  }
9689
10283
  ) }) : null
9690
10284
  ] }),
9691
- sidebar: /* @__PURE__ */ jsxs49(CallCard, { state: drawer.state, panel: drawer.panel, onPanelChange: setActivePanel, peopleCount: participantsSlice.roster.length, participantsEnabled: feature("participants"), chatEnabled: feature("chat"), onClose: () => setActivePanel(null), children: [
9692
- drawer.panel === "chat" && feature("chat") ? /* @__PURE__ */ jsx76(ChatPanel, { variant: "mobile", generatedAvatars, onClose: () => setActivePanel(null), pickChatFiles }) : null,
9693
- drawer.panel === "participants" && feature("participants") ? /* @__PURE__ */ jsx76(ParticipantsPanel, { variant: "mobile", admissionEnabled: feature("admission"), generatedAvatars, onCommandError, onClose: () => setActivePanel(null) }) : null,
9694
- drawer.panel === "admission" && feature("admission") ? /* @__PURE__ */ jsx76(AdmissionPanel, { className: "h-full w-full rounded-none shadow-none", onClose: () => setActivePanel(null) }) : null
10285
+ sidebar: /* @__PURE__ */ jsxs51(CallCard, { state: drawer.state, panel: drawer.panel, onPanelChange: setActivePanel, peopleCount: participantsSlice.roster.length, participantsEnabled: feature("participants"), chatEnabled: feature("chat"), onClose: () => setActivePanel(null), children: [
10286
+ drawer.panel === "chat" && feature("chat") ? /* @__PURE__ */ jsx77(ChatPanel, { variant: "mobile", generatedAvatars, onClose: () => setActivePanel(null), pickChatFiles }) : null,
10287
+ drawer.panel === "participants" && feature("participants") ? /* @__PURE__ */ jsx77(ParticipantsPanel, { variant: "mobile", admissionEnabled: feature("admission"), generatedAvatars, onCommandError, onClose: () => setActivePanel(null) }) : null,
10288
+ drawer.panel === "admission" && feature("admission") ? /* @__PURE__ */ jsx77(AdmissionPanel, { className: "h-full w-full rounded-none shadow-none", onClose: () => setActivePanel(null) }) : null
9695
10289
  ] }),
9696
- toasts: /* @__PURE__ */ jsx76(
9697
- ToastStack,
9698
- {
9699
- toasts: [...callToasts.toasts.filter((toast) => !(typeof displayedCommandError === "object" && displayedCommandError && toast.failureCode === displayedCommandError.code)), ...whiteboard?.isOpen ? boardToasts : [], ...commandToasts],
9700
- onDismiss: (id) => {
9701
- if (id === "space-command-error") dismissCommandError();
9702
- else if (id.startsWith("board-")) setBoardToasts((current) => current.filter((toast) => toast.id !== id));
9703
- else callToasts.dismiss(id);
9704
- }
9705
- }
9706
- ),
9707
- mediaOutput: /* @__PURE__ */ jsx76("div", { className: "[&>div]:top-[68px] [&>div>button]:h-11 [&>div>button]:shadow-[0_8px_24px_rgba(0,0,0,.4)]", children: /* @__PURE__ */ jsx76(AudioOutput, { resumeAudioLabel: "Your browser paused the sound. Click to hear the call" }) }),
9708
- overlays: /* @__PURE__ */ jsxs49(Fragment32, { children: [
9709
- /* @__PURE__ */ jsx76(CallReady, { open: callLinkOpen ?? callLink.open, onClose: onCallLinkToggle ?? callLink.close, link: effectiveShareUrl }),
10290
+ toasts: /* @__PURE__ */ jsx77(ToastStack, { toasts, onDismiss: dismissToast, onFold: setFoldedNotices }),
10291
+ mediaOutput: /* @__PURE__ */ jsx77("div", { className: "[&>div]:top-[68px] [&>div>button]:h-11 [&>div>button]:shadow-[0_8px_24px_rgba(0,0,0,.4)]", children: /* @__PURE__ */ jsx77(AudioOutput, { resumeAudioLabel: "Your browser paused the sound. Click to hear the call" }) }),
10292
+ overlays: /* @__PURE__ */ jsxs51(Fragment35, { children: [
10293
+ /* @__PURE__ */ jsx77(CallReady, { open: callLinkOpen ?? callLink.open, onClose: onCallLinkToggle ?? callLink.close, link: effectiveShareUrl }),
9710
10294
  overlay,
9711
- reconnecting && reconnecting.status !== "reconnecting" ? /* @__PURE__ */ jsx76(ReconnectingOverlay, { ...reconnecting }) : null,
9712
- /* @__PURE__ */ jsx76(OfflineActions, { reconnecting: reconnecting?.status === "reconnecting" && reconnecting.isVisible })
10295
+ reconnecting && reconnecting.status !== "reconnecting" ? /* @__PURE__ */ jsx77(ReconnectingOverlay, { ...reconnecting }) : null,
10296
+ /* @__PURE__ */ jsx77(OfflineActions, { reconnecting: reconnecting?.status === "reconnecting" && reconnecting.isVisible, hasNotice: offlineNotice !== null })
9713
10297
  ] }),
9714
10298
  className
9715
10299
  }
@@ -9729,7 +10313,9 @@ export {
9729
10313
  useRecording,
9730
10314
  useWhiteboard,
9731
10315
  useCan,
10316
+ useCanDrawWhiteboard,
9732
10317
  useScreenShare,
10318
+ commandFailure,
9733
10319
  chalkThemeStyle,
9734
10320
  ChalkButton,
9735
10321
  ChalkIconButton,
@@ -9766,9 +10352,9 @@ export {
9766
10352
  ControlBarPresentation,
9767
10353
  ControlBar,
9768
10354
  ParticipantsPanel,
9769
- ToastStack,
9770
10355
  StatusSurface,
9771
10356
  CallReady,
10357
+ ToastStack,
9772
10358
  CallCard,
9773
10359
  captureFlip,
9774
10360
  CallDialog,