@q9labsai/chalk-react 5.2.2 → 5.2.3

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.
@@ -866,7 +866,7 @@ function MediaRequestDialog({ request, onDecline, onAllow, onActionError }) {
866
866
  }
867
867
 
868
868
  // src/components/admission-panel/AdmissionPanel.tsx
869
- import React8, { useEffect as useEffect10, useState as useState14 } from "react";
869
+ import React8, { useEffect as useEffect10, useState as useState13 } from "react";
870
870
  import { IconButton, Spinner } from "@q9labsai/chalk-ui";
871
871
 
872
872
  // src/components/audio-output/AudioOutput.tsx
@@ -1226,7 +1226,7 @@ function useHaptics({ enabled = true } = {}) {
1226
1226
 
1227
1227
  // src/components/participant-tile/ParticipantTile.tsx
1228
1228
  import { Facehash as Facehash2 } from "@q9labsai/facehash/react";
1229
- import React5, { useMemo as useMemo6, useRef as useRef6, useState as useState12 } from "react";
1229
+ import React5, { useMemo as useMemo6, useRef as useRef6 } from "react";
1230
1230
 
1231
1231
  // src/internal/useMirrorSetting.ts
1232
1232
  import { useSyncExternalStore as useSyncExternalStore2 } from "react";
@@ -1478,7 +1478,7 @@ var ParticipantTileSurface = React5.memo(function ParticipantTileSurface2({
1478
1478
  onClick,
1479
1479
  onDoubleClick,
1480
1480
  pinned,
1481
- clickAction = "pin",
1481
+ onFollow,
1482
1482
  following = false,
1483
1483
  primary,
1484
1484
  className,
@@ -1492,34 +1492,24 @@ var ParticipantTileSurface = React5.memo(function ParticipantTileSurface2({
1492
1492
  }) {
1493
1493
  const prefersReducedMotion = usePrefersReducedMotion();
1494
1494
  const phone = useMediaQuery("(max-width:640px)");
1495
- const [optionsOpen, setOptionsOpen] = useState12(false);
1496
- const returnFocus = useRef6(null);
1497
- const openOptions = () => {
1498
- returnFocus.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
1499
- setOptionsOpen(true);
1500
- };
1501
- const closeOptions = () => {
1502
- setOptionsOpen(false);
1503
- queueMicrotask(() => returnFocus.current?.focus());
1504
- };
1505
1495
  const [localMirrored] = useMirrorSetting();
1506
1496
  const mirrored = mirror ?? (participant.isLocal === true && localMirrored);
1507
1497
  const colors = useMemo6(() => getParticipantColor(participant.displayName || participant.id, gradientPreference), [gradientPreference, participant.displayName, participant.id]);
1508
1498
  const recipe = useMemo6(() => getParticipantAvatarRecipe(participant.displayName || participant.id, gradientPreference), [participant.displayName, participant.id, gradientPreference]);
1509
- const first = participant.displayName.split(" ")[0] ?? participant.displayName;
1510
- const actionLabel = clickAction === "follow" ? `${following ? "Stop following" : "Follow"} ${first}` : `${pinned ? "Unpin" : "Pin"} ${participant.displayName}`;
1499
+ const actionLabel = `${pinned ? "Unpin" : "Pin"} ${participant.displayName}`;
1500
+ const followLabel = following ? "Stop following" : "Follow";
1511
1501
  const hasPoorConnection = participant.connectionQuality !== void 0 && participant.connectionQuality > 0 && participant.connectionQuality <= 2;
1512
1502
  return /* @__PURE__ */ jsxs12(
1513
1503
  TileShell,
1514
1504
  {
1515
- label: clickAction === "follow" ? actionLabel : `Video tile for ${participant.displayName}`,
1505
+ label: `Video tile for ${participant.displayName}`,
1516
1506
  accentColor: colors.primary,
1517
- pinned: clickAction === "follow" ? following : pinned,
1507
+ pinned,
1518
1508
  ring: following,
1519
1509
  lift: !primary,
1520
1510
  speaking: mediaVisible && participant.isSpeaking && !participant.isMuted,
1521
1511
  audioLevel: participant.audioLevel,
1522
- onClick: onClick && phone && clickAction === "pin" ? openOptions : onClick,
1512
+ onClick,
1523
1513
  onDoubleClick,
1524
1514
  className: cn(aspectRatioClasses[aspectRatio], className),
1525
1515
  style,
@@ -1527,11 +1517,14 @@ var ParticipantTileSurface = React5.memo(function ParticipantTileSurface2({
1527
1517
  hidden,
1528
1518
  corner: /* @__PURE__ */ jsxs12(Fragment6, { children: [
1529
1519
  showStatus && hasPoorConnection ? /* @__PURE__ */ jsx27("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__ */ jsx27(SignalBars, {}) }) : null,
1530
- onClick && !phone ? /* @__PURE__ */ jsx27("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__ */ jsx27(
1520
+ onClick || onFollow ? /* @__PURE__ */ jsx27("span", { "data-part": "tile-menu", className: "absolute top-2 right-2 z-30 transition-opacity duration-150 min-[641px]:opacity-0 group-hover/tile:opacity-100 focus-within:opacity-100", children: /* @__PURE__ */ jsxs12(
1531
1521
  ChalkMenu,
1532
1522
  {
1533
1523
  trigger: /* @__PURE__ */ jsx27("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__ */ jsx27(StageIcon, { name: "more", size: 18 }) }),
1534
- children: /* @__PURE__ */ jsx27(ChalkMenuItem, { onClick, children: actionLabel })
1524
+ children: [
1525
+ onClick ? /* @__PURE__ */ jsx27(ChalkMenuItem, { onClick, children: actionLabel }) : null,
1526
+ onFollow ? /* @__PURE__ */ jsx27(ChalkMenuItem, { onClick: onFollow, children: followLabel }) : null
1527
+ ]
1535
1528
  }
1536
1529
  ) }) : null
1537
1530
  ] }),
@@ -1571,38 +1564,6 @@ var ParticipantTileSurface = React5.memo(function ParticipantTileSurface2({
1571
1564
  colors: recipe.facehashColors
1572
1565
  }
1573
1566
  ) : /* @__PURE__ */ jsx27(Avatar, { name: participant.displayName, src: participant.avatarUrl, size: "xl", generated: false, style: { width: "100%", height: "100%" } }) }) }),
1574
- phone && onClick && clickAction === "pin" ? /* @__PURE__ */ jsxs12(
1575
- ChalkSheet,
1576
- {
1577
- open: optionsOpen,
1578
- onOpenChange: (open) => {
1579
- if (open) openOptions();
1580
- else closeOptions();
1581
- },
1582
- title: `Options for ${participant.displayName}`,
1583
- children: [
1584
- /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-3 px-2 pt-1 pb-3.5 text-base font-semibold", children: [
1585
- /* @__PURE__ */ jsx27(Avatar, { name: participant.displayName, size: "sm" }),
1586
- participant.displayName
1587
- ] }),
1588
- /* @__PURE__ */ jsxs12(
1589
- "button",
1590
- {
1591
- type: "button",
1592
- className: "flex min-h-[52px] w-full items-center gap-3.5 rounded-[16px] bg-[var(--chalk-raised)] px-4 text-left text-base leading-[1.4] hover:bg-[var(--chalk-hover)] active:bg-[var(--chalk-pressed)]",
1593
- onClick: () => {
1594
- onClick();
1595
- closeOptions();
1596
- },
1597
- children: [
1598
- /* @__PURE__ */ jsx27(StageIcon, { name: "pin", size: 20, className: "text-[var(--chalk-soft-text)]" }),
1599
- /* @__PURE__ */ jsx27("span", { children: pinned ? "Unpin" : "Pin" })
1600
- ]
1601
- }
1602
- )
1603
- ]
1604
- }
1605
- ) : null,
1606
1567
  children
1607
1568
  ]
1608
1569
  }
@@ -1615,7 +1576,7 @@ function SignalBars() {
1615
1576
  }
1616
1577
 
1617
1578
  // src/components/atomic/ReactionBubble.tsx
1618
- import React6, { useEffect as useEffect9, useState as useState13, useMemo as useMemo7 } from "react";
1579
+ import React6, { useEffect as useEffect9, useState as useState12, useMemo as useMemo7 } from "react";
1619
1580
  import { CELEBRATION_EMOJIS } from "@q9labsai/chalk-ui/reactions";
1620
1581
  import { jsx as jsx28, jsxs as jsxs13 } from "react/jsx-runtime";
1621
1582
  var BASE_PARTICLE_COLORS = ["#14b8a6", "#f59e0b", "#ec4899", "#8b5cf6", "#06b6d4", "#22c55e"];
@@ -1659,7 +1620,7 @@ function getParticleStyle(particle) {
1659
1620
  };
1660
1621
  }
1661
1622
  var ReactionBubble = React6.memo(({ emoji, participantName, onComplete, duration: baseDuration = 3e3, animated = true, className }) => {
1662
- const [isVisible, setIsVisible] = useState13(true);
1623
+ const [isVisible, setIsVisible] = useState12(true);
1663
1624
  const prefersReducedMotion = usePrefersReducedMotion();
1664
1625
  const participantColors = useMemo7(() => getParticipantColor(participantName || "unknown"), [participantName]);
1665
1626
  const isCelebration = CELEBRATION_EMOJIS.includes(emoji);
@@ -1747,13 +1708,13 @@ function getWaitingLabel(joinedAt) {
1747
1708
  return `Waiting ${minutes} min`;
1748
1709
  }
1749
1710
  var AdmissionPanelSurface = React8.memo(({ participants, onAdmit, onDeny, onAdmitAll, onDenyAll, loading = false, className, onClose, inline = false }) => {
1750
- const [, setTick] = useState14(0);
1711
+ const [, setTick] = useState13(0);
1751
1712
  useEffect10(() => {
1752
1713
  const timer = window.setInterval(() => setTick((t) => t + 1), 6e4);
1753
1714
  return () => window.clearInterval(timer);
1754
1715
  }, []);
1755
- const [busy, setBusy] = useState14(false);
1756
- const [error, setError] = useState14(null);
1716
+ const [busy, setBusy] = useState13(false);
1717
+ const [error, setError] = useState13(null);
1757
1718
  const perform = async (action) => {
1758
1719
  if (busy) return;
1759
1720
  setBusy(true);
@@ -1826,7 +1787,7 @@ AdmissionPanel.displayName = "AdmissionPanel";
1826
1787
 
1827
1788
  // src/components/composite/ChatPanel.tsx
1828
1789
  import { CHALK_CHAT_ATTACHMENT_LIMITS, CHALK_CHAT_ATTACHMENT_MIME_TYPES } from "@q9labsai/chalk-client";
1829
- import React10, { useEffect as useEffect13, useLayoutEffect, useMemo as useMemo8, useRef as useRef9, useState as useState17 } from "react";
1790
+ import React10, { useEffect as useEffect13, useLayoutEffect, useMemo as useMemo8, useRef as useRef9, useState as useState16 } from "react";
1830
1791
  import { Button } from "@q9labsai/chalk-ui";
1831
1792
 
1832
1793
  // src/components/call-card/CallCardStyles.tsx
@@ -2689,14 +2650,14 @@ function CallCardStyles() {
2689
2650
 
2690
2651
  // src/components/composite/MessageBubble.tsx
2691
2652
  import { CHALK_REACTIONS } from "@q9labsai/chalk-client";
2692
- import React9, { useEffect as useEffect12, useRef as useRef8, useState as useState16 } from "react";
2653
+ import React9, { useEffect as useEffect12, useRef as useRef8, useState as useState15 } from "react";
2693
2654
 
2694
2655
  // src/components/composite/use-chat-attachment-previews.ts
2695
- import { useCallback as useCallback4, useEffect as useEffect11, useRef as useRef7, useState as useState15 } from "react";
2656
+ import { useCallback as useCallback4, useEffect as useEffect11, useRef as useRef7, useState as useState14 } from "react";
2696
2657
  function useChatAttachmentPreviews(attachments, resolveUrl) {
2697
- const [resolvedUrls, setResolvedUrls] = useState15(() => /* @__PURE__ */ new Map());
2698
- const [failedAttachmentIds, setFailedAttachmentIds] = useState15(() => /* @__PURE__ */ new Set());
2699
- const [retryGeneration, setRetryGeneration] = useState15(0);
2658
+ const [resolvedUrls, setResolvedUrls] = useState14(() => /* @__PURE__ */ new Map());
2659
+ const [failedAttachmentIds, setFailedAttachmentIds] = useState14(() => /* @__PURE__ */ new Set());
2660
+ const [retryGeneration, setRetryGeneration] = useState14(0);
2700
2661
  const resolvingAttachmentIds = useRef7(/* @__PURE__ */ new Set());
2701
2662
  const resolvedUrlsRef = useRef7(resolvedUrls);
2702
2663
  const failedAttachmentIdsRef = useRef7(failedAttachmentIds);
@@ -2779,9 +2740,9 @@ var MessageBubble = React9.memo(
2779
2740
  className
2780
2741
  }) => {
2781
2742
  const { resolvedUrls, failedAttachmentIds, markFailed, retry } = useChatAttachmentPreviews(attachments, onResolveAttachmentUrl);
2782
- const [reactionError, setReactionError] = useState16(null);
2783
- const [reacting, setReacting] = useState16(false);
2784
- const [reactionBarOpen, setReactionBarOpen] = useState16(false);
2743
+ const [reactionError, setReactionError] = useState15(null);
2744
+ const [reacting, setReacting] = useState15(false);
2745
+ const [reactionBarOpen, setReactionBarOpen] = useState15(false);
2785
2746
  const messageRef = useRef8(null);
2786
2747
  const press = useRef8(null);
2787
2748
  const cancelPress = () => {
@@ -2798,9 +2759,9 @@ var MessageBubble = React9.memo(
2798
2759
  document.addEventListener("pointerdown", dismiss);
2799
2760
  return () => document.removeEventListener("pointerdown", dismiss);
2800
2761
  }, [reactionBarOpen]);
2801
- const [photo, setPhoto] = useState16(null);
2802
- const [downloading, setDownloading] = useState16(false);
2803
- const [downloadError, setDownloadError] = useState16(null);
2762
+ const [photo, setPhoto] = useState15(null);
2763
+ const [downloading, setDownloading] = useState15(false);
2764
+ const [downloadError, setDownloadError] = useState15(null);
2804
2765
  const downloadController = useRef8(null);
2805
2766
  useEffect12(() => () => downloadController.current?.abort(), []);
2806
2767
  const downloadPhoto = async () => {
@@ -2832,7 +2793,7 @@ var MessageBubble = React9.memo(
2832
2793
  downloadController.current = null;
2833
2794
  }
2834
2795
  };
2835
- const [photoClosing, setPhotoClosing] = useState16(false);
2796
+ const [photoClosing, setPhotoClosing] = useState15(false);
2836
2797
  const closeTimer = useRef8(null);
2837
2798
  useEffect12(
2838
2799
  () => () => {
@@ -3188,12 +3149,12 @@ var ChatPanelSurface = React10.memo(
3188
3149
  autoScrollBehavior,
3189
3150
  className
3190
3151
  }) => {
3191
- const [draft, setDraft] = useState17("");
3192
- const [stagedFiles, setStagedFiles] = useState17([]);
3152
+ const [draft, setDraft] = useState16("");
3153
+ const [stagedFiles, setStagedFiles] = useState16([]);
3193
3154
  const uploads = useRef9(/* @__PURE__ */ new Map());
3194
- const [failedUploads, setFailedUploads] = useState17(/* @__PURE__ */ new Set());
3195
- const [uploading, setUploading] = useState17(/* @__PURE__ */ new Set());
3196
- const [previews, setPreviews] = useState17(/* @__PURE__ */ new Map());
3155
+ const [failedUploads, setFailedUploads] = useState16(/* @__PURE__ */ new Set());
3156
+ const [uploading, setUploading] = useState16(/* @__PURE__ */ new Set());
3157
+ const [previews, setPreviews] = useState16(/* @__PURE__ */ new Map());
3197
3158
  useEffect13(() => {
3198
3159
  const urls = new Map(stagedFiles.flatMap((file) => file instanceof File && file.type.startsWith("image/") ? [[file, URL.createObjectURL(file)]] : []));
3199
3160
  setPreviews(urls);
@@ -3201,9 +3162,9 @@ var ChatPanelSurface = React10.memo(
3201
3162
  for (const url of urls.values()) URL.revokeObjectURL(url);
3202
3163
  };
3203
3164
  }, [stagedFiles]);
3204
- const [sending, setSending] = useState17(false);
3205
- const [pickingFiles, setPickingFiles] = useState17(false);
3206
- const [composerError, setComposerError] = useState17(null);
3165
+ const [sending, setSending] = useState16(false);
3166
+ const [pickingFiles, setPickingFiles] = useState16(false);
3167
+ const [composerError, setComposerError] = useState16(null);
3207
3168
  const scrollRef = useRef9(null);
3208
3169
  const textareaRef = useRef9(null);
3209
3170
  const fileInputRef = useRef9(null);
@@ -3519,7 +3480,7 @@ var ChatPanel = React10.memo((props) => {
3519
3480
  ChatPanel.displayName = "ChatPanel";
3520
3481
 
3521
3482
  // src/components/composite/ReactionsOverlay.tsx
3522
- import { useEffect as useEffect14, useRef as useRef10, useState as useState18 } from "react";
3483
+ import { useEffect as useEffect14, useRef as useRef10, useState as useState17 } from "react";
3523
3484
  import { createPortal } from "react-dom";
3524
3485
 
3525
3486
  // src/components/composite/reaction-float.ts
@@ -3563,7 +3524,7 @@ function ReactionsOverlaySurface({ reactions, maxVisible = 6, className }) {
3563
3524
  return /* @__PURE__ */ jsx34("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__ */ jsx34(TileReaction, { reaction, root }, reaction.eventId)) });
3564
3525
  }
3565
3526
  function TileReaction({ reaction, root }) {
3566
- const [host, setHost] = useState18(null);
3527
+ const [host, setHost] = useState17(null);
3567
3528
  const emoji = useRef10(null);
3568
3529
  const reduced = usePrefersReducedMotion();
3569
3530
  useEffect14(() => {
@@ -3609,10 +3570,10 @@ function CommandErrorAlert({ message }) {
3609
3570
  }
3610
3571
 
3611
3572
  // src/components/control-bar/ControlBar.tsx
3612
- import React12, { useEffect as useEffect17, useRef as useRef12, useState as useState23 } from "react";
3573
+ import React12, { useEffect as useEffect17, useRef as useRef12, useState as useState22 } from "react";
3613
3574
 
3614
3575
  // src/components/device-popover/DevicePopover.tsx
3615
- import { useEffect as useEffect15, useRef as useRef11, useState as useState19 } from "react";
3576
+ import { useEffect as useEffect15, useRef as useRef11, useState as useState18 } from "react";
3616
3577
 
3617
3578
  // src/components/control-bar/ShellIcon.tsx
3618
3579
  import { Fragment as Fragment11, jsx as jsx36, jsxs as jsxs19 } from "react/jsx-runtime";
@@ -4271,7 +4232,7 @@ var CALL_SHELL_CSS = `.chalk-shell-dock {
4271
4232
  // src/components/device-popover/DevicePopover.tsx
4272
4233
  import { Fragment as Fragment12, jsx as jsx38, jsxs as jsxs20 } from "react/jsx-runtime";
4273
4234
  function InputLevel({ track }) {
4274
- const [level, setLevel] = useState19(0);
4235
+ const [level, setLevel] = useState18(0);
4275
4236
  useEffect15(() => {
4276
4237
  if (!track || track.readyState !== "live") return;
4277
4238
  const context = new AudioContext();
@@ -4295,7 +4256,7 @@ function InputLevel({ track }) {
4295
4256
  }
4296
4257
  function CameraPreview({ track, mirrored, open, selectedDeviceId, captureCameraPreview }) {
4297
4258
  const video = useRef11(null);
4298
- const [unavailable2, setUnavailable] = useState19(false);
4259
+ const [unavailable2, setUnavailable] = useState18(false);
4299
4260
  const trackEnabled = track?.enabled;
4300
4261
  useEffect15(() => {
4301
4262
  const element = video.current;
@@ -4364,7 +4325,7 @@ function DevicePopover({
4364
4325
  toggleDisabled,
4365
4326
  busy
4366
4327
  }) {
4367
- const [open, setOpen] = useState19(false);
4328
+ const [open, setOpen] = useState18(false);
4368
4329
  const mic = type === "mic";
4369
4330
  const label = mic ? isActive ? "Mute microphone" : "Unmute microphone" : isActive ? "Turn off camera" : "Turn on camera";
4370
4331
  return /* @__PURE__ */ jsxs20("div", { "data-part": `${type}-split`, className: cn("chalk-shell-split", className), "data-off": !isActive, children: [
@@ -4431,10 +4392,10 @@ function LeaveMenu({ onLeave, onEndForEveryone, renderButton }) {
4431
4392
  }
4432
4393
 
4433
4394
  // src/components/control-bar/DockLayout.tsx
4434
- import { useState as useState20 } from "react";
4395
+ import { useState as useState19 } from "react";
4435
4396
  import { Fragment as Fragment13, jsx as jsx41, jsxs as jsxs23 } from "react/jsx-runtime";
4436
4397
  function DockLayout({ layout, onLayoutChange, children }) {
4437
- const [open, setOpen] = useState20(false);
4398
+ const [open, setOpen] = useState19(false);
4438
4399
  return /* @__PURE__ */ jsxs23(Fragment13, { children: [
4439
4400
  /* @__PURE__ */ jsx41(ChalkIconButton, { size: "lg", "aria-label": "More options", "aria-haspopup": "dialog", "aria-expanded": open, onClick: () => setOpen(true), children: /* @__PURE__ */ jsx41("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__ */ jsx41("path", { "data-part": "dock-more-glyph", d: "M5 12h.01M12 12h.01M19 12h.01", strokeWidth: "3" }) }) }),
4440
4401
  /* @__PURE__ */ jsx41(ChalkSheet, { title: "More options", open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs23(
@@ -4475,7 +4436,7 @@ function DockLayout({ layout, onLayoutChange, children }) {
4475
4436
  }
4476
4437
 
4477
4438
  // src/components/control-bar/ShareControl.tsx
4478
- import { useState as useState21 } from "react";
4439
+ import { useState as useState20 } from "react";
4479
4440
  import { Fragment as Fragment14, jsx as jsx42, jsxs as jsxs24 } from "react/jsx-runtime";
4480
4441
  function SourceIcon({ source }) {
4481
4442
  return /* @__PURE__ */ jsx42("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__ */ jsxs24(Fragment14, { children: [
@@ -4490,9 +4451,9 @@ function SourceIcon({ source }) {
4490
4451
  ] }) });
4491
4452
  }
4492
4453
  function ShareControl({ sharing, onToggle, onStart, disabled = false, startDisabled = false }) {
4493
- const [surface, setSurface] = useState21("window");
4494
- const [audio, setAudio] = useState21(true);
4495
- const [replace, setReplace] = useState21(false);
4454
+ const [surface, setSurface] = useState20("window");
4455
+ const [audio, setAudio] = useState20(true);
4456
+ const [replace, setReplace] = useState20(false);
4496
4457
  const start2 = (next) => {
4497
4458
  if (disabled || startDisabled) return;
4498
4459
  setSurface(next);
@@ -4547,7 +4508,7 @@ function ShareControl({ sharing, onToggle, onStart, disabled = false, startDisab
4547
4508
  }
4548
4509
 
4549
4510
  // src/components/control-bar/control-bar-behavior.ts
4550
- import { useCallback as useCallback5, useEffect as useEffect16, useMemo as useMemo9, useState as useState22 } from "react";
4511
+ import { useCallback as useCallback5, useEffect as useEffect16, useMemo as useMemo9, useState as useState21 } from "react";
4551
4512
  var DEFAULT_CONTROL_BAR_BUTTONS = ["mic", "video", "screenshare", "record", "whiteboard", "handraise", "leave", "participants", "chat", "reactions"];
4552
4513
  var EMPTY_DETECTED_DEVICES = {
4553
4514
  audioinput: [],
@@ -4584,7 +4545,7 @@ function useControlBarSurfaceBehavior({
4584
4545
  detectDevices = true
4585
4546
  }) {
4586
4547
  const themeVariables = useMemo9(() => getParticipantThemeVariables(participantColorSeed, participantGradientPreference), [participantColorSeed, participantGradientPreference]);
4587
- const [detectedDevices, setDetectedDevices] = useState22(EMPTY_DETECTED_DEVICES);
4548
+ const [detectedDevices, setDetectedDevices] = useState21(EMPTY_DETECTED_DEVICES);
4588
4549
  const buttonsToRender = buttons ?? DEFAULT_CONTROL_BAR_BUTTONS;
4589
4550
  useEffect16(() => {
4590
4551
  if (!detectDevices || density !== "comfortable" || !navigator.mediaDevices?.enumerateDevices) return;
@@ -4643,10 +4604,10 @@ function useConnectedControlBarBehavior(props) {
4643
4604
  const recordingStatus = recording.current?.status;
4644
4605
  const isRecording = recordingStatus === "recording" || recordingStatus === "stopping";
4645
4606
  const isRecordingPending = recordingStatus === "starting" || recordingStatus === "stopping";
4646
- const [noiseSuppression, setNoiseSuppression] = useState22(true);
4647
- const [commandError, setCommandError] = useState22(null);
4648
- const [commandSource, setCommandSource] = useState22();
4649
- const [commandAction, setCommandAction] = useState22();
4607
+ const [noiseSuppression, setNoiseSuppression] = useState21(true);
4608
+ const [commandError, setCommandError] = useState21(null);
4609
+ const [commandSource, setCommandSource] = useState21();
4610
+ const [commandAction, setCommandAction] = useState21();
4650
4611
  const run = useCallback5(
4651
4612
  async (command, source, action) => {
4652
4613
  setCommandError(null);
@@ -4806,8 +4767,8 @@ function ControlBarPresentation(props) {
4806
4767
  var ControlBarSurface = React12.memo((props) => {
4807
4768
  const { buttonsToRender, effectiveAudioInputDevices, effectiveAudioOutputDevices, effectiveVideoInputDevices, showLeave } = useControlBarSurfaceBehavior({ ...props, density: props.density ?? "comfortable", detectDevices: props.detectDevices ?? true });
4808
4769
  const dock = useRef12(null);
4809
- const [fullscreen, setFullscreen] = useState23(false);
4810
- const [canCaptureDisplay, setCanCaptureDisplay] = useState23(false);
4770
+ const [fullscreen, setFullscreen] = useState22(false);
4771
+ const [canCaptureDisplay, setCanCaptureDisplay] = useState22(false);
4811
4772
  useEffect17(() => setCanCaptureDisplay(typeof navigator.mediaDevices?.getDisplayMedia === "function"), []);
4812
4773
  useEffect17(() => {
4813
4774
  const changed = () => setFullscreen(Boolean(document.fullscreenElement));
@@ -4958,10 +4919,10 @@ function ControlBar(props) {
4958
4919
  }
4959
4920
 
4960
4921
  // src/components/participants-panel/ParticipantsPanel.tsx
4961
- import React13, { useMemo as useMemo10, useState as useState25 } from "react";
4922
+ import React13, { useMemo as useMemo10, useState as useState24 } from "react";
4962
4923
 
4963
4924
  // src/components/participants-panel/participant-row.tsx
4964
- import { useState as useState24, useRef as useRef13, useEffect as useEffect18 } from "react";
4925
+ import { useState as useState23, useRef as useRef13, useEffect as useEffect18 } from "react";
4965
4926
 
4966
4927
  // src/components/participants-panel/participant-options-menu-contract.ts
4967
4928
  function runParticipantAction(action, onClose, onCommandError) {
@@ -5129,14 +5090,15 @@ function ParticipantRow({
5129
5090
  onCommandError,
5130
5091
  participantVolumes,
5131
5092
  onParticipantVolumeChange,
5093
+ onParticipantClick,
5132
5094
  participantGradientPreference,
5133
5095
  generatedAvatars = true,
5134
5096
  menuOpen,
5135
5097
  onMenuToggle,
5136
5098
  onMenuClose
5137
5099
  }) {
5138
- const [isEditing, setIsEditing] = useState24(false);
5139
- const [editName, setEditName] = useState24(participant.displayName);
5100
+ const [isEditing, setIsEditing] = useState23(false);
5101
+ const [editName, setEditName] = useState23(participant.displayName);
5140
5102
  const inputRef = useRef13(null);
5141
5103
  const rootRef = useRef13(null);
5142
5104
  const hasVolumeControl = !!participantVolumes && !!onParticipantVolumeChange;
@@ -5161,78 +5123,100 @@ function ParticipantRow({
5161
5123
  }
5162
5124
  };
5163
5125
  const optionsButtonClassName = variant === "mobile" ? "text-[var(--chalk-app-text-muted)] hover:text-[var(--chalk-app-text)]" : variant === "sidebar" ? "opacity-70 text-[var(--chalk-app-text-muted)] hover:text-[var(--chalk-app-text)] hover:opacity-100" : "opacity-0 group-hover:opacity-100 group-focus-within:opacity-100";
5164
- return /* @__PURE__ */ jsxs27("div", { ref: rootRef, className: "call-person-row group", children: [
5165
- /* @__PURE__ */ jsxs27("div", { className: "flex items-center gap-2.5 min-w-0 flex-1", children: [
5166
- /* @__PURE__ */ jsx45("div", { className: "relative", children: /* @__PURE__ */ jsx45(Avatar, { name: participant.displayName, src: participant.avatarUrl, size: "sm", generated: generatedAvatars, className: "!h-8 !w-8", gradientPreference: participantGradientPreference }) }),
5167
- /* @__PURE__ */ jsxs27("div", { className: "flex flex-col min-w-0 flex-1", children: [
5168
- /* @__PURE__ */ jsxs27("div", { className: "flex items-center gap-1.5 w-full", children: [
5169
- isEditing ? /* @__PURE__ */ jsx45("div", { className: "flex items-center gap-1 flex-1", children: /* @__PURE__ */ jsx45(
5170
- "input",
5171
- {
5172
- ref: inputRef,
5173
- type: "text",
5174
- "aria-label": "Your name",
5175
- value: editName,
5176
- onChange: (e) => setEditName(e.target.value),
5177
- onKeyDown: handleKeyDown,
5178
- onBlur: handleSave,
5179
- className: "w-full min-w-0 rounded border border-[var(--chalk-app-control-active-line)]/30 bg-[var(--chalk-app-input)] px-1.5 py-0.5 text-sm font-normal text-[var(--chalk-app-text)] outline-none focus:border-[var(--chalk-app-control-active-line)]"
5180
- }
5181
- ) }) : /* @__PURE__ */ jsx45("span", { className: "min-w-0 truncate text-sm font-medium text-[var(--chalk-app-text)]", onClick: () => participant.isLocal && onUpdateDisplayName && setIsEditing(true), title: participant.isLocal && onUpdateDisplayName ? "Click to edit" : void 0, children: /* @__PURE__ */ jsx45("bdi", { children: participant.displayName }) }),
5182
- participant.isHost && !isEditing ? /* @__PURE__ */ jsx45("span", { className: "shrink-0 text-xs text-[var(--chalk-app-text-muted)]", children: "Host" }) : null,
5183
- participant.isLocal && !isEditing && /* @__PURE__ */ jsxs27("span", { className: "flex shrink-0 items-center gap-1 text-xs text-[var(--chalk-app-text-muted)]", children: [
5184
- "(You)",
5185
- onUpdateDisplayName && /* @__PURE__ */ jsx45("button", { type: "button", onClick: () => setIsEditing(true), "aria-label": "Edit your name", className: "call-edit-name", children: /* @__PURE__ */ jsx45(Edit02Icon, { className: "w-3 h-3" }) })
5126
+ return /* @__PURE__ */ jsxs27(
5127
+ "div",
5128
+ {
5129
+ ref: rootRef,
5130
+ className: "call-person-row group",
5131
+ onClick: (event) => {
5132
+ if (event.target instanceof Element && event.currentTarget.contains(event.target) && !event.target.closest("button, input, [role=menu]")) onParticipantClick?.(participant.id);
5133
+ },
5134
+ children: [
5135
+ /* @__PURE__ */ jsxs27("div", { className: "flex items-center gap-2.5 min-w-0 flex-1", children: [
5136
+ /* @__PURE__ */ jsx45("div", { className: "relative", children: /* @__PURE__ */ jsx45(Avatar, { name: participant.displayName, src: participant.avatarUrl, size: "sm", generated: generatedAvatars, className: "!h-8 !w-8", gradientPreference: participantGradientPreference }) }),
5137
+ /* @__PURE__ */ jsxs27("div", { className: "flex flex-col min-w-0 flex-1", children: [
5138
+ /* @__PURE__ */ jsxs27("div", { className: "flex items-center gap-1.5 w-full", children: [
5139
+ isEditing ? /* @__PURE__ */ jsx45("div", { className: "flex items-center gap-1 flex-1", children: /* @__PURE__ */ jsx45(
5140
+ "input",
5141
+ {
5142
+ ref: inputRef,
5143
+ type: "text",
5144
+ "aria-label": "Your name",
5145
+ value: editName,
5146
+ onChange: (e) => setEditName(e.target.value),
5147
+ onKeyDown: handleKeyDown,
5148
+ onBlur: handleSave,
5149
+ className: "w-full min-w-0 rounded border border-[var(--chalk-app-control-active-line)]/30 bg-[var(--chalk-app-input)] px-1.5 py-0.5 text-sm font-normal text-[var(--chalk-app-text)] outline-none focus:border-[var(--chalk-app-control-active-line)]"
5150
+ }
5151
+ ) }) : /* @__PURE__ */ jsx45(
5152
+ "button",
5153
+ {
5154
+ type: "button",
5155
+ className: "min-w-0 truncate text-left text-sm font-medium text-[var(--chalk-app-text)]",
5156
+ onClick: () => {
5157
+ if (onParticipantClick) onParticipantClick(participant.id);
5158
+ else if (participant.isLocal && onUpdateDisplayName) setIsEditing(true);
5159
+ },
5160
+ "aria-label": onParticipantClick ? `Show ${participant.displayName} on main screen` : participant.displayName,
5161
+ children: /* @__PURE__ */ jsx45("bdi", { children: participant.displayName })
5162
+ }
5163
+ ),
5164
+ participant.isHost && !isEditing ? /* @__PURE__ */ jsx45("span", { className: "shrink-0 text-xs text-[var(--chalk-app-text-muted)]", children: "Host" }) : null,
5165
+ participant.isLocal && !isEditing && /* @__PURE__ */ jsxs27("span", { className: "flex shrink-0 items-center gap-1 text-xs text-[var(--chalk-app-text-muted)]", children: [
5166
+ "(You)",
5167
+ onUpdateDisplayName && /* @__PURE__ */ jsx45("button", { type: "button", onClick: () => setIsEditing(true), "aria-label": "Edit your name", className: "call-edit-name", children: /* @__PURE__ */ jsx45(Edit02Icon, { className: "w-3 h-3" }) })
5168
+ ] })
5169
+ ] }),
5170
+ /* @__PURE__ */ jsx45("div", { className: "flex items-center gap-1.5" })
5186
5171
  ] })
5187
5172
  ] }),
5188
- /* @__PURE__ */ jsx45("div", { className: "flex items-center gap-1.5" })
5189
- ] })
5190
- ] }),
5191
- /* @__PURE__ */ jsxs27("div", { className: "flex items-center gap-1 shrink-0", children: [
5192
- participant.isMuted ? /* @__PURE__ */ jsx45(MicrophoneOff01Icon, { className: "h-4 w-4 text-[var(--chalk-danger-text)]", "aria-label": "Microphone off", role: "img" }) : null,
5193
- participant.isHandRaised ? /* @__PURE__ */ jsxs27("span", { className: "call-hand", role: "img", "aria-label": `Hand raised${participant.handOrder ? `, number ${participant.handOrder}` : ""}`, children: [
5194
- "\u270B",
5195
- participant.handOrder
5196
- ] }) : null,
5197
- !participant.isVideoEnabled ? /* @__PURE__ */ jsx45(VideoOffIcon, { className: "h-4 w-4 text-[var(--chalk-app-text-muted)]", "aria-label": "Camera off", role: "img" }) : null,
5198
- showMenuButton && !isEditing && /* @__PURE__ */ jsxs27(
5199
- ChalkPopoverPrimitive.Root,
5200
- {
5201
- open: menuOpen,
5202
- onOpenChange: (open) => {
5203
- if (open) onMenuToggle();
5204
- else onMenuClose();
5205
- },
5206
- children: [
5207
- /* @__PURE__ */ jsx45(ChalkPopoverPrimitive.Trigger, { className: cn("call-person-options", optionsButtonClassName), "aria-label": `Options for ${participant.displayName}`, children: /* @__PURE__ */ jsx45("svg", { className: "size-5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx45("path", { d: "M5 12h.01M12 12h.01M19 12h.01" }) }) }),
5208
- /* @__PURE__ */ jsx45(ChalkPopoverPrimitive.Portal, { container: rootRef.current?.closest("[data-chalk]") ?? void 0, children: /* @__PURE__ */ jsx45(ChalkPopoverPrimitive.Positioner, { side: "bottom", align: "end", sideOffset: 6, collisionPadding: 8, className: "chalk-popover-positioner", children: /* @__PURE__ */ jsx45(ChalkPopoverPrimitive.Popup, { "aria-label": `Options for ${participant.displayName}`, className: "chalk-menu w-60", children: /* @__PURE__ */ jsx45(
5209
- ParticipantOptionsMenu,
5210
- {
5211
- participant,
5212
- variant,
5213
- canManageParticipants,
5214
- onClose: onMenuClose,
5215
- onAssignRole,
5216
- onStopParticipantScreenShare,
5217
- onMuteParticipant,
5218
- onRequestUnmute,
5219
- onStopParticipantCamera,
5220
- onRequestStartCamera,
5221
- onRemoveParticipant,
5222
- onEditName: participant.isLocal ? () => {
5223
- setIsEditing(true);
5224
- onMenuClose();
5225
- } : void 0,
5226
- participantVolumes,
5227
- onParticipantVolumeChange,
5228
- onCommandError
5229
- }
5230
- ) }) }) })
5231
- ]
5232
- }
5233
- )
5234
- ] })
5235
- ] });
5173
+ /* @__PURE__ */ jsxs27("div", { className: "flex items-center gap-1 shrink-0", children: [
5174
+ participant.isMuted ? /* @__PURE__ */ jsx45(MicrophoneOff01Icon, { className: "h-4 w-4 text-[var(--chalk-danger-text)]", "aria-label": "Microphone off", role: "img" }) : null,
5175
+ participant.isHandRaised ? /* @__PURE__ */ jsxs27("span", { className: "call-hand", role: "img", "aria-label": `Hand raised${participant.handOrder ? `, number ${participant.handOrder}` : ""}`, children: [
5176
+ "\u270B",
5177
+ participant.handOrder
5178
+ ] }) : null,
5179
+ !participant.isVideoEnabled ? /* @__PURE__ */ jsx45(VideoOffIcon, { className: "h-4 w-4 text-[var(--chalk-app-text-muted)]", "aria-label": "Camera off", role: "img" }) : null,
5180
+ showMenuButton && !isEditing && /* @__PURE__ */ jsxs27(
5181
+ ChalkPopoverPrimitive.Root,
5182
+ {
5183
+ open: menuOpen,
5184
+ onOpenChange: (open) => {
5185
+ if (open) onMenuToggle();
5186
+ else onMenuClose();
5187
+ },
5188
+ children: [
5189
+ /* @__PURE__ */ jsx45(ChalkPopoverPrimitive.Trigger, { className: cn("call-person-options", optionsButtonClassName), "aria-label": `Options for ${participant.displayName}`, children: /* @__PURE__ */ jsx45("svg", { className: "size-5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx45("path", { d: "M5 12h.01M12 12h.01M19 12h.01" }) }) }),
5190
+ /* @__PURE__ */ jsx45(ChalkPopoverPrimitive.Portal, { container: rootRef.current?.closest("[data-chalk]") ?? void 0, children: /* @__PURE__ */ jsx45(ChalkPopoverPrimitive.Positioner, { side: "bottom", align: "end", sideOffset: 6, collisionPadding: 8, className: "chalk-popover-positioner", children: /* @__PURE__ */ jsx45(ChalkPopoverPrimitive.Popup, { "aria-label": `Options for ${participant.displayName}`, className: "chalk-menu w-60", children: /* @__PURE__ */ jsx45(
5191
+ ParticipantOptionsMenu,
5192
+ {
5193
+ participant,
5194
+ variant,
5195
+ canManageParticipants,
5196
+ onClose: onMenuClose,
5197
+ onAssignRole,
5198
+ onStopParticipantScreenShare,
5199
+ onMuteParticipant,
5200
+ onRequestUnmute,
5201
+ onStopParticipantCamera,
5202
+ onRequestStartCamera,
5203
+ onRemoveParticipant,
5204
+ onEditName: participant.isLocal ? () => {
5205
+ setIsEditing(true);
5206
+ onMenuClose();
5207
+ } : void 0,
5208
+ participantVolumes,
5209
+ onParticipantVolumeChange,
5210
+ onCommandError
5211
+ }
5212
+ ) }) }) })
5213
+ ]
5214
+ }
5215
+ )
5216
+ ] })
5217
+ ]
5218
+ }
5219
+ );
5236
5220
  }
5237
5221
 
5238
5222
  // src/components/participants-panel/ParticipantsPanel.tsx
@@ -5259,13 +5243,14 @@ var ParticipantsPanelSurface = React13.memo(
5259
5243
  onCommandError,
5260
5244
  participantVolumes,
5261
5245
  onParticipantVolumeChange,
5246
+ onParticipantClick,
5262
5247
  participantGradientPreference,
5263
5248
  generatedAvatars = true
5264
5249
  }) => {
5265
- const [query, setQuery] = useState25("");
5266
- const [activeMenuId, setActiveMenuId] = useState25(null);
5267
- const [muting, setMuting] = useState25(false);
5268
- const [error, setError] = useState25(null);
5250
+ const [query, setQuery] = useState24("");
5251
+ const [activeMenuId, setActiveMenuId] = useState24(null);
5252
+ const [muting, setMuting] = useState24(false);
5253
+ const [error, setError] = useState24(null);
5269
5254
  const filtered = participants.filter((person) => person.displayName.toLocaleLowerCase().includes(query.trim().toLocaleLowerCase()));
5270
5255
  const raised = participants.filter((person) => person.isHandRaised);
5271
5256
  const unmutedCount = participants.filter((person) => !person.isLocal && !person.isMuted).length;
@@ -5328,6 +5313,7 @@ var ParticipantsPanelSurface = React13.memo(
5328
5313
  ParticipantRow,
5329
5314
  {
5330
5315
  participant,
5316
+ onParticipantClick,
5331
5317
  variant,
5332
5318
  canManageParticipants,
5333
5319
  onAssignRole,
@@ -5368,7 +5354,7 @@ var ParticipantsPanel = React13.memo((props) => {
5368
5354
  const canRemoveParticipants = useCan("removeParticipant");
5369
5355
  const canManageAdmission = useCan("manageAdmission");
5370
5356
  const contextVolumeState = useParticipantVolumeContext();
5371
- const [localVolumes, setLocalVolumes] = useState25(/* @__PURE__ */ new Map());
5357
+ const [localVolumes, setLocalVolumes] = useState24(/* @__PURE__ */ new Map());
5372
5358
  const isControlled = props.participantVolumes !== void 0 && props.onParticipantVolumeChange !== void 0;
5373
5359
  const participantVolumes = isControlled ? props.participantVolumes : contextVolumeState?.volumes ?? localVolumes;
5374
5360
  const onParticipantVolumeChange = isControlled ? props.onParticipantVolumeChange : contextVolumeState?.setVolume ?? ((participantId, volume) => setLocalVolumes((current) => {
@@ -5423,11 +5409,12 @@ ParticipantsPanel.displayName = "ParticipantsPanel";
5423
5409
  import { formatSyncConnectDiagnostic, getConnectionFailureDetail } from "@q9labsai/chalk-client";
5424
5410
  import { Fragment as Fragment18, jsx as jsx47, jsxs as jsxs29 } from "react/jsx-runtime";
5425
5411
  function StatusSurface({ phase, callName = "the call", reason, episodeEndReason, endAction, errorCode, sync, theme, onRejoin, onHome, onRetry, onReportProblem, rejoinDisabled = false, retryPending = false }) {
5412
+ const connectionLost = phase === "failed" && errorCode === "connection.sync_recovery_exhausted";
5426
5413
  const copy = {
5427
5414
  entering: { title: `Joining ${callName}\u2026`, detail: "" },
5428
5415
  left: { title: `You left ${callName}`, detail: "" },
5429
5416
  "episode-ended": episodeEndReason === "maximum_duration" ? { title: "The call reached its time limit", detail: "This call has ended for everyone." } : { title: "The call ended", detail: "" },
5430
- failed: { title: `Couldn\u2019t connect to ${callName}`, detail: getConnectionFailureDetail({ code: errorCode, sync }) },
5417
+ failed: { title: connectionLost ? "Connection lost" : `Couldn\u2019t connect to ${callName}`, detail: getConnectionFailureDetail({ code: errorCode, sync }) },
5431
5418
  removed: { title: `You were removed from ${callName}`, detail: "The host removed you from this call." },
5432
5419
  full: { title: "This call is full", detail: `${callName} has reached 100 people. Ask the host to make room, then try again.` },
5433
5420
  elsewhere: { title: "You joined from another tab", detail: `${callName} is open in another tab or window. Use it here instead?` }
@@ -5455,7 +5442,7 @@ function StatusSurface({ phase, callName = "the call", reason, episodeEndReason,
5455
5442
  ] }) : null,
5456
5443
  /* @__PURE__ */ jsxs29("div", { "data-part": "status-actions", className: "mt-5 flex gap-7", children: [
5457
5444
  phase === "episode-ended" && episodeEndReason === "maximum_duration" && endAction ? /* @__PURE__ */ jsx47(StatusAction, { icon: /* @__PURE__ */ jsx47(StatusIcon, { retry: true }), label: endAction.label, onClick: endAction.onClick }) : null,
5458
- canRetry && retry ? /* @__PURE__ */ jsx47(StatusAction, { icon: /* @__PURE__ */ jsx47(StatusIcon, { retry: true }), label: phase === "elsewhere" ? "Use here" : phase === "left" ? "Rejoin" : "Try again", onClick: retry, disabled: rejoinDisabled || retryPending }) : null,
5445
+ canRetry && retry ? /* @__PURE__ */ jsx47(StatusAction, { icon: /* @__PURE__ */ jsx47(StatusIcon, { retry: true }), label: phase === "elsewhere" ? "Use here" : phase === "left" || connectionLost ? "Rejoin" : "Try again", onClick: retry, disabled: rejoinDisabled || retryPending }) : null,
5459
5446
  phase !== "entering" && onHome ? /* @__PURE__ */ jsx47(StatusAction, { icon: /* @__PURE__ */ jsx47(StatusIcon, {}), label: "Home", onClick: onHome }) : null
5460
5447
  ] }),
5461
5448
  onReportProblem && phase !== "entering" ? /* @__PURE__ */ jsx47("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
@@ -5486,7 +5473,7 @@ function StatusAction({ icon, label, onClick, disabled }) {
5486
5473
  }
5487
5474
 
5488
5475
  // src/components/call-limbo/CallReady.tsx
5489
- import { useEffect as useEffect19, useId, useRef as useRef14, useState as useState26 } from "react";
5476
+ import { useEffect as useEffect19, useId, useRef as useRef14, useState as useState25 } from "react";
5490
5477
 
5491
5478
  // src/components/call-limbo/qr-code.ts
5492
5479
  function inviteQR(text) {
@@ -5629,13 +5616,13 @@ function inviteQR(text) {
5629
5616
  // src/components/call-limbo/CallReady.tsx
5630
5617
  import { Fragment as Fragment19, jsx as jsx48, jsxs as jsxs30 } from "react/jsx-runtime";
5631
5618
  function CallReady({ open, onClose, link = typeof location === "undefined" ? "" : location.href }) {
5632
- const [showQR, setShowQR] = useState26(false);
5619
+ const [showQR, setShowQR] = useState25(false);
5633
5620
  const qrRef = useRef14(null);
5634
5621
  const cardRef = useRef14(null);
5635
5622
  const phone = useIsMobile();
5636
5623
  const reducedMotion = usePrefersReducedMotion();
5637
- const [entered, setEntered] = useState26(false);
5638
- const [present, setPresent] = useState26(open);
5624
+ const [entered, setEntered] = useState25(false);
5625
+ const [present, setPresent] = useState25(open);
5639
5626
  const id = useId();
5640
5627
  const qr = showQR ? inviteQR(link) : null;
5641
5628
  const close = () => {
@@ -5716,11 +5703,11 @@ function CallReady({ open, onClose, link = typeof location === "undefined" ? ""
5716
5703
  }
5717
5704
 
5718
5705
  // src/components/toast-stack/ToastStack.tsx
5719
- import { memo, useLayoutEffect as useLayoutEffect3, useRef as useRef15, useState as useState28 } from "react";
5706
+ import { memo, useLayoutEffect as useLayoutEffect3, useRef as useRef15, useState as useState27 } from "react";
5720
5707
  import { flushSync as flushSync2 } from "react-dom";
5721
5708
 
5722
5709
  // src/components/toast-stack/useToastCapacity.ts
5723
- import { isValidElement as isValidElement2, useLayoutEffect as useLayoutEffect2, useState as useState27 } from "react";
5710
+ import { isValidElement as isValidElement2, useLayoutEffect as useLayoutEffect2, useState as useState26 } from "react";
5724
5711
  import { flushSync } from "react-dom";
5725
5712
 
5726
5713
  // src/components/toast-stack/toast-layout-snapshot.ts
@@ -5765,7 +5752,7 @@ function measureToastLayout(stack, snapshot) {
5765
5752
 
5766
5753
  // src/components/toast-stack/useToastCapacity.ts
5767
5754
  function useToastCapacity(stack, candidates, enabled, ownedIds) {
5768
- const [fit, setFit] = useState27(null);
5755
+ const [fit, setFit] = useState26(null);
5769
5756
  let count = candidates.length;
5770
5757
  if (enabled) {
5771
5758
  count = 0;
@@ -5859,7 +5846,7 @@ var ToastStack = memo(function ToastStack2({ toasts, onDismiss, maxVisible = 3,
5859
5846
  const stack = useRef15(null);
5860
5847
  const short = useShortCall(stack, onFold !== void 0);
5861
5848
  const candidates = maxVisible > 0 ? toasts.slice(-maxVisible) : [];
5862
- const [ownedIds, setOwnedIds] = useState28([]);
5849
+ const [ownedIds, setOwnedIds] = useState27([]);
5863
5850
  const fold = useRef15(onFold);
5864
5851
  const capacity = useToastCapacity(stack, candidates, onFold !== void 0 && short === false, ownedIds);
5865
5852
  const selected = onFold !== void 0 && short !== false ? [] : candidates.slice(candidates.length - capacity.count);
@@ -5868,7 +5855,7 @@ var ToastStack = memo(function ToastStack2({ toasts, onDismiss, maxVisible = 3,
5868
5855
  const foldedIds = folded.map((toast) => toast.id);
5869
5856
  const announced = useRef15(/* @__PURE__ */ new Map());
5870
5857
  const wasAnnounced = (toast) => announced.current.get(toast.id) === noticeIdentity(toast);
5871
- const [unheard, setUnheard] = useState28([]);
5858
+ const [unheard, setUnheard] = useState27([]);
5872
5859
  const reported = useRef15([]);
5873
5860
  const reporting = useRef15(false);
5874
5861
  useLayoutEffect3(() => {
@@ -5915,7 +5902,7 @@ var ToastStack = memo(function ToastStack2({ toasts, onDismiss, maxVisible = 3,
5915
5902
  ] });
5916
5903
  });
5917
5904
  function useShortCall(stack, enabled) {
5918
- const [short, setShort] = useState28(null);
5905
+ const [short, setShort] = useState27(null);
5919
5906
  useLayoutEffect3(() => {
5920
5907
  if (!enabled) {
5921
5908
  setShort(null);
@@ -5936,7 +5923,7 @@ function sameIds(a, b) {
5936
5923
  }
5937
5924
 
5938
5925
  // src/internal/call-motion.ts
5939
- import { useEffect as useEffect20, useState as useState29 } from "react";
5926
+ import { useEffect as useEffect20, useState as useState28 } from "react";
5940
5927
  var moves = /* @__PURE__ */ new WeakMap();
5941
5928
  function captureFlip(elements) {
5942
5929
  const previous = Array.from(elements, (element) => ({ element, box: element.getBoundingClientRect() }));
@@ -5964,7 +5951,7 @@ function playFlip(previous) {
5964
5951
  }
5965
5952
  var ACTIVE_LAYERS = '[role="menu"],[role="dialog"],[data-part="toast"],[data-part="notice-button"],[data-chalk-drawer]';
5966
5953
  function useIdleChrome(root, enabled) {
5967
- const [idle, setIdle] = useState29(false);
5954
+ const [idle, setIdle] = useState28(false);
5968
5955
  const fine = useMediaQuery("(pointer:fine) and (min-width:641px)");
5969
5956
  useEffect20(() => {
5970
5957
  const element = root.current;
@@ -5996,7 +5983,7 @@ function useIdleChrome(root, enabled) {
5996
5983
  }
5997
5984
 
5998
5985
  // src/components/space-header/SpaceHeader.tsx
5999
- import React14, { useCallback as useCallback6, useRef as useRef18, useState as useState31 } from "react";
5986
+ import React14, { useCallback as useCallback6, useRef as useRef18, useState as useState30 } from "react";
6000
5987
 
6001
5988
  // src/internal/useSoundsSetting.ts
6002
5989
  import { useSyncExternalStore as useSyncExternalStore3 } from "react";
@@ -6048,7 +6035,7 @@ function LayoutMenu({ layout, onLayoutChange }) {
6048
6035
 
6049
6036
  // src/components/space-header/CallDetails.tsx
6050
6037
  import { Dialog as Dialog2 } from "@base-ui/react/dialog";
6051
- import { useEffect as useEffect21, useRef as useRef16, useState as useState30 } from "react";
6038
+ import { useEffect as useEffect21, useRef as useRef16, useState as useState29 } from "react";
6052
6039
 
6053
6040
  // src/components/space-header/share-qr.ts
6054
6041
  function shareQr(text) {
@@ -6203,10 +6190,10 @@ function CallDialog({
6203
6190
  const container = useRef16(null);
6204
6191
  const reportInput = useRef16(null);
6205
6192
  const reportOpener = useRef16(null);
6206
- const [text, setText] = useState30("");
6207
- const [includeDiagnostics, setIncludeDiagnostics] = useState30(false);
6208
- const [status, setStatus] = useState30("idle");
6209
- const [error, setError] = useState30("");
6193
+ const [text, setText] = useState29("");
6194
+ const [includeDiagnostics, setIncludeDiagnostics] = useState29(false);
6195
+ const [status, setStatus] = useState29("idle");
6196
+ const [error, setError] = useState29("");
6210
6197
  useEffect21(() => {
6211
6198
  if (open && kind === "report") {
6212
6199
  setText("");
@@ -6341,7 +6328,7 @@ function CallDetails({
6341
6328
  }) {
6342
6329
  const link = shareUrl ?? (typeof location === "undefined" ? "" : location.href);
6343
6330
  const qr = shareQr(link);
6344
- const [details, setDetails] = useState30(false);
6331
+ const [details, setDetails] = useState29(false);
6345
6332
  return /* @__PURE__ */ jsxs32("div", { className: "chalk-details", children: [
6346
6333
  /* @__PURE__ */ jsxs32("div", { className: "chalk-shell-details-heading", children: [
6347
6334
  /* @__PURE__ */ jsx51("h2", { children: spaceName }),
@@ -6493,8 +6480,8 @@ var SpaceHeader = React14.memo((props) => {
6493
6480
  const [sounds, setSounds] = useSoundsSetting();
6494
6481
  const triggerRef = useRef18(null);
6495
6482
  const focusScope = useRef18(null);
6496
- const [open, setOpen] = useState31(false);
6497
- const [dialog, setDialog] = useState31({ kind: "shortcuts", open: false });
6483
+ const [open, setOpen] = useState30(false);
6484
+ const [dialog, setDialog] = useState30({ kind: "shortcuts", open: false });
6498
6485
  const showShortcuts = useCallback6(() => setDialog({ kind: "shortcuts", open: true }), []);
6499
6486
  useCallShortcuts(props.shortcuts, showShortcuts);
6500
6487
  const phone = useMediaQuery("(max-width: 640px)");
@@ -6581,16 +6568,16 @@ var SpaceHeader = React14.memo((props) => {
6581
6568
  SpaceHeader.displayName = "SpaceHeader";
6582
6569
 
6583
6570
  // src/components/composite/ScreenShareView.tsx
6584
- import { useEffect as useEffect23, useMemo as useMemo11, useRef as useRef19, useState as useState32 } from "react";
6571
+ import { useEffect as useEffect23, useMemo as useMemo11, useRef as useRef19, useState as useState31 } from "react";
6585
6572
  import { Fragment as Fragment23, jsx as jsx53, jsxs as jsxs34 } from "react/jsx-runtime";
6586
6573
  function ScreenSharePresentation({ sharedByName, media, mediaVisible, state, isLocal = false, hasAudio = false, capture: capture2, primary = true, pinned, hidden, style, onClick, onDoubleClick, onStopSharing, onChangeShare, switcher, className }) {
6587
6574
  const root = useRef19(null);
6588
- const [fill, setFill] = useState32(false);
6589
- const [controlsOpen, setControlsOpen] = useState32(false);
6590
- const [poppedOut, setPoppedOut] = useState32(false);
6591
- const [zoom, setZoom] = useState32(false);
6592
- const [pending, setPending] = useState32(false);
6593
- const [error, setError] = useState32(null);
6575
+ const [fill, setFill] = useState31(false);
6576
+ const [controlsOpen, setControlsOpen] = useState31(false);
6577
+ const [poppedOut, setPoppedOut] = useState31(false);
6578
+ const [zoom, setZoom] = useState31(false);
6579
+ const [pending, setPending] = useState31(false);
6580
+ const [error, setError] = useState31(null);
6594
6581
  const status = state ?? (mediaVisible ? "live" : "connecting");
6595
6582
  const name = isLocal ? "Your screen" : `${sharedByName}'s screen`;
6596
6583
  const source = capture2?.surface === "window" ? "a window" : capture2?.surface === "browser" ? "a tab" : capture2?.surface === "monitor" ? "your screen" : null;
@@ -6776,16 +6763,16 @@ function ScreenShareView(props) {
6776
6763
  }
6777
6764
 
6778
6765
  // src/components/stage/Stage.tsx
6779
- import { useCallback as useCallback7, useEffect as useEffect24, useLayoutEffect as useLayoutEffect5, useMemo as useMemo12, useRef as useRef22, useState as useState34 } from "react";
6766
+ import { useCallback as useCallback7, useEffect as useEffect24, useLayoutEffect as useLayoutEffect5, useMemo as useMemo12, useRef as useRef22, useState as useState33 } from "react";
6780
6767
  import { createPortal as createPortal2 } from "react-dom";
6781
6768
 
6782
6769
  // src/components/stage/useStageDimensions.ts
6783
- import { useLayoutEffect as useLayoutEffect4, useRef as useRef20, useState as useState33 } from "react";
6770
+ import { useLayoutEffect as useLayoutEffect4, useRef as useRef20, useState as useState32 } from "react";
6784
6771
  import { flushSync as flushSync3 } from "react-dom";
6785
6772
  function useStageDimensions(fallback3) {
6786
6773
  const ref = useRef20(null);
6787
6774
  const current = useRef20(fallback3);
6788
- const [dimensions, setDimensions] = useState33(fallback3);
6775
+ const [dimensions, setDimensions] = useState32(fallback3);
6789
6776
  useLayoutEffect4(() => {
6790
6777
  const element = ref.current;
6791
6778
  if (!element || typeof ResizeObserver === "undefined") return;
@@ -6981,7 +6968,7 @@ function createContentPlacement() {
6981
6968
  };
6982
6969
  }
6983
6970
  function PersistentStageContent({ placement, children }) {
6984
- const [host, setHost] = useState34(null);
6971
+ const [host, setHost] = useState33(null);
6985
6972
  useLayoutEffect5(() => {
6986
6973
  const element = document.createElement("div");
6987
6974
  Object.assign(element.style, TILE_FILL);
@@ -7006,7 +6993,6 @@ function Stage({
7006
6993
  pinnedId,
7007
6994
  followingId = null,
7008
6995
  onFollowChange,
7009
- clickAction = "pin",
7010
6996
  onPinnedChange,
7011
6997
  onOverflowClick,
7012
6998
  onItemClick,
@@ -7025,9 +7011,9 @@ function Stage({
7025
7011
  }) {
7026
7012
  const prefersReducedMotion = usePrefersReducedMotion();
7027
7013
  const { ref: boxRef, dimensions: box } = useStageDimensions(FALLBACK_BOX);
7028
- const [overflowOpen, setOverflowOpen] = useState34(false);
7029
- const [shareAspects, setShareAspects] = useState34(/* @__PURE__ */ new Map());
7030
- const [uncontrolledPinnedId, setUncontrolledPinnedId] = useState34(null);
7014
+ const [overflowOpen, setOverflowOpen] = useState33(false);
7015
+ const [shareAspects, setShareAspects] = useState33(/* @__PURE__ */ new Map());
7016
+ const [uncontrolledPinnedId, setUncontrolledPinnedId] = useState33(null);
7031
7017
  const activePinnedId = pinnedId === void 0 ? uncontrolledPinnedId : pinnedId;
7032
7018
  const orderRef = useRef22([]);
7033
7019
  const seenAtRef = useRef22(/* @__PURE__ */ new Map());
@@ -7108,7 +7094,7 @@ function Stage({
7108
7094
  return /* @__PURE__ */ jsx56(StageContentTile, { item, ...context });
7109
7095
  };
7110
7096
  const animate = animateOverride ?? !prefersReducedMotion;
7111
- const [displayed, setDisplayed] = useState34({
7097
+ const [displayed, setDisplayed] = useState33({
7112
7098
  source: ordered,
7113
7099
  geometry,
7114
7100
  tiles: ordered.map((item) => ({ item, frame: framesById.get(item.id), leaving: false }))
@@ -7174,15 +7160,14 @@ function Stage({
7174
7160
  for (const item of ordered) {
7175
7161
  map.set(item.id, {
7176
7162
  onClick: () => {
7177
- if (clickAction === "follow" && item.kind === "participant" && !item.participant.isLocal) onFollowChange?.(item.id);
7178
- else togglePin(item);
7163
+ togglePin(item);
7179
7164
  onItemClick?.(item);
7180
7165
  },
7181
7166
  onDoubleClick: onItemDoubleClick ? () => onItemDoubleClick(item) : void 0
7182
7167
  });
7183
7168
  }
7184
7169
  return map;
7185
- }, [clickAction, interactive, onFollowChange, onItemDoubleClick, onItemClick, ordered, togglePin]);
7170
+ }, [interactive, onItemDoubleClick, onItemClick, ordered, togglePin]);
7186
7171
  if (ordered.length === 0) {
7187
7172
  return /* @__PURE__ */ jsx56("div", { className: cn("flex h-full w-full items-center justify-center px-6 py-10", className), "data-tour": "video-grid", children: emptyState });
7188
7173
  }
@@ -7207,7 +7192,7 @@ function Stage({
7207
7192
  aspectRatio: "fill",
7208
7193
  primary: frame?.role === "primary",
7209
7194
  pinned,
7210
- clickAction: item.participant.isLocal ? "pin" : clickAction,
7195
+ onFollow: onFollowChange && !item.participant.isLocal ? () => onFollowChange(item.id) : void 0,
7211
7196
  following: followingId === item.id,
7212
7197
  onClick: click,
7213
7198
  onDoubleClick: doubleClick,
@@ -7268,7 +7253,6 @@ function Stage({
7268
7253
  setOverflowOpen(false);
7269
7254
  },
7270
7255
  children: [
7271
- clickAction === "follow" && !item.participant.isLocal ? `${followingId === item.id ? "Stop following" : "Follow"} ` : "",
7272
7256
  item.participant.displayName,
7273
7257
  item.participant.isMuted ? " \xB7 Muted" : ""
7274
7258
  ]
@@ -7477,7 +7461,7 @@ function SpacePresentation({ colorScheme = "dark", stageBackground, header, stag
7477
7461
  // src/components/space-view/SpaceView.tsx
7478
7462
  import "@q9labsai/chalk-client";
7479
7463
  import { DEFAULT_QUICK_REACTIONS as DEFAULT_QUICK_REACTIONS2 } from "@q9labsai/chalk-ui/reactions";
7480
- import { useCallback as useCallback14, useEffect as useEffect36, useMemo as useMemo18, useRef as useRef29, useState as useState45 } from "react";
7464
+ import { useCallback as useCallback14, useEffect as useEffect36, useMemo as useMemo18, useRef as useRef29, useState as useState44 } from "react";
7481
7465
 
7482
7466
  // src/components/space-header/call-diagnostics.ts
7483
7467
  function callDiagnostics(connection, media) {
@@ -7508,11 +7492,11 @@ function callCountdown(deadline, now, previousSeconds) {
7508
7492
  }
7509
7493
 
7510
7494
  // src/internal/useEpisodeDuration.ts
7511
- import { useEffect as useEffect26, useState as useState35 } from "react";
7495
+ import { useEffect as useEffect26, useState as useState34 } from "react";
7512
7496
  function useEpisodeDuration() {
7513
7497
  const connection = useConnection();
7514
7498
  const episodeStart = connection.episode?.startedAt ? Date.parse(connection.episode.startedAt) : null;
7515
- const [now, setNow] = useState35(() => Date.now());
7499
+ const [now, setNow] = useState34(() => Date.now());
7516
7500
  useEffect26(() => {
7517
7501
  if (episodeStart === null || Number.isNaN(episodeStart)) return;
7518
7502
  setNow(Date.now());
@@ -7568,16 +7552,17 @@ function createSoundPlayer({ outputDeviceId } = {}) {
7568
7552
  const created = !context;
7569
7553
  try {
7570
7554
  context ??= new AudioContext();
7555
+ if (created && outputDeviceId && "setSinkId" in context && typeof context.setSinkId === "function") void Promise.resolve(context.setSinkId(outputDeviceId)).catch(() => void 0);
7556
+ if (context.state === "suspended") void context.resume().catch(() => void 0);
7571
7557
  } catch {
7572
7558
  return;
7573
7559
  }
7574
- if (created && outputDeviceId && "setSinkId" in context && typeof context.setSinkId === "function") void Promise.resolve(context.setSinkId(outputDeviceId)).catch(() => void 0);
7575
- if (context.state === "suspended") void context.resume().catch(() => void 0);
7576
7560
  };
7577
7561
  if (typeof window !== "undefined") {
7578
7562
  window.addEventListener("click", unlock, { capture: true });
7579
7563
  window.addEventListener("keydown", unlock, { capture: true });
7580
7564
  }
7565
+ unlock();
7581
7566
  const tone = (frequency, offset, duration, volume = 0.07, type = "sine", to) => {
7582
7567
  if (!context || context.state !== "running") return;
7583
7568
  const oscillator = context.createOscillator(), gain = context.createGain(), time = context.currentTime + offset;
@@ -7616,44 +7601,47 @@ function createSoundPlayer({ outputDeviceId } = {}) {
7616
7601
  return {
7617
7602
  play(cue) {
7618
7603
  if (disposed || !context || context.state !== "running") return;
7619
- switch (cue) {
7620
- case "join":
7621
- tone(659, 0, 0.2);
7622
- tone(988, 0.1, 0.28, 0.06);
7623
- break;
7624
- case "leave":
7625
- tone(784, 0, 0.2);
7626
- tone(523, 0.1, 0.3, 0.06);
7627
- break;
7628
- case "knock":
7629
- thud(0);
7630
- thud(0.17);
7631
- break;
7632
- case "message":
7633
- tone(1175, 0, 0.16, 0.05, "triangle");
7634
- break;
7635
- case "hand-raise":
7636
- tone(1319, 0, 0.32, 0.045);
7637
- tone(1760, 0.07, 0.4, 0.035);
7638
- break;
7639
- case "mute":
7640
- tone(560, 0, 0.07, 0.06, "triangle", 380);
7641
- break;
7642
- case "unmute":
7643
- tone(420, 0, 0.07, 0.06, "triangle", 640);
7644
- break;
7645
- case "recording":
7646
- tone(587, 0, 0.14);
7647
- tone(587, 0.18, 0.14);
7648
- tone(880, 0.36, 0.32, 0.06);
7649
- break;
7650
- case "error":
7651
- tone(392, 0, 0.16, 0.05, "triangle");
7652
- tone(311, 0.13, 0.24, 0.05, "triangle");
7653
- break;
7654
- case "reaction":
7655
- tone(880, 0, 0.12, 0.035, "triangle");
7656
- break;
7604
+ try {
7605
+ switch (cue) {
7606
+ case "join":
7607
+ tone(659, 0, 0.16, 0.04);
7608
+ tone(988, 0.08, 0.22, 0.03);
7609
+ break;
7610
+ case "leave":
7611
+ tone(784, 0, 0.2);
7612
+ tone(523, 0.1, 0.3, 0.06);
7613
+ break;
7614
+ case "knock":
7615
+ thud(0);
7616
+ thud(0.17);
7617
+ break;
7618
+ case "message":
7619
+ tone(1175, 0, 0.16, 0.05, "triangle");
7620
+ break;
7621
+ case "hand-raise":
7622
+ tone(1319, 0, 0.32, 0.045);
7623
+ tone(1760, 0.07, 0.4, 0.035);
7624
+ break;
7625
+ case "mute":
7626
+ tone(560, 0, 0.07, 0.06, "triangle", 380);
7627
+ break;
7628
+ case "unmute":
7629
+ tone(420, 0, 0.07, 0.06, "triangle", 640);
7630
+ break;
7631
+ case "recording":
7632
+ tone(587, 0, 0.14);
7633
+ tone(587, 0.18, 0.14);
7634
+ tone(880, 0.36, 0.32, 0.06);
7635
+ break;
7636
+ case "error":
7637
+ tone(392, 0, 0.16, 0.05, "triangle");
7638
+ tone(311, 0.13, 0.24, 0.05, "triangle");
7639
+ break;
7640
+ case "reaction":
7641
+ tone(880, 0, 0.12, 0.035, "triangle");
7642
+ break;
7643
+ }
7644
+ } catch {
7657
7645
  }
7658
7646
  },
7659
7647
  dispose() {
@@ -7662,7 +7650,7 @@ function createSoundPlayer({ outputDeviceId } = {}) {
7662
7650
  window.removeEventListener("click", unlock, true);
7663
7651
  window.removeEventListener("keydown", unlock, true);
7664
7652
  }
7665
- if (context) void context.close();
7653
+ if (context) void Promise.resolve(context.close()).catch(() => void 0);
7666
7654
  }
7667
7655
  };
7668
7656
  }
@@ -7693,9 +7681,21 @@ function useSoundCues(enabled, createPlayer = createSoundPlayer) {
7693
7681
  const player = createPlayerRef.current({ outputDeviceId });
7694
7682
  playerRef.current = player;
7695
7683
  let previous = client.getSnapshot();
7684
+ const seenParticipants = new Set(previous.participants.roster.map((participant) => participant.participantId));
7685
+ let lastJoinAt = -Infinity;
7696
7686
  const unsubscribe = client.subscribe(() => {
7697
7687
  const next = client.getSnapshot();
7698
- if (enabledRef.current) for (const cue of diffSoundCues(previous, next)) player.play(cue);
7688
+ const hasNewParticipant = next.participants.roster.some((participant) => participant.participantId !== next.self.participantId && !seenParticipants.has(participant.participantId));
7689
+ if (enabledRef.current)
7690
+ for (const cue of diffSoundCues(previous, next)) {
7691
+ if (cue === "join") {
7692
+ const now = performance.now();
7693
+ if (!hasNewParticipant || now - lastJoinAt < 3e3) continue;
7694
+ lastJoinAt = now;
7695
+ }
7696
+ player.play(cue);
7697
+ }
7698
+ for (const participant of next.participants.roster) seenParticipants.add(participant.participantId);
7699
7699
  previous = next;
7700
7700
  });
7701
7701
  const offError = client.on("error", ({ error }) => playFailure(error));
@@ -7719,14 +7719,14 @@ function useSoundCues(enabled, createPlayer = createSoundPlayer) {
7719
7719
  }
7720
7720
 
7721
7721
  // src/components/composite/ReactionPicker.tsx
7722
- import React21, { useMemo as useMemo14, useState as useState37, useCallback as useCallback10, useEffect as useEffect29, useRef as useRef27 } from "react";
7722
+ import React21, { useMemo as useMemo14, useState as useState36, useCallback as useCallback10, useEffect as useEffect29, useRef as useRef27 } from "react";
7723
7723
  import { DEFAULT_QUICK_REACTIONS, EMOJI_CATEGORIES, EMOJI_KEYWORDS } from "@q9labsai/chalk-ui/reactions";
7724
7724
 
7725
7725
  // src/components/composite/ReactionTray.tsx
7726
- import { useCallback as useCallback9, useEffect as useEffect28, useLayoutEffect as useLayoutEffect6, useRef as useRef26, useState as useState36 } from "react";
7726
+ import { useCallback as useCallback9, useEffect as useEffect28, useLayoutEffect as useLayoutEffect6, useRef as useRef26, useState as useState35 } from "react";
7727
7727
  import { Fragment as Fragment27, jsx as jsx61, jsxs as jsxs39 } from "react/jsx-runtime";
7728
7728
  function ReactionTray({ isHandRaised, onToggleHandRaise, reactions, onSelect, onClose, position = "top", className }) {
7729
- const [more, setMore] = useState36(false);
7729
+ const [more, setMore] = useState35(false);
7730
7730
  const returnFocus = useRef26(null);
7731
7731
  const prefersReducedMotion = usePrefersReducedMotion();
7732
7732
  const { trigger } = useHaptics();
@@ -7811,8 +7811,8 @@ import { Fragment as Fragment28, jsx as jsx62, jsxs as jsxs40 } from "react/jsx-
7811
7811
  var ReactionPicker = React21.memo(({ isHandRaised, onToggleHandRaise, isOpen, onClose, onSelect, recentReactions = [], allowedReactions, position = "top", participantColorSeed, participantGradientPreference, className, size = "default" }) => {
7812
7812
  const prefersReducedMotion = usePrefersReducedMotion();
7813
7813
  const { trigger } = useHaptics();
7814
- const [activeCategory, setActiveCategory] = useState37("smileys");
7815
- const [searchQuery, setSearchQuery] = useState37("");
7814
+ const [activeCategory, setActiveCategory] = useState36("smileys");
7815
+ const [searchQuery, setSearchQuery] = useState36("");
7816
7816
  const inputRef = useRef27(null);
7817
7817
  const themeVariables = useMemo14(() => getParticipantThemeVariables(participantColorSeed, participantGradientPreference), [participantColorSeed, participantGradientPreference]);
7818
7818
  useEffect29(() => {
@@ -7969,7 +7969,7 @@ var ReactionPicker = React21.memo(({ isHandRaised, onToggleHandRaise, isOpen, on
7969
7969
  ReactionPicker.displayName = "ReactionPicker";
7970
7970
 
7971
7971
  // src/components/offline-actions/OfflineActions.tsx
7972
- import { useEffect as useEffect30, useState as useState38, useSyncExternalStore as useSyncExternalStore4 } from "react";
7972
+ import { useEffect as useEffect30, useState as useState37, useSyncExternalStore as useSyncExternalStore4 } from "react";
7973
7973
  import { jsx as jsx63 } from "react/jsx-runtime";
7974
7974
  var RECONNECTING_GRACE_MS = 2e3;
7975
7975
  function offlineNoticeText(pendingCount, online) {
@@ -7997,7 +7997,7 @@ function OfflineActions({ reconnecting: requestedReconnecting = false, hasNotice
7997
7997
  return null;
7998
7998
  }
7999
7999
  function useTrueFor(value, milliseconds) {
8000
- const [elapsed, setElapsed] = useState38(false);
8000
+ const [elapsed, setElapsed] = useState37(false);
8001
8001
  useEffect30(() => {
8002
8002
  setElapsed(false);
8003
8003
  if (!value) return;
@@ -8019,12 +8019,12 @@ function readOnline() {
8019
8019
  }
8020
8020
 
8021
8021
  // src/components/reconnecting-overlay/ReconnectingOverlay.tsx
8022
- import { memo as memo3, useEffect as useEffect31, useState as useState39 } from "react";
8022
+ import { memo as memo3, useEffect as useEffect31, useState as useState38 } from "react";
8023
8023
  import { jsx as jsx64 } from "react/jsx-runtime";
8024
8024
  var RECONNECTING_NOTICE_MS = 2e3;
8025
8025
  var ReconnectingOverlay = memo3(function ReconnectingOverlay2({ isVisible, status, onRetry, onLeft, message, showNotice }) {
8026
8026
  const reconnecting = isVisible && status === "reconnecting";
8027
- const [elapsed, setElapsed] = useState39(false);
8027
+ const [elapsed, setElapsed] = useState38(false);
8028
8028
  useEffect31(() => {
8029
8029
  setElapsed(false);
8030
8030
  if (!reconnecting) return;
@@ -8039,7 +8039,7 @@ var ReconnectingOverlay = memo3(function ReconnectingOverlay2({ isVisible, statu
8039
8039
  });
8040
8040
 
8041
8041
  // src/components/toast-stack/useCallToasts.ts
8042
- import { useCallback as useCallback11, useEffect as useEffect32, useRef as useRef28, useState as useState40 } from "react";
8042
+ import { useCallback as useCallback11, useEffect as useEffect32, useRef as useRef28, useState as useState39 } from "react";
8043
8043
 
8044
8044
  // src/components/toast-stack/call-events.tsx
8045
8045
  import { Fragment as Fragment29, jsx as jsx65, jsxs as jsxs41 } from "react/jsx-runtime";
@@ -8136,7 +8136,7 @@ function callEventToasts(previous, next) {
8136
8136
  // src/components/toast-stack/useCallToasts.ts
8137
8137
  function useCallToasts() {
8138
8138
  const client = useSpaceClient();
8139
- const [toasts, setToasts] = useState40([]);
8139
+ const [toasts, setToasts] = useState39([]);
8140
8140
  const sequence = useRef28(0);
8141
8141
  useEffect32(() => {
8142
8142
  setToasts([]);
@@ -8177,9 +8177,9 @@ function useCallToasts() {
8177
8177
  }
8178
8178
 
8179
8179
  // src/components/call-limbo/useCallLink.ts
8180
- import { useEffect as useEffect33, useState as useState41 } from "react";
8180
+ import { useEffect as useEffect33, useState as useState40 } from "react";
8181
8181
  function useCallLink(alone) {
8182
- const [open, setOpen] = useState41(alone);
8182
+ const [open, setOpen] = useState40(alone);
8183
8183
  useEffect33(() => {
8184
8184
  if (!alone) setOpen(false);
8185
8185
  }, [alone]);
@@ -8187,10 +8187,10 @@ function useCallLink(alone) {
8187
8187
  }
8188
8188
 
8189
8189
  // src/components/toast-stack/NoticeButton.tsx
8190
- import { useState as useState42 } from "react";
8190
+ import { useState as useState41 } from "react";
8191
8191
  import { jsx as jsx66, jsxs as jsxs42 } from "react/jsx-runtime";
8192
8192
  function NoticeButton({ toasts, onDismiss }) {
8193
- const [open, setOpen] = useState42(false);
8193
+ const [open, setOpen] = useState41(false);
8194
8194
  if (toasts.length === 0) return null;
8195
8195
  const label = toasts.length === 1 ? "1 notice" : `${toasts.length} notices`;
8196
8196
  const trigger = /* @__PURE__ */ jsxs42("button", { type: "button", "data-part": "notice-button", className: "chalk-notice-button", "aria-label": `Show ${label}`, children: [
@@ -8212,7 +8212,7 @@ function NoticeButton({ toasts, onDismiss }) {
8212
8212
  import { BoardToastContext as BoardToastContext2 } from "@q9labsai/chalk-whiteboard/react";
8213
8213
 
8214
8214
  // src/components/stage/SpaceStage.tsx
8215
- import { useCallback as useCallback13, useEffect as useEffect34, useMemo as useMemo17, useState as useState43 } from "react";
8215
+ import { useCallback as useCallback13, useEffect as useEffect34, useMemo as useMemo17, useState as useState42 } from "react";
8216
8216
 
8217
8217
  // src/components/board/Board.tsx
8218
8218
  import { useContext as useContext3, useMemo as useMemo16 } from "react";
@@ -8282,31 +8282,43 @@ function Board(props) {
8282
8282
 
8283
8283
  // src/components/stage/SpaceStage.tsx
8284
8284
  import { jsx as jsx68 } from "react/jsx-runtime";
8285
- function SpaceStage({ tiles, layout, onLayoutChange, generatedAvatars = true, whiteboard, onOverflowClick, shareCapture, onStopSharing, onChangeShare, colorScheme = "dark", onCloseBoard, className }) {
8285
+ function SpaceStage({ pinnedId, onPinnedChange, tiles, layout, onLayoutChange, generatedAvatars = true, whiteboard, onOverflowClick, shareCapture, onStopSharing, onChangeShare, colorScheme = "dark", onCloseBoard, className }) {
8286
8286
  const items = useMemo17(() => buildStageItems(tiles, whiteboard?.isOpen === true), [tiles, whiteboard?.isOpen]);
8287
- const [selectedShare, setSelectedShare] = useState43(null);
8288
- const [pin, setPin] = useState43(null);
8289
- const [following, setFollowing] = useState43(null);
8287
+ const [selectedShare, setSelectedShare] = useState42(null);
8288
+ const [localPin, setLocalPin] = useState42(null);
8289
+ const pin = pinnedId === void 0 ? localPin : pinnedId;
8290
+ const setPin = useCallback13(
8291
+ (id) => {
8292
+ setLocalPin(id);
8293
+ onPinnedChange?.(id);
8294
+ },
8295
+ [onPinnedChange]
8296
+ );
8297
+ const [following, setFollowing] = useState42(void 0);
8290
8298
  const boardOpen = whiteboard?.isOpen === true && Boolean(whiteboard.props);
8291
8299
  useEffect34(() => {
8292
- if (!boardOpen) setFollowing(null);
8300
+ if (!boardOpen) setFollowing(void 0);
8293
8301
  }, [boardOpen]);
8294
8302
  const canFollow = boardOpen && Boolean(whiteboard?.sendView);
8295
- const followed = canFollow && tiles.some((tile) => tile.id === following) ? following : null;
8303
+ const target = following === void 0 ? whiteboard?.presenterId : following;
8304
+ const followed = canFollow && tiles.some((tile) => tile.id === target && !tile.isLocal) ? target ?? null : null;
8296
8305
  const activePin = items.some((item) => item.id === pin) ? pin : null;
8297
8306
  const shares = items.filter((item) => item.kind === "screen-share");
8298
8307
  const activeShare = selectedShare && shares.some((item) => item.id === selectedShare) ? selectedShare : null;
8299
8308
  useEffect34(() => {
8300
8309
  if (pin !== activePin) setPin(activePin);
8301
8310
  if (selectedShare !== activeShare) setSelectedShare(activeShare);
8302
- }, [pin, activePin, selectedShare, activeShare]);
8303
- const follow = useCallback13((id) => {
8304
- if (id) {
8305
- setPin(null);
8306
- setSelectedShare(null);
8307
- }
8308
- setFollowing(id);
8309
- }, []);
8311
+ }, [pin, activePin, selectedShare, activeShare, setPin]);
8312
+ const follow = useCallback13(
8313
+ (id) => {
8314
+ if (id) {
8315
+ setPin(null);
8316
+ setSelectedShare(null);
8317
+ }
8318
+ setFollowing(id);
8319
+ },
8320
+ [setPin]
8321
+ );
8310
8322
  const renderContentTile = useCallback13(
8311
8323
  (item, context) => {
8312
8324
  if (item.kind === "screen-share")
@@ -8357,15 +8369,14 @@ function SpaceStage({ tiles, layout, onLayoutChange, generatedAvatars = true, wh
8357
8369
  Stage,
8358
8370
  {
8359
8371
  items,
8360
- layout,
8372
+ layout: activePin ? "focus" : layout,
8361
8373
  pinnedId: activePin ?? activeShare ?? (followed ? "whiteboard" : null),
8362
8374
  onPinnedChange: setPin,
8363
- onFollowChange: (id) => follow(followed === id ? null : id),
8375
+ onFollowChange: canFollow ? (id) => follow(followed === id ? null : id) : void 0,
8364
8376
  followingId: followed,
8365
- clickAction: canFollow ? "follow" : "pin",
8366
8377
  renderContentTile,
8367
8378
  onItemClick: focusContent,
8368
- onOverflowClick: canFollow ? void 0 : onOverflowClick,
8379
+ onOverflowClick,
8369
8380
  generatedAvatars,
8370
8381
  emptyState: /* @__PURE__ */ jsx68(QuietSpace, {}),
8371
8382
  className
@@ -8375,11 +8386,11 @@ function SpaceStage({ tiles, layout, onLayoutChange, generatedAvatars = true, wh
8375
8386
  SpaceStage.displayName = "SpaceStage";
8376
8387
 
8377
8388
  // src/components/space-view/useDrawerPresence.ts
8378
- import { useEffect as useEffect35, useState as useState44 } from "react";
8389
+ import { useEffect as useEffect35, useState as useState43 } from "react";
8379
8390
  var DRAWER_EXIT_MS = 200;
8380
8391
  function useDrawerPresence(panel, exitMs) {
8381
- const [retained, setRetained] = useState44(panel);
8382
- const [state, setState] = useState44(panel === null ? "closed" : "open");
8392
+ const [retained, setRetained] = useState43(panel);
8393
+ const [state, setState] = useState43(panel === null ? "closed" : "open");
8383
8394
  useEffect35(() => {
8384
8395
  if (panel !== null) {
8385
8396
  setRetained(panel);
@@ -8429,6 +8440,7 @@ function SpaceView({
8429
8440
  onEndEpisode,
8430
8441
  className
8431
8442
  }) {
8443
+ const phone = useIsMobile();
8432
8444
  const effectiveShareUrl = inviteLink ?? shareUrl;
8433
8445
  const callToasts = useCallToasts();
8434
8446
  const [soundsEnabled] = useSoundsSetting();
@@ -8439,7 +8451,7 @@ function SpaceView({
8439
8451
  const media = useMedia();
8440
8452
  const connection = useConnection();
8441
8453
  const offline = useOffline();
8442
- const [dismissedOfflineNotice, setDismissedOfflineNotice] = useState45(null);
8454
+ const [dismissedOfflineNotice, setDismissedOfflineNotice] = useState44(null);
8443
8455
  const offlineNotice = offline.notice && offline.notice !== dismissedOfflineNotice ? offline.notice : null;
8444
8456
  const recording = useRecording();
8445
8457
  const canPublishScreen = useCan("publishScreen");
@@ -8450,9 +8462,9 @@ function SpaceView({
8450
8462
  const canManageRecording = useCan("manageRecording");
8451
8463
  const episodeDuration = useEpisodeDuration();
8452
8464
  const deadline = connection.episode?.deadline;
8453
- const [now, setNow] = useState45(Date.now);
8465
+ const [now, setNow] = useState44(Date.now);
8454
8466
  const previousCountdown = useRef29(null);
8455
- const [limitWarning, setLimitWarning] = useState45(null);
8467
+ const [limitWarning, setLimitWarning] = useState44(null);
8456
8468
  const countdown = callCountdown(deadline, now);
8457
8469
  useEffect36(() => {
8458
8470
  if (!deadline) return;
@@ -8468,13 +8480,14 @@ function SpaceView({
8468
8480
  if (previous?.deadline !== deadline) setLimitWarning(null);
8469
8481
  if (next.warning !== null) setLimitWarning({ id: `time-limit-${deadline}-${next.warning}`, minutes: next.warning });
8470
8482
  }, [deadline, now]);
8471
- const [uncontrolledLayout, setUncontrolledLayout] = useState45(controlledLayout ?? "focus");
8472
- const [activePanel, setActivePanel] = useState45(initialPanel);
8473
- const [isReactionPickerOpen, setReactionPickerOpen] = useState45(false);
8474
- const [commandError, setCommandError] = useState45(null);
8475
- const [commandSource, setCommandSource] = useState45();
8476
- const [commandAction, setCommandAction] = useState45();
8477
- const [retryCommand, setRetryCommand] = useState45();
8483
+ const [uncontrolledLayout, setUncontrolledLayout] = useState44(controlledLayout ?? "focus");
8484
+ const [pinnedId, setPinnedId] = useState44(null);
8485
+ const [activePanel, setActivePanel] = useState44(initialPanel);
8486
+ const [isReactionPickerOpen, setReactionPickerOpen] = useState44(false);
8487
+ const [commandError, setCommandError] = useState44(null);
8488
+ const [commandSource, setCommandSource] = useState44();
8489
+ const [commandAction, setCommandAction] = useState44();
8490
+ const [retryCommand, setRetryCommand] = useState44();
8478
8491
  const onCommandError = (failure, source, mediaSource, action, command) => {
8479
8492
  if (!failure && commandError && typeof commandError === "object") callToasts.dismissFailure(commandError.code);
8480
8493
  setCommandError(failure);
@@ -8509,7 +8522,9 @@ function SpaceView({
8509
8522
  ...feature("whiteboard") && canDrawWhiteboard ? ["whiteboard"] : [],
8510
8523
  "leave"
8511
8524
  ];
8525
+ const headerLayout = pinnedId ? "focus" : layout;
8512
8526
  const updateLayout = (nextLayout) => {
8527
+ if (nextLayout === "grid") setPinnedId(null);
8513
8528
  setUncontrolledLayout(nextLayout);
8514
8529
  onLayoutChange?.(nextLayout);
8515
8530
  };
@@ -8545,7 +8560,7 @@ function SpaceView({
8545
8560
  };
8546
8561
  const mappedFailure = mediaFailureToast(typeof displayedCommandError === "object" ? displayedCommandError : null, displayedCommandSource, externalCommandError != null ? externalCommandAction : commandAction, externalCommandError == null && retryCommand ? () => void retryMedia() : void 0);
8547
8562
  const commandToasts = mappedFailure ? [{ id: "space-command-error", ...mappedFailure }] : typeof displayedCommandError === "string" ? [{ id: "space-command-error", message: displayedCommandError, type: "error" }] : [];
8548
- const [boardToasts, setBoardToasts] = useState45([]);
8563
+ const [boardToasts, setBoardToasts] = useState44([]);
8549
8564
  const boardToastCount = useRef29(0);
8550
8565
  useEffect36(() => {
8551
8566
  if (!whiteboard?.isOpen) setBoardToasts([]);
@@ -8579,7 +8594,7 @@ function SpaceView({
8579
8594
  else if (id.startsWith("board-")) setBoardToasts((current) => current.filter((toast) => toast.id !== id));
8580
8595
  else callToasts.dismiss(id);
8581
8596
  };
8582
- const [foldedNotices, setFoldedNotices] = useState45([]);
8597
+ const [foldedNotices, setFoldedNotices] = useState44([]);
8583
8598
  const folded = toasts.filter((toast) => foldedNotices.includes(toast.id));
8584
8599
  return /* @__PURE__ */ jsx69(ParticipantVolumeProvider, { children: /* @__PURE__ */ jsx69(BoardToastContext2.Provider, { value: showBoardToast, children: /* @__PURE__ */ jsx69(
8585
8600
  SpacePresentation,
@@ -8607,7 +8622,7 @@ function SpaceView({
8607
8622
  onToggleHand: feature("handRaise") && canRaiseHand ? () => void runCommand(() => self.handRaised ? client.participants.lowerHand() : client.participants.raiseHand()) : void 0,
8608
8623
  onToggleChat: feature("chat") ? () => setActivePanel((current) => current === "chat" ? null : "chat") : void 0,
8609
8624
  onTogglePeople: feature("participants") ? () => setActivePanel((current) => current === "participants" ? null : "participants") : void 0,
8610
- onToggleLayout: () => updateLayout(layout === "grid" ? "focus" : "grid"),
8625
+ onToggleLayout: () => updateLayout(headerLayout === "grid" ? "focus" : "grid"),
8611
8626
  onClosePanel: () => setActivePanel(null),
8612
8627
  onPushToTalk: (enabled) => {
8613
8628
  if (enabled && connection.status !== "live") return;
@@ -8617,13 +8632,15 @@ function SpaceView({
8617
8632
  duration: episodeDuration,
8618
8633
  timeLeft: countdown.seconds,
8619
8634
  isRecording: recording.current?.status === "recording",
8620
- layout,
8635
+ layout: headerLayout,
8621
8636
  onLayoutChange: updateLayout,
8622
8637
  notices: folded.length ? /* @__PURE__ */ jsx69(NoticeButton, { toasts: folded, onDismiss: dismissToast }) : void 0
8623
8638
  },
8624
8639
  stage: /* @__PURE__ */ jsx69(
8625
8640
  SpaceStage,
8626
8641
  {
8642
+ pinnedId,
8643
+ onPinnedChange: setPinnedId,
8627
8644
  tiles,
8628
8645
  layout,
8629
8646
  onLayoutChange: updateLayout,
@@ -8679,7 +8696,20 @@ function SpaceView({
8679
8696
  ] }),
8680
8697
  sidebar: /* @__PURE__ */ jsxs43(CallCard, { state: drawer.state, panel: drawer.panel, onPanelChange: setActivePanel, peopleCount: participantsSlice.roster.length, participantsEnabled: feature("participants"), chatEnabled: feature("chat"), onClose: () => setActivePanel(null), children: [
8681
8698
  drawer.panel === "chat" && feature("chat") ? /* @__PURE__ */ jsx69(ChatPanel, { variant: "mobile", generatedAvatars, onClose: () => setActivePanel(null), pickChatFiles }) : null,
8682
- drawer.panel === "participants" && feature("participants") ? /* @__PURE__ */ jsx69(ParticipantsPanel, { variant: "mobile", admissionEnabled: feature("admission"), generatedAvatars, onCommandError, onClose: () => setActivePanel(null) }) : null,
8699
+ drawer.panel === "participants" && feature("participants") ? /* @__PURE__ */ jsx69(
8700
+ ParticipantsPanel,
8701
+ {
8702
+ onParticipantClick: (id) => {
8703
+ setPinnedId((current) => current === id ? null : id);
8704
+ if (phone) setActivePanel(null);
8705
+ },
8706
+ variant: "mobile",
8707
+ admissionEnabled: feature("admission"),
8708
+ generatedAvatars,
8709
+ onCommandError,
8710
+ onClose: () => setActivePanel(null)
8711
+ }
8712
+ ) : null,
8683
8713
  drawer.panel === "admission" && feature("admission") ? /* @__PURE__ */ jsx69(AdmissionPanel, { className: "h-full w-full rounded-none shadow-none", onClose: () => setActivePanel(null) }) : null
8684
8714
  ] }),
8685
8715
  toasts: /* @__PURE__ */ jsx69(ToastStack, { toasts, onDismiss: dismissToast, onFold: setFoldedNotices }),