@ai-matrx/meet 0.11.27 → 0.11.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/{collab-CIyXdQ5l.d.cts → collab-CUxjUtrb.d.cts} +13 -0
- package/dist/{collab-CIyXdQ5l.d.ts → collab-CUxjUtrb.d.ts} +13 -0
- package/dist/{components-DiGxfKXI.d.ts → components-CUPzD0y3.d.ts} +1 -1
- package/dist/{components-DUvy3BQf.d.cts → components-DPdU3POT.d.cts} +1 -1
- package/dist/core.cjs +27 -5
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +12 -2
- package/dist/core.d.ts +12 -2
- package/dist/core.js +27 -5
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +27 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +27 -5
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +66 -8
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +3 -3
- package/dist/react.d.ts +3 -3
- package/dist/react.js +66 -8
- package/dist/react.js.map +1 -1
- package/dist/skins/meet.cjs +5 -1
- package/dist/skins/meet.cjs.map +1 -1
- package/dist/skins/meet.d.cts +2 -2
- package/dist/skins/meet.d.ts +2 -2
- package/dist/skins/meet.js +5 -1
- package/dist/skins/meet.js.map +1 -1
- package/package.json +5 -5
package/dist/skins/meet.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/skins/meet.ts","../../src/skins/meet/components.tsx","../../src/react/facade.ts","../../src/core/names.ts","../../src/core/errors.ts","../../src/core/tokens.ts","../../src/core/recording.ts","../../src/core/attendance.ts","../../src/core/types.ts","../../src/core/phase.ts","../../src/core/app-panel.ts","../../src/core/collab.ts","../../src/core/transcript.ts","../../src/core/store.ts","../../src/react/root.tsx","../../src/core/observation.ts","../../src/react/provider.tsx","../../src/core/meetings.ts","../../src/core/note-taker.ts","../../src/core/livekit-driver.ts","../../src/core/format.ts","../../src/core/layout.ts","../../src/react/app-panel.tsx","../../src/core/collab-state.ts","../../src/react/hooks.ts","../../src/core/record.ts","../../src/react/collab-hooks.ts","../../src/core/reactions.ts","../../src/skins/meet/collab.tsx","../../src/skins/meet/icons.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * `@ai-matrx/meet/skins/meet` — Meet skin version 0 (CORE-DESIGN §5.3, S1).\n *\n * Today's in-call UI, moved behind the headless boundary unchanged: it reads\n * only `src/react/facade.ts` (guard: `scripts/check-skin-imports.mjs`). These\n * are its PARTS — a host layout (matrx-frontend's Board) composes them, never\n * package internals. The meeting page itself renders `<MeetingSkinRoot>` from\n * `@ai-matrx/meet/react`, which chooses the viewer's skin.\n */\n\nexport {\n AiPanel,\n AttendanceNotice,\n Avatar,\n Captions,\n ChatPanel,\n ConnectionDot,\n ConsentNotice,\n ControlBar,\n ControlButton,\n HostMenu,\n LobbyPanel,\n MeetSkinRoom,\n MeetingOverlays,\n MenuItem,\n PanelShell,\n ParticipantTile,\n PeoplePanel,\n PersonMenu,\n Popover,\n PreJoin,\n QualityBars,\n ReactionOverlay,\n RecordingIndicator,\n SettingsPanel,\n ShortcutsSheet,\n Stage,\n Toast,\n} from \"./meet/components\";\n\nexport {\n ActivitiesControl,\n BreakoutBanner,\n BreakoutsPanel,\n CaptionLanguageSettings,\n PollsPanel,\n QaPanel,\n WhiteboardPanel,\n} from \"./meet/collab\";\n\nexport * from \"./meet/icons\";\n","\"use client\";\n\n/**\n * THE PRODUCT UI (R6/R7). Not a headless kernel with a note telling the host to\n * build the rest — the whole meeting surface ships here, because per THE\n * ALL-INCLUSIVE LAW a package that withholds its components withholds its\n * product.\n *\n * The bar: FaceTime and Zoom raised by Apple's design team. Concretely, and\n * every one of these is enforced by the stylesheet or the canary:\n *\n * - `dvh`, never `vh` — iOS Safari's `vh` includes the collapsing toolbar, so a\n * `100vh` stage puts the control bar under the browser chrome.\n * - Safe-area insets on the control bar, so the hang-up button is not under the\n * home indicator.\n * - 44px touch targets, and 16px input text (anything smaller zooms iOS Safari\n * on focus, which in a meeting looks like the app broke).\n * - Semantic tokens only. There is not one hardcoded color in the package.\n * - Real loading states and no dead ends: a control the actor cannot use is\n * ABSENT, never disabled-looking, and never a button that will be refused.\n */\n\nimport { useEffect, useLayoutEffect, useRef, useState, type ReactNode } from \"react\";\nimport { type CallMode, type CameraPreview, type CollabPanel, GUEST_RECORD_ACCESS_CHOICES, MEET_SHORTCUTS, type MediaEffects, type MeetHost, type MeetParticipant, type MeetingRecord, type ParticipantIdentity, type ParticipantRole, type ParticipantStanding, isLinkFollower, type RoomChatMessage, type RoomName, type UseHostControlsResult, type VideoEffect, avatarPaletteIndex, createCameraPreview, formatDurationMs, getInitials, gridColumns, guestRecordAccessExplanation, guestRecordAccessLabel, isNoteTaker, listenOnlyNotice, microphoneNoticeCode, participantAttributes, participantSummary, useAudioLevel, useConsentNotice, useDeviceSnapshot, useElapsed, useEndMeeting, useGuestRecordAccess, useHandQueue, useHostControls, useIsHost, useLobby, useWaiting, useLocalMedia, useMediaEffects, useMeetAi, useMeetHost, useMeetShortcuts, useMeetSnapshot, useModerationNotice, useReactions, useRecording, useRequiredMeetHost, useRole, useRoom, useStage, useTrack, videoEffectsSupported, useMeetCore, MEET_REACTIONS, useCaptionTranslation, useCollabPanelRequest, isObserverRole, useMeetAppPanel, captureHeldOf, isRefusedPhase } from \"../../react/facade\";\nimport { Alert, AlertDescription, Button, Checkbox, ErrorBox, Label, SegmentedControl, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@ai-matrx/design-system\";\nimport { ActivitiesControl, BreakoutBanner, BreakoutsPanel, CaptionLanguageSettings, PollsPanel, QaPanel, WhiteboardPanel } from \"./collab\";\nimport { CameraIcon, CameraOffIcon, ChatIcon, CheckIcon, CloseIcon, HandIcon, LockIcon, MicIcon, MicOffIcon, PeopleIcon, PhoneOffIcon, PinIcon, RecordIcon, ScreenShareIcon, SettingsIcon, SignalIcon, SmileIcon, SpeakerIcon, SpotlightIcon, SparklesIcon, KeyboardIcon, FlagIcon, PencilIcon, MoreIcon, SearchIcon, WifiLowIcon } from \"./icons\";\n\n/* ------------------------------------------------------------------ inert */\n\n/**\n * 🚨 A COMPONENT IN THIS FILE NEVER THROWS BECAUSE THE PROVIDER IS INERT.\n *\n * `<MeetProvider>` is deliberately inert until a Supabase client, an\n * organization and an identity are all real — which is every server render,\n * every signed-out visitor, and every moment before the active organization\n * hydrates. Through 0.2.0 the entry components called `useRequiredMeetHost()`\n * unconditionally, so the README's own three-line integration returned a 500 on\n * every route for those visitors, and the first real consumer had to guard OUR\n * components in ITS code (register MRI-D1, matrx-frontend 2026-09-08). That is\n * exactly the massage C22 forbids, and the defect was ours.\n *\n * The rule from 0.2.1 on: an entry component either renders its product or an\n * HONEST sentence — `<InertNotice>` below — and the ambient, always-mounted\n * ones (`<IncomingCallHost>`, `<CallButton>`, `<RecordingIndicator>`) render\n * NOTHING, because there is nothing truthful to say about a call surface on a\n * page with no runtime. Internal children (`<DevicePicker>`, `<HostMenu>`,\n * `<WaitingRoom>`) keep `useRequiredMeetHost()`: they are only ever rendered\n * BELOW one of these guards, so an absent host there is a package bug and must\n * be loud.\n */\nexport function InertNotice({ what }: { what: string }): ReactNode {\n return (\n <div className=\"mx-meet__empty\" role=\"status\">\n <p className=\"mx-meet__empty-title\">{what} is not ready yet</p>\n <p className=\"mx-meet__empty-body\">\n Calls and meetings need a signed-in identity and an active organization. This will\n appear as soon as they resolve.\n </p>\n </div>\n );\n}\n\n/* ------------------------------------------------------------------ parts */\n\nexport function Avatar({\n name,\n url,\n size = 40,\n}: {\n name: string;\n url?: string | null;\n size?: number;\n}): ReactNode {\n const tint = avatarPaletteIndex(name);\n if (url != null && url.length > 0) {\n return (\n <img\n className=\"mx-meet__avatar\"\n src={url}\n alt=\"\"\n width={size}\n height={size}\n style={{ width: size, height: size }}\n />\n );\n }\n return (\n <span\n className=\"mx-meet__avatar mx-meet__avatar--initials\"\n style={{\n width: size,\n height: size,\n fontSize: Math.round(size * 0.38),\n background: `var(--mx-meet-avatar-${tint})`,\n }}\n aria-hidden=\"true\"\n >\n {getInitials(name)}\n </span>\n );\n}\n\nexport function ControlButton({\n label,\n active = false,\n danger = false,\n onClick,\n children,\n}: {\n label: string;\n active?: boolean;\n danger?: boolean;\n onClick: () => void;\n children: ReactNode;\n}): ReactNode {\n return (\n <button\n type=\"button\"\n className={[\n \"mx-meet__control\",\n active ? \"mx-meet__control--active\" : \"\",\n danger ? \"mx-meet__control--danger\" : \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n // The accessible name is the STATE, not the icon: \"Unmute\" when muted.\n aria-label={label}\n title={label}\n onClick={onClick}\n >\n {children}\n </button>\n );\n}\n\n/**\n * The recording indicator. Rendered for EVERY participant, guests included —\n * there is no prop that hides it (R5). Consent is a property of the package.\n */\nexport function RecordingIndicator(): ReactNode {\n const { state, label } = useRecording();\n if (label === null) return null;\n return (\n <span\n className={`mx-meet__recording mx-meet__recording--${state}`}\n role=\"status\"\n aria-live=\"polite\"\n >\n <RecordIcon width={10} height={10} />\n {label}\n </span>\n );\n}\n\nexport function ConnectionDot({ quality }: { quality: string }): ReactNode {\n if (quality === \"excellent\" || quality === \"good\" || quality === \"unknown\") return null;\n // Only shown when it MEANS something. A permanent green dot teaches people\n // to ignore the indicator that matters.\n return (\n <span className={`mx-meet__quality mx-meet__quality--${quality}`} title={`Connection ${quality}`}>\n <WifiLowIcon width={14} height={14} />\n </span>\n );\n}\n\n/* ------------------------------------------------------------------- tile */\n\n/** Host, Co-host or Guest — and nothing at all for an ordinary participant. Guest is the\n * STANDING of a link follower, never a role (CORE-DESIGN §2.1). */\nfunction RoleBadge({\n role,\n standing,\n}: {\n role: ParticipantRole;\n standing?: ParticipantStanding | null | undefined;\n}): ReactNode {\n if (role === \"host\") return <span className=\"mx-meet__badge\">Host</span>;\n if (role === \"cohost\") return <span className=\"mx-meet__badge\">Co-host</span>;\n // MD-16: a silent observer is listed, and says so.\n if (isObserverRole(role)) return <span className=\"mx-meet__badge\" data-meet-observing=\"\">Observing</span>;\n if (isLinkFollower(standing)) return <span className=\"mx-meet__badge\">Guest</span>;\n return null;\n}\n\nexport function ParticipantTile({\n participant,\n featured = false,\n}: {\n participant: MeetParticipant;\n featured?: boolean;\n}): ReactNode {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const queue = useHandQueue();\n const [menu, setMenu] = useState(false);\n const videoRef = useTrack(\n participant.identity,\n participant.screenSharing ? \"screen\" : \"camera\",\n );\n const showVideo = participant.cameraEnabled || participant.screenSharing;\n const pinnedHere = snapshot?.pinned === participant.identity;\n const spotlit = snapshot?.spotlight === participant.identity;\n const handPosition = participant.handRaised\n ? queue.findIndex((entry) => entry.identity === participant.identity) + 1\n : 0;\n\n return (\n <div\n className={[\n \"mx-meet__tile\",\n featured ? \"mx-meet__tile--featured\" : \"\",\n participant.speaking ? \"mx-meet__tile--speaking\" : \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n data-identity={participant.identity}\n {...participantAttributes(participant)}\n >\n {showVideo ? (\n <video\n ref={videoRef}\n className=\"mx-meet__video\"\n autoPlay\n playsInline\n muted={participant.isLocal}\n />\n ) : (\n <div className=\"mx-meet__tile-placeholder\">\n <Avatar name={participant.displayName} url={participant.avatarUrl} size={featured ? 96 : 56} />\n </div>\n )}\n\n {participant.handRaised ? (\n <span className=\"mx-meet__tile-hand\" title={`Hand raised · ${ordinal(handPosition)} in line`}>\n <HandIcon width={14} height={14} />\n {handPosition > 0 ? handPosition : null}\n </span>\n ) : null}\n\n <div className=\"mx-meet__tile-bar\">\n <span className=\"mx-meet__tile-name\">\n {spotlit ? (\n <span title=\"Spotlighted for everyone\" className=\"mx-meet__tile-flag\">\n <SpotlightIcon width={12} height={12} />\n </span>\n ) : null}\n {participant.displayName}\n {participant.isLocal ? \" (you)\" : \"\"}\n {/* An agent is LABELLED. An invisible listener is the thing nobody\n should ever ship. */}\n {participant.isAgent ? <span className=\"mx-meet__badge\">AI</span> : null}\n {/* ONE role badge in this package (MRI-A11). */}\n <RoleBadge role={participant.role} standing={participant.standing} />\n </span>\n {!participant.micEnabled ? <MicOffIcon width={14} height={14} /> : null}\n <QualityBars quality={participant.quality} />\n </div>\n\n <div className={menu ? \"mx-meet__tile-actions mx-meet__tile-actions--open\" : \"mx-meet__tile-actions\"}>\n <ControlButton\n label={pinnedHere ? \"Unpin\" : `Pin ${participant.displayName} for me`}\n active={pinnedHere}\n onClick={() => host?.engine.pin(pinnedHere ? null : participant.identity)}\n >\n <PinIcon width={16} height={16} />\n </ControlButton>\n <span className=\"mx-meet__anchor\">\n <ControlButton\n label={`More for ${participant.displayName}`}\n active={menu}\n onClick={() => setMenu(!menu)}\n >\n <MoreIcon width={16} height={16} />\n </ControlButton>\n {menu ? (\n <PersonMenu participant={participant} onClose={() => setMenu(false)} placement=\"down\" />\n ) : null}\n </span>\n </div>\n </div>\n );\n}\n\nfunction ordinal(position: number): string {\n if (position <= 0) return \"\";\n const suffix =\n position % 100 >= 11 && position % 100 <= 13\n ? \"th\"\n : ({ 1: \"st\", 2: \"nd\", 3: \"rd\" } as Record<number, string>)[position % 10] ?? \"th\";\n return `${position}${suffix}`;\n}\n\n/**\n * CONNECTION QUALITY, always legible (Meet wave 2). Four bars, like every phone:\n * a stable good connection is quiet (the bars are there, faint), and a poor or\n * lost one is loud. `unknown` draws nothing — a guess is not a reading.\n */\nexport function QualityBars({ quality }: { quality: string }): ReactNode {\n if (quality === \"unknown\") return null;\n const bars = quality === \"excellent\" ? 3 : quality === \"good\" ? 2 : quality === \"poor\" ? 1 : 0;\n const label =\n quality === \"lost\" ? \"Connection lost\" : quality === \"poor\" ? \"Poor connection\" : `Connection ${quality}`;\n return (\n <span className={`mx-meet__signal mx-meet__signal--${quality}`} title={label} aria-label={label} role=\"img\">\n <SignalIcon bars={bars as 0 | 1 | 2 | 3} width={14} height={14} />\n </span>\n );\n}\n\n/* ------------------------------------------------------------------ stage */\n\nexport function Stage({ pageSize }: { pageSize?: number }): ReactNode {\n const stage = useStage(pageSize);\n const columns = gridColumns(stage.tiles.length);\n\n if (stage.feature === null && stage.tiles.length === 0) {\n return (\n <div className=\"mx-meet__stage mx-meet__stage--empty\">\n <p className=\"mx-meet__empty-title\">Waiting for others to join</p>\n <p className=\"mx-meet__empty-body\">\n Share the meeting link and people can join from anywhere — no account needed.\n </p>\n </div>\n );\n }\n\n return (\n <div className={`mx-meet__stage${stage.feature !== null ? \" mx-meet__stage--speaker\" : \"\"}`}>\n {stage.feature !== null ? (\n <ParticipantTile participant={stage.feature} featured />\n ) : null}\n <div\n className=\"mx-meet__grid\"\n style={{ \"--mx-meet-columns\": columns } as React.CSSProperties}\n >\n {stage.tiles.map((participant) => (\n <ParticipantTile key={participant.identity} participant={participant} />\n ))}\n </div>\n {stage.overflow > 0 ? (\n <p className=\"mx-meet__overflow\">\n +{stage.overflow} more {stage.overflow === 1 ? \"person\" : \"people\"} in this meeting\n </p>\n ) : null}\n </div>\n );\n}\n\n/* ------------------------------------------------------------- control bar */\n\ntype Panel = \"none\" | \"chat\" | \"people\" | \"ai\" | \"settings\" | \"polls\" | \"qa\" | \"breakouts\" | \"app\";\n\n\n/**\n * THE CONTROL BAR — one row, the same in the package's room and in any host\n * layout that composes it (the Board). Everything a person does in a meeting is\n * reachable from here or from the panels it opens, and every keyboard shortcut\n * is bound here, so a layout that renders this bar has all of it.\n */\nexport function ControlBar({ onLeave }: { onLeave?: () => void }): ReactNode {\n const room = useRoom();\n const recording = useRecording();\n const [panel, setPanel] = useState<Panel>(\"none\");\n const [chatTo, setChatTo] = useState<ParticipantIdentity | null>(null);\n const [popover, setPopover] = useState<\"none\" | \"reactions\" | \"more\">(\"none\");\n const [shortcuts, setShortcuts] = useState(false);\n const snapshot = useMeetSnapshot();\n const lobby = useLobby();\n const controls = useHostControls();\n const queue = useHandQueue();\n const micUnavailable = useLocalMedia().microphone.unavailable;\n const unread = useUnreadChat(panel === \"chat\");\n // Meet wave 5: the shared whiteboard takes the stage while open; the panels stay.\n const [whiteboard, setWhiteboard] = useState(false);\n // HR-360 wave 2 (MD-15): the meeting's app panel, when the host app registered its key.\n const app = useMeetAppPanel();\n // The panel is why this meeting exists: it opens once, on arrival; the person may close it.\n const appOpened = useRef(false);\n useEffect(() => {\n if (app === null || appOpened.current) return;\n appOpened.current = true;\n setPanel(\"app\");\n }, [app]);\n\n const toggle = (next: Panel) => setPanel(panel === next ? \"none\" : next);\n const openActivity = (activity: CollabPanel) => {\n if (activity === \"whiteboard\") setWhiteboard(true);\n else setPanel(activity);\n };\n // A poll launched (for everyone else): open it, the way Zoom puts a poll in front of people.\n useCollabPanelRequest((activity) => {\n if (!controls.isHost) openActivity(activity);\n });\n\n // A per-person menu anywhere in the layout asked to message someone privately.\n const privateChatAt = snapshot?.privateChatRequest?.at ?? null;\n const meetStore = useMeetHost()?.store ?? null;\n useEffect(() => {\n const request = meetStore?.snapshot().privateChatRequest ?? null;\n if (request === null) return;\n setChatTo(request.to);\n setPanel(\"chat\");\n meetStore?.patch({ privateChatRequest: null });\n }, [privateChatAt, meetStore]);\n\n useMeetShortcuts({\n mic: () => void room.toggleMicrophone(),\n camera: () => void room.toggleCamera(),\n hand: () => void room.toggleHand(),\n chat: () => toggle(\"chat\"),\n people: () => toggle(\"people\"),\n help: () => setShortcuts(true),\n });\n\n const hasSpotlight = snapshot?.spotlight !== null && snapshot?.spotlight !== undefined;\n\n return (\n <>\n <MeetingOverlays />\n {shortcuts ? <ShortcutsSheet onClose={() => setShortcuts(false)} /> : null}\n {panel === \"chat\" ? (\n <ChatPanel onClose={() => setPanel(\"none\")} to={chatTo} onTo={setChatTo} />\n ) : null}\n {panel === \"people\" ? (\n <PeoplePanel\n onClose={() => setPanel(\"none\")}\n onMessage={(identity) => {\n setChatTo(identity);\n setPanel(\"chat\");\n }}\n />\n ) : null}\n {panel === \"ai\" ? <AiPanel onClose={() => setPanel(\"none\")} /> : null}\n {panel === \"settings\" ? <SettingsPanel onClose={() => setPanel(\"none\")} /> : null}\n {panel === \"polls\" ? <PollsPanel onClose={() => setPanel(\"none\")} /> : null}\n {panel === \"qa\" ? <QaPanel onClose={() => setPanel(\"none\")} /> : null}\n {panel === \"breakouts\" ? <BreakoutsPanel onClose={() => setPanel(\"none\")} /> : null}\n {whiteboard ? <WhiteboardPanel onClose={() => setWhiteboard(false)} /> : null}\n {panel === \"app\" && app !== null ? (\n <PanelShell title={app.title} onClose={() => setPanel(\"none\")}>\n <div className=\"mx-meet__app-panel\" data-meet-app-panel={app.panel.key}>\n <app.Component\n recordId={app.panel.recordId}\n organizationId={app.organizationId}\n panelKey={app.panel.key}\n />\n </div>\n </PanelShell>\n ) : null}\n <BreakoutBanner />\n\n {/* The People panel says it inline, beside the row it acted on. */}\n {controls.notice !== null && panel !== \"people\" ? (\n <Toast text={controls.notice.text} failed={controls.notice.failed} onDone={controls.clearNotice} />\n ) : null}\n\n <div className=\"mx-meet__controls\">\n {/* The label states the REFUSAL when there is one, so the control is\n never a button that looks like it should work and doesn't. */}\n <ControlButton\n label={\n micUnavailable !== null\n ? `Unmute — ${micUnavailable.remedy}`\n : room.local?.micEnabled === true\n ? \"Mute (⌘D)\"\n : \"Unmute (⌘D)\"\n }\n active={room.local?.micEnabled === true}\n onClick={() => void room.toggleMicrophone()}\n >\n {room.local?.micEnabled === true ? <MicIcon /> : <MicOffIcon />}\n </ControlButton>\n\n <ControlButton\n label={room.local?.cameraEnabled === true ? \"Stop video (⌘E)\" : \"Start video (⌘E)\"}\n active={room.local?.cameraEnabled === true}\n onClick={() => void room.toggleCamera()}\n >\n {room.local?.cameraEnabled === true ? <CameraIcon /> : <CameraOffIcon />}\n </ControlButton>\n\n {room.canShareScreen || room.local?.screenSharing === true ? (\n <ControlButton\n label={room.local?.screenSharing === true ? \"Stop sharing\" : \"Share screen\"}\n active={room.local?.screenSharing === true}\n onClick={() => void room.toggleScreenShare()}\n >\n <ScreenShareIcon />\n </ControlButton>\n ) : null}\n\n <span className=\"mx-meet__anchor\">\n <ControlButton\n label=\"Send a reaction\"\n active={popover === \"reactions\"}\n onClick={() => setPopover(popover === \"reactions\" ? \"none\" : \"reactions\")}\n >\n <SmileIcon />\n </ControlButton>\n {popover === \"reactions\" ? (\n <Popover label=\"Reactions\" onClose={() => setPopover(\"none\")}>\n <div className=\"mx-meet__emoji-bar\">\n {MEET_REACTIONS.map((emoji) => (\n <button\n key={emoji}\n type=\"button\"\n className=\"mx-meet__emoji\"\n aria-label={`React ${emoji}`}\n onClick={() => void room.react(emoji)}\n >\n {emoji}\n </button>\n ))}\n </div>\n </Popover>\n ) : null}\n </span>\n\n <ControlButton\n label={room.local?.handRaised === true ? \"Lower hand (⌘⌥H)\" : \"Raise hand (⌘⌥H)\"}\n active={room.local?.handRaised === true}\n onClick={() => void room.toggleHand()}\n >\n <HandIcon />\n </ControlButton>\n\n {app !== null ? (\n <ControlButton label={app.title} active={panel === \"app\"} onClick={() => toggle(\"app\")}>\n <FlagIcon />\n </ControlButton>\n ) : null}\n\n <ControlButton label=\"Chat (⌘⌥C)\" active={panel === \"chat\"} onClick={() => toggle(\"chat\")}>\n <ChatIcon />\n {unread > 0 ? <span className=\"mx-meet__dot\" aria-hidden=\"true\" /> : null}\n </ControlButton>\n\n <ControlButton\n label={\n lobby.waiting.length > 0\n ? `People — ${lobby.waiting.length} waiting`\n : `People — ${(snapshot?.participants ?? []).length} in the meeting`\n }\n active={panel === \"people\"}\n onClick={() => toggle(\"people\")}\n >\n <PeopleIcon />\n {lobby.waiting.length > 0 || (controls.isHost && queue.length > 0) ? (\n <span className=\"mx-meet__dot\" aria-hidden=\"true\" />\n ) : null}\n </ControlButton>\n\n {/* Meet wave 5: polls, Q&A, whiteboard, breakout rooms — one door, Google\n Meet's Activities. Absent when the organization turned them all off. */}\n <ActivitiesControl\n active={whiteboard ? \"whiteboard\" : panel === \"polls\" || panel === \"qa\" || panel === \"breakouts\" ? panel : null}\n onOpen={openActivity}\n />\n\n {/* The AI control appears only when the meeting's AI is on — never a\n button that resolves to \"not configured\". */}\n <AiControl active={panel === \"ai\"} onToggle={() => toggle(\"ai\")} />\n\n {recording.canStart ? (\n <ControlButton label=\"Start recording\" onClick={() => void recording.start()}>\n <RecordIcon />\n </ControlButton>\n ) : null}\n {recording.canStop ? (\n <ControlButton label=\"Stop recording\" active onClick={() => void recording.stop()}>\n <RecordIcon />\n </ControlButton>\n ) : null}\n\n <span className=\"mx-meet__anchor\">\n <ControlButton\n label=\"More options\"\n active={popover === \"more\" || panel === \"settings\"}\n onClick={() => setPopover(popover === \"more\" ? \"none\" : \"more\")}\n >\n <MoreIcon />\n </ControlButton>\n {popover === \"more\" ? (\n <Popover label=\"More options\" onClose={() => setPopover(\"none\")}>\n <div className=\"mx-meet__menu\" role=\"menu\">\n <MenuItem\n onSelect={() => {\n setPopover(\"none\");\n setPanel(\"settings\");\n }}\n >\n <SettingsIcon width={16} height={16} /> Settings\n </MenuItem>\n <MenuItem\n onSelect={() => {\n setPopover(\"none\");\n setShortcuts(true);\n }}\n >\n <KeyboardIcon width={16} height={16} /> Keyboard shortcuts\n </MenuItem>\n {controls.isHost ? (\n <>\n <div className=\"mx-meet__menu-sep\" role=\"separator\" />\n <MenuItem\n onSelect={() => {\n setPopover(\"none\");\n void controls.run({ action: \"mute_all\" });\n }}\n >\n <MicOffIcon width={16} height={16} /> Mute everyone\n </MenuItem>\n <MenuItem\n onSelect={() => {\n setPopover(\"none\");\n void controls.run({ action: \"ask_all_unmute\" });\n }}\n >\n <MicIcon width={16} height={16} /> Ask everyone to unmute\n </MenuItem>\n {queue.length > 0 ? (\n <MenuItem\n onSelect={() => {\n setPopover(\"none\");\n void controls.run({ action: \"lower_all_hands\" });\n }}\n >\n <HandIcon width={16} height={16} /> Lower all hands ({queue.length})\n </MenuItem>\n ) : null}\n {hasSpotlight ? (\n <MenuItem\n onSelect={() => {\n setPopover(\"none\");\n void controls.run({ action: \"spotlight\", identity: null });\n }}\n >\n <SpotlightIcon width={16} height={16} /> Remove spotlight\n </MenuItem>\n ) : null}\n </>\n ) : null}\n </div>\n </Popover>\n ) : null}\n </span>\n\n <ControlButton\n label=\"Leave\"\n danger\n onClick={() => {\n void room.leave();\n onLeave?.();\n }}\n >\n <PhoneOffIcon />\n </ControlButton>\n </div>\n </>\n );\n}\n\n/** Messages that arrived while the chat panel was closed. */\nfunction useUnreadChat(open: boolean): number {\n const snapshot = useMeetSnapshot();\n const chat = snapshot?.chat ?? [];\n const local = snapshot?.localIdentity ?? null;\n const [seen, setSeen] = useState(0);\n useEffect(() => {\n if (open) setSeen(chat.length);\n }, [open, chat.length]);\n // The saved history read on join is not \"unread\" — only what arrives after it.\n const loaded = snapshot?.chatLoaded === true;\n const [baseline, setBaseline] = useState<number | null>(null);\n useEffect(() => {\n if (loaded && baseline === null) setBaseline(chat.length);\n }, [loaded, baseline, chat.length]);\n if (open) return 0;\n const from = Math.max(seen, baseline ?? chat.length);\n return chat.slice(from).filter((message) => message.identity !== local).length;\n}\n\n/**\n * A small floating surface anchored to its control. Escape and an outside click\n * close it. It is FIXED-positioned from the anchor's rectangle, because the\n * controls it hangs off live inside tiles that clip (`overflow: hidden`) and\n * inside a Board that pans — an absolutely positioned menu would be cut in half.\n */\nexport function Popover({\n label,\n onClose,\n children,\n placement = \"up\",\n}: {\n label: string;\n onClose: () => void;\n children: ReactNode;\n placement?: \"up\" | \"down\";\n}): ReactNode {\n const ref = useRef<HTMLDivElement | null>(null);\n // FIXED FROM THE FIRST FRAME. Hidden but in normal flow, the popover made its\n // anchor as tall as the whole menu, so the anchor's measured bottom was the\n // menu's bottom and the menu opened a full menu-height below its button\n // (host menu: Make host / Report / Remove off-screen, 2026-09-27).\n const [style, setStyle] = useState<React.CSSProperties>({\n position: \"fixed\",\n top: 0,\n left: 0,\n visibility: \"hidden\",\n });\n useLayoutEffect(() => {\n const element = ref.current;\n const anchor = element?.parentElement ?? null;\n if (element === null || anchor === null) return;\n const place = () => {\n const rect = anchor.getBoundingClientRect();\n const width = element.offsetWidth;\n const height = element.offsetHeight;\n const gutter = 8;\n const left = Math.min(\n Math.max(gutter, rect.left + rect.width / 2 - width / 2),\n window.innerWidth - width - gutter,\n );\n const fitsBelow = rect.bottom + gutter + height <= window.innerHeight;\n const fitsAbove = rect.top - gutter - height >= 0;\n if (!fitsBelow && !fitsAbove) {\n // NEITHER SIDE HOLDS IT (a tall host menu from a row mid-screen): pin it\n // inside the viewport and let it scroll, so the last items — Make host,\n // Remove from meeting — are never below the fold (2026-09-27).\n const top = Math.max(gutter, window.innerHeight - height - gutter);\n setStyle({\n position: \"fixed\",\n left,\n top,\n maxHeight: window.innerHeight - top - gutter,\n overflowY: \"auto\",\n });\n return;\n }\n const down = placement === \"down\" ? fitsBelow || !fitsAbove : !fitsAbove && fitsBelow;\n setStyle(\n down\n ? { position: \"fixed\", left, top: rect.bottom + gutter }\n : { position: \"fixed\", left, bottom: window.innerHeight - rect.top + gutter },\n );\n };\n place();\n // Re-placed when the layout moves (a lobby row arriving, a panel scrolling),\n // not only on window resize.\n const frame = requestAnimationFrame(place);\n const observer =\n typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(() => place());\n observer?.observe(document.body);\n observer?.observe(element);\n window.addEventListener(\"resize\", place);\n window.addEventListener(\"scroll\", place, true);\n return () => {\n cancelAnimationFrame(frame);\n observer?.disconnect();\n window.removeEventListener(\"resize\", place);\n window.removeEventListener(\"scroll\", place, true);\n };\n }, [placement]);\n useEffect(() => {\n const onKey = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") onClose();\n };\n const onPointer = (event: PointerEvent) => {\n const anchor = ref.current?.parentElement ?? null;\n if (anchor !== null && !anchor.contains(event.target as Node)) onClose();\n };\n window.addEventListener(\"keydown\", onKey);\n window.addEventListener(\"pointerdown\", onPointer);\n return () => {\n window.removeEventListener(\"keydown\", onKey);\n window.removeEventListener(\"pointerdown\", onPointer);\n };\n }, [onClose]);\n return (\n <div ref={ref} className=\"mx-meet__popover\" style={style} role=\"dialog\" aria-label={label}>\n {children}\n </div>\n );\n}\n\nexport function MenuItem({\n onSelect,\n danger = false,\n children,\n}: {\n onSelect: () => void;\n danger?: boolean;\n children: ReactNode;\n}): ReactNode {\n return (\n <button\n type=\"button\"\n role=\"menuitem\"\n className={danger ? \"mx-meet__menu-item mx-meet__menu-item--danger\" : \"mx-meet__menu-item\"}\n onClick={onSelect}\n >\n {children}\n </button>\n );\n}\n\n/** A transient sentence above the control bar. Stays 4 s, or until dismissed. */\nexport function Toast({ text, failed, onDone }: { text: string; failed: boolean; onDone: () => void }): ReactNode {\n useEffect(() => {\n const timer = setTimeout(onDone, failed ? 7000 : 4000);\n return () => clearTimeout(timer);\n }, [text, failed, onDone]);\n return (\n <div className={failed ? \"mx-meet__toast mx-meet__toast--failed\" : \"mx-meet__toast\"} role=\"status\">\n <span>{text}</span>\n <button type=\"button\" className=\"mx-meet__toast-close\" aria-label=\"Dismiss\" onClick={onDone}>\n <CloseIcon width={14} height={14} />\n </button>\n </div>\n );\n}\n\n/**\n * WHAT FLOATS OVER THE MEETING, in every layout (Meet wave 2): the reactions\n * everyone sends, and what a host just did to THIS person — \"the host is asking\n * you to unmute\" with the Unmute button right there, which is the whole point.\n */\nexport function MeetingOverlays(): ReactNode {\n const reactions = useReactions();\n const { notice, dismiss } = useModerationNotice();\n const room = useRoom();\n return (\n <>\n <ReactionOverlay reactions={reactions} />\n {notice !== null && notice.kind !== \"removed\" ? (\n // A host's request (\"the host is asking you to unmute\"), not an error:\n // an assertive status, so it is announced at once without being filed\n // as a failure (design-system error-box census).\n <div className=\"mx-meet__notice-bar\" role=\"status\" aria-live=\"assertive\">\n <span>{notice.text}</span>\n {notice.kind === \"ask-unmute\" && room.local?.micEnabled !== true ? (\n <button\n type=\"button\"\n className=\"mx-meet__primary mx-meet__primary--small\"\n onClick={() => {\n dismiss();\n void room.toggleMicrophone();\n }}\n >\n Unmute\n </button>\n ) : null}\n <button type=\"button\" className=\"mx-meet__toast-close\" aria-label=\"Dismiss\" onClick={dismiss}>\n <CloseIcon width={14} height={14} />\n </button>\n </div>\n ) : null}\n </>\n );\n}\n\n/** Every shortcut, on one sheet. Opened by Shift+? or from More options. */\nexport function ShortcutsSheet({ onClose }: { onClose: () => void }): ReactNode {\n useEffect(() => {\n const onKey = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") onClose();\n };\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [onClose]);\n return (\n <div className=\"mx-meet__sheet-backdrop\" onClick={onClose}>\n <div\n className=\"mx-meet__sheet\"\n role=\"dialog\"\n aria-label=\"Keyboard shortcuts\"\n onClick={(event) => event.stopPropagation()}\n >\n <header className=\"mx-meet__panel-head\">\n <h2 className=\"mx-meet__panel-title\">Keyboard shortcuts</h2>\n <ControlButton label=\"Close\" onClick={onClose}>\n <CloseIcon width={16} height={16} />\n </ControlButton>\n </header>\n <dl className=\"mx-meet__shortcuts\">\n {MEET_SHORTCUTS.map((shortcut) => (\n <div key={shortcut.id} className=\"mx-meet__shortcut\">\n <dt>{shortcut.label}</dt>\n <dd>\n <kbd className=\"mx-meet__kbd\">{shortcut.keys}</kbd>\n </dd>\n </div>\n ))}\n </dl>\n </div>\n </div>\n );\n}\n\n/**\n * The assistant control. It appears for every meeting whose AI is on, because\n * as of 0.3.0 the capability is the SERVER's: there is no host config that\n * could leave it unfilled, and a meeting with no Holder bound gets the server's\n * sentence in the panel rather than a control that never appears. A meeting\n * with `ai_enabled = false` gets NO control at all — absent, not disabled.\n */\nfunction AiControl({ active, onToggle }: { active: boolean; onToggle: () => void }): ReactNode {\n const snapshot = useMeetSnapshot();\n const meeting = snapshot?.meeting ?? null;\n if (meeting === null || !meeting.aiEnabled) return null;\n return (\n <ControlButton label=\"Meeting assistant\" active={active} onClick={onToggle}>\n <SparklesIcon />\n </ControlButton>\n );\n}\n\n/* ----------------------------------------------------------------- panels */\n\nexport function PanelShell({\n title,\n onClose,\n children,\n}: {\n title: string;\n onClose: () => void;\n children: ReactNode;\n}): ReactNode {\n // 🚨 A SIDE PANEL NEVER COVERS THE HEADER (verifier, 2026-09-29: People and\n // Chat hid End and Invite). The panel is positioned against the room root, so\n // it starts where the room's header ENDS — measured, because the header's\n // height is the host's (its controls, a wrapped title, the safe area).\n const ref = useRef<HTMLElement | null>(null);\n useLayoutEffect(() => {\n const panel = ref.current;\n const root = panel?.closest(\".mx-meet\");\n const header = root?.querySelector<HTMLElement>(\":scope > .mx-meet__header\") ?? null;\n if (!panel || !header) return undefined;\n const place = () => panel.style.setProperty(\"--mx-meet-panel-top\", `${header.offsetHeight}px`);\n place();\n if (typeof ResizeObserver === \"undefined\") return undefined;\n const observer = new ResizeObserver(place);\n observer.observe(header);\n return () => observer.disconnect();\n }, []);\n return (\n <aside ref={ref} className=\"mx-meet__panel\" aria-label={title}>\n <header className=\"mx-meet__panel-head\">\n <h2 className=\"mx-meet__panel-title\">{title}</h2>\n <ControlButton label=\"Close panel\" onClick={onClose}>\n <CloseIcon width={16} height={16} />\n </ControlButton>\n </header>\n <div className=\"mx-meet__panel-body\">{children}</div>\n </aside>\n );\n}\n\n/**\n * IN-MEETING CHAT (Meet wave 2). A message to everyone is SAVED with the\n * meeting — a late joiner scrolls up and reads it, and it is part of the record.\n * A private message goes to one person only and is never saved; the composer\n * says which of the two you are about to send.\n */\nexport function ChatPanel({\n onClose,\n to = null,\n onTo,\n}: {\n onClose: () => void;\n to?: ParticipantIdentity | null;\n onTo?: (identity: ParticipantIdentity | null) => void;\n}): ReactNode {\n const snapshot = useMeetSnapshot();\n const room = useRoom();\n const [draft, setDraft] = useState(\"\");\n const [recipient, setRecipient] = useState<ParticipantIdentity | null>(to);\n const listRef = useRef<HTMLUListElement | null>(null);\n const messages = snapshot?.chat ?? [];\n const local = snapshot?.localIdentity ?? null;\n const people = (snapshot?.participants ?? []).filter(\n (participant) => !participant.isLocal && !participant.isAgent,\n );\n const recipientName =\n recipient === null ? null : people.find((p) => p.identity === recipient)?.displayName ?? null;\n\n useEffect(() => setRecipient(to), [to]);\n useEffect(() => {\n const list = listRef.current;\n if (list !== null) list.scrollTop = list.scrollHeight;\n }, [messages.length]);\n\n const choose = (next: ParticipantIdentity | null) => {\n setRecipient(next);\n onTo?.(next);\n };\n\n return (\n <PanelShell title=\"Chat\" onClose={onClose}>\n <ul className=\"mx-meet__chat\" ref={listRef}>\n {snapshot?.chatHistoryError != null ? (\n <li className=\"mx-meet__panel-note\">{snapshot.chatHistoryError}</li>\n ) : null}\n {snapshot?.chatLoaded !== true && messages.length === 0 ? (\n <li className=\"mx-meet__panel-note\">Reading earlier messages…</li>\n ) : messages.length === 0 && snapshot?.chatHistoryError == null ? (\n <li className=\"mx-meet__panel-note\">\n No messages yet. Messages to everyone stay with the meeting, so people who join later\n see them.\n </li>\n ) : null}\n {messages.map((message) => (\n <ChatLine key={message.id} message={message} local={local} people={people} />\n ))}\n </ul>\n <form\n className=\"mx-meet__composer mx-meet__composer--chat\"\n onSubmit={(event) => {\n event.preventDefault();\n const text = draft;\n if (text.trim().length === 0) return;\n setDraft(\"\");\n if (recipient === null) void room.sendChat(text).catch(() => undefined);\n else void room.sendPrivateChat(recipient, text);\n }}\n >\n {people.length > 0 ? (\n <select\n className=\"mx-meet__select mx-meet__select--compact\"\n aria-label=\"Send to\"\n value={recipient ?? \"\"}\n onChange={(event) =>\n choose(event.target.value === \"\" ? null : (event.target.value as ParticipantIdentity))\n }\n >\n <option value=\"\">Everyone</option>\n {people.map((participant) => (\n <option key={participant.identity} value={participant.identity}>\n {participant.displayName} (private)\n </option>\n ))}\n </select>\n ) : null}\n <div className=\"mx-meet__composer-row\">\n <input\n className=\"mx-meet__input\"\n value={draft}\n onChange={(event) => setDraft(event.target.value)}\n placeholder={recipientName === null ? \"Message everyone\" : `Message ${recipientName} privately`}\n aria-label={recipientName === null ? \"Message everyone\" : `Message ${recipientName} privately`}\n maxLength={4000}\n />\n <button type=\"submit\" className=\"mx-meet__send\" disabled={draft.trim().length === 0}>\n Send\n </button>\n </div>\n {recipientName !== null ? (\n <p className=\"mx-meet__panel-note\">Only {recipientName} sees this. Private messages are not saved.</p>\n ) : null}\n </form>\n </PanelShell>\n );\n}\n\nfunction ChatLine({\n message,\n local,\n people,\n}: {\n message: RoomChatMessage;\n local: ParticipantIdentity | null;\n people: readonly MeetParticipant[];\n}): ReactNode {\n const mine = message.identity === local;\n const privateTo = message.to ?? null;\n const counterpart =\n privateTo === null\n ? null\n : mine\n ? people.find((p) => p.identity === privateTo)?.displayName ?? \"them\"\n : \"you\";\n return (\n <li\n className={[\n \"mx-meet__chat-line\",\n privateTo !== null ? \"mx-meet__chat-line--private\" : \"\",\n message.failed !== undefined ? \"mx-meet__chat-line--failed\" : \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n >\n <span className=\"mx-meet__chat-meta\">\n <strong>{mine ? \"You\" : message.displayName}</strong>\n {counterpart !== null ? <span className=\"mx-meet__chat-private\">to {counterpart} · private</span> : null}\n <time dateTime={message.at}>{clockOf(message.at)}</time>\n </span>\n <span className=\"mx-meet__chat-text\">{message.text}</span>\n {message.failed !== undefined ? (\n <span className=\"mx-meet__chat-failed\">{message.failed}</span>\n ) : privateTo === null && message.saved === false ? (\n <span className=\"mx-meet__chat-pending\">\n {message.id.startsWith(\"pending-\") ? \"Sending…\" : \"Not saved — people who join later will not see this\"}\n </span>\n ) : null}\n </li>\n );\n}\n\nexport function PeoplePanel({\n onClose,\n onMessage,\n}: {\n onClose: () => void;\n onMessage?: (identity: ParticipantIdentity) => void;\n}): ReactNode {\n const snapshot = useMeetSnapshot();\n const controls = useHostControls();\n const queue = useHandQueue();\n const [query, setQuery] = useState(\"\");\n const [menuFor, setMenuFor] = useState<ParticipantIdentity | null>(null);\n // THE LOCAL ROW IS BADGED FROM THE EFFECTIVE ROLE (MRI-A11).\n const myRole = useRole();\n const local = snapshot?.localIdentity ?? null;\n // MD-16: observers are listed — to everyone by default; to the hosts only when the\n // organization's `meet.observers_visible_to` knob says so (the server sends it).\n const observersHidden = snapshot?.grants.observersVisibleTo === \"hosts\" && !controls.isHost;\n const everyone = (snapshot?.participants ?? []).filter(\n (participant) =>\n !observersHidden || !isObserverRole(participant.role) || participant.identity === local,\n );\n const needle = query.trim().toLowerCase();\n const shown =\n needle.length === 0\n ? everyone\n : everyone.filter((participant) => participant.displayName.toLowerCase().includes(needle));\n\n return (\n <PanelShell title={`People (${everyone.length})`} onClose={onClose}>\n <LobbyPanel />\n\n {queue.length > 0 ? (\n <section className=\"mx-meet__section\" aria-label=\"Raised hands\">\n <header className=\"mx-meet__section-head\">\n <h3 className=\"mx-meet__section-title\">Raised hands · {queue.length}</h3>\n {controls.isHost ? (\n <button\n type=\"button\"\n className=\"mx-meet__link\"\n onClick={() => void controls.run({ action: \"lower_all_hands\" })}\n >\n Lower all\n </button>\n ) : null}\n </header>\n <ol className=\"mx-meet__queue\">\n {queue.map((participant) => (\n <li key={participant.identity} className=\"mx-meet__person\">\n <Avatar name={participant.displayName} url={participant.avatarUrl} size={28} />\n <span className=\"mx-meet__person-name\">\n {participant.displayName}\n {participant.identity === local ? <span className=\"mx-meet__you\"> (you)</span> : null}\n </span>\n {controls.isHost || participant.identity === local ? (\n <button\n type=\"button\"\n className=\"mx-meet__link\"\n onClick={() =>\n void controls.run({ action: \"lower_hand\", identity: participant.identity })\n }\n >\n Lower\n </button>\n ) : null}\n </li>\n ))}\n </ol>\n </section>\n ) : null}\n\n {controls.isHost && everyone.length > 1 ? (\n <div className=\"mx-meet__bulk\">\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => void controls.run({ action: \"mute_all\" })}>\n <MicOffIcon width={14} height={14} /> Mute all\n </button>\n <button\n type=\"button\"\n className=\"mx-meet__chip\"\n onClick={() => void controls.run({ action: \"ask_all_unmute\" })}\n >\n <MicIcon width={14} height={14} /> Ask all to unmute\n </button>\n </div>\n ) : null}\n\n {everyone.length > 4 ? (\n <label className=\"mx-meet__search\">\n <SearchIcon width={16} height={16} />\n <input\n className=\"mx-meet__input mx-meet__input--bare\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n placeholder=\"Search people\"\n aria-label=\"Search people\"\n />\n </label>\n ) : null}\n\n {controls.notice !== null ? (\n <p className={controls.notice.failed ? \"mx-meet__error\" : \"mx-meet__notice\"} role=\"status\">\n {controls.notice.text}\n </p>\n ) : null}\n\n <ul className=\"mx-meet__people\">\n {shown.map((participant) => (\n <li key={participant.identity} className=\"mx-meet__person\">\n <Avatar name={participant.displayName} url={participant.avatarUrl} size={32} />\n <span className=\"mx-meet__person-name\">\n {participant.displayName}\n {participant.identity === local ? <span className=\"mx-meet__you\"> (you)</span> : null}\n <RoleBadge\n role={participant.identity === local ? myRole : participant.role}\n standing={participant.standing}\n />\n {isNoteTaker(participant) ? (\n <span className=\"mx-meet__badge\">Meeting assistant</span>\n ) : null}\n {snapshot?.spotlight === participant.identity ? (\n <span className=\"mx-meet__badge\">Spotlight</span>\n ) : null}\n </span>\n {participant.handRaised ? <HandIcon width={14} height={14} /> : null}\n {!participant.micEnabled && !isNoteTaker(participant) ? (\n <MicOffIcon width={14} height={14} />\n ) : null}\n <QualityBars quality={participant.quality} />\n <span className=\"mx-meet__anchor\">\n <ControlButton\n label={`More for ${participant.displayName}`}\n active={menuFor === participant.identity}\n onClick={() => setMenuFor(menuFor === participant.identity ? null : participant.identity)}\n >\n <MoreIcon width={16} height={16} />\n </ControlButton>\n {menuFor === participant.identity ? (\n <PersonMenu\n participant={participant}\n placement=\"down\"\n onClose={() => setMenuFor(null)}\n {...(onMessage !== undefined ? { onMessage } : {})}\n />\n ) : null}\n </span>\n </li>\n ))}\n {shown.length === 0 ? <li className=\"mx-meet__panel-note\">Nobody matches “{query}”.</li> : null}\n </ul>\n\n {/* MRF-1: a host decides what the guests in this list will be able to read\n afterwards, WHILE the meeting is running — which is when they know. */}\n {snapshot?.meeting !== null && snapshot?.meeting !== undefined ? (\n <GuestRecordAccessControl meeting={snapshot.meeting} />\n ) : null}\n </PanelShell>\n );\n}\n\n/**\n * EVERYTHING YOU CAN DO ABOUT ONE PERSON (Meet wave 2), in one menu — on their\n * tile and on their People row. An item appears only when it would be honoured:\n * the rank ladder decides moderation (a co-host never sees \"Remove\" on the host),\n * only the host sees \"Make host\", and nobody sees a control that the server\n * would refuse. Removing someone and handing the meeting over NAME THEIR\n * CONSEQUENCE first, inline — never a browser dialog.\n */\nexport function PersonMenu({\n participant,\n onClose,\n onMessage,\n placement = \"down\",\n}: {\n participant: MeetParticipant;\n onClose: () => void;\n onMessage?: (identity: ParticipantIdentity) => void;\n placement?: \"up\" | \"down\";\n}): ReactNode {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const controls = useHostControls();\n const [mode, setMode] = useState<\"menu\" | \"rename\" | \"report\" | \"remove\" | \"transfer\">(\"menu\");\n const [text, setText] = useState(\"\");\n const self = participant.identity === snapshot?.localIdentity;\n const moderate = controls.canModerate(participant);\n const pinned = snapshot?.pinned === participant.identity;\n const spotlit = snapshot?.spotlight === participant.identity;\n const hasAccount = participant.userId !== null;\n // A refused act keeps its pane open WITH the server's sentence and live\n // buttons — never a confirm that silently stays up (verifier, 2026-09-27).\n const [refused, setRefused] = useState(false);\n const act = (request: Parameters<UseHostControlsResult[\"run\"]>[0]) => {\n setRefused(false);\n void controls.run(request).then((result) => {\n if (result !== null) onClose();\n else setRefused(true);\n });\n };\n const refusal =\n refused && controls.notice?.failed === true ? (\n <ErrorBox as=\"p\" className=\"mx-meet__error\" message={controls.notice.text} operation=\"Change this participant\" origin=\"meet/components\">\n {controls.notice.text}\n </ErrorBox>\n ) : null;\n\n if (mode === \"rename\" || mode === \"report\") {\n const renaming = mode === \"rename\";\n return (\n <Popover label={renaming ? \"Rename\" : \"Report\"} onClose={onClose} placement={placement}>\n <form\n className=\"mx-meet__menu-form\"\n onSubmit={(event) => {\n event.preventDefault();\n if (text.trim().length === 0) return;\n act(\n renaming\n ? { action: \"rename\", identity: participant.identity, name: text.trim() }\n : { action: \"report\", identity: participant.identity, reason: text.trim() },\n );\n }}\n >\n <label className=\"mx-meet__field\">\n <span className=\"mx-meet__field-label\">\n {renaming ? \"Name shown in this meeting\" : `What did ${participant.displayName} do?`}\n </span>\n {renaming ? (\n <input\n className=\"mx-meet__input\"\n autoFocus\n maxLength={80}\n value={text}\n onChange={(event) => setText(event.target.value)}\n />\n ) : (\n <textarea\n className=\"mx-meet__input\"\n autoFocus\n rows={3}\n maxLength={1000}\n value={text}\n onChange={(event) => setText(event.target.value)}\n />\n )}\n </label>\n {refusal}\n <div className=\"mx-meet__menu-actions\">\n <button type=\"button\" className=\"mx-meet__link\" onClick={() => setMode(\"menu\")}>\n Back\n </button>\n <button type=\"submit\" className=\"mx-meet__send\" disabled={text.trim().length === 0 || controls.busy}>\n {renaming ? \"Rename\" : \"Report\"}\n </button>\n </div>\n </form>\n </Popover>\n );\n }\n\n if (mode === \"remove\" || mode === \"transfer\") {\n const removing = mode === \"remove\";\n return (\n <Popover label={removing ? \"Remove\" : \"Make host\"} onClose={onClose} placement={placement}>\n <div className=\"mx-meet__menu-form\">\n <p className=\"mx-meet__confirm-text\">\n {removing\n ? `Remove ${participant.displayName}? They leave immediately and cannot rejoin this meeting.`\n : `Make ${participant.displayName} the host? They get every host control and own this meeting. You stay on as a co-host.`}\n </p>\n {refusal}\n <div className=\"mx-meet__menu-actions\">\n <button type=\"button\" className=\"mx-meet__link\" onClick={() => setMode(\"menu\")}>\n Cancel\n </button>\n <button\n type=\"button\"\n className={removing ? \"mx-meet__send mx-meet__send--danger\" : \"mx-meet__send\"}\n disabled={controls.busy}\n onClick={() =>\n act({ action: removing ? \"remove\" : \"transfer_host\", identity: participant.identity })\n }\n >\n {removing ? \"Remove\" : \"Make host\"}\n </button>\n </div>\n </div>\n </Popover>\n );\n }\n\n return (\n <Popover label={`Options for ${participant.displayName}`} onClose={onClose} placement={placement}>\n <div className=\"mx-meet__menu\" role=\"menu\">\n <MenuItem\n onSelect={() => {\n host?.engine.pin(pinned ? null : participant.identity);\n onClose();\n }}\n >\n <PinIcon width={16} height={16} /> {pinned ? \"Unpin\" : \"Pin for me\"}\n </MenuItem>\n {!self && !participant.isAgent && (onMessage !== undefined || host !== null) ? (\n <MenuItem\n onSelect={() => {\n if (onMessage !== undefined) onMessage(participant.identity);\n else\n host?.store.patch({\n privateChatRequest: { to: participant.identity, at: Date.now() },\n });\n onClose();\n }}\n >\n <ChatIcon width={16} height={16} /> Message privately\n </MenuItem>\n ) : null}\n {controls.isHost ? (\n <MenuItem onSelect={() => act({ action: \"spotlight\", identity: spotlit ? null : participant.identity })}>\n <SpotlightIcon width={16} height={16} /> {spotlit ? \"Remove spotlight\" : \"Spotlight for everyone\"}\n </MenuItem>\n ) : null}\n {moderate ? (\n <>\n <div className=\"mx-meet__menu-sep\" role=\"separator\" />\n {participant.micEnabled ? (\n <MenuItem onSelect={() => act({ action: \"mute\", identity: participant.identity })}>\n <MicOffIcon width={16} height={16} /> Mute\n </MenuItem>\n ) : !participant.isAgent ? (\n <MenuItem onSelect={() => act({ action: \"ask_unmute\", identity: participant.identity })}>\n <MicIcon width={16} height={16} /> Ask to unmute\n </MenuItem>\n ) : null}\n {participant.cameraEnabled ? (\n <MenuItem onSelect={() => act({ action: \"stop_video\", identity: participant.identity })}>\n <CameraOffIcon width={16} height={16} /> Stop video\n </MenuItem>\n ) : null}\n {participant.screenSharing ? (\n <MenuItem onSelect={() => act({ action: \"stop_screen\", identity: participant.identity })}>\n <ScreenShareIcon width={16} height={16} /> Stop screen share\n </MenuItem>\n ) : null}\n {participant.handRaised ? (\n <MenuItem onSelect={() => act({ action: \"lower_hand\", identity: participant.identity })}>\n <HandIcon width={16} height={16} /> Lower hand\n </MenuItem>\n ) : null}\n {hasAccount && !participant.isAgent && participant.role !== \"cohost\" ? (\n <MenuItem onSelect={() => act({ action: \"make_cohost\", identity: participant.identity })}>\n <PeopleIcon width={16} height={16} /> Make co-host\n </MenuItem>\n ) : null}\n {!participant.isAgent ? (\n <MenuItem\n onSelect={() => {\n setText(participant.displayName);\n setMode(\"rename\");\n }}\n >\n <PencilIcon width={16} height={16} /> Rename\n </MenuItem>\n ) : null}\n </>\n ) : null}\n {controls.isOwner && participant.role === \"cohost\" ? (\n <MenuItem onSelect={() => act({ action: \"revoke_cohost\", identity: participant.identity })}>\n <PeopleIcon width={16} height={16} /> Remove co-host\n </MenuItem>\n ) : null}\n {controls.isOwner && !self && hasAccount && !participant.isAgent ? (\n <MenuItem onSelect={() => setMode(\"transfer\")}>\n <SpotlightIcon width={16} height={16} /> Make host\n </MenuItem>\n ) : null}\n {self ? (\n <MenuItem\n onSelect={() => {\n setText(participant.displayName);\n setMode(\"rename\");\n }}\n >\n <PencilIcon width={16} height={16} /> Rename yourself\n </MenuItem>\n ) : null}\n {!self && !participant.isAgent ? (\n <MenuItem\n onSelect={() => {\n setText(\"\");\n setMode(\"report\");\n }}\n >\n <FlagIcon width={16} height={16} /> Report\n </MenuItem>\n ) : null}\n {moderate && !participant.isAgent ? (\n <MenuItem danger onSelect={() => setMode(\"remove\")}>\n <CloseIcon width={16} height={16} /> Remove from meeting\n </MenuItem>\n ) : null}\n </div>\n </Popover>\n );\n}\n\n/* --------------------------------------------------------------- settings */\n\n/**\n * AUDIO, VIDEO AND EFFECTS (Meet wave 2) — the devices in use, a live\n * microphone level, a speaker test, noise suppression and the camera\n * background. Every change applies to the live meeting at once and is\n * remembered in this browser.\n */\nexport function SettingsPanel({ onClose }: { onClose: () => void }): ReactNode {\n const host = useRequiredMeetHost();\n return (\n <PanelShell title=\"Settings\" onClose={onClose}>\n <DeviceSettings\n onPick={(kind, deviceId) => {\n host.devices.select(kind, deviceId);\n void host.engine.switchDevice(kind, deviceId);\n }}\n />\n <EffectsSettings />\n <CaptionLanguageSettings />\n </PanelShell>\n );\n}\n\n/** Microphone (with a live level), speaker (with a test), camera. */\nfunction DeviceSettings({\n onPick,\n}: {\n onPick: (kind: \"audioinput\" | \"videoinput\" | \"audiooutput\", deviceId: string) => void;\n}): ReactNode {\n const host = useRequiredMeetHost();\n const devices = useDeviceSnapshot();\n const microphone = devices?.selected.audioinput ?? host.devices.resolve(\"audioinput\");\n const speaker = devices?.selected.audiooutput ?? host.devices.resolve(\"audiooutput\");\n const stream = useMicrophoneStream(microphone);\n const level = useAudioLevel(stream);\n const [testing, setTesting] = useState(false);\n\n useEffect(() => {\n void host.devices.refresh();\n }, [host]);\n\n return (\n <section className=\"mx-meet__section\" aria-label=\"Devices\">\n <DevicePicker kind=\"audioinput\" label=\"Microphone\" onPick={onPick} />\n <div className=\"mx-meet__meter\" role=\"meter\" aria-label=\"Microphone level\" aria-valuemin={0} aria-valuemax={100} aria-valuenow={Math.round(level * 100)}>\n <span style={{ width: `${Math.min(100, Math.round(level * 160))}%` }} />\n </div>\n <DevicePicker kind=\"audiooutput\" label=\"Speaker\" onPick={onPick} />\n <button\n type=\"button\"\n className=\"mx-meet__chip\"\n disabled={testing}\n onClick={() => {\n setTesting(true);\n void playTestSound(speaker).finally(() => setTesting(false));\n }}\n >\n <SpeakerIcon width={14} height={14} /> {testing ? \"Playing…\" : \"Test speaker\"}\n </button>\n <DevicePicker kind=\"videoinput\" label=\"Camera\" onPick={onPick} />\n </section>\n );\n}\n\n/** Noise suppression and the camera background. */\nfunction EffectsSettings({ preview }: { preview?: CameraPreview | null }): ReactNode {\n const effects = useMediaEffects();\n const [error, setError] = useState<string | null>(null);\n const fileRef = useRef<HTMLInputElement | null>(null);\n // Pre-join there is no room driver yet, so the browser itself is asked.\n const supported = effects.backgroundsSupported || preview != null || videoEffectsSupported();\n\n const apply = (next: Partial<MediaEffects>) => {\n setError(null);\n void effects.set(next);\n if (preview != null) {\n const merged = { ...effects.effects, ...next };\n void preview.setEffect(toVideoEffect(merged)).catch(() =>\n setError(\"This browser could not run the background effect.\"),\n );\n }\n };\n\n return (\n <section className=\"mx-meet__section\" aria-label=\"Effects\">\n {/* Design-system controls, never native ones restyled here: the lobby\n and settings must read as the same app as every other page. */}\n <div className=\"mx-meet__check\">\n <Checkbox\n id=\"mx-meet-noise\"\n checked={effects.effects.noiseSuppression}\n onCheckedChange={(checked) => apply({ noiseSuppression: checked === true })}\n />\n <div className=\"mx-meet__check-text\">\n <Label htmlFor=\"mx-meet-noise\">Noise suppression</Label>\n <span className=\"mx-meet__hint\">Filters keyboards, fans and background chatter.</span>\n </div>\n </div>\n {supported ? (\n <div className=\"mx-meet__field\">\n <Label>Background</Label>\n <SegmentedControl\n fullWidth\n value={effects.effects.background}\n onValueChange={(kind) => {\n if (kind === \"image\") {\n fileRef.current?.click();\n return;\n }\n apply({ background: kind as MediaEffects[\"background\"] });\n }}\n data={[\n { value: \"none\", label: \"None\" },\n { value: \"blur\", label: \"Blur\" },\n { value: \"image\", label: \"Picture…\" },\n ]}\n />\n <input\n ref={fileRef}\n type=\"file\"\n accept=\"image/*\"\n hidden\n onChange={(event) => {\n const file = event.target.files?.[0];\n event.target.value = \"\";\n if (file === undefined) return;\n void shrinkImage(file)\n .then((url) => apply({ background: \"image\", backgroundImage: url }))\n .catch(() => setError(\"That picture could not be read. Try a JPEG or PNG.\"));\n }}\n />\n {error !== null ? <p className=\"mx-meet__error\">{error}</p> : null}\n </div>\n ) : null}\n </section>\n );\n}\n\nfunction toVideoEffect(effects: MediaEffects): VideoEffect {\n if (effects.background === \"blur\") return { kind: \"blur\" };\n if (effects.background === \"image\" && effects.backgroundImage !== null) {\n return { kind: \"image\", imageUrl: effects.backgroundImage };\n }\n return { kind: \"none\" };\n}\n\n/**\n * A picked background, scaled to 1280 px wide as a JPEG data URL — small enough\n * to remember in this browser (a `blob:` URL dies with the tab), large enough\n * for a 720p camera.\n */\nasync function shrinkImage(file: File): Promise<string> {\n const url = URL.createObjectURL(file);\n try {\n const image = new Image();\n image.src = url;\n await image.decode();\n const scale = Math.min(1, 1280 / image.naturalWidth);\n const canvas = document.createElement(\"canvas\");\n canvas.width = Math.round(image.naturalWidth * scale);\n canvas.height = Math.round(image.naturalHeight * scale);\n const context = canvas.getContext(\"2d\");\n if (context === null) throw new Error(\"no 2d context\");\n context.drawImage(image, 0, 0, canvas.width, canvas.height);\n return canvas.toDataURL(\"image/jpeg\", 0.85);\n } finally {\n URL.revokeObjectURL(url);\n }\n}\n\n/** A short two-tone chime through the chosen speaker. */\nasync function playTestSound(sinkId: string | null): Promise<void> {\n const Context = (globalThis as { AudioContext?: typeof AudioContext }).AudioContext;\n if (Context === undefined) return;\n const context = new Context();\n const destination = context.createMediaStreamDestination();\n const gain = context.createGain();\n gain.gain.value = 0.2;\n gain.connect(destination);\n for (const [frequency, start] of [\n [660, 0],\n [880, 0.25],\n ] as const) {\n const oscillator = context.createOscillator();\n oscillator.frequency.value = frequency;\n oscillator.connect(gain);\n oscillator.start(context.currentTime + start);\n oscillator.stop(context.currentTime + start + 0.22);\n }\n const audio = new Audio();\n audio.srcObject = destination.stream;\n const sinkable = audio as HTMLAudioElement & { setSinkId?: (id: string) => Promise<void> };\n if (sinkId !== null && sinkId !== \"default\" && sinkable.setSinkId !== undefined) {\n await sinkable.setSinkId(sinkId).catch(() => undefined);\n }\n await audio.play().catch(() => undefined);\n await new Promise((resolve) => setTimeout(resolve, 700));\n audio.pause();\n await context.close();\n}\n\n/** A microphone stream for the level meter only — closed with the component. */\nfunction useMicrophoneStream(deviceId: string | null | \"off\"): MediaStream | null {\n const [stream, setStream] = useState<MediaStream | null>(null);\n useEffect(() => {\n if (deviceId === \"off\") return undefined;\n let live = true;\n let opened: MediaStream | null = null;\n const media = (globalThis as { navigator?: Navigator }).navigator?.mediaDevices;\n if (media === undefined) return undefined;\n void media\n .getUserMedia({ audio: deviceId === null ? true : { deviceId: { exact: deviceId } } })\n .then((next) => {\n if (!live) {\n next.getTracks().forEach((track) => track.stop());\n return;\n }\n opened = next;\n setStream(next);\n })\n .catch(() => setStream(null));\n return () => {\n live = false;\n opened?.getTracks().forEach((track) => track.stop());\n setStream(null);\n };\n }, [deviceId]);\n return stream;\n}\n\n/** The waiting room. Rendered for hosts only — a participant sees nothing. */\nexport function LobbyPanel(): ReactNode {\n const lobby = useLobby();\n if (!lobby.canAdmit || lobby.waiting.length === 0) return null;\n return (\n <section className=\"mx-meet__lobby\" aria-label=\"Waiting room\">\n <header className=\"mx-meet__lobby-head\">\n <h3 className=\"mx-meet__lobby-title\">\n {lobby.waiting.length === 1\n ? \"1 person is waiting\"\n : `${lobby.waiting.length} people are waiting`}\n </h3>\n <button type=\"button\" className=\"mx-meet__link\" onClick={() => void lobby.admitAll()}>\n Admit all\n </button>\n </header>\n <ul className=\"mx-meet__lobby-list\">\n {lobby.waiting.map((entry) => (\n <li\n key={entry.identity}\n className=\"mx-meet__lobby-row\"\n data-meet-lobby-entry=\"\"\n data-meet-participant-name={entry.displayName}\n >\n <Avatar name={entry.displayName} url={entry.avatarUrl} size={32} />\n <span className=\"mx-meet__person-name\">\n {entry.displayName}\n {entry.userId === null ? <span className=\"mx-meet__badge\">Guest</span> : null}\n </span>\n <ControlButton label={`Admit ${entry.displayName}`} onClick={() => void lobby.admit(entry)}>\n <CheckIcon width={16} height={16} />\n </ControlButton>\n <ControlButton\n label={`Deny ${entry.displayName}`}\n danger\n onClick={() => void lobby.deny(entry)}\n >\n <CloseIcon width={16} height={16} />\n </ControlButton>\n </li>\n ))}\n </ul>\n </section>\n );\n}\n\n/**\n * THE NOTE-TAKER'S STATE, on screen, with the server's own words.\n *\n * 🚨 NEVER A SPINNER. Every one of the five states (D9/MRI-A2) has a sentence,\n * and a refused dispatch shows the SERVER's `reason` and `remedy` verbatim —\n * \"no worker is registered under matrx-note-taker\", and what to do about it.\n * Through 0.2.1 a failed dispatch threw and the panel showed a remedy with no\n * state behind it; a person could not tell \"it refused\" from \"it is coming\".\n */\nfunction NoteTakerRow(): ReactNode {\n const ai = useMeetAi();\n const status = ai.noteTaker;\n const duplicateError =\n status.refusal !== null &&\n ai.error !== null &&\n status.refusal.reason === ai.error.message &&\n status.refusal.remedy === ai.error.remedy;\n return (\n <section className=\"mx-meet__note-taker\" aria-label=\"Note-taker\">\n <p className={`mx-meet__note-taker-state mx-meet__note-taker-state--${status.state}`}>\n <span className=\"mx-meet__note-taker-dot\" aria-hidden=\"true\" />\n {ai.noteTakerLabel}\n </p>\n {status.detail !== null ? (\n <p className=\"mx-meet__panel-note\">{status.detail}</p>\n ) : null}\n\n {/* THE REFUSAL, VERBATIM. Not summarized, not replaced by a house\n sentence, and not hidden behind a retry that would refuse again. */}\n {status.refusal !== null ? (\n <ErrorBox as=\"div\" className=\"mx-meet__refusal\" error={status.refusal} operation=\"Join this meeting\" origin=\"meet/components\">\n <p className=\"mx-meet__refusal-reason\">{status.refusal.reason}</p>\n {status.refusal.remedy.length > 0 ? (\n <p className=\"mx-meet__refusal-remedy\">{status.refusal.remedy}</p>\n ) : null}\n </ErrorBox>\n ) : null}\n\n {ai.error !== null && !duplicateError ? (\n <ErrorBox as=\"div\" className=\"mx-meet__refusal\" error={ai.error} operation=\"Run the meeting assistant\" origin=\"meet/components\">\n <p className=\"mx-meet__refusal-reason\">{ai.error.message}</p>\n <p className=\"mx-meet__refusal-remedy\">{ai.error.remedy}</p>\n </ErrorBox>\n ) : null}\n\n {/* A control an actor cannot use is ABSENT (no dead ends). A guest and a\n non-host see the STATE and no buttons at all. */}\n {ai.canControlNoteTaker ? (\n status.state === \"transcribing\" ||\n status.state === \"joining\" ||\n status.state === \"handoff_pending\" ? (\n <button\n type=\"button\"\n className=\"mx-meet__link\"\n onClick={() => void ai.stopNoteTaker()}\n >\n Dismiss the note-taker\n </button>\n ) : (\n <button\n type=\"button\"\n className=\"mx-meet__primary\"\n onClick={() => void ai.startNoteTaker()}\n >\n Start note-taker\n </button>\n )\n ) : null}\n </section>\n );\n}\n\nexport function NoteList({\n title,\n notes,\n empty,\n}: {\n title: string;\n notes: readonly { id: string; text: string; assigneeDisplayName?: string | null }[];\n empty: string;\n}): ReactNode {\n return (\n <section className=\"mx-meet__note-group\" aria-label={title}>\n <h3 className=\"mx-meet__note-group-title\">{title}</h3>\n {notes.length === 0 ? (\n <p className=\"mx-meet__panel-note\">{empty}</p>\n ) : (\n <ul className=\"mx-meet__notes\">\n {notes.map((note) => (\n <li key={note.id} className=\"mx-meet__note\">\n {note.text}\n {/* D18: a guest never gets an `assignee_user_id`, so the name is\n read from the row's metadata — an action item whose owner has\n no name is an action item nobody owns. */}\n {note.assigneeDisplayName !== undefined &&\n note.assigneeDisplayName !== null ? (\n <span className=\"mx-meet__assignee\">{note.assigneeDisplayName}</span>\n ) : null}\n </li>\n ))}\n </ul>\n )}\n </section>\n );\n}\n\n/**\n * THE MEETING ASSISTANT (R4). The note-taker's state, what the meeting has\n * decided so far, the wrap-up, and a question box whose answer arrives token by\n * token.\n *\n * Nothing on this panel is computed here: notes, decisions and action items are\n * durable rows delivered by Postgres Changes, and the answer is streamed from\n * `/intelligence/ask`, which builds it from those same rows. The captions this\n * client can see are never sent anywhere (D16).\n */\nexport function AiPanel({ onClose }: { onClose: () => void }): ReactNode {\n const ai = useMeetAi();\n const [question, setQuestion] = useState(\"\");\n\n return (\n <PanelShell title=\"Meeting assistant\" onClose={onClose}>\n <NoteTakerRow />\n\n {ai.summary !== null ? (\n <section className=\"mx-meet__note-group\" aria-label=\"Summary\">\n <h3 className=\"mx-meet__note-group-title\">Summary</h3>\n <p className=\"mx-meet__summary\">{ai.summary.text}</p>\n </section>\n ) : null}\n\n {/*\n THE WRAP-UP, ANNOUNCED IN THE ROOM (MRI-A12). It says the pass FINISHED,\n which is the one thing the notes above cannot say for themselves — a\n panel with no summary is otherwise indistinguishable from a summary that\n is still being written. It is absent, never a placeholder, when nothing\n was announced: on the ordinary ending path the room is deleted before the\n pass runs, and the summary is read on the post-meeting record instead.\n */}\n {ai.wrapUp !== null ? (\n <p className=\"mx-meet__wrap-up-notice\" role=\"status\">\n The wrap-up is written — {ai.wrapUp.noteIds.length}{\" \"}\n {ai.wrapUp.noteIds.length === 1 ? \"entry\" : \"entries\"} from{\" \"}\n {ai.wrapUp.segmentCount}{\" \"}\n {ai.wrapUp.segmentCount === 1 ? \"transcript line\" : \"transcript lines\"}.\n </p>\n ) : null}\n\n <NoteList\n title=\"Notes\"\n notes={ai.notes}\n empty=\"Notes appear here as the meeting goes on.\"\n />\n <NoteList\n title=\"Decisions\"\n notes={ai.decisions}\n empty=\"Nothing has been decided yet.\"\n />\n <NoteList\n title=\"Action items\"\n notes={ai.actionItems}\n empty=\"No action items yet.\"\n />\n\n {ai.transcript.length > 0 ? (\n <section className=\"mx-meet__note-group\" aria-label=\"Transcript\">\n <h3 className=\"mx-meet__note-group-title\">Transcript</h3>\n <ul className=\"mx-meet__transcript\">\n {ai.transcript.map((line) => (\n <li\n key={line.id}\n className={\n line.final && line.source === \"record\"\n ? \"mx-meet__line\"\n : \"mx-meet__line mx-meet__line--interim\"\n }\n >\n <strong>{line.speaker}</strong>\n <span>{line.text}</span>\n </li>\n ))}\n </ul>\n </section>\n ) : null}\n\n <form\n className=\"mx-meet__composer\"\n onSubmit={(event) => {\n event.preventDefault();\n const asked = question;\n setQuestion(\"\");\n void ai.ask(asked);\n }}\n >\n <input\n className=\"mx-meet__input\"\n value={question}\n onChange={(event) => setQuestion(event.target.value)}\n placeholder=\"Ask about this meeting\"\n aria-label=\"Ask about this meeting\"\n />\n <button type=\"submit\" className=\"mx-meet__send\" disabled={ai.asking}>\n {ai.asking ? \"Asking…\" : \"Ask\"}\n </button>\n </form>\n\n {/* THE SERVER'S PHASE, not a spinner of our invention. */}\n {ai.asking && ai.phase !== null ? (\n <p className=\"mx-meet__panel-note\" role=\"status\">\n {ai.phase}\n </p>\n ) : null}\n {ai.answer !== null ? <p className=\"mx-meet__answer\">{ai.answer}</p> : null}\n {/* WHAT THE ANSWER WAS BUILT FROM. The server measures it; a truncated\n transcript is stated rather than quietly answered around. */}\n {ai.answer !== null && ai.coverage !== null ? (\n <p className=\"mx-meet__coverage\">\n Answered from {ai.coverage.segmentsUsed} of {ai.coverage.segmentsTotal} transcript\n lines\n {ai.coverage.truncated ? \" (the oldest were left out)\" : \"\"}.\n </p>\n ) : null}\n {/* THE REMEDY, not a shrug — and rendered by THIS hook instance, because\n `useMeetAi()` holds its own ask state per component. */}\n {ai.error !== null ? (\n <ErrorBox as=\"div\" className=\"mx-meet__refusal\" error={ai.error} operation=\"Run the meeting assistant\" origin=\"meet/components\">\n <p className=\"mx-meet__refusal-reason\">{ai.error.message}</p>\n <p className=\"mx-meet__refusal-remedy\">{ai.error.remedy}</p>\n </ErrorBox>\n ) : null}\n </PanelShell>\n );\n}\n\n/**\n * LIVE CAPTIONS. The last few lines, over the stage, for anyone who reads\n * faster than they hear.\n *\n * They are HINTS (D16) — the durable segment replaces the line it captioned as\n * soon as the row lands — so an unsettled line renders differently from a\n * settled one and nothing downstream ever reads this list.\n */\nexport function Captions({ lines = 3 }: { lines?: number }): ReactNode {\n const ai = useMeetAi();\n const visible = ai.transcript.slice(-lines);\n // Meet wave 5: a viewer who chose a language reads the translation, with the words\n // as spoken beneath it until the translation lands.\n const translation = useCaptionTranslation(visible);\n if (visible.length === 0) return null;\n return (\n <div className=\"mx-meet__captions\" aria-live=\"polite\" aria-label=\"Live captions\">\n {visible.map((line) => {\n const translated = translation.language === null ? undefined : translation.translations[line.id];\n return (\n <p\n key={line.id}\n className={\n line.final ? \"mx-meet__caption\" : \"mx-meet__caption mx-meet__caption--interim\"\n }\n >\n <strong>{line.speaker}</strong> {translated ?? line.text}\n {translated !== undefined ? <span className=\"mx-meet__caption-original\">{line.text}</span> : null}\n </p>\n );\n })}\n {translation.language !== null && translation.error !== null ? (\n <p className=\"mx-meet__caption mx-meet__caption--interim\">{translation.error}</p>\n ) : null}\n </div>\n );\n}\n\n/**\n * CONSENT IS NOT A PROP (README rule 8, D10).\n *\n * Whenever an agent participant is in the room, EVERY participant sees this —\n * host, member and guest alike. There is no prop that hides it and no host code\n * involved: through 0.2.1 the first real consumer had to write this banner\n * itself by reading `isAgent` off the roster, which is exactly the massage C22\n * forbids.\n */\nexport function ConsentNotice(): ReactNode {\n const notice = useConsentNotice();\n if (notice === null) return null;\n return (\n <p className=\"mx-meet__consent\" role=\"status\">\n {notice}\n </p>\n );\n}\n\n/**\n * LISTEN-ONLY ATTENDANCE, said out loud. Shown whenever the local participant\n * is in the room publishing no microphone — whether they chose that or the\n * browser refused. When it was refused, the browser's own reason and the remedy\n * are shown too, because \"others can't hear you\" without \"and here is why\"\n * leaves someone clicking a mute button that cannot work.\n */\nexport function AttendanceNotice(): ReactNode {\n const media = useLocalMedia();\n const notice = listenOnlyNotice(media);\n if (notice === null) return null;\n const refused = media.microphone.unavailable;\n const code = microphoneNoticeCode(media.microphone);\n return (\n <p\n className=\"mx-meet__banner mx-meet__banner--attendance\"\n role=\"status\"\n {...(code !== null ? { \"data-meet-notice\": code } : {})}\n >\n {notice}\n {refused === null ? null : <span className=\"mx-meet__remedy\">{refused.remedy}</span>}\n </p>\n );\n}\n\n/* --------------------------------------------------------------- pre-join */\n\nexport function PreJoin(props: {\n roomName: RoomName;\n meetingId?: string;\n slug?: string;\n onJoined?: () => void;\n /** HR-360 wave 2 (MD-16): join as a silent observer — no camera, no microphone. */\n observe?: boolean;\n}): ReactNode {\n // THE GUARD, not a throw. See <InertNotice>.\n const host = useMeetHost();\n if (host === null) return <InertNotice what=\"This meeting\" />;\n return <PreJoinBody {...props} host={host} />;\n}\n\nfunction PreJoinBody({\n roomName,\n meetingId,\n slug,\n onJoined,\n observe = false,\n host,\n}: {\n roomName: RoomName;\n meetingId?: string;\n slug?: string;\n onJoined?: () => void;\n observe?: boolean;\n host: MeetHost;\n}): ReactNode {\n const room = useRoom();\n const devices = useDeviceSnapshot();\n const recording = useRecording();\n const effects = useMediaEffects();\n const { notice } = useModerationNotice();\n const [mode, setMode] = useState<CallMode>(\"video\");\n // Attendance is not contingent on a device (see core/attendance.ts), so this\n // is an ordinary choice, not a gate. Unchecking it joins listen-only; leaving\n // it checked and having the browser refuse ALSO joins, listen-only, with the\n // refusal's own remedy shown in the room.\n const [microphone, setMicrophone] = useState(true);\n const [joining, setJoining] = useState(false);\n const camera = devices?.selected.videoinput ?? host.devices.resolve(\"videoinput\");\n const mic = devices?.selected.audioinput ?? host.devices.resolve(\"audioinput\");\n const preview = useCameraPreview(mode === \"video\" ? camera : undefined, effects.effects);\n const micStream = useMicrophoneStream(microphone ? mic : \"off\");\n const level = useAudioLevel(micStream);\n const [speakerTesting, setSpeakerTesting] = useState(false);\n\n useEffect(() => {\n void host.devices.refresh();\n }, [host]);\n\n return (\n <div className=\"mx-meet__prejoin\">\n <div className=\"mx-meet__preview\">\n {mode === \"video\" && preview.preview !== null ? (\n <video\n className=\"mx-meet__video mx-meet__video--mirror\"\n autoPlay\n playsInline\n muted\n ref={(element) => {\n if (element !== null) preview.preview?.attach(element);\n }}\n />\n ) : (\n <div className=\"mx-meet__tile-placeholder\">\n <Avatar name={host.identity.displayName} url={host.identity.avatarUrl} size={96} />\n </div>\n )}\n {mode === \"video\" && preview.error !== null ? (\n <p className=\"mx-meet__preview-note\">{preview.error}</p>\n ) : null}\n {microphone && micStream !== null ? (\n <div className=\"mx-meet__meter mx-meet__meter--preview\" role=\"meter\" aria-label=\"Microphone level\" aria-valuemin={0} aria-valuemax={100} aria-valuenow={Math.round(level * 100)}>\n <span style={{ width: `${Math.min(100, Math.round(level * 160))}%` }} />\n </div>\n ) : null}\n </div>\n\n <div className=\"mx-meet__prejoin-panel\">\n <h1 className=\"mx-meet__prejoin-title\">Ready to join?</h1>\n\n {notice?.kind === \"removed\" ? <p className=\"mx-meet__warning\">{notice.text}</p> : null}\n\n {devices?.labelsHidden === true ? (\n <p className=\"mx-meet__panel-note\">\n Your browser is hiding device names until you allow camera and microphone access.\n <button\n type=\"button\"\n className=\"mx-meet__link\"\n onClick={() => {\n void host.devices.requestPermission({ audio: true, video: true });\n }}\n >\n Allow access\n </button>\n </p>\n ) : null}\n\n {/* A FALLBACK ANNOUNCES ITSELF — an unplugged headset never silently\n becomes the laptop mic. */}\n {devices?.fallbackNotice != null ? (\n <p className=\"mx-meet__warning\">{devices.fallbackNotice}</p>\n ) : null}\n\n <DevicePicker kind=\"audioinput\" label=\"Microphone\" />\n <div className=\"mx-meet__field-row\">\n <DevicePicker kind=\"audiooutput\" label=\"Speaker\" />\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={speakerTesting}\n onClick={() => {\n setSpeakerTesting(true);\n void playTestSound(devices?.selected.audiooutput ?? host.devices.resolve(\"audiooutput\")).finally(\n () => setSpeakerTesting(false),\n );\n }}\n >\n <SpeakerIcon /> {speakerTesting ? \"Playing…\" : \"Test\"}\n </Button>\n </div>\n <DevicePicker kind=\"videoinput\" label=\"Camera\" />\n <EffectsSettings preview={preview.preview} />\n\n {recording.consentNotice !== null ? (\n <Alert variant=\"info\">\n <AlertDescription>{recording.consentNotice}</AlertDescription>\n </Alert>\n ) : null}\n\n <div className=\"mx-meet__prejoin-actions\">\n <div className=\"mx-meet__check\">\n <Checkbox\n id=\"mx-meet-join-video\"\n checked={mode === \"video\"}\n onCheckedChange={(checked) => setMode(checked === true ? \"video\" : \"audio\")}\n />\n <Label htmlFor=\"mx-meet-join-video\">Join with video</Label>\n </div>\n <div className=\"mx-meet__check\">\n <Checkbox\n id=\"mx-meet-join-mic\"\n checked={microphone}\n onCheckedChange={(checked) => setMicrophone(checked === true)}\n />\n <Label htmlFor=\"mx-meet-join-mic\">Join with microphone</Label>\n </div>\n <Button\n type=\"button\"\n className=\"mx-meet__join\"\n disabled={joining}\n onClick={() => {\n setJoining(true);\n // The preview hands the camera back before the room takes it.\n preview.release();\n void room\n .join({\n roomName,\n // An observer publishes nothing: no camera, no microphone (MD-16).\n mode: observe ? \"audio\" : mode,\n microphone: observe ? false : microphone,\n ...(meetingId !== undefined ? { meetingId } : {}),\n ...(slug !== undefined ? { slug } : {}),\n ...(observe ? { observe: true } : {}),\n })\n .then(() => onJoined?.())\n // The failure is ON SCREEN (room.failure, the server's sentence); an\n // unhandled rejection only added a page error nobody can act on.\n .catch(() => undefined)\n .finally(() => setJoining(false));\n }}\n >\n {joining ? \"Joining…\" : observe ? \"Observe\" : \"Join now\"}\n </Button>\n </div>\n\n {room.failure !== null ? (\n <p className=\"mx-meet__error\">\n {room.failure.message}\n <span className=\"mx-meet__remedy\">{room.failure.remedy}</span>\n </p>\n ) : null}\n </div>\n </div>\n );\n}\n\n/**\n * The pre-join camera, WITH the chosen background — the same SDK track and\n * processor the room uses. `undefined` device = camera not wanted (no preview,\n * no light). A refused camera is a sentence, never a black box.\n */\nfunction useCameraPreview(\n deviceId: string | null | undefined,\n effects: MediaEffects,\n): { preview: CameraPreview | null; error: string | null; release: () => void } {\n const [preview, setPreview] = useState<CameraPreview | null>(null);\n const [error, setError] = useState<string | null>(null);\n const current = useRef<CameraPreview | null>(null);\n const initialEffect = useRef(toVideoEffect(effects));\n initialEffect.current = toVideoEffect(effects);\n useEffect(() => {\n if (deviceId === undefined) return undefined;\n let live = true;\n setError(null);\n void createCameraPreview(deviceId, initialEffect.current)\n .then((created) => {\n if (!live) {\n created.stop();\n return;\n }\n current.current = created;\n setPreview(created);\n })\n .catch((thrown: unknown) => {\n if (!live) return;\n const failure = thrown as { message?: string; remedy?: string };\n // The person reads the sentence and its remedy — never the operation tag\n // (\"meet.cameraPreview: …\") a diagnostic carries.\n const message = (failure.message ?? \"\").replace(/^meet\\.[A-Za-z.()]+:\\s*/, \"\");\n const sentence = message.length > 0 ? message.charAt(0).toUpperCase() + message.slice(1) : \"\";\n setError([sentence, failure.remedy].filter(Boolean).join(\". \") || \"The camera did not start.\");\n });\n return () => {\n live = false;\n current.current?.stop();\n current.current = null;\n setPreview(null);\n };\n }, [deviceId]);\n return {\n preview,\n error,\n release: () => {\n current.current?.stop();\n current.current = null;\n setPreview(null);\n },\n };\n}\n\nfunction DevicePicker({\n kind,\n label,\n onPick,\n}: {\n kind: \"audioinput\" | \"videoinput\" | \"audiooutput\";\n label: string;\n /** In the room: also switch the live device. Pre-join: the choice is applied on join. */\n onPick?: (kind: \"audioinput\" | \"videoinput\" | \"audiooutput\", deviceId: string) => void;\n}): ReactNode {\n const host = useRequiredMeetHost();\n const devices = useDeviceSnapshot();\n const list = devices?.devices[kind] ?? [];\n // A browser that cannot route audio output (Safari, Firefox without the flag)\n // lists no outputs — the picker is then absent, never a select that lies.\n if (list.length === 0) return null;\n const triggerId = `mx-meet-device-${kind}`;\n // Radix Select refuses an empty-string item value; a device with no id\n // (some browsers before permission) is listed under a stable stand-in.\n const valueOf = (deviceId: string) => (deviceId.length > 0 ? deviceId : \"default\");\n const current = devices?.selected[kind] ?? host.devices.resolve(kind) ?? \"\";\n return (\n <div className=\"mx-meet__field\">\n <Label htmlFor={triggerId}>{label}</Label>\n <Select\n {...(current.length > 0 ? { value: valueOf(current) } : {})}\n onValueChange={(value) => {\n const deviceId = list.find((device) => valueOf(device.deviceId) === value)?.deviceId ?? value;\n if (onPick !== undefined) onPick(kind, deviceId);\n else host.devices.select(kind, deviceId);\n }}\n >\n <SelectTrigger id={triggerId} aria-label={label}>\n <SelectValue placeholder={label} />\n </SelectTrigger>\n <SelectContent>\n {list.map((device) => (\n <SelectItem key={device.deviceId || \"default\"} value={valueOf(device.deviceId)}>\n {device.label.length > 0 ? device.label : `${label} (name hidden)`}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n );\n}\n\n/* ----------------------------------------------------------- meeting room */\n\n/**\n * THE MEET SKIN'S ROOM (v0) — today's pre-join, waiting room and live room,\n * chosen by the store's phase. `<MeetingSkinRoot>` (in `@ai-matrx/meet/react`)\n * owns everything that is not a look: the inert guard, the observation root,\n * watching the room and the meeting, the resolved-row seed and the ended\n * meeting's record. This renders only once a runtime exists and the meeting\n * is live or joinable.\n */\nexport function MeetSkinRoom({\n roomName,\n meetingId,\n slug,\n onLeave,\n headerControls,\n observe = false,\n}: {\n roomName: RoomName;\n meetingId?: string;\n slug?: string;\n onLeave?: () => void;\n /** HR-360 wave 2 (MD-16): the pre-join joins as a silent observer. */\n observe?: boolean;\n /**\n * A HOST-PROVIDED control for the in-meeting header — rendered in the header\n * row of the live room only. The package never inspects it.\n */\n headerControls?: ReactNode;\n}): ReactNode {\n const core = useMeetCore();\n const { host, snapshot, isHost, phase } = core;\n const elapsed = useElapsed(snapshot?.meeting?.startedAt ?? null);\n if (host === null) return null;\n\n // THE PHASE → SCREEN MAP (CORE-DESIGN §3.1, §5.4): one screen per phase, exhaustive — a phase\n // added to the machine without a screen here is a compile error. Nothing the person can be in\n // renders pre-join except `prejoin` itself.\n const screen = (() => {\n if (isRefusedPhase(phase)) return \"refused\" as const;\n switch (phase) {\n case \"resolving\":\n return \"resolving\" as const;\n case \"prejoin\":\n case \"guest_name\":\n return \"prejoin\" as const;\n case \"joining\":\n case \"in_call\":\n return \"room\" as const;\n case \"waiting:host_not_started\":\n case \"waiting:knocking\":\n case \"denied\":\n case \"knock_expired\":\n return \"waiting\" as const;\n case \"left\":\n return snapshot?.waiting?.admission === \"withdrawn\" ? (\"waiting\" as const) : (\"exit\" as const);\n case \"disconnected\":\n case \"removed\":\n case \"superseded\":\n case \"inactive\":\n case \"unsupported\":\n case \"ended\":\n case \"record\":\n case \"ringing_out\":\n case \"ringing_in\":\n case \"call_declined\":\n case \"call_missed\":\n case \"call_busy\":\n case \"answered_elsewhere\":\n return \"exit\" as const;\n default: {\n const unhandled: never = phase;\n return unhandled;\n }\n }\n })();\n\n if (screen === \"resolving\") {\n return (\n <div className=\"mx-meet\">\n <p className=\"mx-meet__empty-body\" role=\"status\">Getting the meeting ready…</p>\n </div>\n );\n }\n\n if (screen === \"prejoin\") {\n return (\n <div className=\"mx-meet\">\n <PreJoin\n roomName={roomName}\n {...(meetingId !== undefined ? { meetingId } : {})}\n {...(slug !== undefined ? { slug } : {})}\n observe={observe}\n />\n </div>\n );\n }\n\n if (screen === \"waiting\") {\n return (\n <div className=\"mx-meet mx-meet--lobby\">\n <WaitingRoom host={host} {...(onLeave !== undefined ? { onLeave } : {})} />\n </div>\n );\n }\n\n if (screen === \"refused\" || screen === \"exit\") {\n return (\n <div className=\"mx-meet mx-meet--lobby\">\n <ExitScreen {...(onLeave !== undefined ? { onLeave } : {})} />\n </div>\n );\n }\n\n return (\n <div className=\"mx-meet\">\n <ConsentNotice />\n <AttendanceNotice />\n <header className=\"mx-meet__header\">\n <h1 className=\"mx-meet__title\">{snapshot?.meeting?.title ?? \"Meeting\"}</h1>\n <span className=\"mx-meet__meta\">\n {participantSummary(snapshot?.participants ?? [])}\n {elapsed > 0 ? ` · ${formatDurationMs(elapsed)}` : \"\"}\n </span>\n <RecordingIndicator />\n {captureHeldOf(snapshot?.meeting) ? (\n <span className=\"mx-meet__badge\" data-meet-capture-held=\"\">\n Confidential · not recorded\n </span>\n ) : null}\n {isObserverRole(snapshot?.grants.role) ? (\n <span className=\"mx-meet__badge\" data-meet-observing=\"self\">Observing</span>\n ) : null}\n {snapshot?.locked === true ? (\n <span className=\"mx-meet__badge\">\n <LockIcon width={12} height={12} /> Locked\n </span>\n ) : null}\n {headerControls !== undefined && headerControls !== null ? (\n <span className=\"mx-meet__header-controls\" data-meet-header-controls=\"\">\n {headerControls}\n </span>\n ) : null}\n {isHost ? <HostMenu /> : null}\n </header>\n\n {/* A reconnect is VISIBLE. A frozen tile with no explanation is\n indistinguishable from a crashed app. */}\n {core.connection !== \"stable\" ? (\n <p className=\"mx-meet__banner\" role=\"status\">\n Reconnecting…\n </p>\n ) : null}\n <ShareNotice />\n\n <Stage />\n <Captions />\n <ControlBar {...(onLeave !== undefined ? { onLeave } : {})} />\n </div>\n );\n}\n\n/**\n * A REFUSED SHARE IS A NOTICE, NEVER AN EXIT (CORE-DESIGN §3.3 SA). It stays until the person\n * dismisses it or shares again; the call carries on underneath.\n */\nfunction ShareNotice(): ReactNode {\n const { share, actions } = useMeetCore();\n if (share.state !== \"refused\") return null;\n const text =\n share.reason === \"unsupported\"\n ? \"You can't share your screen from this browser\"\n : share.reason === \"blocked\"\n ? \"Screen sharing is blocked\"\n : \"Couldn't start sharing your screen\";\n return (\n <p className=\"mx-meet__banner\" role=\"status\" data-meet-notice=\"share-unavailable\">\n {text}\n {share.remedy !== null ? <span className=\"mx-meet__remedy\">{share.remedy}</span> : null}\n <button type=\"button\" className=\"mx-meet__link\" onClick={actions.dismissShareNotice}>\n Dismiss\n </button>\n </p>\n );\n}\n\n/** The words for each refusal and exit reason (the skin owns every string, §5.4). */\nconst REFUSED_TITLES: Record<string, string> = {\n not_found: \"Check your meeting code\",\n cancelled: \"This meeting was cancelled\",\n expired: \"This meeting link has expired\",\n too_early: \"This meeting hasn't started yet\",\n sign_in_required: \"Sign in to join this meeting\",\n wrong_account: \"You're signed in with a different account\",\n not_invited: \"You aren't invited to this meeting\",\n locked: \"This meeting is locked\",\n removed: \"You can't rejoin this meeting\",\n lobby_rejects: \"You can't ask to join this meeting\",\n meeting_full: \"This meeting is full\",\n platform_capacity: \"Meetings are busy right now\",\n verification_required: \"Verify your account to join\",\n registration_required: \"Register to join this meeting\",\n};\n\n/**\n * EVERY WAY OUT OF THE ROOM, SAID PLAINLY (CORE-DESIGN §3.1, §3.4): left, disconnected, removed,\n * replaced, inactive, ended, refused. Each names what happened and offers only what the rules\n * allow — Rejoin where the machine allows it, never a silent pre-join.\n */\nfunction ExitScreen({ onLeave }: { onLeave?: () => void }): ReactNode {\n const { phase, reason, host, snapshot, actions, call } = useMeetCore();\n const [busy, setBusy] = useState(false);\n const failure = snapshot?.failure ?? null;\n const rejoin = (\n <Button\n type=\"button\"\n disabled={busy}\n onClick={() => {\n setBusy(true);\n void actions\n .rejoin()\n .catch(() => undefined)\n .finally(() => setBusy(false));\n }}\n >\n Rejoin\n </Button>\n );\n const home = (\n <a\n className=\"mx-meet__link\"\n href=\"/\"\n onClick={(event) => {\n if (call !== null) {\n event.preventDefault();\n actions.dismissCall();\n }\n if (onLeave !== undefined) {\n event.preventDefault();\n onLeave();\n }\n }}\n >\n Go home\n </a>\n );\n let title: string;\n let line: string | null = null;\n let notice: string | null = null;\n let actionsNode: ReactNode = home;\n if (phase.startsWith(\"refused:\")) {\n const code = phase.slice(\"refused:\".length);\n title = REFUSED_TITLES[code] ?? \"You can't join this meeting\";\n line = failure?.remedy ?? null;\n actionsNode = (\n <>\n <Button type=\"button\" variant=\"outline\" onClick={actions.retry}>\n Try again\n </Button>\n {home}\n </>\n );\n } else {\n switch (phase) {\n case \"left\":\n title = \"You left the meeting\";\n actionsNode = (\n <>\n {rejoin}\n {home}\n </>\n );\n break;\n case \"disconnected\":\n title = \"You lost your connection to the meeting\";\n line = failure?.remedy ?? \"Check your internet connection, then rejoin.\";\n notice = \"disconnected\";\n actionsNode = (\n <>\n {rejoin}\n {home}\n </>\n );\n break;\n case \"removed\":\n title = \"You've been removed from the meeting\";\n notice = \"removed\";\n break;\n case \"superseded\":\n title = \"You joined from another tab or device\";\n line = \"This screen left the meeting so you aren't in it twice.\";\n notice = \"displaced\";\n actionsNode = (\n <>\n {rejoin}\n {home}\n </>\n );\n break;\n case \"inactive\":\n title = \"You left the meeting because this tab was inactive\";\n actionsNode = (\n <>\n {rejoin}\n {home}\n </>\n );\n break;\n case \"unsupported\":\n title = \"This browser can't join meetings\";\n line = \"Open the link in Chrome, Edge, Firefox or Safari.\";\n break;\n case \"ended\":\n case \"record\":\n title = reason === \"time_limit\" ? \"The meeting reached its time limit\" : \"The meeting has ended\";\n notice = \"meeting-ended\";\n break;\n default:\n title = \"The call has ended\";\n break;\n }\n }\n return (\n <div className=\"mx-meet__waiting\" data-meet-exit={phase}>\n <Avatar name={host?.identity.displayName ?? \"\"} url={host?.identity.avatarUrl ?? null} size={72} />\n <h1 className=\"mx-meet__prejoin-title\">{title}</h1>\n {line !== null ? <p className=\"mx-meet__empty-body\">{line}</p> : null}\n {notice !== null ? (\n <span role=\"status\" data-meet-notice={notice} className=\"mx-meet__sr-only\">\n {title}\n </span>\n ) : null}\n <div className=\"mx-meet__waiting-actions\">{actionsNode}</div>\n </div>\n );\n}\n\n/**\n * THE WAITING SCREENS (S4). Every one is the SERVER's word on this person's row — read by the\n * core's state read, never a broadcast: waiting for the host, knocking, declined, no answer,\n * left the waiting room, ended. Controls appear only when the rules allow them.\n */\nfunction WaitingRoom({ host, onLeave }: { host: MeetHost; onLeave?: () => void }): ReactNode {\n const { waiting, withdraw, askAgain } = useWaiting();\n const { phaseNotice } = useMeetCore();\n const [busy, setBusy] = useState(false);\n const admission = waiting?.admission ?? \"knocking\";\n const run = (action: () => Promise<void>) => () => {\n setBusy(true);\n void action()\n .catch(() => undefined)\n .finally(() => setBusy(false));\n };\n const home = (\n <Button type=\"button\" variant=\"outline\" onClick={() => (onLeave !== undefined ? onLeave() : window.location.assign(\"/\"))}>\n Back to home\n </Button>\n );\n\n let title: string;\n let line: string | null = null;\n let actions: ReactNode;\n switch (admission) {\n case \"waiting_host\":\n title = \"Waiting for the host to start\";\n actions = (\n <Button type=\"button\" variant=\"outline\" disabled={busy} onClick={run(withdraw)}>\n Leave\n </Button>\n );\n break;\n case \"denied\":\n title = \"The host declined your request\";\n actions = (\n <>\n {waiting?.reknockAllowed === true ? (\n <Button type=\"button\" disabled={busy} onClick={run(askAgain)}>\n Ask to join again\n </Button>\n ) : null}\n {home}\n </>\n );\n break;\n case \"expired\":\n title = \"No one responded\";\n actions = (\n <>\n <Button type=\"button\" disabled={busy} onClick={run(askAgain)}>\n Ask again\n </Button>\n {home}\n </>\n );\n break;\n case \"withdrawn\":\n title = \"You left the waiting room\";\n actions = (\n <>\n <Button type=\"button\" disabled={busy} onClick={run(askAgain)}>\n Rejoin\n </Button>\n {home}\n </>\n );\n break;\n case \"ended\":\n title = \"The meeting has ended\";\n actions = home;\n break;\n default:\n title = \"Waiting for the host to let you in\";\n line = waiting?.admittersPresent === false ? \"The host isn't here yet\" : null;\n actions = (\n <Button type=\"button\" variant=\"outline\" disabled={busy} onClick={run(withdraw)}>\n Cancel request\n </Button>\n );\n }\n\n return (\n <div className=\"mx-meet__waiting\">\n <Avatar name={host.identity.displayName} url={host.identity.avatarUrl} size={72} />\n <h1 className=\"mx-meet__prejoin-title\">{title}</h1>\n {phaseNotice === \"sent-to-waiting\" ? (\n <p className=\"mx-meet__empty-body\" role=\"status\" data-meet-notice=\"sent-to-waiting\">\n The host moved you back to the waiting room\n </p>\n ) : null}\n {line !== null ? (\n <p className=\"mx-meet__empty-body\" role=\"status\" data-meet-notice=\"host-absent\">\n {line}\n </p>\n ) : null}\n <div className=\"mx-meet__waiting-actions\">{actions}</div>\n </div>\n );\n}\n\nexport function HostMenu(): ReactNode {\n const host = useRequiredMeetHost();\n const snapshot = useMeetSnapshot();\n const end = useEndMeeting();\n // ENDING A MEETING IS DESTRUCTIVE AND IRREVERSIBLE, so the click that does it\n // names the consequence first — inline, never `window.confirm` (no browser\n // dialogs).\n const [confirmingEnd, setConfirmingEnd] = useState(false);\n const locked = snapshot?.locked === true;\n return (\n <div className=\"mx-meet__host-menu\">\n <button\n type=\"button\"\n className=\"mx-meet__link\"\n onClick={() => {\n void host.meetings.setLock(!locked);\n }}\n >\n {locked ? \"Unlock meeting\" : \"Lock meeting\"}\n </button>\n {/* ENDING IT IS A HOST POWER AND A SERVER DECISION (MRI-A6). The button\n is real; a server that does not serve the route yet earns a SENTENCE\n rather than a dead control or a silent no-op. */}\n {end.canEnd ? (\n confirmingEnd ? (\n <span className=\"mx-meet__confirm\" role=\"group\" aria-label=\"End this meeting?\">\n <span className=\"mx-meet__confirm-text\">\n End it for everyone? Every participant is disconnected, the note-taker stops, and the\n wrap-up is written. Nobody can rejoin this meeting afterwards.\n </span>\n <button\n type=\"button\"\n className=\"mx-meet__link mx-meet__link--danger\"\n onClick={() => {\n setConfirmingEnd(false);\n void end.end();\n }}\n >\n End it\n </button>\n <button\n type=\"button\"\n className=\"mx-meet__link\"\n onClick={() => setConfirmingEnd(false)}\n >\n Keep it running\n </button>\n </span>\n ) : (\n <button\n type=\"button\"\n className=\"mx-meet__link mx-meet__link--danger\"\n onClick={() => setConfirmingEnd(true)}\n >\n {end.ending ? \"Ending…\" : \"End meeting for everyone\"}\n </button>\n )\n ) : null}\n {end.notice !== null ? (\n <span className=\"mx-meet__notice\" role=\"status\">\n {end.notice}\n </span>\n ) : null}\n </div>\n );\n}\n\n/** Reactions floating over the stage. Expired by the store, not by hope. */\nexport function ReactionOverlay({\n reactions,\n}: {\n reactions: readonly { id: string; emoji: string }[];\n}): ReactNode {\n if (reactions.length === 0) return null;\n return (\n <div className=\"mx-meet__reactions\" aria-hidden=\"true\">\n {reactions.map((reaction) => (\n <span key={reaction.id} className=\"mx-meet__reaction\">\n {reaction.emoji}\n </span>\n ))}\n </div>\n );\n}\n\nexport type { ParticipantIdentity };\n\n/** `10:35` — the clock a person reads beside a line of transcript. */\nexport function clockOf(iso: string): string {\n const at = new Date(iso);\n if (Number.isNaN(at.getTime())) return \"\";\n return at.toLocaleTimeString(undefined, { hour: \"numeric\", minute: \"2-digit\" });\n}\n\n/**\n * WHAT GUESTS MAY READ OF THIS MEETING — the host's control (MRF-1).\n *\n * 🚨 **HOST-ONLY, AND ABSENT FOR EVERYONE ELSE.** Not greyed out, not present\n * with a tooltip: a participant has no control here at all, because a control\n * that exists only to be refused is the dead-end class this package does not\n * ship (D11-style honesty, same posture as `PersonHostActions`).\n *\n * It is the third rung of the ladder Arman ruled on 2026-09-12 — the system's\n * default, the organization's setting, and this meeting's. \"Use my\n * organization's setting\" is shown WITH what that resolves to, because a label\n * that does not say what the organization chose makes the host guess about\n * other people's words.\n */\nexport function GuestRecordAccessControl({\n meeting,\n}: {\n meeting: MeetingRecord;\n}): ReactNode {\n const host = useMeetHost();\n const inRoomHost = useIsHost();\n const access = useGuestRecordAccess(meeting);\n const viewerId = host?.identity.userId ?? null;\n // The named host of the row, or a host of the room this tick. A co-host who\n // holds only an `iam` grant and is not in the room does not see the control;\n // the server would allow them, so this is the narrow side on purpose — a\n // missing control is honest, a control that lies about who may click it is not.\n const isHost = inRoomHost || (viewerId !== null && meeting.hostUserId === viewerId);\n if (host === null || !isHost) return null;\n\n const resolved =\n access.setting === \"default\" && access.effective !== null\n ? ` Right now that means: ${guestRecordAccessLabel(access.effective.level).toLowerCase()}.`\n : \"\";\n\n return (\n <section className=\"mx-meet__record-section\" aria-label=\"What guests may read\">\n <h2 className=\"mx-meet__record-section-title\">What guests may read</h2>\n <label className=\"mx-meet__field\">\n <span className=\"mx-meet__field-label\">\n People who attended by link, without an account\n </span>\n <select\n className=\"mx-meet__select\"\n value={access.setting}\n disabled={access.saving}\n onChange={(event) => {\n void access.choose(\n event.target.value as (typeof GUEST_RECORD_ACCESS_CHOICES)[number],\n );\n }}\n >\n {GUEST_RECORD_ACCESS_CHOICES.map((choice) => (\n <option key={choice} value={choice}>\n {guestRecordAccessLabel(choice)}\n </option>\n ))}\n </select>\n </label>\n <p className=\"mx-meet__panel-note\">\n {guestRecordAccessExplanation(access.setting)}\n {resolved}\n </p>\n {access.saving ? (\n <p className=\"mx-meet__panel-note\" role=\"status\">\n Saving what guests may read…\n </p>\n ) : null}\n {access.error !== null ? (\n <ErrorBox as=\"div\" className=\"mx-meet__refusal\" error={access.error} operation=\"Change who can join\" origin=\"meet/components\">\n <p className=\"mx-meet__refusal-reason\">{access.error.message}</p>\n <p className=\"mx-meet__refusal-remedy\">{access.error.remedy}</p>\n </ErrorBox>\n ) : null}\n </section>\n );\n}\n","/**\n * THE SKIN FACADE — the ONLY module a skin may import (CORE-DESIGN §5.1, §5.4;\n * step S1). Guard: `scripts/check-skin-imports.mjs` (in `check:package` and\n * `pnpm test`): a file under `src/skins/` may import React, the design system,\n * files inside its own skin folder, and THIS module — nothing else.\n *\n * `useMeetCore()` is the facade over today's store: the runtime, the room\n * snapshot, devices, the server's role and the observation-contract reading,\n * as one object that changes only when one of them does. The re-exports below\n * are the headless hooks, pure helpers and types Meet skin v0 (today's in-call\n * UI, moved here unchanged) reads. Later steps (S6a+) replace the re-exports\n * with the `CoreSnapshot` slices and typed actions; a skin then reads\n * `useMeetCore()` alone.\n */\n\nimport { useMemo } from \"react\";\nimport type { MeetPhase } from \"../core/phase\";\nimport type { JoinRequest } from \"../core/room\";\nimport type { ConnectionRegion, ShareRegion } from \"../core/store\";\nimport type { CallInvite } from \"../core/types\";\nimport type { DeviceSnapshot } from \"../core/devices\";\nimport type { MeetObservation } from \"../core/observation\";\nimport { NO_SHARE, type MeetSnapshot } from \"../core/store\";\nimport type { ParticipantRole } from \"../core/types\";\nimport { useCoreObservation } from \"./root\";\nimport { useDeviceSnapshot, useIsHost, useMeetHost, useMeetSnapshot, useRole, type MeetHost } from \"./provider\";\n\nexport interface MeetCore {\n /** The runtime, or null while auth/organization resolve (render nothing). */\n readonly host: MeetHost | null;\n readonly snapshot: MeetSnapshot | null;\n readonly devices: DeviceSnapshot | null;\n /** The server's word on this person's role (both carriers, MRI-A11). */\n readonly role: ParticipantRole;\n readonly isHost: boolean;\n /** What the observation contract reads for this tab (S0). */\n readonly observation: MeetObservation;\n /**\n * WHERE THIS TAB IS (CORE-DESIGN §3.1) — the contract's phase value, verbatim. A skin renders\n * one screen per phase (exhaustive) and NEVER decides a phase itself: every move goes through\n * `actions`, and the machine refuses any move its table does not allow.\n */\n readonly phase: MeetPhase;\n /** `data-meet-reason` of a refused / terminal phase. */\n readonly reason: string | null;\n /** A notice that came with the phase (`sent-to-waiting`), or null. */\n readonly phaseNotice: string | null;\n /** The `connection` region (reconnecting is here, never a phase). */\n readonly connection: ConnectionRegion;\n /** The `share` region (a refused share is a notice, never a phase). */\n readonly share: ShareRegion;\n /** The 1:1 call this tab is placing or answering (§3.6), or null. */\n readonly call: { readonly invite: CallInvite; readonly direction: \"outgoing\" | \"incoming\" } | null;\n /** The typed actions. None can move the phase on failure (§5.1). */\n readonly actions: MeetCoreActions;\n}\n\nexport interface MeetCoreActions {\n /** Join from pre-join (the person's press). */\n join(request: JoinRequest): Promise<void>;\n /** Rejoin from `left` / `disconnected` / `superseded` / `inactive` (the person's press). */\n rejoin(): Promise<void>;\n leave(): Promise<void>;\n /** Back to the device check from `left` / `disconnected` (the person's press). */\n backToPrejoin(): void;\n /** Try again on a refusal: the link is looked up again (`refused:*` → `resolving`). */\n retry(): void;\n /** The link resolved and the meeting screen mounted (`resolving` → `prejoin`). */\n resolved(): void;\n dismissShareNotice(): void;\n /** Close a finished call's screen (`call_*` / `left` after a call → `resolving`). */\n dismissCall(): void;\n}\n\nconst NO_ACTIONS: MeetCoreActions = {\n join: async () => undefined,\n rejoin: async () => undefined,\n leave: async () => undefined,\n backToPrejoin: () => undefined,\n retry: () => undefined,\n resolved: () => undefined,\n dismissShareNotice: () => undefined,\n dismissCall: () => undefined,\n};\n\nfunction actionsFor(host: MeetHost | null): MeetCoreActions {\n if (host === null) return NO_ACTIONS;\n const { engine, store } = host;\n return {\n join: (request) => engine.join(request),\n rejoin: () => engine.rejoin(),\n leave: () => engine.leave(),\n backToPrejoin: () => engine.backToPrejoin(),\n retry: () => {\n store.dispatch({ type: \"retry\", click: true });\n },\n resolved: () => {\n store.dispatch({ type: \"resolved\" });\n },\n dismissShareNotice: () => engine.dismissShareNotice(),\n dismissCall: () => {\n if (store.dispatch({ type: \"reset\" })) store.patch({ call: null });\n },\n };\n}\n\n/** The one headless read a skin starts from. Stable until something moves. */\nexport function useMeetCore(): MeetCore {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const devices = useDeviceSnapshot();\n const role = useRole();\n const isHost = useIsHost();\n const observation = useCoreObservation();\n const actions = useMemo(() => actionsFor(host), [host]);\n return useMemo(\n () => ({\n host,\n snapshot,\n devices,\n role,\n isHost,\n observation,\n phase: snapshot?.phase ?? \"resolving\",\n reason: snapshot?.reason ?? null,\n phaseNotice: snapshot?.phaseNotice ?? null,\n connection: snapshot?.connection ?? \"stable\",\n share: snapshot?.share ?? NO_SHARE,\n call: snapshot?.call ?? null,\n actions,\n }),\n [host, snapshot, devices, role, isHost, observation, actions],\n );\n}\n\nexport { avatarPaletteIndex, formatDurationMs, getInitials } from \"@ai-matrx/kit/format\";\nexport { fileDownloadPath } from \"../core/api\";\nexport type { MeetFileMedia } from \"../core/api\";\nexport { displayNameFor } from \"../core/names\";\nexport { uniqueAttendees } from \"../core/after\";\nexport { participantSummary } from \"../core/format\";\nexport { gridColumns } from \"../core/layout\";\nexport { meetingLink } from \"../core/meetings\";\nexport { asMeetingId } from \"../core/types\";\nexport type { CallInvite, CallMode, CallablePerson, MeetParticipant, MeetingKind, MeetingRecord, ParticipantIdentity, ParticipantRole, ParticipantStanding, RoomName } from \"../core/types\";\nexport { isLinkFollower, isObserverRole } from \"../core/types\";\n// HR-360 wave 2 (MD-15): the app panel slot.\nexport { useMeetAppPanel } from \"./app-panel\";\nexport { captureHeldOf } from \"../core/app-panel\";\nexport { CAPTION_LANGUAGES, assignedRoom, autoAssign, breakoutRemainingMs, defaultCaptionLanguage } from \"../core/collab\";\nexport type { CollabPanel, CollabSnapshot, MeetBreakoutPlan, MeetPoll, MeetQuestion, MeetStroke } from \"../core/collab\";\nexport { useCalls, useCanModerate, useConsentNotice, useElapsed, useEndMeeting, useHostControls, useLobby, useWaiting, useLocalMedia, useMeetAi, useGuestRecordAccess, useMeetingRecord, useMeetings, useRecording, useRoom, useStage, useTrack, useAudioLevel, useHandQueue, useMediaEffects, useModerationNotice, useMeetShortcuts, useReactions, MEET_SHORTCUTS } from \"./hooks\";\nexport type { UseHostControlsResult, UseWaitingResult } from \"./hooks\";\nexport { createCameraPreview, videoEffectsSupported } from \"../core/livekit-driver\";\nexport type { CameraPreview } from \"../core/livekit-driver\";\nexport type { VideoEffect } from \"../core/room-driver\";\nexport type { MediaEffects, MeetWaiting, RoomChatMessage } from \"../core/store\";\nexport { listenOnlyNotice } from \"../core/attendance\";\nexport { groupTranscript, guestRecordAccessExplanation, guestRecordAccessLabel, GUEST_RECORD_ACCESS_CHOICES, hasEnded, meetingWhen, playableRecordingFileId, recordingNotice, absenceSentence, REFUSED_ABSENCE, summarizeMeetingRecordRestrictions, wasRefused, wrapUpStatus } from \"../core/record\";\nexport { isNoteTaker } from \"../core/note-taker\";\nexport { microphoneNoticeCode, participantAttributes } from \"../core/observation\";\nexport { useDeviceSnapshot, useIsHost, useMeetHost, useMeetSnapshot, useRequiredMeetHost, useRole } from \"./provider\";\nexport type { MeetHost } from \"./provider\";\nexport type { CollabRequest } from \"../core/in-room\";\nexport { setCaptionLanguage, takeMoveRequest } from \"../core/collab-state\";\nexport {\n useActivityAttention,\n useAvailableActivities,\n useCaptionTranslation,\n useCollab,\n useCollabPanelRequest,\n} from \"./collab-hooks\";\nexport type { UseCollabResult } from \"./collab-hooks\";\nexport { MEET_REACTIONS } from \"../core/reactions\";\n// S6a: the phase machine's vocabulary, for the skin's exhaustive phase → screen map.\nexport type { MeetPhase } from \"../core/phase\";\nexport { isRefusedPhase } from \"../core/phase\";\nexport type { ConnectionRegion, ShareRegion } from \"../core/store\";\n","/**\n * WHO SAID THAT — one answer, every surface, both lanes (MRI-A16).\n *\n * 🚨 **A RAW LIVEKIT IDENTITY IS NEVER A PERSON'S NAME.** On production\n * 2026-09-09 the host's caption rail read *\"Ada Lovelace: I planted a marigold\n * in the courtyard\"* and the guest's — the same meeting, the same second —\n * read *\"guest:c8-spk-1\"*, because the host resolved the name from the durable\n * rows it can read and the guest had no fallback at all and printed the token\n * (MRI-C8). Two surfaces disagreeing about who is speaking is worse than either\n * one being ugly: a person reading the transcript cannot tell whether those are\n * one speaker or two.\n *\n * The cure is that nothing resolves a name for itself. Every source that\n * happens to KNOW a name teaches this book — the LiveKit roster, a durable\n * `meet_transcript_segments` row, a durable `meet_participants` row, a chat\n * message, our own identity — and every surface asks it. Three rules:\n *\n * 1. **A name is learned, never unlearned.** Someone who leaves the room keeps\n * the name they had; a caption that lands after them still says who spoke.\n * This is the \"last known name\" rule, and it is why the book is a store\n * field rather than a derivation over the current roster.\n * 2. **An identity is not a name.** `learn` refuses a \"name\" that is the\n * identity itself — which is exactly what the LiveKit roster hands back for\n * a participant whose token carried no name, and exactly how the token got\n * onto the screen in the first place.\n * 3. **When nobody knows, SAY nobody knows.** `displayNameFor` answers with an\n * honest placeholder — \"Unnamed guest\" / \"Unnamed participant\" — never a\n * token, and never an invented name. The placeholder is deliberately not\n * unique, so surfaces that must keep two unnamed people apart group by\n * IDENTITY rather than by the rendered name.\n */\n\nimport type { ParticipantIdentity } from \"./types\";\n\n/** Identity → the best display name anything has ever known for it. */\nexport type NameBook = Readonly<Record<string, string>>;\n\n/** A guest's identity is minted `guest:<something>` by the token route. */\nexport function isGuestIdentity(identity: ParticipantIdentity | string): boolean {\n return String(identity).startsWith(\"guest:\");\n}\n\n/**\n * An AGENT's identity is minted `agent:<capability>:<meeting>` by the server\n * (`services/meet/worker.py`). An agent is never an anonymous \"participant\":\n * it is a product with a name, and a room that cannot say which product is\n * listening has failed the consent notice it is required to carry (D10).\n */\nexport function isAgentIdentity(identity: ParticipantIdentity | string): boolean {\n return String(identity).startsWith(\"agent:\");\n}\n\n/** The note-taker's product name — `services/meet/worker.py::AGENT_DISPLAY_NAME`. */\nexport const NOTE_TAKER_PRODUCT_NAME = \"Matrx note-taker\";\n/** Any other Matrx agent whose dispatch carried no name. */\nexport const AGENT_PRODUCT_NAME = \"Matrx agent\";\n/** Somebody in the room whose name nothing has ever known. */\nexport const UNNAMED_GUEST = \"Unnamed guest\";\n/** The same, for a participant whose identity is not a guest token. */\nexport const UNNAMED_PARTICIPANT = \"Unnamed participant\";\n\n/**\n * The strings THIS MODULE invents when nobody knows a name. They are answers,\n * never facts: `isRealName` refuses them, so a placeholder can never be learned\n * as somebody's name and can never outrank a real name that arrives later.\n *\n * 🚨 This is what makes the roster safe to route through the book. A roster\n * event projects \"Unnamed guest\" for a nameless participant; a durable\n * transcript row then teaches \"Ada Lovelace\"; without this rule the placeholder\n * — a perfectly ordinary-looking string — would win, and the screen would keep\n * saying \"Unnamed guest\" about somebody the meeting plainly knows.\n */\nconst PLACEHOLDER_NAMES: readonly string[] = [\n NOTE_TAKER_PRODUCT_NAME,\n AGENT_PRODUCT_NAME,\n UNNAMED_GUEST,\n UNNAMED_PARTICIPANT,\n];\n\n/** Is this string one of the placeholders this module produces? */\nexport function isPlaceholderName(name: string | null | undefined): boolean {\n return typeof name === \"string\" && PLACEHOLDER_NAMES.includes(name.trim());\n}\n\n/** What an identity is called when NOTHING knows a name for it. Never a token. */\nexport function placeholderNameFor(identity: ParticipantIdentity | string): string {\n const key = String(identity);\n if (isAgentIdentity(key)) {\n return key.startsWith(\"agent:note_taker:\") || key.startsWith(\"agent:note-taker\")\n ? NOTE_TAKER_PRODUCT_NAME\n : AGENT_PRODUCT_NAME;\n }\n return isGuestIdentity(key) ? UNNAMED_GUEST : UNNAMED_PARTICIPANT;\n}\n\n/**\n * Would this string be a real name for this identity? A name equal to the\n * identity is the roster's way of saying it has none.\n */\nexport function isRealName(\n identity: ParticipantIdentity | string,\n name: string | null | undefined,\n): boolean {\n if (typeof name !== \"string\") return false;\n const trimmed = name.trim();\n if (trimmed.length === 0) return false;\n if (trimmed === String(identity)) return false;\n // Our own answer is not new information. See PLACEHOLDER_NAMES.\n if (isPlaceholderName(trimmed)) return false;\n return true;\n}\n\n/**\n * Teach the book one identity's name. Returns the SAME book when nothing was\n * learned, so a store commit can be skipped rather than waking every subscriber\n * on every caption.\n */\nexport function learnName(\n book: NameBook,\n identity: ParticipantIdentity | string,\n name: string | null | undefined,\n): NameBook {\n const key = String(identity);\n if (key.length === 0) return book;\n if (!isRealName(key, name)) return book;\n const learned = (name as string).trim();\n if (book[key] === learned) return book;\n return { ...book, [key]: learned };\n}\n\n/** Teach the book several at once — a roster replace, or a durable read. */\nexport function learnNames(\n book: NameBook,\n entries: readonly {\n identity: ParticipantIdentity | string;\n displayName?: string | null | undefined;\n }[],\n): NameBook {\n let next = book;\n for (const entry of entries) next = learnName(next, entry.identity, entry.displayName);\n return next;\n}\n\n/**\n * The name a surface renders for `identity`. Never a raw token, never invented.\n *\n * `preferred` is what the arriving thing itself claimed (a caption's roster\n * lookup, a durable row's `speaker` column); it wins when it is a real name,\n * because it is the freshest, and teaches the book on its way past.\n */\nexport function displayNameFor(\n book: NameBook,\n identity: ParticipantIdentity | string,\n preferred?: string | null | undefined,\n): string {\n const key = String(identity);\n if (isRealName(key, preferred)) return (preferred as string).trim();\n const known = book[key];\n if (known !== undefined) return known;\n // NOBODY KNOWS, AND THAT IS WHAT IT SAYS. A person reading this can tell the\n // difference between \"someone whose name we never learned\" and a name.\n return placeholderNameFor(key);\n}\n","/**\n * ONE error classification for the whole package (C22).\n *\n * The banned alternative is a host catching a LiveKit or PostgREST error and\n * deciding for itself whether the camera is busy, the token expired, or the\n * meeting is locked — which is how the same five-branch `if` gets copy-pasted\n * into every consumer and drifts. Everything this package throws is a\n * `MeetError` with a stable `code` and a `remedy` SENTENCE. Nothing fails\n * silently, and nothing fails without saying what to do about it.\n *\n * The device codes exist because \"it doesn't work\" in a video product is almost\n * always one of four specific, differently-remediable things: permission\n * denied, no such device, device already in use by another app, or insecure\n * context (http, where `getUserMedia` does not exist at all). A single\n * \"media-error\" would make the UI say the wrong sentence three times out of\n * four.\n */\n\nexport type MeetErrorCode =\n | \"session-unavailable\"\n | \"forbidden\"\n | \"not-found\"\n | \"conflict\"\n | \"token\"\n | \"transport\"\n | \"invalid-response\"\n | \"misconfigured\"\n | \"permission-denied\"\n | \"device-not-found\"\n | \"device-in-use\"\n | \"insecure-context\"\n | \"media-unsupported\"\n | \"room-locked\"\n | \"lobby-denied\"\n | \"capacity\"\n /** Meet wave 1: the meeting was cancelled — its link resolves, its room never opens. */\n | \"cancelled\"\n | \"unknown\";\n\nexport class MeetError extends Error {\n readonly code: MeetErrorCode;\n /** What to DO. Rendered to the user for the device codes. */\n readonly remedy: string;\n override readonly cause?: unknown;\n /**\n * The join gate's reason code (CORE-DESIGN §2.5) when the server named one: `not_found`,\n * `cancelled`, `ended`, `locked`, `removed`, `lobby_rejects`, `not_invited`… The phase\n * renders `refused:<reason>` (or `ended`) from it — never from the HTTP status.\n */\n readonly reason: string | null;\n\n constructor(\n code: MeetErrorCode,\n message: string,\n remedy: string,\n cause?: unknown,\n reason?: string | null,\n ) {\n super(message);\n this.name = \"MeetError\";\n this.code = code;\n this.remedy = remedy;\n this.reason = reason ?? null;\n if (cause !== undefined) this.cause = cause;\n }\n\n /** True when retrying after the host re-establishes a session is the fix. */\n get isRetryable(): boolean {\n return (\n this.code === \"session-unavailable\" ||\n this.code === \"transport\" ||\n this.code === \"token\"\n );\n }\n\n /** True when the user, not the code, must act — the UI shows `remedy`. */\n get isUserActionable(): boolean {\n return (\n this.code === \"permission-denied\" ||\n this.code === \"device-not-found\" ||\n this.code === \"device-in-use\" ||\n this.code === \"insecure-context\"\n );\n }\n}\n\n/** What a person reads when the server failed without a sentence of its own. */\nexport const SERVER_FAILED_SENTENCE =\n \"The server hit an unexpected problem and nothing was changed.\";\n\n/**\n * A developer's text, not a person's: `Internal server error: SomeError`, a\n * bare `SomeError` class name, or a machine code like `internal_error`.\n */\nexport function isRawServerText(value: string): boolean {\n const text = value.trim();\n return (\n /^internal server error\\b/i.test(text) ||\n /^[A-Z][A-Za-z0-9]*(Error|Exception)$/.test(text) ||\n /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/.test(text)\n );\n}\n\n/**\n * A database message a PERSON can read: our RPCs raise plain sentences (\"Only\n * the host can remove a guest.\"); Postgres's own errors (\"permission denied\n * for table …\", \"violates foreign key …\") and class names are not.\n */\nfunction humanDatabaseSentence(message: string): string | null {\n const text = message.trim();\n if (text.length === 0 || isRawServerText(text)) return null;\n if (\n /permission denied for|violates|relation \"|does not exist|syntax error|column \"|jwt|pgrst|duplicate key|null value in/i.test(\n text,\n )\n )\n return null;\n return text;\n}\n\ninterface ErrorLike {\n name?: string | undefined;\n message?: string | undefined;\n code?: string | undefined;\n}\n\nconst FORBIDDEN_CODES = new Set([\"42501\", \"PGRST301\", \"PGRST302\"]);\n// P0002 is how `platform.refuse_not_found` answers through PostgREST (\"no meeting for that\n// link\"): the one honest not-found, and the reason the join gate's `not_found` screen needs.\nconst NOT_FOUND_CODES = new Set([\"PGRST116\", \"PGRST205\", \"P0002\"]);\n// 40001: the row changed since the caller read it (meet_update_meeting's version\n// compare-and-swap). 55000: the object is in a state that refuses the change (a\n// cancelled meeting). Both are conflicts a person resolves by reloading.\nconst CONFLICT_CODES = new Set([\"23505\", \"40001\", \"55000\"]);\n\nconst SESSION_MARKERS = [\n \"auth session missing\",\n \"jwt expired\",\n \"no api key found\",\n \"refresh_token_not_found\",\n \"invalid claim: missing sub claim\",\n];\n\n/**\n * THE ONE PLACE a raw database/transport error becomes a typed one. Called at\n * every repository boundary; nothing else in the package inspects a raw error,\n * and no consumer should ever have to.\n */\nexport function normalizeMeetError(error: unknown, operation: string): MeetError {\n if (error instanceof MeetError) return error;\n\n const raw = (error ?? {}) as ErrorLike;\n const message = typeof raw.message === \"string\" ? raw.message : String(error);\n const code = typeof raw.code === \"string\" ? raw.code : undefined;\n const lowered = message.toLowerCase();\n\n if (raw.name === \"SessionUnavailableError\" || SESSION_MARKERS.some((m) => lowered.includes(m))) {\n return new MeetError(\n \"session-unavailable\",\n `${operation}: no Supabase session available (${message})`,\n \"Normal during sign-in and token refresh. The package retries once after the host's \" +\n \"session source resolves; log it as a warning, never as an error.\",\n error,\n );\n }\n if (code !== undefined && FORBIDDEN_CODES.has(code)) {\n // A PERSON reads `message` and `remedy` (toasts, panels); the database's\n // own words stay on `cause` for the developer. Through 0.7.8 this read\n // \"meet.x: denied by the database (42501: …)\" and a remedy about RLS.\n return new MeetError(\n \"forbidden\",\n humanDatabaseSentence(message) ?? \"You don't have access to do that in this meeting.\",\n \"Ask the meeting's host to do it, or to give you access.\",\n error,\n );\n }\n if (code !== undefined && NOT_FOUND_CODES.has(code)) {\n return new MeetError(\n \"not-found\",\n `${operation}: not found (${code}: ${message})`,\n \"The row is gone, or RLS hides it. If this is PGRST205 the communication.meet_* tables \" +\n \"are not deployed yet — see MEET_SCHEMA in repository.ts for the exact contract.\",\n error,\n \"not_found\",\n );\n }\n if (code !== undefined && CONFLICT_CODES.has(code)) {\n return new MeetError(\n \"conflict\",\n humanDatabaseSentence(message) ?? \"This meeting changed a moment ago, so nothing was saved.\",\n \"Reload to see the latest, then try again.\",\n error,\n );\n }\n const hint = (error as { hint?: unknown } | null)?.hint;\n return new MeetError(\n \"transport\",\n /failed to fetch|networkerror|load failed|network request failed/i.test(message)\n ? \"The server could not be reached.\"\n : (humanDatabaseSentence(message) ?? SERVER_FAILED_SENTENCE),\n typeof hint === \"string\" && humanDatabaseSentence(hint) !== null\n ? hint.trim()\n : \"Nothing was changed — retry in a moment; if it keeps happening, reload the page.\",\n error,\n );\n}\n\n/**\n * THE DEVICE ERROR TRANSLATOR — the second-most-copied block in every video app.\n *\n * `getUserMedia` rejects with DOMException names, and every one of them means\n * something a user could fix if we told them. This is the whole reason the\n * device layer lives in the package (C22 test 3: less-than-perfect unless done\n * perfectly).\n */\nexport function normalizeDeviceError(error: unknown, operation: string): MeetError {\n if (error instanceof MeetError) return error;\n const raw = (error ?? {}) as ErrorLike;\n const name = raw.name ?? \"\";\n const message = typeof raw.message === \"string\" ? raw.message : String(error);\n\n switch (name) {\n case \"NotAllowedError\":\n case \"SecurityError\":\n return new MeetError(\n \"permission-denied\",\n `${operation}: camera/microphone permission denied`,\n \"Allow camera and microphone access for this site, then rejoin. On macOS also check \" +\n \"System Settings › Privacy & Security — a browser denied there shows the same error.\",\n error,\n );\n case \"NotFoundError\":\n case \"OverconstrainedError\":\n return new MeetError(\n \"device-not-found\",\n `${operation}: no matching camera/microphone (${message})`,\n \"The selected device is gone — usually an unplugged headset. Pick another device in \" +\n \"the pre-join panel; the package falls back to the system default automatically.\",\n error,\n );\n case \"NotReadableError\":\n case \"AbortError\":\n return new MeetError(\n \"device-in-use\",\n `${operation}: the device is already in use (${message})`,\n \"Another application (or another tab) holds the camera. Close it and try again — the \" +\n \"hardware, not the meeting, is what refused.\",\n error,\n );\n case \"TypeError\":\n return new MeetError(\n \"insecure-context\",\n `${operation}: media devices are unavailable in this context (${message})`,\n \"getUserMedia only exists on https or localhost. Serve the app over https.\",\n error,\n );\n default:\n return new MeetError(\n \"media-unsupported\",\n `${operation}: ${message}`,\n \"This browser refused the media request for a reason the package does not recognize. \" +\n \"Report it with this message so the class gets a named remedy rather than a shrug.\",\n error,\n );\n }\n}\n\n/**\n * SCREEN SHARE IS NOT A CAMERA. `getDisplayMedia` rejects with `NotAllowedError`\n * when the person simply closes the picker, and that is a CHOICE, not a\n * permission problem — so it must never wear the camera/microphone sentence\n * `normalizeDeviceError` gives that same error name.\n *\n * WHAT BROWSERS ACTUALLY REPORT (the reason there are three outcomes, not two):\n * - Chrome / Edge: a closed picker is `NotAllowedError` \"Permission denied\"; an\n * OS-level refusal (macOS Screen Recording not granted) is `NotAllowedError`\n * \"Permission denied by system\"; a permissions-policy / enterprise block is\n * `NotAllowedError` naming the policy or `display-capture`. The MESSAGE is the\n * only difference, so it is what we read.\n * - Firefox and Safari: a closed picker, a denied OS permission and a policy\n * block are ALL `NotAllowedError` with the same generic message (\"The request\n * is not allowed by the user agent or the platform…\"). They CANNOT be told\n * apart. That is the `ambiguous` outcome: the first one stays silent (a person\n * who closed the picker wants no scolding), and the SECOND one in a row shows\n * the notice, so nobody is left clicking a dead button with no explanation.\n */\nexport type ScreenShareDenial = \"blocked\" | \"ambiguous\" | \"other\";\n\nconst SYSTEM_BLOCK = /by system|permissions?[- ]policy|display-capture|disallowed|not allowed by the system/i;\n\nexport function classifyScreenShareError(error: unknown): ScreenShareDenial {\n const read = (value: unknown, key: \"name\" | \"message\"): string => {\n const v = (value as ErrorLike | null | undefined)?.[key];\n return typeof v === \"string\" ? v : \"\";\n };\n const cause = error instanceof MeetError ? error.cause : undefined;\n const denied = [error, cause].some((value) => {\n const name = read(value, \"name\");\n return name === \"NotAllowedError\" || name === \"AbortError\" || name === \"SecurityError\";\n });\n if (denied) {\n const text = [read(error, \"message\"), read(cause, \"message\")].join(\" \");\n return SYSTEM_BLOCK.test(text) || [error, cause].some((v) => read(v, \"name\") === \"SecurityError\")\n ? \"blocked\"\n : \"ambiguous\";\n }\n // A MeetError we made ourselves for a plain denial (no browser error inside).\n if (error instanceof MeetError && error.code === \"permission-denied\" && cause === undefined) {\n return \"ambiguous\";\n }\n return \"other\";\n}\n\n/** True only for a denial that MAY be the person closing the picker. */\nexport function isScreenShareCancel(error: unknown): boolean {\n return classifyScreenShareError(error) === \"ambiguous\";\n}\n\nexport const SCREEN_SHARE_BLOCKED_REMEDY =\n \"Your browser or computer is blocking screen sharing. On a Mac, open System Settings > \" +\n \"Privacy & Security > Screen Recording, turn it on for your browser, then restart the \" +\n \"browser. On a work computer, ask your administrator. You are still in the call.\";\n\nexport function normalizeScreenShareError(error: unknown, operation: string): MeetError {\n if (error instanceof MeetError) return error;\n const raw = (error ?? {}) as ErrorLike;\n const message = typeof raw.message === \"string\" ? raw.message : String(error);\n const kind = classifyScreenShareError(error);\n if (kind === \"blocked\") {\n return new MeetError(\n \"permission-denied\",\n `${operation}: screen sharing is blocked (${message})`,\n SCREEN_SHARE_BLOCKED_REMEDY,\n error,\n );\n }\n if (kind === \"ambiguous\") {\n return new MeetError(\n \"permission-denied\",\n `${operation}: screen sharing was cancelled or refused`,\n \"Choose a screen, window or tab in the picker to share it. If no picker appeared, \" +\n SCREEN_SHARE_BLOCKED_REMEDY,\n error,\n );\n }\n return new MeetError(\n \"media-unsupported\",\n `${operation}: screen sharing is not available (${message})`,\n \"This browser or device could not share a screen. You are still in the call; try a \" +\n \"desktop browser to present.\",\n error,\n );\n}\n\n/** A response whose SHAPE is wrong — the ingress boundary, not the wire. */\nexport function invalidResponse(operation: string, detail: string): MeetError {\n return new MeetError(\n \"invalid-response\",\n `${operation}: ${detail}`,\n \"The server returned a shape this package does not accept. Rendering it would put a lie \" +\n \"on the screen, so it is refused here. Fix the endpoint's return contract.\",\n );\n}\n","/**\n * THE TOKEN FLOW — the ONLY authorization path into a room (R1).\n *\n * 🚨 **THE CLIENT NEVER HOLDS A LIVEKIT SECRET.** Not in an env var, not in a\n * build-time constant, not \"just for local dev\". A LiveKit API secret in a\n * browser bundle mints a token for ANY room in the project, including one the\n * holder was never invited to — it is a full compromise of every meeting in the\n * organization. This package therefore has no code path that could accept one:\n * it asks aidream, and aidream mints.\n *\n * Four hard parts live here so no host reimplements them (C22):\n *\n * 1. **Single flight.** A React strict-mode double effect, a re-render, and a\n * reconnect all ask at once; they share ONE request. Two tokens for one\n * identity is not harmless — LiveKit disconnects the older session, which\n * looks exactly like \"my call randomly drops when I open the tab\".\n * 2. **Expiry is honored BEFORE it bites.** A token is refreshed at 80% of its\n * life, not when a reconnect fails at 100%. A meeting that outlives its\n * token and cannot reconnect is the classic 2-hour-meeting bug.\n * 3. **Guests are first-class, not an exception.** A guest has no session and\n * the request carries a display name instead; the SERVER decides what a\n * guest may do. There is no client-side \"is this person allowed\" branch.\n * 4. **A missing session is a lifecycle moment.** Between sign-in and token\n * arrival, the fetch retries ONCE after the host's session resolves and then\n * reports `session-unavailable` as a warning — never a red error, never a\n * retry storm.\n */\n\nimport type { MeetApi } from \"./api\";\nimport { invalidResponse, MeetError } from \"./errors\";\nimport type { JoinRefusalReason } from \"./reasons\";\nimport type {\n AdmissionState,\n ObserversVisibleTo,\n MeetIdentity,\n ParticipantIdentity,\n ParticipantRole,\n ParticipantStanding,\n RoomName,\n} from \"./types\";\n\nexport interface RoomToken {\n readonly token: string;\n readonly serverUrl: string;\n readonly roomName: RoomName;\n readonly identity: ParticipantIdentity;\n readonly displayName: string;\n /** ISO. The package refreshes at 80% of the remaining life. */\n readonly expiresAt: string;\n /** What the SERVER granted. The client renders from this; it never decides. */\n readonly canPublish: boolean;\n readonly canSubscribe: boolean;\n readonly canRecord: boolean;\n readonly isHost: boolean;\n /**\n * 🚨 THE SERVER'S WORD ON WHO THIS JOINER IS (MRI-A11) — `host · cohost · presenter ·\n * participant · viewer` (CORE-DESIGN §2.1). The same string is minted into the LiveKit\n * token's participant metadata and its `matrx.participant.role` attribute.\n */\n readonly role: ParticipantRole;\n /** Where this joiner stands with the meeting — beside the role, never inside it. */\n readonly standing: ParticipantStanding | null;\n /**\n * 🚨 THE MEETING PASS (CORE-DESIGN §2.5) — the credential for every aidream meet route,\n * sent as `x-meet-pass`. It is NOT the LiveKit token (`token`), which is for the media\n * connection only. Null for an ad-hoc call room (no meeting) or an older server.\n */\n readonly meetingPass: string | null;\n readonly meetingPassExpiresAt: string | null;\n /** The run (`meet_sessions.id`) this join belongs to; null while waiting before any run. */\n readonly sessionId: string | null;\n /** True when the server put this joiner in the lobby (D6). */\n readonly lobby: boolean;\n /**\n * S4: what the mint wrote on the person row — `admitted` · `knocking` · `waiting_host`.\n * A waiting person then reads `GET /v1/meet/state` every `pollSeconds`.\n */\n readonly admissionState?: AdmissionState | undefined;\n readonly pollSeconds?: number | undefined;\n /**\n * Meet wave 5: the breakout room this pass is for, or null for the main room. The\n * pass is still keyed by the MEETING's room name — it is this person's pass for the\n * meeting, and the server accepts a breakout pass wherever the meeting's is asked.\n */\n readonly breakoutRoomId?: string | null | undefined;\n /** HR-360 wave 2 (MD-16): who sees the \"Observing\" line in People (knob). */\n readonly observersVisibleTo?: ObserversVisibleTo | undefined;\n}\n\nexport type { AccessTokenSource } from \"./api\";\n\nexport interface TokenClientOptions {\n /** The ONE org-aware aidream client (MRI-A16). Owns base url and headers. */\n api: MeetApi;\n /** Injected clock, so expiry logic is provable without waiting two hours. */\n now?: (() => number) | undefined;\n /**\n * Where a GUEST's pass survives a page close (MRF-1). Absent means passes\n * live only in memory, which is correct for a member (they have a session to\n * mint a new one from) and is why this is optional rather than assumed.\n */\n storage?: RoomTokenStorage | undefined;\n}\n\n/**\n * 🚨 **WHY A GUEST'S PASS MUST OUTLIVE THE TAB (MRF-1).**\n *\n * A guest has no session, so their only credential for a meeting is the room\n * token this server minted for them. After the meeting ends, `/token` refuses\n * to mint another one — correctly: the room is gone and nobody may rejoin it.\n * So a guest who closes the tab and comes back to read the summary has, in a\n * purely in-memory client, *no way to prove they were ever there* — and the one\n * honest thing left to show them is a refusal.\n *\n * Persisting the pass is what makes the durable link durable for them. It is\n * per-device by nature (it is stored beside the device id that minted it) and\n * it expires on its own: `meet.room_token_ttl_seconds` is the organization's\n * setting for how long, and an expired pass produces the renew-the-link\n * sentence, never \"you were never here\".\n */\nexport interface RoomTokenStorage {\n read(roomName: RoomName): string | null;\n write(roomName: RoomName, serialized: string): void;\n remove(roomName: RoomName): void;\n}\n\n/** `localStorage` when it exists; a no-op that SAYS so otherwise. */\nexport function createWebRoomTokenStorage(\n onDiagnostic?: (message: string, remedy: string) => void,\n): RoomTokenStorage {\n const store = (globalThis as { localStorage?: Storage }).localStorage;\n const key = (roomName: RoomName): string => `mx.meet.pass.${roomName}`;\n if (store === undefined) {\n // NEVER SILENT. A guest in a browser with no storage will be refused the\n // record after they close the tab, and that is worth a line rather than a\n // mystery.\n onDiagnostic?.(\n \"This browser exposes no localStorage, so a guest's meeting pass cannot be kept.\",\n \"A guest who closes the tab will have to open the meeting link again before the \" +\n \"record opens. Signed-in members are unaffected.\",\n );\n return { read: () => null, write: () => undefined, remove: () => undefined };\n }\n return {\n read(roomName) {\n try {\n return store.getItem(key(roomName));\n } catch {\n return null;\n }\n },\n write(roomName, serialized) {\n try {\n store.setItem(key(roomName), serialized);\n } catch {\n /* a full or blocked store is a lost convenience, never a lost join */\n }\n },\n remove(roomName) {\n try {\n store.removeItem(key(roomName));\n } catch {\n /* see write */\n }\n },\n };\n}\n\n/**\n * The meeting pass a GUEST's browser kept for a room (MRF-1), or null — read from the same\n * storage `createWebRoomTokenStorage` writes. Used by \"keep your notes\" after sign-up.\n */\nexport function rememberedMeetingPass(\n roomName: RoomName,\n storage: RoomTokenStorage = createWebRoomTokenStorage(),\n): string | null {\n const raw = storage.read(roomName);\n if (raw === null) return null;\n try {\n const parsed = JSON.parse(raw) as Partial<RoomToken>;\n if (parsed.standing !== \"guest\") return null;\n return typeof parsed.meetingPass === \"string\" && parsed.meetingPass.length > 0\n ? parsed.meetingPass\n : null;\n } catch {\n return null;\n }\n}\n\nexport interface TokenRequest {\n readonly roomName: RoomName;\n readonly identity: MeetIdentity;\n /** Present for a meeting join; absent for an ad-hoc call room. */\n readonly meetingId?: string | undefined;\n /** The public meeting slug, when joining by link without knowing the id. */\n readonly slug?: string | undefined;\n /** Meet wave 5: a breakout room of this meeting; absent = the main room. */\n readonly breakoutRoomId?: string | null | undefined;\n /** HR-360 wave 2 (MD-16): ask to join as a silent observer. The server decides. */\n readonly observe?: boolean | undefined;\n}\n\nexport interface TokenClient {\n /** A currently-valid token, minting or refreshing only when it must. */\n get(request: TokenRequest): Promise<RoomToken>;\n /** Drop the cached token for a room (after a `denied`, or on leave). */\n invalidate(roomName: RoomName): void;\n /** True when the cached token is inside its refresh window. */\n needsRefresh(roomName: RoomName): boolean;\n /**\n * The pass this DEVICE holds for a room, **including an expired one** (MRF-1).\n *\n * 🚨 The expired case is the whole reason this returns the token instead of\n * `null`: \"your pass expired, open the link again\" and \"you were never in\n * this meeting\" are different sentences, and only the caller that can see the\n * expiry can say the right one. Never used to join — `get` mints for that.\n */\n remembered(roomName: RoomName): RoomToken | null;\n}\n\n/** The aidream routes this package calls. ONE place, so a rename is one edit. */\n/**\n * EVERY aidream ROUTE THIS PACKAGE CALLS, in one object.\n *\n * This is the contract in both directions: aidream carries a test\n * (`services/meet/tests/test_meet_service.py`) that reads THIS object out of\n * this source file and fails if the server stops serving one of these paths.\n * The package is the spec.\n *\n * `meetingEnd` is pinned here BEFORE the server serves it (MRI-A6 owns landing\n * it). That is deliberate and it is not a lie on screen: the control that calls\n * it renders, and a 404 from a server that has not landed A6 is reported as the\n * honest sentence \"Ending a meeting is not available on this server yet\" —\n * never a dead button, never a spinner, and never a silent no-op.\n */\nexport const MEET_ROUTES = {\n token: \"/api/v1/meet/token\",\n recordingStart: \"/api/v1/meet/recording/start\",\n recordingStop: \"/api/v1/meet/recording/stop\",\n noteTakerStart: \"/api/v1/meet/agent/start\",\n noteTakerStop: \"/api/v1/meet/agent/stop\",\n /** In-meeting Q&A. Streams NDJSON; body is `{meeting_id, question}` only. */\n intelligenceAsk: \"/api/v1/meet/intelligence/ask\",\n /** A host ends the meeting for everyone. Served by MRI-A6. */\n meetingEnd: \"/api/v1/meet/end\",\n /**\n * THE POST-MEETING RECORD FOR A READER WITH NO DATABASE (MRF-1). A `GET`,\n * authorized by the room token this device already holds, carried in the\n * `x-meet-pass` header — never a query parameter, because a bearer\n * credential in a URL lands in access logs, referrers and history.\n */\n record: \"/api/v1/meet/record\",\n /** A host chooses what guests may read of ONE meeting (MRF-1). */\n recordAccess: \"/api/v1/meet/record/access\",\n /**\n * KEEP YOUR NOTES (2026-10-01): a guest who just created an account presents\n * their kept pass in `x-meet-pass` with their new session, and the\n * meeting they attended becomes theirs to read. Idempotent; never a grant.\n */\n claim: \"/api/v1/meet/claim\",\n /**\n * Meet wave 1 (0.8): bring every invitee up to date after a write — sends the\n * invitation / update / cancellation emails with their `.ics` and re-queues\n * reminders. Idempotent. `{meeting_id}` is substituted by `meetingPath`.\n */\n meetingAnnounce: \"/api/v1/meet/meetings/{meeting_id}/announce\",\n /** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */\n meetingGoogleCalendar: \"/api/v1/meet/meetings/{meeting_id}/google-calendar\",\n /**\n * Meet wave 3: the recap email after the meeting. `GET` = the draft (subject,\n * body from the wrap-up, who it can go to); `POST` = send what the host\n * reviewed (`dry_run` renders and queues nothing). Never sent automatically.\n */\n meetingFollowUp: \"/api/v1/meet/meetings/{meeting_id}/follow-up\",\n /**\n * Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to\n * unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).\n * Decided by the server from the meeting itself; a guest's pass rides\n * `x-meet-pass`.\n */\n moderate: \"/api/v1/meet/moderate\",\n /** Meet wave 2: the SAVED chat — `POST` sends to everyone, `GET ?meeting_id=` reads history. */\n chat: \"/api/v1/meet/chat\",\n /**\n * Meet wave 5: polls, Q&A, the shared whiteboard and breakout rooms. `POST` acts\n * (`{meeting_id, action, …}`), `GET ?meeting_id=` reads what this viewer may see.\n */\n collab: \"/api/v1/meet/collab\",\n /** Meet wave 5: translate finished caption lines into this viewer's language. */\n captionsTranslate: \"/api/v1/meet/captions/translate\",\n /**\n * S4 — THE STATE READ (CORE-DESIGN §2.6): `GET ?meeting_id=` with `x-meet-pass`. This\n * person's admission (lazy expiry applied), whether anyone who can admit is here, the run's\n * phase. Read every `waiting_poll_seconds` while waiting and on every `mx.state.changed`.\n */\n state: \"/api/v1/meet/state\",\n /** S4: `withdraw` (stop waiting) or `leave` (close this tab); the pass may ride in the body. */\n leave: \"/api/v1/meet/leave\",\n /** S4: a pass holder (guests included) saw the recording notice, for this run. */\n consent: \"/api/v1/meet/consent\",\n} as const;\n\n/** A `MEET_ROUTES` template with its `{meeting_id}` filled in, URL-encoded. */\nexport function meetingPath(template: string, meetingId: string): string {\n return template.replace(\"{meeting_id}\", encodeURIComponent(meetingId));\n}\n\n/** The header the meeting pass rides on (CORE-DESIGN §2.5). Pinned on both sides. */\nexport const MEETING_PASS_HEADER = \"x-meet-pass\";\n\n/** The `x-meet-pass` header for a held token, or no header at all. */\nexport function meetingPassHeaders(token: RoomToken | null | undefined): Record<string, string> {\n const pass = token?.meetingPass ?? null;\n return pass !== null && pass.length > 0 ? { [MEETING_PASS_HEADER]: pass } : {};\n}\n\n/** Refresh at 80% of the token's life — never at the moment it dies. */\nconst REFRESH_RATIO = 0.8;\n\ninterface CacheEntry {\n token: RoomToken;\n mintedAt: number;\n inFlight: Promise<RoomToken> | null;\n}\n\nfunction readString(source: Record<string, unknown>, key: string): string | null {\n const value = source[key];\n return typeof value === \"string\" && value.length > 0 ? value : null;\n}\n\nfunction readBoolean(source: Record<string, unknown>, key: string, fallback: boolean): boolean {\n const value = source[key];\n return typeof value === \"boolean\" ? value : fallback;\n}\n\nconst TOKEN_STANDINGS: readonly ParticipantStanding[] = [\"member\", \"invited\", \"outsider\", \"guest\"];\n\nfunction readStanding(source: Record<string, unknown>, key: string): ParticipantStanding | null {\n const value = source[key];\n return TOKEN_STANDINGS.includes(value as ParticipantStanding) ? (value as ParticipantStanding) : null;\n}\n\nconst TOKEN_ROLES: readonly ParticipantRole[] = [\"host\", \"cohost\", \"presenter\", \"participant\", \"viewer\"];\n\n/** A word this package does not know grants nothing — it is not a host. */\nfunction readRole(source: Record<string, unknown>, key: string): ParticipantRole | null {\n const value = source[key];\n return TOKEN_ROLES.includes(value as ParticipantRole) ? (value as ParticipantRole) : null;\n}\n\nexport function parseRoomToken(body: unknown, roomName: RoomName): RoomToken {\n if (typeof body !== \"object\" || body === null) {\n throw invalidResponse(\"meet.token\", \"the response body was not an object\");\n }\n const envelope = body as Record<string, unknown>;\n // aidream's ordinary envelope is `{ success, data, msg }`; a bare object is\n // accepted too so a self-hosted route does not have to imitate it.\n const payload =\n typeof envelope[\"data\"] === \"object\" && envelope[\"data\"] !== null\n ? (envelope[\"data\"] as Record<string, unknown>)\n : envelope;\n\n const isHost = readBoolean(payload, \"is_host\", false);\n const token = readString(payload, \"token\");\n const serverUrl = readString(payload, \"server_url\") ?? readString(payload, \"serverUrl\");\n const identity = readString(payload, \"identity\");\n const expiresAt = readString(payload, \"expires_at\") ?? readString(payload, \"expiresAt\");\n\n if (token === null || serverUrl === null || identity === null) {\n throw invalidResponse(\n \"meet.token\",\n \"the response is missing token, server_url, or identity — joining with a partial \" +\n \"token would fail inside LiveKit with an opaque error instead of here\",\n );\n }\n if (expiresAt === null) {\n throw invalidResponse(\n \"meet.token\",\n \"the response carries no expires_at. Without it the package cannot refresh before \" +\n \"the token dies, and a long meeting silently loses its ability to reconnect\",\n );\n }\n return {\n token,\n serverUrl,\n roomName,\n identity: identity as ParticipantIdentity,\n displayName: readString(payload, \"display_name\") ?? readString(payload, \"displayName\") ?? identity,\n expiresAt,\n canPublish: readBoolean(payload, \"can_publish\", true),\n canSubscribe: readBoolean(payload, \"can_subscribe\", true),\n canRecord: readBoolean(payload, \"can_record\", false),\n isHost,\n // An older server sends `is_host` and no `role`; a guest join sends neither.\n // Deriving the role from what DID arrive keeps a host a host against a\n // server that has not landed MRI-A11 — and never invents a host from\n // silence, because both fallbacks below grant nothing.\n // A word this package does not know grants nothing: `participant`, never a host.\n role: readRole(payload, \"role\") ?? (isHost ? \"host\" : \"participant\"),\n standing: readStanding(payload, \"standing\"),\n meetingPass: readString(payload, \"meeting_pass\"),\n meetingPassExpiresAt: readString(payload, \"meeting_pass_expires_at\"),\n sessionId: readString(payload, \"session_id\"),\n lobby: readBoolean(payload, \"lobby\", false),\n admissionState: (readString(payload, \"admission_state\") as AdmissionState | null) ?? undefined,\n pollSeconds: typeof payload[\"poll_seconds\"] === \"number\" ? (payload[\"poll_seconds\"] as number) : undefined,\n breakoutRoomId: readString(payload, \"breakout_room_id\") ?? null,\n observersVisibleTo: readString(payload, \"observers_visible_to\") === \"hosts\" ? \"hosts\" : \"everyone\",\n };\n}\n\nexport function createTokenClient(options: TokenClientOptions): TokenClient {\n const cache = new Map<RoomName, CacheEntry>();\n const now = options.now ?? (() => Date.now());\n const storage = options.storage;\n\n /** A GUEST's pass is kept; a member's is not — they can always mint again. */\n function remember(token: RoomToken): void {\n if (storage === undefined) return;\n if (token.standing !== \"guest\") return;\n storage.write(token.roomName, JSON.stringify(token));\n }\n\n function recall(roomName: RoomName): RoomToken | null {\n if (storage === undefined) return null;\n const raw = storage.read(roomName);\n if (raw === null) return null;\n try {\n const parsed = JSON.parse(raw) as RoomToken;\n // A stored blob that is not a pass for THIS room proves nothing, and a\n // half-written one must not be handed out as a credential.\n if (typeof parsed.token !== \"string\" || parsed.token.length === 0) return null;\n if (parsed.roomName !== roomName) return null;\n return parsed;\n } catch {\n storage.remove(roomName);\n return null;\n }\n }\n\n function expired(entry: CacheEntry): boolean {\n const expiry = Date.parse(entry.token.expiresAt);\n if (Number.isNaN(expiry)) return true;\n const life = expiry - entry.mintedAt;\n if (life <= 0) return true;\n return now() >= entry.mintedAt + life * REFRESH_RATIO;\n }\n\n async function request(input: TokenRequest, retryOnMissingSession: boolean): Promise<RoomToken> {\n const guest = input.identity.userId === null;\n let response: Response;\n try {\n // THE ORGANIZATION IS A HEADER, NOT ONLY A BODY FIELD — and it is put\n // there by `core/api.ts`, the one client, so no caller can forget it\n // (MRI-A16). A body-only `organization_id` looked right and never\n // arrived: the org-admission middleware refuses first, 400, before the\n // meet router is ever reached.\n response = await options.api.request({\n path: MEET_ROUTES.token,\n method: \"POST\",\n // A GUEST HAS NO SESSION AND IS STILL A FIRST-CLASS JOINER (D6), so\n // only an authenticated join requires one.\n requireAuth: !guest,\n operation: \"meet.token\",\n body: {\n room_name: input.roomName,\n ...(input.meetingId !== undefined ? { meeting_id: input.meetingId } : {}),\n ...(input.slug !== undefined ? { slug: input.slug } : {}),\n ...(input.breakoutRoomId != null ? { breakout_room_id: input.breakoutRoomId } : {}),\n ...(input.observe === true ? { observe: true } : {}),\n organization_id: input.identity.organizationId,\n display_name: input.identity.displayName,\n device_id: input.identity.deviceId,\n guest,\n },\n });\n } catch (error) {\n if (\n retryOnMissingSession &&\n (error as { code?: string }).code === \"session-unavailable\"\n ) {\n // ONE retry, after the host's session source has had a turn. This is\n // the cure for \"909 captured errors in 0.6 seconds\": a read that lands\n // between sign-in and token arrival is a lifecycle moment.\n await Promise.resolve();\n return request(input, false);\n }\n throw error;\n }\n\n if (response.status === 401 || response.status === 403) {\n // THE SERVER'S SENTENCE IS THE ONE A PERSON READS. aidream answers every\n // refusal with `{ message, remedy }` (\"The host did not admit you…\", \"A host\n // removed you…\"); the fallback only covers a body that says nothing.\n const said = await serverSentence(response);\n throw new MeetError(\n \"forbidden\",\n said?.message ?? \"You can't join this meeting.\",\n said?.remedy ??\n \"Ask the person who shared the link to let you in, then open the link again.\",\n undefined,\n said?.reason ?? null,\n );\n }\n if (response.status === 404) {\n throw new MeetError(\n \"not-found\",\n \"meet.token: no such meeting\",\n \"The link is wrong or the meeting was deleted. Show the 'meeting not found' screen; \" +\n \"a durable link that 404s is never a retry.\",\n undefined,\n \"not_found\" satisfies JoinRefusalReason,\n );\n }\n if (response.status === 410) {\n // Meet wave 1: a CANCELLED meeting. The link resolves; the room never opens.\n // The server's own sentence (with the host's reason) is the message.\n const said = await serverSentence(response);\n throw new MeetError(\n \"cancelled\",\n said?.message ?? \"This meeting was cancelled.\",\n \"Nobody can join a cancelled meeting. Show the cancelled screen with this sentence; \" +\n \"ask the host to schedule a new one.\",\n undefined,\n said?.reason ?? (\"cancelled\" satisfies JoinRefusalReason),\n );\n }\n if (response.status === 423) {\n const said = await serverSentence(response);\n throw new MeetError(\n \"room-locked\",\n said?.message ?? \"The meeting is locked.\",\n said?.remedy ??\n \"The host locked the room. Ask them to unlock it, then join again.\",\n undefined,\n said?.reason ?? (\"locked\" satisfies JoinRefusalReason),\n );\n }\n if (response.status === 503) {\n throw new MeetError(\n \"misconfigured\",\n \"meet.token: the meeting service is not configured\",\n \"aidream is missing LIVEKIT_URL / LIVEKIT_API_KEY / LIVEKIT_API_SECRET. That is a \" +\n \"server deployment fix; the client cannot and must not supply them.\",\n );\n }\n if (!response.ok) {\n throw new MeetError(\n \"token\",\n `meet.token: the server returned ${response.status}`,\n \"Transient. The package retries the join once; if it persists check aidream's meet \" +\n \"router logs for the room name in question.\",\n );\n }\n\n return parseRoomToken(await response.json(), input.roomName);\n }\n\n return {\n get(input) {\n const entry = cache.get(input.roomName);\n // A pass for the main room is not a pass for a breakout room, and back.\n const sameRoom =\n entry !== undefined &&\n (entry.token.breakoutRoomId ?? null) === (input.breakoutRoomId ?? null) &&\n // An observer's pass is not a participant's, and back (MD-16).\n (entry.token.role === \"viewer\") === (input.observe === true);\n if (entry !== undefined && sameRoom) {\n if (entry.inFlight !== null) return entry.inFlight;\n if (!expired(entry)) return Promise.resolve(entry.token);\n }\n // SINGLE FLIGHT. Every concurrent caller shares this promise, so a strict\n // mode double-mount cannot mint two tokens for one identity.\n const pending = request(input, true)\n .then((token) => {\n cache.set(input.roomName, { token, mintedAt: now(), inFlight: null });\n // MRF-1: kept HERE, at the one place a pass is ever minted, so no\n // future caller can acquire one without it being remembered.\n remember(token);\n return token;\n })\n .catch((error: unknown) => {\n // A failure is NEVER cached — one wobble would otherwise leave the\n // room unjoinable until a reload.\n cache.delete(input.roomName);\n throw error;\n });\n cache.set(input.roomName, {\n token: entry?.token ?? placeholder(input.roomName),\n mintedAt: entry?.mintedAt ?? now(),\n inFlight: pending,\n });\n return pending;\n },\n invalidate(roomName) {\n cache.delete(roomName);\n // 🚨 THE STORED PASS SURVIVES `invalidate` ON PURPOSE. This is called on\n // leave and after a refused mint, and a guest leaving a meeting is\n // exactly the person who comes back to read its summary. What `get`\n // refuses to REUSE and what the record read may PRESENT are two different\n // questions; clearing the pass here would answer the second one wrong.\n },\n needsRefresh(roomName) {\n const entry = cache.get(roomName);\n return entry === undefined || expired(entry);\n },\n remembered(roomName) {\n // The in-flight `placeholder()` occupies the cache slot with an EMPTY\n // token string and is never handed out — including here, where handing it\n // out would present \"\" as a credential and earn a 403 that means nothing.\n const live = cache.get(roomName)?.token;\n if (live !== undefined && live.token.length > 0) return live;\n return recall(roomName);\n },\n };\n}\n\n/** Never handed out: it only occupies the slot while a mint is in flight. */\nfunction placeholder(roomName: RoomName): RoomToken {\n return {\n token: \"\",\n serverUrl: \"\",\n roomName,\n identity: \"\" as ParticipantIdentity,\n displayName: \"\",\n expiresAt: new Date(0).toISOString(),\n canPublish: false,\n canSubscribe: false,\n canRecord: false,\n isHost: false,\n role: \"participant\",\n standing: null,\n meetingPass: null,\n meetingPassExpiresAt: null,\n sessionId: null,\n lobby: false,\n breakoutRoomId: null,\n };\n}\n\n/**\n * The `{ message, remedy }` aidream puts on a refusal (`MeetError` →\n * `{ error, message, user_message, remedy }`, or FastAPI's `{ detail: {…} }`),\n * or null when the body carries no sentence.\n */\nasync function serverSentence(\n response: Response,\n): Promise<{ message: string; remedy: string | undefined; reason: string | null } | null> {\n try {\n const body = (await response.json()) as Record<string, unknown> | null;\n const source =\n body !== null && typeof body.detail === \"object\" && body.detail !== null\n ? (body.detail as Record<string, unknown>)\n : body;\n if (source === null) return null;\n const message =\n typeof source.user_message === \"string\" && source.user_message.trim() !== \"\"\n ? source.user_message\n : typeof source.message === \"string\" && source.message.trim() !== \"\"\n ? source.message\n : null;\n if (message === null) return null;\n const remedy =\n typeof source.remedy === \"string\" && source.remedy.trim() !== \"\" ? source.remedy : undefined;\n const reason = typeof source.reason === \"string\" && source.reason !== \"\" ? source.reason : null;\n return { message, remedy, reason };\n } catch {\n return null;\n }\n}\n","/**\n * RECORDING — LiveKit Egress, host-controlled, consent-enforced (R3/R5).\n *\n * 🚨 **CONSENT IS NOT OPTIONAL AND NOT A PROP.** There is no\n * `showRecordingIndicator={false}`. Recording a person without telling them is\n * illegal in a two-party-consent jurisdiction and is indefensible everywhere\n * else, so the indicator and the join-time notice are properties of the\n * package, not decisions a consumer gets to make. The only knob is the org's\n * `recordingPolicy` — disabled / host-controlled / always-on — which decides\n * whether recording may happen at all, never whether people are told.\n *\n * 🚨 **THE RECORDING BELONGS TO THE PLATFORM, NOT TO LIVEKIT.** The server\n * lands the artifact in the platform files system and returns a FILE ID. A\n * provider URL would be an orphan the day the room is garbage collected — the\n * reason `RecordingRecord.fileId` is not `url`.\n *\n * The client half is small on purpose: start/stop are aidream calls (the API\n * secret never comes near a browser), and the truth about a recording arrives\n * from the server's webhook through the room channel. This module never\n * *guesses* that a recording started.\n */\n\nimport type { MeetApi } from \"./api\";\nimport { MeetError } from \"./errors\";\nimport type { MeetStore } from \"./store\";\nimport { MEET_ROUTES } from \"./tokens\";\nimport type {\n MeetingId,\n MeetingRecord,\n RecordingState,\n RoomName,\n UserId,\n} from \"./types\";\n\nexport interface RecordingControllerOptions {\n /** The ONE org-aware aidream client (MRI-A16). Owns base url and headers. */\n api: MeetApi;\n store: MeetStore;\n /** Announce the new state to everyone in the room (the indicator is for all). */\n announce?: ((state: RecordingState, by: UserId | null) => void) | undefined;\n /** Persist that this participant saw the notice. Consent is RECORDED (R5). */\n acknowledgeConsent?: ((meetingId: MeetingId) => Promise<void>) | undefined;\n}\n\nexport interface RecordingController {\n /** True when the org policy and the actor's role allow starting a recording. */\n canStart(meeting: MeetingRecord | null, isHost: boolean): boolean;\n start(args: { meetingId: MeetingId; roomName: RoomName; by: UserId }): Promise<void>;\n stop(args: { meetingId: MeetingId; by: UserId }): Promise<void>;\n /** The join-time notice text. One sentence, one place, every surface. */\n consentNotice(meeting: MeetingRecord | null): string | null;\n acknowledge(meetingId: MeetingId): Promise<void>;\n}\n\nexport function createRecordingController(\n options: RecordingControllerOptions,\n): RecordingController {\n async function post(route: string, body: Record<string, unknown>): Promise<void> {\n let response: Response;\n try {\n response = await options.api.request({\n path: route,\n method: \"POST\",\n // A GUEST CAN NEVER START OR STOP A RECORDING (R5) — refused here,\n // before the wire, with the same sentence the hidden control implies.\n requireAuth: true,\n operation: \"meet.recording\",\n body,\n });\n } catch (error) {\n if ((error as { code?: string }).code === \"session-unavailable\") {\n throw new MeetError(\n \"forbidden\",\n \"meet.recording: recording requires an authenticated host\",\n \"A guest can never start or stop a recording (R5). The control is hidden for guests.\",\n );\n }\n throw error;\n }\n if (response.status === 403) {\n throw new MeetError(\n \"forbidden\",\n \"meet.recording: the server refused this recording action\",\n \"The actor is not a host of this meeting, or the org policy is `disabled`. The server \" +\n \"is the authority; do not re-decide it on the client.\",\n );\n }\n if (!response.ok) {\n throw new MeetError(\n \"transport\",\n `meet.recording: the server returned ${response.status}`,\n \"Egress could not be controlled. The recording state stays where it was rather than \" +\n \"showing a recording indicator for a recording that is not running.\",\n );\n }\n }\n\n return {\n canStart(meeting, isHost) {\n if (meeting === null) return false;\n if (meeting.recordingPolicy === \"disabled\") return false;\n // `always-on` is started by the SERVER when the room opens; a host cannot\n // start (or stop) it, which is the point of the policy.\n if (meeting.recordingPolicy === \"always-on\") return false;\n return isHost;\n },\n\n async start({ meetingId, roomName, by }) {\n // OPTIMISTIC ONLY AS FAR AS \"starting\", and marked `\"local\"` so the\n // durable row overrules it the moment it lands (D16). The indicator turns\n // solid when the SERVER says the egress is running — never on a client's\n // hope, and never STUCK on that hope either: `meet_recordings` is\n // subscribed and `matrx.meet.recording` reaches the guests (MRI-A16).\n options.store.setRecording(\"starting\", by, \"local\");\n try {\n await post(MEET_ROUTES.recordingStart, {\n meeting_id: meetingId,\n room_name: roomName,\n });\n } catch (error) {\n options.store.setRecording(\"idle\", null, \"local\");\n throw error;\n }\n options.announce?.(\"starting\", by);\n },\n\n async stop({ meetingId, by }) {\n options.store.setRecording(\"stopping\", options.store.snapshot().recordingStartedBy, \"local\");\n try {\n await post(MEET_ROUTES.recordingStop, { meeting_id: meetingId });\n } catch (error) {\n options.store.setRecording(\n \"recording\",\n options.store.snapshot().recordingStartedBy,\n \"local\",\n );\n throw error;\n }\n options.announce?.(\"stopping\", by);\n },\n\n consentNotice(meeting) {\n if (meeting === null || meeting.recordingPolicy === \"disabled\") return null;\n if (meeting.recordingPolicy === \"always-on\") {\n return \"This meeting is recorded and transcribed automatically. By joining, you consent to being recorded.\";\n }\n return \"The host can record and transcribe this meeting. You will see a recording indicator whenever it is running.\";\n },\n\n async acknowledge(meetingId) {\n await options.acknowledgeConsent?.(meetingId);\n },\n };\n}\n\n/**\n * 🚨 IS A RECORDING IN FLIGHT RIGHT NOW? One definition (MRI-A16).\n *\n * The control used to ask `state === \"idle\"`, which was survivable only while\n * the store never learned any state but its own: the moment the DURABLE states\n * started arriving, a meeting whose first recording had finished sat on\n * `available` forever and the host could never start a second one — the Start\n * button simply stopped existing. `processing`, `available` and `failed` all\n * mean \"nothing is recording\"; only these three mean it is.\n */\nexport const RECORDING_IN_FLIGHT: readonly RecordingState[] = [\n \"starting\",\n \"recording\",\n \"stopping\",\n];\n\nexport function isRecordingInFlight(state: RecordingState): boolean {\n return RECORDING_IN_FLIGHT.includes(state);\n}\n\n/** The indicator's words. One definition, so no surface invents a softer one. */\nexport function recordingLabel(state: RecordingState): string | null {\n switch (state) {\n case \"starting\":\n return \"Starting recording…\";\n case \"recording\":\n return \"Recording\";\n case \"stopping\":\n return \"Stopping recording…\";\n case \"processing\":\n return \"Processing recording…\";\n case \"available\":\n return \"Recording ready\";\n case \"expired\":\n return \"Recording expired\";\n case \"failed\":\n return \"Recording failed\";\n default:\n return null;\n }\n}\n","/**\n * LISTEN-ONLY ATTENDANCE — being IN the room is never contingent on a device.\n *\n * 0.3.1 and every version before it enabled the microphone as the last step of\n * `connect()`, inside the same `try` as the join. So a denied, absent, or busy\n * microphone did not produce a muted participant — it threw, the join was\n * failed with a device remedy, and the user was dropped back to `<PreJoin>`\n * having never entered the meeting. The whole in-room surface (tiles, captions,\n * live notes, Q&A, the note-taker consent notice) was unreachable to anyone\n * without a working mic, which is ordinary: a locked-down laptop, a conference\n * room dial-in, a phone on a call, a browser profile that denies capture by\n * policy, or a person who simply wants to listen. Every competitor allows it.\n *\n * THE CLASS, and how it is closed here:\n *\n * 1. **Connecting and publishing are two different operations.** The engine\n * reaches `connected` the moment the transport is up. Media is attempted\n * AFTER that, per track, and a rejection never unwinds the join.\n * 2. **A track that could not be acquired is STATE, not an exception.** The\n * reason and its remedy — the browser's own, translated by\n * `normalizeDeviceError` — ride in the snapshot so the UI says WHY the mic\n * is off instead of showing a mute button that silently does nothing.\n * 3. **Enabling later runs the SAME code path.** `setMicrophoneEnabled(true)`\n * from the control bar records exactly what the join attempt records, so a\n * person who plugs in a headset mid-meeting is one click from speaking and\n * a person whose second attempt also fails gets the same honest sentence.\n * A separate \"retry\" path is how the two drift.\n * 4. **The notice is one sentence, defined once.** Both the package's own room\n * chrome and any host that renders its own read `listenOnlyNotice()`, so\n * there is no second wording to keep in sync.\n */\n\nimport type { MeetError } from \"./errors\";\n\n/** Why a local track is not publishing, in the browser's terms plus a remedy. */\nexport interface MediaUnavailable {\n readonly code: string;\n readonly message: string;\n readonly remedy: string;\n}\n\nexport interface LocalTrackState {\n /** True only when the track is actually published. Never optimistic. */\n readonly enabled: boolean;\n /**\n * Set when acquisition was ATTEMPTED and refused. `null` covers both \"on\"\n * and \"deliberately off\" — a user who unchecked the mic in pre-join is not\n * looking at an error, they are looking at a choice they made.\n */\n readonly unavailable: MediaUnavailable | null;\n}\n\nexport interface LocalMediaState {\n readonly microphone: LocalTrackState;\n readonly camera: LocalTrackState;\n}\n\nexport const IDLE_TRACK: LocalTrackState = { enabled: false, unavailable: null };\n\nexport const IDLE_MEDIA: LocalMediaState = {\n microphone: IDLE_TRACK,\n camera: IDLE_TRACK,\n};\n\nexport function unavailableFrom(error: MeetError): MediaUnavailable {\n return { code: error.code, message: error.message, remedy: error.remedy };\n}\n\n/**\n * THE SENTENCE. One definition, so the room chrome, a host's own chrome and the\n * tests cannot say three different things about the same fact.\n */\nexport const LISTEN_ONLY_NOTICE =\n \"You're attending without a microphone — others can't hear you\";\n\n/**\n * True when this participant is in the room with NO USABLE microphone — the\n * device was refused or is missing. A person who simply muted themselves has a\n * working microphone and is not \"attending without\" one, so a mute never\n * counts: `unavailable` is the only signal.\n */\nexport function isListenOnly(media: LocalMediaState): boolean {\n return !media.microphone.enabled && media.microphone.unavailable !== null;\n}\n\n/**\n * The notice to render while in the room, or `null` when the mic is live.\n * Returns the plain sentence; the reason and remedy, when there is one, are\n * rendered beside it from `media.microphone.unavailable`.\n */\nexport function listenOnlyNotice(media: LocalMediaState): string | null {\n return isListenOnly(media) ? LISTEN_ONLY_NOTICE : null;\n}\n","/**\n * THE MEET DOMAIN MODEL.\n *\n * D3: an audio call, a video call, and a meeting are ONE thing — a room. An\n * audio call is a room with video off; a meeting is a room plus scheduling,\n * lobby and host metadata. That ruling is expressed here as ONE `MeetRoom`\n * identity carried by both `/calls` and `/meetings`, not as three parallel\n * hierarchies that drift.\n *\n * Identity-bearing values are BRANDED (C22). A `MeetingId` and a `RoomName` are\n * both strings and are NOT interchangeable — passing the meeting's row id where\n * LiveKit wants the room name produces a room nobody else joins, which looks\n * exactly like \"the other person's camera is broken\".\n */\n\ndeclare const brand: unique symbol;\ntype Brand<T, B extends string> = T & { readonly [brand]: B };\n\nexport type MeetingId = Brand<string, \"MeetingId\">;\nexport type CallId = Brand<string, \"CallId\">;\n/** The LiveKit room name. THE MEDIA ROOM KEY — durable for a meeting link. */\nexport type RoomName = Brand<string, \"RoomName\">;\nexport type UserId = Brand<string, \"UserId\">;\nexport type OrganizationId = Brand<string, \"OrganizationId\">;\n/** LiveKit participant identity. `user:<uuid>` or `guest:<opaque>`. */\nexport type ParticipantIdentity = Brand<string, \"ParticipantIdentity\">;\n\nexport const asMeetingId = (value: string): MeetingId => value as MeetingId;\nexport const asCallId = (value: string): CallId => value as CallId;\nexport const asRoomName = (value: string): RoomName => value as RoomName;\nexport const asUserId = (value: string): UserId => value as UserId;\nexport const asOrganizationId = (value: string): OrganizationId =>\n value as OrganizationId;\nexport const asParticipantIdentity = (value: string): ParticipantIdentity =>\n value as ParticipantIdentity;\n\nexport type JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonValue[]\n | { [key: string]: JsonValue };\nexport type JsonObject = { [key: string]: JsonValue };\n\n/** Audio-only or with video. A call escalates; it never becomes a new call. */\nexport type CallMode = \"audio\" | \"video\";\n\n/**\n * WHO IS IN THE ROOM, as far as authorization is concerned.\n *\n * A guest has no `userId` — by construction, not by convention. Everything that\n * requires authentication (host powers, recording, starting a call) takes a\n * `userId` and therefore cannot compile against a guest identity (D6).\n */\nexport interface MeetIdentity {\n readonly userId: UserId | null;\n readonly organizationId: OrganizationId;\n readonly displayName: string;\n readonly avatarUrl: string | null;\n /** Stable per-device value so a reconnect keeps ONE LiveKit identity. */\n readonly deviceId: string;\n}\n\nexport function isGuest(identity: MeetIdentity): boolean {\n return identity.userId === null;\n}\n\n/**\n * WHO SOMEBODY IS IN A ROOM — the SERVER's word, never this client's (MRI-A11).\n *\n * THE ROLE VOCABULARY (CORE-DESIGN §2.1, S3): `host · cohost · presenter · participant ·\n * viewer`. `host` is `meet_meetings.host_user_id`; `cohost` is a real `iam` grant on the\n * meeting. Being a guest is a STANDING (`ParticipantStanding`), never a role: a guest is a\n * `participant` whose standing is `guest`. Pinned against aidream's `config.py` by\n * `test_meet_service.py`.\n */\nexport type ParticipantRole = \"host\" | \"cohost\" | \"presenter\" | \"participant\" | \"viewer\";\n\n/**\n * Where a person stands with the meeting, beside the role: `member` of its organization,\n * `invited` (shared with them), `outsider` (signed in, followed the link), `guest` (no\n * session). The server mints it (attribute `matrx.participant.standing`).\n */\nexport type ParticipantStanding = \"member\" | \"invited\" | \"outsider\" | \"guest\";\n\n/** Rank, so \"can this actor do that to that participant\" is one comparison. */\nexport const ROLE_RANK: Readonly<Record<ParticipantRole, number>> = {\n host: 4,\n cohost: 3,\n presenter: 2,\n participant: 1,\n viewer: 0,\n};\n\n/** Does this standing follow the link-holder (guest) rules? `outsider` and `guest`. */\nexport function isLinkFollower(standing: ParticipantStanding | null | undefined): boolean {\n return standing === \"guest\" || standing === \"outsider\";\n}\n\n/**\n * 🚨 THE ONE PREDICATE FOR \"DOES THIS PERSON HOLD HOST POWERS\".\n *\n * Every host control in this package answers this question and no other. It is\n * a pure function of a SERVER-minted role, so there is no path by which a\n * client's own state can grant it anything.\n */\nexport function isHostRole(role: ParticipantRole): boolean {\n return role === \"host\" || role === \"cohost\";\n}\n\n/**\n * THE SILENT OBSERVER (HR-360 wave 2, MD-16) — the watch-only role `viewer` the core design\n * already froze: publishes nothing, has no tile on anyone's stage, and is listed in People\n * as \"Observing\". The server decides it at the join gate; nothing here grants it.\n */\nexport function isObserverRole(role: ParticipantRole | null | undefined): boolean {\n return role === \"viewer\";\n}\n\n/** Who sees the \"Observing\" line in People — knob `meet.observers_visible_to`. */\nexport type ObserversVisibleTo = \"everyone\" | \"hosts\";\n\n/**\n * THE LOBBY LADDER (D6). A guest link-follower starts `knocking`; the host\n * admits or denies. An authenticated member of the meeting's org is `admitted`\n * on arrival unless the meeting is locked.\n */\nexport type AdmissionState =\n | \"waiting_host\"\n | \"knocking\"\n | \"admitted\"\n | \"denied\"\n | \"expired\"\n | \"withdrawn\"\n | \"removed\"\n | \"ended\";\n\n/** States a person is still WAITING in (CORE-DESIGN §2.2). */\nexport const WAITING_ADMISSION_STATES: readonly AdmissionState[] = [\"waiting_host\", \"knocking\"];\n\nexport interface LobbyEntry {\n readonly identity: ParticipantIdentity;\n readonly displayName: string;\n readonly avatarUrl: string | null;\n readonly userId: UserId | null;\n readonly state: AdmissionState;\n readonly knockedAt: string;\n /** When an unanswered knock lapses (`knock_timeout_minutes`); null = never. */\n readonly knockExpiresAt?: string | null | undefined;\n readonly decidedAt: string | null;\n readonly decidedBy: UserId | null;\n}\n\nexport type ConnectionQuality = \"excellent\" | \"good\" | \"poor\" | \"lost\" | \"unknown\";\n\n/** One person in the room, as the UI needs them. Media state is a FACT here. */\nexport interface MeetParticipant {\n readonly identity: ParticipantIdentity;\n readonly userId: UserId | null;\n readonly displayName: string;\n readonly avatarUrl: string | null;\n readonly role: ParticipantRole;\n /** Where they stand with the meeting (server-minted); null when the server sent none. */\n readonly standing?: ParticipantStanding | null;\n /**\n * Whether the SERVER granted this participant the power to start a recording\n * (MRI-A11). It is not `role === \"host\"`: a meeting whose recording policy is\n * `always-on` is the server's to start and a host may not touch it, and a\n * `disabled` policy grants it to nobody. The client renders this; it never\n * recomputes the policy.\n */\n readonly canRecord: boolean;\n readonly isLocal: boolean;\n readonly isAgent: boolean;\n readonly micEnabled: boolean;\n readonly cameraEnabled: boolean;\n readonly screenSharing: boolean;\n readonly speaking: boolean;\n readonly handRaised: boolean;\n /**\n * When the hand went up (epoch ms), or null. The HAND QUEUE is ordered by it —\n * first raised, first called on (Meet wave 2). Optional so a roster built by a\n * host before 0.8 still type-checks.\n */\n readonly handRaisedAt?: number | null | undefined;\n readonly quality: ConnectionQuality;\n readonly joinedAt: string;\n /**\n * LiveKit participant attributes, verbatim. The note-taker announces itself\n * here (`matrx.agent.consent_notice`), which is how a consent notice can be\n * rendered for a participant whose token metadata this client cannot read.\n */\n readonly attributes: Readonly<Record<string, string>>;\n}\n\nexport type MeetingKind = \"instant\" | \"scheduled\" | \"recurring\";\n\nexport interface MeetingRecord {\n readonly id: MeetingId;\n readonly organizationId: OrganizationId;\n /** Durable: the same room name before, during, and after (R3). */\n readonly roomName: RoomName;\n /** The public slug in a meeting link. Durable for recurring meetings. */\n readonly slug: string;\n readonly title: string;\n readonly kind: MeetingKind;\n readonly hostUserId: UserId;\n readonly scheduledFor: string | null;\n readonly scheduledDurationMinutes: number | null;\n readonly startedAt: string | null;\n readonly endedAt: string | null;\n readonly locked: boolean;\n readonly lobbyEnabled: boolean;\n readonly recordingPolicy: RecordingPolicy;\n readonly aiEnabled: boolean;\n readonly metadata: JsonObject;\n /*\n * ── MEET WAVE 1 — management (0.8). Always present on a row read from the\n * database by this package; optional in the type only so a hand-built\n * fixture from before 0.8 still compiles.\n */\n /** IANA zone the meeting's wall-clock time is authored in (`America/Los_Angeles`). */\n readonly timeZone?: string;\n /** Description / agenda text. */\n readonly agenda?: string | null;\n /** RFC 5545 RRULE subset — one row and one durable link per series. */\n readonly recurrenceRule?: string | null;\n /** Signed-in members and invitees may enter before a host arrives. */\n readonly joinBeforeHost?: boolean;\n /** Cancel is a STATE: the link still resolves and says so; nobody can join. */\n readonly cancelledAt?: string | null;\n readonly cancellationReason?: string | null;\n /** Archived (soft deleted). Restorable by the host or a co-host. */\n readonly deletedAt?: string | null;\n /** iCalendar SEQUENCE — bumped by the database on every reschedule and on cancel. */\n readonly calendarSequence?: number;\n /** Row version, for the compare-and-swap in `updateMeeting`. */\n readonly version?: number;\n}\n\n/* ─────────────────────────────── MEET WAVE 1 — management vocabulary (0.8) */\n\n/** An invitee's answer. `needs_action` = not answered yet (iCalendar NEEDS-ACTION). */\nexport type RsvpState = \"needs_action\" | \"accepted\" | \"declined\" | \"tentative\";\n/** The answers a person can give. */\nexport type RsvpAnswer = Exclude<RsvpState, \"needs_action\">;\n/** A co-host holds host moderation through a real `iam` admin grant on the meeting. */\nexport type InviteeRole = \"invitee\" | \"cohost\";\n\n/** One person a meeting was sent to (`communication.meet_invitees`). */\nexport interface MeetingInvitee {\n readonly id: string;\n readonly meetingId: MeetingId;\n /** Set when the person has an account — they skip the waiting room and answer in-app. */\n readonly userId: UserId | null;\n readonly email: string | null;\n readonly displayName: string | null;\n readonly role: InviteeRole;\n readonly rsvpState: RsvpState;\n readonly respondedAt: string | null;\n readonly rsvpNote: string | null;\n /** The calendar_sequence of the last invitation they were sent; null = never told. */\n readonly lastSentSequence: number | null;\n readonly lastSentAt: string | null;\n}\n\n/** Who to invite: an account (`userId`) or an address (`email`), or both. */\nexport interface InviteeInput {\n readonly userId?: UserId | undefined;\n readonly email?: string | undefined;\n readonly displayName?: string | undefined;\n /** Omit to keep an existing invitee's role; `cohost` needs the host or a co-host. */\n readonly role?: InviteeRole | undefined;\n}\n\n/** Where one occurrence of a meeting stands. */\nexport type OccurrenceState = \"scheduled\" | \"moved\" | \"cancelled\";\n\n/** One occurrence — for a one-off meeting, the meeting itself. */\nexport interface MeetingOccurrence {\n readonly meetingId: MeetingId;\n /** The start the rule generates (iCalendar RECURRENCE-ID). The key for exceptions. */\n readonly originalStart: string;\n /** When it actually happens (differs from `originalStart` only when `moved`). */\n readonly occurrenceStart: string;\n readonly durationMinutes: number | null;\n readonly state: OccurrenceState;\n readonly sequence: number;\n}\n\n/** One row of the Upcoming list — an occurrence plus what the list shows about its meeting. */\nexport interface UpcomingOccurrence extends MeetingOccurrence {\n readonly organizationId: OrganizationId;\n readonly title: string;\n readonly slug: string;\n readonly kind: MeetingKind;\n readonly hostUserId: UserId;\n readonly timeZone: string;\n readonly meetingCancelled: boolean;\n /** The signed-in reader's own answer, when they are an invitee. */\n readonly myRsvp: RsvpState | null;\n /** `host`, `cohost` or `invitee` — null when the reader simply can see it (org member). */\n readonly myRole: \"host\" | InviteeRole | null;\n}\n\n/** An exception row (`communication.meet_occurrence_overrides`). */\nexport interface OccurrenceOverride {\n readonly id: string;\n readonly meetingId: MeetingId;\n readonly originalStart: string;\n readonly state: \"cancelled\" | \"moved\";\n readonly newStart: string | null;\n readonly newDurationMinutes: number | null;\n readonly reason: string | null;\n readonly calendarSequence: number;\n /** Set when the exception was withdrawn (`restore`) and the occurrence is back on schedule. */\n readonly deletedAt: string | null;\n}\n\n/** Settings a meeting carries; unset ones come from the person's / organization's knobs. */\nexport interface MeetingSettings {\n readonly lobbyEnabled?: boolean | undefined;\n readonly recordingPolicy?: RecordingPolicy | undefined;\n readonly aiEnabled?: boolean | undefined;\n readonly joinBeforeHost?: boolean | undefined;\n}\n\n/** A partial edit. `null` clears a clearable field (agenda, recurrence rule, time). */\nexport interface MeetingChanges extends MeetingSettings {\n readonly title?: string | undefined;\n readonly agenda?: string | null | undefined;\n readonly timeZone?: string | undefined;\n readonly scheduledFor?: string | null | undefined;\n readonly scheduledDurationMinutes?: number | null | undefined;\n readonly recurrenceRule?: string | null | undefined;\n /**\n * When the SERIES moves (start, rule or zone), carry its per-date changes\n * (cancelled or moved occurrences) to the new schedule instead of archiving\n * them — Google Calendar's and Outlook's \"keep changes to individual events\".\n * Absent or false archives them (restorable). Ask the person; never decide.\n */\n readonly keepExceptions?: boolean | undefined;\n}\n\n/** What the emailed RSVP link shows and answers (no account needed). */\nexport type RsvpInvitation =\n | {\n readonly ok: true;\n readonly meeting: {\n readonly title: string;\n readonly slug: string;\n readonly scheduledFor: string | null;\n readonly durationMinutes: number | null;\n readonly timeZone: string;\n readonly agenda: string | null;\n readonly recurrenceRule: string | null;\n readonly cancelledAt: string | null;\n readonly cancellationReason: string | null;\n readonly endedAt: string | null;\n };\n readonly invitee: {\n readonly displayName: string | null;\n readonly email: string | null;\n readonly rsvpState: RsvpState;\n readonly respondedAt: string | null;\n };\n }\n | { readonly ok: false; readonly message: string };\n\n/** Org-configurable, never hardcoded taste (Law 6 / \"opinions become knobs\"). */\nexport type RecordingPolicy = \"disabled\" | \"host-controlled\" | \"always-on\";\n\nexport type RecordingState =\n | \"idle\"\n | \"starting\"\n | \"recording\"\n | \"stopping\"\n | \"processing\"\n | \"available\"\n /** Bytes were purged under retention; notes and transcript remain durable. */\n | \"expired\"\n | \"failed\";\n\n/**\n * WHICH transport last said what the recording is doing (MRI-A16).\n *\n * `local` — this browser's own optimistic `starting`/`stopping`, which is all\n * the indicator ever had through 0.4.7, and the reason it never went solid.\n * `room` — a `matrx.meet.recording` packet, the ONLY feed a guest has.\n * `record` — the durable `meet_recordings` row. It outranks both (D16).\n */\nexport type RecordingSource = \"local\" | \"room\" | \"record\";\n\n/** The server's durable retention receipt; never inferred from a browser clock. */\nexport interface RecordingRetention {\n readonly operationId: string;\n readonly policyId: string;\n readonly policyUpdatedAt: string;\n readonly phase: \"warning\" | \"expired\";\n readonly dueAt: string;\n readonly expiredAt: string | null;\n}\n\nexport interface RecordingRecord {\n readonly id: string;\n readonly meetingId: MeetingId;\n readonly state: RecordingState;\n readonly startedAt: string | null;\n readonly endedAt: string | null;\n readonly startedBy: UserId | null;\n /** The PLATFORM file id. Never a provider URL (R3). */\n readonly fileId: string | null;\n readonly failureReason: string | null;\n /** Present when central retention warned or purged this recording. */\n readonly retention: RecordingRetention | null;\n}\n\n/**\n * WHERE A TRANSCRIPT LINE CAME FROM — and therefore how much it is worth.\n *\n * D16: `lk.transcription` is published by a room participant and its\n * `sender_identity` is client-supplied and unvalidated by LiveKit, so a caption\n * is a HINT — good enough to read live, never good enough to reason from. A\n * `record` line is a durable `meet_transcript_segments` row written server-side\n * by the note-taker: that is the transcript. A record always beats a caption;\n * a caption never overwrites a record.\n */\nexport type TranscriptSource = \"caption\" | \"record\";\n\nexport interface TranscriptSegment {\n readonly id: string;\n readonly meetingId: MeetingId;\n readonly identity: ParticipantIdentity;\n readonly speaker: string;\n readonly text: string;\n readonly startedAt: string;\n /** Interim text is replaced in place by its final; it is never appended. */\n readonly final: boolean;\n readonly source: TranscriptSource;\n /**\n * The `lk.segment_id` this line was captioned under, when known. A durable\n * row carries it too (the worker writes it into the row's metadata), which is\n * what lets a record REPLACE the caption it supersedes rather than doubling it.\n */\n readonly captionId: string | null;\n}\n\n/**\n * SOMEBODY WHO WAS IN THE MEETING — the durable `meet_participants` row, which\n * outlives the LiveKit room (MRI-D2). `MeetParticipant` above is a live tile;\n * THIS is the attendance record a person reads a week later.\n */\nexport interface MeetingAttendee {\n readonly id: string;\n readonly meetingId: MeetingId;\n readonly identity: ParticipantIdentity;\n readonly displayName: string;\n readonly avatarUrl: string | null;\n readonly userId: UserId | null;\n readonly role: ParticipantRole;\n readonly admissionState: AdmissionState;\n /** When this person knocked, and when an unanswered knock lapses (null = never). */\n readonly knockedAt?: string | null | undefined;\n readonly knockExpiresAt?: string | null | undefined;\n readonly isAgent: boolean;\n /** FIRST arrival in the room — a rejoin never moves it. */\n readonly joinedAt: string | null;\n /** Null while in the room, else the last time they left. */\n readonly leftAt: string | null;\n /**\n * Every stay in the room, oldest first (`metadata.sessions`, written by the\n * server's one attendance rule). Empty for a row recorded before stays were\n * kept; the report then falls back to `joinedAt`–`leftAt`.\n */\n readonly sessions?: readonly AttendanceStay[] | undefined;\n}\n\n/** One stay in the room: arrived, and left (null = still there or never recorded). */\nexport interface AttendanceStay {\n readonly joinedAt: string;\n readonly leftAt: string | null;\n}\n\nexport type MeetingNoteKind = \"note\" | \"decision\" | \"action-item\";\n\n/**\n * ONE ROW OF WHAT THE MEETING DECIDED. `meet_notes.kind` is a live CHECK\n * constraint of exactly three values (D5) — there is no `summary` kind and no\n * `transcript` kind — so the wrap-up summary is a `note` carrying\n * `metadata.role = \"summary\"`, and a live-window note carries `metadata.window`.\n * Both are projected here so the UI never re-reads raw metadata.\n */\nexport interface MeetingNote {\n readonly id: string;\n readonly meetingId: MeetingId;\n readonly text: string;\n readonly createdAt: string;\n readonly kind: MeetingNoteKind;\n /** Set ONLY when the assignee resolved to a real user (D18). */\n readonly assigneeUserId: UserId | null;\n /** Who the item is for, as a person reads it. A guest keeps their name here. */\n readonly assigneeDisplayName: string | null;\n /** `\"summary\"` for the wrap-up; `null` for everything else. */\n readonly role: \"summary\" | null;\n /** The live-notes window index this came out of, when it came from one. */\n readonly window: number | null;\n /**\n * 🚨 THE WRAP-UP IS THE AUTHORITATIVE POST-MEETING SET (D20).\n *\n * A live-notes window and the wrap-up both write decisions and action items,\n * and on production 2026-09-09 meeting `9cb-39f8-782` carried the SAME\n * decision twice — once from the note-taker at 04:43:57 and once from the\n * wrap-up at 04:47:15 — and the record view listed both. A reader cannot tell\n * a repeated decision from two decisions.\n *\n * So when the wrap-up lands, the server MARKS the window-written decisions\n * and action items it supersedes (`metadata.superseded_by` = the wrap-up's\n * run id) and never deletes them: the row is still the timeline of what was\n * known at 04:43. This field carries that marker, and the record view and the\n * assistant panel render only the rows where it is `null`.\n */\n readonly supersededBy: string | null;\n /**\n * WHICH TRANSPORT DELIVERED THIS COPY (MRI-A12). The same durable row reaches\n * a signed-in member twice — once as a `meet_notes` row over Postgres Changes\n * (`\"record\"`) and once as a `matrx.meet.note` packet on the media plane\n * (`\"room\"`), which is the ONLY copy a session-less guest can receive, because\n * `anon` holds no grant on the table (MRI-A10).\n *\n * They carry the same id and the same words, so the store renders one note.\n * Where they could ever disagree, `\"record\"` wins and `\"room\"` never overwrites\n * it — the database is the record and the packet is the courtesy copy. That is\n * the same precedence captions and segments already follow (D16).\n */\n readonly source: \"record\" | \"room\";\n}\n\n/**\n * THE WRAP-UP, ANNOUNCED (MRI-A12). The summary, decisions and action items\n * arrive as ordinary notes; this says the pass that wrote them FINISHED, which\n * is the difference between \"no summary yet\" and \"no summary is coming\".\n *\n * 🚨 It reaches a screen only while the room still exists. `POST /v1/meet/end`\n * deletes the LiveKit room and the wrap-up runs afterwards, so on the ordinary\n * ending path nobody is in the room to be told — the post-meeting record view is\n * where the summary is read. A surface must therefore treat `null` as \"not\n * announced\", never as \"not written\".\n */\nexport interface WrapUpAnnouncement {\n readonly meetingId: MeetingId;\n /** The `meet_notes` ids the pass wrote, in the order it wrote them. */\n readonly noteIds: readonly string[];\n /** How many durable transcript segments it summarized. */\n readonly segmentCount: number;\n readonly at: string;\n}\n\n/**\n * THE NOTE-TAKER'S STATE, as the meeting row records it\n * (`meet_meetings.metadata.note_taker.state`, MRI-A2). Five values and no\n * spinner: a surface renders the state it is in, or the server's refusal.\n *\n * - `absent` — nobody summoned it. The control offers to.\n * - `joining` — dispatched; it has not entered the room yet.\n * - `transcribing` — the server recorded active transcription and the exact\n * deterministic actor is present in the room.\n * - `handoff_pending` — a row that said transcribing no longer has that exact\n * actor in the room. The next state and any missed audio are not inferred.\n * - `ended` — the recorded note-taker stopped. The cause is not inferred.\n */\nexport type NoteTakerState =\n | \"absent\"\n | \"joining\"\n | \"transcribing\"\n | \"handoff_pending\"\n | \"ended\";\n\n/** The lifecycle of a 1:1 or ad-hoc group call (R2). */\nexport type CallState =\n | \"idle\"\n | \"dialing\"\n | \"ringing\"\n | \"connecting\"\n | \"active\"\n | \"declined\"\n | \"missed\"\n | \"busy\"\n | \"cancelled\"\n | \"ended\"\n | \"failed\";\n\nexport interface CallInvite {\n readonly id: CallId;\n readonly organizationId: OrganizationId;\n readonly roomName: RoomName;\n readonly mode: CallMode;\n readonly callerUserId: UserId;\n readonly callerName: string;\n readonly callerAvatarUrl: string | null;\n readonly calleeUserIds: readonly UserId[];\n /** The durable conversation the invite was delivered into, if any. */\n readonly conversationId: string | null;\n readonly createdAt: string;\n readonly expiresAt: string;\n readonly state: CallState;\n readonly declineMessage: string | null;\n}\n\n/** A person the UI can call. Deliberately minimal: hosts already have this. */\nexport interface CallablePerson {\n readonly userId: UserId;\n readonly displayName: string;\n readonly avatarUrl: string | null;\n}\n\nexport type StageLayout = \"grid\" | \"speaker\" | \"sidebar\";\n","/**\n * THE PHASE MACHINE — the one place a meeting tab's top-level phase changes (CORE-DESIGN §3.1,\n * step S6a).\n *\n * The values ARE the observation contract's `data-meet-phase` column (HARNESS-CONTRACT §2), so\n * the core renders its own state with no translation layer. Every change goes through\n * `nextPhase(from, event)`: an explicit table of which event may move which phase where. An\n * event the table does not allow from the current phase is REFUSED (the store keeps the phase\n * and reports it) — never applied.\n *\n * 🚨 THE INVARIANT (guard: `phase.test.ts`, `in-call-phase-guard.test.ts`): nothing enters\n * `prejoin` except `resolving` (the link resolved) or the person's own click. In-call outcomes —\n * share, devices, recording, captions, chat, collab, moderation — live in REGIONS and have no\n * event here at all, so the MD-01 class (\"a cancelled share picker threw a live meeting back to\n * Ready to join?\") cannot recur: from `in_call` the only ways out are the exit-reason\n * resolution (§3.4), the person's own Leave, and the meeting ending.\n *\n * 1:1 calls run on this same machine (§3.6): ringing is a pre-room phase set, and an answered\n * call goes `joining` → `in_call` exactly like a meeting.\n */\n\nimport { isJoinRefusalReason, type JoinRefusalReason } from \"./reasons\";\nimport type { AdmissionState } from \"./types\";\n\n/** Every gate reason except `ended`, which renders the `ended` phase (contract §2). */\nexport type RefusedReason = Exclude<JoinRefusalReason, \"ended\">;\n\n/** Where the person is (HARNESS-CONTRACT §2, verbatim). */\nexport type MeetPhase =\n | \"resolving\"\n | \"unsupported\"\n | `refused:${RefusedReason}`\n | \"guest_name\"\n | \"prejoin\"\n | \"joining\"\n | \"waiting:host_not_started\"\n | \"waiting:knocking\"\n | \"denied\"\n | \"knock_expired\"\n | \"in_call\"\n | \"disconnected\"\n | \"left\"\n | \"removed\"\n | \"superseded\"\n | \"inactive\"\n | \"ended\"\n | \"record\"\n | \"ringing_out\"\n | \"ringing_in\"\n | \"call_declined\"\n | \"call_missed\"\n | \"call_busy\"\n | \"answered_elsewhere\";\n\n/** The phases a §3.4 exit-reason resolution can land on. */\nexport type ExitPhase = \"removed\" | \"superseded\" | \"inactive\" | \"ended\" | \"disconnected\" | \"waiting:knocking\";\n\n/** How a 1:1 call ended before it connected (§3.6). */\nexport type CallOutcomePhase = \"call_declined\" | \"call_missed\" | \"call_busy\" | \"answered_elsewhere\";\n\n/**\n * Every event that can move the phase. `click: true` marks the ones only the person's own\n * press may send — the guard test reads it.\n */\nexport type PhaseEvent =\n /** The link resolved on page load / mount: `resolving` → `prejoin` (first entry). */\n | { readonly type: \"resolved\" }\n /** Capability gate failed: this browser cannot join. */\n | { readonly type: \"unsupported\" }\n /** The person pressed Join / Rejoin / Ask again. */\n | { readonly type: \"join\"; readonly click: true }\n /** The person chose to go back to the device check from `left` / `disconnected`. */\n | { readonly type: \"back_to_prejoin\"; readonly click: true }\n /** The person pressed Try again on a refusal: look the link up again. */\n | { readonly type: \"retry\"; readonly click: true }\n /** The server said wait (or decided a wait): the admission row's own word. */\n | { readonly type: \"admission\"; readonly admission: AdmissionState }\n /** Admitted from the waiting room: the core mints the real pass and joins. */\n | { readonly type: \"admitted\" }\n /** LiveKit said connected. */\n | { readonly type: \"connected\" }\n /** The join itself failed: a gate reason (→ `refused:*` / `ended`) or none (→ `disconnected`). */\n | { readonly type: \"join_failed\"; readonly reason: string | null }\n /** §3.4: the exit-reason resolution decided where an in-call person went. */\n | { readonly type: \"exit\"; readonly to: ExitPhase }\n /** My Leave (in the room, joining, or withdrawing from the waiting room). */\n | { readonly type: \"leave\" }\n /** The meeting (or this run of it) is over, by any carrier. */\n | { readonly type: \"ended\" }\n /** §3.6: I placed a call / a call rings me while nothing else is on screen. */\n | { readonly type: \"ring_out\" }\n | { readonly type: \"ring_in\" }\n /** §3.6: an answered call (by me, or my callee) — join its room. */\n | { readonly type: \"call_answered\" }\n /** §3.6: the call ended before it connected. */\n | { readonly type: \"call_outcome\"; readonly outcome: CallOutcomePhase }\n /** Back to the start: a different meeting mounted, the call surface was dismissed, or dispose. */\n | { readonly type: \"reset\" };\n\nexport type PhaseEventType = PhaseEvent[\"type\"];\n\n/** Phases in which this tab holds (or is getting) a live media connection. */\nexport const LIVE_PHASES: ReadonlySet<MeetPhase> = new Set<MeetPhase>([\"joining\", \"in_call\"]);\n\n/** The waiting-room phase set (the core reads the server's state while in one of these). */\nexport const WAITING_PHASES: ReadonlySet<MeetPhase> = new Set<MeetPhase>([\n \"waiting:host_not_started\",\n \"waiting:knocking\",\n \"denied\",\n \"knock_expired\",\n]);\n\n/** A phase the person can press Join / Rejoin from. */\nconst JOINABLE_FROM: ReadonlySet<MeetPhase> = new Set<MeetPhase>([\n \"prejoin\",\n \"left\",\n \"disconnected\",\n \"superseded\",\n \"inactive\",\n \"denied\",\n \"knock_expired\",\n]);\n\n/** A call that never connected has no meeting to end; an ended meeting stays ended. */\nconst NOT_ENDABLE: ReadonlySet<MeetPhase> = new Set<MeetPhase>([\n \"ended\",\n \"record\",\n \"unsupported\",\n \"ringing_out\",\n \"ringing_in\",\n \"call_declined\",\n \"call_missed\",\n \"call_busy\",\n \"answered_elsewhere\",\n]);\n\nconst RINGING: ReadonlySet<MeetPhase> = new Set<MeetPhase>([\"ringing_out\", \"ringing_in\"]);\n\n/** Nothing live, nothing pending: a call may take the screen from here. */\nexport const IDLE_PHASES: ReadonlySet<MeetPhase> = new Set<MeetPhase>([\n \"resolving\",\n \"left\",\n \"call_declined\",\n \"call_missed\",\n \"call_busy\",\n \"answered_elsewhere\",\n]);\n\nexport function isLivePhase(phase: MeetPhase): boolean {\n return LIVE_PHASES.has(phase);\n}\n\nexport function isWaitingPhase(phase: MeetPhase): boolean {\n return WAITING_PHASES.has(phase);\n}\n\nexport function isRefusedPhase(phase: MeetPhase): phase is `refused:${RefusedReason}` {\n return phase.startsWith(\"refused:\");\n}\n\n/** The reason a refused / terminal phase carries (`data-meet-reason`). */\nexport function reasonOf(phase: MeetPhase): string | null {\n return isRefusedPhase(phase) ? phase.slice(\"refused:\".length) : null;\n}\n\n/** A gate reason → the phase it renders. `ended` is the ended phase, never `refused:ended`. */\nexport function refusalPhase(reason: string | null): MeetPhase {\n if (reason === null || !isJoinRefusalReason(reason)) return \"disconnected\";\n return reason === \"ended\" ? \"ended\" : (`refused:${reason}` as const);\n}\n\n/** The server's admission word → the waiting phase it renders (§3.1, S4). */\nexport function admissionPhase(admission: AdmissionState): MeetPhase | null {\n switch (admission) {\n case \"waiting_host\":\n return \"waiting:host_not_started\";\n case \"knocking\":\n return \"waiting:knocking\";\n case \"denied\":\n return \"denied\";\n case \"expired\":\n return \"knock_expired\";\n case \"withdrawn\":\n return \"left\";\n case \"ended\":\n return \"ended\";\n case \"removed\":\n return \"refused:removed\";\n default:\n // `admitted` is never a waiting phase: it is a join (`admitted` event).\n return null;\n }\n}\n\n/**\n * THE TABLE. Returns the next phase, or null when `event` may not move `from` (refused).\n * Pure — the store applies it, and the guard tests enumerate it.\n */\nexport function nextPhase(from: MeetPhase, event: PhaseEvent): MeetPhase | null {\n switch (event.type) {\n case \"resolved\":\n return from === \"resolving\" ? \"prejoin\" : null;\n case \"unsupported\":\n return from === \"resolving\" ? \"unsupported\" : null;\n case \"join\":\n return JOINABLE_FROM.has(from) ? \"joining\" : null;\n case \"back_to_prejoin\":\n return from === \"left\" || from === \"disconnected\" ? \"prejoin\" : null;\n case \"retry\":\n return isRefusedPhase(from) || from === \"unsupported\" ? \"resolving\" : null;\n case \"admission\": {\n // The server's word on a wait applies while joining or waiting — never to a live call\n // (being sent back to waiting from the room is an EXIT, resolved by §3.4).\n if (from !== \"joining\" && !WAITING_PHASES.has(from)) return null;\n return admissionPhase(event.admission);\n }\n case \"admitted\":\n return WAITING_PHASES.has(from) ? \"joining\" : null;\n case \"connected\":\n return from === \"joining\" ? \"in_call\" : null;\n case \"join_failed\":\n // ONLY a join can fail the join. A failure reported while in the room is not this event.\n return from === \"joining\" ? refusalPhase(event.reason) : null;\n case \"exit\":\n return from === \"in_call\" || from === \"joining\" ? event.to : null;\n case \"leave\":\n return from === \"in_call\" || from === \"joining\" || WAITING_PHASES.has(from) ? \"left\" : null;\n case \"ended\":\n return NOT_ENDABLE.has(from) ? null : \"ended\";\n case \"ring_out\":\n return IDLE_PHASES.has(from) || from === \"prejoin\" ? \"ringing_out\" : null;\n case \"ring_in\":\n return IDLE_PHASES.has(from) ? \"ringing_in\" : null;\n case \"call_answered\":\n return RINGING.has(from) ? \"joining\" : null;\n case \"call_outcome\":\n return RINGING.has(from) ? event.outcome : null;\n case \"reset\":\n // Never out from under a live connection: leave first.\n return LIVE_PHASES.has(from) ? null : \"resolving\";\n }\n}\n\n/* ------------------------------------------------------- §3.4 exit-reason resolution (ER) */\n\n/** What the server's state read (`GET /v1/meet/state?connection=`) says about this exit. */\nexport interface ExitServerWord {\n /** `meet_connections.exit_reason` of THIS connection, if the server recorded one. */\n readonly exitReason: string | null;\n /** The run's phase: `ended` means the meeting (this run) is over. */\n readonly sessionPhase: string | null;\n readonly meetingPhase?: string | null;\n}\n\nexport interface ExitInput {\n /** The server's word; `null` = the state read failed (offline / unreachable); undefined = no ST for this room (a call). */\n readonly server: ExitServerWord | null | undefined;\n /** LiveKit's `DisconnectReason` name (`PARTICIPANT_REMOVED`, `DUPLICATE_IDENTITY`, …), or null. */\n readonly hint: string | null;\n /** The page was frozen (Page Lifecycle) when the connection dropped. */\n readonly frozen?: boolean;\n}\n\nexport interface ExitResolution {\n readonly to: ExitPhase;\n /** `data-meet-reason` for the exit (null when the phase says it all). */\n readonly reason: string | null;\n /** `data-meet-connection` to show beside `disconnected`. */\n readonly connection: \"stable\" | \"offline\" | \"server_unreachable\";\n /** A notice code the skin renders with it (`sent-to-waiting`), when one applies. */\n readonly notice: string | null;\n}\n\n/** §3.4, row by row. Pure: the engine gathers the inputs, this decides. */\nexport function resolveExit(input: ExitInput): ExitResolution {\n const { server, hint } = input;\n if (server === null) {\n return { to: \"disconnected\", reason: null, connection: \"server_unreachable\", notice: null };\n }\n const word = server?.exitReason ?? null;\n if (server !== undefined && (server.sessionPhase === \"ended\" || server.meetingPhase === \"ended\" || word === \"ended\" || word === \"time_limit\")) {\n return { to: \"ended\", reason: word === \"time_limit\" ? \"time_limit\" : \"ended\", connection: \"stable\", notice: null };\n }\n if (word === \"removed\") return { to: \"removed\", reason: \"removed\", connection: \"stable\", notice: null };\n if (word === \"displaced\") return { to: \"superseded\", reason: \"displaced\", connection: \"stable\", notice: null };\n if (word === \"sent_to_waiting\") {\n return { to: \"waiting:knocking\", reason: \"sent_to_waiting\", connection: \"stable\", notice: \"sent-to-waiting\" };\n }\n if (hint === \"DUPLICATE_IDENTITY\") return { to: \"superseded\", reason: \"displaced\", connection: \"stable\", notice: null };\n if (hint === \"PARTICIPANT_REMOVED\" && word === null && server === undefined) {\n // A call room has no state read: LiveKit's own word is all there is.\n return { to: \"removed\", reason: \"removed\", connection: \"stable\", notice: null };\n }\n if (hint === \"ROOM_DELETED\" && server === undefined) {\n return { to: \"ended\", reason: \"ended\", connection: \"stable\", notice: null };\n }\n if (input.frozen === true) return { to: \"inactive\", reason: \"inactive\", connection: \"stable\", notice: null };\n return { to: \"disconnected\", reason: null, connection: \"stable\", notice: null };\n}\n","/**\n * THE APP PANEL SLOT (HR-360 wave 2, Meet register MD-15).\n *\n * A meeting can carry one APP PANEL: a surface the HOST APP registered under a key, opened\n * beside the call on one record (`meet_meetings.metadata.app_panel = {key, record_id}`,\n * written by `meet_schedule_meeting`'s `app_panel` setting). The package knows only the key\n * and the record id; the host app's registered component reads the record through the\n * person's OWN doors, so someone who cannot read the record sees nothing of it.\n *\n * Everyone in the call looks at the same SECTION of the panel: one shared focus per run,\n * kept by the server and carried on the existing collab packet (`collab.focus`,\n * action `focus_set`) — never a second channel.\n */\n\nimport { settingsPatch } from \"./repository\";\nimport type { MeetingRecord, MeetingSettings } from \"./types\";\n\nexport interface MeetingAppPanel {\n /** The registered surface key, e.g. `hr.review_360`. */\n readonly key: string;\n /** The record the panel opens on. */\n readonly recordId: string;\n}\n\n/** The app panel this meeting carries, or null. Malformed values are never guessed at. */\nexport function appPanelOf(meeting: Pick<MeetingRecord, \"metadata\"> | null | undefined): MeetingAppPanel | null {\n const raw = meeting?.metadata?.[\"app_panel\"];\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) return null;\n const key = (raw as Record<string, unknown>)[\"key\"];\n const recordId = (raw as Record<string, unknown>)[\"record_id\"];\n if (typeof key !== \"string\" || key.length === 0) return null;\n if (typeof recordId !== \"string\" || recordId.length === 0) return null;\n return { key, recordId };\n}\n\n/**\n * HR-360 wave 3: the server held capture off because the meeting is about a Confidential record\n * (`metadata.capture_hold = \"confidential_record\"`, stamped by the meet_meetings trigger) and it\n * is still off. The room says so in one short line. Once the organization turns capture back on\n * (knob `meet.confidential_capture`), it is no longer held.\n */\nexport function captureHeldOf(\n meeting: Pick<MeetingRecord, \"metadata\" | \"aiEnabled\" | \"recordingPolicy\"> | null | undefined,\n): boolean {\n if (!meeting) return false;\n return (\n meeting.metadata?.[\"capture_hold\"] === \"confidential_record\" &&\n !meeting.aiEnabled &&\n meeting.recordingPolicy === \"disabled\"\n );\n}\n\n/** The `p_settings` of `meet_schedule_meeting`, with the panel and the named observers. */\nexport function scheduleSettingsPatch(\n settings: MeetingSettings,\n appPanel: MeetingAppPanel | undefined,\n observers: readonly string[] | undefined,\n): Record<string, unknown> {\n const patch = settingsPatch(settings);\n if (appPanel !== undefined) patch[\"app_panel\"] = { key: appPanel.key, record_id: appPanel.recordId };\n if (observers !== undefined && observers.length > 0) patch[\"observers\"] = [...observers];\n return patch;\n}\n\n/** The one shared section focus of the meeting's app panel (`collab.focus`). */\nexport interface MeetAppFocus {\n readonly panel: string;\n readonly section: string;\n readonly byName: string;\n readonly at: string;\n}\n\nexport function focusFromRow(value: unknown): MeetAppFocus | null {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return null;\n const row = value as Record<string, unknown>;\n if (typeof row[\"panel\"] !== \"string\") return null;\n return {\n panel: row[\"panel\"],\n section: typeof row[\"section\"] === \"string\" ? row[\"section\"] : \"\",\n byName: typeof row[\"by_name\"] === \"string\" ? row[\"by_name\"] : \"\",\n at: typeof row[\"at\"] === \"string\" ? row[\"at\"] : \"\",\n };\n}\n","/**\n * MEETING ROOM ITEMS — polls, Q&A, the shared whiteboard and breakout rooms (Meet wave 5).\n *\n * The server decides every one of them (`aidream/services/meet/collab.py`,\n * `breakouts.py`): who may create, launch, vote, ask, answer, draw, erase, open rooms or\n * move people — and what each viewer SEES (a draft poll is the host's alone; counts\n * reach everyone only once shared; anonymous votes and questions carry no identity;\n * a dismissed question stays with the hosts). This file only holds what the server\n * said and folds the room's `matrx.meet.collab` packets into it.\n *\n * Two reads, one truth: `GET /v1/meet/collab` once per join (a late joiner sees every\n * poll, question, stroke and the open breakout plan), then packets. Every item is keyed\n * by its row id, so the same item arriving twice is one item.\n */\n\nimport { readServerRefusal } from \"./ai\";\nimport type { MeetApi } from \"./api\";\nimport { MEET_ROUTES, MEETING_PASS_HEADER } from \"./tokens\";\nimport type { ParticipantIdentity } from \"./types\";\nimport { focusFromRow, type MeetAppFocus } from \"./app-panel\";\n\nexport interface MeetPollOption {\n readonly id: string;\n readonly text: string;\n /** Present when this viewer may see counts (a host, or results shared). */\n readonly votes?: number | undefined;\n /** Who picked it — hosts only, never for an anonymous poll. */\n readonly voters?: readonly string[] | undefined;\n}\n\nexport type MeetPollState = \"draft\" | \"live\" | \"closed\";\n\nexport interface MeetPoll {\n readonly id: string;\n readonly question: string;\n readonly options: readonly MeetPollOption[];\n readonly multiple: boolean;\n readonly anonymous: boolean;\n readonly state: MeetPollState;\n readonly resultsShared: boolean;\n readonly voterCount: number | null;\n /** This viewer's own answer — kept locally, since a room packet cannot know it. */\n readonly myVote: readonly string[];\n readonly createdByName: string;\n readonly createdAt: string | null;\n}\n\nexport type MeetQuestionState = \"open\" | \"answered\" | \"dismissed\";\n\nexport interface MeetQuestion {\n readonly id: string;\n readonly text: string;\n readonly askerName: string;\n readonly anonymous: boolean;\n readonly state: MeetQuestionState;\n readonly answer: string | null;\n readonly answeredByName: string | null;\n readonly upvotes: number;\n readonly upvotedByMe: boolean;\n readonly mine: boolean;\n readonly createdAt: string | null;\n}\n\nexport interface MeetStroke {\n readonly id: string;\n /** Points in the board's own 0..1 space. */\n readonly points: readonly (readonly [number, number])[];\n readonly color: string;\n readonly width: number;\n readonly tool: \"pen\" | \"highlighter\";\n readonly by: string;\n readonly byName: string;\n /** True while this browser's stroke has not come back from the server. */\n readonly pending?: boolean | undefined;\n}\n\nexport interface MeetBreakoutRoom {\n readonly id: string;\n readonly name: string;\n readonly identities: readonly string[];\n}\n\nexport interface MeetBreakoutBroadcast {\n readonly text: string;\n readonly by: string;\n readonly at: string;\n}\n\nexport interface MeetBreakoutPlan {\n readonly id: string;\n readonly state: \"open\" | \"closed\";\n readonly rooms: readonly MeetBreakoutRoom[];\n readonly allowChoose: boolean;\n readonly minutes: number;\n readonly openedAt: string | null;\n readonly endsAt: string | null;\n readonly closedAt: string | null;\n readonly broadcasts: readonly MeetBreakoutBroadcast[];\n}\n\n/** The org's knobs for this meeting — which controls EXIST (absent, never dead). */\nexport interface MeetCollabSettings {\n readonly pollsEnabled: boolean;\n readonly pollMaxOptions: number;\n readonly qaEnabled: boolean;\n readonly qaAnonymousAllowed: boolean;\n readonly whiteboardEnabled: boolean;\n readonly breakoutsEnabled: boolean;\n readonly breakoutMaxRooms: number;\n readonly breakoutDefaultMinutes: number;\n readonly captionTranslationEnabled: boolean;\n /** Empty = the viewer's own language. */\n readonly captionTranslationLanguage: string;\n}\n\nexport type CollabPanel = \"polls\" | \"qa\" | \"whiteboard\" | \"breakouts\";\n\nexport interface CollabSnapshot {\n readonly loaded: boolean;\n /** Why the state could not be read, in words — or null. */\n readonly error: string | null;\n readonly settings: MeetCollabSettings | null;\n readonly polls: readonly MeetPoll[];\n readonly questions: readonly MeetQuestion[];\n readonly strokes: readonly MeetStroke[];\n readonly breakout: MeetBreakoutPlan | null;\n /** The newest message a host sent to every breakout room, for the banner. */\n readonly broadcast: MeetBreakoutBroadcast | null;\n /** A host moved THIS person (or everyone came back): where to go. `undefined` = nothing asked. */\n readonly moveRequest: { readonly roomId: string | null; readonly at: number } | null;\n /** Something asked a panel to open (a poll launched, a question arrived for a host). */\n readonly panelRequest: { readonly panel: CollabPanel; readonly at: number } | null;\n /** Caption translations for THIS viewer: line id → text, for `captionLanguage`. */\n readonly captionLanguage: string | null;\n readonly captionTranslations: Readonly<Record<string, string>>;\n readonly captionTranslationError: string | null;\n /**\n * HR-360 wave 2 (MD-15): the meeting's app panel's ONE shared section focus for this run,\n * as the server keeps it (`focus_set`); optional so a snapshot built before 0.11 compiles.\n */\n readonly focus?: MeetAppFocus | null;\n}\n\nexport const EMPTY_COLLAB: CollabSnapshot = {\n loaded: false,\n error: null,\n settings: null,\n polls: [],\n questions: [],\n strokes: [],\n breakout: null,\n broadcast: null,\n moveRequest: null,\n panelRequest: null,\n captionLanguage: null,\n captionTranslations: {},\n captionTranslationError: null,\n focus: null,\n};\n\ntype Row = Record<string, unknown>;\n\nfunction obj(value: unknown): Row | null {\n return typeof value === \"object\" && value !== null && !Array.isArray(value) ? (value as Row) : null;\n}\nfunction str(value: unknown): string | null {\n return typeof value === \"string\" ? value : null;\n}\nfunction num(value: unknown): number | null {\n return typeof value === \"number\" && Number.isFinite(value) ? value : null;\n}\nfunction strings(value: unknown): string[] {\n return Array.isArray(value) ? value.filter((v): v is string => typeof v === \"string\") : [];\n}\n\nexport function pollFromRow(row: Row): MeetPoll | null {\n const id = str(row[\"id\"]);\n if (id === null) return null;\n const options: MeetPollOption[] = [];\n for (const raw of Array.isArray(row[\"options\"]) ? row[\"options\"] : []) {\n const option = obj(raw);\n const optionId = option === null ? null : str(option[\"id\"]);\n if (option === null || optionId === null) continue;\n const votes = num(option[\"votes\"]);\n options.push({\n id: optionId,\n text: str(option[\"text\"]) ?? \"\",\n ...(votes !== null ? { votes } : {}),\n ...(Array.isArray(option[\"voters\"]) ? { voters: strings(option[\"voters\"]) } : {}),\n });\n }\n const state = str(row[\"state\"]);\n return {\n id,\n question: str(row[\"question\"]) ?? \"\",\n options,\n multiple: row[\"multiple\"] === true,\n anonymous: row[\"anonymous\"] === true,\n state: state === \"live\" || state === \"closed\" ? state : \"draft\",\n resultsShared: row[\"results_shared\"] === true,\n voterCount: num(row[\"voter_count\"]),\n myVote: strings(row[\"my_vote\"]),\n createdByName: str(row[\"created_by_name\"]) ?? \"\",\n createdAt: str(row[\"created_at\"]),\n };\n}\n\nexport function questionFromRow(row: Row): MeetQuestion | null {\n const id = str(row[\"id\"]);\n if (id === null) return null;\n const state = str(row[\"state\"]);\n return {\n id,\n text: str(row[\"text\"]) ?? \"\",\n askerName: str(row[\"asker_name\"]) ?? \"\",\n anonymous: row[\"anonymous\"] === true,\n state: state === \"answered\" || state === \"dismissed\" ? state : \"open\",\n answer: str(row[\"answer\"]),\n answeredByName: str(row[\"answered_by_name\"]),\n upvotes: num(row[\"upvotes\"]) ?? 0,\n upvotedByMe: row[\"upvoted_by_me\"] === true,\n mine: row[\"mine\"] === true,\n createdAt: str(row[\"created_at\"]),\n };\n}\n\nexport function strokeFromRow(row: Row): MeetStroke | null {\n const id = str(row[\"id\"]);\n if (id === null || !Array.isArray(row[\"points\"])) return null;\n const points: (readonly [number, number])[] = [];\n for (const raw of row[\"points\"]) {\n if (Array.isArray(raw) && typeof raw[0] === \"number\" && typeof raw[1] === \"number\") {\n points.push([raw[0], raw[1]]);\n }\n }\n return {\n id,\n points,\n color: str(row[\"color\"]) ?? \"#111827\",\n width: num(row[\"width\"]) ?? 3,\n tool: row[\"tool\"] === \"highlighter\" ? \"highlighter\" : \"pen\",\n by: str(row[\"by\"]) ?? \"\",\n byName: str(row[\"by_name\"]) ?? \"\",\n };\n}\n\nfunction broadcastFrom(value: unknown): MeetBreakoutBroadcast | null {\n const row = obj(value);\n const text = row === null ? null : str(row[\"text\"]);\n if (row === null || text === null) return null;\n return { text, by: str(row[\"by\"]) ?? \"\", at: str(row[\"at\"]) ?? \"\" };\n}\n\nexport function planFromRow(row: Row): MeetBreakoutPlan | null {\n const id = str(row[\"id\"]);\n if (id === null) return null;\n const rooms: MeetBreakoutRoom[] = [];\n for (const raw of Array.isArray(row[\"rooms\"]) ? row[\"rooms\"] : []) {\n const room = obj(raw);\n const roomId = room === null ? null : str(room[\"id\"]);\n if (room === null || roomId === null) continue;\n rooms.push({ id: roomId, name: str(room[\"name\"]) ?? roomId, identities: strings(room[\"identities\"]) });\n }\n return {\n id,\n state: row[\"state\"] === \"open\" ? \"open\" : \"closed\",\n rooms,\n allowChoose: row[\"allow_choose\"] === true,\n minutes: num(row[\"minutes\"]) ?? 0,\n openedAt: str(row[\"opened_at\"]),\n endsAt: str(row[\"ends_at\"]),\n closedAt: str(row[\"closed_at\"]),\n broadcasts: (Array.isArray(row[\"broadcasts\"]) ? row[\"broadcasts\"] : [])\n .map(broadcastFrom)\n .filter((b): b is MeetBreakoutBroadcast => b !== null),\n };\n}\n\nexport function settingsFromRow(row: Row | null): MeetCollabSettings | null {\n if (row === null) return null;\n return {\n pollsEnabled: row[\"polls_enabled\"] === true,\n pollMaxOptions: num(row[\"poll_max_options\"]) ?? 10,\n qaEnabled: row[\"qa_enabled\"] === true,\n qaAnonymousAllowed: row[\"qa_anonymous_allowed\"] === true,\n whiteboardEnabled: row[\"whiteboard_enabled\"] === true,\n breakoutsEnabled: row[\"breakouts_enabled\"] === true,\n breakoutMaxRooms: num(row[\"breakout_max_rooms\"]) ?? 20,\n breakoutDefaultMinutes: num(row[\"breakout_default_minutes\"]) ?? 15,\n captionTranslationEnabled: row[\"caption_translation_enabled\"] === true,\n captionTranslationLanguage: str(row[\"caption_translation_language\"]) ?? \"\",\n };\n}\n\n/** The server's `GET /collab` body → a loaded snapshot (keeps this viewer's caption state). */\nexport function collabFromState(body: Row, previous: CollabSnapshot): CollabSnapshot {\n const list = <T>(key: string, parse: (row: Row) => T | null): T[] =>\n (Array.isArray(body[key]) ? body[key] : [])\n .map((raw) => {\n const row = obj(raw);\n return row === null ? null : parse(row);\n })\n .filter((item): item is T => item !== null);\n const breakoutRow = obj(body[\"breakout\"]);\n return {\n ...previous,\n loaded: true,\n error: null,\n settings: settingsFromRow(obj(body[\"settings\"])),\n polls: list(\"polls\", pollFromRow),\n questions: list(\"questions\", questionFromRow),\n strokes: list(\"strokes\", strokeFromRow),\n breakout: breakoutRow === null ? null : planFromRow(breakoutRow),\n focus: focusFromRow(body[\"focus\"]),\n };\n}\n\nfunction upsert<T extends { id: string }>(items: readonly T[], next: T): T[] {\n const index = items.findIndex((item) => item.id === next.id);\n if (index === -1) return [...items, next];\n const copy = items.slice();\n copy[index] = next;\n return copy;\n}\n\n/**\n * Fold one server item into the snapshot. `fromRoom` = it came on the room broadcast,\n * which cannot know THIS viewer's vote, upvote or authorship — those are kept from what\n * the viewer's own reads and answers already said.\n */\nexport function withItem(collab: CollabSnapshot, row: Row, fromRoom = false): CollabSnapshot {\n const kind = str(row[\"kind\"]);\n if (kind === \"poll\") {\n const poll = pollFromRow(row);\n if (poll === null) return collab;\n // A ROOM packet cannot know this viewer's vote; keep the one we already hold.\n const known = collab.polls.find((p) => p.id === poll.id);\n let merged = fromRoom && known !== undefined ? { ...poll, myVote: known.myVote } : poll;\n // A room copy carries no counts until results are shared; a HOST already holding\n // counts (from their own read or the hosts' packet) keeps them until newer ones land.\n if (fromRoom && known !== undefined && !poll.options.some((o) => o.votes !== undefined)) {\n const counted = new Map(known.options.map((o) => [o.id, o]));\n merged = {\n ...merged,\n voterCount: known.voterCount,\n options: merged.options.map((o) => {\n const had = counted.get(o.id);\n return had === undefined ? o : { ...o, votes: had.votes, voters: had.voters };\n }),\n };\n }\n return { ...collab, polls: upsert(collab.polls, merged) };\n }\n if (kind === \"question\") {\n const question = questionFromRow(row);\n if (question === null) return collab;\n const known = collab.questions.find((q) => q.id === question.id);\n // `mine` / `upvoted_by_me` are per viewer; a room packet says false for everyone.\n const merged =\n fromRoom && known !== undefined\n ? { ...question, mine: known.mine, upvotedByMe: known.upvotedByMe }\n : question;\n return { ...collab, questions: upsert(collab.questions, merged) };\n }\n if (kind === \"stroke\") {\n const stroke = strokeFromRow(row);\n if (stroke === null) return collab;\n return { ...collab, strokes: upsert(collab.strokes, stroke) };\n }\n if (kind === \"breakout\") {\n const plan = planFromRow(row);\n if (plan === null) return collab;\n return { ...collab, breakout: plan };\n }\n return collab;\n}\n\nfunction without(collab: CollabSnapshot, ids: readonly string[]): CollabSnapshot {\n const gone = new Set(ids);\n return {\n ...collab,\n polls: collab.polls.filter((p) => !gone.has(p.id)),\n questions: collab.questions.filter((q) => !gone.has(q.id)),\n strokes: collab.strokes.filter((s) => !gone.has(s.id)),\n };\n}\n\nexport interface CollabPacketContext {\n readonly localIdentity: ParticipantIdentity | null;\n /** Hosts and co-hosts receive a second, fuller packet; they ignore the room copy. */\n readonly isHost: boolean;\n readonly now: number;\n}\n\n/**\n * One `matrx.meet.collab` packet → the next snapshot, or `null` when it must be dropped\n * (with the reason in `dropped`). Pure; the room-event dispatcher patches the store.\n */\nexport function applyCollabPacket(\n collab: CollabSnapshot,\n payload: Row,\n context: CollabPacketContext,\n): { next: CollabSnapshot | null; dropped?: string } {\n const item = obj(payload[\"item\"]);\n if (item !== null) {\n const kind = str(item[\"kind\"]);\n // Every copy is folded — the room's AND the hosts' fuller one. `withItem` keeps a\n // host's counts when a count-less room copy arrives, so neither copy can blank the\n // other, and a host still sees an item the server sent the room only (a new question).\n let next = withItem(collab, item, true);\n if (kind === \"poll\" && str(item[\"state\"]) === \"live\") {\n const before = collab.polls.find((p) => p.id === str(item[\"id\"]));\n if (before === undefined || before.state !== \"live\") {\n next = { ...next, panelRequest: { panel: \"polls\", at: context.now } };\n }\n }\n if (kind === \"breakout\") {\n const plan = planFromRow(item);\n if (plan !== null && plan.state === \"closed\") {\n next = { ...next, moveRequest: { roomId: null, at: context.now } };\n }\n }\n const move = obj(payload[\"move\"]);\n if (move !== null && str(move[\"identity\"]) === context.localIdentity) {\n next = { ...next, moveRequest: { roomId: str(move[\"room_id\"]), at: context.now } };\n }\n return { next };\n }\n if (payload[\"focus\"] !== undefined) {\n const focus = focusFromRow(payload[\"focus\"]);\n return focus === null ? { next: null, dropped: \"a focus packet without a panel\" } : { next: { ...collab, focus } };\n }\n if (Array.isArray(payload[\"removed\"])) {\n return { next: without(collab, strings(payload[\"removed\"])) };\n }\n if (Array.isArray(payload[\"hidden\"])) {\n // A dismissed question leaves everyone's list — but a host keeps it (their packet\n // carries the dismissed state).\n return context.isHost ? { next: null, dropped: \"hosts keep dismissed questions\" } : { next: without(collab, strings(payload[\"hidden\"])) };\n }\n if (payload[\"cleared\"] === \"whiteboard\") {\n return { next: { ...collab, strokes: [] } };\n }\n const broadcast = broadcastFrom(payload[\"broadcast\"]);\n if (broadcast !== null) {\n return { next: { ...collab, broadcast } };\n }\n return { next: null, dropped: \"a collab packet with nothing this version understands\" };\n}\n\n/** The breakout room a person is assigned to, or null. */\nexport function assignedRoom(plan: MeetBreakoutPlan | null, identity: string | null): MeetBreakoutRoom | null {\n if (plan === null || plan.state !== \"open\" || identity === null) return null;\n return plan.rooms.find((room) => room.identities.includes(identity)) ?? null;\n}\n\n/**\n * Split people into `rooms` rooms, round-robin in the order given — Zoom's \"assign\n * automatically\". Hosts are left out (they visit); pass them in `exclude`.\n */\nexport function autoAssign(\n identities: readonly string[],\n rooms: number,\n exclude: readonly string[] = [],\n): string[][] {\n const count = Math.max(1, Math.floor(rooms));\n const out: string[][] = Array.from({ length: count }, () => []);\n const skip = new Set(exclude);\n let index = 0;\n for (const identity of identities) {\n if (skip.has(identity)) continue;\n out[index % count]!.push(identity);\n index += 1;\n }\n return out;\n}\n\n/** Milliseconds until the plan's timer ends, or null when it has none. */\nexport function breakoutRemainingMs(plan: MeetBreakoutPlan | null, now: number): number | null {\n if (plan === null || plan.state !== \"open\" || plan.endsAt === null) return null;\n const end = Date.parse(plan.endsAt);\n return Number.isNaN(end) ? null : Math.max(0, end - now);\n}\n\n/** The language translated captions start in: the knob, else the viewer's own. */\nexport function defaultCaptionLanguage(\n settings: MeetCollabSettings | null,\n viewerLanguage: string | null | undefined,\n): string {\n const configured = settings?.captionTranslationLanguage.trim() ?? \"\";\n if (configured.length > 0) return configured;\n const own = (viewerLanguage ?? \"\").trim();\n return own.length > 0 ? own : \"en\";\n}\n\n/** Languages offered in the captions menu (tags the translator understands). */\nexport const CAPTION_LANGUAGES: readonly { readonly tag: string; readonly name: string }[] = [\n { tag: \"en\", name: \"English\" },\n { tag: \"es\", name: \"Spanish\" },\n { tag: \"fr\", name: \"French\" },\n { tag: \"de\", name: \"German\" },\n { tag: \"it\", name: \"Italian\" },\n { tag: \"pt\", name: \"Portuguese\" },\n { tag: \"nl\", name: \"Dutch\" },\n { tag: \"pl\", name: \"Polish\" },\n { tag: \"ru\", name: \"Russian\" },\n { tag: \"uk\", name: \"Ukrainian\" },\n { tag: \"tr\", name: \"Turkish\" },\n { tag: \"ar\", name: \"Arabic\" },\n { tag: \"he\", name: \"Hebrew\" },\n { tag: \"fa\", name: \"Persian\" },\n { tag: \"hi\", name: \"Hindi\" },\n { tag: \"bn\", name: \"Bengali\" },\n { tag: \"zh\", name: \"Chinese\" },\n { tag: \"ja\", name: \"Japanese\" },\n { tag: \"ko\", name: \"Korean\" },\n { tag: \"vi\", name: \"Vietnamese\" },\n { tag: \"th\", name: \"Thai\" },\n { tag: \"id\", name: \"Indonesian\" },\n { tag: \"sv\", name: \"Swedish\" },\n];\n\nexport interface MeetingActivities {\n /** Polls (with results), questions, whiteboard strokes — or null when refused. */\n readonly collab: CollabSnapshot | null;\n readonly refusal: { readonly message: string; readonly remedy: string } | null;\n}\n\n/**\n * THE MEETING'S POLLS, Q&A AND WHITEBOARD, AFTER IT (Meet wave 5) — for the record page.\n * The same `GET /v1/meet/collab` the room reads on join; a refusal is returned as data,\n * so the record still renders everything else.\n */\nexport async function loadMeetingActivities(\n api: MeetApi,\n meetingId: string,\n meetingPass: string | null = null,\n): Promise<MeetingActivities> {\n let response: Response;\n try {\n response = await api.request({\n path: `${MEET_ROUTES.collab}?meeting_id=${encodeURIComponent(meetingId)}`,\n method: \"GET\",\n operation: \"meet.loadMeetingActivities\",\n requireAuth: meetingPass === null,\n ...(meetingPass !== null ? { headers: { [MEETING_PASS_HEADER]: meetingPass } } : {}),\n });\n } catch (error) {\n return {\n collab: null,\n refusal: {\n message: `Polls, questions and the whiteboard could not be read (${(error as Error).message}).`,\n remedy: (error as { remedy?: string }).remedy ?? \"Reload the page.\",\n },\n };\n }\n if (!response.ok) {\n let body: unknown = null;\n try {\n body = await response.json();\n } catch {\n body = null;\n }\n const said = readServerRefusal(body);\n return {\n collab: null,\n refusal: {\n message: said?.reason ?? `Polls and questions could not be read (the server answered ${response.status}).`,\n remedy: said?.remedy || \"Reload the page.\",\n },\n };\n }\n return { collab: collabFromState((await response.json()) as Row, EMPTY_COLLAB), refusal: null };\n}\n","/**\n * CAPTIONS ARE HINTS; SEGMENTS ARE TRUTH (D16).\n *\n * Two independent streams describe the same speech and they arrive out of order\n * with different identities:\n *\n * 1. **Captions** ride the LiveKit `lk.transcription` text stream, keyed by\n * `lk.segment_id`, interim-then-final, milliseconds behind the voice. They\n * exist so a person can READ the room live. Their `sender_identity` is\n * supplied by the publishing client and LiveKit does not validate it, so\n * nothing downstream — notes, Q&A, the wrap-up — may ever read them.\n * 2. **Records** are `communication.meet_transcript_segments` rows, written\n * server-side by the note-taker with a deterministic id (D6) and delivered\n * here by Postgres Changes. They are the meeting's transcript.\n *\n * The reconciliation below is what keeps the screen from saying everything\n * twice. A record REPLACES the caption it supersedes — matched by the caption\n * id the worker carries into the row, and failing that by speaker + text — and\n * a caption never overwrites a record, however late it lands.\n *\n * Everything here is a pure function over a list so the rules are provable\n * without a store, a socket, or a browser.\n */\n\nimport type { ParticipantIdentity, TranscriptSegment } from \"./types\";\n\n/** Comparable text: case, punctuation and spacing are not differences. */\nexport function normalizeSpoken(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^\\p{L}\\p{N}]+/gu, \" \")\n .trim();\n}\n\nfunction supersedes(record: TranscriptSegment, caption: TranscriptSegment): boolean {\n if (caption.source !== \"caption\") return false;\n if (caption.identity !== record.identity) return false;\n if (record.captionId !== null && caption.captionId === record.captionId) return true;\n const left = normalizeSpoken(record.text);\n const right = normalizeSpoken(caption.text);\n if (left.length === 0 || right.length === 0) return false;\n // An interim caption is a PREFIX of what was finally said; a final caption and\n // its record usually differ only in punctuation. Both are the same utterance.\n return left === right || left.startsWith(right) || right.startsWith(left);\n}\n\n/**\n * A caption arrived. Interim replaces interim in place; a final never loses to a\n * late interim; a record is never touched.\n */\nexport function mergeCaption(\n list: readonly TranscriptSegment[],\n caption: TranscriptSegment,\n): readonly TranscriptSegment[] {\n const index = list.findIndex(\n (entry) => entry.captionId !== null && entry.captionId === caption.captionId,\n );\n if (index === -1) {\n // The record for this utterance may already be here (the row can beat the\n // caption on a slow media path). Do not draw the same sentence twice.\n if (list.some((entry) => entry.source === \"record\" && supersedes(entry, caption))) {\n return list;\n }\n return [...list, caption];\n }\n const existing = list[index];\n if (existing === undefined) return list;\n // TRUTH WINS. A caption cannot revert a durable row, and a late interim cannot\n // revert a final — that is a finished sentence visibly becoming a half-word.\n if (existing.source === \"record\") return list;\n if (existing.final && !caption.final) return list;\n return list.map((entry, position) => (position === index ? caption : entry));\n}\n\n/**\n * A durable row arrived. It replaces the caption it supersedes — in place, so\n * the line does not jump down the panel — or lands as a new line if no caption\n * ever showed it (a surface that joined late, or captions that never came).\n */\nexport function mergeRecord(\n list: readonly TranscriptSegment[],\n record: TranscriptSegment,\n): readonly TranscriptSegment[] {\n const byId = list.findIndex((entry) => entry.source === \"record\" && entry.id === record.id);\n if (byId !== -1) {\n const existing = list[byId];\n if (existing !== undefined && existing.text === record.text && existing.final === record.final) {\n return list;\n }\n return list.map((entry, position) => (position === byId ? record : entry));\n }\n const captionIndex = list.findIndex((entry) => supersedes(record, entry));\n if (captionIndex === -1) return [...list, record];\n return list.map((entry, position) => (position === captionIndex ? record : entry));\n}\n\n/** The transcript a person reads: chronological, and never an empty line. */\nexport function readableTranscript(\n list: readonly TranscriptSegment[],\n): readonly TranscriptSegment[] {\n return [...list]\n .filter((entry) => entry.text.trim().length > 0)\n .sort((left, right) => left.startedAt.localeCompare(right.startedAt));\n}\n\n/** Build a caption line from the media plane. */\nexport function captionSegment(args: {\n captionId: string;\n meetingId: TranscriptSegment[\"meetingId\"];\n identity: ParticipantIdentity;\n speaker: string;\n text: string;\n final: boolean;\n startedAt: string;\n}): TranscriptSegment {\n return {\n id: `caption:${args.captionId}`,\n meetingId: args.meetingId,\n identity: args.identity,\n speaker: args.speaker,\n text: args.text,\n startedAt: args.startedAt,\n final: args.final,\n source: \"caption\",\n captionId: args.captionId,\n };\n}\n","/**\n * THE ROOM SNAPSHOT — one immutable object the UI renders and nothing else.\n *\n * Deliberately NOT Redux, NOT a context full of `useState`, and NOT a bag of\n * refs: one store with `getSnapshot`/`subscribe` is exactly what\n * `useSyncExternalStore` wants, which is what makes tearing impossible when a\n * roster event and a speaker event land in the same tick.\n *\n * The rules encoded here:\n *\n * - **The roster is replaced wholesale, never patched.** The driver hands us\n * the full truth on every media event (see `room-driver.ts`); merging deltas\n * from six event sources is how a tile for someone who left stays on screen.\n * - **Ephemera EXPIRE.** Reactions and speaking flags are time-boxed in the\n * snapshot, so a dropped \"stop\" event cannot leave a permanent 🎉 or a\n * permanently-speaking ring. (Nothing here relies on receiving an end event.)\n * - **An interim transcript line is REPLACED in place by its final.** Appending\n * both is how a transcript ends up saying everything twice.\n */\n\nimport { displayNameFor, learnName, learnNames, type NameBook } from \"./names\";\nimport { isRecordingInFlight } from \"./recording\";\nimport { IDLE_MEDIA, type LocalMediaState, type LocalTrackState } from \"./attendance\";\nimport { ROLE_RANK } from \"./types\";\nimport { nextPhase, reasonOf, type MeetPhase, type PhaseEvent } from \"./phase\";\nimport type { ObserversVisibleTo } from \"./types\";\nimport type { NoteTakerRefusal } from \"./note-taker\";\nimport { EMPTY_COLLAB, type CollabSnapshot } from \"./collab\";\nimport { mergeCaption, mergeRecord } from \"./transcript\";\nimport type {\n AdmissionState,\n ConnectionQuality,\n LobbyEntry,\n MeetParticipant,\n MeetingNote,\n MeetingRecord,\n ParticipantIdentity,\n ParticipantRole,\n ParticipantStanding,\n RecordingSource,\n RecordingState,\n StageLayout,\n TranscriptSegment,\n UserId,\n WrapUpAnnouncement,\n RoomName,\n CallInvite,\n} from \"./types\";\n\nexport interface RoomChatMessage {\n readonly id: string;\n readonly identity: ParticipantIdentity;\n readonly displayName: string;\n readonly text: string;\n readonly at: string;\n /**\n * Set for a PRIVATE message: the one identity it was addressed to (from the\n * sender's side) or this participant (from the receiver's). Private messages\n * travel the data plane only and are never saved (Meet wave 2).\n */\n readonly to?: ParticipantIdentity | null | undefined;\n /** True once the server has SAVED it — a message everyone sees, late joiners too. */\n readonly saved?: boolean | undefined;\n /** A send the server refused, with its sentence. The message stays visible, marked. */\n readonly failed?: string | undefined;\n}\n\n/**\n * A HOST ACTED ON *ME* (Meet wave 2) — the server's own `matrx.meet.moderation`\n * packet, addressed to this participant. Rendered once, dismissible, in words.\n */\nexport interface ModerationNotice {\n readonly kind: \"muted\" | \"ask-unmute\" | \"removed\" | \"role\";\n readonly text: string;\n readonly at: number;\n}\n\n/** What this participant chose for their own media. Remembered per browser. */\nexport interface MediaEffects {\n readonly noiseSuppression: boolean;\n readonly background: \"none\" | \"blur\" | \"image\";\n readonly backgroundImage: string | null;\n}\n\nexport const DEFAULT_EFFECTS: MediaEffects = {\n noiseSuppression: true,\n background: \"none\",\n backgroundImage: null,\n};\n\nexport interface RoomReaction {\n readonly id: string;\n readonly identity: ParticipantIdentity;\n readonly emoji: string;\n readonly at: number;\n}\n\n/**\n * 🚨 WHAT THE SERVER GRANTED THIS JOINER, straight off the token response\n * (MRI-A11). It is placed in the snapshot the instant the token lands — before\n * the LiveKit roster exists at all — so a host's controls are correct on the\n * first frame of the room rather than one roster event later. The roster entry\n * for the local participant carries the SAME decision as a LiveKit attribute;\n * the two agree because one server statement produced both, and the UI reads\n * whichever has arrived.\n */\nexport interface MeetGrants {\n readonly role: ParticipantRole;\n /** Where this joiner stands with the meeting (server-minted, beside the role). */\n readonly standing?: ParticipantStanding | null;\n readonly canRecord: boolean;\n readonly canPublish: boolean;\n readonly canSubscribe: boolean;\n /** HR-360 wave 2 (MD-16): who sees the \"Observing\" line in People (knob). */\n readonly observersVisibleTo?: ObserversVisibleTo | undefined;\n}\n\n/** No token yet grants nothing. Never \"assume host until told otherwise\". */\nexport const NO_GRANTS: MeetGrants = {\n role: \"participant\",\n standing: null,\n canRecord: false,\n canPublish: false,\n canSubscribe: false,\n};\n\n/**\n * S4 — THIS PERSON'S WAIT, as the server's state read (`GET /v1/meet/state`) last said it\n * (CORE-DESIGN §2.6). Set while `phase === \"lobby\"`; the waiting screens read it, never a\n * broadcast. `admission` is the row's own word: `waiting_host` · `knocking` · `denied` ·\n * `expired` · `withdrawn` · `ended` (`admitted` is never held here — it is a join).\n */\nexport interface MeetWaiting {\n readonly admission: AdmissionState;\n /** Is anyone who may admit in the room? null until the first read. */\n readonly admittersPresent: boolean | null;\n /** May a denied person ask again (`reknock_after_deny`)? */\n readonly reknockAllowed: boolean;\n readonly knockExpiresAt: string | null;\n /** How often the core reads while waiting (`waiting_poll_seconds`). */\n readonly pollSeconds: number;\n /** The run's `live_version` at the last read. */\n readonly liveVersion: number;\n}\n\n/** The core's `connection` region (CORE-DESIGN §3.2) — never a phase. */\nexport type ConnectionRegion = \"stable\" | \"signal_reconnecting\" | \"reconnecting\" | \"offline\" | \"server_unreachable\";\n\n/**\n * The core's `share` region (CORE-DESIGN §3.2/§3.3 SA). A refused share is a situation the person\n * is in — a notice (`share-unavailable`), never a phase.\n */\nexport interface ShareRegion {\n readonly state: \"none\" | \"picking\" | \"presenting\" | \"refused\";\n /** Why a share was refused: this browser cannot share, the system blocks it, or it failed. */\n readonly reason: \"unsupported\" | \"blocked\" | \"failed\" | null;\n /** The fix, in the core's words (system setting, administrator policy, …), or null. */\n readonly remedy: string | null;\n readonly at: number;\n}\n\nexport const NO_SHARE: ShareRegion = { state: \"none\", reason: null, remedy: null, at: 0 };\n\nexport interface MeetSnapshot {\n /**\n * WHERE THIS TAB IS — the contract's `data-meet-phase` value, verbatim (CORE-DESIGN §3.1).\n * Written ONLY by `store.dispatch(event)` through the phase machine (`core/phase.ts`);\n * `patch` cannot touch it.\n */\n readonly phase: MeetPhase;\n /** `data-meet-reason` for a refused / terminal phase; null otherwise. */\n readonly reason: string | null;\n /** A notice that came WITH the phase (`sent-to-waiting`), or null. */\n readonly phaseNotice: string | null;\n /** The room this tab last joined (or is joining) — a different meeting mounting resets. */\n readonly roomName: RoomName | null;\n /** The `connection` region: reconnecting is here, never a phase. */\n readonly connection: ConnectionRegion;\n /** The `share` region. */\n readonly share: ShareRegion;\n /**\n * The 1:1 call this tab is placing or answering (CORE-DESIGN §3.6) — the core `calls` slice.\n * The ringing phases (`ringing_out` / `ringing_in` / `call_*`) describe it; null = no call.\n */\n readonly call: { readonly invite: CallInvite; readonly direction: \"outgoing\" | \"incoming\" } | null;\n /** S4: this person's wait (see `MeetWaiting`); null outside the waiting room. */\n readonly waiting: MeetWaiting | null;\n readonly meeting: MeetingRecord | null;\n readonly localIdentity: ParticipantIdentity | null;\n /** The server's word on what THIS joiner may do. See `MeetGrants`. */\n readonly grants: MeetGrants;\n readonly participants: readonly MeetParticipant[];\n readonly activeSpeakers: readonly ParticipantIdentity[];\n readonly pinned: ParticipantIdentity | null;\n readonly layout: StageLayout;\n readonly lobby: readonly LobbyEntry[];\n readonly locked: boolean;\n /**\n * 🚨 THE DURABLE `meet_recordings.state`, not a client's hope. Through 0.4.7\n * this only ever held what THIS browser optimistically patched — `starting`\n * on the click, `stopping` on the stop — so the indicator read \"Starting\n * recording…\" for the whole meeting while a perfectly healthy egress ran, and\n * \"Stopping recording…\" forever after (MRI-C8). The server's transitions\n * arrive on two transports now: the `meet_recordings` row for a member, and\n * a `matrx.meet.recording` room event for a guest, who has no database.\n */\n readonly recording: RecordingState;\n readonly recordingStartedBy: UserId | null;\n /**\n * WHICH transport last spoke, so the two cannot undo each other (D16). The\n * database wins: `\"record\"` is never overwritten by `\"room\"` or `\"local\"`.\n */\n readonly recordingSource: RecordingSource;\n /**\n * WHICH recording this state is about. It is what tells a LATE packet for a\n * finished recording apart from the FIRST packet of the next one — without\n * it, \"the database wins\" either blocks a second recording forever or lets a\n * stale packet drag a finished one back to `recording`.\n */\n readonly recordingId: string | null;\n readonly chat: readonly RoomChatMessage[];\n /** Whether the SAVED chat has been read for this meeting (late joiners see history). */\n readonly chatLoaded: boolean;\n /** Why the saved chat could not be read on join, in words — or null. */\n readonly chatHistoryError: string | null;\n readonly reactions: readonly RoomReaction[];\n /**\n * The participant a HOST spotlighted for everyone, from the room's metadata\n * (server-set, delivered on join). A local pin outranks it on this screen only.\n */\n readonly spotlight: ParticipantIdentity | null;\n /** The last thing a host did to THIS participant, in words. Dismissible. */\n readonly moderationNotice: ModerationNotice | null;\n /**\n * The server's answer to the last host action THIS participant took (\"Muted 3\n * people\", or the refusal). One place, so whichever surface acted — a tile's\n * menu, the People panel, More options — the sentence survives the menu closing.\n */\n readonly actionNotice: { readonly text: string; readonly failed: boolean; readonly at: number } | null;\n /**\n * \"Message privately\" asked for from ANY per-person menu — a tile's, the\n * People panel's — and answered by the one `ControlBar` that owns the chat\n * panel (0.7.8). Through 0.7.7 only the People panel could open a private\n * chat, so the Board's tile menu (and the Room's) had no such item while the\n * People panel did: two menus for one person that disagreed.\n */\n readonly privateChatRequest: { readonly to: ParticipantIdentity; readonly at: number } | null;\n readonly effects: MediaEffects;\n /**\n * Captions AND durable rows, reconciled into one list (D16). A record\n * replaces the caption it supersedes; a caption never overwrites a record.\n */\n readonly transcript: readonly TranscriptSegment[];\n readonly notes: readonly MeetingNote[];\n /**\n * 🚨 IDENTITY → THE BEST NAME ANYTHING HAS EVER KNOWN (MRI-A16). Learned from\n * every source that has one — the roster, a durable segment, a durable\n * attendance row, a chat message — and never unlearned, so a caption that\n * lands after somebody leaves still says who spoke. It exists because the\n * host and the guest rendered DIFFERENT names for the same speaker in the\n * same second, one of them a raw LiveKit token (MRI-C8). See `core/names.ts`.\n */\n readonly names: NameBook;\n /**\n * The wrap-up pass ANNOUNCED itself (MRI-A12). `null` means it has not been\n * announced in this room — which is not the same as \"no summary exists\", and\n * no surface may render it as such.\n */\n readonly wrapUp: WrapUpAnnouncement | null;\n /**\n * The server's last refusal to dispatch the note-taker, in ITS words (D7).\n * Rendered verbatim; never replaced by a spinner or a house sentence.\n */\n readonly noteTakerRefusal: NoteTakerRefusal | null;\n /**\n * What the LOCAL participant is actually publishing, and why not when it is\n * not. Attendance never depends on it (see `attendance.ts`): a refused\n * microphone is a state with a remedy, not a failed join.\n */\n readonly media: LocalMediaState;\n /** The last failure, with its remedy. Never null-and-hoping. */\n readonly failure: {\n readonly message: string;\n readonly remedy: string;\n /** The join gate's reason code, when the server named one (CORE-DESIGN §2.5). */\n readonly reason?: string | null;\n } | null;\n /**\n * Meet wave 5: polls, Q&A, the shared whiteboard and the breakout plan — what the\n * server said, per viewer (`core/collab.ts`). Read once per join, then packets.\n */\n readonly collab: CollabSnapshot;\n /** The breakout room THIS participant is in, or null for the main room. */\n readonly breakoutRoomId: string | null;\n}\n\nconst EMPTY: MeetSnapshot = {\n phase: \"resolving\",\n reason: null,\n phaseNotice: null,\n roomName: null,\n connection: \"stable\",\n share: NO_SHARE,\n call: null,\n waiting: null,\n meeting: null,\n localIdentity: null,\n grants: NO_GRANTS,\n participants: [],\n activeSpeakers: [],\n pinned: null,\n layout: \"grid\",\n lobby: [],\n locked: false,\n recording: \"idle\",\n recordingStartedBy: null,\n recordingSource: \"local\",\n recordingId: null,\n chat: [],\n chatLoaded: false,\n chatHistoryError: null,\n reactions: [],\n spotlight: null,\n moderationNotice: null,\n actionNotice: null,\n privateChatRequest: null,\n effects: DEFAULT_EFFECTS,\n transcript: [],\n notes: [],\n names: {},\n wrapUp: null,\n noteTakerRefusal: null,\n media: IDLE_MEDIA,\n failure: null,\n collab: EMPTY_COLLAB,\n breakoutRoomId: null,\n};\n\n/**\n * 🚨 THE ONE ANSWER TO \"WHAT ROLE DOES THIS CLIENT'S USER HOLD\" (MRI-A11).\n *\n * Two SERVER-minted carriers, one decision: the `/meet/token` response (in the\n * store from the moment the token lands, before any roster exists) and the\n * local participant's roster entry (whose role comes from the\n * `matrx.participant.role` LiveKit attribute). The stronger wins. Every host\n * power in this package — the UI that renders it AND the code that performs it\n * — asks THIS function, so a control can never render from one source and then\n * be refused by another. That split is exactly how a \"host control\" becomes a\n * button that throws.\n */\nexport function effectiveRole(snapshot: MeetSnapshot): ParticipantRole {\n const me = snapshot.participants.find(\n (participant) => participant.identity === snapshot.localIdentity,\n );\n const fromRoster = me?.role ?? \"participant\";\n const fromToken = snapshot.grants.role;\n return ROLE_RANK[fromRoster] >= ROLE_RANK[fromToken] ? fromRoster : fromToken;\n}\n\n/** This person's standing: the token's word, else what the roster carries for them. */\nexport function effectiveStanding(snapshot: MeetSnapshot): ParticipantStanding | null {\n if (snapshot.grants.standing != null) return snapshot.grants.standing;\n const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);\n return me?.standing ?? null;\n}\n\n/** Reactions live this long. A dropped end event cannot outlive it. */\nexport const REACTION_TTL_MS = 5_000;\n/** In-room chat is EPHEMERAL by design (R3) — this is the visible backlog. */\nconst CHAT_LIMIT = 200;\n\n/** What `patch` accepts: the snapshot minus the machine-owned fields. */\nexport type MeetPatch = Partial<Omit<MeetSnapshot, \"phase\" | \"reason\" | \"phaseNotice\">>;\n\nexport interface MeetStore {\n snapshot(): MeetSnapshot;\n subscribe(listener: () => void): () => void;\n /** Everything EXCEPT the phase. The phase moves only through `dispatch`. */\n patch(next: MeetPatch): void;\n /**\n * THE ONE WRITER OF THE PHASE (CORE-DESIGN §3.1). Applies `event` through the machine's table;\n * returns false — and changes nothing — when the table does not allow it from the current\n * phase. `reason` overrides the phase's own reason (an exit's `removed`, `displaced`, …).\n */\n dispatch(event: PhaseEvent, detail?: { readonly reason?: string | null; readonly notice?: string | null }): boolean;\n setParticipants(participants: readonly MeetParticipant[]): void;\n setSpeaking(identities: readonly ParticipantIdentity[]): void;\n setQuality(identity: ParticipantIdentity, quality: ConnectionQuality): void;\n /**\n * Raise or lower a hand. `at` is WHEN it went up, which is the hand queue's\n * order — first raised, first called on, the way Meet orders it.\n */\n setHand(identity: ParticipantIdentity, raised: boolean, at?: number): void;\n addChat(message: RoomChatMessage): void;\n /** The saved chat, read once per join. Merged with anything that arrived live. */\n loadChat(messages: readonly RoomChatMessage[]): void;\n /** Replace a pending message (by id) with what the server saved, or mark it failed. */\n settleChat(localId: string, next: RoomChatMessage | { failed: string }): void;\n addReaction(reaction: RoomReaction): void;\n /** Drops reactions past their TTL. Called by the UI's animation frame. */\n expireReactions(now: number): void;\n /**\n * The recording reached a state, and WHO says so. `\"local\"` is this browser's\n * optimistic `starting`/`stopping`; `\"room\"` is the server's packet on the\n * media plane; `\"record\"` is the durable row. Precedence is D16's: the\n * database's word stands until the database changes it.\n */\n setRecording(\n state: RecordingState,\n by: UserId | null,\n source: RecordingSource,\n recordingId?: string | null,\n ): void;\n upsertLobby(entry: LobbyEntry): void;\n setAdmission(identity: ParticipantIdentity, state: AdmissionState, by: UserId | null): void;\n /**\n * Teach the name book. Every source that knows a name calls this, and only\n * a REAL name is learned — an identity handed back as its own name is not one.\n */\n learn(entries: readonly { identity: ParticipantIdentity; displayName?: string | null }[]): void;\n /** A live caption from the media plane. A HINT (D16). */\n ingestCaption(segment: TranscriptSegment): void;\n /** A durable `meet_transcript_segments` row. THE TRANSCRIPT (D16). */\n ingestRecord(segment: TranscriptSegment): void;\n /** Durable notes are re-written in place — a re-run rewrites the same ids. */\n upsertNote(note: MeetingNote): void;\n /** The wrap-up pass finished. Announced on the media plane only (MRI-A12). */\n announceWrapUp(announcement: WrapUpAnnouncement): void;\n /** Record what a local track ACTUALLY did — published, or refused and why. */\n setLocalTrack(kind: \"microphone\" | \"camera\", state: LocalTrackState): void;\n reset(): void;\n}\n\nexport function createMeetStore(initial: Partial<MeetSnapshot> = {}): MeetStore {\n let state: MeetSnapshot = { ...EMPTY, ...initial };\n const listeners = new Set<() => void>();\n\n function commit(next: MeetSnapshot): void {\n if (next === state) return;\n state = next;\n for (const listener of listeners) listener();\n }\n\n return {\n snapshot: () => state,\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n patch(next) {\n if (\"phase\" in next || \"reason\" in next || \"phaseNotice\" in next) {\n // The type forbids it; this catches a cast. The phase has ONE writer.\n throw new TypeError(\"meet store: the phase moves only through store.dispatch(event)\");\n }\n commit({ ...state, ...next });\n },\n dispatch(event, detail) {\n const to = nextPhase(state.phase, event);\n if (to === null) return false;\n const reason = detail?.reason !== undefined ? detail.reason : reasonOf(to);\n const phaseNotice = detail?.notice ?? null;\n if (to === state.phase && reason === state.reason && phaseNotice === state.phaseNotice) return true;\n commit({ ...state, phase: to, reason, phaseNotice });\n return true;\n },\n setParticipants(participants) {\n // The roster TEACHES the book first — a participant whose token carried a\n // real name is where most names come from — and every roster entry is then\n // RESOLVED back through it, so a participant the roster cannot name still\n // gets the best name a durable row or a caption ever taught (MRI-A18).\n const names = learnNames(state.names, participants);\n // Hand-raise state lives on OUR side of the wire (it rides the data\n // plane, not the media plane), so it is carried across a roster replace\n // rather than reset — otherwise every mute event lowers every hand.\n const hands = new Map(state.participants.map((p) => [p.identity, p.handRaised]));\n const handTimes = new Map(state.participants.map((p) => [p.identity, p.handRaisedAt ?? null]));\n const quality = new Map(state.participants.map((p) => [p.identity, p.quality]));\n commit({\n ...state,\n names,\n participants: participants.map((participant) => ({\n ...participant,\n // 🚨 THE ONE ROSTER RESOLUTION. Every surface that renders a person —\n // the tile, the People row, the header summary, every Pin/Mute label —\n // reads `participant.displayName`, so this is the single place a raw\n // identity could reach a screen, and it is the place that asks the\n // name book. `roster-names.test.ts` fails if a token ever gets past it.\n displayName: displayNameFor(names, participant.identity, participant.displayName),\n handRaised: hands.get(participant.identity) ?? participant.handRaised,\n handRaisedAt: handTimes.get(participant.identity) ?? participant.handRaisedAt ?? null,\n quality:\n participant.quality === \"unknown\"\n ? quality.get(participant.identity) ?? \"unknown\"\n : participant.quality,\n })),\n });\n },\n setSpeaking(identities) {\n const speaking = new Set(identities);\n commit({\n ...state,\n activeSpeakers: identities,\n participants: state.participants.map((participant) =>\n participant.speaking === speaking.has(participant.identity)\n ? participant\n : { ...participant, speaking: speaking.has(participant.identity) },\n ),\n });\n },\n setQuality(identity, quality) {\n commit({\n ...state,\n participants: state.participants.map((participant) =>\n participant.identity === identity ? { ...participant, quality } : participant,\n ),\n });\n },\n setHand(identity, raised, at) {\n commit({\n ...state,\n participants: state.participants.map((participant) =>\n participant.identity === identity\n ? {\n ...participant,\n handRaised: raised,\n // A hand that is ALREADY up keeps its place in the queue when\n // it is re-announced (to a late joiner): the first time wins.\n handRaisedAt: raised\n ? participant.handRaised && participant.handRaisedAt != null\n ? participant.handRaisedAt\n : at ?? Date.now()\n : null,\n }\n : participant,\n ),\n });\n },\n addChat(message) {\n if (state.chat.some((existing) => existing.id === message.id)) return;\n const chat = [...state.chat, message];\n // A chat message carries its sender's name — another source, same book.\n const names = learnName(state.names, message.identity, message.displayName);\n commit({ ...state, chat: chat.slice(-CHAT_LIMIT), names });\n },\n loadChat(messages) {\n const known = new Set(state.chat.map((message) => message.id));\n const merged = [...messages.filter((message) => !known.has(message.id)), ...state.chat].sort(\n (a, b) => (a.at < b.at ? -1 : a.at > b.at ? 1 : 0),\n );\n const names = learnNames(\n state.names,\n messages.map((message) => ({ identity: message.identity, displayName: message.displayName })),\n );\n commit({ ...state, chat: merged.slice(-CHAT_LIMIT), chatLoaded: true, chatHistoryError: null, names });\n },\n settleChat(localId, next) {\n const index = state.chat.findIndex((message) => message.id === localId);\n if (index === -1) return;\n const current = state.chat[index] as RoomChatMessage;\n let replacement: RoomChatMessage;\n if (\"failed\" in next && typeof next.failed === \"string\" && !(\"id\" in next)) {\n replacement = { ...current, failed: next.failed };\n } else {\n replacement = next as RoomChatMessage;\n }\n // The saved copy may ALREADY be here (the room packet beat the response):\n // then the pending one simply goes.\n const duplicate =\n replacement.id !== localId && state.chat.some((message) => message.id === replacement.id);\n const chat = duplicate\n ? state.chat.filter((message) => message.id !== localId)\n : state.chat.map((message, position) => (position === index ? replacement : message));\n commit({ ...state, chat });\n },\n addReaction(reaction) {\n commit({ ...state, reactions: [...state.reactions, reaction] });\n },\n expireReactions(now) {\n const live = state.reactions.filter((reaction) => now - reaction.at < REACTION_TTL_MS);\n if (live.length === state.reactions.length) return;\n commit({ ...state, reactions: live });\n },\n setRecording(next, by, source, recordingId = null) {\n // 🚨 THE DATABASE WINS (D16). A member receives every transition TWICE —\n // the `meet_recordings` row over Postgres Changes and a\n // `matrx.meet.recording` packet in the room — and whichever is slower\n // must not be able to drag the indicator backwards.\n //\n // The rule is about ONE recording, not about the meeting: a meeting can\n // be recorded again, and the second recording's first word is not a\n // contradiction of the first one's ending. So the durable state stands\n // against anything that is about the SAME recording, and against a\n // sourceless update while a recording is still in flight.\n // `record` always wins. The other two are refused when they would\n // contradict the durable word rather than continue past it:\n //\n // * `local` is this browser acting NOW — a host pressing Start again on a\n // meeting whose last recording is over. It is refused only while a\n // recording is genuinely in flight, where a stale optimistic patch\n // could drag a live indicator backwards.\n // * `room` carries the recording's id, so a LATE packet about the\n // recording that just finished is told apart from the FIRST packet of\n // the next one. Same recording: refused. Different recording: refused\n // only while the durable one is still in flight.\n if (state.recordingSource === \"record\" && source !== \"record\") {\n const aboutTheSameRecording =\n source === \"room\" &&\n (recordingId === null ||\n state.recordingId === null ||\n recordingId === state.recordingId);\n if (aboutTheSameRecording || isRecordingInFlight(state.recording)) return;\n }\n if (\n state.recording === next &&\n state.recordingStartedBy === by &&\n state.recordingSource === source &&\n state.recordingId === (recordingId ?? state.recordingId)\n ) {\n // Identity-stable: an unchanged state must not wake every subscriber.\n return;\n }\n commit({\n ...state,\n recording: next,\n recordingStartedBy: by,\n recordingSource: source,\n recordingId: recordingId ?? state.recordingId,\n });\n },\n upsertLobby(entry) {\n const index = state.lobby.findIndex((existing) => existing.identity === entry.identity);\n const lobby =\n index === -1\n ? [...state.lobby, entry]\n : state.lobby.map((existing, position) => (position === index ? entry : existing));\n commit({ ...state, lobby });\n },\n setAdmission(identity, admissionState, by) {\n commit({\n ...state,\n lobby: state.lobby.map((entry) =>\n entry.identity === identity\n ? {\n ...entry,\n state: admissionState,\n decidedAt: new Date().toISOString(),\n decidedBy: by,\n }\n : entry,\n ),\n });\n },\n learn(entries) {\n const names = learnNames(state.names, entries);\n // Identity-stable: learning nothing must not wake every subscriber, and\n // this is called on every roster event and every durable row.\n if (names === state.names) return;\n commit({ ...state, names });\n },\n ingestCaption(segment) {\n // A caption's roster lookup is a source of names like any other.\n const names = learnName(state.names, segment.identity, segment.speaker);\n const transcript = mergeCaption(state.transcript, segment);\n if (transcript === state.transcript && names === state.names) return;\n commit({ ...state, transcript, names });\n },\n ingestRecord(segment) {\n // 🚨 THE DURABLE ROW'S `speaker` IS THE BEST NAME THERE IS: the note-taker\n // wrote it from `meet_participants`, which is where \"Ada Lovelace\" lived\n // while the guest's screen was printing `guest:c8-spk-1`.\n const names = learnName(state.names, segment.identity, segment.speaker);\n const transcript = mergeRecord(state.transcript, segment);\n if (transcript === state.transcript && names === state.names) return;\n commit({ ...state, transcript, names });\n },\n upsertNote(note) {\n const index = state.notes.findIndex((existing) => existing.id === note.id);\n if (index === -1) {\n commit({ ...state, notes: [...state.notes, note] });\n return;\n }\n const existing = state.notes[index];\n // Identity-stable: an unchanged note must not wake `useSyncExternalStore`.\n // 🚨 `source` is part of \"unchanged\" and that is load-bearing, not\n // cosmetic: when the room copy and the database copy carry the same words,\n // dropping the second arrival would leave the note marked `\"room\"` forever\n // — and the precedence rule below would then let a LATER stale packet\n // overwrite a row the database had already confirmed.\n if (\n existing !== undefined &&\n existing.text === note.text &&\n existing.kind === note.kind &&\n existing.supersededBy === note.supersededBy &&\n existing.source === note.source\n ) {\n return;\n }\n // 🚨 THE DATABASE WINS (D16, MRI-A12). The identical row arrives on two\n // transports — a `meet_notes` row over Postgres Changes and a\n // `matrx.meet.note` packet on the media plane — and whichever is slower\n // must not be able to undo the other. A `\"room\"` copy never overwrites a\n // `\"record\"` one; a `\"record\"` copy always overwrites a `\"room\"` one.\n // Without this, a re-run that rewrote a note could flip a member's screen\n // back to the superseded wording purely on arrival order.\n if (existing !== undefined && existing.source === \"record\" && note.source === \"room\") {\n return;\n }\n commit({\n ...state,\n notes: state.notes.map((entry, position) => (position === index ? note : entry)),\n });\n },\n announceWrapUp(announcement) {\n const current = state.wrapUp;\n if (current !== null && current.at === announcement.at) return;\n commit({ ...state, wrapUp: announcement });\n },\n setLocalTrack(kind, next) {\n const current = state.media[kind];\n // Identity-stable: an unchanged track must not wake `useSyncExternalStore`.\n if (\n current.enabled === next.enabled &&\n current.unavailable?.code === next.unavailable?.code &&\n current.unavailable?.message === next.unavailable?.message\n ) {\n return;\n }\n commit({ ...state, media: { ...state.media, [kind]: next } });\n },\n reset() {\n // A person's own media choices outlive a room: they are theirs, per browser.\n commit({ ...EMPTY, effects: state.effects });\n },\n };\n}\n","/**\n * THE OBSERVATION ROOT (CORE-DESIGN §5.6, step S0).\n *\n * Exactly one `[data-meet-root]` per tab on a meeting page, carrying the\n * HARNESS-CONTRACT's attributes derived from the store (`core/observation.ts`).\n * The package renders it once — `<MeetingRoom>` wraps every screen in it, and a\n * consumer that draws its own screen around the room (link resolution, the\n * guest-name step, a Board layout) wraps that screen in `<MeetRoot>` too, with\n * a `phase` when there is no store yet.\n *\n * The wrapper is `display: contents`: it generates no box, so no layout, size\n * or stacking changes; it exists only to carry the attributes. It also\n * publishes `window.__matrxMeet` — diagnostics only, never a verdict.\n */\n\nimport { useEffect, useMemo, useRef, type ReactNode } from \"react\";\nimport {\n observe,\n rootAttributes,\n staticObservation,\n type MeetObservation,\n type ObservedPhase,\n} from \"../core/observation\";\nimport type { MeetingRecord } from \"../core/types\";\nimport { useMeetSnapshot } from \"./provider\";\n\nexport interface CoreRootOptions {\n /** The row the consumer resolved (the guest lane has no other copy). */\n readonly meeting?: MeetingRecord | null | undefined;\n /** A screen the consumer renders before any store exists (resolving, a dead link). */\n readonly phase?: ObservedPhase | undefined;\n readonly reason?: string | null | undefined;\n}\n\n/** The observation for this tab, one stable object per change. */\nexport function useCoreObservation(options: CoreRootOptions = {}): MeetObservation {\n const snapshot = useMeetSnapshot();\n const { meeting, phase, reason } = options;\n return useMemo(\n () =>\n phase !== undefined\n ? staticObservation(phase, reason ?? null)\n : observe({ snapshot, meeting: meeting ?? null }),\n [snapshot, meeting, phase, reason],\n );\n}\n\n/** The root element's `data-meet-*` attributes. Spread onto ONE element per tab. */\nexport function useCoreRootProps(options: CoreRootOptions = {}): Record<string, string> {\n const observation = useCoreObservation(options);\n return useMemo(() => rootAttributes(observation), [observation]);\n}\n\ninterface MeetDiagnostics {\n readonly contract: \"HARNESS-CONTRACT\";\n snapshot(): { readonly observation: MeetObservation; readonly storePhase: string | null };\n /** Not derived before CORE-DESIGN step S6a — an empty list here means \"not computed\", not \"none\". */\n readonly activeCatalogStates: readonly string[];\n readonly activeCatalogStatesDerived: false;\n}\n\n/** `<MeetRoot>` — the one observation root. Generates no box. */\nexport function MeetRoot({\n children,\n ...options\n}: CoreRootOptions & { children?: ReactNode }): ReactNode {\n const snapshot = useMeetSnapshot();\n const observation = useCoreObservation(options);\n const attrs = useMemo(() => rootAttributes(observation), [observation]);\n const latest = useRef({ observation, storePhase: snapshot?.phase ?? null });\n latest.current = { observation, storePhase: snapshot?.phase ?? null };\n\n useEffect(() => {\n if (typeof window === \"undefined\") return undefined;\n const diagnostics: MeetDiagnostics = {\n contract: \"HARNESS-CONTRACT\",\n snapshot: () => latest.current,\n activeCatalogStates: [],\n activeCatalogStatesDerived: false,\n };\n const target = window as unknown as { __matrxMeet?: MeetDiagnostics };\n target.__matrxMeet = diagnostics;\n return () => {\n if (target.__matrxMeet === diagnostics) delete target.__matrxMeet;\n };\n }, []);\n\n return (\n <div {...attrs} style={{ display: \"contents\" }}>\n {children}\n </div>\n );\n}\n","/**\n * THE OBSERVATION CONTRACT, DERIVED FROM TODAY'S STORE (CORE-DESIGN §7 step S0).\n *\n * The real-browser scenario harness reads a meeting through a small set of\n * `data-meet-*` attributes (common-docs `meet/duplicates/HARNESS-CONTRACT.md`,\n * owned by the harness lane). This module turns the store's `MeetSnapshot`\n * into exactly those values — pure, framework-free, rendered ONCE by the\n * package (`useCoreRootProps()` / `<MeetRoot>`), never per skin.\n *\n * S0 rule: TODAY'S VALUES ONLY. Every value here is what the person actually\n * sees on today's screens, including today's defects — a `failed` or `left`\n * room renders the pre-join screen, so its phase is `prejoin`, and the harness\n * records that as the defect it is. A value the store does not know (host\n * presence before joining, autoplay blocking) is OMITTED rather than guessed:\n * an absent attribute reads as \"unknown\", a guessed one would lie.\n */\n\nimport type { LocalTrackState } from \"./attendance\";\nimport type { ConnectionRegion, MeetSnapshot } from \"./store\";\nimport { isLivePhase, isWaitingPhase, reasonOf, type MeetPhase } from \"./phase\";\nimport { effectiveRole, effectiveStanding } from \"./store\";\nimport { isLinkFollower } from \"./types\";\nimport type {\n ConnectionQuality,\n MeetParticipant,\n MeetingRecord,\n ParticipantRole,\n ParticipantStanding,\n} from \"./types\";\n\n/**\n * `data-meet-role` is the HARNESS CONTRACT's vocabulary (host · cohost · participant · guest):\n * a link follower whose role is `participant` renders `guest` (CORE-DESIGN §2.1); `presenter` /\n * `viewer` render `participant` until the contract adopts them (§3.7 C3).\n */\nexport type ObservedRole = \"host\" | \"cohost\" | \"participant\" | \"guest\";\n\nexport function observedRole(role: ParticipantRole, standing: ParticipantStanding | null | undefined): ObservedRole {\n if (role === \"host\" || role === \"cohost\") return role;\n return isLinkFollower(standing) ? \"guest\" : \"participant\";\n}\n\n/** `data-meet-phase` — the core's phase IS the contract value (S6a: no translation layer). */\nexport type ObservedPhase = MeetPhase;\n\nexport type ObservedConnection = ConnectionRegion;\n\nexport interface MeetObservation {\n readonly phase: ObservedPhase;\n readonly reason: string | null;\n readonly role: ObservedRole | \"none\";\n readonly connection: ObservedConnection;\n /** null = this tab does not know (it is not in the room). */\n readonly hostPresent: boolean | null;\n readonly lobbyCount: number;\n readonly recording: boolean;\n /** null = today's store does not track autoplay blocking. */\n readonly audioBlocked: boolean | null;\n /** null outside `joining` / `in_call` — no device is in play there today. */\n readonly camera: string | null;\n readonly microphone: string | null;\n}\n\nexport interface ObserveInput {\n readonly snapshot: MeetSnapshot | null;\n /** The row the consumer resolved, when the store has none yet (the guest lane). */\n readonly meeting?: MeetingRecord | null | undefined;\n}\n\n/** The store's device state → the contract's `camera` / `microphone` value. */\nexport function observedDevice(track: LocalTrackState): string {\n if (track.enabled) return \"on\";\n const refused = track.unavailable;\n if (refused === null) return \"muted_by_me\";\n switch (refused.code) {\n case \"permission-denied\":\n return \"unavailable:denied\";\n case \"device-not-found\":\n return \"unavailable:none\";\n case \"device-in-use\":\n return \"unavailable:busy\";\n case \"insecure-context\":\n return \"unavailable:blocked_site\";\n default:\n return \"unavailable:failed\";\n }\n}\n\n/** Does this snapshot's meeting row say it is over? */\nfunction endedRecord(snapshot: MeetSnapshot | null, given: MeetingRecord | null): boolean {\n const record = snapshot?.meeting ?? given;\n return record !== null && record.endedAt !== null;\n}\n\n/**\n * The phase the person is looking at: the store's phase, verbatim (CORE-DESIGN §3.1). The one\n * derivation left is the ended meeting: a row that says it is over renders `ended` for anyone not\n * in a live connection, whichever carrier delivered the ending first.\n */\nexport function observedPhase(input: ObserveInput): ObservedPhase {\n const { snapshot } = input;\n if (snapshot === null) return \"resolving\";\n const phase = snapshot.phase;\n if (endedRecord(snapshot, input.meeting ?? null) && !isLivePhase(phase) && !isCallPhase(phase)) return \"ended\";\n return phase;\n}\n\nfunction isCallPhase(phase: MeetPhase): boolean {\n return phase.startsWith(\"ringing_\") || phase.startsWith(\"call_\") || phase === \"answered_elsewhere\";\n}\n\nconst ADMITTER_ROLES: ReadonlySet<ParticipantRole> = new Set([\"host\", \"cohost\"]);\n\nexport function observe(input: ObserveInput): MeetObservation {\n const { snapshot } = input;\n const phase = observedPhase(input);\n const inCall = phase === \"in_call\";\n const actual: ParticipantRole | \"none\" = inCall && snapshot !== null ? effectiveRole(snapshot) : \"none\";\n const canAdmit = actual !== \"none\" && ADMITTER_ROLES.has(actual);\n const role: ObservedRole | \"none\" =\n actual === \"none\" || snapshot === null ? \"none\" : observedRole(actual, effectiveStanding(snapshot));\n const deviceInPlay = phase === \"joining\" || inCall;\n return {\n phase,\n reason: phase === \"ended\" ? (snapshot?.reason ?? null) : (snapshot?.reason ?? reasonOf(phase)),\n role,\n connection: snapshot?.connection ?? \"stable\",\n hostPresent: inCall && snapshot !== null\n ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role))\n : snapshot !== null && isWaitingPhase(snapshot.phase)\n ? (snapshot.waiting?.admittersPresent ?? null)\n : null,\n lobbyCount: canAdmit && snapshot !== null\n ? snapshot.lobby.filter((entry) => entry.state === \"knocking\").length\n : 0,\n recording: snapshot?.recording === \"recording\" || snapshot?.recording === \"stopping\",\n audioBlocked: null,\n camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera) : null,\n microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone) : null,\n };\n}\n\n/** A phase the consumer renders itself (link resolution, the guest-name step, a dead link). */\nexport function staticObservation(phase: ObservedPhase, reason: string | null = null): MeetObservation {\n return {\n phase,\n reason,\n role: \"none\",\n connection: phase === \"disconnected\" ? \"server_unreachable\" : \"stable\",\n hostPresent: null,\n lobbyCount: 0,\n recording: false,\n audioBlocked: null,\n camera: null,\n microphone: null,\n };\n}\n\n/** The root element's attributes. Unknown values are omitted, never guessed. */\nexport function rootAttributes(o: MeetObservation): Record<string, string> {\n const attrs: Record<string, string> = {\n \"data-meet-root\": \"\",\n \"data-meet-phase\": o.phase,\n \"data-meet-role\": o.role,\n \"data-meet-connection\": o.connection,\n \"data-meet-lobby-count\": String(o.lobbyCount),\n \"data-meet-recording\": String(o.recording),\n };\n if (o.reason !== null) attrs[\"data-meet-reason\"] = o.reason;\n if (o.hostPresent !== null) attrs[\"data-meet-host-present\"] = String(o.hostPresent);\n if (o.audioBlocked !== null) attrs[\"data-meet-audio-blocked\"] = String(o.audioBlocked);\n if (o.camera !== null) attrs[\"data-meet-camera\"] = o.camera;\n if (o.microphone !== null) attrs[\"data-meet-microphone\"] = o.microphone;\n attrs[\"data-meet-regions\"] = JSON.stringify({\n connection: o.connection,\n camera: o.camera,\n mic: o.microphone,\n audio_output: o.audioBlocked === null ? null : o.audioBlocked ? \"blocked\" : \"playing\",\n recording: o.recording,\n });\n return attrs;\n}\n\n/** The contract's per-person connection vocabulary. `unknown` is not a reading. */\nexport function observedQuality(quality: ConnectionQuality): string | null {\n switch (quality) {\n case \"excellent\":\n case \"good\":\n return \"good\";\n case \"poor\":\n return \"poor\";\n case \"lost\":\n return \"lost\";\n default:\n return null;\n }\n}\n\n/** `[data-meet-participant]` attributes for one person's tile or roster row. */\nexport function participantAttributes(p: MeetParticipant): Record<string, string> {\n const attrs: Record<string, string> = {\n \"data-meet-participant\": \"\",\n \"data-meet-participant-identity\": p.identity,\n \"data-meet-participant-name\": p.displayName,\n \"data-meet-participant-role\": observedRole(p.role, p.standing),\n \"data-meet-participant-self\": String(p.isLocal),\n };\n const quality = observedQuality(p.quality);\n if (quality !== null) attrs[\"data-meet-participant-connection\"] = quality;\n return attrs;\n}\n\n/** The listen-only notice's contract code, from why the microphone is off. */\nexport function microphoneNoticeCode(track: LocalTrackState): \"mic-blocked\" | \"device-lost\" | null {\n const refused = track.unavailable;\n if (refused === null) return null;\n return refused.code === \"permission-denied\" || refused.code === \"insecure-context\"\n ? \"mic-blocked\"\n : \"device-lost\";\n}\n","\"use client\";\n\n/**\n * `<MeetProvider>` — the one place an app wires calls and meetings (R7).\n *\n * THE ONE-HOUR INTEGRATION CONTRACT is this component's job: a host passes a\n * Supabase client, who they are, their org, and aidream's base URL. Everything\n * else — the repository, the realtime manager and its three channels, the token\n * client, the room engine, the call center, the meeting session, the recording\n * controller, the device manager, the AI surface — is constructed and owned\n * here.\n *\n * What a host may inject is IDENTITY and app chrome: session source, a\n * navigator, a notification sink. It may NOT inject an agent id — as of 0.3.0\n * there is no prop for one, because which agent answers is a Mandate binding\n * the SERVER resolves (D2). What a host must NEVER have to inject is\n * behavior — if you find yourself catching a package error, retrying a package\n * call, or branching on a package quirk in host code, that logic belongs in\n * here (C22).\n *\n * 🚨 THE PROVIDER'S LIFETIME IS `client` + the SESSION'S user (or `guestName`)\n * + `organizationId` + `baseUrl`, AND NOTHING ELSE. Hosts pass fresh function\n * literals for `onDiagnostic` and `accessToken` on every render; keying the\n * runtime on those identities would tear down and rebuild every channel — and\n * drop the call in progress — on every parent re-render.\n *\n * 🚨 THERE IS NO `userId` PROP, AND THERE NEVER WILL BE AGAIN (DD-240, 0.6.0).\n * The acting user is read from `client`'s own session — the same client that\n * mints the JWT on every request — so the id in a `p_user_id` argument and the\n * id in the token cannot disagree. Through 0.5.x this was a prop, every host\n * filled it from a copy in its own state, and a rotated auth cookie left that\n * copy naming an account the tab could no longer prove it was:\n * `meet_pending_call_invites` refused it at 403 with \"the acting user … is not\n * the authenticated user\", 8.8 h and 32.9 h into two tabs' lives. The whole\n * story is in `../core/session-identity.ts`.\n *\n * 🚨 A GUEST IS A FIRST-CLASS IDENTITY (D6), not a degraded user. Pass\n * `guestName` and the provider builds a runtime with no calling surface and no\n * host powers, which is exactly what a link-follower needs — and, because a\n * guest has no account at all, the guest lane never reads the session. Nothing\n * downstream branches on \"is this a real user\".\n */\n\nimport { globalSlot } from \"@ai-matrx/kit/global-slot\";\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type ReactNode,\n} from \"react\";\nimport {\n RealtimeProvider,\n useIsRealtimeProviderMounted,\n useRealtimeManager,\n} from \"@ai-matrx/realtime/react\";\nimport type { MessagingSupabaseClient } from \"@ai-matrx/messaging\";\nimport { createMeetAi, type MeetAi } from \"../core/ai\";\nimport { createMeetApi, type MeetApi } from \"../core/api\";\nimport { createCallCenter, type CallCenter } from \"../core/calls\";\nimport {\n createDeviceManager,\n createWebDeviceStorage,\n type DeviceManager,\n type DeviceSnapshot,\n type MediaDevicesLike,\n} from \"../core/devices\";\nimport { createMeetingSession, type MeetingSession } from \"../core/meetings\";\nimport { createRecordingController, type RecordingController } from \"../core/recording\";\nimport { createMeetRepository, type MeetRepository } from \"../core/repository\";\nimport {\n createMeetSessionIdentity as createSessionIdentity,\n SERVER_SESSION_IDENTITY,\n type SessionIdentitySnapshot,\n} from \"../core/session-identity\";\nimport { createRoomEngine, type RoomEngine } from \"../core/room\";\nimport { isWaitingPhase } from \"../core/phase\";\nimport { createWebEffectsStorage } from \"../core/effects\";\nimport { createInRoomClient, type InRoomClient } from \"../core/in-room\";\nimport { createLiveKitDriver } from \"../core/livekit-driver\";\nimport type { RoomDriverFactory } from \"../core/room-driver\";\nimport {\n createMeetStore,\n effectiveRole,\n type MeetSnapshot,\n type MeetStore,\n} from \"../core/store\";\nimport {\n createTokenClient,\n createWebRoomTokenStorage,\n type AccessTokenSource,\n type TokenClient,\n} from \"../core/tokens\";\nimport {\n asOrganizationId,\n isHostRole,\n type CallInvite,\n type MeetIdentity,\n type ParticipantRole,\n type UserId,\n} from \"../core/types\";\n\nexport interface MeetDiagnostic {\n readonly level: \"info\" | \"warn\" | \"error\";\n readonly message: string;\n readonly remedy?: string | undefined;\n}\n\nexport interface MeetHost {\n readonly store: MeetStore;\n readonly engine: RoomEngine;\n readonly calls: CallCenter | null;\n readonly meetings: MeetingSession;\n /**\n * Meet wave 2: host moderation and the SAVED chat, both decided and written by\n * the server (`core/in-room.ts`). A guest's calls carry their room pass.\n */\n readonly inRoom: InRoomClient;\n readonly recording: RecordingController;\n readonly devices: DeviceManager;\n readonly repository: MeetRepository;\n readonly tokens: TokenClient;\n /**\n * 🚨 NEVER NULL as of 0.3.0. Through 0.2.1 this was `null` unless the host\n * injected a transport AND four agent ids, so the meeting assistant silently\n * did not exist for any app that had not filled in a config line. The\n * capability is the SERVER's now: if a Mandate has no Holder bound, the\n * server refuses with a sentence naming the Mandate admin, and that sentence\n * is what the panel shows. Absence is a fact the server states, never a prop\n * a host forgot.\n */\n readonly ai: MeetAi;\n /**\n * 🚨 THE ONE WAY ANYTHING IN THIS PACKAGE TALKS TO AIDREAM (MRI-A16). Every\n * request it builds carries `x-organization-id`; a request built any other\n * way is refused by the org-admission middleware with 400\n * `organization_required`, and `core/api.test.ts` fails the build if a second\n * way appears. Playing a recording — `GET /files/{id}/download` — goes\n * through here too, which is the defect this replaced.\n */\n readonly api: MeetApi;\n readonly identity: MeetIdentity;\n readonly baseUrl: string;\n /**\n * The host's session source, as handed to `<MeetProvider>`. Exposed for the\n * host's own diagnostics; package code reaches aidream through `api`.\n */\n readonly accessToken: AccessTokenSource;\n readonly report: (event: MeetDiagnostic) => void;\n}\n\n// 🚨 ONE CONTEXT ACROSS ENTRIES (S1). `/react` and `/skins/meet` are separate\n// bundles (`splitting: false`), so each carries its own copy of this module. A\n// module-level `createContext` would give the skin's parts a DIFFERENT context\n// than the `<MeetProvider>` above them, and every part would read \"no host\".\n// The context lives in the globalThis slot, like every process-global (rule 4).\nconst MeetContext = globalSlot(\"ai-matrx.meet.react.context\", () => createContext<MeetHost | null>(null));\n\nexport interface MeetProviderProps {\n /**\n * Any Supabase client. `null` while the host is still resolving one.\n *\n * 🚨 DELIBERATELY THE SHALLOW SHAPE (0.2.1) — pass the\n * `SupabaseClient<Database>` you already have, with no cast. The rich\n * internal contract sent tsc into `TS2589` for any host with generated\n * Supabase types; see `core/repository.ts`'s header for the whole story.\n */\n client: MessagingSupabaseClient | null | undefined;\n /** aidream's base URL. LiveKit credentials live THERE, never here. */\n baseUrl: string;\n /**\n * A guest's typed name — and the ONE way to declare the guest lane (D6).\n * With it, this mount is a link-follower's room: no session is read, no\n * calling surface is built, and nothing user-scoped is ever issued. Without\n * it, the acting user is whoever `client`'s session says it is.\n *\n * 🚨 There is NO `userId` prop. See this file's header and\n * `../core/session-identity.ts`.\n */\n guestName?: string | null | undefined;\n organizationId: string | null | undefined;\n displayName?: string | undefined;\n avatarUrl?: string | null | undefined;\n /** The bearer token source for aidream calls. */\n accessToken: AccessTokenSource;\n /** Swap the media driver. Defaults to LiveKit — there is no reason to. */\n driverFactory?: RoomDriverFactory | undefined;\n /** A call is ringing this device — for a sound, a toast, or a badge. */\n onIncomingCall?: ((invite: CallInvite) => void) | undefined;\n onDiagnostic?: ((event: MeetDiagnostic) => void) | undefined;\n ringTimeoutMs?: number | undefined;\n children: ReactNode;\n}\n\nfunction defaultDiagnostics(event: MeetDiagnostic): void {\n const line = `[@ai-matrx/meet] ${event.message}${\n event.remedy !== undefined ? `\\n → ${event.remedy}` : \"\"\n }`;\n if (event.level === \"error\") console.error(line);\n else if (event.level === \"warn\") console.warn(line);\n else console.info(line);\n}\n\n/** A stable per-device id, so a reconnect keeps ONE LiveKit identity. */\nfunction deviceId(): string {\n const storage = (globalThis as { localStorage?: Storage }).localStorage;\n const key = \"mx.meet.device\";\n try {\n const existing = storage?.getItem(key);\n if (existing !== null && existing !== undefined && existing.length > 0) return existing;\n const created = Math.random().toString(36).slice(2, 14);\n storage?.setItem(key, created);\n return created;\n } catch {\n return Math.random().toString(36).slice(2, 14);\n }\n}\n\n/**\n * THE ONE SESSION READ. Subscribed per client, torn down with it, and honest\n * about the `resolving` window in between — see `../core/session-identity.ts`\n * for the class this closes.\n *\n * `client === null` (the guest lane, or a host still resolving one) never opens\n * a subscription at all: a link-follower has no account to read.\n */\nfunction useSessionIdentity(\n client: MessagingSupabaseClient | null | undefined,\n report: (event: MeetDiagnostic) => void,\n): SessionIdentitySnapshot {\n const [snapshot, setSnapshot] = useState<SessionIdentitySnapshot>(SERVER_SESSION_IDENTITY);\n\n useEffect(() => {\n if (client == null) {\n setSnapshot(SERVER_SESSION_IDENTITY);\n return undefined;\n }\n const identity = createSessionIdentity({ client, onDiagnostic: report });\n setSnapshot(identity.snapshot());\n const unsubscribe = identity.subscribe(() => setSnapshot(identity.snapshot()));\n return () => {\n unsubscribe();\n identity.dispose();\n };\n }, [client, report]);\n\n return snapshot;\n}\n\n/** A guest lane is declared by a NON-EMPTY typed name, and by nothing else. */\nfunction guestLaneName(guestName: string | null | undefined): string | null {\n return typeof guestName === \"string\" && guestName.trim().length > 0 ? guestName : null;\n}\n\nexport function MeetProvider(props: MeetProviderProps): ReactNode {\n const { client } = props;\n\n // Live ref so a host's fresh function literal per render never becomes part\n // of anything's lifetime, while callbacks still see the CURRENT one.\n const diagnosticRef = useRef(props.onDiagnostic);\n diagnosticRef.current = props.onDiagnostic;\n const report = useCallback((event: MeetDiagnostic): void => {\n (diagnosticRef.current ?? defaultDiagnostics)(event);\n }, []);\n\n // 🚨 NOTHING FAILS SILENTLY. A host still passing the 0.5.x `userId` prop —\n // a stale bundle, a JS consumer tsc never checked — is told, once, that it is\n // ignored and why. Silently dropping it would look exactly like the bug.\n const legacyUserIdReported = useRef(false);\n const legacyUserId = (props as { userId?: unknown }).userId;\n useEffect(() => {\n if (legacyUserId === undefined || legacyUserId === null) return;\n if (legacyUserIdReported.current) return;\n legacyUserIdReported.current = true;\n report({\n level: \"error\",\n message:\n \"<MeetProvider userId> is ignored: since 0.6.0 Meet reads the acting user from the \" +\n \"Supabase client's own session.\",\n remedy:\n \"Delete the userId prop. Passing a copy of the user id is what let a tab whose auth \" +\n \"cookie had rotated issue reads as an account it could no longer prove it was — the \" +\n \"database refused them at 403 (DD-240).\",\n });\n }, [legacyUserId, report]);\n\n const guestName = guestLaneName(props.guestName);\n // The guest lane reads no session; the member lane reads nothing else.\n const session = useSessionIdentity(guestName === null ? client : null, report);\n const actingUserId = guestName === null ? session.userId : null;\n\n // 🚨 RIDE THE HOST'S REALTIME MANAGER WHEN THERE IS ONE (0.6.5).\n //\n // Through 0.6.4 this ALWAYS mounted a provider of its own, with `client={null}`\n // until an organization was selected. That shadowed the host's manager for\n // everything below Meet: a sibling that rides a mounted host provider (as\n // `@ai-matrx/messaging` does) read a NULL manager forever when no org was\n // selected — `/messages` spun and a thread showed \"0 members\" — and with an\n // org selected it got a SECOND manager (two sockets, two write ledgers).\n // The probe is provider PRESENCE (`@ai-matrx/realtime` 0.3.0), never\n // `useRealtimeManager() !== null`, which is also null while a mounted host\n // provider waits for its client.\n //\n // And the realtime connection never waits on the organization: a socket is\n // the PERSON's (access belongs to the person, 2026-09-23). Whether Meet's own\n // runtime needs an org is MeetRuntime's question, not the transport's.\n const hostProvidesRealtime = useIsRealtimeProviderMounted();\n const runtime = (\n <MeetRuntime\n {...props}\n report={report}\n actingUserId={actingUserId}\n guestLaneName={guestName}\n sessionResolved={guestName !== null || session.state === \"resolved\"}\n />\n );\n if (hostProvidesRealtime) return runtime;\n return (\n <RealtimeProvider\n client={client != null ? (client as never) : null}\n actorId={actingUserId ?? undefined}\n >\n {runtime}\n </RealtimeProvider>\n );\n}\n\ninterface MeetRuntimeProps extends MeetProviderProps {\n readonly report: (event: MeetDiagnostic) => void;\n /** Who the SESSION says is acting. Never a prop from outside the package. */\n readonly actingUserId: UserId | null;\n readonly guestLaneName: string | null;\n readonly sessionResolved: boolean;\n}\n\nfunction MeetRuntime(props: MeetRuntimeProps): ReactNode {\n const { client, actingUserId, guestLaneName: guestName, organizationId, baseUrl, children } =\n props;\n const report = props.report;\n const manager = useRealtimeManager();\n\n // Live refs so callbacks always see the CURRENT render's functions without\n // those functions' identities being part of the runtime's lifetime.\n const incomingRef = useRef(props.onIncomingCall);\n incomingRef.current = props.onIncomingCall;\n const accessTokenRef = useRef(props.accessToken);\n accessTokenRef.current = props.accessToken;\n const driverRef = useRef(props.driverFactory);\n driverRef.current = props.driverFactory;\n\n const displayName =\n props.displayName ?? (typeof guestName === \"string\" ? guestName : \"Guest\");\n\n // 🚨 INERT UNTIL THE SESSION HAS ANSWERED. A runtime built during the\n // `resolving` window would issue its first `pendingInvites` read on behalf of\n // a guess; the whole point of DD-240 is that there is no guess.\n const ready =\n client != null &&\n manager !== null &&\n typeof organizationId === \"string\" &&\n organizationId.length > 0 &&\n props.sessionResolved &&\n (guestName !== null || actingUserId !== null);\n\n const host = useMemo<MeetHost | null>(() => {\n if (!ready || manager === null || client == null) return null;\n\n const identity: MeetIdentity = {\n // THE SESSION'S ANSWER, never a prop (DD-240).\n userId: actingUserId,\n organizationId: asOrganizationId(organizationId as string),\n displayName,\n avatarUrl: props.avatarUrl ?? null,\n deviceId: deviceId(),\n };\n\n const effectsStorage = createWebEffectsStorage();\n // Seeded BEFORE the room: the pre-join screen shows (and previews) the\n // remembered choice, and the engine applies the same one on connect.\n const store = createMeetStore({ effects: effectsStorage.load() });\n const devicesHolder: { current: DeviceManager | null } = { current: null };\n const api = createMeetApi({\n baseUrl,\n organizationId: identity.organizationId,\n accessToken: (...args) => accessTokenRef.current(...args),\n });\n const repository = createMeetRepository({ client });\n // MRF-1: a GUEST's pass is kept on this device so the durable link still\n // opens the record after the meeting — `/token` will not mint another one\n // for a finished meeting, correctly, and a guest has no session to prove\n // they were here with. `createTokenClient` stores only guest passes.\n const tokens = createTokenClient({\n api,\n storage: createWebRoomTokenStorage((message, remedy) =>\n report({ level: \"warn\", message, remedy }),\n ),\n });\n const engine = createRoomEngine({\n store,\n tokens,\n identity,\n resolveMeeting: (slug) => repository.meetingBySlug(slug),\n driverFactory: (handlers) =>\n (driverRef.current ?? createLiveKitDriver)(handlers),\n onDiagnostic: report,\n effectsStorage,\n resolveDevice: (kind) => devicesHolder.current?.resolve(kind) ?? null,\n // §3.4: on every LiveKit disconnect, ask the server what happened to this connection.\n readExit: (connection) => meetings.readExit(connection),\n });\n const meetings = createMeetingSession({\n manager,\n repository,\n store,\n identity,\n api,\n onDiagnostic: report,\n // ADMISSION IS A JOIN, NOT A RE-RENDER. The lobby token grants neither\n // publish nor subscribe, so the host saying yes has to mint a new one. The\n // \"yes\" is read from the server (the state read), never a broadcast (S4).\n onAdmitted: () => {\n void engine.rejoin().catch(() => undefined);\n },\n rejoin: () => engine.rejoin(),\n // THE MEETING PASS this device holds — what the state read and `/leave` carry.\n resolvePass: () => {\n const roomName = store.snapshot().meeting?.roomName;\n const active = engine.token();\n if (active?.meetingPass) return active.meetingPass;\n return roomName === undefined ? null : (tokens.remembered(roomName)?.meetingPass ?? null);\n },\n });\n // THE AI IS PART OF THE RUNTIME, not an optional attachment. Nothing here\n // names an agent: every capability is a server-owned Mandate (D2).\n const resolveRoomToken = (): string | null => {\n const roomName = store.snapshot().meeting?.roomName;\n if (roomName === undefined) return null;\n const active = engine.token();\n // THE MEETING PASS, never the LiveKit token (CORE-DESIGN §2.5): `x-meet-pass`.\n if (active?.roomName === roomName && active.meetingPass) return active.meetingPass;\n return tokens.remembered(roomName)?.meetingPass ?? null;\n };\n const ai = createMeetAi({ api, store, resolveRoomToken });\n const inRoom = createInRoomClient({ api, store, resolveRoomToken });\n // THE SAVED CHAT IS READ ON EVERY JOIN (Meet wave 2) — that is what makes a\n // late joiner see what was already said. Once per connected room; a refusal\n // is a diagnostic with a remedy, and live messages keep arriving regardless.\n // S4: THE WAIT READS SERVER TRUTH while (and only while) this tab is in the waiting room.\n store.subscribe(() => {\n const snapshot = store.snapshot();\n if (isWaitingPhase(snapshot.phase) && snapshot.waiting !== null &&\n (snapshot.waiting.admission === \"knocking\" || snapshot.waiting.admission === \"waiting_host\")) {\n meetings.startWaiting();\n } else if (!isWaitingPhase(snapshot.phase)) {\n meetings.stopWaiting();\n }\n });\n let chatReadFor: string | null = null;\n store.subscribe(() => {\n const snapshot = store.snapshot();\n if (snapshot.phase !== \"in_call\" || snapshot.meeting === null) {\n if (snapshot.phase === \"left\" || snapshot.phase === \"prejoin\" || snapshot.phase === \"resolving\") chatReadFor = null;\n return;\n }\n if (chatReadFor === snapshot.meeting.id) return;\n chatReadFor = snapshot.meeting.id;\n // Meet wave 5: polls, questions, the whiteboard and an open breakout plan — read\n // on the same once-per-join moment, so a late joiner sees them all. A refusal is\n // written to `collab.error` by the client and reported here.\n void inRoom.loadCollab().catch((error: unknown) => {\n const normalized = error as { message?: string; remedy?: string };\n report({\n level: \"warn\",\n message: normalized.message ?? \"meet: polls, questions and the whiteboard could not be read\",\n ...(normalized.remedy !== undefined ? { remedy: normalized.remedy } : {}),\n });\n });\n void inRoom.loadChat().catch((error: unknown) => {\n const normalized = error as { message?: string; remedy?: string };\n // NEVER \"Reading…\" FOREVER: the panel says history is unavailable, and why.\n store.patch({\n chatLoaded: true,\n chatHistoryError:\n normalized.remedy ?? \"Messages sent before you joined could not be read. New messages still arrive.\",\n });\n report({\n level: \"warn\",\n message: normalized.message ?? \"meet: the saved chat could not be read\",\n ...(normalized.remedy !== undefined ? { remedy: normalized.remedy } : {}),\n });\n });\n });\n const recording = createRecordingController({\n api,\n store,\n acknowledgeConsent: async (meetingId) => {\n await repository.recordConsent({\n meetingId,\n identity: store.snapshot().localIdentity ?? identity.deviceId,\n acknowledgedAt: new Date().toISOString(),\n });\n },\n });\n const devices = createDeviceManager({\n media: (globalThis as unknown as { navigator?: { mediaDevices?: MediaDevicesLike } })\n .navigator?.mediaDevices ?? {\n // An SSR render or a browser without media devices gets an INERT\n // manager that reports the truth, rather than a crash on import.\n enumerateDevices: async () => [],\n getUserMedia: async () => ({ getTracks: () => [] }),\n },\n storage: createWebDeviceStorage(),\n });\n devicesHolder.current = devices;\n // A GUEST HAS NO CALLING SURFACE. Not \"a disabled one\" — none at all, so\n // there is nothing to render as a dead button (D6, no dead ends).\n const calls =\n identity.userId === null\n ? null\n : createCallCenter({\n manager,\n repository,\n store,\n identity,\n onIncoming: (invite) => incomingRef.current?.(invite),\n // §3.6: my call was answered — the caller joins the call's room on the same machine.\n onAnswered: (invite) => {\n void engine.join({ roomName: invite.roomName, mode: invite.mode }).catch(() => undefined);\n },\n onDiagnostic: report,\n ...(props.ringTimeoutMs !== undefined ? { ringTimeoutMs: props.ringTimeoutMs } : {}),\n });\n\n return {\n store,\n engine,\n calls,\n meetings,\n inRoom,\n recording,\n devices,\n repository,\n tokens,\n ai,\n api,\n identity,\n baseUrl,\n accessToken: (...args) => accessTokenRef.current(...args),\n report,\n };\n // 🚨 IDENTITY ONLY. See the header: adding callbacks or option objects here\n // rebuilds every channel — and drops the call in progress — on every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n ready,\n manager,\n client,\n actingUserId,\n guestName,\n organizationId,\n baseUrl,\n displayName,\n report,\n ]);\n\n useEffect(() => {\n if (host?.calls == null) return undefined;\n const center = host.calls;\n void center.start().catch((error: unknown) => {\n host.report({\n level: \"error\",\n message: `Meet could not start listening for calls: ${String(\n (error as Error)?.message ?? error,\n )}`,\n remedy:\n \"Calls will not ring on this device until this succeeds. Check the session and \" +\n \"organization, then reload the surface.\",\n });\n });\n return () => {\n center.dispose();\n };\n }, [host]);\n\n useEffect(() => {\n if (host === null) return undefined;\n return () => {\n host.meetings.dispose();\n host.devices.dispose();\n void host.engine.dispose();\n };\n }, [host]);\n\n return <MeetContext.Provider value={host}>{children}</MeetContext.Provider>;\n}\n\n/** The host, or `null` while auth/org are still resolving. */\nexport function useMeetHost(): MeetHost | null {\n return useContext(MeetContext);\n}\n\nexport function useRequiredMeetHost(): MeetHost {\n const host = useMeetHost();\n if (host === null) {\n throw new Error(\n \"[@ai-matrx/meet] No meet host. Wrap this tree in <MeetProvider client={supabase} \" +\n \"baseUrl={aidreamUrl} organizationId={orgId} accessToken={fn}> and render it only \" +\n \"once auth has resolved.\",\n );\n }\n return host;\n}\n\n/** The whole room snapshot, re-rendering only when it actually changes. */\nexport function useMeetSnapshot(): MeetSnapshot | null {\n const host = useMeetHost();\n const store = host?.store ?? null;\n const empty = useRef<MeetSnapshot | null>(null);\n return useSyncExternalStore(\n (listener) => (store === null ? () => undefined : store.subscribe(listener)),\n () => (store === null ? empty.current : store.snapshot()),\n () => (store === null ? empty.current : store.snapshot()),\n );\n}\n\nexport function useDeviceSnapshot(): DeviceSnapshot | null {\n const host = useMeetHost();\n const devices = host?.devices ?? null;\n return useSyncExternalStore(\n (listener) => (devices === null ? () => undefined : devices.subscribe(() => listener())),\n () => devices?.snapshot() ?? null,\n () => devices?.snapshot() ?? null,\n );\n}\n\n/**\n * 🚨 THE LOCAL PARTICIPANT'S ROLE — THE SERVER'S WORD, FROM BOTH ITS CARRIERS\n * (MRI-A11, feedback `7418ef78`).\n *\n * Two sources, one decision made once in `services/meet/service.py`:\n *\n * 1. `snapshot.grants.role` — the `/meet/token` response, in the store from the\n * instant the token lands, which is BEFORE any LiveKit roster exists.\n * 2. the local participant's roster entry, whose role comes from the\n * `matrx.participant.role` LiveKit attribute the same mint published.\n *\n * The stronger of the two wins, and both are server-minted, so this can never\n * be client state: there is no branch here a browser could take on its own.\n * Reading only (2) is the shipped defect this replaces — a roster entry arrives\n * a beat later than the token and, before MRI-A11, carried no role at all.\n */\nexport function useRole(): ParticipantRole {\n const snapshot = useMeetSnapshot();\n // ONE definition, shared with the code that PERFORMS the host actions\n // (`core/store.ts`). A second copy here is how the UI and the action drift.\n return snapshot === null ? \"participant\" : effectiveRole(snapshot);\n}\n\n/** Does the local participant hold host powers? ONE predicate, one definition. */\nexport function useIsHost(): boolean {\n return isHostRole(useRole());\n}\n\n/**\n * May the local participant start a recording? NOT `isHost` — the meeting's\n * recording policy is the server's, and an `always-on` meeting is recorded by\n * the server with no host control at all (MRI-A11).\n */\nexport function useCanRecord(): boolean {\n const snapshot = useMeetSnapshot();\n if (snapshot === null) return false;\n const me = snapshot.participants.find(\n (participant) => participant.identity === snapshot.localIdentity,\n );\n return snapshot.grants.canRecord || me?.canRecord === true;\n}\n\nexport function useLocalUserId(): UserId | null {\n return useMeetHost()?.identity.userId ?? null;\n}\n","/**\n * MEETINGS — the lobby, the host powers, and the durable link.\n *\n * A meeting is a room plus scheduling, lobby and host metadata (D3). Everything\n * here is about the moments OUTSIDE the media session, which is exactly why it\n * cannot ride LiveKit's data plane: a guest waiting in the lobby is not in the\n * LiveKit room yet, by construction. The lobby therefore lives on the meet-room\n * realtime channel.\n *\n * 🚨 THE THREE THINGS THAT MAKE GUEST JOIN SAFE (D6)\n *\n * 1. **The link is an identity, not a session.** A durable slug resolves before,\n * during, and after the scheduled time, and a recurring meeting keeps ONE\n * link. Nothing here mints a per-occurrence URL.\n * 2. **A guest's capability is the TOKEN, and nothing else.** They are scoped to\n * exactly one room. There is no guest row this client writes that could grant\n * anything, and no client-side \"is this person allowed\" branch.\n * 3. **Admission is a SERVER decision, recorded.** `admit` is an RPC; the\n * broadcast that tells the knocker is a NOTIFICATION of that decision, never\n * the decision itself. Two hosts clicking admit is the ordinary case, and the\n * RPC is what makes it idempotent.\n *\n * Host powers are checked against the role the SERVER minted into the token\n * (see `room.ts`), and the UI hides what an actor cannot do rather than\n * rendering a dead button (no dead ends).\n */\n\nimport { isWaitingPhase, type ExitServerWord } from \"./phase\";\nimport type { ChannelHandle, RealtimeManager } from \"@ai-matrx/realtime\";\nimport { MEET_EVENTS, meetingTopic, roomTopic } from \"./channels\";\nimport type { MeetWaiting } from \"./store\";\nimport type { MeetApi } from \"./api\";\nimport { MeetError } from \"./errors\";\nimport { readServerRefusal } from \"./ai\";\nimport type { MeetRepository } from \"./repository\";\nimport { effectiveRole, type MeetStore } from \"./store\";\nimport { MEET_ROUTES, MEETING_PASS_HEADER } from \"./tokens\";\nimport {\n asParticipantIdentity,\n isHostRole,\n isGuest,\n WAITING_ADMISSION_STATES,\n type AdmissionState,\n ROLE_RANK,\n type LobbyEntry,\n type MeetIdentity,\n type MeetParticipant,\n type MeetingAttendee,\n type MeetingId,\n type MeetingRecord,\n type ParticipantIdentity,\n type ParticipantRole,\n type RecordingState,\n type RoomName,\n type UserId,\n} from \"./types\";\n\nexport interface MeetingSessionOptions {\n manager: RealtimeManager;\n repository: MeetRepository;\n store: MeetStore;\n identity: MeetIdentity;\n /**\n * The ONE org-aware aidream client (MRI-A16) — `endForEveryone` is a SERVER\n * decision (MRI-A6), and every request it makes carries the organization.\n */\n api: MeetApi;\n onDiagnostic?: ((event: { level: \"info\" | \"warn\" | \"error\"; message: string; remedy?: string }) => void) | undefined;\n /**\n * 🚨 THE HOST ADMITTED *US*. Called exactly once per admission verdict that\n * names this participant, and only for `admitted`.\n *\n * Admission is not a UI state change: a lobby token is minted with\n * `canPublish: false` and `canSubscribe: false`, so the waiting room can only\n * become a room by minting a NEW token. Before this existed the waiting room\n * said \"you will join automatically once you are admitted\" and then waited\n * forever — a sentence on screen that was not true. The provider wires this\n * to `engine.rejoin()`.\n */\n onAdmitted?: (() => void) | undefined;\n /**\n * S4: the meeting pass this device holds (`x-meet-pass`) — what the state read and\n * `/leave` are authorized by. Null when no pass is held yet.\n */\n resolvePass?: (() => string | null) | undefined;\n /** S4: mint again (Ask to join again / Rejoin from the waiting screens). */\n rejoin?: (() => Promise<void>) | undefined;\n}\n\nexport interface MeetingSession {\n /** Subscribe to the room's out-of-band plane. Hosts see knocks; guests see verdicts. */\n watch(roomName: RoomName): void;\n /**\n * Subscribe to the MEETING RECORD's durable feed — transcript segments, notes,\n * and the meeting row itself (which is where the note-taker's state lives).\n * Reconnecting IS re-reading: the backfill door re-reads all three, because\n * realtime has no replay and a socket blip would otherwise leave a transcript\n * permanently missing the minute nobody noticed.\n */\n watchMeeting(meetingId: MeetingId): void;\n admit(entry: LobbyEntry, admitted: boolean): Promise<void>;\n /** S4: lift a denial so the person may knock again (`meet_undeny`). */\n undeny(entry: LobbyEntry): Promise<void>;\n admitAll(): Promise<void>;\n /**\n * 🚨 S4 — THE WAIT READS SERVER TRUTH (CORE-DESIGN §2.6). While this tab waits, the state\n * read (`GET /v1/meet/state`) runs every `waiting_poll_seconds`, on every\n * `mx.state.changed` hint, on `online` and on visibility return. Admitted → a fresh mint\n * (`rejoin`); denied / expired / ended are what the row says, never a broadcast's guess.\n */\n startWaiting(): void;\n stopWaiting(): void;\n /** One state read now (idempotent while one is in flight). */\n readState(): Promise<void>;\n /**\n * §3.4 ER: what the server recorded for THIS connection's exit, and the run's phase. null =\n * the read failed (offline / unreachable); undefined = no meeting or no pass to ask with.\n */\n readExit(connection: string): Promise<ExitServerWord | null | undefined>;\n /** Stop waiting: the row becomes `withdrawn` (`POST /v1/meet/leave {intent: \"withdraw\"}`). */\n withdraw(): Promise<void>;\n /** Ask again after a denial or an expired knock (only offered when allowed). */\n askAgain(): Promise<void>;\n setLock(locked: boolean): Promise<MeetingRecord>;\n /** End it for everyone, through the server (MRI-A6). */\n endForEveryone(): Promise<EndMeetingResult>;\n dispose(): void;\n}\n\n/** Can `actor` moderate `target`? A host outranks a co-host outranks everyone. */\nexport function canModerateParticipant(\n actor: MeetParticipant | undefined,\n target: MeetParticipant | undefined,\n): boolean {\n if (actor === undefined || target === undefined) return false;\n if (actor.role === \"participant\") return false;\n if (actor.identity === target.identity) return false;\n // A co-host cannot remove the host. Without this rank check, \"host controls\"\n // is a mutual-eviction button.\n return ROLE_RANK[actor.role] > ROLE_RANK[target.role];\n}\n\nexport function roleOf(\n participants: readonly MeetParticipant[],\n identity: ParticipantIdentity | null,\n): ParticipantRole {\n if (identity === null) return \"participant\";\n return participants.find((p) => p.identity === identity)?.role ?? \"participant\";\n}\n\n/**\n * What ending a meeting produced. `served: false` is the honest answer from a\n * server that has not landed `POST /v1/meet/end` yet (MRI-A6) — the control is\n * real, the refusal is a sentence, and nothing pretends the meeting ended.\n */\nexport interface EndMeetingResult {\n readonly endedAt: string | null;\n readonly served: boolean;\n}\n\n/** The sentence a server without MRI-A6 earns. Pinned so every surface says it. */\nexport const END_MEETING_NOT_SERVED =\n \"Ending a meeting is not available on this server yet\";\n\n/**\n * One durable `meet_participants` row as a lobby entry, or `null` when it is\n * not one. A row that has been decided is not a knocker; the panel shows people\n * who are WAITING, and `store.upsertLobby` keyed by identity is what lets the\n * table and the broadcast be two copies of one person rather than two rows.\n */\nfunction lobbyEntryFrom(attendee: MeetingAttendee, now: number = Date.now()): LobbyEntry | null {\n if (!WAITING_ADMISSION_STATES.includes(attendee.admissionState)) return null;\n // A knock past its expiry IS expired (the server CASes it on the knocker's next read).\n const expires = attendee.knockExpiresAt ? Date.parse(attendee.knockExpiresAt) : Number.NaN;\n if (!Number.isNaN(expires) && expires <= now) return null;\n return {\n identity: attendee.identity,\n displayName: attendee.displayName,\n avatarUrl: attendee.avatarUrl,\n userId: attendee.userId,\n state: \"knocking\",\n knockedAt: attendee.knockedAt ?? attendee.joinedAt ?? new Date().toISOString(),\n knockExpiresAt: attendee.knockExpiresAt ?? null,\n decidedAt: null,\n decidedBy: null,\n };\n}\n\n/** The row's word for a person no longer waiting, as the host's lobby list records it. */\nfunction decidedState(state: AdmissionState): AdmissionState {\n return WAITING_ADMISSION_STATES.includes(state) ? \"expired\" : state;\n}\n\n/** The server's state read, as this package reads it. */\ninterface StateRead {\n readonly admission_state?: string | null;\n readonly admitters_present?: boolean;\n readonly reknock_allowed?: boolean;\n readonly knock_expires_at?: string | null;\n readonly session_phase?: string;\n readonly meeting_phase?: string;\n readonly live_version?: number;\n readonly poll_seconds?: number;\n readonly exit_reason?: string | null;\n}\n\nexport function createMeetingSession(options: MeetingSessionOptions): MeetingSession {\n const { manager, repository, store, identity } = options;\n let channel: ChannelHandle | null = null;\n let meetingChannel: ChannelHandle | null = null;\n\n /**\n * The meeting row, for anyone who may be in it. A member or a person the\n * meeting was shared with reads it under RLS; a signed-in person from ANOTHER\n * organization who followed the link cannot, and gets the same row through\n * the link door (`meet_meeting_by_slug`) a guest uses — the server lets them\n * in by the link, so the screen must not fail them for holding it.\n */\n async function readMeeting(meetingId: MeetingId): Promise<MeetingRecord> {\n try {\n return await repository.meeting(meetingId);\n } catch (error) {\n const slug = store.snapshot().meeting?.slug;\n if (slug === undefined) throw error;\n return repository.meetingBySlug(slug);\n }\n }\n\n function requireHostUser(operation: string): UserId {\n // THE SAME PREDICATE THE UI RENDERS FROM (MRI-A11). Reading only the roster\n // here — while the control renders from the token grant — is how a host\n // control becomes a button that throws the moment it is used.\n if (identity.userId === null || !isHostRole(effectiveRole(store.snapshot()))) {\n throw new MeetError(\n \"forbidden\",\n `${operation}: this participant is not a host`,\n \"Host powers come from the server-minted token metadata and are enforced by the RPC. \" +\n \"Hide the control rather than rendering a button that will be refused.\",\n );\n }\n return identity.userId;\n }\n\n function requireMeeting(operation: string): MeetingRecord {\n const meeting = store.snapshot().meeting;\n if (meeting === null) {\n throw new MeetError(\n \"misconfigured\",\n `${operation}: no meeting is loaded`,\n \"Ad-hoc call rooms have no meeting record and therefore no lobby or lock. Use \" +\n \"joinMeeting()/scheduleMeeting() when you need those powers.\",\n );\n }\n return meeting;\n }\n\n // ── S4: the waiting supervisor ─────────────────────────────────────────\n let polling = false;\n let pollTimer: ReturnType<typeof setTimeout> | null = null;\n let reading: Promise<void> | null = null;\n let admittedOnce = false;\n let unlisten: (() => void) | null = null;\n\n function report(level: \"info\" | \"warn\" | \"error\", message: string, remedy?: string): void {\n options.onDiagnostic?.(remedy === undefined ? { level, message } : { level, message, remedy });\n }\n\n function pollMs(): number {\n const seconds = store.snapshot().waiting?.pollSeconds ?? 3;\n return Math.max(1, Math.min(60, seconds)) * 1000;\n }\n\n function schedule(ms: number): void {\n if (pollTimer !== null) clearTimeout(pollTimer);\n if (!polling) return;\n pollTimer = setTimeout(() => {\n pollTimer = null;\n void readState().finally(() => schedule(pollMs()));\n }, ms);\n }\n\n function listen(): void {\n if (unlisten !== null || typeof window === \"undefined\") return;\n const nudge = (): void => {\n if (typeof document !== \"undefined\" && document.visibilityState === \"hidden\") return;\n void readState();\n };\n // Closing the tab while waiting withdraws the knock (beacon: no headers, pass in the body).\n const goodbye = (): void => {\n const meeting = store.snapshot().meeting;\n const pass = options.resolvePass?.() ?? null;\n const waiting = store.snapshot().waiting;\n if (meeting === null || pass === null || waiting === null) return;\n if (!WAITING_ADMISSION_STATES.includes(waiting.admission)) return;\n try {\n navigator.sendBeacon?.(\n options.api.url(MEET_ROUTES.leave),\n new Blob([JSON.stringify({ meeting_id: meeting.id, intent: \"withdraw\", meeting_pass: pass })], {\n type: \"application/json\",\n }),\n );\n } catch {\n // Best effort: an unanswered knock still lapses on its own (knock_timeout_minutes).\n }\n };\n window.addEventListener(\"online\", nudge);\n window.addEventListener(\"pagehide\", goodbye);\n document.addEventListener(\"visibilitychange\", nudge);\n unlisten = () => {\n window.removeEventListener(\"online\", nudge);\n window.removeEventListener(\"pagehide\", goodbye);\n document.removeEventListener(\"visibilitychange\", nudge);\n };\n }\n\n function stopWaiting(): void {\n polling = false;\n if (pollTimer !== null) clearTimeout(pollTimer);\n pollTimer = null;\n unlisten?.();\n unlisten = null;\n }\n\n function patchWaiting(next: Partial<MeetWaiting>): void {\n const current = store.snapshot().waiting;\n const base: MeetWaiting = current ?? {\n admission: \"knocking\",\n admittersPresent: null,\n reknockAllowed: true,\n knockExpiresAt: null,\n pollSeconds: 3,\n liveVersion: 0,\n };\n store.patch({ waiting: { ...base, ...next } });\n }\n\n function apply(read: StateRead): void {\n const snapshot = store.snapshot();\n if (!isWaitingPhase(snapshot.phase)) return;\n const admission = (read.admission_state ?? null) as AdmissionState | null;\n const ended =\n admission === \"ended\" || read.session_phase === \"ended\" || read.meeting_phase === \"ended\";\n if (admission === \"admitted\" && !ended) {\n if (admittedOnce) return;\n admittedOnce = true;\n stopWaiting();\n // ADMISSION IS A JOIN: a fresh mint with the grants the server now gives.\n options.onAdmitted?.();\n return;\n }\n if (ended) {\n stopWaiting();\n patchWaiting({ admission: \"ended\" });\n const meeting = snapshot.meeting;\n if (read.meeting_phase === \"ended\" && meeting !== null && meeting.endedAt === null) {\n store.patch({ meeting: { ...meeting, endedAt: new Date().toISOString() } });\n }\n store.dispatch({ type: \"admission\", admission: \"ended\" }, { reason: \"ended\" });\n return;\n }\n if (admission === null) return;\n patchWaiting({\n admission,\n admittersPresent: read.admitters_present ?? null,\n reknockAllowed: read.reknock_allowed ?? true,\n knockExpiresAt: read.knock_expires_at ?? null,\n pollSeconds: read.poll_seconds ?? snapshot.waiting?.pollSeconds ?? 3,\n liveVersion: read.live_version ?? 0,\n });\n // THE SERVER'S WORD MOVES THE PHASE (§3.1): one machine event, never a patch.\n store.dispatch({ type: \"admission\", admission });\n // A decision that is not \"yes\" ends the wait; the screen offers what the rules allow.\n if (admission === \"denied\" || admission === \"expired\" || admission === \"withdrawn\" || admission === \"removed\") {\n stopWaiting();\n }\n }\n\n async function readState(): Promise<void> {\n if (reading !== null) return reading;\n const meeting = store.snapshot().meeting;\n const pass = options.resolvePass?.() ?? null;\n if (meeting === null || pass === null) return;\n reading = (async () => {\n try {\n const response = await options.api.request({\n path: `${MEET_ROUTES.state}?meeting_id=${encodeURIComponent(meeting.id)}`,\n method: \"GET\",\n requireAuth: false,\n operation: \"meet.state\",\n headers: { [MEETING_PASS_HEADER]: pass },\n });\n if (!response.ok) {\n let body: unknown = null;\n try {\n body = await response.json();\n } catch {\n body = null;\n }\n const refusal = readServerRefusal(body);\n report(\n \"warn\",\n refusal?.reason ?? `meet.state: the server answered ${response.status}`,\n refusal?.remedy ?? \"The waiting room keeps asking; nothing on this screen changes until the server answers.\",\n );\n return;\n }\n apply((await response.json()) as StateRead);\n } catch (error) {\n // Offline or the server is unreachable: the next tick (or `online`) asks again.\n report(\"info\", `meet.state: ${String(error)}`, \"Waiting continues; the state is read again shortly.\");\n } finally {\n reading = null;\n }\n })();\n return reading;\n }\n\n async function postLeave(intent: \"withdraw\" | \"leave\"): Promise<void> {\n const meeting = requireMeeting(`meet.${intent}`);\n const pass = options.resolvePass?.() ?? null;\n const response = await options.api.request({\n path: MEET_ROUTES.leave,\n method: \"POST\",\n requireAuth: false,\n operation: `meet.${intent}`,\n body: { meeting_id: meeting.id, intent },\n ...(pass !== null ? { headers: { [MEETING_PASS_HEADER]: pass } } : {}),\n });\n if (!response.ok) {\n let body: unknown = null;\n try {\n body = await response.json();\n } catch {\n body = null;\n }\n const refusal = readServerRefusal(body);\n throw new MeetError(\n \"transport\",\n refusal?.reason ?? `meet.${intent}: the server answered ${response.status}`,\n refusal?.remedy ?? \"You are still in the waiting room. Try again.\",\n );\n }\n }\n\n /** After a member-client admission write the CLIENT sends the hint (CORE-DESIGN §2.4). */\n function hint(): void {\n channel?.send(MEET_EVENTS.stateChanged, { v: null });\n }\n\n /** The host's second carrier: on a hint, re-read who is at the door. */\n async function refreshQueue(): Promise<void> {\n const meeting = store.snapshot().meeting;\n if (meeting === null || isGuest(identity) || !isHostRole(effectiveRole(store.snapshot()))) return;\n try {\n const attendees = await repository.attendees(meeting.id);\n store.learn(attendees);\n for (const attendee of attendees) {\n const entry = lobbyEntryFrom(attendee);\n if (entry !== null) store.upsertLobby(entry);\n else store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);\n }\n } catch {\n // Postgres Changes on the same rows is the other carrier; a miss here costs nothing.\n }\n }\n\n return {\n watch(roomName) {\n channel?.close();\n channel = manager.open({\n topic: roomTopic(roomName),\n broadcast: [\n {\n // 🚨 PAYLOAD-FREE. Every receiver reads again; a spoofed hint only causes a read.\n event: MEET_EVENTS.stateChanged,\n onMessage: () => {\n if (isWaitingPhase(store.snapshot().phase)) void readState();\n void refreshQueue();\n },\n },\n ],\n // THE BACKFILL DOOR. A socket blip must not lose a decision: re-read the meeting\n // row, and a waiting tab re-reads its own state.\n onBackfill: async () => {\n const meeting = store.snapshot().meeting;\n if (meeting === null) return;\n const fresh = isGuest(identity)\n ? await repository.meetingBySlug(meeting.slug)\n : await readMeeting(meeting.id);\n store.patch({ meeting: fresh, locked: fresh.locked });\n if (isWaitingPhase(store.snapshot().phase)) void readState();\n },\n });\n },\n\n watchMeeting(meetingId) {\n meetingChannel?.close();\n meetingChannel = null;\n if (isGuest(identity)) {\n options.onDiagnostic?.({\n level: \"info\",\n message: \"Guest notes and captions arrive through the live meeting room; saved history is not available without a session.\",\n remedy: \"Sign in with access to this meeting to reload saved notes and transcript history.\",\n });\n return;\n }\n meetingChannel = manager.open({\n topic: meetingTopic(meetingId),\n postgresChanges: [\n {\n event: \"*\",\n schema: repository.schema,\n table: repository.tables.transcripts,\n filter: `meeting_id=eq.${meetingId}`,\n rowId: (row) => (typeof row[\"id\"] === \"string\" ? row[\"id\"] : undefined),\n onChange: ({ row }) => {\n // D16: THE ROW IS THE TRANSCRIPT. It replaces the caption that\n // showed the same words a moment earlier.\n if (row !== null) store.ingestRecord(repository.projectSegment(row));\n },\n },\n {\n event: \"*\",\n schema: repository.schema,\n table: repository.tables.notes,\n filter: `meeting_id=eq.${meetingId}`,\n rowId: (row) => (typeof row[\"id\"] === \"string\" ? row[\"id\"] : undefined),\n onChange: ({ row }) => {\n // Upsert, not append: a re-run of the wrap-up rewrites the SAME\n // row ids by design, and appending would show every note twice.\n if (row !== null) store.upsertNote(repository.projectNote(row));\n },\n },\n {\n // 🚨 THE LOBBY IS A TABLE, NOT ONLY A CHANNEL (MRI-A16). A guest\n // knocks by writing `admission_state='knocking'` AND broadcasting;\n // through 0.4.7 the host's People panel rendered only the broadcast,\n // so a knocker whose packet landed before this browser opened the\n // channel — a host who joined second, reloaded, or slept a laptop —\n // was INVISIBLE while the guest sat in the waiting room forever. A\n // host can read this table; a guest cannot, and gets no lobby list\n // at all, which is correct.\n event: \"*\",\n schema: repository.schema,\n table: repository.tables.participants,\n filter: `meeting_id=eq.${meetingId}`,\n rowId: (row) => (typeof row[\"id\"] === \"string\" ? row[\"id\"] : undefined),\n onChange: ({ row }) => {\n if (row === null) return;\n const attendee = repository.projectAttendee(row);\n store.learn([attendee]);\n const entry = lobbyEntryFrom(attendee);\n if (entry !== null) {\n store.upsertLobby(entry);\n return;\n }\n // 🚨 A DECIDED ROW MUST CLEAR THE DOOR. Admitting a guest from\n // another tab writes `admitted` here, and simply ignoring the row\n // would leave this host's panel offering to admit somebody who is\n // already in the meeting — a ghost knocker who can never be\n // dismissed. The durable row is the verdict, wherever it was made.\n store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);\n },\n },\n {\n event: \"*\",\n schema: repository.schema,\n table: repository.tables.recordings,\n filter: `meeting_id=eq.${meetingId}`,\n rowId: (row) => (typeof row[\"id\"] === \"string\" ? row[\"id\"] : undefined),\n onChange: ({ row }) => {\n // 🚨 THE INDICATOR RENDERS THE DURABLE STATE, not this browser's\n // hope. Through 0.4.7 nothing subscribed here, so the only\n // transitions the store ever saw were the optimistic `starting`\n // and `stopping` THIS client patched — and the indicator sat on\n // \"Starting recording…\" for a whole meeting while a healthy\n // egress ran, then \"Stopping recording…\" forever (MRI-C8).\n if (row === null) return;\n const recording = repository.projectRecording(row);\n store.setRecording(recording.state, recording.startedBy, \"record\", recording.id);\n },\n },\n {\n event: \"*\",\n schema: repository.schema,\n table: repository.tables.meetings,\n filter: `id=eq.${meetingId}`,\n rowId: (row) => (typeof row[\"id\"] === \"string\" ? row[\"id\"] : undefined),\n onChange: ({ row }) => {\n // The note-taker's state lives on this row's metadata (D9/MRI-A2),\n // so this subscription is what keeps each server-recorded state\n // current on screen without a reload.\n if (row === null) return;\n const meeting = repository.projectMeeting(row);\n store.patch({ meeting, locked: meeting.locked });\n },\n },\n ],\n // THE BACKFILL DOOR. Realtime has no replay: everything that happened\n // while the socket was down is gone. Re-reading all three is what keeps\n // a recovered surface from looking healthy and being wrong.\n onBackfill: async () => {\n const [meeting, transcript, notes, recordings, attendees] = await Promise.all([\n readMeeting(meetingId),\n repository.transcript(meetingId),\n repository.notes(meetingId),\n repository.recordings(meetingId),\n repository.attendees(meetingId),\n ]);\n store.patch({ meeting, locked: meeting.locked });\n for (const segment of transcript) store.ingestRecord(segment);\n for (const note of notes) store.upsertNote(note);\n // A socket blip must not leave the indicator saying something the\n // database stopped agreeing with ten minutes ago. `recordings` is\n // newest first.\n const latest = recordings[0];\n if (latest !== undefined) {\n store.setRecording(latest.state, latest.startedBy, \"record\", latest.id);\n }\n // Everyone the durable rows know a name for, and every knocker still\n // waiting. A host who reloads must not lose a person at the door.\n store.learn(attendees);\n for (const attendee of attendees) {\n const entry = lobbyEntryFrom(attendee);\n if (entry !== null) {\n store.upsertLobby(entry);\n } else {\n // The same rule on the backfill door: a host who reloads must not\n // find people at the door who were admitted while they were gone.\n store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);\n }\n }\n },\n });\n // The FIRST read is the same read: a surface that joins a meeting already\n // in progress must see what was already said.\n meetingChannel.requestBackfill(\"manual\");\n },\n\n async admit(entry, admitted) {\n const by = requireHostUser(\"meet.admit\");\n const meeting = requireMeeting(\"meet.admit\");\n // THE RPC IS THE DECISION, and it returns the row as it now stands — that row (not\n // this click) is what the panel shows. Two hosts deciding at once is the ordinary\n // case: the second sees the first's verdict.\n const row = await repository.decideAdmission({\n meetingId: meeting.id,\n identity: entry.identity,\n decision: admitted ? \"admit\" : \"deny\",\n byUserId: by,\n });\n store.upsertLobby({\n ...entry,\n state: decidedState(row.admissionState),\n decidedAt: new Date().toISOString(),\n decidedBy: by,\n });\n hint();\n },\n\n async undeny(entry) {\n const by = requireHostUser(\"meet.undeny\");\n const meeting = requireMeeting(\"meet.undeny\");\n const row = await repository.decideAdmission({\n meetingId: meeting.id,\n identity: entry.identity,\n decision: \"undeny\",\n byUserId: by,\n });\n store.upsertLobby({ ...entry, state: decidedState(row.admissionState), decidedAt: new Date().toISOString(), decidedBy: by });\n hint();\n },\n\n async admitAll() {\n const by = requireHostUser(\"meet.admitAll\");\n const meeting = requireMeeting(\"meet.admitAll\");\n const rows = await repository.admitAll(meeting.id, by);\n for (const row of rows) {\n store.setAdmission(row.identity, row.admissionState, by);\n }\n hint();\n },\n\n startWaiting() {\n if (polling) return;\n polling = true;\n admittedOnce = false;\n listen();\n schedule(0);\n },\n\n stopWaiting,\n readState,\n\n async readExit(connection) {\n const meeting = store.snapshot().meeting;\n const pass = options.resolvePass?.() ?? null;\n if (meeting === null || pass === null) return undefined;\n try {\n const response = await options.api.request({\n path: `${MEET_ROUTES.state}?meeting_id=${encodeURIComponent(meeting.id)}&connection=${encodeURIComponent(connection)}`,\n method: \"GET\",\n requireAuth: false,\n operation: \"meet.state(exit)\",\n headers: { [MEETING_PASS_HEADER]: pass },\n });\n if (!response.ok) return null;\n const read = (await response.json()) as StateRead;\n return {\n exitReason: read.exit_reason ?? null,\n sessionPhase: read.session_phase ?? null,\n meetingPhase: read.meeting_phase ?? null,\n };\n } catch {\n return null;\n }\n },\n\n async withdraw() {\n stopWaiting();\n await postLeave(\"withdraw\");\n patchWaiting({ admission: \"withdrawn\" });\n store.dispatch({ type: \"admission\", admission: \"withdrawn\" });\n hint();\n },\n\n async askAgain() {\n // A new mint IS the new knock (the server upserts the person row per the re-knock rule).\n await options.rejoin?.();\n },\n\n async setLock(locked) {\n const by = requireHostUser(\"meet.setLock\");\n const meeting = requireMeeting(\"meet.setLock\");\n const updated = await repository.setLock(meeting.id, locked, by);\n store.patch({ meeting: updated, locked: updated.locked });\n // The lock is the meeting row (members see it through Postgres Changes); waiting people\n // are told to read again.\n hint();\n return updated;\n },\n\n /**\n * END IT FOR EVERYONE — a SERVER decision, not a row this client updates.\n *\n * 0.2.1 called the `meet_end_meeting` RPC directly, which ended the meeting\n * in the database while LiveKit kept the room open and the note-taker kept\n * writing into an ended meeting. `POST /v1/meet/end` re-checks the host,\n * closes the room, and triggers the wrap-up — one decision, one place\n * (MRI-A6).\n *\n * 🚨 A SERVER THAT HAS NOT LANDED THAT ROUTE ANSWERS 404, and that is\n * reported as a sentence, never as a dead control and never as a success.\n */\n async endForEveryone() {\n requireHostUser(\"meet.endForEveryone\");\n const meeting = requireMeeting(\"meet.endForEveryone\");\n const response = await options.api.request({\n path: MEET_ROUTES.meetingEnd,\n method: \"POST\",\n requireAuth: true,\n operation: \"meet.endForEveryone\",\n body: {\n meeting_id: meeting.id,\n organization_id: identity.organizationId,\n },\n });\n if (response.status === 404) {\n return { endedAt: null, served: false };\n }\n if (!response.ok) {\n let body: unknown = null;\n try {\n body = await response.json();\n } catch {\n body = null;\n }\n const refusal = readServerRefusal(body);\n throw new MeetError(\n response.status === 403 ? \"forbidden\" : \"transport\",\n refusal?.reason ?? `meet.endForEveryone: the server answered ${response.status}`,\n refusal?.remedy !== undefined && refusal.remedy.length > 0\n ? refusal.remedy\n : \"The meeting is still running. Only a host may end it, and the server \" +\n \"re-checks that — being a host on this screen is not the decision.\",\n );\n }\n let payload: unknown = null;\n try {\n payload = await response.json();\n } catch {\n payload = null;\n }\n const row =\n typeof payload === \"object\" && payload !== null\n ? (payload as Record<string, unknown>)\n : {};\n const endedAt =\n typeof row[\"ended_at\"] === \"string\" ? row[\"ended_at\"] : new Date().toISOString();\n const current = store.snapshot().meeting;\n if (current !== null) store.patch({ meeting: { ...current, endedAt } });\n return { endedAt, served: true };\n },\n\n dispose() {\n stopWaiting();\n channel?.close();\n channel = null;\n meetingChannel?.close();\n meetingChannel = null;\n },\n };\n}\n\n/**\n * A durable public slug. Lowercase, hyphenated, and deliberately readable —\n * people read these aloud on phone calls. The alphabet excludes the four\n * characters that get misheard (0/O, 1/l).\n */\nconst SLUG_ALPHABET = \"abcdefghijkmnpqrstuvwxyz23456789\";\n\nexport function meetingSlug(random: () => number = Math.random): string {\n const group = (length: number): string =>\n Array.from({ length }, () => {\n const index = Math.floor(random() * SLUG_ALPHABET.length);\n return SLUG_ALPHABET[index] ?? \"a\";\n }).join(\"\");\n return `${group(3)}-${group(4)}-${group(3)}`;\n}\n\n/** The link a host copies. ONE shape, so every surface produces the same URL. */\nexport function meetingLink(baseUrl: string, slug: string): string {\n return `${baseUrl.replace(/\\/+$/, \"\")}/meet/${slug}`;\n}\n","/**\n * THE NOTE-TAKER, AS A SURFACE MUST RENDER IT.\n *\n * 🚨 **THE PACKAGE NEVER GUESSES WHAT THE AGENT IS DOING.** Two facts decide\n * everything here and both come from outside this client: the meeting row's\n * `metadata.note_taker` (written by the server with a CAS, MRI-A2/D9) and\n * whether the agent participant is actually in the LiveKit room. A spinner —\n * \"starting…\" held open by a client-side timer — is the exact lie this file\n * exists to prevent: it survives a worker that never came, a dispatch nobody\n * claimed, and a job that died on a release.\n *\n * 🚨 **A REFUSAL IS RENDERED VERBATIM.** `/agent/start` answers a 503 whose body\n * carries `reason` and `remedy` written by the readiness gate (D7) — \"no worker\n * is registered under matrx-note-taker\", \"every worker is at capacity\",\n * \"the fleet is draining\". Those sentences are the truth about the system and\n * they reach the screen unedited. This package adds no sentence of its own on\n * top of one the server already wrote.\n *\n * The agent's identity is DETERMINISTIC — `agent:note_taker:<meeting_id>` (D9) —\n * which is what makes \"is it in the room\" answerable without a round trip, and\n * what makes a second note-taker impossible to mistake for the first.\n */\n\nimport type { JsonObject, MeetParticipant, MeetingRecord, NoteTakerState } from \"./types\";\n\n/** The one spelling of the agent's participant identity (D9). */\nexport const NOTE_TAKER_IDENTITY_PREFIX = \"agent:note_taker:\";\n\nexport function noteTakerIdentity(meetingId: string): string {\n return `${NOTE_TAKER_IDENTITY_PREFIX}${meetingId}`;\n}\n\n/**\n * The participant attribute the note-taker sets on itself so EVERY client —\n * including one that cannot read the token metadata — knows a notice is owed\n * (D10).\n */\nexport const CONSENT_NOTICE_ATTRIBUTE = \"matrx.agent.consent_notice\";\n\n/** Where the state lives on the meeting row. One spelling, one file. */\nexport const NOTE_TAKER_METADATA_KEY = \"note_taker\";\n\nconst STATES: readonly NoteTakerState[] = [\n \"absent\",\n \"joining\",\n \"transcribing\",\n \"handoff_pending\",\n \"ended\",\n];\n\n/** The server's refusal, as it wrote it. Never paraphrased. */\nexport interface NoteTakerRefusal {\n readonly reason: string;\n readonly remedy: string;\n readonly status: number;\n}\n\nexport interface NoteTakerStatus {\n readonly state: NoteTakerState;\n /** The server's own words for why it is in this state, when it gave any. */\n readonly detail: string | null;\n /** True when the agent participant is in the room RIGHT NOW. */\n readonly present: boolean;\n /** The last dispatch refusal, rendered verbatim. */\n readonly refusal: NoteTakerRefusal | null;\n}\n\nfunction readState(value: unknown): NoteTakerState | null {\n return typeof value === \"string\" && STATES.includes(value as NoteTakerState)\n ? (value as NoteTakerState)\n : null;\n}\n\n/** Is this participant the note-taker (or any agent that owes a notice, D10)? */\nexport function isNoteTaker(participant: MeetParticipant): boolean {\n return (\n participant.identity.startsWith(NOTE_TAKER_IDENTITY_PREFIX) ||\n participant.attributes[CONSENT_NOTICE_ATTRIBUTE] !== undefined ||\n participant.isAgent\n );\n}\n\n/** The agent participant in the room, if any. */\nexport function noteTakerParticipant(\n participants: readonly MeetParticipant[],\n): MeetParticipant | null {\n return participants.find(isNoteTaker) ?? null;\n}\n\n/** The exact deterministic actor whose room presence can corroborate the row. */\nfunction exactNoteTakerParticipant(\n meeting: MeetingRecord | null,\n participants: readonly MeetParticipant[],\n): MeetParticipant | null {\n if (meeting === null) return null;\n const identity = noteTakerIdentity(meeting.id);\n return participants.find((participant) => participant.identity === identity) ?? null;\n}\n\n/**\n * THE STATE, from the two facts that decide it.\n *\n * The durable row is the authority for work. Exact room presence corroborates\n * only a durable `transcribing` state; an arbitrary agent, a consent marker,\n * or a stale note-taker participant never proves speech capture.\n */\nexport function noteTakerStatus(args: {\n meeting: MeetingRecord | null;\n participants: readonly MeetParticipant[];\n refusal?: NoteTakerRefusal | null;\n}): NoteTakerStatus {\n const present = exactNoteTakerParticipant(args.meeting, args.participants) !== null;\n const raw = args.meeting?.metadata[NOTE_TAKER_METADATA_KEY];\n const row: JsonObject =\n typeof raw === \"object\" && raw !== null && !Array.isArray(raw) ? raw : {};\n const recorded = readState(row[\"state\"]);\n const detail = typeof row[\"reason\"] === \"string\" && row[\"reason\"].length > 0\n ? row[\"reason\"]\n : null;\n const refusal = args.refusal ?? null;\n\n if (recorded === null) {\n return { state: \"absent\", detail, present, refusal };\n }\n if (recorded === \"transcribing\" && !present) {\n return { state: \"handoff_pending\", detail, present, refusal };\n }\n return { state: recorded, detail, present, refusal };\n}\n\n/**\n * THE SENTENCE FOR EACH STATE. One place, so the room, the panel and any future\n * surface say the same true thing — and so a state can never render as a bare\n * enum name.\n */\nexport function noteTakerLabel(status: NoteTakerStatus): string {\n switch (status.state) {\n case \"joining\":\n return \"The note-taker is joining this meeting\";\n case \"transcribing\":\n return \"The note-taker is connected and listening for speech\";\n case \"handoff_pending\":\n return \"The note-taker's transcription is interrupted; reconnection is pending\";\n case \"ended\":\n return \"The note-taker has stopped\";\n case \"absent\":\n default:\n return status.present\n ? \"A meeting assistant is connected. Transcription is not confirmed\"\n : \"No note-taker is in this meeting\";\n }\n}\n","/**\n * THE ONE PLACE `livekit-client` IS TOUCHED.\n *\n * Everything above this file talks to `MeetRoomDriver`. That containment is\n * what lets the engine's whole behavior — ring timeouts, admission, escalation,\n * reconnection — be proven in a node test, and it is why an SDK upgrade is a\n * single-file review rather than a package-wide audit.\n *\n * 🚨 **THE IMPORT IS DYNAMIC, DELIBERATELY.** `import type` is erased, and the\n * runtime `await import(\"livekit-client\")` happens inside `connect()`. So:\n * importing `@ai-matrx/meet` during SSR, or on a page that renders a call\n * BUTTON but never a call, pulls in none of the media SDK and touches no\n * browser API (independence contract §4). A static import here would put ~400KB\n * of WebRTC into every consumer's first load and break RSC rendering.\n *\n * The mapping helpers below are exported for their own tests: an SDK enum\n * renamed upstream must fail a named test, not silently become \"unknown\" on\n * every tile.\n */\n\nimport type {\n ConnectionQuality as LiveKitQuality,\n Participant,\n Room,\n RoomOptions,\n} from \"livekit-client\";\nimport { MeetError, normalizeDeviceError, normalizeScreenShareError } from \"./errors\";\nimport {\n TRANSCRIPTION_ATTRIBUTES,\n TRANSCRIPTION_TOPIC,\n type AudioProcessing,\n type DriverConnectionState,\n type DriverParticipant,\n type DriverTrackKind,\n type MeetRoomDriver,\n type RoomDriverHandlers,\n type VideoEffect,\n} from \"./room-driver\";\nimport type { ConnectionQuality } from \"./types\";\n\n/**\n * LiveKit's quality enum → ours. Mapped explicitly rather than cast: the SDK\n * has five states and our UI has five sentences, and an unmapped one must\n * degrade to `unknown` (an honest \"we don't know\") rather than to `excellent`\n * (a lie that hides a failing call).\n */\nexport function mapQuality(quality: LiveKitQuality | string): ConnectionQuality {\n switch (quality) {\n case \"excellent\":\n return \"excellent\";\n case \"good\":\n return \"good\";\n case \"poor\":\n return \"poor\";\n case \"lost\":\n return \"lost\";\n default:\n return \"unknown\";\n }\n}\n\nexport function mapConnectionState(state: string): DriverConnectionState {\n switch (state) {\n case \"connected\":\n return \"connected\";\n case \"connecting\":\n return \"connecting\";\n case \"reconnecting\":\n case \"signalReconnecting\":\n return \"reconnecting\";\n default:\n return \"disconnected\";\n }\n}\n\n/**\n * A LiveKit participant → our snapshot.\n *\n * 🚨 `name` IS REPORTED EXACTLY AS LIVEKIT GAVE IT, empty string and all\n * (MRI-A18). Through 0.4.12 it fell back to the identity here, which meant a\n * driver — the thing furthest from any screen — invented a \"name\" that\n * `core/names.ts` then had to recognize and refuse. A driver reports; it does\n * not name. The one fallback lives in `projectParticipant`, and it is the name\n * book's honest placeholder rather than a token.\n */\nexport function snapshotParticipant(\n participant: Pick<\n Participant,\n \"identity\" | \"name\" | \"metadata\" | \"isMicrophoneEnabled\" | \"isCameraEnabled\" | \"isScreenShareEnabled\"\n > & { attributes?: Readonly<Record<string, string>> | undefined },\n isLocal: boolean,\n): DriverParticipant {\n const name = participant.name;\n return {\n identity: participant.identity,\n name: name ?? \"\",\n metadata: participant.metadata ?? null,\n // ATTRIBUTES ARE PART OF THE SNAPSHOT, not read ad hoc later: the note-taker\n // announces the consent notice here (D10) and the roster is the only place\n // this package looks at a participant.\n attributes: participant.attributes ?? {},\n isLocal,\n micEnabled: participant.isMicrophoneEnabled,\n cameraEnabled: participant.isCameraEnabled,\n screenSharing: participant.isScreenShareEnabled,\n };\n}\n\nexport interface LiveKitDriverOptions {\n /** Adaptive stream + dynacast default ON — R5 scale honesty, not a knob. */\n roomOptions?: RoomOptions | undefined;\n}\n\n/** The browser defaults every call product ships with. */\nexport const DEFAULT_AUDIO_PROCESSING: AudioProcessing = {\n noiseSuppression: true,\n echoCancellation: true,\n autoGainControl: true,\n};\n\n/**\n * Background effects run a segmentation model on the GPU (MediaPipe via\n * `@livekit/track-processors`). A browser without `OffscreenCanvas` or WebGL2\n * cannot — the control is then ABSENT, never a toggle that silently fails.\n */\nexport function videoEffectsSupported(): boolean {\n const scope = globalThis as unknown as {\n OffscreenCanvas?: unknown;\n document?: { createElement(tag: string): { getContext(kind: string): unknown } };\n };\n if (typeof scope.OffscreenCanvas === \"undefined\" || scope.document === undefined) return false;\n try {\n return scope.document.createElement(\"canvas\").getContext(\"webgl2\") !== null;\n } catch {\n return false;\n }\n}\n\n/**\n * Put the chosen effect on the local camera. The processor package is imported\n * lazily, exactly like `livekit-client` itself, so a meeting nobody blurs never\n * downloads a segmentation model.\n */\nasync function applyVideoEffect(target: Room, effect: VideoEffect): Promise<void> {\n const { Track } = await import(\"livekit-client\");\n const track = target.localParticipant.getTrackPublication(Track.Source.Camera)?.track as\n | {\n setProcessor(processor: unknown): Promise<void>;\n stopProcessor(): Promise<void>;\n }\n | undefined;\n if (track === undefined) return; // camera off: applied when it turns on.\n try {\n if (effect.kind === \"none\") {\n await track.stopProcessor();\n return;\n }\n const processors = await import(\"@livekit/track-processors\");\n const processor =\n effect.kind === \"blur\"\n ? processors.BackgroundBlur(effect.radius ?? 12)\n : processors.VirtualBackground(effect.imageUrl);\n await track.setProcessor(processor);\n } catch (error) {\n throw new MeetError(\n \"media-unsupported\",\n \"The background effect could not start on this camera\",\n \"Background effects need a browser with WebGL2 (current Chrome, Edge, Firefox or Safari). \" +\n \"Your video continues without the effect.\",\n error,\n );\n }\n}\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\nexport function createLiveKitDriver(\n handlers: RoomDriverHandlers,\n options: LiveKitDriverOptions = {},\n): MeetRoomDriver {\n let room: Room | null = null;\n /**\n * The roster projection for the CURRENT room, kept out here so `connect()`\n * can fire it once the transport is up. See the call site for why.\n */\n let projectRoster: (() => void) | null = null;\n /** What the person chose, kept so a re-enabled track gets it too. */\n let audioCapture: AudioProcessing = DEFAULT_AUDIO_PROCESSING;\n let videoEffect: VideoEffect = { kind: \"none\" };\n\n async function load(): Promise<Room> {\n if (room !== null) return room;\n let module: typeof import(\"livekit-client\");\n try {\n module = await import(\"livekit-client\");\n } catch (error) {\n throw new MeetError(\n \"misconfigured\",\n \"livekit-client could not be loaded\",\n \"`livekit-client` is a dependency of @ai-matrx/meet and is imported lazily at \" +\n \"connect time. If this failed, the bundler excluded it — remove it from any \" +\n \"`external`/`serverExternalPackages` list, or pass your own driver factory.\",\n error,\n );\n }\n const { Room: LiveKitRoom, RoomEvent, Track } = module;\n const created = new LiveKitRoom({\n // R5: large meetings degrade gracefully instead of melting a laptop.\n adaptiveStream: true,\n dynacast: true,\n ...options.roomOptions,\n });\n\n const roster = (): void => {\n const local = snapshotParticipant(created.localParticipant, true);\n const remotes = [...created.remoteParticipants.values()].map((participant) =>\n snapshotParticipant(participant, false),\n );\n // The FULL roster every time. Every one of these six events changes what\n // a tile must render, and a delta protocol across six sources drifts.\n handlers.participants([local, ...remotes]);\n };\n\n projectRoster = roster;\n\n // 🚨 A REMOTE MICROPHONE IS PLAYED HERE, OR NOWHERE. `livekit-client` never\n // plays a remote audio track by itself (`webAudioMix` is off by default): a\n // subscribed track makes sound only once `track.attach()` gives it a media\n // element. Until 0.7.10 nothing attached audio, so every meeting received\n // everyone's audio packets and played none of them (production\n // re-verification, 2026-09-29). Audio is NOT tied to tiles on purpose: the\n // stage paginates and the Board strip overflows, and a person on page two\n // must still be heard.\n created\n .on(RoomEvent.TrackSubscribed, (track) => {\n if (track.kind !== Track.Kind.Audio || typeof document === \"undefined\") return;\n const element = track.attach();\n element.setAttribute(\"data-mx-meet-audio\", \"\");\n audioSink().appendChild(element);\n })\n .on(RoomEvent.TrackUnsubscribed, (track) => {\n if (track.kind !== Track.Kind.Audio) return;\n for (const element of track.detach()) element.remove();\n });\n\n created\n .on(RoomEvent.ParticipantConnected, roster)\n .on(RoomEvent.ParticipantDisconnected, roster)\n .on(RoomEvent.TrackSubscribed, roster)\n .on(RoomEvent.TrackUnsubscribed, roster)\n .on(RoomEvent.TrackMuted, roster)\n .on(RoomEvent.TrackUnmuted, roster)\n .on(RoomEvent.LocalTrackPublished, roster)\n .on(RoomEvent.LocalTrackUnpublished, roster)\n // A SERVER DECISION CHANGED MID-MEETING (Meet wave 2): a co-host made or\n // unmade, the host handed over, a rename. The server re-stamps the\n // roster through `UpdateParticipant`; these three events carry it here.\n .on(RoomEvent.ParticipantNameChanged, roster)\n .on(RoomEvent.ParticipantAttributesChanged, roster)\n .on(RoomEvent.ParticipantMetadataChanged, roster)\n .on(RoomEvent.RoomMetadataChanged, (metadata) => {\n handlers.roomMetadata?.(metadata ?? \"\");\n })\n .on(RoomEvent.ActiveSpeakersChanged, (speakers) => {\n handlers.activeSpeakers(speakers.map((speaker) => speaker.identity));\n })\n .on(RoomEvent.ConnectionQualityChanged, (quality, participant) => {\n handlers.quality(participant.identity, mapQuality(quality));\n })\n .on(RoomEvent.ConnectionStateChanged, (state) => {\n handlers.connectionState(mapConnectionState(state));\n })\n .on(RoomEvent.DataReceived, (payload, participant, _kind, topic) => {\n handlers.data(\n topic ?? \"\",\n decoder.decode(payload),\n participant?.identity ?? null,\n );\n })\n .on(RoomEvent.Disconnected, (reason) => {\n audioContainer?.remove();\n audioContainer = null;\n // §3.4 ER reads LiveKit's reason by NAME (`DUPLICATE_IDENTITY`, `PARTICIPANT_REMOVED`, …):\n // the enum is numeric on the wire, and a number means nothing outside this file.\n const named = reason === undefined\n ? null\n : ((module?.DisconnectReason as unknown as Record<number, string> | undefined)?.[reason as unknown as number] ?? String(reason));\n handlers.disconnected(named);\n });\n\n // LIVE CAPTIONS (D16). The agent publishes one text stream per utterance on\n // `lk.transcription`, re-sending the same `lk.segment_id` as the words firm\n // up, and the FINAL flag says which one is the last. The chunks are read as\n // they arrive — waiting for `readAll()` would make captions appear only\n // once the sentence was over, which is not a caption.\n //\n // 🚨 REGISTERED BEFORE `connect()`, deliberately: LiveKit buffers data\n // packets that arrive before the room is connected and replays them, but a\n // handler registered after the first stream header lands misses that\n // utterance outright — the first thing anyone says in the meeting.\n try {\n created.registerTextStreamHandler(TRANSCRIPTION_TOPIC, (reader, participantInfo) => {\n const attributes: Record<string, string> = { ...(reader.info.attributes ?? {}) };\n const segmentId =\n attributes[TRANSCRIPTION_ATTRIBUTES.segmentId] ?? reader.info.id;\n const trackId = attributes[TRANSCRIPTION_ATTRIBUTES.trackId] ?? null;\n const final = attributes[TRANSCRIPTION_ATTRIBUTES.final] === \"true\";\n // `lk.transcribed_participant_identity` is WHO IS BEING TRANSCRIBED.\n // The SFU's publisher identity stays the note-taker, so using it here\n // would attribute the whole meeting to the agent. Do not use the\n // sender-identity override: LiveKit accepts it from an agent publisher,\n // but aidream deliberately does not set it.\n const speakerIdentity =\n attributes[TRANSCRIPTION_ATTRIBUTES.speakerIdentity] ?? participantInfo.identity;\n const at = new Date(reader.info.timestamp || Date.now()).toISOString();\n void (async () => {\n let text = \"\";\n try {\n for await (const chunk of reader) {\n text += chunk;\n // Emit as it grows: a caption that only appears when the sentence\n // ends is a transcript, not a caption.\n handlers.transcription({\n segmentId,\n speakerIdentity,\n publisherIdentity: participantInfo.identity,\n text,\n final,\n trackId,\n at,\n });\n }\n } catch (error) {\n // A caption stream that dies is a caption we do not have. It is\n // never fatal to the meeting and never silent.\n handlers.data(\n TRANSCRIPTION_TOPIC,\n JSON.stringify({ error: String((error as Error)?.message ?? error) }),\n participantInfo.identity,\n );\n }\n })();\n });\n } catch {\n // Registering twice throws in the SDK. `load()` is single-flight through\n // `room`, so this can only happen on a re-entrant remount — in which case\n // the existing handler is already delivering captions.\n }\n\n // Kept for `attach`, which needs the SDK's Source enum at call time.\n sources = {\n camera: Track.Source.Camera,\n microphone: Track.Source.Microphone,\n screen: Track.Source.ScreenShare,\n };\n trackEvents = {\n subscribed: RoomEvent.TrackSubscribed,\n localPublished: RoomEvent.LocalTrackPublished,\n };\n room = created;\n return created;\n }\n\n let sources: Record<DriverTrackKind, string> | null = null;\n /** The SDK's event names `attach` listens on, captured with `sources`. */\n let trackEvents: { subscribed: string; localPublished: string } | null = null;\n\n /** The hidden container every remote audio element of THIS room lives in. */\n let audioContainer: HTMLElement | null = null;\n function audioSink(): HTMLElement {\n if (audioContainer !== null && audioContainer.isConnected) return audioContainer;\n const container = document.createElement(\"div\");\n container.setAttribute(\"data-mx-meet-audio-sink\", \"\");\n container.setAttribute(\"aria-hidden\", \"true\");\n container.style.display = \"none\";\n document.body.appendChild(container);\n audioContainer = container;\n return container;\n }\n\n function requireRoom(operation: string): Room {\n if (room === null) {\n throw new MeetError(\n \"transport\",\n `${operation}: the room is not connected`,\n \"Call connect() first. The engine never reaches this — it gates every media call \" +\n \"on its own phase — so seeing it means a consumer drove the driver directly.\",\n );\n }\n return room;\n }\n\n return {\n async connect(serverUrl, token) {\n const target = await load();\n try {\n await target.connect(serverUrl, token);\n } catch (error) {\n // A connect failure is either media permission (user-actionable) or\n // transport. Both must reach the UI with a sentence, never a spinner.\n throw normalizeDeviceError(error, \"meet.connect\");\n }\n // 🚨 PROJECT THE ROSTER ONCE, HERE. Every other roster source is an\n // EVENT, and a participant who publishes nothing into an empty room\n // generates none of them: no `LocalTrackPublished`, no\n // `ParticipantConnected`, no track events. Until 0.3.2 the join always\n // published a microphone, so `LocalTrackPublished` fired and hid this —\n // and the moment listen-only attendance became possible, a listen-only\n // host was absent from their own People panel, had no tile, and got NO\n // host controls at all (the note-taker could not be started), because\n // host powers are read off the local participant's roster entry.\n projectRoster?.();\n // The room's metadata arrives WITH the join (the spotlight lives there),\n // and `RoomMetadataChanged` fires only for later changes.\n handlers.roomMetadata?.(target.metadata ?? \"\");\n },\n async disconnect() {\n if (room === null) return;\n const target = room;\n room = null;\n try {\n target.unregisterTextStreamHandler(TRANSCRIPTION_TOPIC);\n } catch {\n // Already gone. Teardown is idempotent by contract.\n }\n projectRoster = null;\n audioContainer?.remove();\n audioContainer = null;\n await target.disconnect();\n },\n async setMicrophoneEnabled(enabled) {\n try {\n await requireRoom(\"setMicrophoneEnabled\").localParticipant.setMicrophoneEnabled(\n enabled,\n audioCapture,\n );\n } catch (error) {\n throw normalizeDeviceError(error, \"meet.setMicrophoneEnabled\");\n }\n },\n async setCameraEnabled(enabled) {\n try {\n const target = requireRoom(\"setCameraEnabled\");\n await target.localParticipant.setCameraEnabled(enabled);\n // A REMEMBERED EFFECT FOLLOWS THE CAMERA. Turning the camera back on must\n // not quietly show the room behind someone who chose to blur it.\n if (enabled && videoEffect.kind !== \"none\") await applyVideoEffect(target, videoEffect);\n } catch (error) {\n throw normalizeDeviceError(error, \"meet.setCameraEnabled\");\n }\n },\n async setScreenShareEnabled(enabled) {\n try {\n await requireRoom(\"setScreenShareEnabled\").localParticipant.setScreenShareEnabled(enabled);\n } catch (error) {\n throw normalizeScreenShareError(error, \"meet.setScreenShareEnabled\");\n }\n },\n async publishData(topic, payload, to) {\n await requireRoom(\"publishData\").localParticipant.publishData(\n // `reliable` is not optional for a control plane: a dropped\n // \"hand lowered\" leaves a hand up on every other screen forever.\n encoder.encode(payload) as Uint8Array<ArrayBuffer>,\n to === undefined || to.length === 0\n ? { reliable: true, topic }\n : { reliable: true, topic, destinationIdentities: [...to] },\n );\n },\n async setAudioProcessing(processing) {\n audioCapture = { ...audioCapture, ...processing };\n const target = room;\n if (target === null) return;\n const { Track } = await import(\"livekit-client\");\n const publication = target.localParticipant.getTrackPublication(Track.Source.Microphone);\n const track = publication?.track;\n if (track === undefined || publication?.isMuted === true) return;\n try {\n // The browser's own processing, applied to the LIVE track: a restart\n // with new constraints, the same device, no renegotiation.\n await (track as unknown as { restartTrack(options: object): Promise<void> }).restartTrack({\n ...audioCapture,\n ...(target.getActiveDevice(\"audioinput\") !== undefined\n ? { deviceId: target.getActiveDevice(\"audioinput\") }\n : {}),\n });\n } catch (error) {\n throw normalizeDeviceError(error, \"meet.setAudioProcessing\");\n }\n },\n async setVideoEffect(effect) {\n videoEffect = effect;\n const target = room;\n if (target === null) return;\n await applyVideoEffect(target, effect);\n },\n supportsVideoEffects() {\n return videoEffectsSupported();\n },\n supportsScreenShare() {\n // iOS Safari and most phones have no `getDisplayMedia` at all.\n return (\n typeof navigator !== \"undefined\" &&\n typeof navigator.mediaDevices?.getDisplayMedia === \"function\"\n );\n },\n attach(identity, kind, element) {\n const target = requireRoom(\"attach\");\n const source = sources?.[kind];\n if (source === undefined) return () => undefined;\n // 🚨 THE TRACK MAY NOT EXIST YET, AND MAY BE REPLACED LATER. A tile\n // renders as soon as the roster says the camera is on — which is known\n // from the PUBLICATION, before the track is SUBSCRIBED — and the tile's\n // element stays mounted, so its ref never runs again. Until 0.7.10 an\n // attach that found no track returned a no-op for good: a person\n // admitted from the waiting room was a black tile for everyone already\n // inside (production re-verification, 2026-09-29). So the attach keeps\n // listening for this participant's track and (re)attaches whenever it\n // arrives or is republished; the returned cleanup stops listening too.\n let current: { detach(element: HTMLMediaElement): unknown } | null = null;\n const sync = (): void => {\n const participant =\n target.localParticipant.identity === identity\n ? target.localParticipant\n : target.remoteParticipants.get(identity);\n const track = participant?.getTrackPublication(source as never)?.track;\n if (track === undefined || track === current) return;\n current?.detach(element);\n track.attach(element);\n current = track;\n };\n const onTrack = (...args: unknown[]): void => {\n // TrackSubscribed(track, publication, participant) /\n // LocalTrackPublished(publication, participant) — the participant is\n // always the last argument.\n const who = args[args.length - 1] as { identity?: string } | undefined;\n if (who?.identity === identity) sync();\n };\n sync();\n const events = trackEvents;\n if (events !== null) {\n target.on(events.subscribed as never, onTrack as never);\n target.on(events.localPublished as never, onTrack as never);\n }\n // The DETACH IS THE RETURN VALUE — a React effect's cleanup cannot forget\n // to call something it was handed.\n return () => {\n if (events !== null) {\n target.off(events.subscribed as never, onTrack as never);\n target.off(events.localPublished as never, onTrack as never);\n }\n current?.detach(element);\n current = null;\n };\n },\n activeDevices() {\n const target = room;\n if (target === null) {\n return { audioinput: null, videoinput: null, audiooutput: null };\n }\n return {\n audioinput: target.getActiveDevice(\"audioinput\") ?? null,\n videoinput: target.getActiveDevice(\"videoinput\") ?? null,\n audiooutput: target.getActiveDevice(\"audiooutput\") ?? null,\n };\n },\n async switchDevice(kind, deviceId) {\n try {\n await requireRoom(\"switchDevice\").switchActiveDevice(kind, deviceId);\n } catch (error) {\n throw normalizeDeviceError(error, \"meet.switchDevice\");\n }\n },\n };\n}\n\n/**\n * THE PRE-JOIN CAMERA PREVIEW (Meet wave 2) — the same SDK track and the same\n * background processor the room will use, so what someone sees before joining\n * is exactly what the meeting will see. Lives here because this file is the one\n * place `livekit-client` is touched.\n */\nexport interface CameraPreview {\n attach(element: HTMLVideoElement): void;\n setEffect(effect: VideoEffect): Promise<void>;\n stop(): void;\n}\n\nexport async function createCameraPreview(\n deviceId: string | null,\n effect: VideoEffect,\n): Promise<CameraPreview> {\n let module: typeof import(\"livekit-client\");\n try {\n module = await import(\"livekit-client\");\n } catch (error) {\n throw new MeetError(\n \"misconfigured\",\n \"livekit-client could not be loaded for the camera preview\",\n \"The bundler excluded `livekit-client`; remove it from any `external` list.\",\n error,\n );\n }\n let track: Awaited<ReturnType<typeof module.createLocalVideoTrack>>;\n try {\n track = await module.createLocalVideoTrack(deviceId === null ? {} : { deviceId });\n } catch (error) {\n throw normalizeDeviceError(error, \"meet.cameraPreview\");\n }\n const target = track as unknown as {\n setProcessor(processor: unknown): Promise<void>;\n stopProcessor(): Promise<void>;\n };\n async function apply(next: VideoEffect): Promise<void> {\n if (next.kind === \"none\") {\n await target.stopProcessor();\n return;\n }\n const processors = await import(\"@livekit/track-processors\");\n await target.setProcessor(\n next.kind === \"blur\"\n ? processors.BackgroundBlur(next.radius ?? 12)\n : processors.VirtualBackground(next.imageUrl),\n );\n }\n if (effect.kind !== \"none\" && videoEffectsSupported()) {\n try {\n await apply(effect);\n } catch {\n // The preview still shows the camera; the settings say the effect failed.\n }\n }\n return {\n attach(element) {\n track.attach(element);\n },\n setEffect: apply,\n stop() {\n track.stop();\n track.detach();\n },\n };\n}\n","/**\n * PRESENTATION HELPERS specific to a CALL. Pure, tested, and shared by every\n * surface — so a roster line reads the same in the meeting, in the history\n * list, and in the notification.\n *\n * The GENERIC formatters this file used to define — `formatDuration`,\n * `getInitials`, `avatarPaletteIndex` — moved to `@ai-matrx/kit/format` on\n * 2026-09-07 (duplication census H1). They were byte-for-byte duplicated in\n * `@ai-matrx/messaging`, and because two packages claimed them, ~65 host\n * twins across six repos had no correct owner to point at: no surface should\n * take a dependency on the video-calling package to print a duration. Nothing\n * is re-exported from here — import `@ai-matrx/kit/format` directly.\n */\n\nimport type { CallState, MeetParticipant } from \"./types\";\n\n/** \"Ana, Ben and 3 others\" — the roster line, never a raw array join. */\nexport function participantSummary(participants: readonly MeetParticipant[]): string {\n const names = participants.filter((p) => !p.isLocal).map((p) => p.displayName);\n if (names.length === 0) return \"Just you\";\n if (names.length === 1) return names[0] ?? \"\";\n if (names.length === 2) return `${names[0]} and ${names[1]}`;\n return `${names[0]}, ${names[1]} and ${names.length - 2} ${names.length - 2 === 1 ? \"other\" : \"others\"}`;\n}\n\n/** What a settled call says in history. One sentence per terminal state. */\nexport function callStateLabel(state: CallState, outgoing: boolean): string {\n switch (state) {\n case \"dialing\":\n case \"ringing\":\n return outgoing ? \"Calling…\" : \"Incoming call\";\n case \"connecting\":\n return \"Connecting…\";\n case \"active\":\n return \"In call\";\n case \"declined\":\n return outgoing ? \"Declined\" : \"You declined\";\n case \"missed\":\n return outgoing ? \"No answer\" : \"Missed call\";\n case \"busy\":\n return outgoing ? \"On another call\" : \"You were busy\";\n case \"cancelled\":\n return outgoing ? \"Cancelled\" : \"Cancelled by caller\";\n case \"ended\":\n return \"Call ended\";\n case \"failed\":\n return \"Call failed\";\n default:\n return \"Call\";\n }\n}\n\n/** \"2 waiting\" for the lobby badge — plural handled once, not per surface. */\nexport function countLabel(count: number, singular: string, plural = `${singular}s`): string {\n return `${count} ${count === 1 ? singular : plural}`;\n}\n","/**\n * THE STAGE — who is big, who is small, and who is not rendered at all.\n *\n * This is pure arithmetic on purpose: layout is the part of a meeting UI that\n * everyone re-derives inside a component and gets subtly wrong, and it is\n * trivially testable once it stops living in JSX.\n *\n * R5 SCALE HONESTY lives here. A 200-person meeting does not render 200 video\n * tiles — every browser dies, and LiveKit's adaptive stream cannot save a\n * layout that asked for 200 subscriptions. The grid PAGES, and the ordering is\n * deliberate rather than \"whatever the roster map iterated\":\n *\n * 1. a screen share (or an explicit pin) owns the stage;\n * 2. then the active speakers, most recent first;\n * 3. then anyone with a hand raised — they are asking to be seen;\n * 4. then cameras on before cameras off (an avatar tile is cheap);\n * 5. then stable by join time, so tiles do not shuffle every time someone\n * breathes. Shuffling tiles is the single most disorienting thing a\n * meeting UI can do.\n */\n\nimport { isObserverRole, type MeetParticipant, type ParticipantIdentity, type StageLayout } from \"./types\";\n\nexport interface StageArgs {\n readonly participants: readonly MeetParticipant[];\n readonly layout: StageLayout;\n readonly pinned: ParticipantIdentity | null;\n readonly activeSpeakers: readonly ParticipantIdentity[];\n /**\n * The participant a HOST spotlighted for everyone (Meet wave 2). A local pin\n * outranks it on this screen; a screen share outranks both.\n */\n readonly spotlight?: ParticipantIdentity | null | undefined;\n /** How many tiles this viewport can honestly render. Default 12. */\n readonly pageSize?: number;\n readonly page?: number;\n}\n\nexport interface Stage {\n /** The large tile, when the layout has one. */\n readonly feature: MeetParticipant | null;\n /** What actually renders as a tile, already paged. */\n readonly tiles: readonly MeetParticipant[];\n /** Everyone not on this page. Rendered as \"+N others\", never dropped silently. */\n readonly overflow: number;\n readonly page: number;\n readonly pageCount: number;\n /** True when a screen share, not a person, is the reason for the big tile. */\n readonly presenting: boolean;\n}\n\nexport const DEFAULT_PAGE_SIZE = 12;\n\nexport function orderParticipants(\n participants: readonly MeetParticipant[],\n activeSpeakers: readonly ParticipantIdentity[],\n): readonly MeetParticipant[] {\n const speakerRank = new Map<ParticipantIdentity, number>();\n activeSpeakers.forEach((identity, index) => speakerRank.set(identity, index));\n\n return [...participants].sort((a, b) => {\n const speakerA = speakerRank.get(a.identity) ?? Number.MAX_SAFE_INTEGER;\n const speakerB = speakerRank.get(b.identity) ?? Number.MAX_SAFE_INTEGER;\n if (speakerA !== speakerB) return speakerA - speakerB;\n if (a.handRaised !== b.handRaised) return a.handRaised ? -1 : 1;\n if (a.cameraEnabled !== b.cameraEnabled) return a.cameraEnabled ? -1 : 1;\n // STABLE from here down. Join time, then identity — never Math.random,\n // never map order, both of which reshuffle the grid on every event.\n if (a.joinedAt !== b.joinedAt) return a.joinedAt < b.joinedAt ? -1 : 1;\n return a.identity < b.identity ? -1 : 1;\n });\n}\n\nexport function computeStage(args: StageArgs): Stage {\n const pageSize = Math.max(1, args.pageSize ?? DEFAULT_PAGE_SIZE);\n // A SILENT OBSERVER (MD-16) watches; it is never a tile — not on anyone's stage, its own included.\n const ordered = orderParticipants(\n args.participants.filter((participant) => !isObserverRole(participant.role)),\n args.activeSpeakers,\n );\n\n const sharer = ordered.find((participant) => participant.screenSharing) ?? null;\n const pinnedParticipant =\n args.pinned === null\n ? null\n : ordered.find((participant) => participant.identity === args.pinned) ?? null;\n\n // A SCREEN SHARE OUTRANKS A PIN, deliberately: someone presenting is the\n // reason everyone is looking, and a stale pin hiding a live demo is the\n // complaint this rule exists to prevent.\n const spotlit =\n args.spotlight == null\n ? null\n : ordered.find((participant) => participant.identity === args.spotlight) ?? null;\n\n const feature =\n sharer !== null\n ? sharer\n : pinnedParticipant !== null && args.layout !== \"grid\"\n ? pinnedParticipant\n : spotlit !== null\n ? spotlit\n : args.layout === \"grid\"\n ? null\n : ordered[0] ?? null;\n\n const rest =\n feature === null\n ? ordered\n : ordered.filter((participant) => participant.identity !== feature.identity);\n\n const pageCount = Math.max(1, Math.ceil(rest.length / pageSize));\n const page = Math.min(Math.max(0, args.page ?? 0), pageCount - 1);\n const tiles = rest.slice(page * pageSize, page * pageSize + pageSize);\n\n return {\n feature,\n tiles,\n overflow: rest.length - tiles.length,\n page,\n pageCount,\n presenting: sharer !== null,\n };\n}\n\n/** Column count for a grid of N tiles — the shape that keeps tiles squarest. */\nexport function gridColumns(count: number): number {\n if (count <= 1) return 1;\n if (count <= 4) return 2;\n if (count <= 9) return 3;\n if (count <= 16) return 4;\n return 5;\n}\n","/**\n * THE APP PANEL SLOT, FOR REACT (HR-360 wave 2, Meet register MD-15).\n *\n * The HOST APP registers its panels — `<MeetAppPanels panels={{ \"hr.review_360\": … }}>` around\n * the meeting — and the skin opens the one the meeting carries (`appPanelOf`) beside the call.\n * Nothing app-specific lives in this package: a panel is a key, a title and a component that\n * reads its record through the viewer's own doors.\n *\n * `useSharedFocus` is the panel's one shared \"current section\": the server keeps it per run\n * and tells the room on the existing collab packet, so every seat moves together.\n */\n\nimport { createContext, useContext, useMemo, type ComponentType, type ReactNode } from \"react\";\nimport { appPanelOf, type MeetingAppPanel } from \"../core/app-panel\";\nimport { performCollab } from \"../core/collab-state\";\nimport { isObserverRole, type MeetingRecord } from \"../core/types\";\nimport { useMeetHost, useMeetSnapshot } from \"./provider\";\n\nexport interface MeetAppPanelProps {\n /** The record the meeting carries. */\n readonly recordId: string;\n /** The meeting's organization — the panel's records are read in it. */\n readonly organizationId: string;\n /** The panel's key, e.g. `hr.review_360`. */\n readonly panelKey: string;\n}\n\nexport interface MeetAppPanelEntry {\n /** The panel's header title. */\n readonly title: string;\n readonly Component: ComponentType<MeetAppPanelProps>;\n}\n\nexport type MeetAppPanelRegistry = Readonly<Record<string, MeetAppPanelEntry>>;\n\nconst RegistryContext = createContext<MeetAppPanelRegistry>({});\n\n/** Register the host app's panels for every meeting rendered inside. */\nexport function MeetAppPanels({\n panels,\n children,\n}: {\n panels: MeetAppPanelRegistry;\n children: ReactNode;\n}): ReactNode {\n return <RegistryContext.Provider value={panels}>{children}</RegistryContext.Provider>;\n}\n\nexport function useMeetAppPanelRegistry(): MeetAppPanelRegistry {\n return useContext(RegistryContext);\n}\n\nexport interface ResolvedMeetAppPanel extends MeetAppPanelEntry {\n readonly panel: MeetingAppPanel;\n readonly organizationId: string;\n}\n\n/**\n * The panel this meeting carries AND this app registered, or null. A key the app did not\n * register resolves to null — the slot is absent, never a dead button.\n */\nexport function useMeetAppPanel(meeting?: MeetingRecord | null): ResolvedMeetAppPanel | null {\n const snapshot = useMeetSnapshot();\n const registry = useMeetAppPanelRegistry();\n const record = meeting ?? snapshot?.meeting ?? null;\n return useMemo(() => {\n const panel = appPanelOf(record);\n if (panel === null || record === null) return null;\n const entry = registry[panel.key];\n if (entry === undefined) return null;\n return { ...entry, panel, organizationId: record.organizationId };\n }, [record, registry]);\n}\n\nexport interface SharedFocus {\n /** The section everyone is on, or null before anyone moved it. */\n readonly section: string | null;\n /** Who moved it last. */\n readonly byName: string | null;\n /** False for an observer — they watch, the people in the meeting move the panel. */\n readonly canMove: boolean;\n /** Move everyone to `section`. Resolves with the server's sentence or its refusal. */\n setSection(section: string): Promise<{ ok: boolean; message: string }>;\n}\n\n/** The app panel's one shared section focus (server-kept, on the collab packet). */\nexport function useSharedFocus(panelKey: string): SharedFocus {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const focus = snapshot?.collab.focus ?? null;\n const mine = focus !== null && focus.panel === panelKey ? focus : null;\n const canMove = !isObserverRole(snapshot?.grants.role) && snapshot?.phase === \"in_call\";\n return {\n section: mine?.section || null,\n byName: mine?.byName || null,\n canMove,\n async setSection(section) {\n if (host !== null && canMove) {\n // Move this seat at once; the room packet confirms it for everyone.\n const collab = host.store.snapshot().collab;\n host.store.patch({\n collab: { ...collab, focus: { panel: panelKey, section, byName: host.identity.displayName, at: new Date().toISOString() } },\n });\n }\n return performCollab(host, { action: \"focus_set\", panel: panelKey, section });\n },\n };\n}\n","/**\n * COLLAB AND CAPTION LOGIC, HEADLESS (CORE-DESIGN §5.3, step S1).\n *\n * What used to live inside the React hooks of the in-room activities: which\n * activities a person has, when the Activities door deserves a dot, the act /\n * move-to-breakout calls with their sentences, the caption-translation work\n * queue, and the one-shot panel and language requests on the store. Pure or\n * store-shaped, so every skin reads the same answers; the `/react` hooks are\n * thin selectors over these.\n */\n\nimport type { CollabPanel, CollabSnapshot } from \"./collab\";\nimport type { CollabRequest, CollabResult } from \"./in-room\";\nimport type { MeetSnapshot } from \"./store\";\n\n/** The activities this person has, in the order Meet lists them. */\nexport function availableActivities(collab: CollabSnapshot | null, isHost: boolean): CollabPanel[] {\n const settings = collab?.settings ?? null;\n if (settings === null) return [];\n const out: CollabPanel[] = [];\n if (settings.whiteboardEnabled) out.push(\"whiteboard\");\n // A participant sees Polls once a poll exists for them; a host always (to create one).\n if (settings.pollsEnabled && (isHost || (collab?.polls.length ?? 0) > 0)) out.push(\"polls\");\n if (settings.qaEnabled) out.push(\"qa\");\n // Breakouts: hosts run them; a participant needs the panel only when rooms are open.\n if (settings.breakoutsEnabled && (isHost || collab?.breakout?.state === \"open\")) out.push(\"breakouts\");\n return out;\n}\n\n/** Something worth a dot on the Activities button: a live poll unanswered, a new question for a host. */\nexport function activityAttention(collab: CollabSnapshot | null, isHost: boolean): boolean {\n if (collab === null) return false;\n if (collab.polls.some((poll) => poll.state === \"live\" && poll.myVote.length === 0 && !isHost)) return true;\n if (isHost && collab.questions.some((q) => q.state === \"open\")) return true;\n return false;\n}\n\nexport interface CollabRuntime {\n readonly inRoom: { collab(request: CollabRequest): Promise<CollabResult> };\n readonly engine: { moveToBreakout(roomId: string | null): Promise<unknown> };\n}\n\n/** Perform one act; resolves with the server's sentence, or its refusal (never throws). */\nexport async function performCollab(\n runtime: CollabRuntime | null,\n request: CollabRequest,\n): Promise<{ ok: boolean; message: string; itemId?: string | null }> {\n if (runtime === null) return { ok: false, message: \"This meeting is not connected.\" };\n try {\n const result = await runtime.inRoom.collab(request);\n return { ok: true, message: result.message, itemId: result.itemId };\n } catch (error) {\n return { ok: false, message: (error as { message?: string }).message ?? \"That did not work.\" };\n }\n}\n\n/** Go to a breakout room (`null` = the main room), with the sentence to show. */\nexport async function moveToRoom(\n runtime: CollabRuntime | null,\n roomId: string | null,\n): Promise<{ ok: boolean; message: string }> {\n if (runtime === null) return { ok: false, message: \"This meeting is not connected.\" };\n try {\n await runtime.engine.moveToBreakout(roomId);\n return { ok: true, message: roomId === null ? \"Back in the main room.\" : \"Joined the breakout room.\" };\n } catch (error) {\n return { ok: false, message: (error as { message?: string }).message ?? \"That room could not be joined.\" };\n }\n}\n\nexport interface CollabStoreLike {\n snapshot(): MeetSnapshot;\n patch(next: Partial<MeetSnapshot>): void;\n}\n\n/** Finished caption lines that still need a translation into `language`. */\nexport function untranslatedLines(\n lines: readonly { id: string; text: string; final: boolean }[],\n translations: Readonly<Record<string, string>>,\n): { id: string; text: string }[] {\n return lines\n .filter((line) => line.final && translations[line.id] === undefined)\n .map(({ id, text }) => ({ id, text }));\n}\n\n/** The next batch to send: never a line already asked for in this language, at most 20. */\nexport function nextTranslationBatch(\n pending: readonly { id: string; text: string }[],\n language: string,\n asked: Set<string>,\n): { id: string; text: string }[] {\n const batch = pending.filter((line) => !asked.has(`${language}:${line.id}`)).slice(-20);\n for (const line of batch) asked.add(`${language}:${line.id}`);\n return batch;\n}\n\n/** The viewer chose a caption language (or `null` = as spoken): start over in it. */\nexport function setCaptionLanguage(store: CollabStoreLike, next: string | null): void {\n store.patch({\n collab: {\n ...store.snapshot().collab,\n captionLanguage: next,\n captionTranslations: {},\n captionTranslationError: null,\n },\n });\n}\n\n/** Take (and clear) the one-shot \"open this panel\" request, if any. */\nexport function takePanelRequest(store: CollabStoreLike): CollabPanel | null {\n const request = store.snapshot().collab.panelRequest;\n if (request === null) return null;\n store.patch({ collab: { ...store.snapshot().collab, panelRequest: null } });\n return request.panel;\n}\n\n/** Take (and clear) the one-shot \"move to this breakout room\" request, if any. */\nexport function takeMoveRequest(store: CollabStoreLike): { roomId: string | null } | null {\n const request = store.snapshot().collab.moveRequest;\n if (request === null) return null;\n store.patch({ collab: { ...store.snapshot().collab, moveRequest: null } });\n return { roomId: request.roomId };\n}\n","\"use client\";\n\n/**\n * THE HOOKS. Thin shells over the framework-free core — every one of them\n * exists so a consumer never writes the sequence itself.\n *\n * The rule that shapes this file: a hook returns DATA and ACTIONS, never a\n * \"you must also call this on unmount\" instruction. `useTrack` hands back a ref\n * callback whose cleanup is the detach; `useRoom` disposes with the component.\n * A correct-usage checklist in a README is a bug waiting for a deadline.\n *\n * 🚨 **NO HOOK IN THIS FILE THROWS WHEN THE PROVIDER IS INERT** (0.2.1).\n * `<MeetProvider>` is deliberately inert until a client, an organization and an\n * identity are all real — which is EVERY server render, every signed-out\n * visitor, and every moment before the active organization hydrates. Through\n * 0.2.0 these hooks called `useRequiredMeetHost()`, so the exact three-line\n * integration the README prescribes returned a 500 on every route for every\n * such visitor, and the first real consumer had to wrap our own components in\n * a host-side guard (register MRI-D1). A hook now reports an inert runtime as\n * DATA — empty lists, `canCall: false`, `available: false` — so the components\n * above render nothing or an honest sentence on their own. Only an ACTION\n * invoked with no runtime fails, and it fails loudly with a remedy; it can not\n * be reached from a surface that renders correctly.\n */\n\nimport type { MeetPhase } from \"../core/phase\";\nimport { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport { useRealtimeManager } from \"@ai-matrx/realtime/react\";\nimport { meetingTopic } from \"../core/channels\";\nimport {\n createMeetingRecordCache,\n type MeetingRecordAnswer,\n type MeetingRecordCache,\n} from \"../core/record-cache\";\nimport type { MeetAnswerCoverage } from \"../core/ai\";\nimport { IDLE_MEDIA, listenOnlyNotice, type LocalMediaState } from \"../core/attendance\";\nimport { MeetError } from \"../core/errors\";\nimport {\n noteTakerLabel,\n noteTakerParticipant,\n noteTakerStatus,\n type NoteTakerStatus,\n} from \"../core/note-taker\";\nimport { displayNameFor, type NameBook } from \"../core/names\";\nimport { readableTranscript } from \"../core/transcript\";\n\n/** A stable empty book, so `useMemo` does not re-run on every render. */\nconst EMPTY_NAMES: NameBook = {};\nimport { END_MEETING_NOT_SERVED } from \"../core/meetings\";\nimport { computeStage, DEFAULT_PAGE_SIZE, type Stage } from \"../core/layout\";\nimport { canModerateParticipant } from \"../core/meetings\";\nimport { isRecordingInFlight, recordingLabel } from \"../core/recording\";\nimport type { MeetingSession } from \"../core/meetings\";\nimport type { JoinRequest, RoomEngine } from \"../core/room\";\nimport type { ModerationRequest, ModerationResult } from \"../core/in-room\";\nimport {\n DEFAULT_EFFECTS,\n REACTION_TTL_MS,\n type MediaEffects,\n type MeetWaiting,\n type ModerationNotice,\n} from \"../core/store\";\nimport { isHostRole } from \"../core/types\";\nimport {\n guestRecordAccessSetting,\n activeWrapUpSummary,\n loadGuestMeetingRecord,\n loadMeetingRecordParts,\n setGuestRecordAccess,\n type GuestRecordAccessResolution,\n type GuestRecordAccessSetting,\n type MeetingRecordBundle,\n} from \"../core/record\";\nimport type {\n CallInvite,\n CallMode,\n LobbyEntry,\n MeetParticipant,\n MeetingId,\n MeetingNote,\n MeetingRecord,\n OrganizationId,\n ParticipantIdentity,\n RoomName,\n StageLayout,\n TranscriptSource,\n UserId,\n WrapUpAnnouncement,\n} from \"../core/types\";\nimport {\n useCanRecord,\n useIsHost,\n useRole,\n useMeetHost,\n useMeetSnapshot,\n type MeetHost,\n} from \"./provider\";\n\n/**\n * The ONE sentence every hook says when an action is invoked with no runtime.\n * It names the state and the fix; it never returns a silent no-op, because a\n * button that quietly does nothing is the failure this package exists to avoid.\n */\nconst NO_RUNTIME_REMEDY =\n \"Render this inside a <MeetProvider> that has a Supabase client, an organization id and \" +\n \"an identity (userId or guestName). The provider stays inert until all three are real, \" +\n \"and while it is inert this package's components render nothing rather than a dead control.\";\n\nfunction noRuntime(action: string): MeetError {\n return new MeetError(\n \"misconfigured\",\n `[@ai-matrx/meet] ${action}: there is no meet runtime on this tree.`,\n NO_RUNTIME_REMEDY,\n );\n}\n\nfunction requireHost(host: MeetHost | null, action: string): MeetHost {\n if (host === null) throw noRuntime(action);\n return host;\n}\n\nfunction requireEngine(engine: RoomEngine | null, action: string): RoomEngine {\n if (engine === null) throw noRuntime(action);\n return engine;\n}\n\nfunction requireSession(session: MeetingSession | null, action: string): MeetingSession {\n if (session === null) throw noRuntime(action);\n return session;\n}\n\nexport interface UseRoomResult {\n /** The contract phase, verbatim (CORE-DESIGN §3.1). */\n readonly phase: MeetPhase;\n readonly participants: readonly MeetParticipant[];\n readonly failure: { readonly message: string; readonly remedy: string } | null;\n join: (request: JoinRequest) => Promise<void>;\n leave: () => Promise<void>;\n toggleMicrophone: () => Promise<void>;\n toggleCamera: () => Promise<void>;\n toggleScreenShare: () => Promise<void>;\n /** False where the browser cannot share a screen at all (iOS): the control is absent. */\n readonly canShareScreen: boolean;\n toggleHand: () => Promise<void>;\n react: (emoji: string) => Promise<void>;\n /** To EVERYONE — saved through the server (Meet wave 2). */\n sendChat: (text: string) => Promise<void>;\n /** To ONE person — data plane only, never saved. */\n sendPrivateChat: (to: ParticipantIdentity, text: string) => Promise<void>;\n pin: (identity: ParticipantIdentity | null) => void;\n setLayout: (layout: StageLayout) => void;\n readonly local: MeetParticipant | null;\n}\n\nexport function useRoom(): UseRoomResult {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const engine = host?.engine ?? null;\n\n const local =\n snapshot?.participants.find(\n (participant) => participant.identity === snapshot.localIdentity,\n ) ?? null;\n\n return {\n phase: snapshot?.phase ?? \"resolving\",\n participants: snapshot?.participants ?? [],\n failure: snapshot?.failure ?? null,\n local,\n canShareScreen: engine?.supportsScreenShare() ?? true,\n join: useCallback(\n async (request: JoinRequest) => requireEngine(engine, \"join\").join(request),\n [engine],\n ),\n leave: useCallback(async () => requireEngine(engine, \"leave\").leave(), [engine]),\n toggleMicrophone: useCallback(\n async () =>\n requireEngine(engine, \"toggleMicrophone\").setMicrophoneEnabled(\n !(local?.micEnabled ?? false),\n ),\n [engine, local],\n ),\n toggleCamera: useCallback(\n async () =>\n requireEngine(engine, \"toggleCamera\").setCameraEnabled(!(local?.cameraEnabled ?? false)),\n [engine, local],\n ),\n toggleScreenShare: useCallback(\n async () =>\n requireEngine(engine, \"toggleScreenShare\").setScreenShareEnabled(\n !(local?.screenSharing ?? false),\n ),\n [engine, local],\n ),\n toggleHand: useCallback(\n async () => requireEngine(engine, \"toggleHand\").raiseHand(!(local?.handRaised ?? false)),\n [engine, local],\n ),\n react: useCallback(\n async (emoji: string) => requireEngine(engine, \"react\").react(emoji),\n [engine],\n ),\n sendChat: useCallback(\n async (text: string) => {\n if (host === null) throw noRuntime(\"sendChat\");\n await host.inRoom.sendChat(text);\n },\n [host],\n ),\n sendPrivateChat: useCallback(\n async (to: ParticipantIdentity, text: string) =>\n requireEngine(engine, \"sendPrivateChat\").sendPrivateChat(to, text),\n [engine],\n ),\n pin: useCallback(\n (identity: ParticipantIdentity | null) => requireEngine(engine, \"pin\").pin(identity),\n [engine],\n ),\n setLayout: useCallback(\n (layout: StageLayout) => requireEngine(engine, \"setLayout\").setLayout(layout),\n [engine],\n ),\n };\n}\n\n/** The stage, recomputed only when the roster or the layout actually changes. */\nexport function useStage(pageSize = DEFAULT_PAGE_SIZE): Stage {\n const snapshot = useMeetSnapshot();\n const [page, setPage] = useState(0);\n const stage = useMemo(\n () =>\n computeStage({\n participants: snapshot?.participants ?? [],\n layout: snapshot?.layout ?? \"grid\",\n pinned: snapshot?.pinned ?? null,\n spotlight: snapshot?.spotlight ?? null,\n activeSpeakers: snapshot?.activeSpeakers ?? [],\n pageSize,\n page,\n }),\n [snapshot, pageSize, page],\n );\n // Clamp back into range when people leave — a page that scrolled past the\n // end must not render an empty stage.\n useEffect(() => {\n if (page > stage.pageCount - 1) setPage(Math.max(0, stage.pageCount - 1));\n }, [page, stage.pageCount]);\n return stage;\n}\n\n/**\n * Attach a participant's media to an element. The returned ref callback's\n * CLEANUP is the detach — there is nothing a consumer can forget to call.\n */\nexport function useTrack(\n identity: ParticipantIdentity | null,\n kind: \"camera\" | \"microphone\" | \"screen\",\n): (element: HTMLMediaElement | null) => void {\n const host = useMeetHost();\n const detachRef = useRef<(() => void) | null>(null);\n return useCallback(\n (element: HTMLMediaElement | null) => {\n detachRef.current?.();\n detachRef.current = null;\n if (element === null || identity === null || host === null) return;\n detachRef.current = host.engine.attach(identity, kind, element);\n },\n [host, identity, kind],\n );\n}\n\nexport interface UseCallsResult {\n readonly incoming: readonly CallInvite[];\n readonly outgoing: CallInvite | null;\n /** `null` for a guest identity — the UI renders nothing rather than a dead button. */\n readonly canCall: boolean;\n call: (people: readonly UserId[], mode: CallMode) => Promise<CallInvite>;\n accept: (invite: CallInvite) => Promise<void>;\n decline: (invite: CallInvite, message?: string) => Promise<void>;\n cancel: (invite: CallInvite) => Promise<void>;\n}\n\nexport function useCalls(): UseCallsResult {\n // NOT `useRequiredMeetHost`. `<IncomingCallHost/>` is mounted app-wide by\n // contract, so it renders on every server render and for every signed-out\n // visitor — states in which this provider is correctly inert.\n const host = useMeetHost();\n const center = host?.calls ?? null;\n const [, force] = useState(0);\n\n // The call center is not a store (it is a lifecycle object), so a ring must\n // re-render this hook. A 500ms poll would be a lie about liveness; the\n // provider's onIncoming already fires, so we re-render on the same tick.\n useEffect(() => {\n if (center === null) return undefined;\n const timer = setInterval(() => force((value) => value + 1), 1_000);\n return () => clearInterval(timer);\n }, [center]);\n\n return {\n incoming: center?.incoming() ?? [],\n outgoing: center?.outgoing() ?? null,\n canCall: center !== null,\n call: useCallback(\n async (people: readonly UserId[], mode: CallMode) => {\n if (center === null) {\n throw noRuntime(\n host === null ? \"call\" : \"call (a guest identity has no calling surface, D6)\",\n );\n }\n const invite = await center.call({ people, mode });\n force((value) => value + 1);\n return invite;\n },\n [center, host],\n ),\n accept: useCallback(\n async (invite: CallInvite) => {\n if (center === null || host === null) return;\n await center.accept(invite.id, invite.mode);\n await host.engine.join({ roomName: invite.roomName, mode: invite.mode });\n force((value) => value + 1);\n },\n [center, host],\n ),\n decline: useCallback(\n async (invite: CallInvite, message?: string) => {\n if (center === null) return;\n await center.decline(invite.id, message);\n force((value) => value + 1);\n },\n [center],\n ),\n cancel: useCallback(\n async (invite: CallInvite) => {\n if (center === null) return;\n await center.cancel(invite.id);\n force((value) => value + 1);\n },\n [center],\n ),\n };\n}\n\nexport interface UseLobbyResult {\n readonly waiting: readonly LobbyEntry[];\n readonly canAdmit: boolean;\n admit: (entry: LobbyEntry) => Promise<void>;\n deny: (entry: LobbyEntry) => Promise<void>;\n admitAll: () => Promise<void>;\n /** Lift a denial (`meet_undeny`) so the person may ask again. */\n undeny: (entry: LobbyEntry) => Promise<void>;\n}\n\nexport function useLobby(): UseLobbyResult {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const isHost = useIsHost();\n const session = host?.meetings ?? null;\n return {\n waiting: (snapshot?.lobby ?? []).filter((entry) => entry.state === \"knocking\"),\n // No runtime means no host powers — `<LobbyPanel>` renders nothing rather\n // than an admit button that would throw.\n canAdmit: session !== null && isHost,\n admit: useCallback(\n async (entry: LobbyEntry) => requireSession(session, \"admit\").admit(entry, true),\n [session],\n ),\n deny: useCallback(\n async (entry: LobbyEntry) => requireSession(session, \"deny\").admit(entry, false),\n [session],\n ),\n admitAll: useCallback(\n async () => requireSession(session, \"admitAll\").admitAll(),\n [session],\n ),\n undeny: useCallback(\n async (entry: LobbyEntry) => requireSession(session, \"undeny\").undeny(entry),\n [session],\n ),\n };\n}\n\nexport interface UseWaitingResult {\n /** This person's wait as the server last said it; null outside the waiting room. */\n readonly waiting: MeetWaiting | null;\n /** Stop waiting (the row becomes `withdrawn`). */\n withdraw: () => Promise<void>;\n /** Ask again after a denial or an expired knock — offered only when allowed. */\n askAgain: () => Promise<void>;\n}\n\n/** S4: the waiting screens' one read (CORE-DESIGN §2.6). */\nexport function useWaiting(): UseWaitingResult {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const session = host?.meetings ?? null;\n return {\n waiting: snapshot?.waiting ?? null,\n withdraw: useCallback(async () => requireSession(session, \"withdraw\").withdraw(), [session]),\n askAgain: useCallback(async () => requireSession(session, \"askAgain\").askAgain(), [session]),\n };\n}\n\nexport interface UseRecordingResult {\n readonly state: string;\n readonly label: string | null;\n readonly canStart: boolean;\n readonly canStop: boolean;\n readonly consentNotice: string | null;\n start: () => Promise<void>;\n stop: () => Promise<void>;\n}\n\nexport function useRecording(): UseRecordingResult {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n // 🚨 THE SERVER'S GRANT, not `isHost` (MRI-A11). aidream computes\n // `can_record = is_host AND policy == host-controlled` and puts it on the\n // token and the roster attribute; the policy half is why a host of an\n // `always-on` meeting correctly gets NO control. The policy check below is\n // kept as the package's own agreement with that rule, never as a substitute.\n const canRecord = useCanRecord();\n const meeting = snapshot?.meeting ?? null;\n const state = snapshot?.recording ?? \"idle\";\n // 🚨 NOT `state === \"idle\"` (MRI-A16). Once the durable states actually\n // arrive, a meeting whose recording finished sits on `available`, and asking\n // for `idle` made the Start control vanish for the rest of the session.\n const canStart =\n host !== null && canRecord && host.recording.canStart(meeting, true) && !isRecordingInFlight(state);\n\n return {\n state,\n label: recordingLabel(state),\n canStart,\n // `stopping` is already stopping; the other in-flight states can be.\n canStop: host !== null && canRecord && (state === \"recording\" || state === \"starting\"),\n consentNotice: host?.recording.consentNotice(meeting) ?? null,\n start: useCallback(async () => {\n const live = requireHost(host, \"startRecording\");\n if (meeting === null || live.identity.userId === null) return;\n await live.recording.start({\n meetingId: meeting.id,\n roomName: meeting.roomName,\n by: live.identity.userId,\n });\n }, [host, meeting]),\n stop: useCallback(async () => {\n const live = requireHost(host, \"stopRecording\");\n if (meeting === null || live.identity.userId === null) return;\n await live.recording.stop({ meetingId: meeting.id, by: live.identity.userId });\n }, [host, meeting]),\n };\n}\n\n/** One line of the transcript, as a panel renders it. */\nexport interface TranscriptLine {\n readonly id: string;\n /** Who said it, as the room knows them. Surfaces group by THIS, not by name. */\n readonly identity: ParticipantIdentity;\n readonly speaker: string;\n readonly text: string;\n readonly final: boolean;\n /** `caption` is a live hint; `record` is the durable transcript (D16). */\n readonly source: TranscriptSource;\n}\n\nexport interface UseMeetAiResult {\n /** The note-taker's honest state, and the server's refusal if there was one. */\n readonly noteTaker: NoteTakerStatus;\n /** The sentence for that state. Never a bare enum, never a spinner. */\n readonly noteTakerLabel: string;\n /** True only for a host of a meeting whose AI is on. Guests get no control. */\n readonly canControlNoteTaker: boolean;\n /** Live-window notes, oldest first. */\n readonly notes: readonly MeetingNote[];\n readonly decisions: readonly MeetingNote[];\n readonly actionItems: readonly MeetingNote[];\n /** The wrap-up summary, once the meeting has produced one. */\n readonly summary: MeetingNote | null;\n /**\n * The wrap-up pass ANNOUNCED itself into this room (MRI-A12). `null` means it\n * has not been announced HERE — never that no summary exists. The ordinary\n * ending path deletes the LiveKit room before the pass runs, so this is\n * normally null and the summary is read on the post-meeting record.\n */\n readonly wrapUp: WrapUpAnnouncement | null;\n readonly transcript: readonly TranscriptLine[];\n readonly asking: boolean;\n /** The answer SO FAR. It grows while the answer streams. */\n readonly answer: string | null;\n readonly coverage: MeetAnswerCoverage | null;\n /** The server's phase while an answer is being built. */\n readonly phase: string | null;\n readonly error: { readonly message: string; readonly remedy: string } | null;\n ask: (question: string) => Promise<void>;\n startNoteTaker: () => Promise<void>;\n stopNoteTaker: () => Promise<void>;\n}\n\n/**\n * THE MEETING ASSISTANT, as a surface needs it.\n *\n * Everything below is DATA, including every failure. There is no state in which\n * this hook returns a spinner with nothing behind it: the note-taker is in one\n * of five named states or the server refused with a sentence, and an answer is\n * either arriving (visibly, token by token) or has failed with a remedy.\n */\nexport function useMeetAi(): UseMeetAiResult {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const isHost = useIsHost();\n const [asking, setAsking] = useState(false);\n const [answer, setAnswer] = useState<string | null>(null);\n const [coverage, setCoverage] = useState<MeetAnswerCoverage | null>(null);\n const [phase, setPhase] = useState<string | null>(null);\n const [error, setError] = useState<{ message: string; remedy: string } | null>(null);\n const ai = host?.ai ?? null;\n const meeting = snapshot?.meeting ?? null;\n const participants = snapshot?.participants ?? [];\n const notes = snapshot?.notes ?? [];\n const wrapUp = snapshot?.wrapUp ?? null;\n\n const status = useMemo(\n () =>\n noteTakerStatus({\n meeting,\n participants,\n refusal: snapshot?.noteTakerRefusal ?? null,\n }),\n [meeting, participants, snapshot?.noteTakerRefusal],\n );\n\n const names = snapshot?.names ?? EMPTY_NAMES;\n const transcript = useMemo(\n () =>\n readableTranscript(snapshot?.transcript ?? []).map((line) => ({\n id: line.id,\n identity: line.identity,\n // 🚨 RESOLVED, not rendered raw (MRI-A16). A line stamped before its\n // speaker's name was known — a durable row that beat the roster, a\n // caption from someone who has since left — is re-attributed the moment\n // anything learns the name, in BOTH lanes, so the host and the guest\n // never read two different names for one speaker again (MRI-C8).\n speaker: displayNameFor(names, line.identity, line.speaker),\n text: line.text,\n final: line.final,\n source: line.source,\n })),\n [snapshot?.transcript, names],\n );\n\n const dispatchNoteTaker = useCallback(\n async (run: () => Promise<unknown>) => {\n setError(null);\n try {\n await run();\n } catch (thrown) {\n // THE SERVER'S SENTENCE REACHES THE SCREEN. A dispatch that failed and\n // said nothing is a meeting everyone believes is being minuted.\n setError({\n message: (thrown as Error).message,\n remedy:\n (thrown as { remedy?: string }).remedy ??\n \"The note-taker action could not be confirmed. Check its current state and try again.\",\n });\n }\n },\n [],\n );\n\n return {\n noteTaker: status,\n noteTakerLabel: noteTakerLabel(status),\n // A guest cannot dispatch an agent, and neither can anyone in a meeting\n // whose AI is off. The control is ABSENT for them, never disabled-looking.\n canControlNoteTaker:\n ai !== null && meeting !== null && meeting.aiEnabled && isHost && host?.identity.userId !== null,\n notes: notes.filter((note) => note.kind === \"note\" && note.role === null),\n // D20: the wrap-up is the authoritative post-meeting set. A window-written\n // decision the wrap-up superseded stays in `notes` — the timeline — and is\n // not listed a second time as a decision that was made.\n decisions: notes.filter((note) => note.kind === \"decision\" && note.supersededBy === null),\n actionItems: notes.filter(\n (note) => note.kind === \"action-item\" && note.supersededBy === null,\n ),\n summary: activeWrapUpSummary(notes),\n wrapUp,\n transcript,\n asking,\n answer,\n coverage,\n phase,\n error,\n ask: useCallback(\n async (question: string) => {\n if (ai === null || meeting === null) return;\n setAsking(true);\n setError(null);\n setAnswer(null);\n setCoverage(null);\n setPhase(null);\n try {\n const result = await ai.ask({\n meetingId: meeting.id,\n question,\n // THE ANSWER RENDERS AS IT ARRIVES. Waiting for the last token to\n // show the first is a spinner wearing an answer's clothes.\n onText: (text) => setAnswer(text),\n onPhase: (value) => setPhase(value),\n });\n setAnswer(result.text);\n setCoverage(result.coverage);\n } catch (thrown) {\n setError({\n message: (thrown as Error).message,\n remedy:\n (thrown as { remedy?: string }).remedy ??\n \"The assistant could not answer. Nothing was changed by the attempt.\",\n });\n } finally {\n setAsking(false);\n setPhase(null);\n }\n },\n [ai, meeting],\n ),\n startNoteTaker: useCallback(async () => {\n if (ai === null || meeting === null) return;\n await dispatchNoteTaker(() =>\n ai.startNoteTaker({ meetingId: meeting.id, roomName: meeting.roomName }),\n );\n }, [ai, meeting, dispatchNoteTaker]),\n stopNoteTaker: useCallback(async () => {\n if (ai === null || meeting === null) return;\n await dispatchNoteTaker(() => ai.stopNoteTaker({ meetingId: meeting.id }));\n }, [ai, meeting, dispatchNoteTaker]),\n };\n}\n\n/**\n * ENDING THE MEETING FOR EVERYONE — a host control with an honest failure.\n *\n * `POST /v1/meet/end` is served by MRI-A6. A server that has not landed it\n * answers 404, and this hook reports that as the sentence\n * `END_MEETING_NOT_SERVED` rather than pretending the meeting ended or leaving\n * a button that appears to do nothing.\n */\nexport interface UseEndMeetingResult {\n readonly canEnd: boolean;\n readonly ending: boolean;\n readonly ended: boolean;\n /** The honest sentence when the attempt did not end the meeting. */\n readonly notice: string | null;\n end: () => Promise<void>;\n}\n\nexport function useEndMeeting(): UseEndMeetingResult {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const isHost = useIsHost();\n const [ending, setEnding] = useState(false);\n const [notice, setNotice] = useState<string | null>(null);\n const meeting = snapshot?.meeting ?? null;\n const session = host?.meetings ?? null;\n\n return {\n canEnd: session !== null && isHost && meeting !== null && meeting.endedAt === null,\n ending,\n ended: meeting?.endedAt !== null && meeting?.endedAt !== undefined,\n notice,\n end: useCallback(async () => {\n const live = requireSession(session, \"endForEveryone\");\n setEnding(true);\n setNotice(null);\n try {\n const result = await live.endForEveryone();\n if (!result.served) {\n // NOT A DEAD BUTTON AND NOT A LIE: the route is pinned in the\n // package's contract and this server has not landed it yet (MRI-A6).\n setNotice(END_MEETING_NOT_SERVED);\n }\n } catch (thrown) {\n const remedy = (thrown as { remedy?: string }).remedy;\n setNotice(\n remedy !== undefined\n ? `${(thrown as Error).message} ${remedy}`\n : (thrown as Error).message,\n );\n } finally {\n setEnding(false);\n }\n }, [session]),\n };\n}\n\n/**\n * THE CONSENT NOTICE (D10) — a property of this package, never a prop.\n *\n * Returns the sentence every participant must see while an agent is in the\n * room, or `null` when none is. There is deliberately no way to turn it off:\n * the org's policy decides whether a note-taker may run, and it never decides\n * whether the people in the room are told about it.\n */\nexport function useConsentNotice(): string | null {\n const snapshot = useMeetSnapshot();\n const agent = noteTakerParticipant(snapshot?.participants ?? []);\n if (agent === null) return null;\n return (\n `${agent.displayName} is in this meeting: it is transcribing what is said and ` +\n \"writing notes everyone here can read.\"\n );\n}\n\n/**\n * What the LOCAL participant is actually publishing, and why not when it is not.\n * Read this — never the roster's `micEnabled` — to decide whether to tell\n * someone they cannot be heard: the roster reflects the media plane, this\n * reflects the ATTEMPT, including one the browser refused outright (there is no\n * track to be un-muted in that case, so the roster would simply say `false`\n * with no reason attached).\n */\nexport function useLocalMedia(): LocalMediaState {\n const snapshot = useMeetSnapshot();\n return snapshot?.media ?? IDLE_MEDIA;\n}\n\n/**\n * The listen-only sentence, or `null` while the microphone is live. The remedy,\n * when the device was refused rather than declined, is on\n * `useLocalMedia().microphone.unavailable`.\n */\nexport function useListenOnlyNotice(): string | null {\n return listenOnlyNotice(useLocalMedia());\n}\n\n/** Live reactions, already expired. Rendering stale confetti is the alternative. */\nexport function useReactions(): readonly { id: string; emoji: string }[] {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const store = host?.store ?? null;\n useEffect(() => {\n if (store === null) return undefined;\n const timer = setInterval(() => store.expireReactions(Date.now()), REACTION_TTL_MS / 2);\n return () => clearInterval(timer);\n }, [store]);\n return snapshot?.reactions ?? [];\n}\n\n/** A ticking duration for the call header. One timer, not one per component. */\nexport function useElapsed(startedAt: string | null): number {\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n if (startedAt === null) return undefined;\n const timer = setInterval(() => setNow(Date.now()), 1_000);\n return () => clearInterval(timer);\n }, [startedAt]);\n if (startedAt === null) return 0;\n return Math.max(0, now - Date.parse(startedAt));\n}\n\n/** Whether the local actor may moderate a given participant. One rank check. */\nexport function useCanModerate(target: MeetParticipant | undefined): boolean {\n const snapshot = useMeetSnapshot();\n const role = useRole();\n const me = snapshot?.participants.find(\n (participant) => participant.identity === snapshot.localIdentity,\n );\n // The EFFECTIVE role (token ∪ roster attribute — MRI-A11), not whichever of\n // the two the roster happened to carry this tick.\n return canModerateParticipant(me === undefined ? undefined : { ...me, role }, target);\n}\n\n/**\n * 🚨 EVERY HOST POWER, THROUGH THE SERVER (MRI-A11 → Meet wave 2).\n *\n * `canModerate` is the rank check — a host outranks a co-host outranks\n * everyone, and NOBODY may moderate themselves — so a surface renders a control\n * only where it would actually be honoured. The act itself is a server call\n * (`core/in-room.ts`): through 0.7.x mute and remove were data packets nothing\n * listened to, which made them buttons that did nothing.\n *\n * `notice` is the server's last sentence (or refusal) for the actor — a control\n * that acted and said nothing is indistinguishable from one that did nothing.\n */\nexport interface UseHostControlsResult {\n readonly isHost: boolean;\n /** The host itself (not a co-host) — only the host hands the meeting over. */\n readonly isOwner: boolean;\n readonly notice: { readonly text: string; readonly failed: boolean } | null;\n readonly busy: boolean;\n clearNotice: () => void;\n canModerate: (target: MeetParticipant) => boolean;\n run: (request: ModerationRequest) => Promise<ModerationResult | null>;\n mute: (target: MeetParticipant) => Promise<void>;\n remove: (target: MeetParticipant) => Promise<void>;\n}\n\nexport function useHostControls(): UseHostControlsResult {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const role = useRole();\n const me = snapshot?.participants.find(\n (participant) => participant.identity === snapshot.localIdentity,\n );\n const actor = me === undefined ? undefined : { ...me, role };\n const [busy, setBusy] = useState(false);\n const inRoom = host?.inRoom ?? null;\n const store = host?.store ?? null;\n const notice = snapshot?.actionNotice ?? null;\n const setNotice = useCallback(\n (next: { text: string; failed: boolean } | null) =>\n store?.patch({ actionNotice: next === null ? null : { ...next, at: Date.now() } }),\n [store],\n );\n\n const run = useCallback(\n async (request: ModerationRequest): Promise<ModerationResult | null> => {\n if (inRoom === null) throw noRuntime(`moderate(${request.action})`);\n setBusy(true);\n try {\n const result = await inRoom.moderate(request);\n setNotice({ text: result.message, failed: false });\n return result;\n } catch (thrown) {\n const error = thrown as { message?: string; remedy?: string };\n setNotice({\n text: [error.message, error.remedy].filter(Boolean).join(\" \"),\n failed: true,\n });\n return null;\n } finally {\n setBusy(false);\n }\n },\n [inRoom, setNotice],\n );\n\n return {\n isHost: isHostRole(role),\n isOwner: role === \"host\",\n notice,\n busy,\n clearNotice: useCallback(() => setNotice(null), [setNotice]),\n canModerate: useCallback(\n (target: MeetParticipant) => canModerateParticipant(actor, target),\n [actor],\n ),\n run,\n mute: useCallback(\n async (target: MeetParticipant) => {\n await run({ action: \"mute\", identity: target.identity });\n },\n [run],\n ),\n remove: useCallback(\n async (target: MeetParticipant) => {\n await run({ action: \"remove\", identity: target.identity });\n },\n [run],\n ),\n };\n}\n\n/**\n * THE HAND QUEUE — raised hands in the order they went up (Meet wave 2).\n * First raised, first called on; a late joiner learns the order from each\n * client's re-announcement (`room.ts`).\n */\nexport function useHandQueue(): readonly MeetParticipant[] {\n const snapshot = useMeetSnapshot();\n return useMemo(\n () =>\n (snapshot?.participants ?? [])\n .filter((participant) => participant.handRaised)\n .sort((a, b) => (a.handRaisedAt ?? 0) - (b.handRaisedAt ?? 0)),\n [snapshot?.participants],\n );\n}\n\n/** This participant's noise suppression and background, and the setter. */\nexport interface UseMediaEffectsResult {\n readonly effects: MediaEffects;\n /** False = this browser cannot run a background effect; the control is absent. */\n readonly backgroundsSupported: boolean;\n set: (next: Partial<MediaEffects>) => Promise<void>;\n}\n\nexport function useMediaEffects(): UseMediaEffectsResult {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const engine = host?.engine ?? null;\n return {\n effects: snapshot?.effects ?? DEFAULT_EFFECTS,\n backgroundsSupported: engine?.supportsVideoEffects() ?? false,\n set: useCallback(\n async (next: Partial<MediaEffects>) => requireEngine(engine, \"setEffects\").setEffects(next),\n [engine],\n ),\n };\n}\n\n/** What a host last did to THIS participant, and a way to dismiss it. */\nexport function useModerationNotice(): { notice: ModerationNotice | null; dismiss: () => void } {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n return {\n notice: snapshot?.moderationNotice ?? null,\n dismiss: useCallback(() => host?.store.patch({ moderationNotice: null }), [host]),\n };\n}\n\n/**\n * A live microphone level, 0..1, for a meter. Reads the given stream through an\n * `AnalyserNode`; returns 0 while there is none. One animation frame loop,\n * closed with the stream.\n */\nexport function useAudioLevel(stream: MediaStream | null): number {\n const [level, setLevel] = useState(0);\n useEffect(() => {\n if (stream === null || stream.getAudioTracks().length === 0) {\n setLevel(0);\n return undefined;\n }\n const Context =\n (globalThis as { AudioContext?: typeof AudioContext }).AudioContext ??\n (globalThis as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (Context === undefined) return undefined;\n const context = new Context();\n const source = context.createMediaStreamSource(stream);\n const analyser = context.createAnalyser();\n analyser.fftSize = 512;\n source.connect(analyser);\n const samples = new Uint8Array(analyser.fftSize);\n let frame = 0;\n const tick = () => {\n analyser.getByteTimeDomainData(samples);\n let peak = 0;\n for (const sample of samples) peak = Math.max(peak, Math.abs(sample - 128) / 128);\n setLevel((previous) => (Math.abs(previous - peak) > 0.02 ? peak : previous));\n frame = requestAnimationFrame(tick);\n };\n frame = requestAnimationFrame(tick);\n return () => {\n cancelAnimationFrame(frame);\n source.disconnect();\n void context.close();\n };\n }, [stream]);\n return level;\n}\n\n/**\n * THE KEYBOARD SHORTCUTS (Meet wave 2) — Google Meet's own chords, so nobody\n * relearns anything: ⌘/Ctrl+D mute, ⌘/Ctrl+E camera, ⌘/Ctrl+Alt+H hand,\n * ⌘/Ctrl+Alt+C chat, ⌘/Ctrl+Alt+P people, Shift+? the shortcuts sheet. Never\n * fires while the person is typing in a field.\n */\nexport const MEET_SHORTCUTS: readonly {\n readonly id: \"mic\" | \"camera\" | \"hand\" | \"chat\" | \"people\" | \"help\";\n readonly keys: string;\n readonly label: string;\n}[] = [\n { id: \"mic\", keys: \"⌘/Ctrl + D\", label: \"Mute or unmute your microphone\" },\n { id: \"camera\", keys: \"⌘/Ctrl + E\", label: \"Turn your camera on or off\" },\n { id: \"hand\", keys: \"⌘/Ctrl + Alt + H\", label: \"Raise or lower your hand\" },\n { id: \"chat\", keys: \"⌘/Ctrl + Alt + C\", label: \"Open or close chat\" },\n { id: \"people\", keys: \"⌘/Ctrl + Alt + P\", label: \"Open or close people\" },\n { id: \"help\", keys: \"Shift + ?\", label: \"Show keyboard shortcuts\" },\n];\n\nexport function useMeetShortcuts(\n handlers: Partial<Record<(typeof MEET_SHORTCUTS)[number][\"id\"], () => void>>,\n): void {\n const ref = useRef(handlers);\n ref.current = handlers;\n useEffect(() => {\n const target = (globalThis as { window?: Window }).window;\n if (target === undefined) return undefined;\n const onKey = (event: KeyboardEvent) => {\n const element = event.target as HTMLElement | null;\n const typing =\n element !== null &&\n (element.isContentEditable ||\n element.tagName === \"INPUT\" ||\n element.tagName === \"TEXTAREA\" ||\n element.tagName === \"SELECT\");\n const mod = event.metaKey || event.ctrlKey;\n const key = event.key.toLowerCase();\n let id: (typeof MEET_SHORTCUTS)[number][\"id\"] | null = null;\n if (mod && !event.altKey && !event.shiftKey && key === \"d\") id = \"mic\";\n else if (mod && !event.altKey && !event.shiftKey && key === \"e\") id = \"camera\";\n else if (mod && event.altKey && (event.code === \"KeyH\" || key === \"h\")) id = \"hand\";\n else if (mod && event.altKey && (event.code === \"KeyC\" || key === \"c\")) id = \"chat\";\n else if (mod && event.altKey && (event.code === \"KeyP\" || key === \"p\")) id = \"people\";\n else if (!mod && !typing && event.key === \"?\") id = \"help\";\n if (id === null) return;\n const handler = ref.current[id];\n if (handler === undefined) return;\n // ⌘D is \"bookmark\" and ⌘E is \"search\" in browsers: the meeting wins while\n // it is on screen, exactly as it does in Meet.\n event.preventDefault();\n handler();\n };\n target.addEventListener(\"keydown\", onKey);\n return () => target.removeEventListener(\"keydown\", onKey);\n }, []);\n}\n\nexport function useMeeting(): MeetingRecord | null {\n return useMeetSnapshot()?.meeting ?? null;\n}\n\nexport function useMeetingId(): MeetingId | null {\n return useMeetSnapshot()?.meeting?.id ?? null;\n}\n\n/* ------------------------------------------------------ the meeting record */\n\n/**\n * THE MEETING AFTER THE MEETING (MRI-D2).\n *\n * A durable link outlives the room (R3), and until this hook existed it\n * resolved to a pre-join screen for a room nobody could enter — the summary,\n * decisions, action items, live notes, transcript and recording were reachable\n * only from SQL. This loads all of it from durable rows, states what the reader\n * is not allowed to see instead of rendering emptiness, and keeps the Q&A box\n * working against the record: `/intelligence/ask` reads the same durable rows\n * and answers a signed-in member long after the room closed.\n *\n * It is deliberately independent of the room store: nothing here needs a\n * LiveKit connection, a token, or a joined participant.\n */\nexport interface UseMeetingRecordResult {\n readonly loading: boolean;\n readonly record: MeetingRecordBundle | null;\n /** A failure that stopped the WHOLE record loading. Partial refusals are in\n * `record.restrictions` — this is null whenever a record exists. */\n readonly failure: { readonly message: string; readonly remedy: string } | null;\n /** Q&A against the durable record. Streams, exactly as it does in-room. */\n readonly asking: boolean;\n readonly answer: string | null;\n readonly coverage: MeetAnswerCoverage | null;\n readonly askError: { readonly message: string; readonly remedy: string } | null;\n ask: (question: string) => Promise<void>;\n reload: () => void;\n}\n\n/**\n * 🚨 ONE RECORD PER READER PER TAB (0.7.79) — the bundle lives in a shared\n * cache (`core/record-cache.ts`), not in this hook. The first view of a\n * meeting reads; a wake from sleep, a remount, an Undo and every second view\n * render the kept record and read NOTHING. A member's record is kept current\n * by ONE change-feed channel per meeting (notes, transcript, attendance and\n * recordings, applied row by row), held while any view is mounted and a grace\n * after. `reload` and a reconnect read again; an older answer never paints over\n * a newer one. One cache per tab, on `globalThis`, so a dual-loaded graph\n * still shares it.\n */\nconst RECORD_CACHE_KEY = Symbol.for(\"@ai-matrx/meet/meeting-record-cache\");\n\nexport function meetingRecordCache(): MeetingRecordCache {\n const holder = globalThis as unknown as Record<symbol, MeetingRecordCache | undefined>;\n let cache = holder[RECORD_CACHE_KEY];\n if (cache === undefined) {\n cache = createMeetingRecordCache();\n holder[RECORD_CACHE_KEY] = cache;\n }\n return cache;\n}\n\nconst rowIdOf = (row: Record<string, unknown>) => (typeof row[\"id\"] === \"string\" ? row[\"id\"] : undefined);\n\nexport function useMeetingRecord(meeting: MeetingRecord | null): UseMeetingRecordResult {\n const host = useMeetHost();\n const repository = host?.repository ?? null;\n const ai = host?.ai ?? null;\n const readerId = host?.identity.userId ?? null;\n const [asking, setAsking] = useState(false);\n const [answer, setAnswer] = useState<string | null>(null);\n const [coverage, setCoverage] = useState<MeetAnswerCoverage | null>(null);\n const [askError, setAskError] = useState<{ message: string; remedy: string } | null>(null);\n\n const meetingId = meeting?.id ?? null;\n\n const api = host?.api ?? null;\n const tokens = host?.tokens ?? null;\n const roomName = meeting?.roomName ?? null;\n const manager = useRealtimeManager();\n const cache = meetingRecordCache();\n // Who is reading is part of the question: a member's record and a guest's\n // share of it are two different answers.\n const key =\n repository !== null && meetingId !== null\n ? `${readerId ?? `guest:${roomName ?? \"-\"}`}|${meetingId}`\n : null;\n const state = useSyncExternalStore(\n cache.subscribe,\n () => cache.state(key),\n () => cache.state(null),\n );\n\n // The read itself, built from this render's host and meeting. Held in a ref\n // so the cache can call the latest one (Retry, a reconnect) without the view\n // re-running its effects.\n const readRef = useRef<() => Promise<MeetingRecordAnswer>>(() => Promise.reject(new Error(\"not ready\")));\n readRef.current = async (): Promise<MeetingRecordAnswer> => {\n if (repository === null || meeting === null || meetingId === null) throw new Error(\"No meeting to read.\");\n // 🚨 TWO READERS, TWO DOORS, ONE BUNDLE (MRF-1).\n //\n // A signed-in member reads the record DIRECT from Supabase under RLS, as\n // they always have. A guest has no database at all — `anon` holds no grant\n // on the four org-scoped tables and widening it is forbidden (MRI-A10) — so\n // they read what the organization shares through `GET /v1/meet/record`,\n // authorized by the pass this device kept from the meeting. Both produce\n // the same `MeetingRecordBundle`, which is what keeps the two readers\n // looking at one product instead of two.\n if (readerId === null && api !== null && tokens !== null && roomName !== null) {\n return {\n kind: \"bundle\",\n bundle: await loadGuestMeetingRecord({\n api,\n repository,\n meetingId,\n meeting,\n roomToken: tokens.remembered(roomName),\n }),\n };\n }\n const parts = await loadMeetingRecordParts(repository, meetingId, { userId: readerId });\n // A SIGNED-IN OUTSIDER IS NOT A STRANGER (2026-10-01). The database\n // answers members and invitees; a person from another organization\n // the host admitted — or a guest who later made an account — reads\n // the guest share through the server, with their session as the pass.\n if (api !== null && parts.restrictions.some((part) => part.part === \"notes\")) {\n const viaServer = await loadGuestMeetingRecord({\n api,\n repository,\n meetingId,\n meeting,\n roomToken: null,\n session: true,\n });\n if (viaServer.restrictions.length < parts.restrictions.length) return { kind: \"bundle\", bundle: viaServer };\n }\n return { kind: \"parts\", meeting, parts };\n };\n\n // Mount and wake: read unless this reader's record of this meeting is kept.\n useEffect(() => {\n if (key === null) return;\n void cache.ensure(key, () => readRef.current());\n }, [cache, key]);\n\n // The meeting row moved (a rename, it ended): the kept record is assembled\n // again around it — nothing is read.\n useEffect(() => {\n if (key !== null && meeting !== null) cache.setMeeting(key, meeting);\n }, [cache, key, meeting]);\n\n // Live: ONE channel per meeting per reader, however many views hold it. A\n // guest has no database and no change feed; their record is what the\n // server shared.\n useEffect(() => {\n if (key === null || manager === null || repository === null || meetingId === null || readerId === null) {\n return undefined;\n }\n const repo = repository;\n const binding = (\n table: string,\n apply: (row: Record<string, unknown> | null, removedId: string | undefined) => void,\n ) => ({\n event: \"*\" as const,\n schema: repo.schema,\n table,\n filter: `meeting_id=eq.${meetingId}`,\n rowId: rowIdOf,\n onChange: ({ row, payload }: { row: Record<string, unknown> | null; payload: { eventType?: string } }) => {\n if (row === null) return;\n const gone = payload.eventType === \"DELETE\";\n apply(gone ? null : row, gone ? rowIdOf(row) : undefined);\n },\n });\n return cache.hold(\n key,\n () => {\n const handle = manager.open({\n topic: meetingTopic(meetingId),\n postgresChanges: [\n binding(repo.tables.notes, (row, removedId) =>\n cache.apply(key, { part: \"notes\", row: row && repo.projectNote(row), removedId }),\n ),\n binding(repo.tables.transcripts, (row, removedId) =>\n cache.apply(key, { part: \"transcript\", row: row && repo.projectSegment(row), removedId }),\n ),\n binding(repo.tables.participants, (row, removedId) =>\n cache.apply(key, { part: \"attendance\", row: row && repo.projectAttendee(row), removedId }),\n ),\n binding(repo.tables.recordings, (row, removedId) =>\n // An archived recording is off the record (the read filters it the same way).\n row !== null && row[\"deleted_at\"] != null\n ? cache.apply(key, { part: \"recording\", row: null, removedId: rowIdOf(row) })\n : cache.apply(key, { part: \"recording\", row: row && repo.projectRecording(row), removedId }),\n ),\n ],\n // Realtime has no replay: a socket that blipped reads the record again.\n onBackfill: async () => {\n await cache.ensure(key, () => readRef.current(), { force: true });\n },\n });\n return () => handle.close();\n },\n manager,\n );\n }, [cache, key, manager, repository, meetingId, readerId]);\n\n return {\n loading: state.loading && state.failure === null,\n record: state.record,\n failure: state.failure,\n asking,\n answer,\n coverage,\n askError,\n ask: useCallback(\n async (question: string) => {\n if (ai === null || meetingId === null) return;\n setAsking(true);\n setAskError(null);\n setAnswer(null);\n setCoverage(null);\n try {\n const result = await ai.ask({\n meetingId,\n question,\n onText: (text) => setAnswer(text),\n });\n setAnswer(result.text);\n setCoverage(result.coverage);\n } catch (thrown) {\n // A guest gets the server's own 403 here (\"a guest can read the notes\n // behind the link, but cannot ask the live assistant about a finished\n // meeting. Sign in to do that.\"). It is rendered verbatim.\n setAskError({\n message: (thrown as Error).message,\n remedy:\n (thrown as { remedy?: string }).remedy ??\n \"The assistant could not answer. Nothing was changed by the attempt.\",\n });\n } finally {\n setAsking(false);\n }\n },\n [ai, meetingId],\n ),\n reload: useCallback(() => {\n if (key !== null) void cache.ensure(key, () => readRef.current(), { force: true });\n }, [cache, key]),\n };\n}\n\n/**\n * EVERY MEETING THIS ORGANIZATION HAS, newest first — including the ended ones\n * (MRI-D2). A list that shows only live meetings is a list that empties itself\n * the moment the work is done, and it is what left every wrap-up unreachable.\n */\nexport interface UseMeetingsResult {\n readonly loading: boolean;\n readonly meetings: readonly MeetingRecord[];\n readonly hasMore: boolean;\n readonly failure: { readonly message: string; readonly remedy: string } | null;\n reload: () => void;\n}\n\nexport function useMeetings(\n limit = 25,\n /**\n * The page's own organization filter. Absent/null = ALL the person's\n * organizations, which is the default on every visit. It is never the host's\n * active organization (active-org-is-never-a-list-filter.md).\n */\n organizationFilter: OrganizationId | null = null,\n): UseMeetingsResult {\n const host = useMeetHost();\n const repository = host?.repository ?? null;\n const organizationId = organizationFilter;\n const [meetings, setMeetings] = useState<readonly MeetingRecord[]>([]);\n const [hasMore, setHasMore] = useState(false);\n const [loading, setLoading] = useState(true);\n const [failure, setFailure] = useState<{ message: string; remedy: string } | null>(null);\n const [nonce, setNonce] = useState(0);\n\n useEffect(() => {\n if (repository === null) return undefined;\n let live = true;\n setLoading(true);\n setFailure(null);\n void repository\n .listMeetings({ organizationId, limit })\n .then((page) => {\n if (!live) return;\n setMeetings(page.items);\n setHasMore(page.hasMore);\n setLoading(false);\n })\n .catch((thrown: unknown) => {\n if (!live) return;\n setFailure({\n message: (thrown as Error).message,\n remedy:\n (thrown as { remedy?: string }).remedy ??\n \"The meetings could not be listed. Reload the page; nothing was changed.\",\n });\n setLoading(false);\n });\n return () => {\n live = false;\n };\n }, [repository, organizationId, limit, nonce]);\n\n return {\n loading: loading && failure === null,\n meetings,\n hasMore,\n failure,\n reload: useCallback(() => setNonce((value) => value + 1), []),\n };\n}\n\n\n/* ------------------------------------- what guests may read of this meeting */\n\n/**\n * THE HOST'S CHOICE FOR ONE MEETING (MRF-1).\n *\n * Arman ruled on 2026-09-12 that guest access to the post-meeting record is a\n * configuration and never a question: the system's default, the organization's\n * override, and this — a host deciding for the meeting in front of them,\n * because they are the only person who knows who was in the room and what was\n * said in it.\n *\n * `setting` is what the host chose (`default` means \"use my organization's\");\n * `effective` is what that resolves to RIGHT NOW, which is the only honest way\n * to render \"use my organization's setting\" — a label that does not say what\n * the organization's setting IS makes the host guess.\n *\n * The write is the SERVER's decision and it re-checks host power. This hook\n * never renders a control for a non-host; the component does that, and the\n * server refuses one anyway.\n */\nexport interface UseGuestRecordAccessResult {\n readonly setting: GuestRecordAccessSetting;\n /** Null until the server has said what `default` means for this meeting. */\n readonly effective: GuestRecordAccessResolution | null;\n readonly saving: boolean;\n readonly error: { readonly message: string; readonly remedy: string } | null;\n choose: (setting: GuestRecordAccessSetting) => Promise<void>;\n}\n\nexport function useGuestRecordAccess(\n meeting: MeetingRecord | null,\n): UseGuestRecordAccessResult {\n const host = useMeetHost();\n const api = host?.api ?? null;\n const stored = guestRecordAccessSetting(meeting);\n // The row's own value is the truth until a write of ours succeeds; this is\n // the optimistic half, and it is reset by the row arriving again.\n const [chosen, setChosen] = useState<GuestRecordAccessSetting | null>(null);\n const [effective, setEffective] = useState<GuestRecordAccessResolution | null>(null);\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<{ message: string; remedy: string } | null>(null);\n\n useEffect(() => {\n setChosen(null);\n }, [stored, meeting?.id]);\n\n return {\n setting: chosen ?? stored,\n effective,\n saving,\n error,\n choose: useCallback(\n async (next: GuestRecordAccessSetting) => {\n if (api === null || meeting === null) return;\n setSaving(true);\n setError(null);\n try {\n const result = await setGuestRecordAccess({\n api,\n meetingId: meeting.id,\n organizationId: meeting.organizationId,\n access: next,\n });\n setChosen(result.setting);\n setEffective(result.effective);\n } catch (thrown) {\n // The server's own sentence, rendered. A host who is not a host of\n // this meeting reads why, not a generic failure.\n setError({\n message: (thrown as Error).message,\n remedy:\n (thrown as { remedy?: string }).remedy ??\n \"Nothing was changed. Reload the page and try again.\",\n });\n } finally {\n setSaving(false);\n }\n },\n [api, meeting],\n ),\n };\n}\n","/**\n * THE MEETING RECORD — what a meeting IS once the room is gone (MRI-D2).\n *\n * 🚨 THE DEFECT THIS EXISTS TO KILL. Through 2026-09-08 a meeting produced a\n * wrap-up summary, decisions, owner-assigned action items, five windows of live\n * notes, thirty-two transcript segments and a recording — and then had NO\n * SURFACE AT ALL. `/meet/<slug>` fell back to `<PreJoin>` on an ended meeting,\n * so the durable link that R3 promises would outlive the meeting resolved to a\n * screen offering to join a room that no longer exists. Everything the campaign\n * built after the room closed was reachable only from SQL.\n *\n * Three rules hold this file together:\n *\n * 1. **Every read is INDEPENDENT and every refusal is NAMED.** The transcript,\n * the notes, the attendance list and the recording live in four RLS-guarded\n * tables, and a guest holding the link can read NONE of them (only\n * `meet_meeting_by_slug` is granted to `anon`). One `Promise.all` would turn\n * a permission boundary into a blank page. Known guest restrictions never issue private reads; each authenticated read that is refused\n * becomes a `MeetingRecordRestriction` with a sentence — the reader is told\n * what exists and why they cannot see it, which is the difference between a\n * record they can ask for and a product that looks broken.\n * 2. **A partial record is still a record.** A failed transcript read never\n * hides the summary somebody CAN read.\n * 3. **Nothing here computes meaning.** Grouping, ordering and formatting only.\n * The summary, decisions and action items are durable rows written by the\n * wrap-up mandate (D5/D18); this module never re-derives them from the\n * transcript, because two sources for one fact is how they start to disagree.\n */\n\nimport { formatDurationMs } from \"@ai-matrx/kit/format\";\nimport type { MeetApi } from \"./api\";\nimport { MeetError } from \"./errors\";\nimport { displayNameFor, learnNames, type NameBook } from \"./names\";\nimport type { MeetRepository } from \"./repository\";\nimport { MEET_ROUTES, MEETING_PASS_HEADER, type RoomToken } from \"./tokens\";\nimport type {\n MeetingAttendee,\n MeetingId,\n MeetingNote,\n MeetIdentity,\n MeetingRecord,\n OrganizationId,\n ParticipantIdentity,\n RecordingRecord,\n TranscriptSegment,\n} from \"./types\";\n\n/** One thing the reader could not see, and the honest reason. Never a blank. */\nexport interface MeetingRecordRestriction {\n /** Which part of the record: `transcript`, `notes`, `attendance`, `recording`. */\n readonly part: \"transcript\" | \"notes\" | \"attendance\" | \"recording\";\n readonly message: string;\n readonly remedy: string;\n}\n\n/** One visible access state, made from restrictions that share one remedy. */\nexport interface MeetingRecordRestrictionNotice {\n readonly parts: readonly MeetingRecordRestriction[\"part\"][];\n readonly message: string;\n readonly remedy: string;\n}\n\n/** A run of consecutive lines by one speaker — how a person reads a transcript. */\nexport interface TranscriptBlock {\n readonly id: string;\n /** Who spoke. Blocks break on THIS, never on the rendered name (MRI-A16). */\n readonly identity: ParticipantIdentity;\n readonly speaker: string;\n /** ISO of the first line in the block. */\n readonly startedAt: string;\n readonly lines: readonly TranscriptSegment[];\n}\n\nexport interface MeetingRecordBundle {\n readonly meeting: MeetingRecord;\n readonly attendees: readonly MeetingAttendee[];\n /** The wrap-up summary: `kind='note'` with `metadata.role='summary'` (D5). */\n readonly summary: MeetingNote | null;\n /** D20: the AUTHORITATIVE set — window rows the wrap-up superseded are out. */\n readonly decisions: readonly MeetingNote[];\n readonly actionItems: readonly MeetingNote[];\n /** The live-notes windows, in window order (D3). */\n readonly liveNotes: readonly MeetingNote[];\n readonly transcript: readonly TranscriptSegment[];\n /** The most recent recording attempt, whatever state it reached. */\n readonly recording: RecordingRecord | null;\n /** What this reader could NOT see. Rendered; never swallowed. */\n readonly restrictions: readonly MeetingRecordRestriction[];\n /**\n * Identity → name, learned from everything this read COULD see: the durable\n * attendance rows and the transcript's own `speaker` column. It is what stops\n * a raw LiveKit token reaching the screen when one of those reads was refused\n * (MRI-A16).\n */\n readonly names: NameBook;\n}\n\n/**\n * The current wrap-up summary. Repairs preserve the prior durable row for\n * history, then mark it `superseded_by`; every current-facing projection must\n * therefore make the same distinction as decisions and action items.\n */\nexport function activeWrapUpSummary(notes: readonly MeetingNote[]): MeetingNote | null {\n return notes.find((note) => note.role === \"summary\" && note.supersededBy === null) ?? null;\n}\n\n/**\n * The sentence a reader gets when the database refuses them a part of the\n * record. It is deliberately about TENANCY, not about them: `meet_notes`,\n * `meet_transcript_segments`, `meet_participants` and `meet_recordings` have no\n * `anon` policy at all, so a link-follower with no session reaches exactly the\n * meeting row and nothing else. That is the correct posture and it must be\n * SAID, not rendered as emptiness.\n */\nconst RESTRICTION_REMEDY =\n \"Open it from the meeting link in the browser you attended with, or ask the host to share it.\";\n\nconst PART_LABEL: Readonly<Record<MeetingRecordRestriction[\"part\"], string>> = {\n transcript: \"The transcript of this meeting is not readable from this account.\",\n notes: \"The notes, decisions and action items are not readable from this account.\",\n attendance: \"Who attended is not readable from this account.\",\n recording: \"The recording is not readable from this account.\",\n};\n\n/** A refusal is DATA. A transport wobble is not, and is re-thrown as itself. */\nfunction restrictionFor(\n part: MeetingRecordRestriction[\"part\"],\n error: unknown,\n): MeetingRecordRestriction {\n const code = (error as { code?: string }).code;\n const denied =\n code === \"forbidden\" || code === \"session-unavailable\" || code === \"not-found\";\n return {\n part,\n message: denied\n ? PART_LABEL[part]\n : `${PART_LABEL[part]} (${(error as Error).message ?? \"the read failed\"})`,\n remedy: denied\n ? RESTRICTION_REMEDY\n : (error as { remedy?: string }).remedy ??\n \"This is a transient read failure, not a permission decision. Reload the page.\",\n };\n}\n\n/**\n * The four durable reads behind a record, as the database returned them, plus\n * what the reader could not see. `assembleMeetingRecord` turns them into the\n * bundle; a cache that keeps them can apply a realtime row to one part and\n * assemble again, without reading the other three (0.7.79).\n */\nexport interface MeetingRecordParts {\n readonly attendees: readonly MeetingAttendee[];\n /** Every note row (all kinds, superseded included), oldest first. */\n readonly notes: readonly MeetingNote[];\n readonly transcript: readonly TranscriptSegment[];\n /** Newest first; archived recordings are not here. */\n readonly recordings: readonly RecordingRecord[];\n readonly restrictions: readonly MeetingRecordRestriction[];\n}\n\n/**\n * Load the record parts the reader may request; state all restrictions.\n *\n * `meeting` is passed in when the caller already resolved it (the slug route\n * does, through the `anon`-granted RPC) — re-reading it through the table would\n * fail for exactly the reader who most needs the page to work.\n */\nexport async function loadMeetingRecord(\n repository: MeetRepository,\n meetingId: MeetingId,\n meeting: MeetingRecord,\n reader: Pick<MeetIdentity, \"userId\"> = { userId: null },\n): Promise<MeetingRecordBundle> {\n return assembleMeetingRecord(meeting, await loadMeetingRecordParts(repository, meetingId, reader));\n}\n\n/** The four reads, each with its own verdict (see `loadMeetingRecord`). */\nexport async function loadMeetingRecordParts(\n repository: MeetRepository,\n meetingId: MeetingId,\n reader: Pick<MeetIdentity, \"userId\"> = { userId: null },\n): Promise<MeetingRecordParts> {\n const restrictions: MeetingRecordRestriction[] = [];\n\n async function attempt<T>(\n part: MeetingRecordRestriction[\"part\"],\n run: () => Promise<T>,\n empty: T,\n ): Promise<T> {\n // No anonymous policy exists for these four tables. State the known\n // restriction without probing private data or generating expected 401s.\n if (reader.userId === null) {\n restrictions.push(restrictionFor(part, { code: \"session-unavailable\" }));\n return empty;\n }\n try {\n return await run();\n } catch (error) {\n restrictions.push(restrictionFor(part, error));\n return empty;\n }\n }\n\n // Four independent reads, four independent verdicts. Deliberately NOT\n // `Promise.all` over a single try/catch — see rule 1 in the header.\n const [attendees, notes, transcript, recordings] = await Promise.all([\n attempt<readonly MeetingAttendee[]>(\"attendance\", () => repository.attendees(meetingId), []),\n attempt<readonly MeetingNote[]>(\"notes\", () => repository.notes(meetingId), []),\n attempt<readonly TranscriptSegment[]>(\n \"transcript\",\n () => repository.transcript(meetingId),\n [],\n ),\n attempt<readonly RecordingRecord[]>(\n \"recording\",\n () => repository.recordings(meetingId),\n [],\n ),\n ]);\n return { attendees, notes, transcript, recordings, restrictions };\n}\n\n/** The bundle a reader sees, from the parts. Grouping and ordering only (rule 3). */\nexport function assembleMeetingRecord(\n meeting: MeetingRecord,\n parts: MeetingRecordParts,\n): MeetingRecordBundle {\n const { attendees, notes, transcript, recordings, restrictions } = parts;\n return {\n meeting,\n attendees,\n summary: activeWrapUpSummary(notes),\n // 🚨 D20: THE WRAP-UP IS THE AUTHORITATIVE POST-MEETING SET. A window row the\n // wrap-up superseded is still in `notes` and still in the timeline below —\n // it is simply not listed a second time as a decision that was made.\n decisions: notes.filter((note) => note.kind === \"decision\" && note.supersededBy === null),\n actionItems: notes.filter(\n (note) => note.kind === \"action-item\" && note.supersededBy === null,\n ),\n liveNotes: notes\n .filter((note) => note.kind === \"note\" && note.role === null)\n // Window order, then creation order for the notes of one window.\n .slice()\n .sort((left, right) => {\n const a = left.window ?? Number.MAX_SAFE_INTEGER;\n const b = right.window ?? Number.MAX_SAFE_INTEGER;\n return a === b ? left.createdAt.localeCompare(right.createdAt) : a - b;\n }),\n transcript,\n // `recordings` is newest first; the record shows the latest attempt,\n // including a failed one — a recording that failed is a fact the reader\n // needs, not a row to hide.\n recording: recordings[0] ?? null,\n restrictions,\n names: learnNames(learnNames({}, attendees), transcript.map((line) => ({\n identity: line.identity,\n displayName: line.speaker,\n }))),\n };\n}\n\n/**\n * 🚨 **A READ THAT WAS REFUSED IS NOT AN EMPTY RESULT (MRI-A16).**\n *\n * On production 2026-09-09 a guest's record view said *\"No wrap-up was written\n * for this meeting\"* directly underneath a panel that had just told them, in\n * the same screen, that the notes are not readable from their account — and\n * *\"Nothing was recorded as a decision\"* beside a decision that exists\n * (MRI-C8). The page asserted an absence it had no way to know, which is worse\n * than saying nothing: a person who reads \"no wrap-up was written\" stops\n * looking for one.\n *\n * So no surface writes its own empty sentence any more. It asks here, and gets\n * either its absence sentence — earned, because the read SUCCEEDED and returned\n * nothing — or the refusal, said plainly.\n */\nexport const REFUSED_ABSENCE = \"This record is not shared with guests.\";\n\n/** Was this part of the record refused to this reader? */\nexport function wasRefused(\n bundle: MeetingRecordBundle,\n part: MeetingRecordRestriction[\"part\"],\n): boolean {\n return bundle.restrictions.some((restriction) => restriction.part === part);\n}\n\n/**\n * The sentence to show where a part of the record has nothing in it. NEVER an\n * absence claim over a read that was refused.\n */\nexport function absenceSentence(\n bundle: MeetingRecordBundle,\n part: MeetingRecordRestriction[\"part\"],\n absence: string,\n): string {\n return wasRefused(bundle, part) ? REFUSED_ABSENCE : absence;\n}\n\n/**\n * 🚨 **\"NO WRAP-UP WAS WRITTEN\" IS A CLAIM, AND FOR ~33 SECONDS IT WAS FALSE.**\n *\n * On production 2026-09-09 the HOST's record view said *\"No wrap-up was written\n * for this meeting\"* between 04:46:42 — the flip from the room to the record —\n * and 04:47:15, when the wrap-up landed. The read was permitted and genuinely\n * empty, so this is not the refusal class above; it is the OTHER way a screen\n * lies, by reading \"not here yet\" as \"never happened\". A person who reads *\"no\n * wrap-up was written\"* closes the tab.\n *\n * The meeting row already knows the difference, and it is not a guess: the\n * wrap-up is a durable job, `enqueue_wrap_up` stamps\n * `metadata.note_taker.intelligence.wrap_up` when it queues it, and the graph\n * action stamps `state: \"failed\"` when the pass refuses TERMINALLY. So:\n *\n * - a summary exists → `written`;\n * - the notes read was refused → `refused` (never an absence claim);\n * - the job is marked failed → `failed`, in the server's own words;\n * - the job is queued or running → `in-progress`, with a refresh affordance;\n * - nothing ever queued one → `absent`, which is the only EARNED absence.\n *\n * There is no timeout that flips `in-progress` into `absent`. A wrap-up that is\n * genuinely stuck says it is still being written — which is TRUE — rather than\n * inventing a verdict this browser has no way to reach.\n */\nexport type WrapUpStatus =\n | { readonly state: \"written\" }\n | { readonly state: \"in-progress\"; readonly message: string; readonly canRefresh: true }\n | { readonly state: \"failed\"; readonly message: string }\n /** Nobody spoke: a normal outcome, said plainly (not an alert). */\n | { readonly state: \"empty\"; readonly message: string }\n | { readonly state: \"refused\"; readonly message: string }\n | { readonly state: \"absent\"; readonly message: string };\n\n/** What `metadata.note_taker` holds, as far as this file cares. */\nfunction noteTakerMetadata(meeting: MeetingRecord): Record<string, unknown> {\n const raw = (meeting.metadata as Record<string, unknown>)[\"note_taker\"];\n return typeof raw === \"object\" && raw !== null && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : {};\n}\n\nconst WRAP_UP_IN_PROGRESS =\n \"The wrap-up is being written… The summary, the decisions and the action items are \" +\n \"produced from the whole transcript once the meeting ends, which takes a moment longer \" +\n \"than the meeting does.\";\n\nconst WRAP_UP_ABSENT =\n \"No wrap-up was written for this meeting. A summary is produced from the transcript \" +\n \"when the meeting ends and the note-taker was in the room.\";\n\n/** Where the wrap-up actually is. Never \"absent\" over a job that is running. */\nexport function wrapUpStatus(bundle: MeetingRecordBundle): WrapUpStatus {\n if (bundle.summary !== null) return { state: \"written\" };\n if (wasRefused(bundle, \"notes\")) return { state: \"refused\", message: REFUSED_ABSENCE };\n\n const noteTaker = noteTakerMetadata(bundle.meeting);\n const intelligenceRaw = noteTaker[\"intelligence\"];\n const intelligence: Record<string, unknown> =\n typeof intelligenceRaw === \"object\" && intelligenceRaw !== null && !Array.isArray(intelligenceRaw)\n ? (intelligenceRaw as Record<string, unknown>)\n : {};\n const wrapUpRaw = intelligence[\"wrap_up\"];\n const wrapUp: Record<string, unknown> =\n typeof wrapUpRaw === \"object\" && wrapUpRaw !== null && !Array.isArray(wrapUpRaw)\n ? (wrapUpRaw as Record<string, unknown>)\n : {};\n\n if (wrapUp[\"state\"] === \"empty\") {\n const reason = wrapUp[\"reason\"];\n return {\n state: \"empty\",\n message:\n typeof reason === \"string\" && reason.trim().length > 0\n ? reason.trim()\n : \"No one spoke in this meeting, so there is no summary.\",\n };\n }\n\n if (wrapUp[\"state\"] === \"failed\") {\n const reason = wrapUp[\"reason\"];\n return {\n state: \"failed\",\n // THE SERVER'S OWN SENTENCE, verbatim. It already names the remedy, and a\n // house sentence over it would hide the one thing worth reading.\n message:\n typeof reason === \"string\" && reason.trim().length > 0\n ? reason.trim()\n : \"The wrap-up pass failed and no summary was written. It is not retried \" +\n \"automatically; a host can end the meeting again to re-queue it.\",\n };\n }\n\n const queued =\n typeof wrapUp[\"run_id\"] === \"string\" ||\n typeof intelligence[\"wrap_up_run_id\"] === \"string\" ||\n wrapUp[\"state\"] === \"queued\" ||\n wrapUp[\"state\"] === \"running\";\n // A meeting the note-taker actually attended will get a wrap-up even if this\n // browser loaded the record before the enqueue stamp landed — that race is\n // the whole defect, so `ended + the note-taker was there` is enough.\n const attended =\n noteTaker[\"state\"] === \"ended\" ||\n noteTaker[\"state\"] === \"transcribing\" ||\n noteTaker[\"state\"] === \"handoff_pending\";\n\n if (hasEnded(bundle.meeting) && (queued || attended)) {\n return { state: \"in-progress\", message: WRAP_UP_IN_PROGRESS, canRefresh: true };\n }\n return { state: \"absent\", message: WRAP_UP_ABSENT };\n}\n\n/**\n * GROUP THE TRANSCRIPT THE WAY A PERSON READS IT: consecutive lines by one\n * speaker become one block with one timestamp. Reading thirty-two rows each\n * re-stamped with a name and a clock is how a transcript becomes unreadable.\n */\nexport function groupTranscript(\n segments: readonly TranscriptSegment[],\n names: NameBook = {},\n): readonly TranscriptBlock[] {\n const blocks: TranscriptBlock[] = [];\n for (const segment of segments) {\n const last = blocks[blocks.length - 1];\n // 🚨 GROUPED BY IDENTITY, not by the rendered name (MRI-A16). Two people\n // whose names nobody ever learned both render \"Unnamed guest\", and merging\n // their lines into one block would put words in the wrong mouth — the exact\n // failure the honest placeholder exists to avoid.\n if (last !== undefined && last.identity === segment.identity) {\n blocks[blocks.length - 1] = { ...last, lines: [...last.lines, segment] };\n continue;\n }\n blocks.push({\n id: segment.id,\n identity: segment.identity,\n // One resolution, shared with the live rail — so the record and the room\n // can never disagree about who said something.\n speaker: displayNameFor(names, segment.identity, segment.speaker),\n startedAt: segment.startedAt,\n lines: [segment],\n });\n }\n return blocks;\n}\n\n/**\n * WHAT THE RECORDING IS DOING, in a sentence — including the two states people\n * hit most and understand least. `processing` is not \"broken\" and `failed` is\n * never silence: both say what happened and what to do.\n */\nexport function recordingNotice(recording: RecordingRecord | null): string | null {\n if (recording === null) return null;\n switch (recording.state) {\n case \"available\":\n if (recording.retention?.phase !== \"warning\") return null;\n return `This recording expires on ${retentionDueDate(recording.retention.dueAt)}. The transcript and notes remain available.`;\n case \"expired\":\n return \"This recording has expired. The transcript and notes remain available.\";\n case \"processing\":\n case \"stopping\":\n return \"The recording is still being processed. It appears here on its own once the file lands — nothing is lost while it is being written.\";\n case \"recording\":\n case \"starting\":\n return \"This recording is still being written. It becomes playable once the meeting's egress finishes.\";\n case \"failed\":\n return recording.failureReason !== null && recording.failureReason.length > 0\n ? `The recording failed: ${recording.failureReason}`\n : \"The recording failed and no file was produced. The transcript below was written separately and is unaffected.\";\n default:\n return null;\n }\n}\n\nfunction retentionDueDate(value: string): string {\n const dueAt = new Date(value);\n return Number.isNaN(dueAt.getTime()) ? \"its scheduled retention date\" : dueAt.toLocaleString();\n}\n\n/** Only an `available` recording has a file to play. Nothing else pretends to. */\nexport function playableRecordingFileId(recording: RecordingRecord | null): string | null {\n if (recording === null) return null;\n return recording.state === \"available\" ? recording.fileId : null;\n}\n\n/**\n * A MEETING IS OVER WHEN THE SERVER SAYS SO. `ended_at` is stamped by\n * `POST /v1/meet/end` or by the auto-end evaluation (MRI-A6) — never by a\n * client noticing the room is empty.\n */\nexport function hasEnded(meeting: MeetingRecord | null): boolean {\n return meeting !== null && meeting.endedAt !== null;\n}\n\n/**\n * \"Thursday, 8 September 2026, 4:21 PM · 19 minutes\". One shape everywhere.\n *\n * THE SPAN IS `formatDurationMs`'s (2026-09-12). This body used to divide by\n * `60_000` and pluralise \"minute\" itself — a duration formatter living in a\n * PACKAGE, found the day `aidream/apps/shared` became a scan root. `@ai-matrx/\n * meet` already depends on `@ai-matrx/kit`, and `style: \"long\"` is precisely\n * this voice (\"19 minutes\", \"1 minute\"). Three things improve by adopting it:\n * a 40-second meeting read \"0 minutes\" and now reads \"40 seconds\" (the\n * confident zero the package refuses), a 19-minute-35-second meeting read\n * \"20 minutes\" and now reads \"19 minutes\" (prose asserts at-least, so it\n * floors), and a 90-minute meeting reads \"1 hour\" instead of \"90 minutes\".\n */\nexport function meetingWhen(meeting: MeetingRecord, locale?: string): string {\n const start = meeting.startedAt ?? meeting.scheduledFor;\n if (start === null) return \"Time not recorded\";\n const startedAt = new Date(start);\n if (Number.isNaN(startedAt.getTime())) return \"Time not recorded\";\n const when = startedAt.toLocaleString(locale, {\n weekday: \"long\",\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n });\n if (meeting.endedAt === null || meeting.startedAt === null) return when;\n const endedAt = new Date(meeting.endedAt);\n if (Number.isNaN(endedAt.getTime())) return when;\n const elapsedMs = endedAt.getTime() - startedAt.getTime();\n return `${when} · ${formatDurationMs(elapsedMs, { style: \"long\" })}`;\n}\n\n/* ═══════════════════════════════════════════════════════════════════════════\n * MRF-1 — WHAT A GUEST MAY READ, AND HOW THEY READ IT\n *\n * 🚨 THE DEFECT THIS CLOSES. Everything above this line is correct and was\n * still the wrong product: a guest who attended a meeting, was admitted by the\n * host, spoke, and was transcribed got FOUR refusal sentences where the record\n * should have been. `anon` has no grant on the four org-scoped tables and\n * widening it is forbidden (MRI-A10) — a meeting link is not organization\n * membership — so the honest refusals were the only thing this file could say.\n *\n * Arman ruled on 2026-09-12 that guest access to the record is a CONFIGURATION\n * and never a question: a system default, an organization override, and a\n * per-meeting setting a host chooses. The server resolves it and serves the\n * shared subset at `GET /api/v1/meet/record`, authorized by the room token this\n * device already holds. So this half does three things and no more:\n *\n * 1. reads that route and turns its answer into the SAME `MeetingRecordBundle`\n * the member lane produces — so one screen renders both readers and they can\n * never drift into two products;\n * 2. states every part the level does NOT share as a `MeetingRecordRestriction`\n * with the server's own sentence, which is what keeps \"not shared\" and\n * \"nothing happened\" different things on screen;\n * 3. gives a host the control that sets it.\n *\n * What it does NOT do is decide anything. The level, the refusals and the\n * remedies are the server's words; a client that computed its own would be a\n * second authority on who may read a transcript.\n * ═══════════════════════════════════════════════════════════════════════════ */\n\n\n/** How much of a finished meeting a GUEST may read. Pinned with the server. */\nexport type GuestRecordAccess = \"none\" | \"summary\" | \"full\";\n\n/** Widest last. The ORDER is the comparison, exactly as it is on the server. */\nexport const GUEST_RECORD_ACCESS_LEVELS: readonly GuestRecordAccess[] = [\n \"none\",\n \"summary\",\n \"full\",\n];\n\n/**\n * What a HOST may choose for one meeting. `default` is not a fourth level — it\n * is the absence of a per-meeting choice, and it is the only way to un-choose.\n */\nexport type GuestRecordAccessSetting = \"default\" | GuestRecordAccess;\n\nexport const GUEST_RECORD_ACCESS_CHOICES: readonly GuestRecordAccessSetting[] = [\n \"default\",\n \"summary\",\n \"full\",\n \"none\",\n];\n\n/** `meet_meetings.metadata.guest_record_access`. No DDL; a setting on a row. */\nexport const GUEST_RECORD_ACCESS_METADATA_KEY = \"guest_record_access\";\n\n/** What a host chose for THIS meeting, read from the row they already have. */\nexport function guestRecordAccessSetting(\n meeting: MeetingRecord | null,\n): GuestRecordAccessSetting {\n const raw = (meeting?.metadata as Record<string, unknown> | undefined)?.[\n GUEST_RECORD_ACCESS_METADATA_KEY\n ];\n return GUEST_RECORD_ACCESS_LEVELS.includes(raw as GuestRecordAccess)\n ? (raw as GuestRecordAccess)\n : \"default\";\n}\n\n/** The sentence on the host's control. Plain English, never a token. */\nexport function guestRecordAccessLabel(setting: GuestRecordAccessSetting): string {\n switch (setting) {\n case \"default\":\n return \"Use my organization's setting\";\n case \"summary\":\n return \"Summary, decisions and action items\";\n case \"full\":\n return \"Everything, including the transcript\";\n case \"none\":\n return \"Nothing — keep the record internal\";\n }\n}\n\n/** What each choice actually means for the people who were in the room. */\nexport function guestRecordAccessExplanation(setting: GuestRecordAccessSetting): string {\n switch (setting) {\n case \"default\":\n return \"Guests who attended read whatever your organization shares by default. An administrator sets that once, for every meeting nobody thinks about.\";\n case \"summary\":\n return \"Guests who attended read the wrap-up summary, the decisions, the action items, the notes taken as it happened, and who was here. They do not get the word-for-word transcript or the recording.\";\n case \"full\":\n return \"Guests who attended read everything this meeting produced, including the word-for-word transcript of what each person said.\";\n case \"none\":\n return \"Guests who attended are told plainly that this meeting's record is kept inside your organization. They see nothing of it.\";\n }\n}\n\n/** What came back from the server, so a surface never has to infer a level. */\nexport interface GuestRecordAccessResolution {\n readonly level: GuestRecordAccess;\n /** `meeting` when a host chose for this one; `organization` otherwise. */\n readonly source: \"meeting\" | \"organization\";\n}\n\n/** What the parts of the record the level does not share are called, and why. */\nconst NOT_SHARED: Readonly<Record<MeetingRecordRestriction[\"part\"], string>> = {\n transcript:\n \"The word-for-word transcript of this meeting is not shared with guests.\",\n notes: \"The notes, decisions and action items are not shared with guests.\",\n attendance: \"Who attended is not shared with guests.\",\n recording: \"The recording of this meeting is not shared with guests.\",\n};\n\nconst NOT_SHARED_REMEDY =\n \"The organization that held this meeting decided how much of its record guests can read. \" +\n \"If you need more, ask whoever invited you — they can send it to you, or change the \" +\n \"setting for this meeting.\";\n\nconst RESTRICTION_PART_NAME: Readonly<Record<MeetingRecordRestriction[\"part\"], string>> = {\n attendance: \"attendance\",\n notes: \"notes and decisions\",\n transcript: \"word-for-word transcript\",\n recording: \"recording\",\n};\n\nfunction joinPartNames(parts: readonly MeetingRecordRestriction[\"part\"][]): string {\n const names = parts.map((part) => RESTRICTION_PART_NAME[part]);\n if (names.length < 2) return names[0] ?? \"this part of the record\";\n if (names.length === 2) return `${names[0]} and ${names[1]}`;\n return `${names.slice(0, -1).join(\", \")}, and ${names[names.length - 1]}`;\n}\n\n/**\n * Repeated policy refusals are one access state, not four alarming cards.\n *\n * The individual rows remain durable data for absence handling in their own\n * sections. This projection groups only rows with the exact same remedy, so a\n * distinct transport or integrity failure remains visible on its own.\n */\nexport function summarizeMeetingRecordRestrictions(\n restrictions: readonly MeetingRecordRestriction[],\n): readonly MeetingRecordRestrictionNotice[] {\n const grouped = new Map<string, MeetingRecordRestriction[]>();\n for (const restriction of restrictions) {\n const rows = grouped.get(restriction.remedy) ?? [];\n rows.push(restriction);\n grouped.set(restriction.remedy, rows);\n }\n return [...grouped.values()].map((rows) => {\n if (rows.length === 1) {\n const row = rows[0]!;\n return { parts: [row.part], message: row.message, remedy: row.remedy };\n }\n return {\n parts: rows.map((row) => row.part),\n message: `Some parts of this meeting record are not available to this reader: ${joinPartNames(rows.map((row) => row.part))}.`,\n remedy: rows[0]!.remedy,\n };\n });\n}\n\n/**\n * Read the record the server shares with THIS guest.\n *\n * `roomToken` is the pass this device kept from the meeting\n * (`TokenClient.remembered`). It is presented in a header, never appended to\n * the URL. A guest with no pass, an expired pass, or a level of `none` gets the\n * server's refusal — as restrictions on a real bundle, so the screen says what\n * it cannot show instead of collapsing into an error page.\n */\nexport async function loadGuestMeetingRecord(args: {\n api: MeetApi;\n repository: MeetRepository;\n meetingId: MeetingId;\n meeting: MeetingRecord;\n roomToken: RoomToken | null;\n /**\n * A SIGNED-IN reader the database refused (from another organization, or a\n * guest who later created an account). Their session stands in for the\n * pass: the server reads their `user:<id>` admission or the attendance they\n * claimed, and shares what guests may read (2026-10-01).\n */\n session?: boolean;\n /**\n * THE SESSION-AWARE RECORD (CORE-DESIGN §2.3): one run of a series. Omitted = the server's\n * default (a one-off meeting's whole record; a series' latest ended run).\n */\n sessionId?: string | null;\n}): Promise<MeetingRecordBundle> {\n const { api, repository, meetingId, meeting, roomToken } = args;\n const session = args.session === true;\n\n if (roomToken?.meetingPass == null && !session) {\n // NOT A GUESS AND NOT A FETCH. This browser has no pass for this meeting,\n // which the server would answer with the same sentence — asking it first\n // would only add a round trip and a 403 in the console.\n return refusedBundle(\n meeting,\n \"This meeting's record is only readable by the people who were in it.\",\n \"Open the meeting from the link you were invited with, in the same browser you used \" +\n \"to attend — that is what identifies you to this meeting. Signing in with an \" +\n \"account in the organization that held the meeting also works.\",\n );\n }\n\n let response: Response;\n try {\n response = await api.request({\n path:\n `${MEET_ROUTES.record}?meeting_id=${encodeURIComponent(meetingId)}` +\n (args.sessionId ? `&session_id=${encodeURIComponent(args.sessionId)}` : \"\"),\n method: \"GET\",\n // A GUEST HAS NO SESSION AND IS STILL A FIRST-CLASS READER (D6).\n requireAuth: session,\n operation: \"meet.record\",\n headers:\n roomToken?.meetingPass != null ? { [MEETING_PASS_HEADER]: roomToken.meetingPass } : {},\n });\n } catch (error) {\n // A transport wobble is not a permission decision and must not read as one.\n return refusedBundle(\n meeting,\n `This meeting's record could not be read (${(error as Error).message}).`,\n (error as { remedy?: string }).remedy ??\n \"This is a connection failure, not a decision about you. Reload the page.\",\n );\n }\n\n if (!response.ok) {\n const refusal = await readRefusal(response);\n return refusedBundle(meeting, refusal.message, refusal.remedy);\n }\n\n const body = (await response.json()) as Record<string, unknown>;\n const payload = (\n typeof body[\"data\"] === \"object\" && body[\"data\"] !== null ? body[\"data\"] : body\n ) as GuestRecordPayload;\n\n const rows = (key: keyof GuestRecordPayload): Record<string, unknown>[] =>\n Array.isArray(payload[key]) ? (payload[key] as Record<string, unknown>[]) : [];\n\n const attendees = rows(\"attendees\").map((row) => repository.projectAttendee(row));\n const notes = rows(\"notes\").map((row) => repository.projectNote(row));\n const transcript = rows(\"transcript\").map((row) => repository.projectSegment(row));\n const recordings = rows(\"recordings\").map((row) => repository.projectRecording(row));\n\n // 🚨 THE RESTRICTIONS COME FROM `shared`, NOT FROM AN EMPTY ARRAY. A level of\n // `summary` and a meeting where nobody spoke both produce zero transcript\n // rows, and telling the reader they are the same thing is the MRI-A16 defect.\n const shared = (payload.shared ?? {}) as Record<string, boolean>;\n const restrictions: MeetingRecordRestriction[] = [];\n for (const part of [\"attendance\", \"notes\", \"transcript\", \"recording\"] as const) {\n if (shared[part] === false) {\n restrictions.push({ part, message: NOT_SHARED[part], remedy: NOT_SHARED_REMEDY });\n }\n }\n\n return {\n meeting,\n attendees,\n summary: activeWrapUpSummary(notes),\n decisions: notes.filter((note) => note.kind === \"decision\" && note.supersededBy === null),\n actionItems: notes.filter(\n (note) => note.kind === \"action-item\" && note.supersededBy === null,\n ),\n liveNotes: notes\n .filter((note) => note.kind === \"note\" && note.role === null)\n .slice()\n .sort((left, right) => {\n const a = left.window ?? Number.MAX_SAFE_INTEGER;\n const b = right.window ?? Number.MAX_SAFE_INTEGER;\n return a === b ? left.createdAt.localeCompare(right.createdAt) : a - b;\n }),\n transcript,\n recording: recordings[0] ?? null,\n restrictions,\n names: learnNames(\n learnNames({}, attendees),\n transcript.map((line) => ({ identity: line.identity, displayName: line.speaker })),\n ),\n };\n}\n\n/** The row shapes the route returns — the same snake_case the tables use. */\ninterface GuestRecordPayload {\n readonly access?: GuestRecordAccess;\n readonly access_source?: \"meeting\" | \"organization\";\n readonly shared?: Record<string, boolean>;\n readonly attendees?: unknown;\n readonly notes?: unknown;\n readonly transcript?: unknown;\n readonly recordings?: unknown;\n readonly recording_notice?: string;\n}\n\n/**\n * THE SERVER'S OWN WORDS, verbatim. Every meet refusal carries\n * `{message, remedy}`, and a house sentence written over it would hide the one\n * thing worth reading — including the difference between \"your pass expired,\n * open the link again\" and \"this record is not shared with guests\".\n */\nasync function readRefusal(\n response: Response,\n): Promise<{ message: string; remedy: string }> {\n try {\n const body = (await response.json()) as Record<string, unknown>;\n const detail = (\n typeof body[\"detail\"] === \"object\" && body[\"detail\"] !== null ? body[\"detail\"] : body\n ) as Record<string, unknown>;\n const message = detail[\"message\"];\n const remedy = detail[\"remedy\"];\n if (typeof message === \"string\" && message.length > 0) {\n return {\n message,\n remedy:\n typeof remedy === \"string\" && remedy.length > 0\n ? remedy\n : \"Ask whoever hosted the meeting for what you need.\",\n };\n }\n } catch {\n /* falls through to the status sentence below */\n }\n return {\n message: `This meeting's record could not be read (the server answered ${response.status}).`,\n remedy:\n \"This is not a decision about you — it is a failure to reach the record. Reload the \" +\n \"page; if it repeats, tell whoever hosted the meeting.\",\n };\n}\n\n/** A real bundle whose every part is refused, with ONE reason said once. */\nfunction refusedBundle(\n meeting: MeetingRecord,\n message: string,\n remedy: string,\n): MeetingRecordBundle {\n return {\n meeting,\n attendees: [],\n summary: null,\n decisions: [],\n actionItems: [],\n liveNotes: [],\n transcript: [],\n recording: null,\n // Every empty collection below is a refusal, never evidence of absence.\n // The record renderer asks its own part before it writes an empty-state\n // sentence, so omitting attendance here made a denied roster read as\n // “Nobody attended.” `summarizeMeetingRecordRestrictions` still presents\n // this shared server reason once at the top of the record.\n restrictions: ([\"attendance\", \"notes\", \"transcript\", \"recording\"] as const).map(\n (part) => ({ part, message, remedy }),\n ),\n names: {},\n };\n}\n\n/**\n * A HOST chooses what guests may read of THIS meeting.\n *\n * Host power is the SERVER's decision and it re-checks it; this function exists\n * so the control has one call to make, and it returns what the choice RESOLVES\n * to so the screen never has to guess what \"my organization's setting\" means\n * today.\n */\nexport async function setGuestRecordAccess(args: {\n api: MeetApi;\n meetingId: MeetingId;\n organizationId: OrganizationId;\n access: GuestRecordAccessSetting;\n}): Promise<{\n readonly setting: GuestRecordAccessSetting;\n readonly effective: GuestRecordAccessResolution;\n}> {\n const response = await args.api.request({\n path: MEET_ROUTES.recordAccess,\n method: \"POST\",\n requireAuth: true,\n operation: \"meet.record.access\",\n body: {\n meeting_id: args.meetingId,\n organization_id: args.organizationId,\n access: args.access,\n },\n });\n if (!response.ok) {\n const refusal = await readRefusal(response);\n throw new MeetError(\"forbidden\", refusal.message, refusal.remedy);\n }\n const body = (await response.json()) as Record<string, unknown>;\n const payload = (\n typeof body[\"data\"] === \"object\" && body[\"data\"] !== null ? body[\"data\"] : body\n ) as {\n setting?: GuestRecordAccessSetting;\n effective?: GuestRecordAccessResolution;\n };\n return {\n setting: payload.setting ?? args.access,\n effective: payload.effective ?? { level: \"summary\", source: \"organization\" },\n };\n}\n\n/** What a guest is told when the level shares nothing. Rendered, never hidden. */\nexport const GUEST_RECORD_NOT_SHARED = NOT_SHARED;\n","/**\n * THE COLLAB AND CAPTION HOOKS — thin selectors over the core (CORE-DESIGN\n * §5.3, step S1). The logic lives in `core/collab-state.ts`; these only bind it\n * to the provider's store so every skin gets the same answers.\n */\n\nimport { useEffect, useRef } from \"react\";\nimport type { CollabPanel, CollabSnapshot } from \"../core/collab\";\nimport {\n activityAttention,\n availableActivities,\n moveToRoom,\n nextTranslationBatch,\n performCollab,\n takePanelRequest,\n untranslatedLines,\n} from \"../core/collab-state\";\nimport type { CollabRequest } from \"../core/in-room\";\nimport { useIsHost, useMeetHost, useMeetSnapshot } from \"./provider\";\n\nexport interface UseCollabResult {\n readonly collab: CollabSnapshot | null;\n readonly isHost: boolean;\n readonly localIdentity: string | null;\n readonly breakoutRoomId: string | null;\n /** Perform one act; resolves with the server's sentence, or its refusal (never throws). */\n act(request: CollabRequest): Promise<{ ok: boolean; message: string; itemId?: string | null }>;\n /** Go to a breakout room (`null` = the main room). */\n moveTo(roomId: string | null): Promise<{ ok: boolean; message: string }>;\n}\n\nexport function useCollab(): UseCollabResult {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const isHost = useIsHost();\n return {\n collab: snapshot?.collab ?? null,\n isHost,\n localIdentity: snapshot?.localIdentity ?? null,\n breakoutRoomId: snapshot?.breakoutRoomId ?? null,\n act: (request) => performCollab(host, request),\n moveTo: (roomId) => moveToRoom(host, roomId),\n };\n}\n\n/** The activities this person has, in the order Meet lists them. */\nexport function useAvailableActivities(): CollabPanel[] {\n const { collab, isHost } = useCollab();\n return availableActivities(collab, isHost);\n}\n\n/** Something worth a dot on the Activities button. */\nexport function useActivityAttention(): boolean {\n const { collab, isHost } = useCollab();\n return activityAttention(collab, isHost);\n}\n\n/**\n * The viewer's caption-translation choice and the translator's work queue. Finished\n * lines without a translation are sent in small batches; each line is translated once\n * per language for the whole meeting (server cache).\n */\nexport function useCaptionTranslation(lines: readonly { id: string; text: string; final: boolean }[]): {\n language: string | null;\n translations: Readonly<Record<string, string>>;\n error: string | null;\n} {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const collab = snapshot?.collab ?? null;\n const language = collab?.captionLanguage ?? null;\n const asked = useRef<Set<string>>(new Set());\n const translations = collab?.captionTranslations ?? {};\n const missing = untranslatedLines(lines, translations);\n // The batch's identity: the effect below runs when it changes, never per render.\n const key = `${language ?? \"\"}|${missing.map((l) => l.id).join(\",\")}`;\n const pending = useRef(missing);\n pending.current = missing;\n useEffect(() => {\n if (host === null || language === null) return;\n const batch = nextTranslationBatch(pending.current, language, asked.current);\n if (batch.length === 0) return;\n void host.inRoom.translateCaptions(language, batch).catch(() => undefined);\n }, [host, language, key]);\n return { language, translations, error: collab?.captionTranslationError ?? null };\n}\n\n/** Which collab panel the bar should open, asked by something else (a poll launched). */\nexport function useCollabPanelRequest(onRequest: (panel: CollabPanel) => void): void {\n const host = useMeetHost();\n const at = useMeetSnapshot()?.collab.panelRequest?.at ?? null;\n const callback = useRef(onRequest);\n callback.current = onRequest;\n useEffect(() => {\n if (host === null) return;\n const panel = takePanelRequest(host.store);\n if (panel === null) return;\n callback.current(panel);\n }, [at, host]);\n}\n","/**\n * The reaction ids every skin offers (CORE-DESIGN §5.3). The id IS the emoji\n * the wire carries; a skin may draw its own art for each, never a different set.\n */\nexport const MEET_REACTIONS: readonly string[] = [\"💖\", \"👍\", \"🎉\", \"👏\", \"😂\", \"😮\", \"😢\", \"🤔\", \"👎\"];\n","\"use client\";\n\n/**\n * POLLS, Q&A, THE SHARED WHITEBOARD AND BREAKOUT ROOMS — the in-room surfaces (Meet wave 5).\n *\n * Every act goes to the server (`host.inRoom.collab`), which decides who may do it and\n * what each person sees; these components render what the store holds and say the\n * server's sentence when it refuses. A control exists only when the organization's knob\n * turns it on (`collab.settings`) — absent, never dead.\n *\n * The champions: Google Meet's Activities panel (one door to polls, Q&A, whiteboard and\n * breakouts) and Zoom's breakout-room controls (automatic / manual / let people choose,\n * timer, broadcast, move, bring everyone back).\n */\n\nimport { useEffect, useRef, useState, type PointerEvent as ReactPointerEvent, type ReactNode } from \"react\";\nimport { CAPTION_LANGUAGES, type CollabPanel, type MeetBreakoutPlan, type MeetPoll, type MeetQuestion, type MeetStroke, assignedRoom, autoAssign, breakoutRemainingMs, defaultCaptionLanguage, displayNameFor, formatDurationMs, useMeetHost, useMeetSnapshot, setCaptionLanguage, takeMoveRequest, useActivityAttention, useAvailableActivities, useCollab } from \"../../react/facade\";\n\nimport { ControlButton, MenuItem, PanelShell, Popover } from \"./components\";\nimport { ActivitiesIcon, BreakoutIcon, CloseIcon, EraserIcon, HighlighterIcon, PencilIcon, PollIcon, QuestionIcon, UndoIcon, UpvoteIcon, WhiteboardIcon } from \"./icons\";\n\nfunction useNotice(): [\n { text: string; failed: boolean } | null,\n (result: { ok: boolean; message: string }) => void,\n] {\n const [notice, setNotice] = useState<{ text: string; failed: boolean } | null>(null);\n useEffect(() => {\n if (notice === null) return undefined;\n const timer = setTimeout(() => setNotice(null), notice.failed ? 7000 : 3000);\n return () => clearTimeout(timer);\n }, [notice]);\n return [notice, (result) => setNotice({ text: result.message, failed: !result.ok })];\n}\n\nfunction Notice({ notice }: { notice: { text: string; failed: boolean } | null }): ReactNode {\n if (notice === null) return null;\n return (\n <p className={notice.failed ? \"mx-meet__panel-note mx-meet__collab-error\" : \"mx-meet__panel-note\"} role=\"status\">\n {notice.text}\n </p>\n );\n}\n\n// ── the Activities control ─────────────────────────────────────────────────\n\nconst ACTIVITY_LABELS: Record<CollabPanel, string> = {\n polls: \"Polls\",\n qa: \"Q&A\",\n whiteboard: \"Whiteboard\",\n breakouts: \"Breakout rooms\",\n};\n\nfunction ActivityIcon({ panel }: { panel: CollabPanel }): ReactNode {\n const props = { width: 16, height: 16 };\n if (panel === \"polls\") return <PollIcon {...props} />;\n if (panel === \"qa\") return <QuestionIcon {...props} />;\n if (panel === \"whiteboard\") return <WhiteboardIcon {...props} />;\n return <BreakoutIcon {...props} />;\n}\n\nexport function ActivitiesControl({\n active,\n onOpen,\n}: {\n active: CollabPanel | null;\n onOpen: (panel: CollabPanel) => void;\n}): ReactNode {\n const [open, setOpen] = useState(false);\n const available = useAvailableActivities();\n const attention = useActivityAttention();\n if (available.length === 0) return null;\n return (\n <span className=\"mx-meet__anchor\">\n <ControlButton label=\"Activities\" active={open || active !== null} onClick={() => setOpen(!open)}>\n <ActivitiesIcon />\n {attention ? <span className=\"mx-meet__dot\" aria-hidden=\"true\" /> : null}\n </ControlButton>\n {open ? (\n <Popover label=\"Activities\" onClose={() => setOpen(false)}>\n <div className=\"mx-meet__menu\" role=\"menu\">\n {available.map((panel) => (\n <MenuItem\n key={panel}\n onSelect={() => {\n setOpen(false);\n onOpen(panel);\n }}\n >\n <ActivityIcon panel={panel} /> {ACTIVITY_LABELS[panel]}\n </MenuItem>\n ))}\n </div>\n </Popover>\n ) : null}\n </span>\n );\n}\n\n// ── polls ──────────────────────────────────────────────────────────────────\n\nfunction PollComposer({ maxOptions, onDone }: { maxOptions: number; onDone: () => void }): ReactNode {\n const { act } = useCollab();\n const [question, setQuestion] = useState(\"\");\n const [options, setOptions] = useState<string[]>([\"\", \"\"]);\n const [multiple, setMultiple] = useState(false);\n const [anonymous, setAnonymous] = useState(false);\n const [busy, setBusy] = useState(false);\n const [notice, show] = useNotice();\n const filled = options.map((o) => o.trim()).filter(Boolean);\n const ready = question.trim().length > 0 && filled.length >= 2;\n\n const save = async (launch: boolean) => {\n setBusy(true);\n const created = await act({ action: \"poll_create\", question, options: filled, multiple, anonymous });\n if (!created.ok) {\n show(created);\n setBusy(false);\n return;\n }\n if (launch && created.itemId != null) {\n const launched = await act({ action: \"poll_launch\", itemId: created.itemId });\n if (!launched.ok) {\n show(launched);\n setBusy(false);\n return;\n }\n }\n setBusy(false);\n onDone();\n };\n\n return (\n <form\n className=\"mx-meet__collab-card\"\n onSubmit={(event) => {\n event.preventDefault();\n if (ready) void save(true);\n }}\n >\n <label className=\"mx-meet__field\">\n <span className=\"mx-meet__field-label\">Question</span>\n <input\n className=\"mx-meet__input\"\n value={question}\n maxLength={1000}\n onChange={(event) => setQuestion(event.target.value)}\n placeholder=\"Ask the room something\"\n autoFocus\n />\n </label>\n {options.map((option, index) => (\n <div className=\"mx-meet__composer-row\" key={index}>\n <input\n className=\"mx-meet__input\"\n value={option}\n maxLength={200}\n aria-label={`Answer ${index + 1}`}\n placeholder={`Answer ${index + 1}`}\n onChange={(event) => setOptions(options.map((o, i) => (i === index ? event.target.value : o)))}\n />\n {options.length > 2 ? (\n <ControlButton label={`Remove answer ${index + 1}`} onClick={() => setOptions(options.filter((_, i) => i !== index))}>\n <CloseIcon width={14} height={14} />\n </ControlButton>\n ) : null}\n </div>\n ))}\n {options.length < maxOptions ? (\n <button type=\"button\" className=\"mx-meet__link\" onClick={() => setOptions([...options, \"\"])}>\n Add an answer\n </button>\n ) : null}\n <label className=\"mx-meet__toggle\">\n <input type=\"checkbox\" checked={multiple} onChange={(event) => setMultiple(event.target.checked)} />\n People can pick more than one\n </label>\n <label className=\"mx-meet__toggle\">\n <input type=\"checkbox\" checked={anonymous} onChange={(event) => setAnonymous(event.target.checked)} />\n Anonymous — nobody sees who picked what\n </label>\n <Notice notice={notice} />\n <div className=\"mx-meet__collab-actions\">\n <button type=\"submit\" className=\"mx-meet__primary mx-meet__primary--small\" disabled={!ready || busy}>\n Launch\n </button>\n <button type=\"button\" className=\"mx-meet__chip\" disabled={!ready || busy} onClick={() => void save(false)}>\n Save draft\n </button>\n <button type=\"button\" className=\"mx-meet__link\" onClick={onDone}>\n Cancel\n </button>\n </div>\n </form>\n );\n}\n\nfunction PollCard({ poll }: { poll: MeetPoll }): ReactNode {\n const { act, isHost } = useCollab();\n const [picked, setPicked] = useState<string[]>(poll.myVote.slice());\n const [notice, show] = useNotice();\n useEffect(() => setPicked(poll.myVote.slice()), [poll.myVote]);\n const counts = poll.options.some((o) => o.votes !== undefined);\n const total = poll.options.reduce((sum, o) => sum + (o.votes ?? 0), 0);\n const canVote = poll.state === \"live\";\n const voted = poll.myVote.length > 0;\n const stateWord =\n poll.state === \"draft\" ? \"Draft\" : poll.state === \"live\" ? \"Open\" : \"Closed\";\n\n const toggle = (id: string) => {\n if (!canVote) return;\n setPicked(poll.multiple ? (picked.includes(id) ? picked.filter((p) => p !== id) : [...picked, id]) : [id]);\n };\n\n return (\n <article className=\"mx-meet__collab-card\" aria-label={poll.question}>\n <header className=\"mx-meet__collab-head\">\n <strong className=\"mx-meet__collab-title\">{poll.question}</strong>\n <span className=\"mx-meet__collab-state\">{stateWord}</span>\n </header>\n <p className=\"mx-meet__panel-note\">\n {poll.multiple ? \"Pick any.\" : \"Pick one.\"}\n {poll.anonymous ? \" Anonymous.\" : \"\"}\n {counts && poll.voterCount !== null ? ` ${poll.voterCount} voted.` : \"\"}\n {isHost && poll.state !== \"draft\" && !poll.resultsShared ? \" Results are visible only to hosts.\" : \"\"}\n </p>\n <ul className=\"mx-meet__poll-options\">\n {poll.options.map((option) => {\n const share = counts && total > 0 ? Math.round(((option.votes ?? 0) / total) * 100) : 0;\n const chosen = picked.includes(option.id);\n return (\n <li key={option.id}>\n <button\n type=\"button\"\n className={chosen ? \"mx-meet__poll-option mx-meet__poll-option--chosen\" : \"mx-meet__poll-option\"}\n aria-pressed={chosen}\n disabled={!canVote}\n onClick={() => toggle(option.id)}\n >\n {counts ? <span className=\"mx-meet__poll-bar\" style={{ width: `${share}%` }} aria-hidden=\"true\" /> : null}\n <span className=\"mx-meet__poll-text\">{option.text}</span>\n {counts ? (\n <span className=\"mx-meet__poll-count\">\n {option.votes ?? 0} · {share}%\n </span>\n ) : null}\n </button>\n {option.voters !== undefined && option.voters.length > 0 ? (\n <span className=\"mx-meet__poll-voters\">{option.voters.join(\", \")}</span>\n ) : null}\n </li>\n );\n })}\n </ul>\n <Notice notice={notice} />\n <div className=\"mx-meet__collab-actions\">\n {canVote ? (\n <button\n type=\"button\"\n className=\"mx-meet__primary mx-meet__primary--small\"\n disabled={picked.length === 0 || (voted && picked.join() === poll.myVote.join())}\n onClick={() => void act({ action: \"poll_vote\", itemId: poll.id, optionIds: picked }).then(show)}\n >\n {voted ? \"Change vote\" : \"Vote\"}\n </button>\n ) : null}\n {isHost && poll.state === \"draft\" ? (\n <button type=\"button\" className=\"mx-meet__primary mx-meet__primary--small\" onClick={() => void act({ action: \"poll_launch\", itemId: poll.id }).then(show)}>\n Launch\n </button>\n ) : null}\n {isHost && poll.state === \"live\" ? (\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => void act({ action: \"poll_close\", itemId: poll.id }).then(show)}>\n End poll\n </button>\n ) : null}\n {isHost && poll.state !== \"draft\" ? (\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => void act({ action: \"poll_share\", itemId: poll.id }).then(show)}>\n {poll.resultsShared ? \"Hide results\" : \"Share results\"}\n </button>\n ) : null}\n {isHost && poll.state === \"live\" ? null : isHost ? (\n <button type=\"button\" className=\"mx-meet__link\" onClick={() => void act({ action: \"poll_delete\", itemId: poll.id }).then(show)}>\n Delete\n </button>\n ) : null}\n </div>\n </article>\n );\n}\n\nexport function PollsPanel({ onClose }: { onClose: () => void }): ReactNode {\n const { collab, isHost } = useCollab();\n const [composing, setComposing] = useState(false);\n const polls = collab?.polls ?? [];\n const maxOptions = collab?.settings?.pollMaxOptions ?? 10;\n // Newest first: the poll that just launched is the one people are looking for.\n const ordered = [...polls].reverse();\n return (\n <PanelShell title=\"Polls\" onClose={onClose}>\n {collab?.error != null ? <p className=\"mx-meet__panel-note mx-meet__collab-error\">{collab.error}</p> : null}\n {isHost && !composing ? (\n <button type=\"button\" className=\"mx-meet__primary mx-meet__primary--small\" onClick={() => setComposing(true)}>\n New poll\n </button>\n ) : null}\n {isHost && composing ? <PollComposer maxOptions={maxOptions} onDone={() => setComposing(false)} /> : null}\n {ordered.length === 0 && !composing ? (\n <p className=\"mx-meet__panel-note\">\n {isHost ? \"No polls yet. Polls and their results are kept with the meeting.\" : \"No polls yet.\"}\n </p>\n ) : null}\n {ordered.map((poll) => (\n <PollCard key={poll.id} poll={poll} />\n ))}\n </PanelShell>\n );\n}\n\n// ── Q&A ────────────────────────────────────────────────────────────────────\n\nfunction QuestionCard({ question }: { question: MeetQuestion }): ReactNode {\n const { act, isHost } = useCollab();\n const [answering, setAnswering] = useState(false);\n const [reply, setReply] = useState(\"\");\n const [notice, show] = useNotice();\n return (\n <li className={`mx-meet__collab-card mx-meet__question mx-meet__question--${question.state}`}>\n <div className=\"mx-meet__question-row\">\n <button\n type=\"button\"\n className={question.upvotedByMe ? \"mx-meet__upvote mx-meet__upvote--on\" : \"mx-meet__upvote\"}\n aria-pressed={question.upvotedByMe}\n aria-label={question.upvotedByMe ? \"Remove your upvote\" : \"Upvote\"}\n disabled={question.state === \"dismissed\"}\n onClick={() => void act({ action: \"question_upvote\", itemId: question.id }).then((r) => (r.ok ? undefined : show(r)))}\n >\n <UpvoteIcon width={16} height={16} />\n <span>{question.upvotes}</span>\n </button>\n <div className=\"mx-meet__question-body\">\n <span className=\"mx-meet__chat-meta\">\n <strong>{question.mine ? \"You\" : question.askerName}</strong>\n {question.mine && question.anonymous ? <span className=\"mx-meet__chat-private\">anonymous</span> : null}\n {question.state !== \"open\" ? (\n <span className=\"mx-meet__collab-state\">{question.state === \"answered\" ? \"Answered\" : \"Dismissed\"}</span>\n ) : null}\n </span>\n <span className=\"mx-meet__chat-text\">{question.text}</span>\n {question.answer !== null ? (\n <span className=\"mx-meet__question-answer\">\n <strong>{question.answeredByName ?? \"Host\"}:</strong> {question.answer}\n </span>\n ) : null}\n </div>\n </div>\n {answering ? (\n <form\n className=\"mx-meet__composer-row\"\n onSubmit={(event) => {\n event.preventDefault();\n void act({ action: \"question_answer\", itemId: question.id, text: reply }).then((r) => {\n show(r);\n if (r.ok) {\n setAnswering(false);\n setReply(\"\");\n }\n });\n }}\n >\n <input\n className=\"mx-meet__input\"\n value={reply}\n maxLength={2000}\n onChange={(event) => setReply(event.target.value)}\n placeholder=\"Type an answer, or leave empty if you answered out loud\"\n aria-label=\"Answer\"\n autoFocus\n />\n <button type=\"submit\" className=\"mx-meet__send\">\n Done\n </button>\n </form>\n ) : null}\n <Notice notice={notice} />\n {isHost || question.mine ? (\n <div className=\"mx-meet__collab-actions\">\n {isHost && question.state === \"open\" && !answering ? (\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => setAnswering(true)}>\n Answer\n </button>\n ) : null}\n {isHost && question.state === \"open\" ? (\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => void act({ action: \"question_dismiss\", itemId: question.id }).then(show)}>\n Dismiss\n </button>\n ) : null}\n {isHost && question.state !== \"open\" ? (\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => void act({ action: \"question_reopen\", itemId: question.id }).then(show)}>\n Reopen\n </button>\n ) : null}\n {question.mine || isHost ? (\n <button type=\"button\" className=\"mx-meet__link\" onClick={() => void act({ action: \"question_delete\", itemId: question.id }).then(show)}>\n Delete\n </button>\n ) : null}\n </div>\n ) : null}\n </li>\n );\n}\n\nexport function QaPanel({ onClose }: { onClose: () => void }): ReactNode {\n const { collab, act } = useCollab();\n const [draft, setDraft] = useState(\"\");\n const [anonymous, setAnonymous] = useState(false);\n const [order, setOrder] = useState<\"popular\" | \"recent\">(\"popular\");\n const [notice, show] = useNotice();\n const allowAnonymous = collab?.settings?.qaAnonymousAllowed === true;\n const questions = [...(collab?.questions ?? [])];\n const rank = (q: MeetQuestion) => (q.state === \"open\" ? 0 : q.state === \"answered\" ? 1 : 2);\n questions.sort((a, b) =>\n rank(a) !== rank(b)\n ? rank(a) - rank(b)\n : order === \"popular\"\n ? b.upvotes - a.upvotes || (b.createdAt ?? \"\").localeCompare(a.createdAt ?? \"\")\n : (b.createdAt ?? \"\").localeCompare(a.createdAt ?? \"\"),\n );\n return (\n <PanelShell title=\"Q&A\" onClose={onClose}>\n {collab?.error != null ? <p className=\"mx-meet__panel-note mx-meet__collab-error\">{collab.error}</p> : null}\n <form\n className=\"mx-meet__composer mx-meet__composer--chat\"\n onSubmit={(event) => {\n event.preventDefault();\n const text = draft.trim();\n if (text.length === 0) return;\n void act({ action: \"question_ask\", text, anonymous: allowAnonymous && anonymous }).then((r) => {\n if (r.ok) setDraft(\"\");\n else show(r);\n });\n }}\n >\n <div className=\"mx-meet__composer-row\">\n <input\n className=\"mx-meet__input\"\n value={draft}\n maxLength={1000}\n onChange={(event) => setDraft(event.target.value)}\n placeholder=\"Ask the host a question\"\n aria-label=\"Ask the host a question\"\n />\n <button type=\"submit\" className=\"mx-meet__send\" disabled={draft.trim().length === 0}>\n Ask\n </button>\n </div>\n {allowAnonymous ? (\n <label className=\"mx-meet__toggle\">\n <input type=\"checkbox\" checked={anonymous} onChange={(event) => setAnonymous(event.target.checked)} />\n Ask anonymously\n </label>\n ) : null}\n </form>\n <Notice notice={notice} />\n {questions.length > 1 ? (\n <div className=\"mx-meet__pills\" role=\"group\" aria-label=\"Order questions\">\n {([\"popular\", \"recent\"] as const).map((value) => (\n <button\n key={value}\n type=\"button\"\n className={order === value ? \"mx-meet__pill mx-meet__pill--on\" : \"mx-meet__pill\"}\n aria-pressed={order === value}\n onClick={() => setOrder(value)}\n >\n {value === \"popular\" ? \"Popular\" : \"Recent\"}\n </button>\n ))}\n </div>\n ) : null}\n {questions.length === 0 ? (\n <p className=\"mx-meet__panel-note\">No questions yet. Questions are kept with the meeting.</p>\n ) : (\n <ul className=\"mx-meet__chat\">\n {questions.map((question) => (\n <QuestionCard key={question.id} question={question} />\n ))}\n </ul>\n )}\n </PanelShell>\n );\n}\n\n// ── the shared whiteboard ──────────────────────────────────────────────────\n\nconst BOARD_W = 1600;\nconst BOARD_H = 1000;\nconst INK = [\"#111827\", \"#dc2626\", \"#2563eb\", \"#16a34a\", \"#f59e0b\", \"#7c3aed\"];\n\ntype Tool = \"pen\" | \"highlighter\" | \"eraser\";\n\nfunction strokePath(points: readonly (readonly [number, number])[]): string {\n if (points.length === 0) return \"\";\n const [first, ...rest] = points;\n const move = `M${(first![0] * BOARD_W).toFixed(1)} ${(first![1] * BOARD_H).toFixed(1)}`;\n if (rest.length === 0) return `${move} l0.1 0`;\n return `${move} ${rest.map(([x, y]) => `L${(x * BOARD_W).toFixed(1)} ${(y * BOARD_H).toFixed(1)}`).join(\" \")}`;\n}\n\nfunction StrokeView({ stroke, onPick }: { stroke: MeetStroke; onPick?: (() => void) | undefined }): ReactNode {\n return (\n <path\n d={strokePath(stroke.points)}\n fill=\"none\"\n stroke={stroke.color}\n strokeWidth={stroke.tool === \"highlighter\" ? stroke.width * 4 : stroke.width}\n strokeOpacity={stroke.tool === \"highlighter\" ? 0.35 : stroke.pending === true ? 0.6 : 1}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n onPointerDown={onPick}\n style={onPick !== undefined ? { cursor: \"pointer\" } : undefined}\n >\n <title>{stroke.byName}</title>\n </path>\n );\n}\n\n/**\n * THE SHARED WHITEBOARD — one board per meeting, everyone draws, it is saved with the\n * meeting (every stroke is a room item). Replaces the stage while open, the way Zoom's\n * whiteboard does; the control bar stays.\n */\nexport function WhiteboardPanel({ onClose }: { onClose: () => void }): ReactNode {\n const { collab, act, isHost, localIdentity } = useCollab();\n const [tool, setTool] = useState<Tool>(\"pen\");\n const [color, setColor] = useState(INK[0]!);\n const [drawing, setDrawing] = useState<[number, number][] | null>(null);\n const [pending, setPending] = useState<MeetStroke[]>([]);\n const [notice, show] = useNotice();\n const svgRef = useRef<SVGSVGElement | null>(null);\n const strokes = collab?.strokes ?? [];\n const mine = strokes.filter((s) => s.by === localIdentity);\n\n const point = (event: ReactPointerEvent<SVGSVGElement>): [number, number] | null => {\n const svg = svgRef.current;\n if (svg === null) return null;\n const rect = svg.getBoundingClientRect();\n // The board keeps its 16:10 shape inside whatever box it gets (xMidYMid meet).\n const scale = Math.min(rect.width / BOARD_W, rect.height / BOARD_H);\n const offsetX = (rect.width - BOARD_W * scale) / 2;\n const offsetY = (rect.height - BOARD_H * scale) / 2;\n const x = (event.clientX - rect.left - offsetX) / (BOARD_W * scale);\n const y = (event.clientY - rect.top - offsetY) / (BOARD_H * scale);\n return [Math.min(1, Math.max(0, x)), Math.min(1, Math.max(0, y))];\n };\n\n const finish = () => {\n const points = drawing;\n setDrawing(null);\n if (points === null || points.length === 0 || tool === \"eraser\") return;\n // Thin the stroke: a point every ~2px of board is plenty and keeps rows small.\n const thinned = points.filter((p, i) => i === 0 || i === points.length - 1 || i % 2 === 0).slice(0, 2000);\n const stroke = {\n points: thinned.map(([x, y]) => [Number(x.toFixed(4)), Number(y.toFixed(4))] as [number, number]),\n color,\n width: tool === \"highlighter\" ? 4 : 3,\n tool: tool === \"highlighter\" ? (\"highlighter\" as const) : (\"pen\" as const),\n };\n const localId = `pending-${Date.now()}`;\n setPending((list) => [...list, { id: localId, ...stroke, by: localIdentity ?? \"\", byName: \"You\", pending: true }]);\n void act({ action: \"stroke_add\", stroke }).then((result) => {\n setPending((list) => list.filter((s) => s.id !== localId));\n if (!result.ok) show(result);\n });\n };\n\n return (\n <section className=\"mx-meet__whiteboard\" aria-label=\"Whiteboard\">\n <header className=\"mx-meet__whiteboard-bar\">\n <strong className=\"mx-meet__panel-title\">Whiteboard</strong>\n <div className=\"mx-meet__pills\" role=\"group\" aria-label=\"Tool\">\n {(\n [\n [\"pen\", \"Pen\", <PencilIcon key=\"p\" width={16} height={16} />],\n [\"highlighter\", \"Highlighter\", <HighlighterIcon key=\"h\" width={16} height={16} />],\n [\"eraser\", \"Eraser — click a stroke\", <EraserIcon key=\"e\" width={16} height={16} />],\n ] as const\n ).map(([value, label, icon]) => (\n <button\n key={value}\n type=\"button\"\n className={tool === value ? \"mx-meet__pill mx-meet__pill--on\" : \"mx-meet__pill\"}\n aria-pressed={tool === value}\n aria-label={label}\n title={label}\n onClick={() => setTool(value)}\n >\n {icon}\n </button>\n ))}\n </div>\n <div className=\"mx-meet__swatches\" role=\"group\" aria-label=\"Color\">\n {INK.map((ink) => (\n <button\n key={ink}\n type=\"button\"\n className={ink === color ? \"mx-meet__swatch mx-meet__swatch--on\" : \"mx-meet__swatch\"}\n style={{ background: ink }}\n aria-label={`Color ${ink}`}\n aria-pressed={ink === color}\n onClick={() => {\n setColor(ink);\n if (tool === \"eraser\") setTool(\"pen\");\n }}\n />\n ))}\n </div>\n <ControlButton\n label=\"Undo my last stroke\"\n onClick={() => {\n const last = mine[mine.length - 1];\n if (last !== undefined) void act({ action: \"stroke_erase\", itemId: last.id }).then((r) => (r.ok ? undefined : show(r)));\n }}\n >\n <UndoIcon width={16} height={16} />\n </ControlButton>\n {isHost && strokes.length > 0 ? (\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => void act({ action: \"whiteboard_clear\" }).then(show)}>\n Clear board\n </button>\n ) : null}\n <span className=\"mx-meet__whiteboard-note\">Saved with the meeting</span>\n <ControlButton label=\"Close whiteboard\" onClick={onClose}>\n <CloseIcon width={16} height={16} />\n </ControlButton>\n </header>\n <Notice notice={notice} />\n <svg\n ref={svgRef}\n className={tool === \"eraser\" ? \"mx-meet__board mx-meet__board--erase\" : \"mx-meet__board\"}\n viewBox={`0 0 ${BOARD_W} ${BOARD_H}`}\n preserveAspectRatio=\"xMidYMid meet\"\n role=\"img\"\n aria-label={`Shared whiteboard, ${strokes.length} strokes`}\n onPointerDown={(event) => {\n if (tool === \"eraser\") return;\n const p = point(event);\n if (p === null) return;\n (event.target as Element).setPointerCapture?.(event.pointerId);\n setDrawing([p]);\n }}\n onPointerMove={(event) => {\n if (drawing === null) return;\n const p = point(event);\n if (p !== null) setDrawing([...drawing, p]);\n }}\n onPointerUp={finish}\n onPointerCancel={finish}\n >\n <rect x={0} y={0} width={BOARD_W} height={BOARD_H} className=\"mx-meet__board-paper\" />\n {strokes.map((stroke) => (\n <StrokeView\n key={stroke.id}\n stroke={stroke}\n onPick={\n tool === \"eraser\" && (isHost || stroke.by === localIdentity)\n ? () => void act({ action: \"stroke_erase\", itemId: stroke.id }).then((r) => (r.ok ? undefined : show(r)))\n : undefined\n }\n />\n ))}\n {pending.map((stroke) => (\n <StrokeView key={stroke.id} stroke={stroke} />\n ))}\n {drawing !== null ? (\n <StrokeView\n stroke={{\n id: \"drawing\",\n points: drawing,\n color,\n width: tool === \"highlighter\" ? 4 : 3,\n tool: tool === \"highlighter\" ? \"highlighter\" : \"pen\",\n by: \"\",\n byName: \"You\",\n }}\n />\n ) : null}\n </svg>\n </section>\n );\n}\n\n// ── breakout rooms ─────────────────────────────────────────────────────────\n\n/** A countdown: half a second left still reads 0:01, so round up to the whole second. */\nfunction clock(ms: number): string {\n return formatDurationMs(Math.ceil(ms / 1000) * 1000, { style: \"clock\" });\n}\n\n/** How long a refused `breakout_expire` waits before asking again. */\nconst BREAKOUT_EXPIRE_RETRY_MS = 5000;\n\nfunction useNow(active: boolean): number {\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n if (!active) return undefined;\n const timer = setInterval(() => setNow(Date.now()), 1000);\n return () => clearInterval(timer);\n }, [active]);\n return now;\n}\n\ntype AssignMode = \"auto\" | \"manual\" | \"choose\";\n\nfunction BreakoutSetup(): ReactNode {\n const { collab, act } = useCollab();\n const snapshot = useMeetSnapshot();\n const settings = collab?.settings ?? null;\n const people = (snapshot?.participants ?? []).filter(\n (p) => !p.isAgent && p.role !== \"host\" && p.role !== \"cohost\",\n );\n const [count, setCount] = useState(Math.min(Math.max(2, Math.ceil(people.length / 4)), settings?.breakoutMaxRooms ?? 20));\n const [mode, setMode] = useState<AssignMode>(\"auto\");\n const [minutes, setMinutes] = useState(settings?.breakoutDefaultMinutes ?? 15);\n const [manual, setManual] = useState<Record<string, number>>({});\n const [busy, setBusy] = useState(false);\n const [notice, show] = useNotice();\n const max = settings?.breakoutMaxRooms ?? 20;\n const rooms = Math.min(Math.max(1, count), max);\n const assignment =\n mode === \"auto\"\n ? autoAssign(\n people.map((p) => p.identity),\n rooms,\n )\n : mode === \"manual\"\n ? Array.from({ length: rooms }, (_, index) =>\n people.filter((p) => (manual[p.identity] ?? -1) === index).map((p) => p.identity),\n )\n : Array.from({ length: rooms }, () => [] as string[]);\n const nameOf = (identity: string) => people.find((p) => p.identity === identity)?.displayName ?? identity;\n\n return (\n <div className=\"mx-meet__collab-card\">\n <label className=\"mx-meet__field\">\n <span className=\"mx-meet__field-label\">Rooms</span>\n <input\n className=\"mx-meet__input\"\n type=\"number\"\n min={1}\n max={max}\n value={count}\n onChange={(event) => setCount(Number(event.target.value) || 1)}\n />\n </label>\n <div className=\"mx-meet__pills\" role=\"group\" aria-label=\"How people are placed\">\n {(\n [\n [\"auto\", \"Automatically\"],\n [\"manual\", \"Manually\"],\n [\"choose\", \"Let people choose\"],\n ] as const\n ).map(([value, label]) => (\n <button\n key={value}\n type=\"button\"\n className={mode === value ? \"mx-meet__pill mx-meet__pill--on\" : \"mx-meet__pill\"}\n aria-pressed={mode === value}\n onClick={() => setMode(value)}\n >\n {label}\n </button>\n ))}\n </div>\n <label className=\"mx-meet__field\">\n <span className=\"mx-meet__field-label\">Timer (minutes, 0 for none)</span>\n <input\n className=\"mx-meet__input\"\n type=\"number\"\n min={0}\n max={720}\n value={minutes}\n onChange={(event) => setMinutes(Math.max(0, Number(event.target.value) || 0))}\n />\n </label>\n {mode === \"manual\" ? (\n people.length === 0 ? (\n <p className=\"mx-meet__panel-note\">Nobody else is in the meeting yet.</p>\n ) : (\n <ul className=\"mx-meet__people\">\n {people.map((person) => (\n <li className=\"mx-meet__person\" key={person.identity}>\n <span className=\"mx-meet__person-name\">{person.displayName}</span>\n <select\n className=\"mx-meet__select mx-meet__select--compact\"\n aria-label={`Room for ${person.displayName}`}\n value={manual[person.identity] ?? -1}\n onChange={(event) => setManual({ ...manual, [person.identity]: Number(event.target.value) })}\n >\n <option value={-1}>Main room</option>\n {Array.from({ length: rooms }, (_, index) => (\n <option key={index} value={index}>\n Room {index + 1}\n </option>\n ))}\n </select>\n </li>\n ))}\n </ul>\n )\n ) : mode === \"auto\" ? (\n <ul className=\"mx-meet__breakout-rooms\">\n {assignment.map((members, index) => (\n <li key={index}>\n <strong>Room {index + 1}</strong>{\" \"}\n <span className=\"mx-meet__panel-note\">{members.length === 0 ? \"empty\" : members.map(nameOf).join(\", \")}</span>\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"mx-meet__panel-note\">Everyone picks a room from their own screen.</p>\n )}\n <Notice notice={notice} />\n <button\n type=\"button\"\n className=\"mx-meet__primary mx-meet__primary--small\"\n disabled={busy}\n onClick={() => {\n setBusy(true);\n void act({\n action: \"breakout_open\",\n rooms: assignment.map((identities, index) => ({ name: `Room ${index + 1}`, identities })),\n allowChoose: mode === \"choose\",\n minutes,\n }).then((result) => {\n setBusy(false);\n show(result);\n });\n }}\n >\n Open {rooms} room{rooms === 1 ? \"\" : \"s\"}\n </button>\n </div>\n );\n}\n\nfunction BreakoutRun({ plan }: { plan: MeetBreakoutPlan }): ReactNode {\n const { act, moveTo, isHost, breakoutRoomId, localIdentity } = useCollab();\n const snapshot = useMeetSnapshot();\n const now = useNow(plan.endsAt !== null);\n const remaining = breakoutRemainingMs(plan, now);\n const [broadcast, setBroadcast] = useState(\"\");\n const [notice, show] = useNotice();\n const nameOf = (identity: string) =>\n displayNameFor(snapshot?.names ?? {}, identity, snapshot?.participants.find((p) => p.identity === identity)?.displayName);\n const mine = assignedRoom(plan, localIdentity);\n\n const join = async (roomId: string | null) => {\n const said = await act({ action: \"breakout_join\", roomId });\n if (!said.ok) {\n show(said);\n return;\n }\n show(await moveTo(roomId));\n };\n\n return (\n <div className=\"mx-meet__collab-card\">\n <p className=\"mx-meet__panel-note\">\n {plan.rooms.length} room{plan.rooms.length === 1 ? \"\" : \"s\"} open\n {remaining !== null ? ` · ${clock(remaining)} left` : \"\"}\n {plan.allowChoose ? \" · people choose their room\" : \"\"}\n </p>\n <ul className=\"mx-meet__breakout-rooms\">\n {plan.rooms.map((room) => {\n const here = breakoutRoomId === room.id;\n const mayJoin = isHost || plan.allowChoose || mine?.id === room.id;\n return (\n <li key={room.id} className={here ? \"mx-meet__breakout-room mx-meet__breakout-room--here\" : \"mx-meet__breakout-room\"}>\n <div className=\"mx-meet__collab-head\">\n <strong>{room.name}</strong>\n {here ? (\n <span className=\"mx-meet__collab-state\">You are here</span>\n ) : mayJoin ? (\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => void join(room.id)}>\n Join\n </button>\n ) : null}\n </div>\n <span className=\"mx-meet__panel-note\">\n {room.identities.length === 0 ? \"Nobody assigned\" : room.identities.map(nameOf).join(\", \")}\n </span>\n {isHost && room.identities.length > 0 ? (\n <ul className=\"mx-meet__people\">\n {room.identities.map((identity) => (\n <li className=\"mx-meet__person\" key={identity}>\n <span className=\"mx-meet__person-name\">{nameOf(identity)}</span>\n <select\n className=\"mx-meet__select mx-meet__select--compact\"\n aria-label={`Move ${nameOf(identity)}`}\n value={room.id}\n onChange={(event) =>\n void act({\n action: \"breakout_move\",\n identity,\n roomId: event.target.value === \"\" ? null : event.target.value,\n }).then(show)\n }\n >\n <option value=\"\">Main room</option>\n {plan.rooms.map((option) => (\n <option key={option.id} value={option.id}>\n {option.name}\n </option>\n ))}\n </select>\n </li>\n ))}\n </ul>\n ) : null}\n </li>\n );\n })}\n </ul>\n {breakoutRoomId !== null ? (\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => void join(null)}>\n Return to the main room\n </button>\n ) : null}\n {isHost ? (\n <form\n className=\"mx-meet__composer-row\"\n onSubmit={(event) => {\n event.preventDefault();\n const text = broadcast.trim();\n if (text.length === 0) return;\n void act({ action: \"breakout_broadcast\", text }).then((result) => {\n show(result);\n if (result.ok) setBroadcast(\"\");\n });\n }}\n >\n <input\n className=\"mx-meet__input\"\n value={broadcast}\n maxLength={500}\n onChange={(event) => setBroadcast(event.target.value)}\n placeholder=\"Message every room\"\n aria-label=\"Message every room\"\n />\n <button type=\"submit\" className=\"mx-meet__send\" disabled={broadcast.trim().length === 0}>\n Send\n </button>\n </form>\n ) : null}\n <Notice notice={notice} />\n {isHost ? (\n <button\n type=\"button\"\n className=\"mx-meet__primary mx-meet__primary--small\"\n onClick={() => void act({ action: \"breakout_close\" }).then(show)}\n >\n Bring everyone back\n </button>\n ) : null}\n </div>\n );\n}\n\nexport function BreakoutsPanel({ onClose }: { onClose: () => void }): ReactNode {\n const { collab, isHost } = useCollab();\n const plan = collab?.breakout?.state === \"open\" ? collab.breakout : null;\n return (\n <PanelShell title=\"Breakout rooms\" onClose={onClose}>\n {collab?.error != null ? <p className=\"mx-meet__panel-note mx-meet__collab-error\">{collab.error}</p> : null}\n {plan !== null ? <BreakoutRun plan={plan} /> : isHost ? <BreakoutSetup /> : (\n <p className=\"mx-meet__panel-note\">Breakout rooms are closed.</p>\n )}\n </PanelShell>\n );\n}\n\n/**\n * WHAT A PERSON IN A MEETING WITH BREAKOUTS SEES WITHOUT OPENING ANYTHING: the room they\n * were assigned to (with Join), the room they are in and its countdown (with Return),\n * a host's broadcast — and the moves: a host moving them, and everyone coming back.\n * Also asks the server to close the plan when the countdown reaches zero (the server's\n * clock decides; no schedule).\n */\nexport function BreakoutBanner(): ReactNode {\n const host = useMeetHost();\n const { collab, act, moveTo, localIdentity, breakoutRoomId } = useCollab();\n const plan = collab?.breakout ?? null;\n const open = plan?.state === \"open\" ? plan : null;\n const now = useNow(open?.endsAt != null);\n const remaining = breakoutRemainingMs(open, now);\n const assigned = assignedRoom(open, localIdentity);\n const [dismissedBroadcast, setDismissedBroadcast] = useState<string | null>(null);\n const [failure, setFailure] = useState<string | null>(null);\n const expireAsked = useRef<{ id: string; at: number } | null>(null);\n\n // Moves the server asked for: a host moved me, or the rooms closed.\n const moveAt = collab?.moveRequest?.at ?? null;\n useEffect(() => {\n if (host === null) return;\n const request = takeMoveRequest(host.store);\n if (request === null) return;\n const here = host.store.snapshot().breakoutRoomId;\n if ((here ?? null) === request.roomId) return;\n void moveTo(request.roomId).then((result) => setFailure(result.ok ? null : result.message));\n }, [moveAt, host, moveTo]);\n\n // The timer ran out on this screen: ask; the server's clock decides. A screen whose\n // clock runs ahead is refused, so it asks again every few seconds, never only once.\n useEffect(() => {\n if (open === null || remaining === null || remaining > 0) return;\n const asked = expireAsked.current;\n if (asked !== null && asked.id === open.id && now - asked.at < BREAKOUT_EXPIRE_RETRY_MS) return;\n expireAsked.current = { id: open.id, at: now };\n void act({ action: \"breakout_expire\" });\n }, [open, remaining, act, now]);\n\n const broadcast = collab?.broadcast ?? null;\n const showBroadcast = broadcast !== null && broadcast.at !== dismissedBroadcast && open !== null;\n const room = open?.rooms.find((r) => r.id === breakoutRoomId) ?? null;\n\n if (open === null && failure === null) return null;\n return (\n <div className=\"mx-meet__breakout-banner\" role=\"status\">\n {failure !== null ? <span className=\"mx-meet__collab-error\">{failure}</span> : null}\n {room !== null ? (\n <span>\n <strong>{room.name}</strong>\n {remaining !== null ? ` · ${clock(remaining)} left` : \"\"}\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => void moveTo(null)}>\n Return to main room\n </button>\n </span>\n ) : assigned !== null ? (\n <span>\n You are assigned to <strong>{assigned.name}</strong>\n {remaining !== null ? ` · ${clock(remaining)} left` : \"\"}\n <button\n type=\"button\"\n className=\"mx-meet__primary mx-meet__primary--small\"\n onClick={() =>\n void act({ action: \"breakout_join\", roomId: assigned.id }).then(async (said) => {\n const result = said.ok ? await moveTo(assigned.id) : said;\n setFailure(result.ok ? null : result.message);\n })\n }\n >\n Join\n </button>\n </span>\n ) : open?.allowChoose ? (\n <span>Breakout rooms are open — pick one under Activities.</span>\n ) : null}\n {showBroadcast ? (\n <span className=\"mx-meet__breakout-broadcast\">\n <strong>{broadcast.by}:</strong> {broadcast.text}\n <button type=\"button\" className=\"mx-meet__link\" onClick={() => setDismissedBroadcast(broadcast.at)}>\n Dismiss\n </button>\n </span>\n ) : null}\n </div>\n );\n}\n\n// ── translated captions ────────────────────────────────────────────────────\n\n/** The Settings panel's captions section: translate captions into a language, or not. */\nexport function CaptionLanguageSettings(): ReactNode {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const collab = snapshot?.collab ?? null;\n const settings = collab?.settings ?? null;\n if (host === null || settings === null || !settings.captionTranslationEnabled) return null;\n const viewer =\n typeof navigator !== \"undefined\" && typeof navigator.language === \"string\" ? navigator.language.slice(0, 2) : null;\n const preferred = defaultCaptionLanguage(settings, viewer);\n const current = collab?.captionLanguage ?? \"\";\n const choices = CAPTION_LANGUAGES.some((l) => l.tag === preferred)\n ? CAPTION_LANGUAGES\n : [{ tag: preferred, name: preferred }, ...CAPTION_LANGUAGES];\n return (\n <section className=\"mx-meet__section\" aria-label=\"Captions\">\n <label className=\"mx-meet__field\">\n <span className=\"mx-meet__field-label\">Translate captions to</span>\n <select\n className=\"mx-meet__select\"\n value={current}\n onChange={(event) => {\n const next = event.target.value === \"\" ? null : event.target.value;\n setCaptionLanguage(host.store, next);\n }}\n >\n <option value=\"\">Off — as spoken</option>\n {current === \"\" ? <option value={preferred}>{choices.find((c) => c.tag === preferred)?.name ?? preferred} (yours)</option> : null}\n {choices\n .filter((choice) => current !== \"\" || choice.tag !== preferred)\n .map((choice) => (\n <option key={choice.tag} value={choice.tag}>\n {choice.name}\n </option>\n ))}\n </select>\n </label>\n {collab?.captionTranslationError != null ? (\n <p className=\"mx-meet__panel-note mx-meet__collab-error\">{collab.captionTranslationError}</p>\n ) : null}\n </section>\n );\n}\n","\"use client\";\n\n/**\n * INLINED SVGs — no icon-library dependency, ever (C19).\n *\n * The tap-target precedent: ~60 buttons, all inlined, a fraction of a percent\n * of bloat and zero version-skew risk. These are Lucide-shaped paths, adapted;\n * they inherit `currentColor` so the token contract, not an icon prop, decides\n * every color.\n */\n\nimport type { SVGProps } from \"react\";\n\ntype IconProps = SVGProps<SVGSVGElement>;\n\nfunction Icon({ children, ...props }: IconProps): React.ReactElement {\n return (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n focusable=\"false\"\n {...props}\n >\n {children}\n </svg>\n );\n}\n\nexport function MicIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z\" />\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\" />\n <path d=\"M12 19v3\" />\n </Icon>\n );\n}\n\nexport function MicOffIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M2 2l20 20\" />\n <path d=\"M9 9v3a3 3 0 0 0 5.12 2.12\" />\n <path d=\"M15 9.34V5a3 3 0 0 0-5.94-.6\" />\n <path d=\"M19 10v2a7 7 0 0 1-.11 1.23M5 10v2a7 7 0 0 0 11 5.66\" />\n <path d=\"M12 19v3\" />\n </Icon>\n );\n}\n\nexport function CameraIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"m23 7-7 5 7 5V7Z\" />\n <rect x=\"1\" y=\"5\" width=\"15\" height=\"14\" rx=\"2\" />\n </Icon>\n );\n}\n\nexport function CameraOffIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M2 2l20 20\" />\n <path d=\"M10.66 5H14a2 2 0 0 1 2 2v3.34l1 1L23 7v10\" />\n <path d=\"M16 16a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2\" />\n </Icon>\n );\n}\n\nexport function ScreenShareIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <rect x=\"2\" y=\"3\" width=\"20\" height=\"14\" rx=\"2\" />\n <path d=\"M8 21h8M12 17v4\" />\n <path d=\"M12 13V8m0 0-2.5 2.5M12 8l2.5 2.5\" />\n </Icon>\n );\n}\n\nexport function PhoneIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6A19.79 19.79 0 0 1 2.12 4.18 2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.9.34 1.85.57 2.81.7A2 2 0 0 1 22 16.92Z\" />\n </Icon>\n );\n}\n\n/**\n * \"Call end\" — the downward handset on the red leave button (the glyph Meet\n * users recognise). A FILLED shape, so it overrides the line-icon defaults.\n */\nexport function PhoneOffIcon(props: IconProps): React.ReactElement {\n return (\n <Icon fill=\"currentColor\" stroke=\"none\" {...props}>\n <path d=\"M12 9c-1.6 0-3.15.25-4.6.72v3.1c0 .39-.23.74-.56.9-.98.49-1.87 1.12-2.66 1.85-.18.18-.43.28-.7.28-.28 0-.53-.11-.71-.29L.29 13.08a.99.99 0 0 1-.29-.7c0-.28.11-.53.29-.71C3.34 8.78 7.46 7 12 7s8.66 1.78 11.71 4.67c.18.18.29.43.29.71 0 .28-.11.53-.29.71l-2.48 2.48c-.18.18-.43.29-.71.29-.27 0-.52-.11-.7-.28a11.27 11.27 0 0 0-2.67-1.85.996.996 0 0 1-.56-.9v-3.1C15.15 9.25 13.6 9 12 9Z\" />\n </Icon>\n );\n}\n\nexport function HandIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M18 11V6a2 2 0 0 0-4 0v5\" />\n <path d=\"M14 10V4a2 2 0 0 0-4 0v7\" />\n <path d=\"M10 10.5V6a2 2 0 0 0-4 0v8\" />\n <path d=\"M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2a8 8 0 0 1-8-8v-1a2 2 0 1 1 4 0\" />\n </Icon>\n );\n}\n\nexport function ChatIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2Z\" />\n </Icon>\n );\n}\n\nexport function PeopleIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\" />\n <circle cx=\"9\" cy=\"7\" r=\"4\" />\n <path d=\"M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75\" />\n </Icon>\n );\n}\n\nexport function RecordIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <circle cx=\"12\" cy=\"12\" r=\"8\" fill=\"currentColor\" stroke=\"none\" />\n </Icon>\n );\n}\n\nexport function SparklesIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M12 3l1.9 4.6L18.5 9.5l-4.6 1.9L12 16l-1.9-4.6L5.5 9.5l4.6-1.9L12 3Z\" />\n <path d=\"M19 15l.9 2.1 2.1.9-2.1.9-.9 2.1-.9-2.1-2.1-.9 2.1-.9L19 15Z\" />\n </Icon>\n );\n}\n\nexport function LockIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <rect x=\"4\" y=\"10\" width=\"16\" height=\"11\" rx=\"2\" />\n <path d=\"M8 10V7a4 4 0 0 1 8 0v3\" />\n </Icon>\n );\n}\n\nexport function LayoutIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" />\n <path d=\"M3 9h18M9 21V9\" />\n </Icon>\n );\n}\n\nexport function SettingsIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z\" />\n </Icon>\n );\n}\n\nexport function CheckIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M20 6 9 17l-5-5\" />\n </Icon>\n );\n}\n\nexport function CloseIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </Icon>\n );\n}\n\nexport function PinIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M12 17v5\" />\n <path d=\"M9 2h6l-1 6 3.5 3.5V14H6.5v-2.5L10 8 9 2Z\" />\n </Icon>\n );\n}\n\nexport function WifiLowIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M12 20h.01\" />\n <path d=\"M8.5 16.5a5 5 0 0 1 7 0\" />\n </Icon>\n );\n}\n\n/* Meet wave 2 */\n\nexport function SmileIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M8 14s1.5 2 4 2 4-2 4-2\" />\n <line x1=\"9\" x2=\"9.01\" y1=\"9\" y2=\"9\" />\n <line x1=\"15\" x2=\"15.01\" y1=\"9\" y2=\"9\" />\n </Icon>\n );\n}\n\nexport function MoreIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <circle cx=\"12\" cy=\"12\" r=\"1\" />\n <circle cx=\"19\" cy=\"12\" r=\"1\" />\n <circle cx=\"5\" cy=\"12\" r=\"1\" />\n </Icon>\n );\n}\n\nexport function SearchIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.3-4.3\" />\n </Icon>\n );\n}\n\nexport function KeyboardIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M10 8h.01M12 12h.01M14 8h.01M16 12h.01M18 8h.01M6 8h.01M7 16h10M8 12h.01\" />\n <rect width=\"20\" height=\"16\" x=\"2\" y=\"4\" rx=\"2\" />\n </Icon>\n );\n}\n\nexport function SpotlightIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M12 2 15 8.5 22 9.3 17 14 18.2 21 12 17.6 5.8 21 7 14 2 9.3 9 8.5Z\" />\n </Icon>\n );\n}\n\nexport function SignalIcon({ bars, ...props }: IconProps & { bars: 0 | 1 | 2 | 3 }): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M4 20v-2\" opacity={bars >= 1 ? 1 : 0.3} />\n <path d=\"M10 20v-6\" opacity={bars >= 2 ? 1 : 0.3} />\n <path d=\"M16 20V9\" opacity={bars >= 3 ? 1 : 0.3} />\n <path d=\"M22 20V4\" opacity={bars >= 3 ? 1 : 0.3} />\n </Icon>\n );\n}\n\nexport function SpeakerIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M11 5 6 9H2v6h4l5 4V5Z\" />\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\" />\n <path d=\"M19.07 4.93a10 10 0 0 1 0 14.14\" />\n </Icon>\n );\n}\n\nexport function FlagIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z\" />\n <line x1=\"4\" x2=\"4\" y1=\"22\" y2=\"15\" />\n </Icon>\n );\n}\n\nexport function PencilIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z\" />\n </Icon>\n );\n}\n\n/** Lucide `shapes` — the Activities menu (polls, Q&A, whiteboard, breakout rooms). */\nexport function ActivitiesIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M8.3 10a.7.7 0 0 1-.626-1.079L11.4 3a.7.7 0 0 1 1.198-.043L16.3 8.9a.7.7 0 0 1-.572 1.1Z\" />\n <rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\" />\n <circle cx=\"17.5\" cy=\"17.5\" r=\"3.5\" />\n </Icon>\n );\n}\n\n/** Lucide `chart-no-axes-column` — polls. */\nexport function PollIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <line x1=\"18\" x2=\"18\" y1=\"20\" y2=\"10\" />\n <line x1=\"12\" x2=\"12\" y1=\"20\" y2=\"4\" />\n <line x1=\"6\" x2=\"6\" y1=\"20\" y2=\"14\" />\n </Icon>\n );\n}\n\n/** Lucide `message-circle-question` — Q&A. */\nexport function QuestionIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M7.9 20A9 9 0 1 0 4 16.1L2 22Z\" />\n <path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\" />\n <path d=\"M12 17h.01\" />\n </Icon>\n );\n}\n\n/** Lucide `presentation` — the shared whiteboard. */\nexport function WhiteboardIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M2 3h20\" />\n <path d=\"M21 3v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V3\" />\n <path d=\"m7 21 5-5 5 5\" />\n </Icon>\n );\n}\n\n/** Lucide `layout-panel-left`-like split — breakout rooms. */\nexport function BreakoutIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <rect width=\"7\" height=\"18\" x=\"3\" y=\"3\" rx=\"1\" />\n <rect width=\"7\" height=\"7\" x=\"14\" y=\"3\" rx=\"1\" />\n <rect width=\"7\" height=\"7\" x=\"14\" y=\"14\" rx=\"1\" />\n </Icon>\n );\n}\n\n/** Lucide `arrow-big-up` — upvote. */\nexport function UpvoteIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M9 18v-6H5l7-7 7 7h-4v6H9z\" />\n </Icon>\n );\n}\n\n/** Lucide `eraser`. */\nexport function EraserIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21\" />\n <path d=\"M22 21H7\" />\n <path d=\"m5 11 9 9\" />\n </Icon>\n );\n}\n\n/** Lucide `highlighter`. */\nexport function HighlighterIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"m9 11-6 6v3h9l3-3\" />\n <path d=\"m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4\" />\n </Icon>\n );\n}\n\n/** Lucide `undo-2`. */\nexport function UndoIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M9 14 4 9l5-5\" />\n <path d=\"M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11\" />\n </Icon>\n );\n}\n"],"mappings":"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","names":["import_react","import_react","import_react","import_react","module","import_jsx_runtime","import_format","import_react","import_jsx_runtime","import_react","import_react","import_react","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","observe"]}
|
|
1
|
+
{"version":3,"sources":["../../src/skins/meet.ts","../../src/skins/meet/components.tsx","../../src/react/facade.ts","../../src/core/names.ts","../../src/core/errors.ts","../../src/core/tokens.ts","../../src/core/recording.ts","../../src/core/attendance.ts","../../src/core/types.ts","../../src/core/phase.ts","../../src/core/app-panel.ts","../../src/core/collab.ts","../../src/core/transcript.ts","../../src/core/store.ts","../../src/react/root.tsx","../../src/core/observation.ts","../../src/react/provider.tsx","../../src/core/meetings.ts","../../src/core/note-taker.ts","../../src/core/livekit-driver.ts","../../src/core/format.ts","../../src/core/layout.ts","../../src/react/app-panel.tsx","../../src/core/collab-state.ts","../../src/react/hooks.ts","../../src/core/record.ts","../../src/react/collab-hooks.ts","../../src/core/reactions.ts","../../src/skins/meet/collab.tsx","../../src/skins/meet/icons.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * `@ai-matrx/meet/skins/meet` — Meet skin version 0 (CORE-DESIGN §5.3, S1).\n *\n * Today's in-call UI, moved behind the headless boundary unchanged: it reads\n * only `src/react/facade.ts` (guard: `scripts/check-skin-imports.mjs`). These\n * are its PARTS — a host layout (matrx-frontend's Board) composes them, never\n * package internals. The meeting page itself renders `<MeetingSkinRoot>` from\n * `@ai-matrx/meet/react`, which chooses the viewer's skin.\n */\n\nexport {\n AiPanel,\n AttendanceNotice,\n Avatar,\n Captions,\n ChatPanel,\n ConnectionDot,\n ConsentNotice,\n ControlBar,\n ControlButton,\n HostMenu,\n LobbyPanel,\n MeetSkinRoom,\n MeetingOverlays,\n MenuItem,\n PanelShell,\n ParticipantTile,\n PeoplePanel,\n PersonMenu,\n Popover,\n PreJoin,\n QualityBars,\n ReactionOverlay,\n RecordingIndicator,\n SettingsPanel,\n ShortcutsSheet,\n Stage,\n Toast,\n} from \"./meet/components\";\n\nexport {\n ActivitiesControl,\n BreakoutBanner,\n BreakoutsPanel,\n CaptionLanguageSettings,\n PollsPanel,\n QaPanel,\n WhiteboardPanel,\n} from \"./meet/collab\";\n\nexport * from \"./meet/icons\";\n","\"use client\";\n\n/**\n * THE PRODUCT UI (R6/R7). Not a headless kernel with a note telling the host to\n * build the rest — the whole meeting surface ships here, because per THE\n * ALL-INCLUSIVE LAW a package that withholds its components withholds its\n * product.\n *\n * The bar: FaceTime and Zoom raised by Apple's design team. Concretely, and\n * every one of these is enforced by the stylesheet or the canary:\n *\n * - `dvh`, never `vh` — iOS Safari's `vh` includes the collapsing toolbar, so a\n * `100vh` stage puts the control bar under the browser chrome.\n * - Safe-area insets on the control bar, so the hang-up button is not under the\n * home indicator.\n * - 44px touch targets, and 16px input text (anything smaller zooms iOS Safari\n * on focus, which in a meeting looks like the app broke).\n * - Semantic tokens only. There is not one hardcoded color in the package.\n * - Real loading states and no dead ends: a control the actor cannot use is\n * ABSENT, never disabled-looking, and never a button that will be refused.\n */\n\nimport { useEffect, useLayoutEffect, useRef, useState, type ReactNode } from \"react\";\nimport { type CallMode, type CameraPreview, type CollabPanel, GUEST_RECORD_ACCESS_CHOICES, MEET_SHORTCUTS, type MediaEffects, type MeetHost, type MeetParticipant, type MeetingRecord, type ParticipantIdentity, type ParticipantRole, type ParticipantStanding, isLinkFollower, type RoomChatMessage, type RoomName, type UseHostControlsResult, type VideoEffect, avatarPaletteIndex, createCameraPreview, formatDurationMs, getInitials, gridColumns, guestRecordAccessExplanation, guestRecordAccessLabel, isNoteTaker, listenOnlyNotice, microphoneNoticeCode, participantAttributes, participantSummary, useAudioLevel, useConsentNotice, useDeviceSnapshot, useElapsed, useEndMeeting, useGuestRecordAccess, useHandQueue, useHostControls, useIsHost, useLobby, useWaiting, useLocalMedia, useMediaEffects, useMeetAi, useMeetHost, useMeetShortcuts, useMeetSnapshot, useModerationNotice, useReactions, useRecording, useRequiredMeetHost, useRole, useRoom, useStage, useTrack, videoEffectsSupported, useMeetCore, MEET_REACTIONS, useCaptionTranslation, useCollabPanelRequest, isObserverRole, useMeetAppPanel, captureHeldOf, isRefusedPhase } from \"../../react/facade\";\nimport { Alert, AlertDescription, Button, Checkbox, ErrorBox, Label, SegmentedControl, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@ai-matrx/design-system\";\nimport { ActivitiesControl, BreakoutBanner, BreakoutsPanel, CaptionLanguageSettings, PollsPanel, QaPanel, WhiteboardPanel } from \"./collab\";\nimport { CameraIcon, CameraOffIcon, ChatIcon, CheckIcon, CloseIcon, HandIcon, LockIcon, MicIcon, MicOffIcon, PeopleIcon, PhoneOffIcon, PinIcon, RecordIcon, ScreenShareIcon, SettingsIcon, SignalIcon, SmileIcon, SpeakerIcon, SpotlightIcon, SparklesIcon, KeyboardIcon, FlagIcon, PencilIcon, MoreIcon, SearchIcon, WifiLowIcon } from \"./icons\";\n\n/* ------------------------------------------------------------------ inert */\n\n/**\n * 🚨 A COMPONENT IN THIS FILE NEVER THROWS BECAUSE THE PROVIDER IS INERT.\n *\n * `<MeetProvider>` is deliberately inert until a Supabase client, an\n * organization and an identity are all real — which is every server render,\n * every signed-out visitor, and every moment before the active organization\n * hydrates. Through 0.2.0 the entry components called `useRequiredMeetHost()`\n * unconditionally, so the README's own three-line integration returned a 500 on\n * every route for those visitors, and the first real consumer had to guard OUR\n * components in ITS code (register MRI-D1, matrx-frontend 2026-09-08). That is\n * exactly the massage C22 forbids, and the defect was ours.\n *\n * The rule from 0.2.1 on: an entry component either renders its product or an\n * HONEST sentence — `<InertNotice>` below — and the ambient, always-mounted\n * ones (`<IncomingCallHost>`, `<CallButton>`, `<RecordingIndicator>`) render\n * NOTHING, because there is nothing truthful to say about a call surface on a\n * page with no runtime. Internal children (`<DevicePicker>`, `<HostMenu>`,\n * `<WaitingRoom>`) keep `useRequiredMeetHost()`: they are only ever rendered\n * BELOW one of these guards, so an absent host there is a package bug and must\n * be loud.\n */\nexport function InertNotice({ what }: { what: string }): ReactNode {\n return (\n <div className=\"mx-meet__empty\" role=\"status\">\n <p className=\"mx-meet__empty-title\">{what} is not ready yet</p>\n <p className=\"mx-meet__empty-body\">\n Calls and meetings need a signed-in identity and an active organization. This will\n appear as soon as they resolve.\n </p>\n </div>\n );\n}\n\n/* ------------------------------------------------------------------ parts */\n\nexport function Avatar({\n name,\n url,\n size = 40,\n}: {\n name: string;\n url?: string | null;\n size?: number;\n}): ReactNode {\n const tint = avatarPaletteIndex(name);\n if (url != null && url.length > 0) {\n return (\n <img\n className=\"mx-meet__avatar\"\n src={url}\n alt=\"\"\n width={size}\n height={size}\n style={{ width: size, height: size }}\n />\n );\n }\n return (\n <span\n className=\"mx-meet__avatar mx-meet__avatar--initials\"\n style={{\n width: size,\n height: size,\n fontSize: Math.round(size * 0.38),\n background: `var(--mx-meet-avatar-${tint})`,\n }}\n aria-hidden=\"true\"\n >\n {getInitials(name)}\n </span>\n );\n}\n\nexport function ControlButton({\n label,\n active = false,\n danger = false,\n onClick,\n children,\n}: {\n label: string;\n active?: boolean;\n danger?: boolean;\n onClick: () => void;\n children: ReactNode;\n}): ReactNode {\n return (\n <button\n type=\"button\"\n className={[\n \"mx-meet__control\",\n active ? \"mx-meet__control--active\" : \"\",\n danger ? \"mx-meet__control--danger\" : \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n // The accessible name is the STATE, not the icon: \"Unmute\" when muted.\n aria-label={label}\n title={label}\n onClick={onClick}\n >\n {children}\n </button>\n );\n}\n\n/**\n * The recording indicator. Rendered for EVERY participant, guests included —\n * there is no prop that hides it (R5). Consent is a property of the package.\n */\nexport function RecordingIndicator(): ReactNode {\n const { state, label } = useRecording();\n if (label === null) return null;\n return (\n <span\n className={`mx-meet__recording mx-meet__recording--${state}`}\n role=\"status\"\n aria-live=\"polite\"\n >\n <RecordIcon width={10} height={10} />\n {label}\n </span>\n );\n}\n\nexport function ConnectionDot({ quality }: { quality: string }): ReactNode {\n if (quality === \"excellent\" || quality === \"good\" || quality === \"unknown\") return null;\n // Only shown when it MEANS something. A permanent green dot teaches people\n // to ignore the indicator that matters.\n return (\n <span className={`mx-meet__quality mx-meet__quality--${quality}`} title={`Connection ${quality}`}>\n <WifiLowIcon width={14} height={14} />\n </span>\n );\n}\n\n/* ------------------------------------------------------------------- tile */\n\n/** Host, Co-host or Guest — and nothing at all for an ordinary participant. Guest is the\n * STANDING of a link follower, never a role (CORE-DESIGN §2.1). */\nfunction RoleBadge({\n role,\n standing,\n}: {\n role: ParticipantRole;\n standing?: ParticipantStanding | null | undefined;\n}): ReactNode {\n if (role === \"host\") return <span className=\"mx-meet__badge\">Host</span>;\n if (role === \"cohost\") return <span className=\"mx-meet__badge\">Co-host</span>;\n // MD-16: a silent observer is listed, and says so.\n if (isObserverRole(role)) return <span className=\"mx-meet__badge\" data-meet-observing=\"\">Observing</span>;\n if (isLinkFollower(standing)) return <span className=\"mx-meet__badge\">Guest</span>;\n return null;\n}\n\nexport function ParticipantTile({\n participant,\n featured = false,\n}: {\n participant: MeetParticipant;\n featured?: boolean;\n}): ReactNode {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const queue = useHandQueue();\n const [menu, setMenu] = useState(false);\n const videoRef = useTrack(\n participant.identity,\n participant.screenSharing ? \"screen\" : \"camera\",\n );\n const showVideo = participant.cameraEnabled || participant.screenSharing;\n const pinnedHere = snapshot?.pinned === participant.identity;\n const spotlit = snapshot?.spotlight === participant.identity;\n const handPosition = participant.handRaised\n ? queue.findIndex((entry) => entry.identity === participant.identity) + 1\n : 0;\n\n return (\n <div\n className={[\n \"mx-meet__tile\",\n featured ? \"mx-meet__tile--featured\" : \"\",\n participant.speaking ? \"mx-meet__tile--speaking\" : \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n data-identity={participant.identity}\n {...participantAttributes(participant)}\n >\n {showVideo ? (\n <video\n ref={videoRef}\n className=\"mx-meet__video\"\n autoPlay\n playsInline\n muted={participant.isLocal}\n />\n ) : (\n <div className=\"mx-meet__tile-placeholder\">\n <Avatar name={participant.displayName} url={participant.avatarUrl} size={featured ? 96 : 56} />\n </div>\n )}\n\n {participant.handRaised ? (\n <span className=\"mx-meet__tile-hand\" title={`Hand raised · ${ordinal(handPosition)} in line`}>\n <HandIcon width={14} height={14} />\n {handPosition > 0 ? handPosition : null}\n </span>\n ) : null}\n\n <div className=\"mx-meet__tile-bar\">\n <span className=\"mx-meet__tile-name\">\n {spotlit ? (\n <span title=\"Spotlighted for everyone\" className=\"mx-meet__tile-flag\">\n <SpotlightIcon width={12} height={12} />\n </span>\n ) : null}\n {participant.displayName}\n {participant.isLocal ? \" (you)\" : \"\"}\n {/* An agent is LABELLED. An invisible listener is the thing nobody\n should ever ship. */}\n {participant.isAgent ? <span className=\"mx-meet__badge\">AI</span> : null}\n {/* ONE role badge in this package (MRI-A11). */}\n <RoleBadge role={participant.role} standing={participant.standing} />\n </span>\n {!participant.micEnabled ? <MicOffIcon width={14} height={14} /> : null}\n <QualityBars quality={participant.quality} />\n </div>\n\n <div className={menu ? \"mx-meet__tile-actions mx-meet__tile-actions--open\" : \"mx-meet__tile-actions\"}>\n <ControlButton\n label={pinnedHere ? \"Unpin\" : `Pin ${participant.displayName} for me`}\n active={pinnedHere}\n onClick={() => host?.engine.pin(pinnedHere ? null : participant.identity)}\n >\n <PinIcon width={16} height={16} />\n </ControlButton>\n <span className=\"mx-meet__anchor\">\n <ControlButton\n label={`More for ${participant.displayName}`}\n active={menu}\n onClick={() => setMenu(!menu)}\n >\n <MoreIcon width={16} height={16} />\n </ControlButton>\n {menu ? (\n <PersonMenu participant={participant} onClose={() => setMenu(false)} placement=\"down\" />\n ) : null}\n </span>\n </div>\n </div>\n );\n}\n\nfunction ordinal(position: number): string {\n if (position <= 0) return \"\";\n const suffix =\n position % 100 >= 11 && position % 100 <= 13\n ? \"th\"\n : ({ 1: \"st\", 2: \"nd\", 3: \"rd\" } as Record<number, string>)[position % 10] ?? \"th\";\n return `${position}${suffix}`;\n}\n\n/**\n * CONNECTION QUALITY, always legible (Meet wave 2). Four bars, like every phone:\n * a stable good connection is quiet (the bars are there, faint), and a poor or\n * lost one is loud. `unknown` draws nothing — a guess is not a reading.\n */\nexport function QualityBars({ quality }: { quality: string }): ReactNode {\n if (quality === \"unknown\") return null;\n const bars = quality === \"excellent\" ? 3 : quality === \"good\" ? 2 : quality === \"poor\" ? 1 : 0;\n const label =\n quality === \"lost\" ? \"Connection lost\" : quality === \"poor\" ? \"Poor connection\" : `Connection ${quality}`;\n return (\n <span className={`mx-meet__signal mx-meet__signal--${quality}`} title={label} aria-label={label} role=\"img\">\n <SignalIcon bars={bars as 0 | 1 | 2 | 3} width={14} height={14} />\n </span>\n );\n}\n\n/* ------------------------------------------------------------------ stage */\n\nexport function Stage({ pageSize }: { pageSize?: number }): ReactNode {\n const stage = useStage(pageSize);\n const columns = gridColumns(stage.tiles.length);\n\n if (stage.feature === null && stage.tiles.length === 0) {\n return (\n <div className=\"mx-meet__stage mx-meet__stage--empty\">\n <p className=\"mx-meet__empty-title\">Waiting for others to join</p>\n <p className=\"mx-meet__empty-body\">\n Share the meeting link and people can join from anywhere — no account needed.\n </p>\n </div>\n );\n }\n\n return (\n <div className={`mx-meet__stage${stage.feature !== null ? \" mx-meet__stage--speaker\" : \"\"}`}>\n {stage.feature !== null ? (\n <ParticipantTile participant={stage.feature} featured />\n ) : null}\n <div\n className=\"mx-meet__grid\"\n style={{ \"--mx-meet-columns\": columns } as React.CSSProperties}\n >\n {stage.tiles.map((participant) => (\n <ParticipantTile key={participant.identity} participant={participant} />\n ))}\n </div>\n {stage.overflow > 0 ? (\n <p className=\"mx-meet__overflow\">\n +{stage.overflow} more {stage.overflow === 1 ? \"person\" : \"people\"} in this meeting\n </p>\n ) : null}\n </div>\n );\n}\n\n/* ------------------------------------------------------------- control bar */\n\ntype Panel = \"none\" | \"chat\" | \"people\" | \"ai\" | \"settings\" | \"polls\" | \"qa\" | \"breakouts\" | \"app\";\n\n\n/**\n * THE CONTROL BAR — one row, the same in the package's room and in any host\n * layout that composes it (the Board). Everything a person does in a meeting is\n * reachable from here or from the panels it opens, and every keyboard shortcut\n * is bound here, so a layout that renders this bar has all of it.\n */\nexport function ControlBar({ onLeave }: { onLeave?: () => void }): ReactNode {\n const room = useRoom();\n const recording = useRecording();\n const [panel, setPanel] = useState<Panel>(\"none\");\n const [chatTo, setChatTo] = useState<ParticipantIdentity | null>(null);\n const [popover, setPopover] = useState<\"none\" | \"reactions\" | \"more\">(\"none\");\n const [shortcuts, setShortcuts] = useState(false);\n const snapshot = useMeetSnapshot();\n const lobby = useLobby();\n const controls = useHostControls();\n const queue = useHandQueue();\n const micUnavailable = useLocalMedia().microphone.unavailable;\n const unread = useUnreadChat(panel === \"chat\");\n // Meet wave 5: the shared whiteboard takes the stage while open; the panels stay.\n const [whiteboard, setWhiteboard] = useState(false);\n // HR-360 wave 2 (MD-15): the meeting's app panel, when the host app registered its key.\n const app = useMeetAppPanel();\n // The panel is why this meeting exists: it opens once, on arrival; the person may close it.\n const appOpened = useRef(false);\n useEffect(() => {\n if (app === null || appOpened.current) return;\n appOpened.current = true;\n setPanel(\"app\");\n }, [app]);\n\n const toggle = (next: Panel) => setPanel(panel === next ? \"none\" : next);\n const openActivity = (activity: CollabPanel) => {\n if (activity === \"whiteboard\") setWhiteboard(true);\n else setPanel(activity);\n };\n // A poll launched (for everyone else): open it, the way Zoom puts a poll in front of people.\n useCollabPanelRequest((activity) => {\n if (!controls.isHost) openActivity(activity);\n });\n\n // A per-person menu anywhere in the layout asked to message someone privately.\n const privateChatAt = snapshot?.privateChatRequest?.at ?? null;\n const meetStore = useMeetHost()?.store ?? null;\n useEffect(() => {\n const request = meetStore?.snapshot().privateChatRequest ?? null;\n if (request === null) return;\n setChatTo(request.to);\n setPanel(\"chat\");\n meetStore?.patch({ privateChatRequest: null });\n }, [privateChatAt, meetStore]);\n\n useMeetShortcuts({\n mic: () => void room.toggleMicrophone(),\n camera: () => void room.toggleCamera(),\n hand: () => void room.toggleHand(),\n chat: () => toggle(\"chat\"),\n people: () => toggle(\"people\"),\n help: () => setShortcuts(true),\n });\n\n const hasSpotlight = snapshot?.spotlight !== null && snapshot?.spotlight !== undefined;\n\n return (\n <>\n <MeetingOverlays />\n {shortcuts ? <ShortcutsSheet onClose={() => setShortcuts(false)} /> : null}\n {panel === \"chat\" ? (\n <ChatPanel onClose={() => setPanel(\"none\")} to={chatTo} onTo={setChatTo} />\n ) : null}\n {panel === \"people\" ? (\n <PeoplePanel\n onClose={() => setPanel(\"none\")}\n onMessage={(identity) => {\n setChatTo(identity);\n setPanel(\"chat\");\n }}\n />\n ) : null}\n {panel === \"ai\" ? <AiPanel onClose={() => setPanel(\"none\")} /> : null}\n {panel === \"settings\" ? <SettingsPanel onClose={() => setPanel(\"none\")} /> : null}\n {panel === \"polls\" ? <PollsPanel onClose={() => setPanel(\"none\")} /> : null}\n {panel === \"qa\" ? <QaPanel onClose={() => setPanel(\"none\")} /> : null}\n {panel === \"breakouts\" ? <BreakoutsPanel onClose={() => setPanel(\"none\")} /> : null}\n {whiteboard ? <WhiteboardPanel onClose={() => setWhiteboard(false)} /> : null}\n {panel === \"app\" && app !== null ? (\n <PanelShell title={app.title} onClose={() => setPanel(\"none\")}>\n <div className=\"mx-meet__app-panel\" data-meet-app-panel={app.panel.key}>\n <app.Component\n recordId={app.panel.recordId}\n organizationId={app.organizationId}\n panelKey={app.panel.key}\n />\n </div>\n </PanelShell>\n ) : null}\n <BreakoutBanner />\n\n {/* The People panel says it inline, beside the row it acted on. */}\n {controls.notice !== null && panel !== \"people\" ? (\n <Toast text={controls.notice.text} failed={controls.notice.failed} onDone={controls.clearNotice} />\n ) : null}\n\n <div className=\"mx-meet__controls\">\n {/* The label states the REFUSAL when there is one, so the control is\n never a button that looks like it should work and doesn't. */}\n <ControlButton\n label={\n micUnavailable !== null\n ? `Unmute — ${micUnavailable.remedy}`\n : room.local?.micEnabled === true\n ? \"Mute (⌘D)\"\n : \"Unmute (⌘D)\"\n }\n active={room.local?.micEnabled === true}\n onClick={() => void room.toggleMicrophone()}\n >\n {room.local?.micEnabled === true ? <MicIcon /> : <MicOffIcon />}\n </ControlButton>\n\n <ControlButton\n label={room.local?.cameraEnabled === true ? \"Stop video (⌘E)\" : \"Start video (⌘E)\"}\n active={room.local?.cameraEnabled === true}\n onClick={() => void room.toggleCamera()}\n >\n {room.local?.cameraEnabled === true ? <CameraIcon /> : <CameraOffIcon />}\n </ControlButton>\n\n {room.canShareScreen || room.local?.screenSharing === true ? (\n <ControlButton\n label={room.local?.screenSharing === true ? \"Stop sharing\" : \"Share screen\"}\n active={room.local?.screenSharing === true}\n onClick={() => void room.toggleScreenShare()}\n >\n <ScreenShareIcon />\n </ControlButton>\n ) : null}\n\n <span className=\"mx-meet__anchor\">\n <ControlButton\n label=\"Send a reaction\"\n active={popover === \"reactions\"}\n onClick={() => setPopover(popover === \"reactions\" ? \"none\" : \"reactions\")}\n >\n <SmileIcon />\n </ControlButton>\n {popover === \"reactions\" ? (\n <Popover label=\"Reactions\" onClose={() => setPopover(\"none\")}>\n <div className=\"mx-meet__emoji-bar\">\n {MEET_REACTIONS.map((emoji) => (\n <button\n key={emoji}\n type=\"button\"\n className=\"mx-meet__emoji\"\n aria-label={`React ${emoji}`}\n onClick={() => void room.react(emoji)}\n >\n {emoji}\n </button>\n ))}\n </div>\n </Popover>\n ) : null}\n </span>\n\n <ControlButton\n label={room.local?.handRaised === true ? \"Lower hand (⌘⌥H)\" : \"Raise hand (⌘⌥H)\"}\n active={room.local?.handRaised === true}\n onClick={() => void room.toggleHand()}\n >\n <HandIcon />\n </ControlButton>\n\n {app !== null ? (\n <ControlButton label={app.title} active={panel === \"app\"} onClick={() => toggle(\"app\")}>\n <FlagIcon />\n </ControlButton>\n ) : null}\n\n <ControlButton label=\"Chat (⌘⌥C)\" active={panel === \"chat\"} onClick={() => toggle(\"chat\")}>\n <ChatIcon />\n {unread > 0 ? <span className=\"mx-meet__dot\" aria-hidden=\"true\" /> : null}\n </ControlButton>\n\n <ControlButton\n label={\n lobby.waiting.length > 0\n ? `People — ${lobby.waiting.length} waiting`\n : `People — ${(snapshot?.participants ?? []).length} in the meeting`\n }\n active={panel === \"people\"}\n onClick={() => toggle(\"people\")}\n >\n <PeopleIcon />\n {lobby.waiting.length > 0 || (controls.isHost && queue.length > 0) ? (\n <span className=\"mx-meet__dot\" aria-hidden=\"true\" />\n ) : null}\n </ControlButton>\n\n {/* Meet wave 5: polls, Q&A, whiteboard, breakout rooms — one door, Google\n Meet's Activities. Absent when the organization turned them all off. */}\n <ActivitiesControl\n active={whiteboard ? \"whiteboard\" : panel === \"polls\" || panel === \"qa\" || panel === \"breakouts\" ? panel : null}\n onOpen={openActivity}\n />\n\n {/* The AI control appears only when the meeting's AI is on — never a\n button that resolves to \"not configured\". */}\n <AiControl active={panel === \"ai\"} onToggle={() => toggle(\"ai\")} />\n\n {recording.canStart ? (\n <ControlButton label=\"Start recording\" onClick={() => void recording.start()}>\n <RecordIcon />\n </ControlButton>\n ) : null}\n {recording.canStop ? (\n <ControlButton label=\"Stop recording\" active onClick={() => void recording.stop()}>\n <RecordIcon />\n </ControlButton>\n ) : null}\n\n <span className=\"mx-meet__anchor\">\n <ControlButton\n label=\"More options\"\n active={popover === \"more\" || panel === \"settings\"}\n onClick={() => setPopover(popover === \"more\" ? \"none\" : \"more\")}\n >\n <MoreIcon />\n </ControlButton>\n {popover === \"more\" ? (\n <Popover label=\"More options\" onClose={() => setPopover(\"none\")}>\n <div className=\"mx-meet__menu\" role=\"menu\">\n <MenuItem\n onSelect={() => {\n setPopover(\"none\");\n setPanel(\"settings\");\n }}\n >\n <SettingsIcon width={16} height={16} /> Settings\n </MenuItem>\n <MenuItem\n onSelect={() => {\n setPopover(\"none\");\n setShortcuts(true);\n }}\n >\n <KeyboardIcon width={16} height={16} /> Keyboard shortcuts\n </MenuItem>\n {controls.isHost ? (\n <>\n <div className=\"mx-meet__menu-sep\" role=\"separator\" />\n <MenuItem\n onSelect={() => {\n setPopover(\"none\");\n void controls.run({ action: \"mute_all\" });\n }}\n >\n <MicOffIcon width={16} height={16} /> Mute everyone\n </MenuItem>\n <MenuItem\n onSelect={() => {\n setPopover(\"none\");\n void controls.run({ action: \"ask_all_unmute\" });\n }}\n >\n <MicIcon width={16} height={16} /> Ask everyone to unmute\n </MenuItem>\n {queue.length > 0 ? (\n <MenuItem\n onSelect={() => {\n setPopover(\"none\");\n void controls.run({ action: \"lower_all_hands\" });\n }}\n >\n <HandIcon width={16} height={16} /> Lower all hands ({queue.length})\n </MenuItem>\n ) : null}\n {hasSpotlight ? (\n <MenuItem\n onSelect={() => {\n setPopover(\"none\");\n void controls.run({ action: \"spotlight\", identity: null });\n }}\n >\n <SpotlightIcon width={16} height={16} /> Remove spotlight\n </MenuItem>\n ) : null}\n </>\n ) : null}\n </div>\n </Popover>\n ) : null}\n </span>\n\n <ControlButton\n label=\"Leave\"\n danger\n onClick={() => {\n void room.leave();\n onLeave?.();\n }}\n >\n <PhoneOffIcon />\n </ControlButton>\n </div>\n </>\n );\n}\n\n/** Messages that arrived while the chat panel was closed. */\nfunction useUnreadChat(open: boolean): number {\n const snapshot = useMeetSnapshot();\n const chat = snapshot?.chat ?? [];\n const local = snapshot?.localIdentity ?? null;\n const [seen, setSeen] = useState(0);\n useEffect(() => {\n if (open) setSeen(chat.length);\n }, [open, chat.length]);\n // The saved history read on join is not \"unread\" — only what arrives after it.\n const loaded = snapshot?.chatLoaded === true;\n const [baseline, setBaseline] = useState<number | null>(null);\n useEffect(() => {\n if (loaded && baseline === null) setBaseline(chat.length);\n }, [loaded, baseline, chat.length]);\n if (open) return 0;\n const from = Math.max(seen, baseline ?? chat.length);\n return chat.slice(from).filter((message) => message.identity !== local).length;\n}\n\n/**\n * A small floating surface anchored to its control. Escape and an outside click\n * close it. It is FIXED-positioned from the anchor's rectangle, because the\n * controls it hangs off live inside tiles that clip (`overflow: hidden`) and\n * inside a Board that pans — an absolutely positioned menu would be cut in half.\n */\nexport function Popover({\n label,\n onClose,\n children,\n placement = \"up\",\n}: {\n label: string;\n onClose: () => void;\n children: ReactNode;\n placement?: \"up\" | \"down\";\n}): ReactNode {\n const ref = useRef<HTMLDivElement | null>(null);\n // FIXED FROM THE FIRST FRAME. Hidden but in normal flow, the popover made its\n // anchor as tall as the whole menu, so the anchor's measured bottom was the\n // menu's bottom and the menu opened a full menu-height below its button\n // (host menu: Make host / Report / Remove off-screen, 2026-09-27).\n const [style, setStyle] = useState<React.CSSProperties>({\n position: \"fixed\",\n top: 0,\n left: 0,\n visibility: \"hidden\",\n });\n useLayoutEffect(() => {\n const element = ref.current;\n const anchor = element?.parentElement ?? null;\n if (element === null || anchor === null) return;\n const place = () => {\n const rect = anchor.getBoundingClientRect();\n const width = element.offsetWidth;\n const height = element.offsetHeight;\n const gutter = 8;\n const left = Math.min(\n Math.max(gutter, rect.left + rect.width / 2 - width / 2),\n window.innerWidth - width - gutter,\n );\n const fitsBelow = rect.bottom + gutter + height <= window.innerHeight;\n const fitsAbove = rect.top - gutter - height >= 0;\n if (!fitsBelow && !fitsAbove) {\n // NEITHER SIDE HOLDS IT (a tall host menu from a row mid-screen): pin it\n // inside the viewport and let it scroll, so the last items — Make host,\n // Remove from meeting — are never below the fold (2026-09-27).\n const top = Math.max(gutter, window.innerHeight - height - gutter);\n setStyle({\n position: \"fixed\",\n left,\n top,\n maxHeight: window.innerHeight - top - gutter,\n overflowY: \"auto\",\n });\n return;\n }\n const down = placement === \"down\" ? fitsBelow || !fitsAbove : !fitsAbove && fitsBelow;\n setStyle(\n down\n ? { position: \"fixed\", left, top: rect.bottom + gutter }\n : { position: \"fixed\", left, bottom: window.innerHeight - rect.top + gutter },\n );\n };\n place();\n // Re-placed when the layout moves (a lobby row arriving, a panel scrolling),\n // not only on window resize.\n const frame = requestAnimationFrame(place);\n const observer =\n typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(() => place());\n observer?.observe(document.body);\n observer?.observe(element);\n window.addEventListener(\"resize\", place);\n window.addEventListener(\"scroll\", place, true);\n return () => {\n cancelAnimationFrame(frame);\n observer?.disconnect();\n window.removeEventListener(\"resize\", place);\n window.removeEventListener(\"scroll\", place, true);\n };\n }, [placement]);\n useEffect(() => {\n const onKey = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") onClose();\n };\n const onPointer = (event: PointerEvent) => {\n const anchor = ref.current?.parentElement ?? null;\n if (anchor !== null && !anchor.contains(event.target as Node)) onClose();\n };\n window.addEventListener(\"keydown\", onKey);\n window.addEventListener(\"pointerdown\", onPointer);\n return () => {\n window.removeEventListener(\"keydown\", onKey);\n window.removeEventListener(\"pointerdown\", onPointer);\n };\n }, [onClose]);\n return (\n <div ref={ref} className=\"mx-meet__popover\" style={style} role=\"dialog\" aria-label={label}>\n {children}\n </div>\n );\n}\n\nexport function MenuItem({\n onSelect,\n danger = false,\n children,\n}: {\n onSelect: () => void;\n danger?: boolean;\n children: ReactNode;\n}): ReactNode {\n return (\n <button\n type=\"button\"\n role=\"menuitem\"\n className={danger ? \"mx-meet__menu-item mx-meet__menu-item--danger\" : \"mx-meet__menu-item\"}\n onClick={onSelect}\n >\n {children}\n </button>\n );\n}\n\n/** A transient sentence above the control bar. Stays 4 s, or until dismissed. */\nexport function Toast({ text, failed, onDone }: { text: string; failed: boolean; onDone: () => void }): ReactNode {\n useEffect(() => {\n const timer = setTimeout(onDone, failed ? 7000 : 4000);\n return () => clearTimeout(timer);\n }, [text, failed, onDone]);\n return (\n <div className={failed ? \"mx-meet__toast mx-meet__toast--failed\" : \"mx-meet__toast\"} role=\"status\">\n <span>{text}</span>\n <button type=\"button\" className=\"mx-meet__toast-close\" aria-label=\"Dismiss\" onClick={onDone}>\n <CloseIcon width={14} height={14} />\n </button>\n </div>\n );\n}\n\n/**\n * WHAT FLOATS OVER THE MEETING, in every layout (Meet wave 2): the reactions\n * everyone sends, and what a host just did to THIS person — \"the host is asking\n * you to unmute\" with the Unmute button right there, which is the whole point.\n */\nexport function MeetingOverlays(): ReactNode {\n const reactions = useReactions();\n const { notice, dismiss } = useModerationNotice();\n const room = useRoom();\n return (\n <>\n <ReactionOverlay reactions={reactions} />\n {notice !== null && notice.kind !== \"removed\" ? (\n // A host's request (\"the host is asking you to unmute\"), not an error:\n // an assertive status, so it is announced at once without being filed\n // as a failure (design-system error-box census).\n <div className=\"mx-meet__notice-bar\" role=\"status\" aria-live=\"assertive\">\n <span>{notice.text}</span>\n {notice.kind === \"ask-unmute\" && room.local?.micEnabled !== true ? (\n <button\n type=\"button\"\n className=\"mx-meet__primary mx-meet__primary--small\"\n onClick={() => {\n dismiss();\n void room.toggleMicrophone();\n }}\n >\n Unmute\n </button>\n ) : null}\n <button type=\"button\" className=\"mx-meet__toast-close\" aria-label=\"Dismiss\" onClick={dismiss}>\n <CloseIcon width={14} height={14} />\n </button>\n </div>\n ) : null}\n </>\n );\n}\n\n/** Every shortcut, on one sheet. Opened by Shift+? or from More options. */\nexport function ShortcutsSheet({ onClose }: { onClose: () => void }): ReactNode {\n useEffect(() => {\n const onKey = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") onClose();\n };\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [onClose]);\n return (\n <div className=\"mx-meet__sheet-backdrop\" onClick={onClose}>\n <div\n className=\"mx-meet__sheet\"\n role=\"dialog\"\n aria-label=\"Keyboard shortcuts\"\n onClick={(event) => event.stopPropagation()}\n >\n <header className=\"mx-meet__panel-head\">\n <h2 className=\"mx-meet__panel-title\">Keyboard shortcuts</h2>\n <ControlButton label=\"Close\" onClick={onClose}>\n <CloseIcon width={16} height={16} />\n </ControlButton>\n </header>\n <dl className=\"mx-meet__shortcuts\">\n {MEET_SHORTCUTS.map((shortcut) => (\n <div key={shortcut.id} className=\"mx-meet__shortcut\">\n <dt>{shortcut.label}</dt>\n <dd>\n <kbd className=\"mx-meet__kbd\">{shortcut.keys}</kbd>\n </dd>\n </div>\n ))}\n </dl>\n </div>\n </div>\n );\n}\n\n/**\n * The assistant control. It appears for every meeting whose AI is on, because\n * as of 0.3.0 the capability is the SERVER's: there is no host config that\n * could leave it unfilled, and a meeting with no Holder bound gets the server's\n * sentence in the panel rather than a control that never appears. A meeting\n * with `ai_enabled = false` gets NO control at all — absent, not disabled.\n */\nfunction AiControl({ active, onToggle }: { active: boolean; onToggle: () => void }): ReactNode {\n const snapshot = useMeetSnapshot();\n const meeting = snapshot?.meeting ?? null;\n if (meeting === null || !meeting.aiEnabled) return null;\n return (\n <ControlButton label=\"Meeting assistant\" active={active} onClick={onToggle}>\n <SparklesIcon />\n </ControlButton>\n );\n}\n\n/* ----------------------------------------------------------------- panels */\n\nexport function PanelShell({\n title,\n onClose,\n children,\n}: {\n title: string;\n onClose: () => void;\n children: ReactNode;\n}): ReactNode {\n // 🚨 A SIDE PANEL NEVER COVERS THE HEADER (verifier, 2026-09-29: People and\n // Chat hid End and Invite). The panel is positioned against the room root, so\n // it starts where the room's header ENDS — measured, because the header's\n // height is the host's (its controls, a wrapped title, the safe area).\n const ref = useRef<HTMLElement | null>(null);\n useLayoutEffect(() => {\n const panel = ref.current;\n const root = panel?.closest(\".mx-meet\");\n const header = root?.querySelector<HTMLElement>(\":scope > .mx-meet__header\") ?? null;\n if (!panel || !header) return undefined;\n const place = () => panel.style.setProperty(\"--mx-meet-panel-top\", `${header.offsetHeight}px`);\n place();\n if (typeof ResizeObserver === \"undefined\") return undefined;\n const observer = new ResizeObserver(place);\n observer.observe(header);\n return () => observer.disconnect();\n }, []);\n return (\n <aside ref={ref} className=\"mx-meet__panel\" aria-label={title}>\n <header className=\"mx-meet__panel-head\">\n <h2 className=\"mx-meet__panel-title\">{title}</h2>\n <ControlButton label=\"Close panel\" onClick={onClose}>\n <CloseIcon width={16} height={16} />\n </ControlButton>\n </header>\n <div className=\"mx-meet__panel-body\">{children}</div>\n </aside>\n );\n}\n\n/**\n * IN-MEETING CHAT (Meet wave 2). A message to everyone is SAVED with the\n * meeting — a late joiner scrolls up and reads it, and it is part of the record.\n * A private message goes to one person only and is never saved; the composer\n * says which of the two you are about to send.\n */\nexport function ChatPanel({\n onClose,\n to = null,\n onTo,\n}: {\n onClose: () => void;\n to?: ParticipantIdentity | null;\n onTo?: (identity: ParticipantIdentity | null) => void;\n}): ReactNode {\n const snapshot = useMeetSnapshot();\n const room = useRoom();\n const [draft, setDraft] = useState(\"\");\n const [recipient, setRecipient] = useState<ParticipantIdentity | null>(to);\n const listRef = useRef<HTMLUListElement | null>(null);\n const messages = snapshot?.chat ?? [];\n const local = snapshot?.localIdentity ?? null;\n const people = (snapshot?.participants ?? []).filter(\n (participant) => !participant.isLocal && !participant.isAgent,\n );\n const recipientName =\n recipient === null ? null : people.find((p) => p.identity === recipient)?.displayName ?? null;\n\n useEffect(() => setRecipient(to), [to]);\n useEffect(() => {\n const list = listRef.current;\n if (list !== null) list.scrollTop = list.scrollHeight;\n }, [messages.length]);\n\n const choose = (next: ParticipantIdentity | null) => {\n setRecipient(next);\n onTo?.(next);\n };\n\n return (\n <PanelShell title=\"Chat\" onClose={onClose}>\n <ul className=\"mx-meet__chat\" ref={listRef}>\n {snapshot?.chatHistoryError != null ? (\n <li className=\"mx-meet__panel-note\">{snapshot.chatHistoryError}</li>\n ) : null}\n {snapshot?.chatLoaded !== true && messages.length === 0 ? (\n <li className=\"mx-meet__panel-note\">Reading earlier messages…</li>\n ) : messages.length === 0 && snapshot?.chatHistoryError == null ? (\n <li className=\"mx-meet__panel-note\">\n No messages yet. Messages to everyone stay with the meeting, so people who join later\n see them.\n </li>\n ) : null}\n {messages.map((message) => (\n <ChatLine key={message.id} message={message} local={local} people={people} />\n ))}\n </ul>\n <form\n className=\"mx-meet__composer mx-meet__composer--chat\"\n onSubmit={(event) => {\n event.preventDefault();\n const text = draft;\n if (text.trim().length === 0) return;\n setDraft(\"\");\n if (recipient === null) void room.sendChat(text).catch(() => undefined);\n else void room.sendPrivateChat(recipient, text);\n }}\n >\n {people.length > 0 ? (\n <select\n className=\"mx-meet__select mx-meet__select--compact\"\n aria-label=\"Send to\"\n value={recipient ?? \"\"}\n onChange={(event) =>\n choose(event.target.value === \"\" ? null : (event.target.value as ParticipantIdentity))\n }\n >\n <option value=\"\">Everyone</option>\n {people.map((participant) => (\n <option key={participant.identity} value={participant.identity}>\n {participant.displayName} (private)\n </option>\n ))}\n </select>\n ) : null}\n <div className=\"mx-meet__composer-row\">\n <input\n className=\"mx-meet__input\"\n value={draft}\n onChange={(event) => setDraft(event.target.value)}\n placeholder={recipientName === null ? \"Message everyone\" : `Message ${recipientName} privately`}\n aria-label={recipientName === null ? \"Message everyone\" : `Message ${recipientName} privately`}\n maxLength={4000}\n />\n <button type=\"submit\" className=\"mx-meet__send\" disabled={draft.trim().length === 0}>\n Send\n </button>\n </div>\n {recipientName !== null ? (\n <p className=\"mx-meet__panel-note\">Only {recipientName} sees this. Private messages are not saved.</p>\n ) : null}\n </form>\n </PanelShell>\n );\n}\n\nfunction ChatLine({\n message,\n local,\n people,\n}: {\n message: RoomChatMessage;\n local: ParticipantIdentity | null;\n people: readonly MeetParticipant[];\n}): ReactNode {\n const mine = message.identity === local;\n const privateTo = message.to ?? null;\n const counterpart =\n privateTo === null\n ? null\n : mine\n ? people.find((p) => p.identity === privateTo)?.displayName ?? \"them\"\n : \"you\";\n return (\n <li\n className={[\n \"mx-meet__chat-line\",\n privateTo !== null ? \"mx-meet__chat-line--private\" : \"\",\n message.failed !== undefined ? \"mx-meet__chat-line--failed\" : \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n >\n <span className=\"mx-meet__chat-meta\">\n <strong>{mine ? \"You\" : message.displayName}</strong>\n {counterpart !== null ? <span className=\"mx-meet__chat-private\">to {counterpart} · private</span> : null}\n <time dateTime={message.at}>{clockOf(message.at)}</time>\n </span>\n <span className=\"mx-meet__chat-text\">{message.text}</span>\n {message.failed !== undefined ? (\n <span className=\"mx-meet__chat-failed\">{message.failed}</span>\n ) : privateTo === null && message.saved === false ? (\n <span className=\"mx-meet__chat-pending\">\n {message.id.startsWith(\"pending-\") ? \"Sending…\" : \"Not saved — people who join later will not see this\"}\n </span>\n ) : null}\n </li>\n );\n}\n\nexport function PeoplePanel({\n onClose,\n onMessage,\n}: {\n onClose: () => void;\n onMessage?: (identity: ParticipantIdentity) => void;\n}): ReactNode {\n const snapshot = useMeetSnapshot();\n const controls = useHostControls();\n const queue = useHandQueue();\n const [query, setQuery] = useState(\"\");\n const [menuFor, setMenuFor] = useState<ParticipantIdentity | null>(null);\n // THE LOCAL ROW IS BADGED FROM THE EFFECTIVE ROLE (MRI-A11).\n const myRole = useRole();\n const local = snapshot?.localIdentity ?? null;\n // MD-16: observers are listed — to everyone by default; to the hosts only when the\n // organization's `meet.observers_visible_to` knob says so (the server sends it).\n const observersHidden = snapshot?.grants.observersVisibleTo === \"hosts\" && !controls.isHost;\n const everyone = (snapshot?.participants ?? []).filter(\n (participant) =>\n !observersHidden || !isObserverRole(participant.role) || participant.identity === local,\n );\n const needle = query.trim().toLowerCase();\n const shown =\n needle.length === 0\n ? everyone\n : everyone.filter((participant) => participant.displayName.toLowerCase().includes(needle));\n\n return (\n <PanelShell title={`People (${everyone.length})`} onClose={onClose}>\n <LobbyPanel />\n\n {queue.length > 0 ? (\n <section className=\"mx-meet__section\" aria-label=\"Raised hands\">\n <header className=\"mx-meet__section-head\">\n <h3 className=\"mx-meet__section-title\">Raised hands · {queue.length}</h3>\n {controls.isHost ? (\n <button\n type=\"button\"\n className=\"mx-meet__link\"\n onClick={() => void controls.run({ action: \"lower_all_hands\" })}\n >\n Lower all\n </button>\n ) : null}\n </header>\n <ol className=\"mx-meet__queue\">\n {queue.map((participant) => (\n <li key={participant.identity} className=\"mx-meet__person\">\n <Avatar name={participant.displayName} url={participant.avatarUrl} size={28} />\n <span className=\"mx-meet__person-name\">\n {participant.displayName}\n {participant.identity === local ? <span className=\"mx-meet__you\"> (you)</span> : null}\n </span>\n {controls.isHost || participant.identity === local ? (\n <button\n type=\"button\"\n className=\"mx-meet__link\"\n onClick={() =>\n void controls.run({ action: \"lower_hand\", identity: participant.identity })\n }\n >\n Lower\n </button>\n ) : null}\n </li>\n ))}\n </ol>\n </section>\n ) : null}\n\n {controls.isHost && everyone.length > 1 ? (\n <div className=\"mx-meet__bulk\">\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => void controls.run({ action: \"mute_all\" })}>\n <MicOffIcon width={14} height={14} /> Mute all\n </button>\n <button\n type=\"button\"\n className=\"mx-meet__chip\"\n onClick={() => void controls.run({ action: \"ask_all_unmute\" })}\n >\n <MicIcon width={14} height={14} /> Ask all to unmute\n </button>\n </div>\n ) : null}\n\n {everyone.length > 4 ? (\n <label className=\"mx-meet__search\">\n <SearchIcon width={16} height={16} />\n <input\n className=\"mx-meet__input mx-meet__input--bare\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n placeholder=\"Search people\"\n aria-label=\"Search people\"\n />\n </label>\n ) : null}\n\n {controls.notice !== null ? (\n <p className={controls.notice.failed ? \"mx-meet__error\" : \"mx-meet__notice\"} role=\"status\">\n {controls.notice.text}\n </p>\n ) : null}\n\n <ul className=\"mx-meet__people\">\n {shown.map((participant) => (\n <li key={participant.identity} className=\"mx-meet__person\">\n <Avatar name={participant.displayName} url={participant.avatarUrl} size={32} />\n <span className=\"mx-meet__person-name\">\n {participant.displayName}\n {participant.identity === local ? <span className=\"mx-meet__you\"> (you)</span> : null}\n <RoleBadge\n role={participant.identity === local ? myRole : participant.role}\n standing={participant.standing}\n />\n {isNoteTaker(participant) ? (\n <span className=\"mx-meet__badge\">Meeting assistant</span>\n ) : null}\n {snapshot?.spotlight === participant.identity ? (\n <span className=\"mx-meet__badge\">Spotlight</span>\n ) : null}\n </span>\n {participant.handRaised ? <HandIcon width={14} height={14} /> : null}\n {!participant.micEnabled && !isNoteTaker(participant) ? (\n <MicOffIcon width={14} height={14} />\n ) : null}\n <QualityBars quality={participant.quality} />\n <span className=\"mx-meet__anchor\">\n <ControlButton\n label={`More for ${participant.displayName}`}\n active={menuFor === participant.identity}\n onClick={() => setMenuFor(menuFor === participant.identity ? null : participant.identity)}\n >\n <MoreIcon width={16} height={16} />\n </ControlButton>\n {menuFor === participant.identity ? (\n <PersonMenu\n participant={participant}\n placement=\"down\"\n onClose={() => setMenuFor(null)}\n {...(onMessage !== undefined ? { onMessage } : {})}\n />\n ) : null}\n </span>\n </li>\n ))}\n {shown.length === 0 ? <li className=\"mx-meet__panel-note\">Nobody matches “{query}”.</li> : null}\n </ul>\n\n {/* MRF-1: a host decides what the guests in this list will be able to read\n afterwards, WHILE the meeting is running — which is when they know. */}\n {snapshot?.meeting !== null && snapshot?.meeting !== undefined ? (\n <GuestRecordAccessControl meeting={snapshot.meeting} />\n ) : null}\n </PanelShell>\n );\n}\n\n/**\n * EVERYTHING YOU CAN DO ABOUT ONE PERSON (Meet wave 2), in one menu — on their\n * tile and on their People row. An item appears only when it would be honoured:\n * the rank ladder decides moderation (a co-host never sees \"Remove\" on the host),\n * only the host sees \"Make host\", and nobody sees a control that the server\n * would refuse. Removing someone and handing the meeting over NAME THEIR\n * CONSEQUENCE first, inline — never a browser dialog.\n */\nexport function PersonMenu({\n participant,\n onClose,\n onMessage,\n placement = \"down\",\n}: {\n participant: MeetParticipant;\n onClose: () => void;\n onMessage?: (identity: ParticipantIdentity) => void;\n placement?: \"up\" | \"down\";\n}): ReactNode {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const controls = useHostControls();\n const [mode, setMode] = useState<\"menu\" | \"rename\" | \"report\" | \"remove\" | \"transfer\">(\"menu\");\n const [text, setText] = useState(\"\");\n const self = participant.identity === snapshot?.localIdentity;\n const moderate = controls.canModerate(participant);\n const pinned = snapshot?.pinned === participant.identity;\n const spotlit = snapshot?.spotlight === participant.identity;\n const hasAccount = participant.userId !== null;\n // A refused act keeps its pane open WITH the server's sentence and live\n // buttons — never a confirm that silently stays up (verifier, 2026-09-27).\n const [refused, setRefused] = useState(false);\n const act = (request: Parameters<UseHostControlsResult[\"run\"]>[0]) => {\n setRefused(false);\n void controls.run(request).then((result) => {\n if (result !== null) onClose();\n else setRefused(true);\n });\n };\n const refusal =\n refused && controls.notice?.failed === true ? (\n <ErrorBox as=\"p\" className=\"mx-meet__error\" message={controls.notice.text} operation=\"Change this participant\" origin=\"meet/components\">\n {controls.notice.text}\n </ErrorBox>\n ) : null;\n\n if (mode === \"rename\" || mode === \"report\") {\n const renaming = mode === \"rename\";\n return (\n <Popover label={renaming ? \"Rename\" : \"Report\"} onClose={onClose} placement={placement}>\n <form\n className=\"mx-meet__menu-form\"\n onSubmit={(event) => {\n event.preventDefault();\n if (text.trim().length === 0) return;\n act(\n renaming\n ? { action: \"rename\", identity: participant.identity, name: text.trim() }\n : { action: \"report\", identity: participant.identity, reason: text.trim() },\n );\n }}\n >\n <label className=\"mx-meet__field\">\n <span className=\"mx-meet__field-label\">\n {renaming ? \"Name shown in this meeting\" : `What did ${participant.displayName} do?`}\n </span>\n {renaming ? (\n <input\n className=\"mx-meet__input\"\n autoFocus\n maxLength={80}\n value={text}\n onChange={(event) => setText(event.target.value)}\n />\n ) : (\n <textarea\n className=\"mx-meet__input\"\n autoFocus\n rows={3}\n maxLength={1000}\n value={text}\n onChange={(event) => setText(event.target.value)}\n />\n )}\n </label>\n {refusal}\n <div className=\"mx-meet__menu-actions\">\n <button type=\"button\" className=\"mx-meet__link\" onClick={() => setMode(\"menu\")}>\n Back\n </button>\n <button type=\"submit\" className=\"mx-meet__send\" disabled={text.trim().length === 0 || controls.busy}>\n {renaming ? \"Rename\" : \"Report\"}\n </button>\n </div>\n </form>\n </Popover>\n );\n }\n\n if (mode === \"remove\" || mode === \"transfer\") {\n const removing = mode === \"remove\";\n return (\n <Popover label={removing ? \"Remove\" : \"Make host\"} onClose={onClose} placement={placement}>\n <div className=\"mx-meet__menu-form\">\n <p className=\"mx-meet__confirm-text\">\n {removing\n ? `Remove ${participant.displayName}? They leave immediately and cannot rejoin this meeting.`\n : `Make ${participant.displayName} the host? They get every host control and own this meeting. You stay on as a co-host.`}\n </p>\n {refusal}\n <div className=\"mx-meet__menu-actions\">\n <button type=\"button\" className=\"mx-meet__link\" onClick={() => setMode(\"menu\")}>\n Cancel\n </button>\n <button\n type=\"button\"\n className={removing ? \"mx-meet__send mx-meet__send--danger\" : \"mx-meet__send\"}\n disabled={controls.busy}\n onClick={() =>\n act({ action: removing ? \"remove\" : \"transfer_host\", identity: participant.identity })\n }\n >\n {removing ? \"Remove\" : \"Make host\"}\n </button>\n </div>\n </div>\n </Popover>\n );\n }\n\n return (\n <Popover label={`Options for ${participant.displayName}`} onClose={onClose} placement={placement}>\n <div className=\"mx-meet__menu\" role=\"menu\">\n <MenuItem\n onSelect={() => {\n host?.engine.pin(pinned ? null : participant.identity);\n onClose();\n }}\n >\n <PinIcon width={16} height={16} /> {pinned ? \"Unpin\" : \"Pin for me\"}\n </MenuItem>\n {!self && !participant.isAgent && (onMessage !== undefined || host !== null) ? (\n <MenuItem\n onSelect={() => {\n if (onMessage !== undefined) onMessage(participant.identity);\n else\n host?.store.patch({\n privateChatRequest: { to: participant.identity, at: Date.now() },\n });\n onClose();\n }}\n >\n <ChatIcon width={16} height={16} /> Message privately\n </MenuItem>\n ) : null}\n {controls.isHost ? (\n <MenuItem onSelect={() => act({ action: \"spotlight\", identity: spotlit ? null : participant.identity })}>\n <SpotlightIcon width={16} height={16} /> {spotlit ? \"Remove spotlight\" : \"Spotlight for everyone\"}\n </MenuItem>\n ) : null}\n {moderate ? (\n <>\n <div className=\"mx-meet__menu-sep\" role=\"separator\" />\n {participant.micEnabled ? (\n <MenuItem onSelect={() => act({ action: \"mute\", identity: participant.identity })}>\n <MicOffIcon width={16} height={16} /> Mute\n </MenuItem>\n ) : !participant.isAgent ? (\n <MenuItem onSelect={() => act({ action: \"ask_unmute\", identity: participant.identity })}>\n <MicIcon width={16} height={16} /> Ask to unmute\n </MenuItem>\n ) : null}\n {participant.cameraEnabled ? (\n <MenuItem onSelect={() => act({ action: \"stop_video\", identity: participant.identity })}>\n <CameraOffIcon width={16} height={16} /> Stop video\n </MenuItem>\n ) : null}\n {participant.screenSharing ? (\n <MenuItem onSelect={() => act({ action: \"stop_screen\", identity: participant.identity })}>\n <ScreenShareIcon width={16} height={16} /> Stop screen share\n </MenuItem>\n ) : null}\n {participant.handRaised ? (\n <MenuItem onSelect={() => act({ action: \"lower_hand\", identity: participant.identity })}>\n <HandIcon width={16} height={16} /> Lower hand\n </MenuItem>\n ) : null}\n {hasAccount && !participant.isAgent && participant.role !== \"cohost\" ? (\n <MenuItem onSelect={() => act({ action: \"make_cohost\", identity: participant.identity })}>\n <PeopleIcon width={16} height={16} /> Make co-host\n </MenuItem>\n ) : null}\n {!participant.isAgent ? (\n <MenuItem\n onSelect={() => {\n setText(participant.displayName);\n setMode(\"rename\");\n }}\n >\n <PencilIcon width={16} height={16} /> Rename\n </MenuItem>\n ) : null}\n </>\n ) : null}\n {controls.isOwner && participant.role === \"cohost\" ? (\n <MenuItem onSelect={() => act({ action: \"revoke_cohost\", identity: participant.identity })}>\n <PeopleIcon width={16} height={16} /> Remove co-host\n </MenuItem>\n ) : null}\n {controls.isOwner && !self && hasAccount && !participant.isAgent ? (\n <MenuItem onSelect={() => setMode(\"transfer\")}>\n <SpotlightIcon width={16} height={16} /> Make host\n </MenuItem>\n ) : null}\n {self ? (\n <MenuItem\n onSelect={() => {\n setText(participant.displayName);\n setMode(\"rename\");\n }}\n >\n <PencilIcon width={16} height={16} /> Rename yourself\n </MenuItem>\n ) : null}\n {!self && !participant.isAgent ? (\n <MenuItem\n onSelect={() => {\n setText(\"\");\n setMode(\"report\");\n }}\n >\n <FlagIcon width={16} height={16} /> Report\n </MenuItem>\n ) : null}\n {moderate && !participant.isAgent ? (\n <MenuItem danger onSelect={() => setMode(\"remove\")}>\n <CloseIcon width={16} height={16} /> Remove from meeting\n </MenuItem>\n ) : null}\n </div>\n </Popover>\n );\n}\n\n/* --------------------------------------------------------------- settings */\n\n/**\n * AUDIO, VIDEO AND EFFECTS (Meet wave 2) — the devices in use, a live\n * microphone level, a speaker test, noise suppression and the camera\n * background. Every change applies to the live meeting at once and is\n * remembered in this browser.\n */\nexport function SettingsPanel({ onClose }: { onClose: () => void }): ReactNode {\n const host = useRequiredMeetHost();\n return (\n <PanelShell title=\"Settings\" onClose={onClose}>\n <DeviceSettings\n onPick={(kind, deviceId) => {\n host.devices.select(kind, deviceId);\n void host.engine.switchDevice(kind, deviceId);\n }}\n />\n <EffectsSettings />\n <CaptionLanguageSettings />\n </PanelShell>\n );\n}\n\n/** Microphone (with a live level), speaker (with a test), camera. */\nfunction DeviceSettings({\n onPick,\n}: {\n onPick: (kind: \"audioinput\" | \"videoinput\" | \"audiooutput\", deviceId: string) => void;\n}): ReactNode {\n const host = useRequiredMeetHost();\n const devices = useDeviceSnapshot();\n const microphone = devices?.selected.audioinput ?? host.devices.resolve(\"audioinput\");\n const speaker = devices?.selected.audiooutput ?? host.devices.resolve(\"audiooutput\");\n const stream = useMicrophoneStream(microphone);\n const level = useAudioLevel(stream);\n const [testing, setTesting] = useState(false);\n\n useEffect(() => {\n void host.devices.refresh();\n }, [host]);\n\n return (\n <section className=\"mx-meet__section\" aria-label=\"Devices\">\n <DevicePicker kind=\"audioinput\" label=\"Microphone\" onPick={onPick} />\n <div className=\"mx-meet__meter\" role=\"meter\" aria-label=\"Microphone level\" aria-valuemin={0} aria-valuemax={100} aria-valuenow={Math.round(level * 100)}>\n <span style={{ width: `${Math.min(100, Math.round(level * 160))}%` }} />\n </div>\n <DevicePicker kind=\"audiooutput\" label=\"Speaker\" onPick={onPick} />\n <button\n type=\"button\"\n className=\"mx-meet__chip\"\n disabled={testing}\n onClick={() => {\n setTesting(true);\n void playTestSound(speaker).finally(() => setTesting(false));\n }}\n >\n <SpeakerIcon width={14} height={14} /> {testing ? \"Playing…\" : \"Test speaker\"}\n </button>\n <DevicePicker kind=\"videoinput\" label=\"Camera\" onPick={onPick} />\n </section>\n );\n}\n\n/** Noise suppression and the camera background. */\nfunction EffectsSettings({ preview }: { preview?: CameraPreview | null }): ReactNode {\n const effects = useMediaEffects();\n const [error, setError] = useState<string | null>(null);\n const fileRef = useRef<HTMLInputElement | null>(null);\n // Pre-join there is no room driver yet, so the browser itself is asked.\n const supported = effects.backgroundsSupported || preview != null || videoEffectsSupported();\n\n const apply = (next: Partial<MediaEffects>) => {\n setError(null);\n void effects.set(next);\n if (preview != null) {\n const merged = { ...effects.effects, ...next };\n void preview.setEffect(toVideoEffect(merged)).catch(() =>\n setError(\"This browser could not run the background effect.\"),\n );\n }\n };\n\n return (\n <section className=\"mx-meet__section\" aria-label=\"Effects\">\n {/* Design-system controls, never native ones restyled here: the lobby\n and settings must read as the same app as every other page. */}\n <div className=\"mx-meet__check\">\n <Checkbox\n id=\"mx-meet-noise\"\n checked={effects.effects.noiseSuppression}\n onCheckedChange={(checked) => apply({ noiseSuppression: checked === true })}\n />\n <div className=\"mx-meet__check-text\">\n <Label htmlFor=\"mx-meet-noise\">Noise suppression</Label>\n <span className=\"mx-meet__hint\">Filters keyboards, fans and background chatter.</span>\n </div>\n </div>\n {supported ? (\n <div className=\"mx-meet__field\">\n <Label>Background</Label>\n <SegmentedControl\n fullWidth\n value={effects.effects.background}\n onValueChange={(kind) => {\n if (kind === \"image\") {\n fileRef.current?.click();\n return;\n }\n apply({ background: kind as MediaEffects[\"background\"] });\n }}\n data={[\n { value: \"none\", label: \"None\" },\n { value: \"blur\", label: \"Blur\" },\n { value: \"image\", label: \"Picture…\" },\n ]}\n />\n <input\n ref={fileRef}\n type=\"file\"\n accept=\"image/*\"\n hidden\n onChange={(event) => {\n const file = event.target.files?.[0];\n event.target.value = \"\";\n if (file === undefined) return;\n void shrinkImage(file)\n .then((url) => apply({ background: \"image\", backgroundImage: url }))\n .catch(() => setError(\"That picture could not be read. Try a JPEG or PNG.\"));\n }}\n />\n {error !== null ? <p className=\"mx-meet__error\">{error}</p> : null}\n </div>\n ) : null}\n </section>\n );\n}\n\nfunction toVideoEffect(effects: MediaEffects): VideoEffect {\n if (effects.background === \"blur\") return { kind: \"blur\" };\n if (effects.background === \"image\" && effects.backgroundImage !== null) {\n return { kind: \"image\", imageUrl: effects.backgroundImage };\n }\n return { kind: \"none\" };\n}\n\n/**\n * A picked background, scaled to 1280 px wide as a JPEG data URL — small enough\n * to remember in this browser (a `blob:` URL dies with the tab), large enough\n * for a 720p camera.\n */\nasync function shrinkImage(file: File): Promise<string> {\n const url = URL.createObjectURL(file);\n try {\n const image = new Image();\n image.src = url;\n await image.decode();\n const scale = Math.min(1, 1280 / image.naturalWidth);\n const canvas = document.createElement(\"canvas\");\n canvas.width = Math.round(image.naturalWidth * scale);\n canvas.height = Math.round(image.naturalHeight * scale);\n const context = canvas.getContext(\"2d\");\n if (context === null) throw new Error(\"no 2d context\");\n context.drawImage(image, 0, 0, canvas.width, canvas.height);\n return canvas.toDataURL(\"image/jpeg\", 0.85);\n } finally {\n URL.revokeObjectURL(url);\n }\n}\n\n/** A short two-tone chime through the chosen speaker. */\nasync function playTestSound(sinkId: string | null): Promise<void> {\n const Context = (globalThis as { AudioContext?: typeof AudioContext }).AudioContext;\n if (Context === undefined) return;\n const context = new Context();\n const destination = context.createMediaStreamDestination();\n const gain = context.createGain();\n gain.gain.value = 0.2;\n gain.connect(destination);\n for (const [frequency, start] of [\n [660, 0],\n [880, 0.25],\n ] as const) {\n const oscillator = context.createOscillator();\n oscillator.frequency.value = frequency;\n oscillator.connect(gain);\n oscillator.start(context.currentTime + start);\n oscillator.stop(context.currentTime + start + 0.22);\n }\n const audio = new Audio();\n audio.srcObject = destination.stream;\n const sinkable = audio as HTMLAudioElement & { setSinkId?: (id: string) => Promise<void> };\n if (sinkId !== null && sinkId !== \"default\" && sinkable.setSinkId !== undefined) {\n await sinkable.setSinkId(sinkId).catch(() => undefined);\n }\n await audio.play().catch(() => undefined);\n await new Promise((resolve) => setTimeout(resolve, 700));\n audio.pause();\n await context.close();\n}\n\n/** A microphone stream for the level meter only — closed with the component. */\nfunction useMicrophoneStream(deviceId: string | null | \"off\"): MediaStream | null {\n const [stream, setStream] = useState<MediaStream | null>(null);\n useEffect(() => {\n if (deviceId === \"off\") return undefined;\n let live = true;\n let opened: MediaStream | null = null;\n const media = (globalThis as { navigator?: Navigator }).navigator?.mediaDevices;\n if (media === undefined) return undefined;\n void media\n .getUserMedia({ audio: deviceId === null ? true : { deviceId: { exact: deviceId } } })\n .then((next) => {\n if (!live) {\n next.getTracks().forEach((track) => track.stop());\n return;\n }\n opened = next;\n setStream(next);\n })\n .catch(() => setStream(null));\n return () => {\n live = false;\n opened?.getTracks().forEach((track) => track.stop());\n setStream(null);\n };\n }, [deviceId]);\n return stream;\n}\n\n/** The waiting room. Rendered for hosts only — a participant sees nothing. */\nexport function LobbyPanel(): ReactNode {\n const lobby = useLobby();\n if (!lobby.canAdmit || lobby.waiting.length === 0) return null;\n return (\n <section className=\"mx-meet__lobby\" aria-label=\"Waiting room\">\n <header className=\"mx-meet__lobby-head\">\n <h3 className=\"mx-meet__lobby-title\">\n {lobby.waiting.length === 1\n ? \"1 person is waiting\"\n : `${lobby.waiting.length} people are waiting`}\n </h3>\n <button type=\"button\" className=\"mx-meet__link\" onClick={() => void lobby.admitAll()}>\n Admit all\n </button>\n </header>\n <ul className=\"mx-meet__lobby-list\">\n {lobby.waiting.map((entry) => (\n <li\n key={entry.identity}\n className=\"mx-meet__lobby-row\"\n data-meet-lobby-entry=\"\"\n data-meet-participant-name={entry.displayName}\n >\n <Avatar name={entry.displayName} url={entry.avatarUrl} size={32} />\n <span className=\"mx-meet__person-name\">\n {entry.displayName}\n {entry.userId === null ? <span className=\"mx-meet__badge\">Guest</span> : null}\n </span>\n <ControlButton label={`Admit ${entry.displayName}`} onClick={() => void lobby.admit(entry)}>\n <CheckIcon width={16} height={16} />\n </ControlButton>\n <ControlButton\n label={`Deny ${entry.displayName}`}\n danger\n onClick={() => void lobby.deny(entry)}\n >\n <CloseIcon width={16} height={16} />\n </ControlButton>\n </li>\n ))}\n </ul>\n </section>\n );\n}\n\n/**\n * THE NOTE-TAKER'S STATE, on screen, with the server's own words.\n *\n * 🚨 NEVER A SPINNER. Every one of the five states (D9/MRI-A2) has a sentence,\n * and a refused dispatch shows the SERVER's `reason` and `remedy` verbatim —\n * \"no worker is registered under matrx-note-taker\", and what to do about it.\n * Through 0.2.1 a failed dispatch threw and the panel showed a remedy with no\n * state behind it; a person could not tell \"it refused\" from \"it is coming\".\n */\nfunction NoteTakerRow(): ReactNode {\n const ai = useMeetAi();\n const status = ai.noteTaker;\n const duplicateError =\n status.refusal !== null &&\n ai.error !== null &&\n status.refusal.reason === ai.error.message &&\n status.refusal.remedy === ai.error.remedy;\n return (\n <section className=\"mx-meet__note-taker\" aria-label=\"Note-taker\">\n <p className={`mx-meet__note-taker-state mx-meet__note-taker-state--${status.state}`}>\n <span className=\"mx-meet__note-taker-dot\" aria-hidden=\"true\" />\n {ai.noteTakerLabel}\n </p>\n {status.detail !== null ? (\n <p className=\"mx-meet__panel-note\">{status.detail}</p>\n ) : null}\n\n {/* THE REFUSAL, VERBATIM. Not summarized, not replaced by a house\n sentence, and not hidden behind a retry that would refuse again. */}\n {status.refusal !== null ? (\n <ErrorBox as=\"div\" className=\"mx-meet__refusal\" error={status.refusal} operation=\"Join this meeting\" origin=\"meet/components\">\n <p className=\"mx-meet__refusal-reason\">{status.refusal.reason}</p>\n {status.refusal.remedy.length > 0 ? (\n <p className=\"mx-meet__refusal-remedy\">{status.refusal.remedy}</p>\n ) : null}\n </ErrorBox>\n ) : null}\n\n {ai.error !== null && !duplicateError ? (\n <ErrorBox as=\"div\" className=\"mx-meet__refusal\" error={ai.error} operation=\"Run the meeting assistant\" origin=\"meet/components\">\n <p className=\"mx-meet__refusal-reason\">{ai.error.message}</p>\n <p className=\"mx-meet__refusal-remedy\">{ai.error.remedy}</p>\n </ErrorBox>\n ) : null}\n\n {/* A control an actor cannot use is ABSENT (no dead ends). A guest and a\n non-host see the STATE and no buttons at all. */}\n {ai.canControlNoteTaker ? (\n status.state === \"transcribing\" ||\n status.state === \"joining\" ||\n status.state === \"handoff_pending\" ? (\n <button\n type=\"button\"\n className=\"mx-meet__link\"\n onClick={() => void ai.stopNoteTaker()}\n >\n Dismiss the note-taker\n </button>\n ) : (\n <button\n type=\"button\"\n className=\"mx-meet__primary\"\n onClick={() => void ai.startNoteTaker()}\n >\n Start note-taker\n </button>\n )\n ) : null}\n </section>\n );\n}\n\nexport function NoteList({\n title,\n notes,\n empty,\n}: {\n title: string;\n notes: readonly { id: string; text: string; assigneeDisplayName?: string | null }[];\n empty: string;\n}): ReactNode {\n return (\n <section className=\"mx-meet__note-group\" aria-label={title}>\n <h3 className=\"mx-meet__note-group-title\">{title}</h3>\n {notes.length === 0 ? (\n <p className=\"mx-meet__panel-note\">{empty}</p>\n ) : (\n <ul className=\"mx-meet__notes\">\n {notes.map((note) => (\n <li key={note.id} className=\"mx-meet__note\">\n {note.text}\n {/* D18: a guest never gets an `assignee_user_id`, so the name is\n read from the row's metadata — an action item whose owner has\n no name is an action item nobody owns. */}\n {note.assigneeDisplayName !== undefined &&\n note.assigneeDisplayName !== null ? (\n <span className=\"mx-meet__assignee\">{note.assigneeDisplayName}</span>\n ) : null}\n </li>\n ))}\n </ul>\n )}\n </section>\n );\n}\n\n/**\n * THE MEETING ASSISTANT (R4). The note-taker's state, what the meeting has\n * decided so far, the wrap-up, and a question box whose answer arrives token by\n * token.\n *\n * Nothing on this panel is computed here: notes, decisions and action items are\n * durable rows delivered by Postgres Changes, and the answer is streamed from\n * `/intelligence/ask`, which builds it from those same rows. The captions this\n * client can see are never sent anywhere (D16).\n */\nexport function AiPanel({ onClose }: { onClose: () => void }): ReactNode {\n const ai = useMeetAi();\n const [question, setQuestion] = useState(\"\");\n\n return (\n <PanelShell title=\"Meeting assistant\" onClose={onClose}>\n <NoteTakerRow />\n\n {ai.summary !== null ? (\n <section className=\"mx-meet__note-group\" aria-label=\"Summary\">\n <h3 className=\"mx-meet__note-group-title\">Summary</h3>\n <p className=\"mx-meet__summary\">{ai.summary.text}</p>\n </section>\n ) : null}\n\n {/*\n THE WRAP-UP, ANNOUNCED IN THE ROOM (MRI-A12). It says the pass FINISHED,\n which is the one thing the notes above cannot say for themselves — a\n panel with no summary is otherwise indistinguishable from a summary that\n is still being written. It is absent, never a placeholder, when nothing\n was announced: on the ordinary ending path the room is deleted before the\n pass runs, and the summary is read on the post-meeting record instead.\n */}\n {ai.wrapUp !== null ? (\n <p className=\"mx-meet__wrap-up-notice\" role=\"status\">\n The wrap-up is written — {ai.wrapUp.noteIds.length}{\" \"}\n {ai.wrapUp.noteIds.length === 1 ? \"entry\" : \"entries\"} from{\" \"}\n {ai.wrapUp.segmentCount}{\" \"}\n {ai.wrapUp.segmentCount === 1 ? \"transcript line\" : \"transcript lines\"}.\n </p>\n ) : null}\n\n <NoteList\n title=\"Notes\"\n notes={ai.notes}\n empty=\"Notes appear here as the meeting goes on.\"\n />\n <NoteList\n title=\"Decisions\"\n notes={ai.decisions}\n empty=\"Nothing has been decided yet.\"\n />\n <NoteList\n title=\"Action items\"\n notes={ai.actionItems}\n empty=\"No action items yet.\"\n />\n\n {ai.transcript.length > 0 ? (\n <section className=\"mx-meet__note-group\" aria-label=\"Transcript\">\n <h3 className=\"mx-meet__note-group-title\">Transcript</h3>\n <ul className=\"mx-meet__transcript\">\n {ai.transcript.map((line) => (\n <li\n key={line.id}\n className={\n line.final && line.source === \"record\"\n ? \"mx-meet__line\"\n : \"mx-meet__line mx-meet__line--interim\"\n }\n >\n <strong>{line.speaker}</strong>\n <span>{line.text}</span>\n </li>\n ))}\n </ul>\n </section>\n ) : null}\n\n <form\n className=\"mx-meet__composer\"\n onSubmit={(event) => {\n event.preventDefault();\n const asked = question;\n setQuestion(\"\");\n void ai.ask(asked);\n }}\n >\n <input\n className=\"mx-meet__input\"\n value={question}\n onChange={(event) => setQuestion(event.target.value)}\n placeholder=\"Ask about this meeting\"\n aria-label=\"Ask about this meeting\"\n />\n <button type=\"submit\" className=\"mx-meet__send\" disabled={ai.asking}>\n {ai.asking ? \"Asking…\" : \"Ask\"}\n </button>\n </form>\n\n {/* THE SERVER'S PHASE, not a spinner of our invention. */}\n {ai.asking && ai.phase !== null ? (\n <p className=\"mx-meet__panel-note\" role=\"status\">\n {ai.phase}\n </p>\n ) : null}\n {ai.answer !== null ? <p className=\"mx-meet__answer\">{ai.answer}</p> : null}\n {/* WHAT THE ANSWER WAS BUILT FROM. The server measures it; a truncated\n transcript is stated rather than quietly answered around. */}\n {ai.answer !== null && ai.coverage !== null ? (\n <p className=\"mx-meet__coverage\">\n Answered from {ai.coverage.segmentsUsed} of {ai.coverage.segmentsTotal} transcript\n lines\n {ai.coverage.truncated ? \" (the oldest were left out)\" : \"\"}.\n </p>\n ) : null}\n {/* THE REMEDY, not a shrug — and rendered by THIS hook instance, because\n `useMeetAi()` holds its own ask state per component. */}\n {ai.error !== null ? (\n <ErrorBox as=\"div\" className=\"mx-meet__refusal\" error={ai.error} operation=\"Run the meeting assistant\" origin=\"meet/components\">\n <p className=\"mx-meet__refusal-reason\">{ai.error.message}</p>\n <p className=\"mx-meet__refusal-remedy\">{ai.error.remedy}</p>\n </ErrorBox>\n ) : null}\n </PanelShell>\n );\n}\n\n/**\n * LIVE CAPTIONS. The last few lines, over the stage, for anyone who reads\n * faster than they hear.\n *\n * They are HINTS (D16) — the durable segment replaces the line it captioned as\n * soon as the row lands — so an unsettled line renders differently from a\n * settled one and nothing downstream ever reads this list.\n */\nexport function Captions({ lines = 3 }: { lines?: number }): ReactNode {\n const ai = useMeetAi();\n const visible = ai.transcript.slice(-lines);\n // Meet wave 5: a viewer who chose a language reads the translation, with the words\n // as spoken beneath it until the translation lands.\n const translation = useCaptionTranslation(visible);\n if (visible.length === 0) return null;\n return (\n <div className=\"mx-meet__captions\" aria-live=\"polite\" aria-label=\"Live captions\">\n {visible.map((line) => {\n const translated = translation.language === null ? undefined : translation.translations[line.id];\n return (\n <p\n key={line.id}\n className={\n line.final ? \"mx-meet__caption\" : \"mx-meet__caption mx-meet__caption--interim\"\n }\n >\n <strong>{line.speaker}</strong> {translated ?? line.text}\n {translated !== undefined ? <span className=\"mx-meet__caption-original\">{line.text}</span> : null}\n </p>\n );\n })}\n {translation.language !== null && translation.error !== null ? (\n <p className=\"mx-meet__caption mx-meet__caption--interim\">{translation.error}</p>\n ) : null}\n </div>\n );\n}\n\n/**\n * CONSENT IS NOT A PROP (README rule 8, D10).\n *\n * Whenever an agent participant is in the room, EVERY participant sees this —\n * host, member and guest alike. There is no prop that hides it and no host code\n * involved: through 0.2.1 the first real consumer had to write this banner\n * itself by reading `isAgent` off the roster, which is exactly the massage C22\n * forbids.\n */\nexport function ConsentNotice(): ReactNode {\n const notice = useConsentNotice();\n if (notice === null) return null;\n return (\n <p className=\"mx-meet__consent\" role=\"status\">\n {notice}\n </p>\n );\n}\n\n/**\n * LISTEN-ONLY ATTENDANCE, said out loud. Shown whenever the local participant\n * is in the room publishing no microphone — whether they chose that or the\n * browser refused. When it was refused, the browser's own reason and the remedy\n * are shown too, because \"others can't hear you\" without \"and here is why\"\n * leaves someone clicking a mute button that cannot work.\n */\nexport function AttendanceNotice(): ReactNode {\n const media = useLocalMedia();\n const notice = listenOnlyNotice(media);\n if (notice === null) return null;\n const refused = media.microphone.unavailable;\n const code = microphoneNoticeCode(media.microphone);\n return (\n <p\n className=\"mx-meet__banner mx-meet__banner--attendance\"\n role=\"status\"\n {...(code !== null ? { \"data-meet-notice\": code } : {})}\n >\n {notice}\n {refused === null ? null : <span className=\"mx-meet__remedy\">{refused.remedy}</span>}\n </p>\n );\n}\n\n/* --------------------------------------------------------------- pre-join */\n\nexport function PreJoin(props: {\n roomName: RoomName;\n meetingId?: string;\n slug?: string;\n onJoined?: () => void;\n /** HR-360 wave 2 (MD-16): join as a silent observer — no camera, no microphone. */\n observe?: boolean;\n}): ReactNode {\n // THE GUARD, not a throw. See <InertNotice>.\n const host = useMeetHost();\n if (host === null) return <InertNotice what=\"This meeting\" />;\n return <PreJoinBody {...props} host={host} />;\n}\n\nfunction PreJoinBody({\n roomName,\n meetingId,\n slug,\n onJoined,\n observe = false,\n host,\n}: {\n roomName: RoomName;\n meetingId?: string;\n slug?: string;\n onJoined?: () => void;\n observe?: boolean;\n host: MeetHost;\n}): ReactNode {\n const room = useRoom();\n const devices = useDeviceSnapshot();\n const recording = useRecording();\n const effects = useMediaEffects();\n const { notice } = useModerationNotice();\n const [mode, setMode] = useState<CallMode>(\"video\");\n // Attendance is not contingent on a device (see core/attendance.ts), so this\n // is an ordinary choice, not a gate. Unchecking it joins listen-only; leaving\n // it checked and having the browser refuse ALSO joins, listen-only, with the\n // refusal's own remedy shown in the room.\n const [microphone, setMicrophone] = useState(true);\n const [joining, setJoining] = useState(false);\n const camera = devices?.selected.videoinput ?? host.devices.resolve(\"videoinput\");\n const mic = devices?.selected.audioinput ?? host.devices.resolve(\"audioinput\");\n const preview = useCameraPreview(mode === \"video\" ? camera : undefined, effects.effects);\n const micStream = useMicrophoneStream(microphone ? mic : \"off\");\n const level = useAudioLevel(micStream);\n const [speakerTesting, setSpeakerTesting] = useState(false);\n\n useEffect(() => {\n void host.devices.refresh();\n }, [host]);\n\n return (\n <div className=\"mx-meet__prejoin\">\n <div className=\"mx-meet__preview\">\n {mode === \"video\" && preview.preview !== null ? (\n <video\n className=\"mx-meet__video mx-meet__video--mirror\"\n autoPlay\n playsInline\n muted\n ref={(element) => {\n if (element !== null) preview.preview?.attach(element);\n }}\n />\n ) : (\n <div className=\"mx-meet__tile-placeholder\">\n <Avatar name={host.identity.displayName} url={host.identity.avatarUrl} size={96} />\n </div>\n )}\n {mode === \"video\" && preview.error !== null ? (\n <p className=\"mx-meet__preview-note\">{preview.error}</p>\n ) : null}\n {microphone && micStream !== null ? (\n <div className=\"mx-meet__meter mx-meet__meter--preview\" role=\"meter\" aria-label=\"Microphone level\" aria-valuemin={0} aria-valuemax={100} aria-valuenow={Math.round(level * 100)}>\n <span style={{ width: `${Math.min(100, Math.round(level * 160))}%` }} />\n </div>\n ) : null}\n </div>\n\n <div className=\"mx-meet__prejoin-panel\">\n <h1 className=\"mx-meet__prejoin-title\">Ready to join?</h1>\n\n {notice?.kind === \"removed\" ? <p className=\"mx-meet__warning\">{notice.text}</p> : null}\n\n {devices?.labelsHidden === true ? (\n <p className=\"mx-meet__panel-note\">\n Your browser is hiding device names until you allow camera and microphone access.\n <button\n type=\"button\"\n className=\"mx-meet__link\"\n onClick={() => {\n void host.devices.requestPermission({ audio: true, video: true });\n }}\n >\n Allow access\n </button>\n </p>\n ) : null}\n\n {/* A FALLBACK ANNOUNCES ITSELF — an unplugged headset never silently\n becomes the laptop mic. */}\n {devices?.fallbackNotice != null ? (\n <p className=\"mx-meet__warning\">{devices.fallbackNotice}</p>\n ) : null}\n\n <DevicePicker kind=\"audioinput\" label=\"Microphone\" />\n <div className=\"mx-meet__field-row\">\n <DevicePicker kind=\"audiooutput\" label=\"Speaker\" />\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={speakerTesting}\n onClick={() => {\n setSpeakerTesting(true);\n void playTestSound(devices?.selected.audiooutput ?? host.devices.resolve(\"audiooutput\")).finally(\n () => setSpeakerTesting(false),\n );\n }}\n >\n <SpeakerIcon /> {speakerTesting ? \"Playing…\" : \"Test\"}\n </Button>\n </div>\n <DevicePicker kind=\"videoinput\" label=\"Camera\" />\n <EffectsSettings preview={preview.preview} />\n\n {recording.consentNotice !== null ? (\n <Alert variant=\"info\">\n <AlertDescription>{recording.consentNotice}</AlertDescription>\n </Alert>\n ) : null}\n\n <div className=\"mx-meet__prejoin-actions\">\n <div className=\"mx-meet__check\">\n <Checkbox\n id=\"mx-meet-join-video\"\n checked={mode === \"video\"}\n onCheckedChange={(checked) => setMode(checked === true ? \"video\" : \"audio\")}\n />\n <Label htmlFor=\"mx-meet-join-video\">Join with video</Label>\n </div>\n <div className=\"mx-meet__check\">\n <Checkbox\n id=\"mx-meet-join-mic\"\n checked={microphone}\n onCheckedChange={(checked) => setMicrophone(checked === true)}\n />\n <Label htmlFor=\"mx-meet-join-mic\">Join with microphone</Label>\n </div>\n <Button\n type=\"button\"\n className=\"mx-meet__join\"\n disabled={joining}\n onClick={() => {\n setJoining(true);\n // The preview hands the camera back before the room takes it.\n preview.release();\n void room\n .join({\n roomName,\n // An observer publishes nothing: no camera, no microphone (MD-16).\n mode: observe ? \"audio\" : mode,\n microphone: observe ? false : microphone,\n ...(meetingId !== undefined ? { meetingId } : {}),\n ...(slug !== undefined ? { slug } : {}),\n ...(observe ? { observe: true } : {}),\n })\n .then(() => onJoined?.())\n // The failure is ON SCREEN (room.failure, the server's sentence); an\n // unhandled rejection only added a page error nobody can act on.\n .catch(() => undefined)\n .finally(() => setJoining(false));\n }}\n >\n {joining ? \"Joining…\" : observe ? \"Observe\" : \"Join now\"}\n </Button>\n </div>\n\n {room.failure !== null ? (\n <p className=\"mx-meet__error\">\n {room.failure.message}\n <span className=\"mx-meet__remedy\">{room.failure.remedy}</span>\n </p>\n ) : null}\n </div>\n </div>\n );\n}\n\n/**\n * The pre-join camera, WITH the chosen background — the same SDK track and\n * processor the room uses. `undefined` device = camera not wanted (no preview,\n * no light). A refused camera is a sentence, never a black box.\n */\nfunction useCameraPreview(\n deviceId: string | null | undefined,\n effects: MediaEffects,\n): { preview: CameraPreview | null; error: string | null; release: () => void } {\n const [preview, setPreview] = useState<CameraPreview | null>(null);\n const [error, setError] = useState<string | null>(null);\n const current = useRef<CameraPreview | null>(null);\n const initialEffect = useRef(toVideoEffect(effects));\n initialEffect.current = toVideoEffect(effects);\n useEffect(() => {\n if (deviceId === undefined) return undefined;\n let live = true;\n setError(null);\n void createCameraPreview(deviceId, initialEffect.current)\n .then((created) => {\n if (!live) {\n created.stop();\n return;\n }\n current.current = created;\n setPreview(created);\n })\n .catch((thrown: unknown) => {\n if (!live) return;\n const failure = thrown as { message?: string; remedy?: string };\n // The person reads the sentence and its remedy — never the operation tag\n // (\"meet.cameraPreview: …\") a diagnostic carries.\n const message = (failure.message ?? \"\").replace(/^meet\\.[A-Za-z.()]+:\\s*/, \"\");\n const sentence = message.length > 0 ? message.charAt(0).toUpperCase() + message.slice(1) : \"\";\n setError([sentence, failure.remedy].filter(Boolean).join(\". \") || \"The camera did not start.\");\n });\n return () => {\n live = false;\n current.current?.stop();\n current.current = null;\n setPreview(null);\n };\n }, [deviceId]);\n return {\n preview,\n error,\n release: () => {\n current.current?.stop();\n current.current = null;\n setPreview(null);\n },\n };\n}\n\nfunction DevicePicker({\n kind,\n label,\n onPick,\n}: {\n kind: \"audioinput\" | \"videoinput\" | \"audiooutput\";\n label: string;\n /** In the room: also switch the live device. Pre-join: the choice is applied on join. */\n onPick?: (kind: \"audioinput\" | \"videoinput\" | \"audiooutput\", deviceId: string) => void;\n}): ReactNode {\n const host = useRequiredMeetHost();\n const devices = useDeviceSnapshot();\n const list = devices?.devices[kind] ?? [];\n // A browser that cannot route audio output (Safari, Firefox without the flag)\n // lists no outputs — the picker is then absent, never a select that lies.\n if (list.length === 0) return null;\n const triggerId = `mx-meet-device-${kind}`;\n // Radix Select refuses an empty-string item value; a device with no id\n // (some browsers before permission) is listed under a stable stand-in.\n const valueOf = (deviceId: string) => (deviceId.length > 0 ? deviceId : \"default\");\n const current = devices?.selected[kind] ?? host.devices.resolve(kind) ?? \"\";\n return (\n <div className=\"mx-meet__field\">\n <Label htmlFor={triggerId}>{label}</Label>\n <Select\n {...(current.length > 0 ? { value: valueOf(current) } : {})}\n onValueChange={(value) => {\n const deviceId = list.find((device) => valueOf(device.deviceId) === value)?.deviceId ?? value;\n if (onPick !== undefined) onPick(kind, deviceId);\n else host.devices.select(kind, deviceId);\n }}\n >\n <SelectTrigger id={triggerId} aria-label={label}>\n <SelectValue placeholder={label} />\n </SelectTrigger>\n <SelectContent>\n {list.map((device) => (\n <SelectItem key={device.deviceId || \"default\"} value={valueOf(device.deviceId)}>\n {device.label.length > 0 ? device.label : `${label} (name hidden)`}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n );\n}\n\n/* ----------------------------------------------------------- meeting room */\n\n/**\n * THE MEET SKIN'S ROOM (v0) — today's pre-join, waiting room and live room,\n * chosen by the store's phase. `<MeetingSkinRoot>` (in `@ai-matrx/meet/react`)\n * owns everything that is not a look: the inert guard, the observation root,\n * watching the room and the meeting, the resolved-row seed and the ended\n * meeting's record. This renders only once a runtime exists and the meeting\n * is live or joinable.\n */\nexport function MeetSkinRoom({\n roomName,\n meetingId,\n slug,\n onLeave,\n headerControls,\n observe = false,\n}: {\n roomName: RoomName;\n meetingId?: string;\n slug?: string;\n onLeave?: () => void;\n /** HR-360 wave 2 (MD-16): the pre-join joins as a silent observer. */\n observe?: boolean;\n /**\n * A HOST-PROVIDED control for the in-meeting header — rendered in the header\n * row of the live room only. The package never inspects it.\n */\n headerControls?: ReactNode;\n}): ReactNode {\n const core = useMeetCore();\n const { host, snapshot, isHost, phase } = core;\n const elapsed = useElapsed(snapshot?.meeting?.startedAt ?? null);\n if (host === null) return null;\n\n // THE PHASE → SCREEN MAP (CORE-DESIGN §3.1, §5.4): one screen per phase, exhaustive — a phase\n // added to the machine without a screen here is a compile error. Nothing the person can be in\n // renders pre-join except `prejoin` itself.\n const screen = (() => {\n if (isRefusedPhase(phase)) return \"refused\" as const;\n switch (phase) {\n case \"resolving\":\n return \"resolving\" as const;\n case \"prejoin\":\n case \"guest_name\":\n return \"prejoin\" as const;\n case \"joining\":\n case \"in_call\":\n return \"room\" as const;\n case \"waiting:host_not_started\":\n case \"waiting:knocking\":\n case \"denied\":\n case \"knock_expired\":\n return \"waiting\" as const;\n case \"left\":\n return snapshot?.waiting?.admission === \"withdrawn\" ? (\"waiting\" as const) : (\"exit\" as const);\n case \"disconnected\":\n case \"removed\":\n case \"superseded\":\n case \"inactive\":\n case \"unsupported\":\n case \"ended\":\n case \"record\":\n case \"ringing_out\":\n case \"ringing_in\":\n case \"call_declined\":\n case \"call_missed\":\n case \"call_busy\":\n case \"answered_elsewhere\":\n return \"exit\" as const;\n default: {\n const unhandled: never = phase;\n return unhandled;\n }\n }\n })();\n\n if (screen === \"resolving\") {\n return (\n <div className=\"mx-meet\">\n <p className=\"mx-meet__empty-body\" role=\"status\">Getting the meeting ready…</p>\n </div>\n );\n }\n\n if (screen === \"prejoin\") {\n return (\n <div className=\"mx-meet\">\n <PreJoin\n roomName={roomName}\n {...(meetingId !== undefined ? { meetingId } : {})}\n {...(slug !== undefined ? { slug } : {})}\n observe={observe}\n />\n </div>\n );\n }\n\n if (screen === \"waiting\") {\n return (\n <div className=\"mx-meet mx-meet--lobby\">\n <WaitingRoom host={host} {...(onLeave !== undefined ? { onLeave } : {})} />\n </div>\n );\n }\n\n if (screen === \"refused\" || screen === \"exit\") {\n return (\n <div className=\"mx-meet mx-meet--lobby\">\n <ExitScreen {...(onLeave !== undefined ? { onLeave } : {})} />\n </div>\n );\n }\n\n return (\n <div className=\"mx-meet\">\n <ConsentNotice />\n <AttendanceNotice />\n <header className=\"mx-meet__header\">\n <h1 className=\"mx-meet__title\">{snapshot?.meeting?.title ?? \"Meeting\"}</h1>\n <span className=\"mx-meet__meta\">\n {participantSummary(snapshot?.participants ?? [])}\n {elapsed > 0 ? ` · ${formatDurationMs(elapsed)}` : \"\"}\n </span>\n <RecordingIndicator />\n {captureHeldOf(snapshot?.meeting) ? (\n <span className=\"mx-meet__badge\" data-meet-capture-held=\"\">\n Confidential · not recorded\n </span>\n ) : null}\n {isObserverRole(snapshot?.grants.role) ? (\n <span className=\"mx-meet__badge\" data-meet-observing=\"self\">Observing</span>\n ) : null}\n {snapshot?.locked === true ? (\n <span className=\"mx-meet__badge\">\n <LockIcon width={12} height={12} /> Locked\n </span>\n ) : null}\n {headerControls !== undefined && headerControls !== null ? (\n <span className=\"mx-meet__header-controls\" data-meet-header-controls=\"\">\n {headerControls}\n </span>\n ) : null}\n {isHost ? <HostMenu /> : null}\n </header>\n\n {/* A reconnect is VISIBLE. A frozen tile with no explanation is\n indistinguishable from a crashed app. */}\n {core.connection !== \"stable\" ? (\n <p className=\"mx-meet__banner\" role=\"status\">\n Reconnecting…\n </p>\n ) : null}\n <ShareNotice />\n\n <Stage />\n <Captions />\n <ControlBar {...(onLeave !== undefined ? { onLeave } : {})} />\n </div>\n );\n}\n\n/**\n * A REFUSED SHARE IS A NOTICE, NEVER AN EXIT (CORE-DESIGN §3.3 SA). It stays until the person\n * dismisses it or shares again; the call carries on underneath.\n */\nfunction ShareNotice(): ReactNode {\n const { share, actions } = useMeetCore();\n if (share.state !== \"refused\") return null;\n const text =\n share.reason === \"unsupported\"\n ? \"You can't share your screen from this browser\"\n : share.reason === \"blocked\"\n ? \"Screen sharing is blocked\"\n : \"Couldn't start sharing your screen\";\n return (\n <p className=\"mx-meet__banner\" role=\"status\" data-meet-notice=\"share-unavailable\">\n {text}\n {share.remedy !== null ? <span className=\"mx-meet__remedy\">{share.remedy}</span> : null}\n <button type=\"button\" className=\"mx-meet__link\" onClick={actions.dismissShareNotice}>\n Dismiss\n </button>\n </p>\n );\n}\n\n/** The words for each refusal and exit reason (the skin owns every string, §5.4). */\nconst REFUSED_TITLES: Record<string, string> = {\n not_found: \"Check your meeting code\",\n cancelled: \"This meeting was cancelled\",\n expired: \"This meeting link has expired\",\n too_early: \"This meeting hasn't started yet\",\n sign_in_required: \"Sign in to join this meeting\",\n wrong_account: \"You're signed in with a different account\",\n not_invited: \"You aren't invited to this meeting\",\n locked: \"This meeting is locked\",\n removed: \"You can't rejoin this meeting\",\n lobby_rejects: \"You can't ask to join this meeting\",\n meeting_full: \"This meeting is full\",\n platform_capacity: \"Meetings are busy right now\",\n verification_required: \"Verify your account to join\",\n registration_required: \"Register to join this meeting\",\n};\n\n/**\n * EVERY WAY OUT OF THE ROOM, SAID PLAINLY (CORE-DESIGN §3.1, §3.4): left, disconnected, removed,\n * replaced, inactive, ended, refused. Each names what happened and offers only what the rules\n * allow — Rejoin where the machine allows it, never a silent pre-join.\n */\nfunction ExitScreen({ onLeave }: { onLeave?: () => void }): ReactNode {\n const { phase, reason, host, snapshot, actions, call } = useMeetCore();\n const [busy, setBusy] = useState(false);\n const failure = snapshot?.failure ?? null;\n const rejoin = (\n <Button\n type=\"button\"\n disabled={busy}\n onClick={() => {\n setBusy(true);\n void actions\n .rejoin()\n .catch(() => undefined)\n .finally(() => setBusy(false));\n }}\n >\n Rejoin\n </Button>\n );\n const home = (\n <a\n className=\"mx-meet__link\"\n href=\"/\"\n onClick={(event) => {\n if (call !== null) {\n event.preventDefault();\n actions.dismissCall();\n }\n if (onLeave !== undefined) {\n event.preventDefault();\n onLeave();\n }\n }}\n >\n Go home\n </a>\n );\n let title: string;\n let line: string | null = null;\n let notice: string | null = null;\n let actionsNode: ReactNode = home;\n if (phase.startsWith(\"refused:\")) {\n const code = phase.slice(\"refused:\".length);\n // THE SKIN OWNS THE WORDS (CORE-DESIGN §5.4): the screen is decided by the reason CODE.\n // The server's text is never shown as the headline — it can be a canned generic line\n // (\"You do not have permission…\") when its own message was short. Only a remedy that\n // carries specifics the code cannot (the next start time) is shown under the title.\n title = REFUSED_TITLES[code] ?? \"You can't join this meeting\";\n line =\n code === \"too_early\" || code === \"registration_required\" || code === \"cancelled\"\n ? (failure?.remedy ?? null)\n : null;\n actionsNode = (\n <>\n <Button type=\"button\" variant=\"outline\" onClick={actions.retry}>\n Try again\n </Button>\n {home}\n </>\n );\n } else {\n switch (phase) {\n case \"left\":\n title = \"You left the meeting\";\n actionsNode = (\n <>\n {rejoin}\n {home}\n </>\n );\n break;\n case \"disconnected\":\n title = \"You lost your connection to the meeting\";\n line = failure?.remedy ?? \"Check your internet connection, then rejoin.\";\n notice = \"disconnected\";\n actionsNode = (\n <>\n {rejoin}\n {home}\n </>\n );\n break;\n case \"removed\":\n title = \"You've been removed from the meeting\";\n notice = \"removed\";\n break;\n case \"superseded\":\n title = \"You joined from another tab or device\";\n line = \"This screen left the meeting so you aren't in it twice.\";\n notice = \"displaced\";\n actionsNode = (\n <>\n {rejoin}\n {home}\n </>\n );\n break;\n case \"inactive\":\n title = \"You left the meeting because this tab was inactive\";\n actionsNode = (\n <>\n {rejoin}\n {home}\n </>\n );\n break;\n case \"unsupported\":\n title = \"This browser can't join meetings\";\n line = \"Open the link in Chrome, Edge, Firefox or Safari.\";\n break;\n case \"ended\":\n case \"record\":\n title = reason === \"time_limit\" ? \"The meeting reached its time limit\" : \"The meeting has ended\";\n // A series run that ended says so, and when the next one starts (the gate's own words).\n if (failure?.reason === \"ended\" && failure.remedy.length > 0 && failure.message.startsWith(\"This run\")) {\n title = failure.message.replace(/\\.$/, \"\");\n line = failure.remedy;\n }\n notice = \"meeting-ended\";\n break;\n default:\n title = \"The call has ended\";\n break;\n }\n }\n return (\n <div className=\"mx-meet__waiting\" data-meet-exit={phase}>\n <Avatar name={host?.identity.displayName ?? \"\"} url={host?.identity.avatarUrl ?? null} size={72} />\n <h1 className=\"mx-meet__prejoin-title\">{title}</h1>\n {line !== null ? <p className=\"mx-meet__empty-body\">{line}</p> : null}\n {notice !== null ? (\n <span role=\"status\" data-meet-notice={notice} className=\"mx-meet__sr-only\">\n {title}\n </span>\n ) : null}\n <div className=\"mx-meet__waiting-actions\">{actionsNode}</div>\n </div>\n );\n}\n\n/**\n * THE WAITING SCREENS (S4). Every one is the SERVER's word on this person's row — read by the\n * core's state read, never a broadcast: waiting for the host, knocking, declined, no answer,\n * left the waiting room, ended. Controls appear only when the rules allow them.\n */\nfunction WaitingRoom({ host, onLeave }: { host: MeetHost; onLeave?: () => void }): ReactNode {\n const { waiting, withdraw, askAgain } = useWaiting();\n const { phaseNotice } = useMeetCore();\n const [busy, setBusy] = useState(false);\n const admission = waiting?.admission ?? \"knocking\";\n const run = (action: () => Promise<void>) => () => {\n setBusy(true);\n void action()\n .catch(() => undefined)\n .finally(() => setBusy(false));\n };\n const home = (\n <Button type=\"button\" variant=\"outline\" onClick={() => (onLeave !== undefined ? onLeave() : window.location.assign(\"/\"))}>\n Back to home\n </Button>\n );\n\n let title: string;\n let line: string | null = null;\n let actions: ReactNode;\n switch (admission) {\n case \"waiting_host\":\n title = \"Waiting for the host to start\";\n actions = (\n <Button type=\"button\" variant=\"outline\" disabled={busy} onClick={run(withdraw)}>\n Leave\n </Button>\n );\n break;\n case \"denied\":\n title = \"The host declined your request\";\n actions = (\n <>\n {waiting?.reknockAllowed === true ? (\n <Button type=\"button\" disabled={busy} onClick={run(askAgain)}>\n Ask to join again\n </Button>\n ) : null}\n {home}\n </>\n );\n break;\n case \"expired\":\n title = \"No one responded\";\n actions = (\n <>\n <Button type=\"button\" disabled={busy} onClick={run(askAgain)}>\n Ask again\n </Button>\n {home}\n </>\n );\n break;\n case \"withdrawn\":\n title = \"You left the waiting room\";\n actions = (\n <>\n <Button type=\"button\" disabled={busy} onClick={run(askAgain)}>\n Rejoin\n </Button>\n {home}\n </>\n );\n break;\n case \"ended\":\n title = \"The meeting has ended\";\n actions = home;\n break;\n default:\n title = \"Waiting for the host to let you in\";\n line = waiting?.admittersPresent === false ? \"The host isn't here yet\" : null;\n actions = (\n <Button type=\"button\" variant=\"outline\" disabled={busy} onClick={run(withdraw)}>\n Cancel request\n </Button>\n );\n }\n\n return (\n <div className=\"mx-meet__waiting\">\n <Avatar name={host.identity.displayName} url={host.identity.avatarUrl} size={72} />\n <h1 className=\"mx-meet__prejoin-title\">{title}</h1>\n {phaseNotice === \"sent-to-waiting\" ? (\n <p className=\"mx-meet__empty-body\" role=\"status\" data-meet-notice=\"sent-to-waiting\">\n The host moved you back to the waiting room\n </p>\n ) : null}\n {line !== null ? (\n <p className=\"mx-meet__empty-body\" role=\"status\" data-meet-notice=\"host-absent\">\n {line}\n </p>\n ) : null}\n <div className=\"mx-meet__waiting-actions\">{actions}</div>\n </div>\n );\n}\n\nexport function HostMenu(): ReactNode {\n const host = useRequiredMeetHost();\n const snapshot = useMeetSnapshot();\n const end = useEndMeeting();\n // ENDING A MEETING IS DESTRUCTIVE AND IRREVERSIBLE, so the click that does it\n // names the consequence first — inline, never `window.confirm` (no browser\n // dialogs).\n const [confirmingEnd, setConfirmingEnd] = useState(false);\n const locked = snapshot?.locked === true;\n return (\n <div className=\"mx-meet__host-menu\">\n <button\n type=\"button\"\n className=\"mx-meet__link\"\n onClick={() => {\n void host.meetings.setLock(!locked);\n }}\n >\n {locked ? \"Unlock meeting\" : \"Lock meeting\"}\n </button>\n {/* ENDING IT IS A HOST POWER AND A SERVER DECISION (MRI-A6). The button\n is real; a server that does not serve the route yet earns a SENTENCE\n rather than a dead control or a silent no-op. */}\n {end.canEnd ? (\n confirmingEnd ? (\n <span className=\"mx-meet__confirm\" role=\"group\" aria-label=\"End this meeting?\">\n <span className=\"mx-meet__confirm-text\">\n End it for everyone? Every participant is disconnected, the note-taker stops, and the\n wrap-up is written. Nobody can rejoin this meeting afterwards.\n </span>\n <button\n type=\"button\"\n className=\"mx-meet__link mx-meet__link--danger\"\n onClick={() => {\n setConfirmingEnd(false);\n void end.end();\n }}\n >\n End it\n </button>\n <button\n type=\"button\"\n className=\"mx-meet__link\"\n onClick={() => setConfirmingEnd(false)}\n >\n Keep it running\n </button>\n </span>\n ) : (\n <button\n type=\"button\"\n className=\"mx-meet__link mx-meet__link--danger\"\n onClick={() => setConfirmingEnd(true)}\n >\n {end.ending ? \"Ending…\" : \"End meeting for everyone\"}\n </button>\n )\n ) : null}\n {end.notice !== null ? (\n <span className=\"mx-meet__notice\" role=\"status\">\n {end.notice}\n </span>\n ) : null}\n </div>\n );\n}\n\n/** Reactions floating over the stage. Expired by the store, not by hope. */\nexport function ReactionOverlay({\n reactions,\n}: {\n reactions: readonly { id: string; emoji: string }[];\n}): ReactNode {\n if (reactions.length === 0) return null;\n return (\n <div className=\"mx-meet__reactions\" aria-hidden=\"true\">\n {reactions.map((reaction) => (\n <span key={reaction.id} className=\"mx-meet__reaction\">\n {reaction.emoji}\n </span>\n ))}\n </div>\n );\n}\n\nexport type { ParticipantIdentity };\n\n/** `10:35` — the clock a person reads beside a line of transcript. */\nexport function clockOf(iso: string): string {\n const at = new Date(iso);\n if (Number.isNaN(at.getTime())) return \"\";\n return at.toLocaleTimeString(undefined, { hour: \"numeric\", minute: \"2-digit\" });\n}\n\n/**\n * WHAT GUESTS MAY READ OF THIS MEETING — the host's control (MRF-1).\n *\n * 🚨 **HOST-ONLY, AND ABSENT FOR EVERYONE ELSE.** Not greyed out, not present\n * with a tooltip: a participant has no control here at all, because a control\n * that exists only to be refused is the dead-end class this package does not\n * ship (D11-style honesty, same posture as `PersonHostActions`).\n *\n * It is the third rung of the ladder Arman ruled on 2026-09-12 — the system's\n * default, the organization's setting, and this meeting's. \"Use my\n * organization's setting\" is shown WITH what that resolves to, because a label\n * that does not say what the organization chose makes the host guess about\n * other people's words.\n */\nexport function GuestRecordAccessControl({\n meeting,\n}: {\n meeting: MeetingRecord;\n}): ReactNode {\n const host = useMeetHost();\n const inRoomHost = useIsHost();\n const access = useGuestRecordAccess(meeting);\n const viewerId = host?.identity.userId ?? null;\n // The named host of the row, or a host of the room this tick. A co-host who\n // holds only an `iam` grant and is not in the room does not see the control;\n // the server would allow them, so this is the narrow side on purpose — a\n // missing control is honest, a control that lies about who may click it is not.\n const isHost = inRoomHost || (viewerId !== null && meeting.hostUserId === viewerId);\n if (host === null || !isHost) return null;\n\n const resolved =\n access.setting === \"default\" && access.effective !== null\n ? ` Right now that means: ${guestRecordAccessLabel(access.effective.level).toLowerCase()}.`\n : \"\";\n\n return (\n <section className=\"mx-meet__record-section\" aria-label=\"What guests may read\">\n <h2 className=\"mx-meet__record-section-title\">What guests may read</h2>\n <label className=\"mx-meet__field\">\n <span className=\"mx-meet__field-label\">\n People who attended by link, without an account\n </span>\n <select\n className=\"mx-meet__select\"\n value={access.setting}\n disabled={access.saving}\n onChange={(event) => {\n void access.choose(\n event.target.value as (typeof GUEST_RECORD_ACCESS_CHOICES)[number],\n );\n }}\n >\n {GUEST_RECORD_ACCESS_CHOICES.map((choice) => (\n <option key={choice} value={choice}>\n {guestRecordAccessLabel(choice)}\n </option>\n ))}\n </select>\n </label>\n <p className=\"mx-meet__panel-note\">\n {guestRecordAccessExplanation(access.setting)}\n {resolved}\n </p>\n {access.saving ? (\n <p className=\"mx-meet__panel-note\" role=\"status\">\n Saving what guests may read…\n </p>\n ) : null}\n {access.error !== null ? (\n <ErrorBox as=\"div\" className=\"mx-meet__refusal\" error={access.error} operation=\"Change who can join\" origin=\"meet/components\">\n <p className=\"mx-meet__refusal-reason\">{access.error.message}</p>\n <p className=\"mx-meet__refusal-remedy\">{access.error.remedy}</p>\n </ErrorBox>\n ) : null}\n </section>\n );\n}\n","/**\n * THE SKIN FACADE — the ONLY module a skin may import (CORE-DESIGN §5.1, §5.4;\n * step S1). Guard: `scripts/check-skin-imports.mjs` (in `check:package` and\n * `pnpm test`): a file under `src/skins/` may import React, the design system,\n * files inside its own skin folder, and THIS module — nothing else.\n *\n * `useMeetCore()` is the facade over today's store: the runtime, the room\n * snapshot, devices, the server's role and the observation-contract reading,\n * as one object that changes only when one of them does. The re-exports below\n * are the headless hooks, pure helpers and types Meet skin v0 (today's in-call\n * UI, moved here unchanged) reads. Later steps (S6a+) replace the re-exports\n * with the `CoreSnapshot` slices and typed actions; a skin then reads\n * `useMeetCore()` alone.\n */\n\nimport { useMemo } from \"react\";\nimport type { MeetPhase } from \"../core/phase\";\nimport type { JoinRequest } from \"../core/room\";\nimport type { ConnectionRegion, ShareRegion } from \"../core/store\";\nimport type { CallInvite } from \"../core/types\";\nimport type { DeviceSnapshot } from \"../core/devices\";\nimport type { MeetObservation } from \"../core/observation\";\nimport { NO_SHARE, type MeetSnapshot } from \"../core/store\";\nimport type { ParticipantRole } from \"../core/types\";\nimport { useCoreObservation } from \"./root\";\nimport { useDeviceSnapshot, useIsHost, useMeetHost, useMeetSnapshot, useRole, type MeetHost } from \"./provider\";\n\nexport interface MeetCore {\n /** The runtime, or null while auth/organization resolve (render nothing). */\n readonly host: MeetHost | null;\n readonly snapshot: MeetSnapshot | null;\n readonly devices: DeviceSnapshot | null;\n /** The server's word on this person's role (both carriers, MRI-A11). */\n readonly role: ParticipantRole;\n readonly isHost: boolean;\n /** What the observation contract reads for this tab (S0). */\n readonly observation: MeetObservation;\n /**\n * WHERE THIS TAB IS (CORE-DESIGN §3.1) — the contract's phase value, verbatim. A skin renders\n * one screen per phase (exhaustive) and NEVER decides a phase itself: every move goes through\n * `actions`, and the machine refuses any move its table does not allow.\n */\n readonly phase: MeetPhase;\n /** `data-meet-reason` of a refused / terminal phase. */\n readonly reason: string | null;\n /** A notice that came with the phase (`sent-to-waiting`), or null. */\n readonly phaseNotice: string | null;\n /** The `connection` region (reconnecting is here, never a phase). */\n readonly connection: ConnectionRegion;\n /** The `share` region (a refused share is a notice, never a phase). */\n readonly share: ShareRegion;\n /** The 1:1 call this tab is placing or answering (§3.6), or null. */\n readonly call: { readonly invite: CallInvite; readonly direction: \"outgoing\" | \"incoming\" } | null;\n /** The typed actions. None can move the phase on failure (§5.1). */\n readonly actions: MeetCoreActions;\n}\n\nexport interface MeetCoreActions {\n /** Join from pre-join (the person's press). */\n join(request: JoinRequest): Promise<void>;\n /** Rejoin from `left` / `disconnected` / `superseded` / `inactive` (the person's press). */\n rejoin(): Promise<void>;\n leave(): Promise<void>;\n /** Back to the device check from `left` / `disconnected` (the person's press). */\n backToPrejoin(): void;\n /** Try again on a refusal: the link is looked up again (`refused:*` → `resolving`). */\n retry(): void;\n /** The link resolved and the meeting screen mounted (`resolving` → `prejoin`). */\n resolved(): void;\n dismissShareNotice(): void;\n /** Close a finished call's screen (`call_*` / `left` after a call → `resolving`). */\n dismissCall(): void;\n}\n\nconst NO_ACTIONS: MeetCoreActions = {\n join: async () => undefined,\n rejoin: async () => undefined,\n leave: async () => undefined,\n backToPrejoin: () => undefined,\n retry: () => undefined,\n resolved: () => undefined,\n dismissShareNotice: () => undefined,\n dismissCall: () => undefined,\n};\n\nfunction actionsFor(host: MeetHost | null): MeetCoreActions {\n if (host === null) return NO_ACTIONS;\n const { engine, store } = host;\n return {\n join: (request) => engine.join(request),\n rejoin: () => engine.rejoin(),\n leave: () => engine.leave(),\n backToPrejoin: () => engine.backToPrejoin(),\n retry: () => {\n store.dispatch({ type: \"retry\", click: true });\n },\n resolved: () => {\n store.dispatch({ type: \"resolved\" });\n },\n dismissShareNotice: () => engine.dismissShareNotice(),\n dismissCall: () => {\n if (store.dispatch({ type: \"reset\" })) store.patch({ call: null });\n },\n };\n}\n\n/** The one headless read a skin starts from. Stable until something moves. */\nexport function useMeetCore(): MeetCore {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const devices = useDeviceSnapshot();\n const role = useRole();\n const isHost = useIsHost();\n const observation = useCoreObservation();\n const actions = useMemo(() => actionsFor(host), [host]);\n return useMemo(\n () => ({\n host,\n snapshot,\n devices,\n role,\n isHost,\n observation,\n phase: snapshot?.phase ?? \"resolving\",\n reason: snapshot?.reason ?? null,\n phaseNotice: snapshot?.phaseNotice ?? null,\n connection: snapshot?.connection ?? \"stable\",\n share: snapshot?.share ?? NO_SHARE,\n call: snapshot?.call ?? null,\n actions,\n }),\n [host, snapshot, devices, role, isHost, observation, actions],\n );\n}\n\nexport { avatarPaletteIndex, formatDurationMs, getInitials } from \"@ai-matrx/kit/format\";\nexport { fileDownloadPath } from \"../core/api\";\nexport type { MeetFileMedia } from \"../core/api\";\nexport { displayNameFor } from \"../core/names\";\nexport { uniqueAttendees } from \"../core/after\";\nexport { participantSummary } from \"../core/format\";\nexport { gridColumns } from \"../core/layout\";\nexport { meetingLink } from \"../core/meetings\";\nexport { asMeetingId } from \"../core/types\";\nexport type { CallInvite, CallMode, CallablePerson, MeetParticipant, MeetingKind, MeetingRecord, ParticipantIdentity, ParticipantRole, ParticipantStanding, RoomName } from \"../core/types\";\nexport { isLinkFollower, isObserverRole } from \"../core/types\";\n// HR-360 wave 2 (MD-15): the app panel slot.\nexport { useMeetAppPanel } from \"./app-panel\";\nexport { captureHeldOf } from \"../core/app-panel\";\nexport { CAPTION_LANGUAGES, assignedRoom, autoAssign, breakoutRemainingMs, defaultCaptionLanguage } from \"../core/collab\";\nexport type { CollabPanel, CollabSnapshot, MeetBreakoutPlan, MeetPoll, MeetQuestion, MeetStroke } from \"../core/collab\";\nexport { useCalls, useCanModerate, useConsentNotice, useElapsed, useEndMeeting, useHostControls, useLobby, useWaiting, useLocalMedia, useMeetAi, useGuestRecordAccess, useMeetingRecord, useMeetings, useRecording, useRoom, useStage, useTrack, useAudioLevel, useHandQueue, useMediaEffects, useModerationNotice, useMeetShortcuts, useReactions, MEET_SHORTCUTS } from \"./hooks\";\nexport type { UseHostControlsResult, UseWaitingResult } from \"./hooks\";\nexport { createCameraPreview, videoEffectsSupported } from \"../core/livekit-driver\";\nexport type { CameraPreview } from \"../core/livekit-driver\";\nexport type { VideoEffect } from \"../core/room-driver\";\nexport type { MediaEffects, MeetWaiting, RoomChatMessage } from \"../core/store\";\nexport { listenOnlyNotice } from \"../core/attendance\";\nexport { groupTranscript, guestRecordAccessExplanation, guestRecordAccessLabel, GUEST_RECORD_ACCESS_CHOICES, hasEnded, meetingWhen, playableRecordingFileId, recordingNotice, absenceSentence, REFUSED_ABSENCE, summarizeMeetingRecordRestrictions, wasRefused, wrapUpStatus } from \"../core/record\";\nexport { isNoteTaker } from \"../core/note-taker\";\nexport { microphoneNoticeCode, participantAttributes } from \"../core/observation\";\nexport { useDeviceSnapshot, useIsHost, useMeetHost, useMeetSnapshot, useRequiredMeetHost, useRole } from \"./provider\";\nexport type { MeetHost } from \"./provider\";\nexport type { CollabRequest } from \"../core/in-room\";\nexport { setCaptionLanguage, takeMoveRequest } from \"../core/collab-state\";\nexport {\n useActivityAttention,\n useAvailableActivities,\n useCaptionTranslation,\n useCollab,\n useCollabPanelRequest,\n} from \"./collab-hooks\";\nexport type { UseCollabResult } from \"./collab-hooks\";\nexport { MEET_REACTIONS } from \"../core/reactions\";\n// S6a: the phase machine's vocabulary, for the skin's exhaustive phase → screen map.\nexport type { MeetPhase } from \"../core/phase\";\nexport { isRefusedPhase } from \"../core/phase\";\nexport type { ConnectionRegion, ShareRegion } from \"../core/store\";\n","/**\n * WHO SAID THAT — one answer, every surface, both lanes (MRI-A16).\n *\n * 🚨 **A RAW LIVEKIT IDENTITY IS NEVER A PERSON'S NAME.** On production\n * 2026-09-09 the host's caption rail read *\"Ada Lovelace: I planted a marigold\n * in the courtyard\"* and the guest's — the same meeting, the same second —\n * read *\"guest:c8-spk-1\"*, because the host resolved the name from the durable\n * rows it can read and the guest had no fallback at all and printed the token\n * (MRI-C8). Two surfaces disagreeing about who is speaking is worse than either\n * one being ugly: a person reading the transcript cannot tell whether those are\n * one speaker or two.\n *\n * The cure is that nothing resolves a name for itself. Every source that\n * happens to KNOW a name teaches this book — the LiveKit roster, a durable\n * `meet_transcript_segments` row, a durable `meet_participants` row, a chat\n * message, our own identity — and every surface asks it. Three rules:\n *\n * 1. **A name is learned, never unlearned.** Someone who leaves the room keeps\n * the name they had; a caption that lands after them still says who spoke.\n * This is the \"last known name\" rule, and it is why the book is a store\n * field rather than a derivation over the current roster.\n * 2. **An identity is not a name.** `learn` refuses a \"name\" that is the\n * identity itself — which is exactly what the LiveKit roster hands back for\n * a participant whose token carried no name, and exactly how the token got\n * onto the screen in the first place.\n * 3. **When nobody knows, SAY nobody knows.** `displayNameFor` answers with an\n * honest placeholder — \"Unnamed guest\" / \"Unnamed participant\" — never a\n * token, and never an invented name. The placeholder is deliberately not\n * unique, so surfaces that must keep two unnamed people apart group by\n * IDENTITY rather than by the rendered name.\n */\n\nimport type { ParticipantIdentity } from \"./types\";\n\n/** Identity → the best display name anything has ever known for it. */\nexport type NameBook = Readonly<Record<string, string>>;\n\n/** A guest's identity is minted `guest:<something>` by the token route. */\nexport function isGuestIdentity(identity: ParticipantIdentity | string): boolean {\n return String(identity).startsWith(\"guest:\");\n}\n\n/**\n * An AGENT's identity is minted `agent:<capability>:<meeting>` by the server\n * (`services/meet/worker.py`). An agent is never an anonymous \"participant\":\n * it is a product with a name, and a room that cannot say which product is\n * listening has failed the consent notice it is required to carry (D10).\n */\nexport function isAgentIdentity(identity: ParticipantIdentity | string): boolean {\n return String(identity).startsWith(\"agent:\");\n}\n\n/** The note-taker's product name — `services/meet/worker.py::AGENT_DISPLAY_NAME`. */\nexport const NOTE_TAKER_PRODUCT_NAME = \"Matrx note-taker\";\n/** Any other Matrx agent whose dispatch carried no name. */\nexport const AGENT_PRODUCT_NAME = \"Matrx agent\";\n/** Somebody in the room whose name nothing has ever known. */\nexport const UNNAMED_GUEST = \"Unnamed guest\";\n/** The same, for a participant whose identity is not a guest token. */\nexport const UNNAMED_PARTICIPANT = \"Unnamed participant\";\n\n/**\n * The strings THIS MODULE invents when nobody knows a name. They are answers,\n * never facts: `isRealName` refuses them, so a placeholder can never be learned\n * as somebody's name and can never outrank a real name that arrives later.\n *\n * 🚨 This is what makes the roster safe to route through the book. A roster\n * event projects \"Unnamed guest\" for a nameless participant; a durable\n * transcript row then teaches \"Ada Lovelace\"; without this rule the placeholder\n * — a perfectly ordinary-looking string — would win, and the screen would keep\n * saying \"Unnamed guest\" about somebody the meeting plainly knows.\n */\nconst PLACEHOLDER_NAMES: readonly string[] = [\n NOTE_TAKER_PRODUCT_NAME,\n AGENT_PRODUCT_NAME,\n UNNAMED_GUEST,\n UNNAMED_PARTICIPANT,\n];\n\n/** Is this string one of the placeholders this module produces? */\nexport function isPlaceholderName(name: string | null | undefined): boolean {\n return typeof name === \"string\" && PLACEHOLDER_NAMES.includes(name.trim());\n}\n\n/** What an identity is called when NOTHING knows a name for it. Never a token. */\nexport function placeholderNameFor(identity: ParticipantIdentity | string): string {\n const key = String(identity);\n if (isAgentIdentity(key)) {\n return key.startsWith(\"agent:note_taker:\") || key.startsWith(\"agent:note-taker\")\n ? NOTE_TAKER_PRODUCT_NAME\n : AGENT_PRODUCT_NAME;\n }\n return isGuestIdentity(key) ? UNNAMED_GUEST : UNNAMED_PARTICIPANT;\n}\n\n/**\n * Would this string be a real name for this identity? A name equal to the\n * identity is the roster's way of saying it has none.\n */\nexport function isRealName(\n identity: ParticipantIdentity | string,\n name: string | null | undefined,\n): boolean {\n if (typeof name !== \"string\") return false;\n const trimmed = name.trim();\n if (trimmed.length === 0) return false;\n if (trimmed === String(identity)) return false;\n // Our own answer is not new information. See PLACEHOLDER_NAMES.\n if (isPlaceholderName(trimmed)) return false;\n return true;\n}\n\n/**\n * Teach the book one identity's name. Returns the SAME book when nothing was\n * learned, so a store commit can be skipped rather than waking every subscriber\n * on every caption.\n */\nexport function learnName(\n book: NameBook,\n identity: ParticipantIdentity | string,\n name: string | null | undefined,\n): NameBook {\n const key = String(identity);\n if (key.length === 0) return book;\n if (!isRealName(key, name)) return book;\n const learned = (name as string).trim();\n if (book[key] === learned) return book;\n return { ...book, [key]: learned };\n}\n\n/** Teach the book several at once — a roster replace, or a durable read. */\nexport function learnNames(\n book: NameBook,\n entries: readonly {\n identity: ParticipantIdentity | string;\n displayName?: string | null | undefined;\n }[],\n): NameBook {\n let next = book;\n for (const entry of entries) next = learnName(next, entry.identity, entry.displayName);\n return next;\n}\n\n/**\n * The name a surface renders for `identity`. Never a raw token, never invented.\n *\n * `preferred` is what the arriving thing itself claimed (a caption's roster\n * lookup, a durable row's `speaker` column); it wins when it is a real name,\n * because it is the freshest, and teaches the book on its way past.\n */\nexport function displayNameFor(\n book: NameBook,\n identity: ParticipantIdentity | string,\n preferred?: string | null | undefined,\n): string {\n const key = String(identity);\n if (isRealName(key, preferred)) return (preferred as string).trim();\n const known = book[key];\n if (known !== undefined) return known;\n // NOBODY KNOWS, AND THAT IS WHAT IT SAYS. A person reading this can tell the\n // difference between \"someone whose name we never learned\" and a name.\n return placeholderNameFor(key);\n}\n","/**\n * ONE error classification for the whole package (C22).\n *\n * The banned alternative is a host catching a LiveKit or PostgREST error and\n * deciding for itself whether the camera is busy, the token expired, or the\n * meeting is locked — which is how the same five-branch `if` gets copy-pasted\n * into every consumer and drifts. Everything this package throws is a\n * `MeetError` with a stable `code` and a `remedy` SENTENCE. Nothing fails\n * silently, and nothing fails without saying what to do about it.\n *\n * The device codes exist because \"it doesn't work\" in a video product is almost\n * always one of four specific, differently-remediable things: permission\n * denied, no such device, device already in use by another app, or insecure\n * context (http, where `getUserMedia` does not exist at all). A single\n * \"media-error\" would make the UI say the wrong sentence three times out of\n * four.\n */\n\nexport type MeetErrorCode =\n | \"session-unavailable\"\n | \"forbidden\"\n | \"not-found\"\n | \"conflict\"\n | \"token\"\n | \"transport\"\n | \"invalid-response\"\n | \"misconfigured\"\n | \"permission-denied\"\n | \"device-not-found\"\n | \"device-in-use\"\n | \"insecure-context\"\n | \"media-unsupported\"\n | \"room-locked\"\n | \"lobby-denied\"\n | \"capacity\"\n /** Meet wave 1: the meeting was cancelled — its link resolves, its room never opens. */\n | \"cancelled\"\n | \"unknown\";\n\nexport class MeetError extends Error {\n readonly code: MeetErrorCode;\n /** What to DO. Rendered to the user for the device codes. */\n readonly remedy: string;\n override readonly cause?: unknown;\n /**\n * The join gate's reason code (CORE-DESIGN §2.5) when the server named one: `not_found`,\n * `cancelled`, `ended`, `locked`, `removed`, `lobby_rejects`, `not_invited`… The phase\n * renders `refused:<reason>` (or `ended`) from it — never from the HTTP status.\n */\n readonly reason: string | null;\n\n constructor(\n code: MeetErrorCode,\n message: string,\n remedy: string,\n cause?: unknown,\n reason?: string | null,\n ) {\n super(message);\n this.name = \"MeetError\";\n this.code = code;\n this.remedy = remedy;\n this.reason = reason ?? null;\n if (cause !== undefined) this.cause = cause;\n }\n\n /** True when retrying after the host re-establishes a session is the fix. */\n get isRetryable(): boolean {\n return (\n this.code === \"session-unavailable\" ||\n this.code === \"transport\" ||\n this.code === \"token\"\n );\n }\n\n /** True when the user, not the code, must act — the UI shows `remedy`. */\n get isUserActionable(): boolean {\n return (\n this.code === \"permission-denied\" ||\n this.code === \"device-not-found\" ||\n this.code === \"device-in-use\" ||\n this.code === \"insecure-context\"\n );\n }\n}\n\n/** What a person reads when the server failed without a sentence of its own. */\nexport const SERVER_FAILED_SENTENCE =\n \"The server hit an unexpected problem and nothing was changed.\";\n\n/**\n * A developer's text, not a person's: `Internal server error: SomeError`, a\n * bare `SomeError` class name, or a machine code like `internal_error`.\n */\nexport function isRawServerText(value: string): boolean {\n const text = value.trim();\n return (\n /^internal server error\\b/i.test(text) ||\n /^[A-Z][A-Za-z0-9]*(Error|Exception)$/.test(text) ||\n /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/.test(text)\n );\n}\n\n/**\n * A database message a PERSON can read: our RPCs raise plain sentences (\"Only\n * the host can remove a guest.\"); Postgres's own errors (\"permission denied\n * for table …\", \"violates foreign key …\") and class names are not.\n */\nfunction humanDatabaseSentence(message: string): string | null {\n const text = message.trim();\n if (text.length === 0 || isRawServerText(text)) return null;\n if (\n /permission denied for|violates|relation \"|does not exist|syntax error|column \"|jwt|pgrst|duplicate key|null value in/i.test(\n text,\n )\n )\n return null;\n return text;\n}\n\ninterface ErrorLike {\n name?: string | undefined;\n message?: string | undefined;\n code?: string | undefined;\n}\n\nconst FORBIDDEN_CODES = new Set([\"42501\", \"PGRST301\", \"PGRST302\"]);\n// P0002 is how `platform.refuse_not_found` answers through PostgREST (\"no meeting for that\n// link\"): the one honest not-found, and the reason the join gate's `not_found` screen needs.\nconst NOT_FOUND_CODES = new Set([\"PGRST116\", \"PGRST205\", \"P0002\"]);\n// 40001: the row changed since the caller read it (meet_update_meeting's version\n// compare-and-swap). 55000: the object is in a state that refuses the change (a\n// cancelled meeting). Both are conflicts a person resolves by reloading.\nconst CONFLICT_CODES = new Set([\"23505\", \"40001\", \"55000\"]);\n\nconst SESSION_MARKERS = [\n \"auth session missing\",\n \"jwt expired\",\n \"no api key found\",\n \"refresh_token_not_found\",\n \"invalid claim: missing sub claim\",\n];\n\n/**\n * THE ONE PLACE a raw database/transport error becomes a typed one. Called at\n * every repository boundary; nothing else in the package inspects a raw error,\n * and no consumer should ever have to.\n */\nexport function normalizeMeetError(error: unknown, operation: string): MeetError {\n if (error instanceof MeetError) return error;\n\n const raw = (error ?? {}) as ErrorLike;\n const message = typeof raw.message === \"string\" ? raw.message : String(error);\n const code = typeof raw.code === \"string\" ? raw.code : undefined;\n const lowered = message.toLowerCase();\n\n if (raw.name === \"SessionUnavailableError\" || SESSION_MARKERS.some((m) => lowered.includes(m))) {\n return new MeetError(\n \"session-unavailable\",\n `${operation}: no Supabase session available (${message})`,\n \"Normal during sign-in and token refresh. The package retries once after the host's \" +\n \"session source resolves; log it as a warning, never as an error.\",\n error,\n );\n }\n if (code !== undefined && FORBIDDEN_CODES.has(code)) {\n // A PERSON reads `message` and `remedy` (toasts, panels); the database's\n // own words stay on `cause` for the developer. Through 0.7.8 this read\n // \"meet.x: denied by the database (42501: …)\" and a remedy about RLS.\n return new MeetError(\n \"forbidden\",\n humanDatabaseSentence(message) ?? \"You don't have access to do that in this meeting.\",\n \"Ask the meeting's host to do it, or to give you access.\",\n error,\n );\n }\n if (code !== undefined && NOT_FOUND_CODES.has(code)) {\n return new MeetError(\n \"not-found\",\n `${operation}: not found (${code}: ${message})`,\n \"The row is gone, or RLS hides it. If this is PGRST205 the communication.meet_* tables \" +\n \"are not deployed yet — see MEET_SCHEMA in repository.ts for the exact contract.\",\n error,\n \"not_found\",\n );\n }\n if (code !== undefined && CONFLICT_CODES.has(code)) {\n return new MeetError(\n \"conflict\",\n humanDatabaseSentence(message) ?? \"This meeting changed a moment ago, so nothing was saved.\",\n \"Reload to see the latest, then try again.\",\n error,\n );\n }\n const hint = (error as { hint?: unknown } | null)?.hint;\n return new MeetError(\n \"transport\",\n /failed to fetch|networkerror|load failed|network request failed/i.test(message)\n ? \"The server could not be reached.\"\n : (humanDatabaseSentence(message) ?? SERVER_FAILED_SENTENCE),\n typeof hint === \"string\" && humanDatabaseSentence(hint) !== null\n ? hint.trim()\n : \"Nothing was changed — retry in a moment; if it keeps happening, reload the page.\",\n error,\n );\n}\n\n/**\n * THE DEVICE ERROR TRANSLATOR — the second-most-copied block in every video app.\n *\n * `getUserMedia` rejects with DOMException names, and every one of them means\n * something a user could fix if we told them. This is the whole reason the\n * device layer lives in the package (C22 test 3: less-than-perfect unless done\n * perfectly).\n */\nexport function normalizeDeviceError(error: unknown, operation: string): MeetError {\n if (error instanceof MeetError) return error;\n const raw = (error ?? {}) as ErrorLike;\n const name = raw.name ?? \"\";\n const message = typeof raw.message === \"string\" ? raw.message : String(error);\n\n switch (name) {\n case \"NotAllowedError\":\n case \"SecurityError\":\n return new MeetError(\n \"permission-denied\",\n `${operation}: camera/microphone permission denied`,\n \"Allow camera and microphone access for this site, then rejoin. On macOS also check \" +\n \"System Settings › Privacy & Security — a browser denied there shows the same error.\",\n error,\n );\n case \"NotFoundError\":\n case \"OverconstrainedError\":\n return new MeetError(\n \"device-not-found\",\n `${operation}: no matching camera/microphone (${message})`,\n \"The selected device is gone — usually an unplugged headset. Pick another device in \" +\n \"the pre-join panel; the package falls back to the system default automatically.\",\n error,\n );\n case \"NotReadableError\":\n case \"AbortError\":\n return new MeetError(\n \"device-in-use\",\n `${operation}: the device is already in use (${message})`,\n \"Another application (or another tab) holds the camera. Close it and try again — the \" +\n \"hardware, not the meeting, is what refused.\",\n error,\n );\n case \"TypeError\":\n return new MeetError(\n \"insecure-context\",\n `${operation}: media devices are unavailable in this context (${message})`,\n \"getUserMedia only exists on https or localhost. Serve the app over https.\",\n error,\n );\n default:\n return new MeetError(\n \"media-unsupported\",\n `${operation}: ${message}`,\n \"This browser refused the media request for a reason the package does not recognize. \" +\n \"Report it with this message so the class gets a named remedy rather than a shrug.\",\n error,\n );\n }\n}\n\n/**\n * SCREEN SHARE IS NOT A CAMERA. `getDisplayMedia` rejects with `NotAllowedError`\n * when the person simply closes the picker, and that is a CHOICE, not a\n * permission problem — so it must never wear the camera/microphone sentence\n * `normalizeDeviceError` gives that same error name.\n *\n * WHAT BROWSERS ACTUALLY REPORT (the reason there are three outcomes, not two):\n * - Chrome / Edge: a closed picker is `NotAllowedError` \"Permission denied\"; an\n * OS-level refusal (macOS Screen Recording not granted) is `NotAllowedError`\n * \"Permission denied by system\"; a permissions-policy / enterprise block is\n * `NotAllowedError` naming the policy or `display-capture`. The MESSAGE is the\n * only difference, so it is what we read.\n * - Firefox and Safari: a closed picker, a denied OS permission and a policy\n * block are ALL `NotAllowedError` with the same generic message (\"The request\n * is not allowed by the user agent or the platform…\"). They CANNOT be told\n * apart. That is the `ambiguous` outcome: the first one stays silent (a person\n * who closed the picker wants no scolding), and the SECOND one in a row shows\n * the notice, so nobody is left clicking a dead button with no explanation.\n */\nexport type ScreenShareDenial = \"blocked\" | \"ambiguous\" | \"other\";\n\nconst SYSTEM_BLOCK = /by system|permissions?[- ]policy|display-capture|disallowed|not allowed by the system/i;\n\nexport function classifyScreenShareError(error: unknown): ScreenShareDenial {\n const read = (value: unknown, key: \"name\" | \"message\"): string => {\n const v = (value as ErrorLike | null | undefined)?.[key];\n return typeof v === \"string\" ? v : \"\";\n };\n const cause = error instanceof MeetError ? error.cause : undefined;\n const denied = [error, cause].some((value) => {\n const name = read(value, \"name\");\n return name === \"NotAllowedError\" || name === \"AbortError\" || name === \"SecurityError\";\n });\n if (denied) {\n const text = [read(error, \"message\"), read(cause, \"message\")].join(\" \");\n return SYSTEM_BLOCK.test(text) || [error, cause].some((v) => read(v, \"name\") === \"SecurityError\")\n ? \"blocked\"\n : \"ambiguous\";\n }\n // A MeetError we made ourselves for a plain denial (no browser error inside).\n if (error instanceof MeetError && error.code === \"permission-denied\" && cause === undefined) {\n return \"ambiguous\";\n }\n return \"other\";\n}\n\n/** True only for a denial that MAY be the person closing the picker. */\nexport function isScreenShareCancel(error: unknown): boolean {\n return classifyScreenShareError(error) === \"ambiguous\";\n}\n\nexport const SCREEN_SHARE_BLOCKED_REMEDY =\n \"Your browser or computer is blocking screen sharing. On a Mac, open System Settings > \" +\n \"Privacy & Security > Screen Recording, turn it on for your browser, then restart the \" +\n \"browser. On a work computer, ask your administrator. You are still in the call.\";\n\nexport function normalizeScreenShareError(error: unknown, operation: string): MeetError {\n if (error instanceof MeetError) return error;\n const raw = (error ?? {}) as ErrorLike;\n const message = typeof raw.message === \"string\" ? raw.message : String(error);\n const kind = classifyScreenShareError(error);\n if (kind === \"blocked\") {\n return new MeetError(\n \"permission-denied\",\n `${operation}: screen sharing is blocked (${message})`,\n SCREEN_SHARE_BLOCKED_REMEDY,\n error,\n );\n }\n if (kind === \"ambiguous\") {\n return new MeetError(\n \"permission-denied\",\n `${operation}: screen sharing was cancelled or refused`,\n \"Choose a screen, window or tab in the picker to share it. If no picker appeared, \" +\n SCREEN_SHARE_BLOCKED_REMEDY,\n error,\n );\n }\n return new MeetError(\n \"media-unsupported\",\n `${operation}: screen sharing is not available (${message})`,\n \"This browser or device could not share a screen. You are still in the call; try a \" +\n \"desktop browser to present.\",\n error,\n );\n}\n\n/** A response whose SHAPE is wrong — the ingress boundary, not the wire. */\nexport function invalidResponse(operation: string, detail: string): MeetError {\n return new MeetError(\n \"invalid-response\",\n `${operation}: ${detail}`,\n \"The server returned a shape this package does not accept. Rendering it would put a lie \" +\n \"on the screen, so it is refused here. Fix the endpoint's return contract.\",\n );\n}\n","/**\n * THE TOKEN FLOW — the ONLY authorization path into a room (R1).\n *\n * 🚨 **THE CLIENT NEVER HOLDS A LIVEKIT SECRET.** Not in an env var, not in a\n * build-time constant, not \"just for local dev\". A LiveKit API secret in a\n * browser bundle mints a token for ANY room in the project, including one the\n * holder was never invited to — it is a full compromise of every meeting in the\n * organization. This package therefore has no code path that could accept one:\n * it asks aidream, and aidream mints.\n *\n * Four hard parts live here so no host reimplements them (C22):\n *\n * 1. **Single flight.** A React strict-mode double effect, a re-render, and a\n * reconnect all ask at once; they share ONE request. Two tokens for one\n * identity is not harmless — LiveKit disconnects the older session, which\n * looks exactly like \"my call randomly drops when I open the tab\".\n * 2. **Expiry is honored BEFORE it bites.** A token is refreshed at 80% of its\n * life, not when a reconnect fails at 100%. A meeting that outlives its\n * token and cannot reconnect is the classic 2-hour-meeting bug.\n * 3. **Guests are first-class, not an exception.** A guest has no session and\n * the request carries a display name instead; the SERVER decides what a\n * guest may do. There is no client-side \"is this person allowed\" branch.\n * 4. **A missing session is a lifecycle moment.** Between sign-in and token\n * arrival, the fetch retries ONCE after the host's session resolves and then\n * reports `session-unavailable` as a warning — never a red error, never a\n * retry storm.\n */\n\nimport type { MeetApi } from \"./api\";\nimport { invalidResponse, MeetError } from \"./errors\";\nimport type { JoinRefusalReason } from \"./reasons\";\nimport type {\n AdmissionState,\n ObserversVisibleTo,\n MeetIdentity,\n ParticipantIdentity,\n ParticipantRole,\n ParticipantStanding,\n RoomName,\n} from \"./types\";\n\nexport interface RoomToken {\n readonly token: string;\n readonly serverUrl: string;\n readonly roomName: RoomName;\n readonly identity: ParticipantIdentity;\n readonly displayName: string;\n /** ISO. The package refreshes at 80% of the remaining life. */\n readonly expiresAt: string;\n /** What the SERVER granted. The client renders from this; it never decides. */\n readonly canPublish: boolean;\n readonly canSubscribe: boolean;\n readonly canRecord: boolean;\n readonly isHost: boolean;\n /**\n * 🚨 THE SERVER'S WORD ON WHO THIS JOINER IS (MRI-A11) — `host · cohost · presenter ·\n * participant · viewer` (CORE-DESIGN §2.1). The same string is minted into the LiveKit\n * token's participant metadata and its `matrx.participant.role` attribute.\n */\n readonly role: ParticipantRole;\n /** Where this joiner stands with the meeting — beside the role, never inside it. */\n readonly standing: ParticipantStanding | null;\n /**\n * 🚨 THE MEETING PASS (CORE-DESIGN §2.5) — the credential for every aidream meet route,\n * sent as `x-meet-pass`. It is NOT the LiveKit token (`token`), which is for the media\n * connection only. Null for an ad-hoc call room (no meeting) or an older server.\n */\n readonly meetingPass: string | null;\n readonly meetingPassExpiresAt: string | null;\n /** The run (`meet_sessions.id`) this join belongs to; null while waiting before any run. */\n readonly sessionId: string | null;\n /** True when the server put this joiner in the lobby (D6). */\n readonly lobby: boolean;\n /**\n * S4: what the mint wrote on the person row — `admitted` · `knocking` · `waiting_host`.\n * A waiting person then reads `GET /v1/meet/state` every `pollSeconds`.\n */\n readonly admissionState?: AdmissionState | undefined;\n readonly pollSeconds?: number | undefined;\n /**\n * Meet wave 5: the breakout room this pass is for, or null for the main room. The\n * pass is still keyed by the MEETING's room name — it is this person's pass for the\n * meeting, and the server accepts a breakout pass wherever the meeting's is asked.\n */\n readonly breakoutRoomId?: string | null | undefined;\n /** HR-360 wave 2 (MD-16): who sees the \"Observing\" line in People (knob). */\n readonly observersVisibleTo?: ObserversVisibleTo | undefined;\n}\n\nexport type { AccessTokenSource } from \"./api\";\n\nexport interface TokenClientOptions {\n /** The ONE org-aware aidream client (MRI-A16). Owns base url and headers. */\n api: MeetApi;\n /** Injected clock, so expiry logic is provable without waiting two hours. */\n now?: (() => number) | undefined;\n /**\n * Where a GUEST's pass survives a page close (MRF-1). Absent means passes\n * live only in memory, which is correct for a member (they have a session to\n * mint a new one from) and is why this is optional rather than assumed.\n */\n storage?: RoomTokenStorage | undefined;\n}\n\n/**\n * 🚨 **WHY A GUEST'S PASS MUST OUTLIVE THE TAB (MRF-1).**\n *\n * A guest has no session, so their only credential for a meeting is the room\n * token this server minted for them. After the meeting ends, `/token` refuses\n * to mint another one — correctly: the room is gone and nobody may rejoin it.\n * So a guest who closes the tab and comes back to read the summary has, in a\n * purely in-memory client, *no way to prove they were ever there* — and the one\n * honest thing left to show them is a refusal.\n *\n * Persisting the pass is what makes the durable link durable for them. It is\n * per-device by nature (it is stored beside the device id that minted it) and\n * it expires on its own: `meet.room_token_ttl_seconds` is the organization's\n * setting for how long, and an expired pass produces the renew-the-link\n * sentence, never \"you were never here\".\n */\nexport interface RoomTokenStorage {\n read(roomName: RoomName): string | null;\n write(roomName: RoomName, serialized: string): void;\n remove(roomName: RoomName): void;\n}\n\n/** `localStorage` when it exists; a no-op that SAYS so otherwise. */\nexport function createWebRoomTokenStorage(\n onDiagnostic?: (message: string, remedy: string) => void,\n): RoomTokenStorage {\n const store = (globalThis as { localStorage?: Storage }).localStorage;\n const key = (roomName: RoomName): string => `mx.meet.pass.${roomName}`;\n if (store === undefined) {\n // NEVER SILENT. A guest in a browser with no storage will be refused the\n // record after they close the tab, and that is worth a line rather than a\n // mystery.\n onDiagnostic?.(\n \"This browser exposes no localStorage, so a guest's meeting pass cannot be kept.\",\n \"A guest who closes the tab will have to open the meeting link again before the \" +\n \"record opens. Signed-in members are unaffected.\",\n );\n return { read: () => null, write: () => undefined, remove: () => undefined };\n }\n return {\n read(roomName) {\n try {\n return store.getItem(key(roomName));\n } catch {\n return null;\n }\n },\n write(roomName, serialized) {\n try {\n store.setItem(key(roomName), serialized);\n } catch {\n /* a full or blocked store is a lost convenience, never a lost join */\n }\n },\n remove(roomName) {\n try {\n store.removeItem(key(roomName));\n } catch {\n /* see write */\n }\n },\n };\n}\n\n/**\n * The meeting pass a GUEST's browser kept for a room (MRF-1), or null — read from the same\n * storage `createWebRoomTokenStorage` writes. Used by \"keep your notes\" after sign-up.\n */\nexport function rememberedMeetingPass(\n roomName: RoomName,\n storage: RoomTokenStorage = createWebRoomTokenStorage(),\n): string | null {\n const raw = storage.read(roomName);\n if (raw === null) return null;\n try {\n const parsed = JSON.parse(raw) as Partial<RoomToken>;\n if (parsed.standing !== \"guest\") return null;\n return typeof parsed.meetingPass === \"string\" && parsed.meetingPass.length > 0\n ? parsed.meetingPass\n : null;\n } catch {\n return null;\n }\n}\n\nexport interface TokenRequest {\n readonly roomName: RoomName;\n readonly identity: MeetIdentity;\n /** Present for a meeting join; absent for an ad-hoc call room. */\n readonly meetingId?: string | undefined;\n /** The public meeting slug, when joining by link without knowing the id. */\n readonly slug?: string | undefined;\n /** Meet wave 5: a breakout room of this meeting; absent = the main room. */\n readonly breakoutRoomId?: string | null | undefined;\n /** HR-360 wave 2 (MD-16): ask to join as a silent observer. The server decides. */\n readonly observe?: boolean | undefined;\n}\n\nexport interface TokenClient {\n /** A currently-valid token, minting or refreshing only when it must. */\n get(request: TokenRequest): Promise<RoomToken>;\n /** Drop the cached token for a room (after a `denied`, or on leave). */\n invalidate(roomName: RoomName): void;\n /** True when the cached token is inside its refresh window. */\n needsRefresh(roomName: RoomName): boolean;\n /**\n * The pass this DEVICE holds for a room, **including an expired one** (MRF-1).\n *\n * 🚨 The expired case is the whole reason this returns the token instead of\n * `null`: \"your pass expired, open the link again\" and \"you were never in\n * this meeting\" are different sentences, and only the caller that can see the\n * expiry can say the right one. Never used to join — `get` mints for that.\n */\n remembered(roomName: RoomName): RoomToken | null;\n}\n\n/** The aidream routes this package calls. ONE place, so a rename is one edit. */\n/**\n * EVERY aidream ROUTE THIS PACKAGE CALLS, in one object.\n *\n * This is the contract in both directions: aidream carries a test\n * (`services/meet/tests/test_meet_service.py`) that reads THIS object out of\n * this source file and fails if the server stops serving one of these paths.\n * The package is the spec.\n *\n * `meetingEnd` is pinned here BEFORE the server serves it (MRI-A6 owns landing\n * it). That is deliberate and it is not a lie on screen: the control that calls\n * it renders, and a 404 from a server that has not landed A6 is reported as the\n * honest sentence \"Ending a meeting is not available on this server yet\" —\n * never a dead button, never a spinner, and never a silent no-op.\n */\nexport const MEET_ROUTES = {\n token: \"/api/v1/meet/token\",\n recordingStart: \"/api/v1/meet/recording/start\",\n recordingStop: \"/api/v1/meet/recording/stop\",\n noteTakerStart: \"/api/v1/meet/agent/start\",\n noteTakerStop: \"/api/v1/meet/agent/stop\",\n /** In-meeting Q&A. Streams NDJSON; body is `{meeting_id, question}` only. */\n intelligenceAsk: \"/api/v1/meet/intelligence/ask\",\n /** A host ends the meeting for everyone. Served by MRI-A6. */\n meetingEnd: \"/api/v1/meet/end\",\n /**\n * THE POST-MEETING RECORD FOR A READER WITH NO DATABASE (MRF-1). A `GET`,\n * authorized by the room token this device already holds, carried in the\n * `x-meet-pass` header — never a query parameter, because a bearer\n * credential in a URL lands in access logs, referrers and history.\n */\n record: \"/api/v1/meet/record\",\n /** A host chooses what guests may read of ONE meeting (MRF-1). */\n recordAccess: \"/api/v1/meet/record/access\",\n /**\n * KEEP YOUR NOTES (2026-10-01): a guest who just created an account presents\n * their kept pass in `x-meet-pass` with their new session, and the\n * meeting they attended becomes theirs to read. Idempotent; never a grant.\n */\n claim: \"/api/v1/meet/claim\",\n /**\n * Meet wave 1 (0.8): bring every invitee up to date after a write — sends the\n * invitation / update / cancellation emails with their `.ics` and re-queues\n * reminders. Idempotent. `{meeting_id}` is substituted by `meetingPath`.\n */\n meetingAnnounce: \"/api/v1/meet/meetings/{meeting_id}/announce\",\n /** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */\n meetingGoogleCalendar: \"/api/v1/meet/meetings/{meeting_id}/google-calendar\",\n /**\n * Meet wave 3: the recap email after the meeting. `GET` = the draft (subject,\n * body from the wrap-up, who it can go to); `POST` = send what the host\n * reviewed (`dry_run` renders and queues nothing). Never sent automatically.\n */\n meetingFollowUp: \"/api/v1/meet/meetings/{meeting_id}/follow-up\",\n /**\n * Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to\n * unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).\n * Decided by the server from the meeting itself; a guest's pass rides\n * `x-meet-pass`.\n */\n moderate: \"/api/v1/meet/moderate\",\n /** Meet wave 2: the SAVED chat — `POST` sends to everyone, `GET ?meeting_id=` reads history. */\n chat: \"/api/v1/meet/chat\",\n /**\n * Meet wave 5: polls, Q&A, the shared whiteboard and breakout rooms. `POST` acts\n * (`{meeting_id, action, …}`), `GET ?meeting_id=` reads what this viewer may see.\n */\n collab: \"/api/v1/meet/collab\",\n /** Meet wave 5: translate finished caption lines into this viewer's language. */\n captionsTranslate: \"/api/v1/meet/captions/translate\",\n /**\n * S4 — THE STATE READ (CORE-DESIGN §2.6): `GET ?meeting_id=` with `x-meet-pass`. This\n * person's admission (lazy expiry applied), whether anyone who can admit is here, the run's\n * phase. Read every `waiting_poll_seconds` while waiting and on every `mx.state.changed`.\n */\n state: \"/api/v1/meet/state\",\n /** S4: `withdraw` (stop waiting) or `leave` (close this tab); the pass may ride in the body. */\n leave: \"/api/v1/meet/leave\",\n /** S4: a pass holder (guests included) saw the recording notice, for this run. */\n consent: \"/api/v1/meet/consent\",\n} as const;\n\n/** A `MEET_ROUTES` template with its `{meeting_id}` filled in, URL-encoded. */\nexport function meetingPath(template: string, meetingId: string): string {\n return template.replace(\"{meeting_id}\", encodeURIComponent(meetingId));\n}\n\n/** The header the meeting pass rides on (CORE-DESIGN §2.5). Pinned on both sides. */\nexport const MEETING_PASS_HEADER = \"x-meet-pass\";\n\n/** The `x-meet-pass` header for a held token, or no header at all. */\nexport function meetingPassHeaders(token: RoomToken | null | undefined): Record<string, string> {\n const pass = token?.meetingPass ?? null;\n return pass !== null && pass.length > 0 ? { [MEETING_PASS_HEADER]: pass } : {};\n}\n\n/** Refresh at 80% of the token's life — never at the moment it dies. */\nconst REFRESH_RATIO = 0.8;\n\ninterface CacheEntry {\n token: RoomToken;\n mintedAt: number;\n inFlight: Promise<RoomToken> | null;\n}\n\nfunction readString(source: Record<string, unknown>, key: string): string | null {\n const value = source[key];\n return typeof value === \"string\" && value.length > 0 ? value : null;\n}\n\nfunction readBoolean(source: Record<string, unknown>, key: string, fallback: boolean): boolean {\n const value = source[key];\n return typeof value === \"boolean\" ? value : fallback;\n}\n\nconst TOKEN_STANDINGS: readonly ParticipantStanding[] = [\"member\", \"invited\", \"outsider\", \"guest\"];\n\nfunction readStanding(source: Record<string, unknown>, key: string): ParticipantStanding | null {\n const value = source[key];\n return TOKEN_STANDINGS.includes(value as ParticipantStanding) ? (value as ParticipantStanding) : null;\n}\n\nconst TOKEN_ROLES: readonly ParticipantRole[] = [\"host\", \"cohost\", \"presenter\", \"participant\", \"viewer\"];\n\n/** A word this package does not know grants nothing — it is not a host. */\nfunction readRole(source: Record<string, unknown>, key: string): ParticipantRole | null {\n const value = source[key];\n return TOKEN_ROLES.includes(value as ParticipantRole) ? (value as ParticipantRole) : null;\n}\n\nexport function parseRoomToken(body: unknown, roomName: RoomName): RoomToken {\n if (typeof body !== \"object\" || body === null) {\n throw invalidResponse(\"meet.token\", \"the response body was not an object\");\n }\n const envelope = body as Record<string, unknown>;\n // aidream's ordinary envelope is `{ success, data, msg }`; a bare object is\n // accepted too so a self-hosted route does not have to imitate it.\n const payload =\n typeof envelope[\"data\"] === \"object\" && envelope[\"data\"] !== null\n ? (envelope[\"data\"] as Record<string, unknown>)\n : envelope;\n\n const isHost = readBoolean(payload, \"is_host\", false);\n const token = readString(payload, \"token\");\n const serverUrl = readString(payload, \"server_url\") ?? readString(payload, \"serverUrl\");\n const identity = readString(payload, \"identity\");\n const expiresAt = readString(payload, \"expires_at\") ?? readString(payload, \"expiresAt\");\n\n if (token === null || serverUrl === null || identity === null) {\n throw invalidResponse(\n \"meet.token\",\n \"the response is missing token, server_url, or identity — joining with a partial \" +\n \"token would fail inside LiveKit with an opaque error instead of here\",\n );\n }\n if (expiresAt === null) {\n throw invalidResponse(\n \"meet.token\",\n \"the response carries no expires_at. Without it the package cannot refresh before \" +\n \"the token dies, and a long meeting silently loses its ability to reconnect\",\n );\n }\n return {\n token,\n serverUrl,\n roomName,\n identity: identity as ParticipantIdentity,\n displayName: readString(payload, \"display_name\") ?? readString(payload, \"displayName\") ?? identity,\n expiresAt,\n canPublish: readBoolean(payload, \"can_publish\", true),\n canSubscribe: readBoolean(payload, \"can_subscribe\", true),\n canRecord: readBoolean(payload, \"can_record\", false),\n isHost,\n // An older server sends `is_host` and no `role`; a guest join sends neither.\n // Deriving the role from what DID arrive keeps a host a host against a\n // server that has not landed MRI-A11 — and never invents a host from\n // silence, because both fallbacks below grant nothing.\n // A word this package does not know grants nothing: `participant`, never a host.\n role: readRole(payload, \"role\") ?? (isHost ? \"host\" : \"participant\"),\n standing: readStanding(payload, \"standing\"),\n meetingPass: readString(payload, \"meeting_pass\"),\n meetingPassExpiresAt: readString(payload, \"meeting_pass_expires_at\"),\n sessionId: readString(payload, \"session_id\"),\n lobby: readBoolean(payload, \"lobby\", false),\n admissionState: (readString(payload, \"admission_state\") as AdmissionState | null) ?? undefined,\n pollSeconds: typeof payload[\"poll_seconds\"] === \"number\" ? (payload[\"poll_seconds\"] as number) : undefined,\n breakoutRoomId: readString(payload, \"breakout_room_id\") ?? null,\n observersVisibleTo: readString(payload, \"observers_visible_to\") === \"hosts\" ? \"hosts\" : \"everyone\",\n };\n}\n\nexport function createTokenClient(options: TokenClientOptions): TokenClient {\n const cache = new Map<RoomName, CacheEntry>();\n const now = options.now ?? (() => Date.now());\n const storage = options.storage;\n\n /** A GUEST's pass is kept; a member's is not — they can always mint again. */\n function remember(token: RoomToken): void {\n if (storage === undefined) return;\n if (token.standing !== \"guest\") return;\n storage.write(token.roomName, JSON.stringify(token));\n }\n\n function recall(roomName: RoomName): RoomToken | null {\n if (storage === undefined) return null;\n const raw = storage.read(roomName);\n if (raw === null) return null;\n try {\n const parsed = JSON.parse(raw) as RoomToken;\n // A stored blob that is not a pass for THIS room proves nothing, and a\n // half-written one must not be handed out as a credential.\n if (typeof parsed.token !== \"string\" || parsed.token.length === 0) return null;\n if (parsed.roomName !== roomName) return null;\n return parsed;\n } catch {\n storage.remove(roomName);\n return null;\n }\n }\n\n function expired(entry: CacheEntry): boolean {\n const expiry = Date.parse(entry.token.expiresAt);\n if (Number.isNaN(expiry)) return true;\n const life = expiry - entry.mintedAt;\n if (life <= 0) return true;\n return now() >= entry.mintedAt + life * REFRESH_RATIO;\n }\n\n async function request(input: TokenRequest, retryOnMissingSession: boolean): Promise<RoomToken> {\n const guest = input.identity.userId === null;\n let response: Response;\n try {\n // THE ORGANIZATION IS A HEADER, NOT ONLY A BODY FIELD — and it is put\n // there by `core/api.ts`, the one client, so no caller can forget it\n // (MRI-A16). A body-only `organization_id` looked right and never\n // arrived: the org-admission middleware refuses first, 400, before the\n // meet router is ever reached.\n response = await options.api.request({\n path: MEET_ROUTES.token,\n method: \"POST\",\n // A GUEST HAS NO SESSION AND IS STILL A FIRST-CLASS JOINER (D6), so\n // only an authenticated join requires one.\n requireAuth: !guest,\n operation: \"meet.token\",\n body: {\n room_name: input.roomName,\n ...(input.meetingId !== undefined ? { meeting_id: input.meetingId } : {}),\n ...(input.slug !== undefined ? { slug: input.slug } : {}),\n ...(input.breakoutRoomId != null ? { breakout_room_id: input.breakoutRoomId } : {}),\n ...(input.observe === true ? { observe: true } : {}),\n organization_id: input.identity.organizationId,\n display_name: input.identity.displayName,\n device_id: input.identity.deviceId,\n guest,\n },\n });\n } catch (error) {\n if (\n retryOnMissingSession &&\n (error as { code?: string }).code === \"session-unavailable\"\n ) {\n // ONE retry, after the host's session source has had a turn. This is\n // the cure for \"909 captured errors in 0.6 seconds\": a read that lands\n // between sign-in and token arrival is a lifecycle moment.\n await Promise.resolve();\n return request(input, false);\n }\n throw error;\n }\n\n if (response.status === 401 || response.status === 403) {\n // THE SERVER'S SENTENCE IS THE ONE A PERSON READS. aidream answers every\n // refusal with `{ message, remedy }` (\"The host did not admit you…\", \"A host\n // removed you…\"); the fallback only covers a body that says nothing.\n const said = await serverSentence(response);\n throw new MeetError(\n \"forbidden\",\n said?.message ?? \"You can't join this meeting.\",\n said?.remedy ??\n \"Ask the person who shared the link to let you in, then open the link again.\",\n undefined,\n said?.reason ?? null,\n );\n }\n if (response.status === 404) {\n throw new MeetError(\n \"not-found\",\n \"meet.token: no such meeting\",\n \"The link is wrong or the meeting was deleted. Show the 'meeting not found' screen; \" +\n \"a durable link that 404s is never a retry.\",\n undefined,\n \"not_found\" satisfies JoinRefusalReason,\n );\n }\n if (response.status === 410) {\n // Meet wave 1: a CANCELLED meeting. The link resolves; the room never opens.\n // The server's own sentence (with the host's reason) is the message.\n const said = await serverSentence(response);\n throw new MeetError(\n \"cancelled\",\n said?.message ?? \"This meeting was cancelled.\",\n \"Nobody can join a cancelled meeting. Show the cancelled screen with this sentence; \" +\n \"ask the host to schedule a new one.\",\n undefined,\n said?.reason ?? (\"cancelled\" satisfies JoinRefusalReason),\n );\n }\n if (response.status === 423) {\n const said = await serverSentence(response);\n throw new MeetError(\n \"room-locked\",\n said?.message ?? \"The meeting is locked.\",\n said?.remedy ??\n \"The host locked the room. Ask them to unlock it, then join again.\",\n undefined,\n said?.reason ?? (\"locked\" satisfies JoinRefusalReason),\n );\n }\n if (response.status === 503) {\n throw new MeetError(\n \"misconfigured\",\n \"meet.token: the meeting service is not configured\",\n \"aidream is missing LIVEKIT_URL / LIVEKIT_API_KEY / LIVEKIT_API_SECRET. That is a \" +\n \"server deployment fix; the client cannot and must not supply them.\",\n );\n }\n if (!response.ok) {\n throw new MeetError(\n \"token\",\n `meet.token: the server returned ${response.status}`,\n \"Transient. The package retries the join once; if it persists check aidream's meet \" +\n \"router logs for the room name in question.\",\n );\n }\n\n return parseRoomToken(await response.json(), input.roomName);\n }\n\n return {\n get(input) {\n const entry = cache.get(input.roomName);\n // A pass for the main room is not a pass for a breakout room, and back.\n const sameRoom =\n entry !== undefined &&\n (entry.token.breakoutRoomId ?? null) === (input.breakoutRoomId ?? null) &&\n // An observer's pass is not a participant's, and back (MD-16).\n (entry.token.role === \"viewer\") === (input.observe === true);\n if (entry !== undefined && sameRoom) {\n if (entry.inFlight !== null) return entry.inFlight;\n if (!expired(entry)) return Promise.resolve(entry.token);\n }\n // SINGLE FLIGHT. Every concurrent caller shares this promise, so a strict\n // mode double-mount cannot mint two tokens for one identity.\n const pending = request(input, true)\n .then((token) => {\n cache.set(input.roomName, { token, mintedAt: now(), inFlight: null });\n // MRF-1: kept HERE, at the one place a pass is ever minted, so no\n // future caller can acquire one without it being remembered.\n remember(token);\n return token;\n })\n .catch((error: unknown) => {\n // A failure is NEVER cached — one wobble would otherwise leave the\n // room unjoinable until a reload.\n cache.delete(input.roomName);\n throw error;\n });\n cache.set(input.roomName, {\n token: entry?.token ?? placeholder(input.roomName),\n mintedAt: entry?.mintedAt ?? now(),\n inFlight: pending,\n });\n return pending;\n },\n invalidate(roomName) {\n cache.delete(roomName);\n // 🚨 THE STORED PASS SURVIVES `invalidate` ON PURPOSE. This is called on\n // leave and after a refused mint, and a guest leaving a meeting is\n // exactly the person who comes back to read its summary. What `get`\n // refuses to REUSE and what the record read may PRESENT are two different\n // questions; clearing the pass here would answer the second one wrong.\n },\n needsRefresh(roomName) {\n const entry = cache.get(roomName);\n return entry === undefined || expired(entry);\n },\n remembered(roomName) {\n // The in-flight `placeholder()` occupies the cache slot with an EMPTY\n // token string and is never handed out — including here, where handing it\n // out would present \"\" as a credential and earn a 403 that means nothing.\n const live = cache.get(roomName)?.token;\n if (live !== undefined && live.token.length > 0) return live;\n return recall(roomName);\n },\n };\n}\n\n/** Never handed out: it only occupies the slot while a mint is in flight. */\nfunction placeholder(roomName: RoomName): RoomToken {\n return {\n token: \"\",\n serverUrl: \"\",\n roomName,\n identity: \"\" as ParticipantIdentity,\n displayName: \"\",\n expiresAt: new Date(0).toISOString(),\n canPublish: false,\n canSubscribe: false,\n canRecord: false,\n isHost: false,\n role: \"participant\",\n standing: null,\n meetingPass: null,\n meetingPassExpiresAt: null,\n sessionId: null,\n lobby: false,\n breakoutRoomId: null,\n };\n}\n\n/**\n * The `{ message, remedy }` aidream puts on a refusal (`MeetError` →\n * `{ error, message, user_message, remedy }`, or FastAPI's `{ detail: {…} }`),\n * or null when the body carries no sentence.\n */\nasync function serverSentence(\n response: Response,\n): Promise<{ message: string; remedy: string | undefined; reason: string | null } | null> {\n try {\n const body = (await response.json()) as Record<string, unknown> | null;\n const source =\n body !== null && typeof body.detail === \"object\" && body.detail !== null\n ? (body.detail as Record<string, unknown>)\n : body;\n if (source === null) return null;\n const message =\n typeof source.user_message === \"string\" && source.user_message.trim() !== \"\"\n ? source.user_message\n : typeof source.message === \"string\" && source.message.trim() !== \"\"\n ? source.message\n : null;\n if (message === null) return null;\n const remedy =\n typeof source.remedy === \"string\" && source.remedy.trim() !== \"\" ? source.remedy : undefined;\n const reason = typeof source.reason === \"string\" && source.reason !== \"\" ? source.reason : null;\n return { message, remedy, reason };\n } catch {\n return null;\n }\n}\n","/**\n * RECORDING — LiveKit Egress, host-controlled, consent-enforced (R3/R5).\n *\n * 🚨 **CONSENT IS NOT OPTIONAL AND NOT A PROP.** There is no\n * `showRecordingIndicator={false}`. Recording a person without telling them is\n * illegal in a two-party-consent jurisdiction and is indefensible everywhere\n * else, so the indicator and the join-time notice are properties of the\n * package, not decisions a consumer gets to make. The only knob is the org's\n * `recordingPolicy` — disabled / host-controlled / always-on — which decides\n * whether recording may happen at all, never whether people are told.\n *\n * 🚨 **THE RECORDING BELONGS TO THE PLATFORM, NOT TO LIVEKIT.** The server\n * lands the artifact in the platform files system and returns a FILE ID. A\n * provider URL would be an orphan the day the room is garbage collected — the\n * reason `RecordingRecord.fileId` is not `url`.\n *\n * The client half is small on purpose: start/stop are aidream calls (the API\n * secret never comes near a browser), and the truth about a recording arrives\n * from the server's webhook through the room channel. This module never\n * *guesses* that a recording started.\n */\n\nimport type { MeetApi } from \"./api\";\nimport { MeetError } from \"./errors\";\nimport type { MeetStore } from \"./store\";\nimport { MEET_ROUTES } from \"./tokens\";\nimport type {\n MeetingId,\n MeetingRecord,\n RecordingState,\n RoomName,\n UserId,\n} from \"./types\";\n\nexport interface RecordingControllerOptions {\n /** The ONE org-aware aidream client (MRI-A16). Owns base url and headers. */\n api: MeetApi;\n store: MeetStore;\n /** Announce the new state to everyone in the room (the indicator is for all). */\n announce?: ((state: RecordingState, by: UserId | null) => void) | undefined;\n /** Persist that this participant saw the notice. Consent is RECORDED (R5). */\n acknowledgeConsent?: ((meetingId: MeetingId) => Promise<void>) | undefined;\n}\n\nexport interface RecordingController {\n /** True when the org policy and the actor's role allow starting a recording. */\n canStart(meeting: MeetingRecord | null, isHost: boolean): boolean;\n start(args: { meetingId: MeetingId; roomName: RoomName; by: UserId }): Promise<void>;\n stop(args: { meetingId: MeetingId; by: UserId }): Promise<void>;\n /** The join-time notice text. One sentence, one place, every surface. */\n consentNotice(meeting: MeetingRecord | null): string | null;\n acknowledge(meetingId: MeetingId): Promise<void>;\n}\n\nexport function createRecordingController(\n options: RecordingControllerOptions,\n): RecordingController {\n async function post(route: string, body: Record<string, unknown>): Promise<void> {\n let response: Response;\n try {\n response = await options.api.request({\n path: route,\n method: \"POST\",\n // A GUEST CAN NEVER START OR STOP A RECORDING (R5) — refused here,\n // before the wire, with the same sentence the hidden control implies.\n requireAuth: true,\n operation: \"meet.recording\",\n body,\n });\n } catch (error) {\n if ((error as { code?: string }).code === \"session-unavailable\") {\n throw new MeetError(\n \"forbidden\",\n \"meet.recording: recording requires an authenticated host\",\n \"A guest can never start or stop a recording (R5). The control is hidden for guests.\",\n );\n }\n throw error;\n }\n if (response.status === 403) {\n throw new MeetError(\n \"forbidden\",\n \"meet.recording: the server refused this recording action\",\n \"The actor is not a host of this meeting, or the org policy is `disabled`. The server \" +\n \"is the authority; do not re-decide it on the client.\",\n );\n }\n if (!response.ok) {\n throw new MeetError(\n \"transport\",\n `meet.recording: the server returned ${response.status}`,\n \"Egress could not be controlled. The recording state stays where it was rather than \" +\n \"showing a recording indicator for a recording that is not running.\",\n );\n }\n }\n\n return {\n canStart(meeting, isHost) {\n if (meeting === null) return false;\n if (meeting.recordingPolicy === \"disabled\") return false;\n // `always-on` is started by the SERVER when the room opens; a host cannot\n // start (or stop) it, which is the point of the policy.\n if (meeting.recordingPolicy === \"always-on\") return false;\n return isHost;\n },\n\n async start({ meetingId, roomName, by }) {\n // OPTIMISTIC ONLY AS FAR AS \"starting\", and marked `\"local\"` so the\n // durable row overrules it the moment it lands (D16). The indicator turns\n // solid when the SERVER says the egress is running — never on a client's\n // hope, and never STUCK on that hope either: `meet_recordings` is\n // subscribed and `matrx.meet.recording` reaches the guests (MRI-A16).\n options.store.setRecording(\"starting\", by, \"local\");\n try {\n await post(MEET_ROUTES.recordingStart, {\n meeting_id: meetingId,\n room_name: roomName,\n });\n } catch (error) {\n options.store.setRecording(\"idle\", null, \"local\");\n throw error;\n }\n options.announce?.(\"starting\", by);\n },\n\n async stop({ meetingId, by }) {\n options.store.setRecording(\"stopping\", options.store.snapshot().recordingStartedBy, \"local\");\n try {\n await post(MEET_ROUTES.recordingStop, { meeting_id: meetingId });\n } catch (error) {\n options.store.setRecording(\n \"recording\",\n options.store.snapshot().recordingStartedBy,\n \"local\",\n );\n throw error;\n }\n options.announce?.(\"stopping\", by);\n },\n\n consentNotice(meeting) {\n if (meeting === null || meeting.recordingPolicy === \"disabled\") return null;\n if (meeting.recordingPolicy === \"always-on\") {\n return \"This meeting is recorded and transcribed automatically. By joining, you consent to being recorded.\";\n }\n return \"The host can record and transcribe this meeting. You will see a recording indicator whenever it is running.\";\n },\n\n async acknowledge(meetingId) {\n await options.acknowledgeConsent?.(meetingId);\n },\n };\n}\n\n/**\n * 🚨 IS A RECORDING IN FLIGHT RIGHT NOW? One definition (MRI-A16).\n *\n * The control used to ask `state === \"idle\"`, which was survivable only while\n * the store never learned any state but its own: the moment the DURABLE states\n * started arriving, a meeting whose first recording had finished sat on\n * `available` forever and the host could never start a second one — the Start\n * button simply stopped existing. `processing`, `available` and `failed` all\n * mean \"nothing is recording\"; only these three mean it is.\n */\nexport const RECORDING_IN_FLIGHT: readonly RecordingState[] = [\n \"starting\",\n \"recording\",\n \"stopping\",\n];\n\nexport function isRecordingInFlight(state: RecordingState): boolean {\n return RECORDING_IN_FLIGHT.includes(state);\n}\n\n/** The indicator's words. One definition, so no surface invents a softer one. */\nexport function recordingLabel(state: RecordingState): string | null {\n switch (state) {\n case \"starting\":\n return \"Starting recording…\";\n case \"recording\":\n return \"Recording\";\n case \"stopping\":\n return \"Stopping recording…\";\n case \"processing\":\n return \"Processing recording…\";\n case \"available\":\n return \"Recording ready\";\n case \"expired\":\n return \"Recording expired\";\n case \"failed\":\n return \"Recording failed\";\n default:\n return null;\n }\n}\n","/**\n * LISTEN-ONLY ATTENDANCE — being IN the room is never contingent on a device.\n *\n * 0.3.1 and every version before it enabled the microphone as the last step of\n * `connect()`, inside the same `try` as the join. So a denied, absent, or busy\n * microphone did not produce a muted participant — it threw, the join was\n * failed with a device remedy, and the user was dropped back to `<PreJoin>`\n * having never entered the meeting. The whole in-room surface (tiles, captions,\n * live notes, Q&A, the note-taker consent notice) was unreachable to anyone\n * without a working mic, which is ordinary: a locked-down laptop, a conference\n * room dial-in, a phone on a call, a browser profile that denies capture by\n * policy, or a person who simply wants to listen. Every competitor allows it.\n *\n * THE CLASS, and how it is closed here:\n *\n * 1. **Connecting and publishing are two different operations.** The engine\n * reaches `connected` the moment the transport is up. Media is attempted\n * AFTER that, per track, and a rejection never unwinds the join.\n * 2. **A track that could not be acquired is STATE, not an exception.** The\n * reason and its remedy — the browser's own, translated by\n * `normalizeDeviceError` — ride in the snapshot so the UI says WHY the mic\n * is off instead of showing a mute button that silently does nothing.\n * 3. **Enabling later runs the SAME code path.** `setMicrophoneEnabled(true)`\n * from the control bar records exactly what the join attempt records, so a\n * person who plugs in a headset mid-meeting is one click from speaking and\n * a person whose second attempt also fails gets the same honest sentence.\n * A separate \"retry\" path is how the two drift.\n * 4. **The notice is one sentence, defined once.** Both the package's own room\n * chrome and any host that renders its own read `listenOnlyNotice()`, so\n * there is no second wording to keep in sync.\n */\n\nimport type { MeetError } from \"./errors\";\n\n/** Why a local track is not publishing, in the browser's terms plus a remedy. */\nexport interface MediaUnavailable {\n readonly code: string;\n readonly message: string;\n readonly remedy: string;\n}\n\nexport interface LocalTrackState {\n /** True only when the track is actually published. Never optimistic. */\n readonly enabled: boolean;\n /**\n * Set when acquisition was ATTEMPTED and refused. `null` covers both \"on\"\n * and \"deliberately off\" — a user who unchecked the mic in pre-join is not\n * looking at an error, they are looking at a choice they made.\n */\n readonly unavailable: MediaUnavailable | null;\n}\n\nexport interface LocalMediaState {\n readonly microphone: LocalTrackState;\n readonly camera: LocalTrackState;\n}\n\nexport const IDLE_TRACK: LocalTrackState = { enabled: false, unavailable: null };\n\nexport const IDLE_MEDIA: LocalMediaState = {\n microphone: IDLE_TRACK,\n camera: IDLE_TRACK,\n};\n\nexport function unavailableFrom(error: MeetError): MediaUnavailable {\n return { code: error.code, message: error.message, remedy: error.remedy };\n}\n\n/**\n * THE SENTENCE. One definition, so the room chrome, a host's own chrome and the\n * tests cannot say three different things about the same fact.\n */\nexport const LISTEN_ONLY_NOTICE =\n \"You're attending without a microphone — others can't hear you\";\n\n/**\n * True when this participant is in the room with NO USABLE microphone — the\n * device was refused or is missing. A person who simply muted themselves has a\n * working microphone and is not \"attending without\" one, so a mute never\n * counts: `unavailable` is the only signal.\n */\nexport function isListenOnly(media: LocalMediaState): boolean {\n return !media.microphone.enabled && media.microphone.unavailable !== null;\n}\n\n/**\n * The notice to render while in the room, or `null` when the mic is live.\n * Returns the plain sentence; the reason and remedy, when there is one, are\n * rendered beside it from `media.microphone.unavailable`.\n */\nexport function listenOnlyNotice(media: LocalMediaState): string | null {\n return isListenOnly(media) ? LISTEN_ONLY_NOTICE : null;\n}\n","/**\n * THE MEET DOMAIN MODEL.\n *\n * D3: an audio call, a video call, and a meeting are ONE thing — a room. An\n * audio call is a room with video off; a meeting is a room plus scheduling,\n * lobby and host metadata. That ruling is expressed here as ONE `MeetRoom`\n * identity carried by both `/calls` and `/meetings`, not as three parallel\n * hierarchies that drift.\n *\n * Identity-bearing values are BRANDED (C22). A `MeetingId` and a `RoomName` are\n * both strings and are NOT interchangeable — passing the meeting's row id where\n * LiveKit wants the room name produces a room nobody else joins, which looks\n * exactly like \"the other person's camera is broken\".\n */\n\ndeclare const brand: unique symbol;\ntype Brand<T, B extends string> = T & { readonly [brand]: B };\n\nexport type MeetingId = Brand<string, \"MeetingId\">;\nexport type CallId = Brand<string, \"CallId\">;\n/** The LiveKit room name. THE MEDIA ROOM KEY — durable for a meeting link. */\nexport type RoomName = Brand<string, \"RoomName\">;\nexport type UserId = Brand<string, \"UserId\">;\nexport type OrganizationId = Brand<string, \"OrganizationId\">;\n/** LiveKit participant identity. `user:<uuid>` or `guest:<opaque>`. */\nexport type ParticipantIdentity = Brand<string, \"ParticipantIdentity\">;\n\nexport const asMeetingId = (value: string): MeetingId => value as MeetingId;\nexport const asCallId = (value: string): CallId => value as CallId;\nexport const asRoomName = (value: string): RoomName => value as RoomName;\nexport const asUserId = (value: string): UserId => value as UserId;\nexport const asOrganizationId = (value: string): OrganizationId =>\n value as OrganizationId;\nexport const asParticipantIdentity = (value: string): ParticipantIdentity =>\n value as ParticipantIdentity;\n\nexport type JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonValue[]\n | { [key: string]: JsonValue };\nexport type JsonObject = { [key: string]: JsonValue };\n\n/** Audio-only or with video. A call escalates; it never becomes a new call. */\nexport type CallMode = \"audio\" | \"video\";\n\n/**\n * WHO IS IN THE ROOM, as far as authorization is concerned.\n *\n * A guest has no `userId` — by construction, not by convention. Everything that\n * requires authentication (host powers, recording, starting a call) takes a\n * `userId` and therefore cannot compile against a guest identity (D6).\n */\nexport interface MeetIdentity {\n readonly userId: UserId | null;\n readonly organizationId: OrganizationId;\n readonly displayName: string;\n readonly avatarUrl: string | null;\n /** Stable per-device value so a reconnect keeps ONE LiveKit identity. */\n readonly deviceId: string;\n}\n\nexport function isGuest(identity: MeetIdentity): boolean {\n return identity.userId === null;\n}\n\n/**\n * WHO SOMEBODY IS IN A ROOM — the SERVER's word, never this client's (MRI-A11).\n *\n * THE ROLE VOCABULARY (CORE-DESIGN §2.1, S3): `host · cohost · presenter · participant ·\n * viewer`. `host` is `meet_meetings.host_user_id`; `cohost` is a real `iam` grant on the\n * meeting. Being a guest is a STANDING (`ParticipantStanding`), never a role: a guest is a\n * `participant` whose standing is `guest`. Pinned against aidream's `config.py` by\n * `test_meet_service.py`.\n */\nexport type ParticipantRole = \"host\" | \"cohost\" | \"presenter\" | \"participant\" | \"viewer\";\n\n/**\n * Where a person stands with the meeting, beside the role: `member` of its organization,\n * `invited` (shared with them), `outsider` (signed in, followed the link), `guest` (no\n * session). The server mints it (attribute `matrx.participant.standing`).\n */\nexport type ParticipantStanding = \"member\" | \"invited\" | \"outsider\" | \"guest\";\n\n/** Rank, so \"can this actor do that to that participant\" is one comparison. */\nexport const ROLE_RANK: Readonly<Record<ParticipantRole, number>> = {\n host: 4,\n cohost: 3,\n presenter: 2,\n participant: 1,\n viewer: 0,\n};\n\n/** Does this standing follow the link-holder (guest) rules? `outsider` and `guest`. */\nexport function isLinkFollower(standing: ParticipantStanding | null | undefined): boolean {\n return standing === \"guest\" || standing === \"outsider\";\n}\n\n/**\n * 🚨 THE ONE PREDICATE FOR \"DOES THIS PERSON HOLD HOST POWERS\".\n *\n * Every host control in this package answers this question and no other. It is\n * a pure function of a SERVER-minted role, so there is no path by which a\n * client's own state can grant it anything.\n */\nexport function isHostRole(role: ParticipantRole): boolean {\n return role === \"host\" || role === \"cohost\";\n}\n\n/**\n * THE SILENT OBSERVER (HR-360 wave 2, MD-16) — the watch-only role `viewer` the core design\n * already froze: publishes nothing, has no tile on anyone's stage, and is listed in People\n * as \"Observing\". The server decides it at the join gate; nothing here grants it.\n */\nexport function isObserverRole(role: ParticipantRole | null | undefined): boolean {\n return role === \"viewer\";\n}\n\n/** Who sees the \"Observing\" line in People — knob `meet.observers_visible_to`. */\nexport type ObserversVisibleTo = \"everyone\" | \"hosts\";\n\n/**\n * THE LOBBY LADDER (D6). A guest link-follower starts `knocking`; the host\n * admits or denies. An authenticated member of the meeting's org is `admitted`\n * on arrival unless the meeting is locked.\n */\nexport type AdmissionState =\n | \"waiting_host\"\n | \"knocking\"\n | \"admitted\"\n | \"denied\"\n | \"expired\"\n | \"withdrawn\"\n | \"removed\"\n | \"ended\";\n\n/** States a person is still WAITING in (CORE-DESIGN §2.2). */\nexport const WAITING_ADMISSION_STATES: readonly AdmissionState[] = [\"waiting_host\", \"knocking\"];\n\nexport interface LobbyEntry {\n readonly identity: ParticipantIdentity;\n readonly displayName: string;\n readonly avatarUrl: string | null;\n readonly userId: UserId | null;\n readonly state: AdmissionState;\n readonly knockedAt: string;\n /** When an unanswered knock lapses (`knock_timeout_minutes`); null = never. */\n readonly knockExpiresAt?: string | null | undefined;\n readonly decidedAt: string | null;\n readonly decidedBy: UserId | null;\n}\n\nexport type ConnectionQuality = \"excellent\" | \"good\" | \"poor\" | \"lost\" | \"unknown\";\n\n/** One person in the room, as the UI needs them. Media state is a FACT here. */\nexport interface MeetParticipant {\n readonly identity: ParticipantIdentity;\n readonly userId: UserId | null;\n readonly displayName: string;\n readonly avatarUrl: string | null;\n readonly role: ParticipantRole;\n /** Where they stand with the meeting (server-minted); null when the server sent none. */\n readonly standing?: ParticipantStanding | null;\n /**\n * Whether the SERVER granted this participant the power to start a recording\n * (MRI-A11). It is not `role === \"host\"`: a meeting whose recording policy is\n * `always-on` is the server's to start and a host may not touch it, and a\n * `disabled` policy grants it to nobody. The client renders this; it never\n * recomputes the policy.\n */\n readonly canRecord: boolean;\n readonly isLocal: boolean;\n readonly isAgent: boolean;\n readonly micEnabled: boolean;\n readonly cameraEnabled: boolean;\n readonly screenSharing: boolean;\n readonly speaking: boolean;\n readonly handRaised: boolean;\n /**\n * When the hand went up (epoch ms), or null. The HAND QUEUE is ordered by it —\n * first raised, first called on (Meet wave 2). Optional so a roster built by a\n * host before 0.8 still type-checks.\n */\n readonly handRaisedAt?: number | null | undefined;\n readonly quality: ConnectionQuality;\n readonly joinedAt: string;\n /**\n * LiveKit participant attributes, verbatim. The note-taker announces itself\n * here (`matrx.agent.consent_notice`), which is how a consent notice can be\n * rendered for a participant whose token metadata this client cannot read.\n */\n readonly attributes: Readonly<Record<string, string>>;\n}\n\nexport type MeetingKind = \"instant\" | \"scheduled\" | \"recurring\";\n\n/**\n * WHAT THE LINK'S CURRENT RUN IS, read through the no-pass link door (`meet_link_state`) so the\n * entry screen can say it before any Join (CORE-DESIGN §2.3). One vocabulary: `reason` is a\n * join-gate reason code (`ended` · `cancelled`) or null; no private data rides it.\n */\nexport interface LinkRunState {\n readonly state: \"open\" | \"not_started\" | \"ended\" | \"cancelled\";\n readonly reason: \"ended\" | \"cancelled\" | null;\n /** `ended`: the next run's start (null = the meeting can never run again). `not_started`: when it starts. */\n readonly nextStart: string | null;\n}\n\nexport interface MeetingRecord {\n readonly id: MeetingId;\n readonly organizationId: OrganizationId;\n /** Durable: the same room name before, during, and after (R3). */\n readonly roomName: RoomName;\n /** The public slug in a meeting link. Durable for recurring meetings. */\n readonly slug: string;\n readonly title: string;\n readonly kind: MeetingKind;\n readonly hostUserId: UserId;\n readonly scheduledFor: string | null;\n readonly scheduledDurationMinutes: number | null;\n readonly startedAt: string | null;\n readonly endedAt: string | null;\n /** Present only on a row resolved through the link door; absent = unknown (never guessed). */\n readonly linkRun?: LinkRunState | undefined;\n readonly locked: boolean;\n readonly lobbyEnabled: boolean;\n readonly recordingPolicy: RecordingPolicy;\n readonly aiEnabled: boolean;\n readonly metadata: JsonObject;\n /*\n * ── MEET WAVE 1 — management (0.8). Always present on a row read from the\n * database by this package; optional in the type only so a hand-built\n * fixture from before 0.8 still compiles.\n */\n /** IANA zone the meeting's wall-clock time is authored in (`America/Los_Angeles`). */\n readonly timeZone?: string;\n /** Description / agenda text. */\n readonly agenda?: string | null;\n /** RFC 5545 RRULE subset — one row and one durable link per series. */\n readonly recurrenceRule?: string | null;\n /** Signed-in members and invitees may enter before a host arrives. */\n readonly joinBeforeHost?: boolean;\n /** Cancel is a STATE: the link still resolves and says so; nobody can join. */\n readonly cancelledAt?: string | null;\n readonly cancellationReason?: string | null;\n /** Archived (soft deleted). Restorable by the host or a co-host. */\n readonly deletedAt?: string | null;\n /** iCalendar SEQUENCE — bumped by the database on every reschedule and on cancel. */\n readonly calendarSequence?: number;\n /** Row version, for the compare-and-swap in `updateMeeting`. */\n readonly version?: number;\n}\n\n/* ─────────────────────────────── MEET WAVE 1 — management vocabulary (0.8) */\n\n/** An invitee's answer. `needs_action` = not answered yet (iCalendar NEEDS-ACTION). */\nexport type RsvpState = \"needs_action\" | \"accepted\" | \"declined\" | \"tentative\";\n/** The answers a person can give. */\nexport type RsvpAnswer = Exclude<RsvpState, \"needs_action\">;\n/** A co-host holds host moderation through a real `iam` admin grant on the meeting. */\nexport type InviteeRole = \"invitee\" | \"cohost\";\n\n/** One person a meeting was sent to (`communication.meet_invitees`). */\nexport interface MeetingInvitee {\n readonly id: string;\n readonly meetingId: MeetingId;\n /** Set when the person has an account — they skip the waiting room and answer in-app. */\n readonly userId: UserId | null;\n readonly email: string | null;\n readonly displayName: string | null;\n readonly role: InviteeRole;\n readonly rsvpState: RsvpState;\n readonly respondedAt: string | null;\n readonly rsvpNote: string | null;\n /** The calendar_sequence of the last invitation they were sent; null = never told. */\n readonly lastSentSequence: number | null;\n readonly lastSentAt: string | null;\n}\n\n/** Who to invite: an account (`userId`) or an address (`email`), or both. */\nexport interface InviteeInput {\n readonly userId?: UserId | undefined;\n readonly email?: string | undefined;\n readonly displayName?: string | undefined;\n /** Omit to keep an existing invitee's role; `cohost` needs the host or a co-host. */\n readonly role?: InviteeRole | undefined;\n}\n\n/** Where one occurrence of a meeting stands. */\nexport type OccurrenceState = \"scheduled\" | \"moved\" | \"cancelled\";\n\n/** One occurrence — for a one-off meeting, the meeting itself. */\nexport interface MeetingOccurrence {\n readonly meetingId: MeetingId;\n /** The start the rule generates (iCalendar RECURRENCE-ID). The key for exceptions. */\n readonly originalStart: string;\n /** When it actually happens (differs from `originalStart` only when `moved`). */\n readonly occurrenceStart: string;\n readonly durationMinutes: number | null;\n readonly state: OccurrenceState;\n readonly sequence: number;\n}\n\n/** One row of the Upcoming list — an occurrence plus what the list shows about its meeting. */\nexport interface UpcomingOccurrence extends MeetingOccurrence {\n readonly organizationId: OrganizationId;\n readonly title: string;\n readonly slug: string;\n readonly kind: MeetingKind;\n readonly hostUserId: UserId;\n readonly timeZone: string;\n readonly meetingCancelled: boolean;\n /** The signed-in reader's own answer, when they are an invitee. */\n readonly myRsvp: RsvpState | null;\n /** `host`, `cohost` or `invitee` — null when the reader simply can see it (org member). */\n readonly myRole: \"host\" | InviteeRole | null;\n}\n\n/** An exception row (`communication.meet_occurrence_overrides`). */\nexport interface OccurrenceOverride {\n readonly id: string;\n readonly meetingId: MeetingId;\n readonly originalStart: string;\n readonly state: \"cancelled\" | \"moved\";\n readonly newStart: string | null;\n readonly newDurationMinutes: number | null;\n readonly reason: string | null;\n readonly calendarSequence: number;\n /** Set when the exception was withdrawn (`restore`) and the occurrence is back on schedule. */\n readonly deletedAt: string | null;\n}\n\n/** Settings a meeting carries; unset ones come from the person's / organization's knobs. */\nexport interface MeetingSettings {\n readonly lobbyEnabled?: boolean | undefined;\n readonly recordingPolicy?: RecordingPolicy | undefined;\n readonly aiEnabled?: boolean | undefined;\n readonly joinBeforeHost?: boolean | undefined;\n}\n\n/** A partial edit. `null` clears a clearable field (agenda, recurrence rule, time). */\nexport interface MeetingChanges extends MeetingSettings {\n readonly title?: string | undefined;\n readonly agenda?: string | null | undefined;\n readonly timeZone?: string | undefined;\n readonly scheduledFor?: string | null | undefined;\n readonly scheduledDurationMinutes?: number | null | undefined;\n readonly recurrenceRule?: string | null | undefined;\n /**\n * When the SERIES moves (start, rule or zone), carry its per-date changes\n * (cancelled or moved occurrences) to the new schedule instead of archiving\n * them — Google Calendar's and Outlook's \"keep changes to individual events\".\n * Absent or false archives them (restorable). Ask the person; never decide.\n */\n readonly keepExceptions?: boolean | undefined;\n}\n\n/** What the emailed RSVP link shows and answers (no account needed). */\nexport type RsvpInvitation =\n | {\n readonly ok: true;\n readonly meeting: {\n readonly title: string;\n readonly slug: string;\n readonly scheduledFor: string | null;\n readonly durationMinutes: number | null;\n readonly timeZone: string;\n readonly agenda: string | null;\n readonly recurrenceRule: string | null;\n readonly cancelledAt: string | null;\n readonly cancellationReason: string | null;\n readonly endedAt: string | null;\n };\n readonly invitee: {\n readonly displayName: string | null;\n readonly email: string | null;\n readonly rsvpState: RsvpState;\n readonly respondedAt: string | null;\n };\n }\n | { readonly ok: false; readonly message: string };\n\n/** Org-configurable, never hardcoded taste (Law 6 / \"opinions become knobs\"). */\nexport type RecordingPolicy = \"disabled\" | \"host-controlled\" | \"always-on\";\n\nexport type RecordingState =\n | \"idle\"\n | \"starting\"\n | \"recording\"\n | \"stopping\"\n | \"processing\"\n | \"available\"\n /** Bytes were purged under retention; notes and transcript remain durable. */\n | \"expired\"\n | \"failed\";\n\n/**\n * WHICH transport last said what the recording is doing (MRI-A16).\n *\n * `local` — this browser's own optimistic `starting`/`stopping`, which is all\n * the indicator ever had through 0.4.7, and the reason it never went solid.\n * `room` — a `matrx.meet.recording` packet, the ONLY feed a guest has.\n * `record` — the durable `meet_recordings` row. It outranks both (D16).\n */\nexport type RecordingSource = \"local\" | \"room\" | \"record\";\n\n/** The server's durable retention receipt; never inferred from a browser clock. */\nexport interface RecordingRetention {\n readonly operationId: string;\n readonly policyId: string;\n readonly policyUpdatedAt: string;\n readonly phase: \"warning\" | \"expired\";\n readonly dueAt: string;\n readonly expiredAt: string | null;\n}\n\nexport interface RecordingRecord {\n readonly id: string;\n readonly meetingId: MeetingId;\n readonly state: RecordingState;\n readonly startedAt: string | null;\n readonly endedAt: string | null;\n readonly startedBy: UserId | null;\n /** The PLATFORM file id. Never a provider URL (R3). */\n readonly fileId: string | null;\n readonly failureReason: string | null;\n /** Present when central retention warned or purged this recording. */\n readonly retention: RecordingRetention | null;\n}\n\n/**\n * WHERE A TRANSCRIPT LINE CAME FROM — and therefore how much it is worth.\n *\n * D16: `lk.transcription` is published by a room participant and its\n * `sender_identity` is client-supplied and unvalidated by LiveKit, so a caption\n * is a HINT — good enough to read live, never good enough to reason from. A\n * `record` line is a durable `meet_transcript_segments` row written server-side\n * by the note-taker: that is the transcript. A record always beats a caption;\n * a caption never overwrites a record.\n */\nexport type TranscriptSource = \"caption\" | \"record\";\n\nexport interface TranscriptSegment {\n readonly id: string;\n readonly meetingId: MeetingId;\n readonly identity: ParticipantIdentity;\n readonly speaker: string;\n readonly text: string;\n readonly startedAt: string;\n /** Interim text is replaced in place by its final; it is never appended. */\n readonly final: boolean;\n readonly source: TranscriptSource;\n /**\n * The `lk.segment_id` this line was captioned under, when known. A durable\n * row carries it too (the worker writes it into the row's metadata), which is\n * what lets a record REPLACE the caption it supersedes rather than doubling it.\n */\n readonly captionId: string | null;\n}\n\n/**\n * SOMEBODY WHO WAS IN THE MEETING — the durable `meet_participants` row, which\n * outlives the LiveKit room (MRI-D2). `MeetParticipant` above is a live tile;\n * THIS is the attendance record a person reads a week later.\n */\nexport interface MeetingAttendee {\n readonly id: string;\n readonly meetingId: MeetingId;\n readonly identity: ParticipantIdentity;\n readonly displayName: string;\n readonly avatarUrl: string | null;\n readonly userId: UserId | null;\n readonly role: ParticipantRole;\n readonly admissionState: AdmissionState;\n /** When this person knocked, and when an unanswered knock lapses (null = never). */\n readonly knockedAt?: string | null | undefined;\n readonly knockExpiresAt?: string | null | undefined;\n readonly isAgent: boolean;\n /** FIRST arrival in the room — a rejoin never moves it. */\n readonly joinedAt: string | null;\n /** Null while in the room, else the last time they left. */\n readonly leftAt: string | null;\n /**\n * Every stay in the room, oldest first (`metadata.sessions`, written by the\n * server's one attendance rule). Empty for a row recorded before stays were\n * kept; the report then falls back to `joinedAt`–`leftAt`.\n */\n readonly sessions?: readonly AttendanceStay[] | undefined;\n}\n\n/** One stay in the room: arrived, and left (null = still there or never recorded). */\nexport interface AttendanceStay {\n readonly joinedAt: string;\n readonly leftAt: string | null;\n}\n\nexport type MeetingNoteKind = \"note\" | \"decision\" | \"action-item\";\n\n/**\n * ONE ROW OF WHAT THE MEETING DECIDED. `meet_notes.kind` is a live CHECK\n * constraint of exactly three values (D5) — there is no `summary` kind and no\n * `transcript` kind — so the wrap-up summary is a `note` carrying\n * `metadata.role = \"summary\"`, and a live-window note carries `metadata.window`.\n * Both are projected here so the UI never re-reads raw metadata.\n */\nexport interface MeetingNote {\n readonly id: string;\n readonly meetingId: MeetingId;\n readonly text: string;\n readonly createdAt: string;\n readonly kind: MeetingNoteKind;\n /** Set ONLY when the assignee resolved to a real user (D18). */\n readonly assigneeUserId: UserId | null;\n /** Who the item is for, as a person reads it. A guest keeps their name here. */\n readonly assigneeDisplayName: string | null;\n /** `\"summary\"` for the wrap-up; `null` for everything else. */\n readonly role: \"summary\" | null;\n /** The live-notes window index this came out of, when it came from one. */\n readonly window: number | null;\n /**\n * 🚨 THE WRAP-UP IS THE AUTHORITATIVE POST-MEETING SET (D20).\n *\n * A live-notes window and the wrap-up both write decisions and action items,\n * and on production 2026-09-09 meeting `9cb-39f8-782` carried the SAME\n * decision twice — once from the note-taker at 04:43:57 and once from the\n * wrap-up at 04:47:15 — and the record view listed both. A reader cannot tell\n * a repeated decision from two decisions.\n *\n * So when the wrap-up lands, the server MARKS the window-written decisions\n * and action items it supersedes (`metadata.superseded_by` = the wrap-up's\n * run id) and never deletes them: the row is still the timeline of what was\n * known at 04:43. This field carries that marker, and the record view and the\n * assistant panel render only the rows where it is `null`.\n */\n readonly supersededBy: string | null;\n /**\n * WHICH TRANSPORT DELIVERED THIS COPY (MRI-A12). The same durable row reaches\n * a signed-in member twice — once as a `meet_notes` row over Postgres Changes\n * (`\"record\"`) and once as a `matrx.meet.note` packet on the media plane\n * (`\"room\"`), which is the ONLY copy a session-less guest can receive, because\n * `anon` holds no grant on the table (MRI-A10).\n *\n * They carry the same id and the same words, so the store renders one note.\n * Where they could ever disagree, `\"record\"` wins and `\"room\"` never overwrites\n * it — the database is the record and the packet is the courtesy copy. That is\n * the same precedence captions and segments already follow (D16).\n */\n readonly source: \"record\" | \"room\";\n}\n\n/**\n * THE WRAP-UP, ANNOUNCED (MRI-A12). The summary, decisions and action items\n * arrive as ordinary notes; this says the pass that wrote them FINISHED, which\n * is the difference between \"no summary yet\" and \"no summary is coming\".\n *\n * 🚨 It reaches a screen only while the room still exists. `POST /v1/meet/end`\n * deletes the LiveKit room and the wrap-up runs afterwards, so on the ordinary\n * ending path nobody is in the room to be told — the post-meeting record view is\n * where the summary is read. A surface must therefore treat `null` as \"not\n * announced\", never as \"not written\".\n */\nexport interface WrapUpAnnouncement {\n readonly meetingId: MeetingId;\n /** The `meet_notes` ids the pass wrote, in the order it wrote them. */\n readonly noteIds: readonly string[];\n /** How many durable transcript segments it summarized. */\n readonly segmentCount: number;\n readonly at: string;\n}\n\n/**\n * THE NOTE-TAKER'S STATE, as the meeting row records it\n * (`meet_meetings.metadata.note_taker.state`, MRI-A2). Five values and no\n * spinner: a surface renders the state it is in, or the server's refusal.\n *\n * - `absent` — nobody summoned it. The control offers to.\n * - `joining` — dispatched; it has not entered the room yet.\n * - `transcribing` — the server recorded active transcription and the exact\n * deterministic actor is present in the room.\n * - `handoff_pending` — a row that said transcribing no longer has that exact\n * actor in the room. The next state and any missed audio are not inferred.\n * - `ended` — the recorded note-taker stopped. The cause is not inferred.\n */\nexport type NoteTakerState =\n | \"absent\"\n | \"joining\"\n | \"transcribing\"\n | \"handoff_pending\"\n | \"ended\";\n\n/** The lifecycle of a 1:1 or ad-hoc group call (R2). */\nexport type CallState =\n | \"idle\"\n | \"dialing\"\n | \"ringing\"\n | \"connecting\"\n | \"active\"\n | \"declined\"\n | \"missed\"\n | \"busy\"\n | \"cancelled\"\n | \"ended\"\n | \"failed\";\n\nexport interface CallInvite {\n readonly id: CallId;\n readonly organizationId: OrganizationId;\n readonly roomName: RoomName;\n readonly mode: CallMode;\n readonly callerUserId: UserId;\n readonly callerName: string;\n readonly callerAvatarUrl: string | null;\n readonly calleeUserIds: readonly UserId[];\n /** The durable conversation the invite was delivered into, if any. */\n readonly conversationId: string | null;\n readonly createdAt: string;\n readonly expiresAt: string;\n readonly state: CallState;\n readonly declineMessage: string | null;\n}\n\n/** A person the UI can call. Deliberately minimal: hosts already have this. */\nexport interface CallablePerson {\n readonly userId: UserId;\n readonly displayName: string;\n readonly avatarUrl: string | null;\n}\n\nexport type StageLayout = \"grid\" | \"speaker\" | \"sidebar\";\n","/**\n * THE PHASE MACHINE — the one place a meeting tab's top-level phase changes (CORE-DESIGN §3.1,\n * step S6a).\n *\n * The values ARE the observation contract's `data-meet-phase` column (HARNESS-CONTRACT §2), so\n * the core renders its own state with no translation layer. Every change goes through\n * `nextPhase(from, event)`: an explicit table of which event may move which phase where. An\n * event the table does not allow from the current phase is REFUSED (the store keeps the phase\n * and reports it) — never applied.\n *\n * 🚨 THE INVARIANT (guard: `phase.test.ts`, `in-call-phase-guard.test.ts`): nothing enters\n * `prejoin` except `resolving` (the link resolved) or the person's own click. In-call outcomes —\n * share, devices, recording, captions, chat, collab, moderation — live in REGIONS and have no\n * event here at all, so the MD-01 class (\"a cancelled share picker threw a live meeting back to\n * Ready to join?\") cannot recur: from `in_call` the only ways out are the exit-reason\n * resolution (§3.4), the person's own Leave, and the meeting ending.\n *\n * 1:1 calls run on this same machine (§3.6): ringing is a pre-room phase set, and an answered\n * call goes `joining` → `in_call` exactly like a meeting.\n */\n\nimport { isJoinRefusalReason, type JoinRefusalReason } from \"./reasons\";\nimport type { AdmissionState } from \"./types\";\n\n/** Every gate reason except `ended`, which renders the `ended` phase (contract §2). */\nexport type RefusedReason = Exclude<JoinRefusalReason, \"ended\">;\n\n/** Where the person is (HARNESS-CONTRACT §2, verbatim). */\nexport type MeetPhase =\n | \"resolving\"\n | \"unsupported\"\n | `refused:${RefusedReason}`\n | \"guest_name\"\n | \"prejoin\"\n | \"joining\"\n | \"waiting:host_not_started\"\n | \"waiting:knocking\"\n | \"denied\"\n | \"knock_expired\"\n | \"in_call\"\n | \"disconnected\"\n | \"left\"\n | \"removed\"\n | \"superseded\"\n | \"inactive\"\n | \"ended\"\n | \"record\"\n | \"ringing_out\"\n | \"ringing_in\"\n | \"call_declined\"\n | \"call_missed\"\n | \"call_busy\"\n | \"answered_elsewhere\";\n\n/** The phases a §3.4 exit-reason resolution can land on. */\nexport type ExitPhase = \"removed\" | \"superseded\" | \"inactive\" | \"ended\" | \"disconnected\" | \"waiting:knocking\";\n\n/** How a 1:1 call ended before it connected (§3.6). */\nexport type CallOutcomePhase = \"call_declined\" | \"call_missed\" | \"call_busy\" | \"answered_elsewhere\";\n\n/**\n * Every event that can move the phase. `click: true` marks the ones only the person's own\n * press may send — the guard test reads it.\n */\nexport type PhaseEvent =\n /** The link resolved on page load / mount: `resolving` → `prejoin` (first entry). */\n | { readonly type: \"resolved\" }\n /** Capability gate failed: this browser cannot join. */\n | { readonly type: \"unsupported\" }\n /**\n * The link's door said, BEFORE any Join, that this run cannot be joined (`ended`, `cancelled`):\n * `resolving` → that screen, never the pre-join with a Join button that will be refused.\n */\n | { readonly type: \"link_state\"; readonly reason: string | null }\n /** The person pressed Join / Rejoin / Ask again. */\n | { readonly type: \"join\"; readonly click: true }\n /** The person chose to go back to the device check from `left` / `disconnected`. */\n | { readonly type: \"back_to_prejoin\"; readonly click: true }\n /** The person pressed Try again on a refusal: look the link up again. */\n | { readonly type: \"retry\"; readonly click: true }\n /** The server said wait (or decided a wait): the admission row's own word. */\n | { readonly type: \"admission\"; readonly admission: AdmissionState }\n /** Admitted from the waiting room: the core mints the real pass and joins. */\n | { readonly type: \"admitted\" }\n /** LiveKit said connected. */\n | { readonly type: \"connected\" }\n /** The join itself failed: a gate reason (→ `refused:*` / `ended`) or none (→ `disconnected`). */\n | { readonly type: \"join_failed\"; readonly reason: string | null }\n /** §3.4: the exit-reason resolution decided where an in-call person went. */\n | { readonly type: \"exit\"; readonly to: ExitPhase }\n /** My Leave (in the room, joining, or withdrawing from the waiting room). */\n | { readonly type: \"leave\" }\n /** The meeting (or this run of it) is over, by any carrier. */\n | { readonly type: \"ended\" }\n /** §3.6: I placed a call / a call rings me while nothing else is on screen. */\n | { readonly type: \"ring_out\" }\n | { readonly type: \"ring_in\" }\n /** §3.6: an answered call (by me, or my callee) — join its room. */\n | { readonly type: \"call_answered\" }\n /** §3.6: the call ended before it connected. */\n | { readonly type: \"call_outcome\"; readonly outcome: CallOutcomePhase }\n /** Back to the start: a different meeting mounted, the call surface was dismissed, or dispose. */\n | { readonly type: \"reset\" };\n\nexport type PhaseEventType = PhaseEvent[\"type\"];\n\n/** Phases in which this tab holds (or is getting) a live media connection. */\nexport const LIVE_PHASES: ReadonlySet<MeetPhase> = new Set<MeetPhase>([\"joining\", \"in_call\"]);\n\n/** The waiting-room phase set (the core reads the server's state while in one of these). */\nexport const WAITING_PHASES: ReadonlySet<MeetPhase> = new Set<MeetPhase>([\n \"waiting:host_not_started\",\n \"waiting:knocking\",\n \"denied\",\n \"knock_expired\",\n]);\n\n/** A phase the person can press Join / Rejoin from. */\nconst JOINABLE_FROM: ReadonlySet<MeetPhase> = new Set<MeetPhase>([\n \"prejoin\",\n \"left\",\n \"disconnected\",\n \"superseded\",\n \"inactive\",\n \"denied\",\n \"knock_expired\",\n]);\n\n/** A call that never connected has no meeting to end; an ended meeting stays ended. */\nconst NOT_ENDABLE: ReadonlySet<MeetPhase> = new Set<MeetPhase>([\n \"ended\",\n \"record\",\n \"unsupported\",\n \"ringing_out\",\n \"ringing_in\",\n \"call_declined\",\n \"call_missed\",\n \"call_busy\",\n \"answered_elsewhere\",\n]);\n\nconst RINGING: ReadonlySet<MeetPhase> = new Set<MeetPhase>([\"ringing_out\", \"ringing_in\"]);\n\n/** Nothing live, nothing pending: a call may take the screen from here. */\nexport const IDLE_PHASES: ReadonlySet<MeetPhase> = new Set<MeetPhase>([\n \"resolving\",\n \"left\",\n \"call_declined\",\n \"call_missed\",\n \"call_busy\",\n \"answered_elsewhere\",\n]);\n\nexport function isLivePhase(phase: MeetPhase): boolean {\n return LIVE_PHASES.has(phase);\n}\n\nexport function isWaitingPhase(phase: MeetPhase): boolean {\n return WAITING_PHASES.has(phase);\n}\n\nexport function isRefusedPhase(phase: MeetPhase): phase is `refused:${RefusedReason}` {\n return phase.startsWith(\"refused:\");\n}\n\n/** The reason a refused / terminal phase carries (`data-meet-reason`). */\nexport function reasonOf(phase: MeetPhase): string | null {\n return isRefusedPhase(phase) ? phase.slice(\"refused:\".length) : null;\n}\n\n/** A gate reason → the phase it renders. `ended` is the ended phase, never `refused:ended`. */\nexport function refusalPhase(reason: string | null): MeetPhase {\n if (reason === null || !isJoinRefusalReason(reason)) return \"disconnected\";\n return reason === \"ended\" ? \"ended\" : (`refused:${reason}` as const);\n}\n\n/** The server's admission word → the waiting phase it renders (§3.1, S4). */\nexport function admissionPhase(admission: AdmissionState): MeetPhase | null {\n switch (admission) {\n case \"waiting_host\":\n return \"waiting:host_not_started\";\n case \"knocking\":\n return \"waiting:knocking\";\n case \"denied\":\n return \"denied\";\n case \"expired\":\n return \"knock_expired\";\n case \"withdrawn\":\n return \"left\";\n case \"ended\":\n return \"ended\";\n case \"removed\":\n return \"refused:removed\";\n default:\n // `admitted` is never a waiting phase: it is a join (`admitted` event).\n return null;\n }\n}\n\n/**\n * THE TABLE. Returns the next phase, or null when `event` may not move `from` (refused).\n * Pure — the store applies it, and the guard tests enumerate it.\n */\nexport function nextPhase(from: MeetPhase, event: PhaseEvent): MeetPhase | null {\n switch (event.type) {\n case \"resolved\":\n return from === \"resolving\" ? \"prejoin\" : null;\n case \"unsupported\":\n return from === \"resolving\" ? \"unsupported\" : null;\n case \"link_state\":\n return from === \"resolving\" && event.reason !== null && isJoinRefusalReason(event.reason)\n ? refusalPhase(event.reason)\n : null;\n case \"join\":\n return JOINABLE_FROM.has(from) ? \"joining\" : null;\n case \"back_to_prejoin\":\n return from === \"left\" || from === \"disconnected\" ? \"prejoin\" : null;\n case \"retry\":\n return isRefusedPhase(from) || from === \"unsupported\" ? \"resolving\" : null;\n case \"admission\": {\n // The server's word on a wait applies while joining or waiting — never to a live call\n // (being sent back to waiting from the room is an EXIT, resolved by §3.4).\n if (from !== \"joining\" && !WAITING_PHASES.has(from)) return null;\n return admissionPhase(event.admission);\n }\n case \"admitted\":\n return WAITING_PHASES.has(from) ? \"joining\" : null;\n case \"connected\":\n return from === \"joining\" ? \"in_call\" : null;\n case \"join_failed\":\n // ONLY a join can fail the join. A failure reported while in the room is not this event.\n return from === \"joining\" ? refusalPhase(event.reason) : null;\n case \"exit\":\n return from === \"in_call\" || from === \"joining\" ? event.to : null;\n case \"leave\":\n return from === \"in_call\" || from === \"joining\" || WAITING_PHASES.has(from) ? \"left\" : null;\n case \"ended\":\n return NOT_ENDABLE.has(from) ? null : \"ended\";\n case \"ring_out\":\n return IDLE_PHASES.has(from) || from === \"prejoin\" ? \"ringing_out\" : null;\n case \"ring_in\":\n return IDLE_PHASES.has(from) ? \"ringing_in\" : null;\n case \"call_answered\":\n return RINGING.has(from) ? \"joining\" : null;\n case \"call_outcome\":\n return RINGING.has(from) ? event.outcome : null;\n case \"reset\":\n // Never out from under a live connection: leave first.\n return LIVE_PHASES.has(from) ? null : \"resolving\";\n }\n}\n\n/* ------------------------------------------------------- §3.4 exit-reason resolution (ER) */\n\n/** What the server's state read (`GET /v1/meet/state?connection=`) says about this exit. */\nexport interface ExitServerWord {\n /** `meet_connections.exit_reason` of THIS connection, if the server recorded one. */\n readonly exitReason: string | null;\n /** The run's phase: `ended` means the meeting (this run) is over. */\n readonly sessionPhase: string | null;\n readonly meetingPhase?: string | null;\n}\n\nexport interface ExitInput {\n /** The server's word; `null` = the state read failed (offline / unreachable); undefined = no ST for this room (a call). */\n readonly server: ExitServerWord | null | undefined;\n /** LiveKit's `DisconnectReason` name (`PARTICIPANT_REMOVED`, `DUPLICATE_IDENTITY`, …), or null. */\n readonly hint: string | null;\n /** The page was frozen (Page Lifecycle) when the connection dropped. */\n readonly frozen?: boolean;\n}\n\nexport interface ExitResolution {\n readonly to: ExitPhase;\n /** `data-meet-reason` for the exit (null when the phase says it all). */\n readonly reason: string | null;\n /** `data-meet-connection` to show beside `disconnected`. */\n readonly connection: \"stable\" | \"offline\" | \"server_unreachable\";\n /** A notice code the skin renders with it (`sent-to-waiting`), when one applies. */\n readonly notice: string | null;\n}\n\n/** §3.4, row by row. Pure: the engine gathers the inputs, this decides. */\nexport function resolveExit(input: ExitInput): ExitResolution {\n const { server, hint } = input;\n if (server === null) {\n return { to: \"disconnected\", reason: null, connection: \"server_unreachable\", notice: null };\n }\n const word = server?.exitReason ?? null;\n if (server !== undefined && (server.sessionPhase === \"ended\" || server.meetingPhase === \"ended\" || word === \"ended\" || word === \"time_limit\")) {\n return { to: \"ended\", reason: word === \"time_limit\" ? \"time_limit\" : \"ended\", connection: \"stable\", notice: null };\n }\n if (word === \"removed\") return { to: \"removed\", reason: \"removed\", connection: \"stable\", notice: null };\n if (word === \"displaced\") return { to: \"superseded\", reason: \"displaced\", connection: \"stable\", notice: null };\n if (word === \"sent_to_waiting\") {\n return { to: \"waiting:knocking\", reason: \"sent_to_waiting\", connection: \"stable\", notice: \"sent-to-waiting\" };\n }\n if (hint === \"DUPLICATE_IDENTITY\") return { to: \"superseded\", reason: \"displaced\", connection: \"stable\", notice: null };\n if (hint === \"PARTICIPANT_REMOVED\" && word === null && server === undefined) {\n // A call room has no state read: LiveKit's own word is all there is.\n return { to: \"removed\", reason: \"removed\", connection: \"stable\", notice: null };\n }\n if (hint === \"ROOM_DELETED\" && server === undefined) {\n return { to: \"ended\", reason: \"ended\", connection: \"stable\", notice: null };\n }\n if (input.frozen === true) return { to: \"inactive\", reason: \"inactive\", connection: \"stable\", notice: null };\n return { to: \"disconnected\", reason: null, connection: \"stable\", notice: null };\n}\n","/**\n * THE APP PANEL SLOT (HR-360 wave 2, Meet register MD-15).\n *\n * A meeting can carry one APP PANEL: a surface the HOST APP registered under a key, opened\n * beside the call on one record (`meet_meetings.metadata.app_panel = {key, record_id}`,\n * written by `meet_schedule_meeting`'s `app_panel` setting). The package knows only the key\n * and the record id; the host app's registered component reads the record through the\n * person's OWN doors, so someone who cannot read the record sees nothing of it.\n *\n * Everyone in the call looks at the same SECTION of the panel: one shared focus per run,\n * kept by the server and carried on the existing collab packet (`collab.focus`,\n * action `focus_set`) — never a second channel.\n */\n\nimport { settingsPatch } from \"./repository\";\nimport type { MeetingRecord, MeetingSettings } from \"./types\";\n\nexport interface MeetingAppPanel {\n /** The registered surface key, e.g. `hr.review_360`. */\n readonly key: string;\n /** The record the panel opens on. */\n readonly recordId: string;\n}\n\n/** The app panel this meeting carries, or null. Malformed values are never guessed at. */\nexport function appPanelOf(meeting: Pick<MeetingRecord, \"metadata\"> | null | undefined): MeetingAppPanel | null {\n const raw = meeting?.metadata?.[\"app_panel\"];\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) return null;\n const key = (raw as Record<string, unknown>)[\"key\"];\n const recordId = (raw as Record<string, unknown>)[\"record_id\"];\n if (typeof key !== \"string\" || key.length === 0) return null;\n if (typeof recordId !== \"string\" || recordId.length === 0) return null;\n return { key, recordId };\n}\n\n/**\n * HR-360 wave 3: the server held capture off because the meeting is about a Confidential record\n * (`metadata.capture_hold = \"confidential_record\"`, stamped by the meet_meetings trigger) and it\n * is still off. The room says so in one short line. Once the organization turns capture back on\n * (knob `meet.confidential_capture`), it is no longer held.\n */\nexport function captureHeldOf(\n meeting: Pick<MeetingRecord, \"metadata\" | \"aiEnabled\" | \"recordingPolicy\"> | null | undefined,\n): boolean {\n if (!meeting) return false;\n return (\n meeting.metadata?.[\"capture_hold\"] === \"confidential_record\" &&\n !meeting.aiEnabled &&\n meeting.recordingPolicy === \"disabled\"\n );\n}\n\n/** The `p_settings` of `meet_schedule_meeting`, with the panel and the named observers. */\nexport function scheduleSettingsPatch(\n settings: MeetingSettings,\n appPanel: MeetingAppPanel | undefined,\n observers: readonly string[] | undefined,\n): Record<string, unknown> {\n const patch = settingsPatch(settings);\n if (appPanel !== undefined) patch[\"app_panel\"] = { key: appPanel.key, record_id: appPanel.recordId };\n if (observers !== undefined && observers.length > 0) patch[\"observers\"] = [...observers];\n return patch;\n}\n\n/** The one shared section focus of the meeting's app panel (`collab.focus`). */\nexport interface MeetAppFocus {\n readonly panel: string;\n readonly section: string;\n readonly byName: string;\n readonly at: string;\n}\n\nexport function focusFromRow(value: unknown): MeetAppFocus | null {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return null;\n const row = value as Record<string, unknown>;\n if (typeof row[\"panel\"] !== \"string\") return null;\n return {\n panel: row[\"panel\"],\n section: typeof row[\"section\"] === \"string\" ? row[\"section\"] : \"\",\n byName: typeof row[\"by_name\"] === \"string\" ? row[\"by_name\"] : \"\",\n at: typeof row[\"at\"] === \"string\" ? row[\"at\"] : \"\",\n };\n}\n","/**\n * MEETING ROOM ITEMS — polls, Q&A, the shared whiteboard and breakout rooms (Meet wave 5).\n *\n * The server decides every one of them (`aidream/services/meet/collab.py`,\n * `breakouts.py`): who may create, launch, vote, ask, answer, draw, erase, open rooms or\n * move people — and what each viewer SEES (a draft poll is the host's alone; counts\n * reach everyone only once shared; anonymous votes and questions carry no identity;\n * a dismissed question stays with the hosts). This file only holds what the server\n * said and folds the room's `matrx.meet.collab` packets into it.\n *\n * Two reads, one truth: `GET /v1/meet/collab` once per join (a late joiner sees every\n * poll, question, stroke and the open breakout plan), then packets. Every item is keyed\n * by its row id, so the same item arriving twice is one item.\n */\n\nimport { readServerRefusal } from \"./ai\";\nimport type { MeetApi } from \"./api\";\nimport { MEET_ROUTES, MEETING_PASS_HEADER } from \"./tokens\";\nimport type { ParticipantIdentity } from \"./types\";\nimport { focusFromRow, type MeetAppFocus } from \"./app-panel\";\n\nexport interface MeetPollOption {\n readonly id: string;\n readonly text: string;\n /** Present when this viewer may see counts (a host, or results shared). */\n readonly votes?: number | undefined;\n /** Who picked it — hosts only, never for an anonymous poll. */\n readonly voters?: readonly string[] | undefined;\n}\n\nexport type MeetPollState = \"draft\" | \"live\" | \"closed\";\n\nexport interface MeetPoll {\n readonly id: string;\n readonly question: string;\n readonly options: readonly MeetPollOption[];\n readonly multiple: boolean;\n readonly anonymous: boolean;\n readonly state: MeetPollState;\n readonly resultsShared: boolean;\n readonly voterCount: number | null;\n /** This viewer's own answer — kept locally, since a room packet cannot know it. */\n readonly myVote: readonly string[];\n readonly createdByName: string;\n readonly createdAt: string | null;\n}\n\nexport type MeetQuestionState = \"open\" | \"answered\" | \"dismissed\";\n\nexport interface MeetQuestion {\n readonly id: string;\n readonly text: string;\n readonly askerName: string;\n readonly anonymous: boolean;\n readonly state: MeetQuestionState;\n readonly answer: string | null;\n readonly answeredByName: string | null;\n readonly upvotes: number;\n readonly upvotedByMe: boolean;\n readonly mine: boolean;\n readonly createdAt: string | null;\n}\n\nexport interface MeetStroke {\n readonly id: string;\n /** Points in the board's own 0..1 space. */\n readonly points: readonly (readonly [number, number])[];\n readonly color: string;\n readonly width: number;\n readonly tool: \"pen\" | \"highlighter\";\n readonly by: string;\n readonly byName: string;\n /** True while this browser's stroke has not come back from the server. */\n readonly pending?: boolean | undefined;\n}\n\nexport interface MeetBreakoutRoom {\n readonly id: string;\n readonly name: string;\n readonly identities: readonly string[];\n}\n\nexport interface MeetBreakoutBroadcast {\n readonly text: string;\n readonly by: string;\n readonly at: string;\n}\n\nexport interface MeetBreakoutPlan {\n readonly id: string;\n readonly state: \"open\" | \"closed\";\n readonly rooms: readonly MeetBreakoutRoom[];\n readonly allowChoose: boolean;\n readonly minutes: number;\n readonly openedAt: string | null;\n readonly endsAt: string | null;\n readonly closedAt: string | null;\n readonly broadcasts: readonly MeetBreakoutBroadcast[];\n}\n\n/** The org's knobs for this meeting — which controls EXIST (absent, never dead). */\nexport interface MeetCollabSettings {\n readonly pollsEnabled: boolean;\n readonly pollMaxOptions: number;\n readonly qaEnabled: boolean;\n readonly qaAnonymousAllowed: boolean;\n readonly whiteboardEnabled: boolean;\n readonly breakoutsEnabled: boolean;\n readonly breakoutMaxRooms: number;\n readonly breakoutDefaultMinutes: number;\n readonly captionTranslationEnabled: boolean;\n /** Empty = the viewer's own language. */\n readonly captionTranslationLanguage: string;\n}\n\nexport type CollabPanel = \"polls\" | \"qa\" | \"whiteboard\" | \"breakouts\";\n\nexport interface CollabSnapshot {\n readonly loaded: boolean;\n /** Why the state could not be read, in words — or null. */\n readonly error: string | null;\n readonly settings: MeetCollabSettings | null;\n readonly polls: readonly MeetPoll[];\n readonly questions: readonly MeetQuestion[];\n readonly strokes: readonly MeetStroke[];\n readonly breakout: MeetBreakoutPlan | null;\n /** The newest message a host sent to every breakout room, for the banner. */\n readonly broadcast: MeetBreakoutBroadcast | null;\n /** A host moved THIS person (or everyone came back): where to go. `undefined` = nothing asked. */\n readonly moveRequest: { readonly roomId: string | null; readonly at: number } | null;\n /** Something asked a panel to open (a poll launched, a question arrived for a host). */\n readonly panelRequest: { readonly panel: CollabPanel; readonly at: number } | null;\n /** Caption translations for THIS viewer: line id → text, for `captionLanguage`. */\n readonly captionLanguage: string | null;\n readonly captionTranslations: Readonly<Record<string, string>>;\n readonly captionTranslationError: string | null;\n /**\n * HR-360 wave 2 (MD-15): the meeting's app panel's ONE shared section focus for this run,\n * as the server keeps it (`focus_set`); optional so a snapshot built before 0.11 compiles.\n */\n readonly focus?: MeetAppFocus | null;\n}\n\nexport const EMPTY_COLLAB: CollabSnapshot = {\n loaded: false,\n error: null,\n settings: null,\n polls: [],\n questions: [],\n strokes: [],\n breakout: null,\n broadcast: null,\n moveRequest: null,\n panelRequest: null,\n captionLanguage: null,\n captionTranslations: {},\n captionTranslationError: null,\n focus: null,\n};\n\ntype Row = Record<string, unknown>;\n\nfunction obj(value: unknown): Row | null {\n return typeof value === \"object\" && value !== null && !Array.isArray(value) ? (value as Row) : null;\n}\nfunction str(value: unknown): string | null {\n return typeof value === \"string\" ? value : null;\n}\nfunction num(value: unknown): number | null {\n return typeof value === \"number\" && Number.isFinite(value) ? value : null;\n}\nfunction strings(value: unknown): string[] {\n return Array.isArray(value) ? value.filter((v): v is string => typeof v === \"string\") : [];\n}\n\nexport function pollFromRow(row: Row): MeetPoll | null {\n const id = str(row[\"id\"]);\n if (id === null) return null;\n const options: MeetPollOption[] = [];\n for (const raw of Array.isArray(row[\"options\"]) ? row[\"options\"] : []) {\n const option = obj(raw);\n const optionId = option === null ? null : str(option[\"id\"]);\n if (option === null || optionId === null) continue;\n const votes = num(option[\"votes\"]);\n options.push({\n id: optionId,\n text: str(option[\"text\"]) ?? \"\",\n ...(votes !== null ? { votes } : {}),\n ...(Array.isArray(option[\"voters\"]) ? { voters: strings(option[\"voters\"]) } : {}),\n });\n }\n const state = str(row[\"state\"]);\n return {\n id,\n question: str(row[\"question\"]) ?? \"\",\n options,\n multiple: row[\"multiple\"] === true,\n anonymous: row[\"anonymous\"] === true,\n state: state === \"live\" || state === \"closed\" ? state : \"draft\",\n resultsShared: row[\"results_shared\"] === true,\n voterCount: num(row[\"voter_count\"]),\n myVote: strings(row[\"my_vote\"]),\n createdByName: str(row[\"created_by_name\"]) ?? \"\",\n createdAt: str(row[\"created_at\"]),\n };\n}\n\nexport function questionFromRow(row: Row): MeetQuestion | null {\n const id = str(row[\"id\"]);\n if (id === null) return null;\n const state = str(row[\"state\"]);\n return {\n id,\n text: str(row[\"text\"]) ?? \"\",\n askerName: str(row[\"asker_name\"]) ?? \"\",\n anonymous: row[\"anonymous\"] === true,\n state: state === \"answered\" || state === \"dismissed\" ? state : \"open\",\n answer: str(row[\"answer\"]),\n answeredByName: str(row[\"answered_by_name\"]),\n upvotes: num(row[\"upvotes\"]) ?? 0,\n upvotedByMe: row[\"upvoted_by_me\"] === true,\n mine: row[\"mine\"] === true,\n createdAt: str(row[\"created_at\"]),\n };\n}\n\nexport function strokeFromRow(row: Row): MeetStroke | null {\n const id = str(row[\"id\"]);\n if (id === null || !Array.isArray(row[\"points\"])) return null;\n const points: (readonly [number, number])[] = [];\n for (const raw of row[\"points\"]) {\n if (Array.isArray(raw) && typeof raw[0] === \"number\" && typeof raw[1] === \"number\") {\n points.push([raw[0], raw[1]]);\n }\n }\n return {\n id,\n points,\n color: str(row[\"color\"]) ?? \"#111827\",\n width: num(row[\"width\"]) ?? 3,\n tool: row[\"tool\"] === \"highlighter\" ? \"highlighter\" : \"pen\",\n by: str(row[\"by\"]) ?? \"\",\n byName: str(row[\"by_name\"]) ?? \"\",\n };\n}\n\nfunction broadcastFrom(value: unknown): MeetBreakoutBroadcast | null {\n const row = obj(value);\n const text = row === null ? null : str(row[\"text\"]);\n if (row === null || text === null) return null;\n return { text, by: str(row[\"by\"]) ?? \"\", at: str(row[\"at\"]) ?? \"\" };\n}\n\nexport function planFromRow(row: Row): MeetBreakoutPlan | null {\n const id = str(row[\"id\"]);\n if (id === null) return null;\n const rooms: MeetBreakoutRoom[] = [];\n for (const raw of Array.isArray(row[\"rooms\"]) ? row[\"rooms\"] : []) {\n const room = obj(raw);\n const roomId = room === null ? null : str(room[\"id\"]);\n if (room === null || roomId === null) continue;\n rooms.push({ id: roomId, name: str(room[\"name\"]) ?? roomId, identities: strings(room[\"identities\"]) });\n }\n return {\n id,\n state: row[\"state\"] === \"open\" ? \"open\" : \"closed\",\n rooms,\n allowChoose: row[\"allow_choose\"] === true,\n minutes: num(row[\"minutes\"]) ?? 0,\n openedAt: str(row[\"opened_at\"]),\n endsAt: str(row[\"ends_at\"]),\n closedAt: str(row[\"closed_at\"]),\n broadcasts: (Array.isArray(row[\"broadcasts\"]) ? row[\"broadcasts\"] : [])\n .map(broadcastFrom)\n .filter((b): b is MeetBreakoutBroadcast => b !== null),\n };\n}\n\nexport function settingsFromRow(row: Row | null): MeetCollabSettings | null {\n if (row === null) return null;\n return {\n pollsEnabled: row[\"polls_enabled\"] === true,\n pollMaxOptions: num(row[\"poll_max_options\"]) ?? 10,\n qaEnabled: row[\"qa_enabled\"] === true,\n qaAnonymousAllowed: row[\"qa_anonymous_allowed\"] === true,\n whiteboardEnabled: row[\"whiteboard_enabled\"] === true,\n breakoutsEnabled: row[\"breakouts_enabled\"] === true,\n breakoutMaxRooms: num(row[\"breakout_max_rooms\"]) ?? 20,\n breakoutDefaultMinutes: num(row[\"breakout_default_minutes\"]) ?? 15,\n captionTranslationEnabled: row[\"caption_translation_enabled\"] === true,\n captionTranslationLanguage: str(row[\"caption_translation_language\"]) ?? \"\",\n };\n}\n\n/** The server's `GET /collab` body → a loaded snapshot (keeps this viewer's caption state). */\nexport function collabFromState(body: Row, previous: CollabSnapshot): CollabSnapshot {\n const list = <T>(key: string, parse: (row: Row) => T | null): T[] =>\n (Array.isArray(body[key]) ? body[key] : [])\n .map((raw) => {\n const row = obj(raw);\n return row === null ? null : parse(row);\n })\n .filter((item): item is T => item !== null);\n const breakoutRow = obj(body[\"breakout\"]);\n return {\n ...previous,\n loaded: true,\n error: null,\n settings: settingsFromRow(obj(body[\"settings\"])),\n polls: list(\"polls\", pollFromRow),\n questions: list(\"questions\", questionFromRow),\n strokes: list(\"strokes\", strokeFromRow),\n breakout: breakoutRow === null ? null : planFromRow(breakoutRow),\n focus: focusFromRow(body[\"focus\"]),\n };\n}\n\nfunction upsert<T extends { id: string }>(items: readonly T[], next: T): T[] {\n const index = items.findIndex((item) => item.id === next.id);\n if (index === -1) return [...items, next];\n const copy = items.slice();\n copy[index] = next;\n return copy;\n}\n\n/**\n * Fold one server item into the snapshot. `fromRoom` = it came on the room broadcast,\n * which cannot know THIS viewer's vote, upvote or authorship — those are kept from what\n * the viewer's own reads and answers already said.\n */\nexport function withItem(collab: CollabSnapshot, row: Row, fromRoom = false): CollabSnapshot {\n const kind = str(row[\"kind\"]);\n if (kind === \"poll\") {\n const poll = pollFromRow(row);\n if (poll === null) return collab;\n // A ROOM packet cannot know this viewer's vote; keep the one we already hold.\n const known = collab.polls.find((p) => p.id === poll.id);\n let merged = fromRoom && known !== undefined ? { ...poll, myVote: known.myVote } : poll;\n // A room copy carries no counts until results are shared; a HOST already holding\n // counts (from their own read or the hosts' packet) keeps them until newer ones land.\n if (fromRoom && known !== undefined && !poll.options.some((o) => o.votes !== undefined)) {\n const counted = new Map(known.options.map((o) => [o.id, o]));\n merged = {\n ...merged,\n voterCount: known.voterCount,\n options: merged.options.map((o) => {\n const had = counted.get(o.id);\n return had === undefined ? o : { ...o, votes: had.votes, voters: had.voters };\n }),\n };\n }\n return { ...collab, polls: upsert(collab.polls, merged) };\n }\n if (kind === \"question\") {\n const question = questionFromRow(row);\n if (question === null) return collab;\n const known = collab.questions.find((q) => q.id === question.id);\n // `mine` / `upvoted_by_me` are per viewer; a room packet says false for everyone.\n const merged =\n fromRoom && known !== undefined\n ? { ...question, mine: known.mine, upvotedByMe: known.upvotedByMe }\n : question;\n return { ...collab, questions: upsert(collab.questions, merged) };\n }\n if (kind === \"stroke\") {\n const stroke = strokeFromRow(row);\n if (stroke === null) return collab;\n return { ...collab, strokes: upsert(collab.strokes, stroke) };\n }\n if (kind === \"breakout\") {\n const plan = planFromRow(row);\n if (plan === null) return collab;\n return { ...collab, breakout: plan };\n }\n return collab;\n}\n\nfunction without(collab: CollabSnapshot, ids: readonly string[]): CollabSnapshot {\n const gone = new Set(ids);\n return {\n ...collab,\n polls: collab.polls.filter((p) => !gone.has(p.id)),\n questions: collab.questions.filter((q) => !gone.has(q.id)),\n strokes: collab.strokes.filter((s) => !gone.has(s.id)),\n };\n}\n\nexport interface CollabPacketContext {\n readonly localIdentity: ParticipantIdentity | null;\n /** Hosts and co-hosts receive a second, fuller packet; they ignore the room copy. */\n readonly isHost: boolean;\n readonly now: number;\n}\n\n/**\n * One `matrx.meet.collab` packet → the next snapshot, or `null` when it must be dropped\n * (with the reason in `dropped`). Pure; the room-event dispatcher patches the store.\n */\nexport function applyCollabPacket(\n collab: CollabSnapshot,\n payload: Row,\n context: CollabPacketContext,\n): { next: CollabSnapshot | null; dropped?: string } {\n const item = obj(payload[\"item\"]);\n if (item !== null) {\n const kind = str(item[\"kind\"]);\n // Every copy is folded — the room's AND the hosts' fuller one. `withItem` keeps a\n // host's counts when a count-less room copy arrives, so neither copy can blank the\n // other, and a host still sees an item the server sent the room only (a new question).\n let next = withItem(collab, item, true);\n if (kind === \"poll\" && str(item[\"state\"]) === \"live\") {\n const before = collab.polls.find((p) => p.id === str(item[\"id\"]));\n if (before === undefined || before.state !== \"live\") {\n next = { ...next, panelRequest: { panel: \"polls\", at: context.now } };\n }\n }\n if (kind === \"breakout\") {\n const plan = planFromRow(item);\n if (plan !== null && plan.state === \"closed\") {\n next = { ...next, moveRequest: { roomId: null, at: context.now } };\n }\n }\n const move = obj(payload[\"move\"]);\n if (move !== null && str(move[\"identity\"]) === context.localIdentity) {\n next = { ...next, moveRequest: { roomId: str(move[\"room_id\"]), at: context.now } };\n }\n return { next };\n }\n if (payload[\"focus\"] !== undefined) {\n const focus = focusFromRow(payload[\"focus\"]);\n return focus === null ? { next: null, dropped: \"a focus packet without a panel\" } : { next: { ...collab, focus } };\n }\n if (Array.isArray(payload[\"removed\"])) {\n return { next: without(collab, strings(payload[\"removed\"])) };\n }\n if (Array.isArray(payload[\"hidden\"])) {\n // A dismissed question leaves everyone's list — but a host keeps it (their packet\n // carries the dismissed state).\n return context.isHost ? { next: null, dropped: \"hosts keep dismissed questions\" } : { next: without(collab, strings(payload[\"hidden\"])) };\n }\n if (payload[\"cleared\"] === \"whiteboard\") {\n return { next: { ...collab, strokes: [] } };\n }\n const broadcast = broadcastFrom(payload[\"broadcast\"]);\n if (broadcast !== null) {\n return { next: { ...collab, broadcast } };\n }\n return { next: null, dropped: \"a collab packet with nothing this version understands\" };\n}\n\n/** The breakout room a person is assigned to, or null. */\nexport function assignedRoom(plan: MeetBreakoutPlan | null, identity: string | null): MeetBreakoutRoom | null {\n if (plan === null || plan.state !== \"open\" || identity === null) return null;\n return plan.rooms.find((room) => room.identities.includes(identity)) ?? null;\n}\n\n/**\n * Split people into `rooms` rooms, round-robin in the order given — Zoom's \"assign\n * automatically\". Hosts are left out (they visit); pass them in `exclude`.\n */\nexport function autoAssign(\n identities: readonly string[],\n rooms: number,\n exclude: readonly string[] = [],\n): string[][] {\n const count = Math.max(1, Math.floor(rooms));\n const out: string[][] = Array.from({ length: count }, () => []);\n const skip = new Set(exclude);\n let index = 0;\n for (const identity of identities) {\n if (skip.has(identity)) continue;\n out[index % count]!.push(identity);\n index += 1;\n }\n return out;\n}\n\n/** Milliseconds until the plan's timer ends, or null when it has none. */\nexport function breakoutRemainingMs(plan: MeetBreakoutPlan | null, now: number): number | null {\n if (plan === null || plan.state !== \"open\" || plan.endsAt === null) return null;\n const end = Date.parse(plan.endsAt);\n return Number.isNaN(end) ? null : Math.max(0, end - now);\n}\n\n/** The language translated captions start in: the knob, else the viewer's own. */\nexport function defaultCaptionLanguage(\n settings: MeetCollabSettings | null,\n viewerLanguage: string | null | undefined,\n): string {\n const configured = settings?.captionTranslationLanguage.trim() ?? \"\";\n if (configured.length > 0) return configured;\n const own = (viewerLanguage ?? \"\").trim();\n return own.length > 0 ? own : \"en\";\n}\n\n/** Languages offered in the captions menu (tags the translator understands). */\nexport const CAPTION_LANGUAGES: readonly { readonly tag: string; readonly name: string }[] = [\n { tag: \"en\", name: \"English\" },\n { tag: \"es\", name: \"Spanish\" },\n { tag: \"fr\", name: \"French\" },\n { tag: \"de\", name: \"German\" },\n { tag: \"it\", name: \"Italian\" },\n { tag: \"pt\", name: \"Portuguese\" },\n { tag: \"nl\", name: \"Dutch\" },\n { tag: \"pl\", name: \"Polish\" },\n { tag: \"ru\", name: \"Russian\" },\n { tag: \"uk\", name: \"Ukrainian\" },\n { tag: \"tr\", name: \"Turkish\" },\n { tag: \"ar\", name: \"Arabic\" },\n { tag: \"he\", name: \"Hebrew\" },\n { tag: \"fa\", name: \"Persian\" },\n { tag: \"hi\", name: \"Hindi\" },\n { tag: \"bn\", name: \"Bengali\" },\n { tag: \"zh\", name: \"Chinese\" },\n { tag: \"ja\", name: \"Japanese\" },\n { tag: \"ko\", name: \"Korean\" },\n { tag: \"vi\", name: \"Vietnamese\" },\n { tag: \"th\", name: \"Thai\" },\n { tag: \"id\", name: \"Indonesian\" },\n { tag: \"sv\", name: \"Swedish\" },\n];\n\nexport interface MeetingActivities {\n /** Polls (with results), questions, whiteboard strokes — or null when refused. */\n readonly collab: CollabSnapshot | null;\n readonly refusal: { readonly message: string; readonly remedy: string } | null;\n}\n\n/**\n * THE MEETING'S POLLS, Q&A AND WHITEBOARD, AFTER IT (Meet wave 5) — for the record page.\n * The same `GET /v1/meet/collab` the room reads on join; a refusal is returned as data,\n * so the record still renders everything else.\n */\nexport async function loadMeetingActivities(\n api: MeetApi,\n meetingId: string,\n meetingPass: string | null = null,\n): Promise<MeetingActivities> {\n let response: Response;\n try {\n response = await api.request({\n path: `${MEET_ROUTES.collab}?meeting_id=${encodeURIComponent(meetingId)}`,\n method: \"GET\",\n operation: \"meet.loadMeetingActivities\",\n requireAuth: meetingPass === null,\n ...(meetingPass !== null ? { headers: { [MEETING_PASS_HEADER]: meetingPass } } : {}),\n });\n } catch (error) {\n return {\n collab: null,\n refusal: {\n message: `Polls, questions and the whiteboard could not be read (${(error as Error).message}).`,\n remedy: (error as { remedy?: string }).remedy ?? \"Reload the page.\",\n },\n };\n }\n if (!response.ok) {\n let body: unknown = null;\n try {\n body = await response.json();\n } catch {\n body = null;\n }\n const said = readServerRefusal(body);\n return {\n collab: null,\n refusal: {\n message: said?.reason ?? `Polls and questions could not be read (the server answered ${response.status}).`,\n remedy: said?.remedy || \"Reload the page.\",\n },\n };\n }\n return { collab: collabFromState((await response.json()) as Row, EMPTY_COLLAB), refusal: null };\n}\n","/**\n * CAPTIONS ARE HINTS; SEGMENTS ARE TRUTH (D16).\n *\n * Two independent streams describe the same speech and they arrive out of order\n * with different identities:\n *\n * 1. **Captions** ride the LiveKit `lk.transcription` text stream, keyed by\n * `lk.segment_id`, interim-then-final, milliseconds behind the voice. They\n * exist so a person can READ the room live. Their `sender_identity` is\n * supplied by the publishing client and LiveKit does not validate it, so\n * nothing downstream — notes, Q&A, the wrap-up — may ever read them.\n * 2. **Records** are `communication.meet_transcript_segments` rows, written\n * server-side by the note-taker with a deterministic id (D6) and delivered\n * here by Postgres Changes. They are the meeting's transcript.\n *\n * The reconciliation below is what keeps the screen from saying everything\n * twice. A record REPLACES the caption it supersedes — matched by the caption\n * id the worker carries into the row, and failing that by speaker + text — and\n * a caption never overwrites a record, however late it lands.\n *\n * Everything here is a pure function over a list so the rules are provable\n * without a store, a socket, or a browser.\n */\n\nimport type { ParticipantIdentity, TranscriptSegment } from \"./types\";\n\n/** Comparable text: case, punctuation and spacing are not differences. */\nexport function normalizeSpoken(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^\\p{L}\\p{N}]+/gu, \" \")\n .trim();\n}\n\nfunction supersedes(record: TranscriptSegment, caption: TranscriptSegment): boolean {\n if (caption.source !== \"caption\") return false;\n if (caption.identity !== record.identity) return false;\n if (record.captionId !== null && caption.captionId === record.captionId) return true;\n const left = normalizeSpoken(record.text);\n const right = normalizeSpoken(caption.text);\n if (left.length === 0 || right.length === 0) return false;\n // An interim caption is a PREFIX of what was finally said; a final caption and\n // its record usually differ only in punctuation. Both are the same utterance.\n return left === right || left.startsWith(right) || right.startsWith(left);\n}\n\n/**\n * A caption arrived. Interim replaces interim in place; a final never loses to a\n * late interim; a record is never touched.\n */\nexport function mergeCaption(\n list: readonly TranscriptSegment[],\n caption: TranscriptSegment,\n): readonly TranscriptSegment[] {\n const index = list.findIndex(\n (entry) => entry.captionId !== null && entry.captionId === caption.captionId,\n );\n if (index === -1) {\n // The record for this utterance may already be here (the row can beat the\n // caption on a slow media path). Do not draw the same sentence twice.\n if (list.some((entry) => entry.source === \"record\" && supersedes(entry, caption))) {\n return list;\n }\n return [...list, caption];\n }\n const existing = list[index];\n if (existing === undefined) return list;\n // TRUTH WINS. A caption cannot revert a durable row, and a late interim cannot\n // revert a final — that is a finished sentence visibly becoming a half-word.\n if (existing.source === \"record\") return list;\n if (existing.final && !caption.final) return list;\n return list.map((entry, position) => (position === index ? caption : entry));\n}\n\n/**\n * A durable row arrived. It replaces the caption it supersedes — in place, so\n * the line does not jump down the panel — or lands as a new line if no caption\n * ever showed it (a surface that joined late, or captions that never came).\n */\nexport function mergeRecord(\n list: readonly TranscriptSegment[],\n record: TranscriptSegment,\n): readonly TranscriptSegment[] {\n const byId = list.findIndex((entry) => entry.source === \"record\" && entry.id === record.id);\n if (byId !== -1) {\n const existing = list[byId];\n if (existing !== undefined && existing.text === record.text && existing.final === record.final) {\n return list;\n }\n return list.map((entry, position) => (position === byId ? record : entry));\n }\n const captionIndex = list.findIndex((entry) => supersedes(record, entry));\n if (captionIndex === -1) return [...list, record];\n return list.map((entry, position) => (position === captionIndex ? record : entry));\n}\n\n/** The transcript a person reads: chronological, and never an empty line. */\nexport function readableTranscript(\n list: readonly TranscriptSegment[],\n): readonly TranscriptSegment[] {\n return [...list]\n .filter((entry) => entry.text.trim().length > 0)\n .sort((left, right) => left.startedAt.localeCompare(right.startedAt));\n}\n\n/** Build a caption line from the media plane. */\nexport function captionSegment(args: {\n captionId: string;\n meetingId: TranscriptSegment[\"meetingId\"];\n identity: ParticipantIdentity;\n speaker: string;\n text: string;\n final: boolean;\n startedAt: string;\n}): TranscriptSegment {\n return {\n id: `caption:${args.captionId}`,\n meetingId: args.meetingId,\n identity: args.identity,\n speaker: args.speaker,\n text: args.text,\n startedAt: args.startedAt,\n final: args.final,\n source: \"caption\",\n captionId: args.captionId,\n };\n}\n","/**\n * THE ROOM SNAPSHOT — one immutable object the UI renders and nothing else.\n *\n * Deliberately NOT Redux, NOT a context full of `useState`, and NOT a bag of\n * refs: one store with `getSnapshot`/`subscribe` is exactly what\n * `useSyncExternalStore` wants, which is what makes tearing impossible when a\n * roster event and a speaker event land in the same tick.\n *\n * The rules encoded here:\n *\n * - **The roster is replaced wholesale, never patched.** The driver hands us\n * the full truth on every media event (see `room-driver.ts`); merging deltas\n * from six event sources is how a tile for someone who left stays on screen.\n * - **Ephemera EXPIRE.** Reactions and speaking flags are time-boxed in the\n * snapshot, so a dropped \"stop\" event cannot leave a permanent 🎉 or a\n * permanently-speaking ring. (Nothing here relies on receiving an end event.)\n * - **An interim transcript line is REPLACED in place by its final.** Appending\n * both is how a transcript ends up saying everything twice.\n */\n\nimport { displayNameFor, learnName, learnNames, type NameBook } from \"./names\";\nimport { isRecordingInFlight } from \"./recording\";\nimport { IDLE_MEDIA, type LocalMediaState, type LocalTrackState } from \"./attendance\";\nimport { ROLE_RANK } from \"./types\";\nimport { nextPhase, reasonOf, type MeetPhase, type PhaseEvent } from \"./phase\";\nimport type { ObserversVisibleTo } from \"./types\";\nimport type { NoteTakerRefusal } from \"./note-taker\";\nimport { EMPTY_COLLAB, type CollabSnapshot } from \"./collab\";\nimport { mergeCaption, mergeRecord } from \"./transcript\";\nimport type {\n AdmissionState,\n ConnectionQuality,\n LobbyEntry,\n MeetParticipant,\n MeetingNote,\n MeetingRecord,\n ParticipantIdentity,\n ParticipantRole,\n ParticipantStanding,\n RecordingSource,\n RecordingState,\n StageLayout,\n TranscriptSegment,\n UserId,\n WrapUpAnnouncement,\n RoomName,\n CallInvite,\n} from \"./types\";\n\nexport interface RoomChatMessage {\n readonly id: string;\n readonly identity: ParticipantIdentity;\n readonly displayName: string;\n readonly text: string;\n readonly at: string;\n /**\n * Set for a PRIVATE message: the one identity it was addressed to (from the\n * sender's side) or this participant (from the receiver's). Private messages\n * travel the data plane only and are never saved (Meet wave 2).\n */\n readonly to?: ParticipantIdentity | null | undefined;\n /** True once the server has SAVED it — a message everyone sees, late joiners too. */\n readonly saved?: boolean | undefined;\n /** A send the server refused, with its sentence. The message stays visible, marked. */\n readonly failed?: string | undefined;\n}\n\n/**\n * A HOST ACTED ON *ME* (Meet wave 2) — the server's own `matrx.meet.moderation`\n * packet, addressed to this participant. Rendered once, dismissible, in words.\n */\nexport interface ModerationNotice {\n readonly kind: \"muted\" | \"ask-unmute\" | \"removed\" | \"role\";\n readonly text: string;\n readonly at: number;\n}\n\n/** What this participant chose for their own media. Remembered per browser. */\nexport interface MediaEffects {\n readonly noiseSuppression: boolean;\n readonly background: \"none\" | \"blur\" | \"image\";\n readonly backgroundImage: string | null;\n}\n\nexport const DEFAULT_EFFECTS: MediaEffects = {\n noiseSuppression: true,\n background: \"none\",\n backgroundImage: null,\n};\n\nexport interface RoomReaction {\n readonly id: string;\n readonly identity: ParticipantIdentity;\n readonly emoji: string;\n readonly at: number;\n}\n\n/**\n * 🚨 WHAT THE SERVER GRANTED THIS JOINER, straight off the token response\n * (MRI-A11). It is placed in the snapshot the instant the token lands — before\n * the LiveKit roster exists at all — so a host's controls are correct on the\n * first frame of the room rather than one roster event later. The roster entry\n * for the local participant carries the SAME decision as a LiveKit attribute;\n * the two agree because one server statement produced both, and the UI reads\n * whichever has arrived.\n */\nexport interface MeetGrants {\n readonly role: ParticipantRole;\n /** Where this joiner stands with the meeting (server-minted, beside the role). */\n readonly standing?: ParticipantStanding | null;\n readonly canRecord: boolean;\n readonly canPublish: boolean;\n readonly canSubscribe: boolean;\n /** HR-360 wave 2 (MD-16): who sees the \"Observing\" line in People (knob). */\n readonly observersVisibleTo?: ObserversVisibleTo | undefined;\n}\n\n/** No token yet grants nothing. Never \"assume host until told otherwise\". */\nexport const NO_GRANTS: MeetGrants = {\n role: \"participant\",\n standing: null,\n canRecord: false,\n canPublish: false,\n canSubscribe: false,\n};\n\n/**\n * S4 — THIS PERSON'S WAIT, as the server's state read (`GET /v1/meet/state`) last said it\n * (CORE-DESIGN §2.6). Set while `phase === \"lobby\"`; the waiting screens read it, never a\n * broadcast. `admission` is the row's own word: `waiting_host` · `knocking` · `denied` ·\n * `expired` · `withdrawn` · `ended` (`admitted` is never held here — it is a join).\n */\nexport interface MeetWaiting {\n readonly admission: AdmissionState;\n /** Is anyone who may admit in the room? null until the first read. */\n readonly admittersPresent: boolean | null;\n /** May a denied person ask again (`reknock_after_deny`)? */\n readonly reknockAllowed: boolean;\n readonly knockExpiresAt: string | null;\n /** How often the core reads while waiting (`waiting_poll_seconds`). */\n readonly pollSeconds: number;\n /** The run's `live_version` at the last read. */\n readonly liveVersion: number;\n}\n\n/** The core's `connection` region (CORE-DESIGN §3.2) — never a phase. */\nexport type ConnectionRegion = \"stable\" | \"signal_reconnecting\" | \"reconnecting\" | \"offline\" | \"server_unreachable\";\n\n/**\n * The core's `share` region (CORE-DESIGN §3.2/§3.3 SA). A refused share is a situation the person\n * is in — a notice (`share-unavailable`), never a phase.\n */\nexport interface ShareRegion {\n readonly state: \"none\" | \"picking\" | \"presenting\" | \"refused\";\n /** Why a share was refused: this browser cannot share, the system blocks it, or it failed. */\n readonly reason: \"unsupported\" | \"blocked\" | \"failed\" | null;\n /** The fix, in the core's words (system setting, administrator policy, …), or null. */\n readonly remedy: string | null;\n readonly at: number;\n}\n\nexport const NO_SHARE: ShareRegion = { state: \"none\", reason: null, remedy: null, at: 0 };\n\nexport interface MeetSnapshot {\n /**\n * WHERE THIS TAB IS — the contract's `data-meet-phase` value, verbatim (CORE-DESIGN §3.1).\n * Written ONLY by `store.dispatch(event)` through the phase machine (`core/phase.ts`);\n * `patch` cannot touch it.\n */\n readonly phase: MeetPhase;\n /** `data-meet-reason` for a refused / terminal phase; null otherwise. */\n readonly reason: string | null;\n /** A notice that came WITH the phase (`sent-to-waiting`), or null. */\n readonly phaseNotice: string | null;\n /** The room this tab last joined (or is joining) — a different meeting mounting resets. */\n readonly roomName: RoomName | null;\n /** The `connection` region: reconnecting is here, never a phase. */\n readonly connection: ConnectionRegion;\n /** The `share` region. */\n readonly share: ShareRegion;\n /**\n * The 1:1 call this tab is placing or answering (CORE-DESIGN §3.6) — the core `calls` slice.\n * The ringing phases (`ringing_out` / `ringing_in` / `call_*`) describe it; null = no call.\n */\n readonly call: { readonly invite: CallInvite; readonly direction: \"outgoing\" | \"incoming\" } | null;\n /** S4: this person's wait (see `MeetWaiting`); null outside the waiting room. */\n readonly waiting: MeetWaiting | null;\n readonly meeting: MeetingRecord | null;\n readonly localIdentity: ParticipantIdentity | null;\n /** The server's word on what THIS joiner may do. See `MeetGrants`. */\n readonly grants: MeetGrants;\n readonly participants: readonly MeetParticipant[];\n readonly activeSpeakers: readonly ParticipantIdentity[];\n readonly pinned: ParticipantIdentity | null;\n readonly layout: StageLayout;\n readonly lobby: readonly LobbyEntry[];\n readonly locked: boolean;\n /**\n * 🚨 THE DURABLE `meet_recordings.state`, not a client's hope. Through 0.4.7\n * this only ever held what THIS browser optimistically patched — `starting`\n * on the click, `stopping` on the stop — so the indicator read \"Starting\n * recording…\" for the whole meeting while a perfectly healthy egress ran, and\n * \"Stopping recording…\" forever after (MRI-C8). The server's transitions\n * arrive on two transports now: the `meet_recordings` row for a member, and\n * a `matrx.meet.recording` room event for a guest, who has no database.\n */\n readonly recording: RecordingState;\n readonly recordingStartedBy: UserId | null;\n /**\n * WHICH transport last spoke, so the two cannot undo each other (D16). The\n * database wins: `\"record\"` is never overwritten by `\"room\"` or `\"local\"`.\n */\n readonly recordingSource: RecordingSource;\n /**\n * WHICH recording this state is about. It is what tells a LATE packet for a\n * finished recording apart from the FIRST packet of the next one — without\n * it, \"the database wins\" either blocks a second recording forever or lets a\n * stale packet drag a finished one back to `recording`.\n */\n readonly recordingId: string | null;\n readonly chat: readonly RoomChatMessage[];\n /** Whether the SAVED chat has been read for this meeting (late joiners see history). */\n readonly chatLoaded: boolean;\n /** Why the saved chat could not be read on join, in words — or null. */\n readonly chatHistoryError: string | null;\n readonly reactions: readonly RoomReaction[];\n /**\n * The participant a HOST spotlighted for everyone, from the room's metadata\n * (server-set, delivered on join). A local pin outranks it on this screen only.\n */\n readonly spotlight: ParticipantIdentity | null;\n /** The last thing a host did to THIS participant, in words. Dismissible. */\n readonly moderationNotice: ModerationNotice | null;\n /**\n * The server's answer to the last host action THIS participant took (\"Muted 3\n * people\", or the refusal). One place, so whichever surface acted — a tile's\n * menu, the People panel, More options — the sentence survives the menu closing.\n */\n readonly actionNotice: { readonly text: string; readonly failed: boolean; readonly at: number } | null;\n /**\n * \"Message privately\" asked for from ANY per-person menu — a tile's, the\n * People panel's — and answered by the one `ControlBar` that owns the chat\n * panel (0.7.8). Through 0.7.7 only the People panel could open a private\n * chat, so the Board's tile menu (and the Room's) had no such item while the\n * People panel did: two menus for one person that disagreed.\n */\n readonly privateChatRequest: { readonly to: ParticipantIdentity; readonly at: number } | null;\n readonly effects: MediaEffects;\n /**\n * Captions AND durable rows, reconciled into one list (D16). A record\n * replaces the caption it supersedes; a caption never overwrites a record.\n */\n readonly transcript: readonly TranscriptSegment[];\n readonly notes: readonly MeetingNote[];\n /**\n * 🚨 IDENTITY → THE BEST NAME ANYTHING HAS EVER KNOWN (MRI-A16). Learned from\n * every source that has one — the roster, a durable segment, a durable\n * attendance row, a chat message — and never unlearned, so a caption that\n * lands after somebody leaves still says who spoke. It exists because the\n * host and the guest rendered DIFFERENT names for the same speaker in the\n * same second, one of them a raw LiveKit token (MRI-C8). See `core/names.ts`.\n */\n readonly names: NameBook;\n /**\n * The wrap-up pass ANNOUNCED itself (MRI-A12). `null` means it has not been\n * announced in this room — which is not the same as \"no summary exists\", and\n * no surface may render it as such.\n */\n readonly wrapUp: WrapUpAnnouncement | null;\n /**\n * The server's last refusal to dispatch the note-taker, in ITS words (D7).\n * Rendered verbatim; never replaced by a spinner or a house sentence.\n */\n readonly noteTakerRefusal: NoteTakerRefusal | null;\n /**\n * What the LOCAL participant is actually publishing, and why not when it is\n * not. Attendance never depends on it (see `attendance.ts`): a refused\n * microphone is a state with a remedy, not a failed join.\n */\n readonly media: LocalMediaState;\n /** The last failure, with its remedy. Never null-and-hoping. */\n readonly failure: {\n readonly message: string;\n readonly remedy: string;\n /** The join gate's reason code, when the server named one (CORE-DESIGN §2.5). */\n readonly reason?: string | null;\n } | null;\n /**\n * Meet wave 5: polls, Q&A, the shared whiteboard and the breakout plan — what the\n * server said, per viewer (`core/collab.ts`). Read once per join, then packets.\n */\n readonly collab: CollabSnapshot;\n /** The breakout room THIS participant is in, or null for the main room. */\n readonly breakoutRoomId: string | null;\n}\n\nconst EMPTY: MeetSnapshot = {\n phase: \"resolving\",\n reason: null,\n phaseNotice: null,\n roomName: null,\n connection: \"stable\",\n share: NO_SHARE,\n call: null,\n waiting: null,\n meeting: null,\n localIdentity: null,\n grants: NO_GRANTS,\n participants: [],\n activeSpeakers: [],\n pinned: null,\n layout: \"grid\",\n lobby: [],\n locked: false,\n recording: \"idle\",\n recordingStartedBy: null,\n recordingSource: \"local\",\n recordingId: null,\n chat: [],\n chatLoaded: false,\n chatHistoryError: null,\n reactions: [],\n spotlight: null,\n moderationNotice: null,\n actionNotice: null,\n privateChatRequest: null,\n effects: DEFAULT_EFFECTS,\n transcript: [],\n notes: [],\n names: {},\n wrapUp: null,\n noteTakerRefusal: null,\n media: IDLE_MEDIA,\n failure: null,\n collab: EMPTY_COLLAB,\n breakoutRoomId: null,\n};\n\n/**\n * 🚨 THE ONE ANSWER TO \"WHAT ROLE DOES THIS CLIENT'S USER HOLD\" (MRI-A11).\n *\n * Two SERVER-minted carriers, one decision: the `/meet/token` response (in the\n * store from the moment the token lands, before any roster exists) and the\n * local participant's roster entry (whose role comes from the\n * `matrx.participant.role` LiveKit attribute). The stronger wins. Every host\n * power in this package — the UI that renders it AND the code that performs it\n * — asks THIS function, so a control can never render from one source and then\n * be refused by another. That split is exactly how a \"host control\" becomes a\n * button that throws.\n */\nexport function effectiveRole(snapshot: MeetSnapshot): ParticipantRole {\n const me = snapshot.participants.find(\n (participant) => participant.identity === snapshot.localIdentity,\n );\n const fromRoster = me?.role ?? \"participant\";\n const fromToken = snapshot.grants.role;\n return ROLE_RANK[fromRoster] >= ROLE_RANK[fromToken] ? fromRoster : fromToken;\n}\n\n/** This person's standing: the token's word, else what the roster carries for them. */\nexport function effectiveStanding(snapshot: MeetSnapshot): ParticipantStanding | null {\n if (snapshot.grants.standing != null) return snapshot.grants.standing;\n const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);\n return me?.standing ?? null;\n}\n\n/** Reactions live this long. A dropped end event cannot outlive it. */\nexport const REACTION_TTL_MS = 5_000;\n/** In-room chat is EPHEMERAL by design (R3) — this is the visible backlog. */\nconst CHAT_LIMIT = 200;\n\n/** What `patch` accepts: the snapshot minus the machine-owned fields. */\nexport type MeetPatch = Partial<Omit<MeetSnapshot, \"phase\" | \"reason\" | \"phaseNotice\">>;\n\nexport interface MeetStore {\n snapshot(): MeetSnapshot;\n subscribe(listener: () => void): () => void;\n /** Everything EXCEPT the phase. The phase moves only through `dispatch`. */\n patch(next: MeetPatch): void;\n /**\n * THE ONE WRITER OF THE PHASE (CORE-DESIGN §3.1). Applies `event` through the machine's table;\n * returns false — and changes nothing — when the table does not allow it from the current\n * phase. `reason` overrides the phase's own reason (an exit's `removed`, `displaced`, …).\n */\n dispatch(event: PhaseEvent, detail?: { readonly reason?: string | null; readonly notice?: string | null }): boolean;\n setParticipants(participants: readonly MeetParticipant[]): void;\n setSpeaking(identities: readonly ParticipantIdentity[]): void;\n setQuality(identity: ParticipantIdentity, quality: ConnectionQuality): void;\n /**\n * Raise or lower a hand. `at` is WHEN it went up, which is the hand queue's\n * order — first raised, first called on, the way Meet orders it.\n */\n setHand(identity: ParticipantIdentity, raised: boolean, at?: number): void;\n addChat(message: RoomChatMessage): void;\n /** The saved chat, read once per join. Merged with anything that arrived live. */\n loadChat(messages: readonly RoomChatMessage[]): void;\n /** Replace a pending message (by id) with what the server saved, or mark it failed. */\n settleChat(localId: string, next: RoomChatMessage | { failed: string }): void;\n addReaction(reaction: RoomReaction): void;\n /** Drops reactions past their TTL. Called by the UI's animation frame. */\n expireReactions(now: number): void;\n /**\n * The recording reached a state, and WHO says so. `\"local\"` is this browser's\n * optimistic `starting`/`stopping`; `\"room\"` is the server's packet on the\n * media plane; `\"record\"` is the durable row. Precedence is D16's: the\n * database's word stands until the database changes it.\n */\n setRecording(\n state: RecordingState,\n by: UserId | null,\n source: RecordingSource,\n recordingId?: string | null,\n ): void;\n upsertLobby(entry: LobbyEntry): void;\n setAdmission(identity: ParticipantIdentity, state: AdmissionState, by: UserId | null): void;\n /**\n * Teach the name book. Every source that knows a name calls this, and only\n * a REAL name is learned — an identity handed back as its own name is not one.\n */\n learn(entries: readonly { identity: ParticipantIdentity; displayName?: string | null }[]): void;\n /** A live caption from the media plane. A HINT (D16). */\n ingestCaption(segment: TranscriptSegment): void;\n /** A durable `meet_transcript_segments` row. THE TRANSCRIPT (D16). */\n ingestRecord(segment: TranscriptSegment): void;\n /** Durable notes are re-written in place — a re-run rewrites the same ids. */\n upsertNote(note: MeetingNote): void;\n /** The wrap-up pass finished. Announced on the media plane only (MRI-A12). */\n announceWrapUp(announcement: WrapUpAnnouncement): void;\n /** Record what a local track ACTUALLY did — published, or refused and why. */\n setLocalTrack(kind: \"microphone\" | \"camera\", state: LocalTrackState): void;\n reset(): void;\n}\n\nexport function createMeetStore(initial: Partial<MeetSnapshot> = {}): MeetStore {\n let state: MeetSnapshot = { ...EMPTY, ...initial };\n const listeners = new Set<() => void>();\n\n function commit(next: MeetSnapshot): void {\n if (next === state) return;\n state = next;\n for (const listener of listeners) listener();\n }\n\n return {\n snapshot: () => state,\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n patch(next) {\n if (\"phase\" in next || \"reason\" in next || \"phaseNotice\" in next) {\n // The type forbids it; this catches a cast. The phase has ONE writer.\n throw new TypeError(\"meet store: the phase moves only through store.dispatch(event)\");\n }\n commit({ ...state, ...next });\n },\n dispatch(event, detail) {\n const to = nextPhase(state.phase, event);\n if (to === null) return false;\n const reason = detail?.reason !== undefined ? detail.reason : reasonOf(to);\n const phaseNotice = detail?.notice ?? null;\n if (to === state.phase && reason === state.reason && phaseNotice === state.phaseNotice) return true;\n commit({ ...state, phase: to, reason, phaseNotice });\n return true;\n },\n setParticipants(participants) {\n // The roster TEACHES the book first — a participant whose token carried a\n // real name is where most names come from — and every roster entry is then\n // RESOLVED back through it, so a participant the roster cannot name still\n // gets the best name a durable row or a caption ever taught (MRI-A18).\n const names = learnNames(state.names, participants);\n // Hand-raise state lives on OUR side of the wire (it rides the data\n // plane, not the media plane), so it is carried across a roster replace\n // rather than reset — otherwise every mute event lowers every hand.\n const hands = new Map(state.participants.map((p) => [p.identity, p.handRaised]));\n const handTimes = new Map(state.participants.map((p) => [p.identity, p.handRaisedAt ?? null]));\n const quality = new Map(state.participants.map((p) => [p.identity, p.quality]));\n commit({\n ...state,\n names,\n participants: participants.map((participant) => ({\n ...participant,\n // 🚨 THE ONE ROSTER RESOLUTION. Every surface that renders a person —\n // the tile, the People row, the header summary, every Pin/Mute label —\n // reads `participant.displayName`, so this is the single place a raw\n // identity could reach a screen, and it is the place that asks the\n // name book. `roster-names.test.ts` fails if a token ever gets past it.\n displayName: displayNameFor(names, participant.identity, participant.displayName),\n handRaised: hands.get(participant.identity) ?? participant.handRaised,\n handRaisedAt: handTimes.get(participant.identity) ?? participant.handRaisedAt ?? null,\n quality:\n participant.quality === \"unknown\"\n ? quality.get(participant.identity) ?? \"unknown\"\n : participant.quality,\n })),\n });\n },\n setSpeaking(identities) {\n const speaking = new Set(identities);\n commit({\n ...state,\n activeSpeakers: identities,\n participants: state.participants.map((participant) =>\n participant.speaking === speaking.has(participant.identity)\n ? participant\n : { ...participant, speaking: speaking.has(participant.identity) },\n ),\n });\n },\n setQuality(identity, quality) {\n commit({\n ...state,\n participants: state.participants.map((participant) =>\n participant.identity === identity ? { ...participant, quality } : participant,\n ),\n });\n },\n setHand(identity, raised, at) {\n commit({\n ...state,\n participants: state.participants.map((participant) =>\n participant.identity === identity\n ? {\n ...participant,\n handRaised: raised,\n // A hand that is ALREADY up keeps its place in the queue when\n // it is re-announced (to a late joiner): the first time wins.\n handRaisedAt: raised\n ? participant.handRaised && participant.handRaisedAt != null\n ? participant.handRaisedAt\n : at ?? Date.now()\n : null,\n }\n : participant,\n ),\n });\n },\n addChat(message) {\n if (state.chat.some((existing) => existing.id === message.id)) return;\n const chat = [...state.chat, message];\n // A chat message carries its sender's name — another source, same book.\n const names = learnName(state.names, message.identity, message.displayName);\n commit({ ...state, chat: chat.slice(-CHAT_LIMIT), names });\n },\n loadChat(messages) {\n const known = new Set(state.chat.map((message) => message.id));\n const merged = [...messages.filter((message) => !known.has(message.id)), ...state.chat].sort(\n (a, b) => (a.at < b.at ? -1 : a.at > b.at ? 1 : 0),\n );\n const names = learnNames(\n state.names,\n messages.map((message) => ({ identity: message.identity, displayName: message.displayName })),\n );\n commit({ ...state, chat: merged.slice(-CHAT_LIMIT), chatLoaded: true, chatHistoryError: null, names });\n },\n settleChat(localId, next) {\n const index = state.chat.findIndex((message) => message.id === localId);\n if (index === -1) return;\n const current = state.chat[index] as RoomChatMessage;\n let replacement: RoomChatMessage;\n if (\"failed\" in next && typeof next.failed === \"string\" && !(\"id\" in next)) {\n replacement = { ...current, failed: next.failed };\n } else {\n replacement = next as RoomChatMessage;\n }\n // The saved copy may ALREADY be here (the room packet beat the response):\n // then the pending one simply goes.\n const duplicate =\n replacement.id !== localId && state.chat.some((message) => message.id === replacement.id);\n const chat = duplicate\n ? state.chat.filter((message) => message.id !== localId)\n : state.chat.map((message, position) => (position === index ? replacement : message));\n commit({ ...state, chat });\n },\n addReaction(reaction) {\n commit({ ...state, reactions: [...state.reactions, reaction] });\n },\n expireReactions(now) {\n const live = state.reactions.filter((reaction) => now - reaction.at < REACTION_TTL_MS);\n if (live.length === state.reactions.length) return;\n commit({ ...state, reactions: live });\n },\n setRecording(next, by, source, recordingId = null) {\n // 🚨 THE DATABASE WINS (D16). A member receives every transition TWICE —\n // the `meet_recordings` row over Postgres Changes and a\n // `matrx.meet.recording` packet in the room — and whichever is slower\n // must not be able to drag the indicator backwards.\n //\n // The rule is about ONE recording, not about the meeting: a meeting can\n // be recorded again, and the second recording's first word is not a\n // contradiction of the first one's ending. So the durable state stands\n // against anything that is about the SAME recording, and against a\n // sourceless update while a recording is still in flight.\n // `record` always wins. The other two are refused when they would\n // contradict the durable word rather than continue past it:\n //\n // * `local` is this browser acting NOW — a host pressing Start again on a\n // meeting whose last recording is over. It is refused only while a\n // recording is genuinely in flight, where a stale optimistic patch\n // could drag a live indicator backwards.\n // * `room` carries the recording's id, so a LATE packet about the\n // recording that just finished is told apart from the FIRST packet of\n // the next one. Same recording: refused. Different recording: refused\n // only while the durable one is still in flight.\n if (state.recordingSource === \"record\" && source !== \"record\") {\n const aboutTheSameRecording =\n source === \"room\" &&\n (recordingId === null ||\n state.recordingId === null ||\n recordingId === state.recordingId);\n if (aboutTheSameRecording || isRecordingInFlight(state.recording)) return;\n }\n if (\n state.recording === next &&\n state.recordingStartedBy === by &&\n state.recordingSource === source &&\n state.recordingId === (recordingId ?? state.recordingId)\n ) {\n // Identity-stable: an unchanged state must not wake every subscriber.\n return;\n }\n commit({\n ...state,\n recording: next,\n recordingStartedBy: by,\n recordingSource: source,\n recordingId: recordingId ?? state.recordingId,\n });\n },\n upsertLobby(entry) {\n const index = state.lobby.findIndex((existing) => existing.identity === entry.identity);\n const lobby =\n index === -1\n ? [...state.lobby, entry]\n : state.lobby.map((existing, position) => (position === index ? entry : existing));\n commit({ ...state, lobby });\n },\n setAdmission(identity, admissionState, by) {\n commit({\n ...state,\n lobby: state.lobby.map((entry) =>\n entry.identity === identity\n ? {\n ...entry,\n state: admissionState,\n decidedAt: new Date().toISOString(),\n decidedBy: by,\n }\n : entry,\n ),\n });\n },\n learn(entries) {\n const names = learnNames(state.names, entries);\n // Identity-stable: learning nothing must not wake every subscriber, and\n // this is called on every roster event and every durable row.\n if (names === state.names) return;\n commit({ ...state, names });\n },\n ingestCaption(segment) {\n // A caption's roster lookup is a source of names like any other.\n const names = learnName(state.names, segment.identity, segment.speaker);\n const transcript = mergeCaption(state.transcript, segment);\n if (transcript === state.transcript && names === state.names) return;\n commit({ ...state, transcript, names });\n },\n ingestRecord(segment) {\n // 🚨 THE DURABLE ROW'S `speaker` IS THE BEST NAME THERE IS: the note-taker\n // wrote it from `meet_participants`, which is where \"Ada Lovelace\" lived\n // while the guest's screen was printing `guest:c8-spk-1`.\n const names = learnName(state.names, segment.identity, segment.speaker);\n const transcript = mergeRecord(state.transcript, segment);\n if (transcript === state.transcript && names === state.names) return;\n commit({ ...state, transcript, names });\n },\n upsertNote(note) {\n const index = state.notes.findIndex((existing) => existing.id === note.id);\n if (index === -1) {\n commit({ ...state, notes: [...state.notes, note] });\n return;\n }\n const existing = state.notes[index];\n // Identity-stable: an unchanged note must not wake `useSyncExternalStore`.\n // 🚨 `source` is part of \"unchanged\" and that is load-bearing, not\n // cosmetic: when the room copy and the database copy carry the same words,\n // dropping the second arrival would leave the note marked `\"room\"` forever\n // — and the precedence rule below would then let a LATER stale packet\n // overwrite a row the database had already confirmed.\n if (\n existing !== undefined &&\n existing.text === note.text &&\n existing.kind === note.kind &&\n existing.supersededBy === note.supersededBy &&\n existing.source === note.source\n ) {\n return;\n }\n // 🚨 THE DATABASE WINS (D16, MRI-A12). The identical row arrives on two\n // transports — a `meet_notes` row over Postgres Changes and a\n // `matrx.meet.note` packet on the media plane — and whichever is slower\n // must not be able to undo the other. A `\"room\"` copy never overwrites a\n // `\"record\"` one; a `\"record\"` copy always overwrites a `\"room\"` one.\n // Without this, a re-run that rewrote a note could flip a member's screen\n // back to the superseded wording purely on arrival order.\n if (existing !== undefined && existing.source === \"record\" && note.source === \"room\") {\n return;\n }\n commit({\n ...state,\n notes: state.notes.map((entry, position) => (position === index ? note : entry)),\n });\n },\n announceWrapUp(announcement) {\n const current = state.wrapUp;\n if (current !== null && current.at === announcement.at) return;\n commit({ ...state, wrapUp: announcement });\n },\n setLocalTrack(kind, next) {\n const current = state.media[kind];\n // Identity-stable: an unchanged track must not wake `useSyncExternalStore`.\n if (\n current.enabled === next.enabled &&\n current.unavailable?.code === next.unavailable?.code &&\n current.unavailable?.message === next.unavailable?.message\n ) {\n return;\n }\n commit({ ...state, media: { ...state.media, [kind]: next } });\n },\n reset() {\n // A person's own media choices outlive a room: they are theirs, per browser.\n commit({ ...EMPTY, effects: state.effects });\n },\n };\n}\n","/**\n * THE OBSERVATION ROOT (CORE-DESIGN §5.6, step S0).\n *\n * Exactly one `[data-meet-root]` per tab on a meeting page, carrying the\n * HARNESS-CONTRACT's attributes derived from the store (`core/observation.ts`).\n * The package renders it once — `<MeetingRoom>` wraps every screen in it, and a\n * consumer that draws its own screen around the room (link resolution, the\n * guest-name step, a Board layout) wraps that screen in `<MeetRoot>` too, with\n * a `phase` when there is no store yet.\n *\n * The wrapper is `display: contents`: it generates no box, so no layout, size\n * or stacking changes; it exists only to carry the attributes. It also\n * publishes `window.__matrxMeet` — diagnostics only, never a verdict.\n */\n\nimport { useEffect, useMemo, useRef, type ReactNode } from \"react\";\nimport {\n observe,\n rootAttributes,\n staticObservation,\n type MeetObservation,\n type ObservedPhase,\n} from \"../core/observation\";\nimport type { MeetingRecord } from \"../core/types\";\nimport { useMeetSnapshot } from \"./provider\";\n\nexport interface CoreRootOptions {\n /** The row the consumer resolved (the guest lane has no other copy). */\n readonly meeting?: MeetingRecord | null | undefined;\n /** A screen the consumer renders before any store exists (resolving, a dead link). */\n readonly phase?: ObservedPhase | undefined;\n readonly reason?: string | null | undefined;\n}\n\n/** The observation for this tab, one stable object per change. */\nexport function useCoreObservation(options: CoreRootOptions = {}): MeetObservation {\n const snapshot = useMeetSnapshot();\n const { meeting, phase, reason } = options;\n return useMemo(\n () =>\n phase !== undefined\n ? staticObservation(phase, reason ?? null)\n : observe({ snapshot, meeting: meeting ?? null }),\n [snapshot, meeting, phase, reason],\n );\n}\n\n/** The root element's `data-meet-*` attributes. Spread onto ONE element per tab. */\nexport function useCoreRootProps(options: CoreRootOptions = {}): Record<string, string> {\n const observation = useCoreObservation(options);\n return useMemo(() => rootAttributes(observation), [observation]);\n}\n\ninterface MeetDiagnostics {\n readonly contract: \"HARNESS-CONTRACT\";\n snapshot(): { readonly observation: MeetObservation; readonly storePhase: string | null };\n /** Not derived before CORE-DESIGN step S6a — an empty list here means \"not computed\", not \"none\". */\n readonly activeCatalogStates: readonly string[];\n readonly activeCatalogStatesDerived: false;\n}\n\n/** `<MeetRoot>` — the one observation root. Generates no box. */\nexport function MeetRoot({\n children,\n ...options\n}: CoreRootOptions & { children?: ReactNode }): ReactNode {\n const snapshot = useMeetSnapshot();\n const observation = useCoreObservation(options);\n const attrs = useMemo(() => rootAttributes(observation), [observation]);\n const latest = useRef({ observation, storePhase: snapshot?.phase ?? null });\n latest.current = { observation, storePhase: snapshot?.phase ?? null };\n\n useEffect(() => {\n if (typeof window === \"undefined\") return undefined;\n const diagnostics: MeetDiagnostics = {\n contract: \"HARNESS-CONTRACT\",\n snapshot: () => latest.current,\n activeCatalogStates: [],\n activeCatalogStatesDerived: false,\n };\n const target = window as unknown as { __matrxMeet?: MeetDiagnostics };\n target.__matrxMeet = diagnostics;\n return () => {\n if (target.__matrxMeet === diagnostics) delete target.__matrxMeet;\n };\n }, []);\n\n return (\n <div {...attrs} style={{ display: \"contents\" }}>\n {children}\n </div>\n );\n}\n","/**\n * THE OBSERVATION CONTRACT, DERIVED FROM TODAY'S STORE (CORE-DESIGN §7 step S0).\n *\n * The real-browser scenario harness reads a meeting through a small set of\n * `data-meet-*` attributes (common-docs `meet/duplicates/HARNESS-CONTRACT.md`,\n * owned by the harness lane). This module turns the store's `MeetSnapshot`\n * into exactly those values — pure, framework-free, rendered ONCE by the\n * package (`useCoreRootProps()` / `<MeetRoot>`), never per skin.\n *\n * S0 rule: TODAY'S VALUES ONLY. Every value here is what the person actually\n * sees on today's screens, including today's defects — a `failed` or `left`\n * room renders the pre-join screen, so its phase is `prejoin`, and the harness\n * records that as the defect it is. A value the store does not know (host\n * presence before joining, autoplay blocking) is OMITTED rather than guessed:\n * an absent attribute reads as \"unknown\", a guessed one would lie.\n */\n\nimport type { LocalTrackState } from \"./attendance\";\nimport type { ConnectionRegion, MeetSnapshot } from \"./store\";\nimport { isLivePhase, isWaitingPhase, reasonOf, type MeetPhase } from \"./phase\";\nimport { effectiveRole, effectiveStanding } from \"./store\";\nimport { isLinkFollower } from \"./types\";\nimport type {\n ConnectionQuality,\n MeetParticipant,\n MeetingRecord,\n ParticipantRole,\n ParticipantStanding,\n} from \"./types\";\n\n/**\n * `data-meet-role` is the HARNESS CONTRACT's vocabulary (host · cohost · participant · guest):\n * a link follower whose role is `participant` renders `guest` (CORE-DESIGN §2.1); `presenter` /\n * `viewer` render `participant` until the contract adopts them (§3.7 C3).\n */\nexport type ObservedRole = \"host\" | \"cohost\" | \"participant\" | \"guest\";\n\nexport function observedRole(role: ParticipantRole, standing: ParticipantStanding | null | undefined): ObservedRole {\n if (role === \"host\" || role === \"cohost\") return role;\n return isLinkFollower(standing) ? \"guest\" : \"participant\";\n}\n\n/** `data-meet-phase` — the core's phase IS the contract value (S6a: no translation layer). */\nexport type ObservedPhase = MeetPhase;\n\nexport type ObservedConnection = ConnectionRegion;\n\nexport interface MeetObservation {\n readonly phase: ObservedPhase;\n readonly reason: string | null;\n readonly role: ObservedRole | \"none\";\n readonly connection: ObservedConnection;\n /** null = this tab does not know (it is not in the room). */\n readonly hostPresent: boolean | null;\n readonly lobbyCount: number;\n readonly recording: boolean;\n /** null = today's store does not track autoplay blocking. */\n readonly audioBlocked: boolean | null;\n /** null outside `joining` / `in_call` — no device is in play there today. */\n readonly camera: string | null;\n readonly microphone: string | null;\n}\n\nexport interface ObserveInput {\n readonly snapshot: MeetSnapshot | null;\n /** The row the consumer resolved, when the store has none yet (the guest lane). */\n readonly meeting?: MeetingRecord | null | undefined;\n}\n\n/** The store's device state → the contract's `camera` / `microphone` value. */\nexport function observedDevice(track: LocalTrackState): string {\n if (track.enabled) return \"on\";\n const refused = track.unavailable;\n if (refused === null) return \"muted_by_me\";\n switch (refused.code) {\n case \"permission-denied\":\n return \"unavailable:denied\";\n case \"device-not-found\":\n return \"unavailable:none\";\n case \"device-in-use\":\n return \"unavailable:busy\";\n case \"insecure-context\":\n return \"unavailable:blocked_site\";\n default:\n return \"unavailable:failed\";\n }\n}\n\n/** Does this snapshot's meeting row say it is over? */\nfunction endedRecord(snapshot: MeetSnapshot | null, given: MeetingRecord | null): boolean {\n const record = snapshot?.meeting ?? given;\n return record !== null && record.endedAt !== null;\n}\n\n/**\n * The phase the person is looking at: the store's phase, verbatim (CORE-DESIGN §3.1). The one\n * derivation left is the ended meeting: a row that says it is over renders `ended` for anyone not\n * in a live connection, whichever carrier delivered the ending first.\n */\nexport function observedPhase(input: ObserveInput): ObservedPhase {\n const { snapshot } = input;\n if (snapshot === null) return \"resolving\";\n const phase = snapshot.phase;\n if (endedRecord(snapshot, input.meeting ?? null) && !isLivePhase(phase) && !isCallPhase(phase)) return \"ended\";\n return phase;\n}\n\nfunction isCallPhase(phase: MeetPhase): boolean {\n return phase.startsWith(\"ringing_\") || phase.startsWith(\"call_\") || phase === \"answered_elsewhere\";\n}\n\nconst ADMITTER_ROLES: ReadonlySet<ParticipantRole> = new Set([\"host\", \"cohost\"]);\n\nexport function observe(input: ObserveInput): MeetObservation {\n const { snapshot } = input;\n const phase = observedPhase(input);\n const inCall = phase === \"in_call\";\n const actual: ParticipantRole | \"none\" = inCall && snapshot !== null ? effectiveRole(snapshot) : \"none\";\n const canAdmit = actual !== \"none\" && ADMITTER_ROLES.has(actual);\n const role: ObservedRole | \"none\" =\n actual === \"none\" || snapshot === null ? \"none\" : observedRole(actual, effectiveStanding(snapshot));\n const deviceInPlay = phase === \"joining\" || inCall;\n return {\n phase,\n reason: phase === \"ended\" ? (snapshot?.reason ?? null) : (snapshot?.reason ?? reasonOf(phase)),\n role,\n connection: snapshot?.connection ?? \"stable\",\n hostPresent: inCall && snapshot !== null\n ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role))\n : snapshot !== null && isWaitingPhase(snapshot.phase)\n ? (snapshot.waiting?.admittersPresent ?? null)\n : null,\n lobbyCount: canAdmit && snapshot !== null\n ? snapshot.lobby.filter((entry) => entry.state === \"knocking\").length\n : 0,\n recording: snapshot?.recording === \"recording\" || snapshot?.recording === \"stopping\",\n audioBlocked: null,\n camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera) : null,\n microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone) : null,\n };\n}\n\n/** A phase the consumer renders itself (link resolution, the guest-name step, a dead link). */\nexport function staticObservation(phase: ObservedPhase, reason: string | null = null): MeetObservation {\n return {\n phase,\n reason,\n role: \"none\",\n connection: phase === \"disconnected\" ? \"server_unreachable\" : \"stable\",\n hostPresent: null,\n lobbyCount: 0,\n recording: false,\n audioBlocked: null,\n camera: null,\n microphone: null,\n };\n}\n\n/** The root element's attributes. Unknown values are omitted, never guessed. */\nexport function rootAttributes(o: MeetObservation): Record<string, string> {\n const attrs: Record<string, string> = {\n \"data-meet-root\": \"\",\n \"data-meet-phase\": o.phase,\n \"data-meet-role\": o.role,\n \"data-meet-connection\": o.connection,\n \"data-meet-lobby-count\": String(o.lobbyCount),\n \"data-meet-recording\": String(o.recording),\n };\n if (o.reason !== null) attrs[\"data-meet-reason\"] = o.reason;\n if (o.hostPresent !== null) attrs[\"data-meet-host-present\"] = String(o.hostPresent);\n if (o.audioBlocked !== null) attrs[\"data-meet-audio-blocked\"] = String(o.audioBlocked);\n if (o.camera !== null) attrs[\"data-meet-camera\"] = o.camera;\n if (o.microphone !== null) attrs[\"data-meet-microphone\"] = o.microphone;\n attrs[\"data-meet-regions\"] = JSON.stringify({\n connection: o.connection,\n camera: o.camera,\n mic: o.microphone,\n audio_output: o.audioBlocked === null ? null : o.audioBlocked ? \"blocked\" : \"playing\",\n recording: o.recording,\n });\n return attrs;\n}\n\n/** The contract's per-person connection vocabulary. `unknown` is not a reading. */\nexport function observedQuality(quality: ConnectionQuality): string | null {\n switch (quality) {\n case \"excellent\":\n case \"good\":\n return \"good\";\n case \"poor\":\n return \"poor\";\n case \"lost\":\n return \"lost\";\n default:\n return null;\n }\n}\n\n/** `[data-meet-participant]` attributes for one person's tile or roster row. */\nexport function participantAttributes(p: MeetParticipant): Record<string, string> {\n const attrs: Record<string, string> = {\n \"data-meet-participant\": \"\",\n \"data-meet-participant-identity\": p.identity,\n \"data-meet-participant-name\": p.displayName,\n \"data-meet-participant-role\": observedRole(p.role, p.standing),\n \"data-meet-participant-self\": String(p.isLocal),\n };\n const quality = observedQuality(p.quality);\n if (quality !== null) attrs[\"data-meet-participant-connection\"] = quality;\n return attrs;\n}\n\n/** The listen-only notice's contract code, from why the microphone is off. */\nexport function microphoneNoticeCode(track: LocalTrackState): \"mic-blocked\" | \"device-lost\" | null {\n const refused = track.unavailable;\n if (refused === null) return null;\n return refused.code === \"permission-denied\" || refused.code === \"insecure-context\"\n ? \"mic-blocked\"\n : \"device-lost\";\n}\n","\"use client\";\n\n/**\n * `<MeetProvider>` — the one place an app wires calls and meetings (R7).\n *\n * THE ONE-HOUR INTEGRATION CONTRACT is this component's job: a host passes a\n * Supabase client, who they are, their org, and aidream's base URL. Everything\n * else — the repository, the realtime manager and its three channels, the token\n * client, the room engine, the call center, the meeting session, the recording\n * controller, the device manager, the AI surface — is constructed and owned\n * here.\n *\n * What a host may inject is IDENTITY and app chrome: session source, a\n * navigator, a notification sink. It may NOT inject an agent id — as of 0.3.0\n * there is no prop for one, because which agent answers is a Mandate binding\n * the SERVER resolves (D2). What a host must NEVER have to inject is\n * behavior — if you find yourself catching a package error, retrying a package\n * call, or branching on a package quirk in host code, that logic belongs in\n * here (C22).\n *\n * 🚨 THE PROVIDER'S LIFETIME IS `client` + the SESSION'S user (or `guestName`)\n * + `organizationId` + `baseUrl`, AND NOTHING ELSE. Hosts pass fresh function\n * literals for `onDiagnostic` and `accessToken` on every render; keying the\n * runtime on those identities would tear down and rebuild every channel — and\n * drop the call in progress — on every parent re-render.\n *\n * 🚨 THERE IS NO `userId` PROP, AND THERE NEVER WILL BE AGAIN (DD-240, 0.6.0).\n * The acting user is read from `client`'s own session — the same client that\n * mints the JWT on every request — so the id in a `p_user_id` argument and the\n * id in the token cannot disagree. Through 0.5.x this was a prop, every host\n * filled it from a copy in its own state, and a rotated auth cookie left that\n * copy naming an account the tab could no longer prove it was:\n * `meet_pending_call_invites` refused it at 403 with \"the acting user … is not\n * the authenticated user\", 8.8 h and 32.9 h into two tabs' lives. The whole\n * story is in `../core/session-identity.ts`.\n *\n * 🚨 A GUEST IS A FIRST-CLASS IDENTITY (D6), not a degraded user. Pass\n * `guestName` and the provider builds a runtime with no calling surface and no\n * host powers, which is exactly what a link-follower needs — and, because a\n * guest has no account at all, the guest lane never reads the session. Nothing\n * downstream branches on \"is this a real user\".\n */\n\nimport { globalSlot } from \"@ai-matrx/kit/global-slot\";\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type ReactNode,\n} from \"react\";\nimport {\n RealtimeProvider,\n useIsRealtimeProviderMounted,\n useRealtimeManager,\n} from \"@ai-matrx/realtime/react\";\nimport type { MessagingSupabaseClient } from \"@ai-matrx/messaging\";\nimport { createMeetAi, type MeetAi } from \"../core/ai\";\nimport { createMeetApi, type MeetApi } from \"../core/api\";\nimport { createCallCenter, type CallCenter } from \"../core/calls\";\nimport {\n createDeviceManager,\n createWebDeviceStorage,\n type DeviceManager,\n type DeviceSnapshot,\n type MediaDevicesLike,\n} from \"../core/devices\";\nimport { createMeetingSession, type MeetingSession } from \"../core/meetings\";\nimport { createRecordingController, type RecordingController } from \"../core/recording\";\nimport { createMeetRepository, type MeetRepository } from \"../core/repository\";\nimport {\n createMeetSessionIdentity as createSessionIdentity,\n SERVER_SESSION_IDENTITY,\n type SessionIdentitySnapshot,\n} from \"../core/session-identity\";\nimport { createRoomEngine, type RoomEngine } from \"../core/room\";\nimport { isWaitingPhase } from \"../core/phase\";\nimport { createWebEffectsStorage } from \"../core/effects\";\nimport { createInRoomClient, type InRoomClient } from \"../core/in-room\";\nimport { createLiveKitDriver } from \"../core/livekit-driver\";\nimport type { RoomDriverFactory } from \"../core/room-driver\";\nimport {\n createMeetStore,\n effectiveRole,\n type MeetSnapshot,\n type MeetStore,\n} from \"../core/store\";\nimport {\n createTokenClient,\n createWebRoomTokenStorage,\n type AccessTokenSource,\n type TokenClient,\n} from \"../core/tokens\";\nimport {\n asOrganizationId,\n isHostRole,\n type CallInvite,\n type MeetIdentity,\n type ParticipantRole,\n type UserId,\n} from \"../core/types\";\n\nexport interface MeetDiagnostic {\n readonly level: \"info\" | \"warn\" | \"error\";\n readonly message: string;\n readonly remedy?: string | undefined;\n}\n\nexport interface MeetHost {\n readonly store: MeetStore;\n readonly engine: RoomEngine;\n readonly calls: CallCenter | null;\n readonly meetings: MeetingSession;\n /**\n * Meet wave 2: host moderation and the SAVED chat, both decided and written by\n * the server (`core/in-room.ts`). A guest's calls carry their room pass.\n */\n readonly inRoom: InRoomClient;\n readonly recording: RecordingController;\n readonly devices: DeviceManager;\n readonly repository: MeetRepository;\n readonly tokens: TokenClient;\n /**\n * 🚨 NEVER NULL as of 0.3.0. Through 0.2.1 this was `null` unless the host\n * injected a transport AND four agent ids, so the meeting assistant silently\n * did not exist for any app that had not filled in a config line. The\n * capability is the SERVER's now: if a Mandate has no Holder bound, the\n * server refuses with a sentence naming the Mandate admin, and that sentence\n * is what the panel shows. Absence is a fact the server states, never a prop\n * a host forgot.\n */\n readonly ai: MeetAi;\n /**\n * 🚨 THE ONE WAY ANYTHING IN THIS PACKAGE TALKS TO AIDREAM (MRI-A16). Every\n * request it builds carries `x-organization-id`; a request built any other\n * way is refused by the org-admission middleware with 400\n * `organization_required`, and `core/api.test.ts` fails the build if a second\n * way appears. Playing a recording — `GET /files/{id}/download` — goes\n * through here too, which is the defect this replaced.\n */\n readonly api: MeetApi;\n readonly identity: MeetIdentity;\n readonly baseUrl: string;\n /**\n * The host's session source, as handed to `<MeetProvider>`. Exposed for the\n * host's own diagnostics; package code reaches aidream through `api`.\n */\n readonly accessToken: AccessTokenSource;\n readonly report: (event: MeetDiagnostic) => void;\n}\n\n// 🚨 ONE CONTEXT ACROSS ENTRIES (S1). `/react` and `/skins/meet` are separate\n// bundles (`splitting: false`), so each carries its own copy of this module. A\n// module-level `createContext` would give the skin's parts a DIFFERENT context\n// than the `<MeetProvider>` above them, and every part would read \"no host\".\n// The context lives in the globalThis slot, like every process-global (rule 4).\nconst MeetContext = globalSlot(\"ai-matrx.meet.react.context\", () => createContext<MeetHost | null>(null));\n\nexport interface MeetProviderProps {\n /**\n * Any Supabase client. `null` while the host is still resolving one.\n *\n * 🚨 DELIBERATELY THE SHALLOW SHAPE (0.2.1) — pass the\n * `SupabaseClient<Database>` you already have, with no cast. The rich\n * internal contract sent tsc into `TS2589` for any host with generated\n * Supabase types; see `core/repository.ts`'s header for the whole story.\n */\n client: MessagingSupabaseClient | null | undefined;\n /** aidream's base URL. LiveKit credentials live THERE, never here. */\n baseUrl: string;\n /**\n * A guest's typed name — and the ONE way to declare the guest lane (D6).\n * With it, this mount is a link-follower's room: no session is read, no\n * calling surface is built, and nothing user-scoped is ever issued. Without\n * it, the acting user is whoever `client`'s session says it is.\n *\n * 🚨 There is NO `userId` prop. See this file's header and\n * `../core/session-identity.ts`.\n */\n guestName?: string | null | undefined;\n organizationId: string | null | undefined;\n displayName?: string | undefined;\n avatarUrl?: string | null | undefined;\n /** The bearer token source for aidream calls. */\n accessToken: AccessTokenSource;\n /** Swap the media driver. Defaults to LiveKit — there is no reason to. */\n driverFactory?: RoomDriverFactory | undefined;\n /** A call is ringing this device — for a sound, a toast, or a badge. */\n onIncomingCall?: ((invite: CallInvite) => void) | undefined;\n onDiagnostic?: ((event: MeetDiagnostic) => void) | undefined;\n ringTimeoutMs?: number | undefined;\n children: ReactNode;\n}\n\nfunction defaultDiagnostics(event: MeetDiagnostic): void {\n const line = `[@ai-matrx/meet] ${event.message}${\n event.remedy !== undefined ? `\\n → ${event.remedy}` : \"\"\n }`;\n if (event.level === \"error\") console.error(line);\n else if (event.level === \"warn\") console.warn(line);\n else console.info(line);\n}\n\n/** A stable per-device id, so a reconnect keeps ONE LiveKit identity. */\nfunction deviceId(): string {\n const storage = (globalThis as { localStorage?: Storage }).localStorage;\n const key = \"mx.meet.device\";\n try {\n const existing = storage?.getItem(key);\n if (existing !== null && existing !== undefined && existing.length > 0) return existing;\n const created = Math.random().toString(36).slice(2, 14);\n storage?.setItem(key, created);\n return created;\n } catch {\n return Math.random().toString(36).slice(2, 14);\n }\n}\n\n/**\n * THE ONE SESSION READ. Subscribed per client, torn down with it, and honest\n * about the `resolving` window in between — see `../core/session-identity.ts`\n * for the class this closes.\n *\n * `client === null` (the guest lane, or a host still resolving one) never opens\n * a subscription at all: a link-follower has no account to read.\n */\nfunction useSessionIdentity(\n client: MessagingSupabaseClient | null | undefined,\n report: (event: MeetDiagnostic) => void,\n): SessionIdentitySnapshot {\n const [snapshot, setSnapshot] = useState<SessionIdentitySnapshot>(SERVER_SESSION_IDENTITY);\n\n useEffect(() => {\n if (client == null) {\n setSnapshot(SERVER_SESSION_IDENTITY);\n return undefined;\n }\n const identity = createSessionIdentity({ client, onDiagnostic: report });\n setSnapshot(identity.snapshot());\n const unsubscribe = identity.subscribe(() => setSnapshot(identity.snapshot()));\n return () => {\n unsubscribe();\n identity.dispose();\n };\n }, [client, report]);\n\n return snapshot;\n}\n\n/** A guest lane is declared by a NON-EMPTY typed name, and by nothing else. */\nfunction guestLaneName(guestName: string | null | undefined): string | null {\n return typeof guestName === \"string\" && guestName.trim().length > 0 ? guestName : null;\n}\n\nexport function MeetProvider(props: MeetProviderProps): ReactNode {\n const { client } = props;\n\n // Live ref so a host's fresh function literal per render never becomes part\n // of anything's lifetime, while callbacks still see the CURRENT one.\n const diagnosticRef = useRef(props.onDiagnostic);\n diagnosticRef.current = props.onDiagnostic;\n const report = useCallback((event: MeetDiagnostic): void => {\n (diagnosticRef.current ?? defaultDiagnostics)(event);\n }, []);\n\n // 🚨 NOTHING FAILS SILENTLY. A host still passing the 0.5.x `userId` prop —\n // a stale bundle, a JS consumer tsc never checked — is told, once, that it is\n // ignored and why. Silently dropping it would look exactly like the bug.\n const legacyUserIdReported = useRef(false);\n const legacyUserId = (props as { userId?: unknown }).userId;\n useEffect(() => {\n if (legacyUserId === undefined || legacyUserId === null) return;\n if (legacyUserIdReported.current) return;\n legacyUserIdReported.current = true;\n report({\n level: \"error\",\n message:\n \"<MeetProvider userId> is ignored: since 0.6.0 Meet reads the acting user from the \" +\n \"Supabase client's own session.\",\n remedy:\n \"Delete the userId prop. Passing a copy of the user id is what let a tab whose auth \" +\n \"cookie had rotated issue reads as an account it could no longer prove it was — the \" +\n \"database refused them at 403 (DD-240).\",\n });\n }, [legacyUserId, report]);\n\n const guestName = guestLaneName(props.guestName);\n // The guest lane reads no session; the member lane reads nothing else.\n const session = useSessionIdentity(guestName === null ? client : null, report);\n const actingUserId = guestName === null ? session.userId : null;\n\n // 🚨 RIDE THE HOST'S REALTIME MANAGER WHEN THERE IS ONE (0.6.5).\n //\n // Through 0.6.4 this ALWAYS mounted a provider of its own, with `client={null}`\n // until an organization was selected. That shadowed the host's manager for\n // everything below Meet: a sibling that rides a mounted host provider (as\n // `@ai-matrx/messaging` does) read a NULL manager forever when no org was\n // selected — `/messages` spun and a thread showed \"0 members\" — and with an\n // org selected it got a SECOND manager (two sockets, two write ledgers).\n // The probe is provider PRESENCE (`@ai-matrx/realtime` 0.3.0), never\n // `useRealtimeManager() !== null`, which is also null while a mounted host\n // provider waits for its client.\n //\n // And the realtime connection never waits on the organization: a socket is\n // the PERSON's (access belongs to the person, 2026-09-23). Whether Meet's own\n // runtime needs an org is MeetRuntime's question, not the transport's.\n const hostProvidesRealtime = useIsRealtimeProviderMounted();\n const runtime = (\n <MeetRuntime\n {...props}\n report={report}\n actingUserId={actingUserId}\n guestLaneName={guestName}\n sessionResolved={guestName !== null || session.state === \"resolved\"}\n />\n );\n if (hostProvidesRealtime) return runtime;\n return (\n <RealtimeProvider\n client={client != null ? (client as never) : null}\n actorId={actingUserId ?? undefined}\n >\n {runtime}\n </RealtimeProvider>\n );\n}\n\ninterface MeetRuntimeProps extends MeetProviderProps {\n readonly report: (event: MeetDiagnostic) => void;\n /** Who the SESSION says is acting. Never a prop from outside the package. */\n readonly actingUserId: UserId | null;\n readonly guestLaneName: string | null;\n readonly sessionResolved: boolean;\n}\n\nfunction MeetRuntime(props: MeetRuntimeProps): ReactNode {\n const { client, actingUserId, guestLaneName: guestName, organizationId, baseUrl, children } =\n props;\n const report = props.report;\n const manager = useRealtimeManager();\n\n // Live refs so callbacks always see the CURRENT render's functions without\n // those functions' identities being part of the runtime's lifetime.\n const incomingRef = useRef(props.onIncomingCall);\n incomingRef.current = props.onIncomingCall;\n const accessTokenRef = useRef(props.accessToken);\n accessTokenRef.current = props.accessToken;\n const driverRef = useRef(props.driverFactory);\n driverRef.current = props.driverFactory;\n\n const displayName =\n props.displayName ?? (typeof guestName === \"string\" ? guestName : \"Guest\");\n\n // 🚨 INERT UNTIL THE SESSION HAS ANSWERED. A runtime built during the\n // `resolving` window would issue its first `pendingInvites` read on behalf of\n // a guess; the whole point of DD-240 is that there is no guess.\n const ready =\n client != null &&\n manager !== null &&\n typeof organizationId === \"string\" &&\n organizationId.length > 0 &&\n props.sessionResolved &&\n (guestName !== null || actingUserId !== null);\n\n const host = useMemo<MeetHost | null>(() => {\n if (!ready || manager === null || client == null) return null;\n\n const identity: MeetIdentity = {\n // THE SESSION'S ANSWER, never a prop (DD-240).\n userId: actingUserId,\n organizationId: asOrganizationId(organizationId as string),\n displayName,\n avatarUrl: props.avatarUrl ?? null,\n deviceId: deviceId(),\n };\n\n const effectsStorage = createWebEffectsStorage();\n // Seeded BEFORE the room: the pre-join screen shows (and previews) the\n // remembered choice, and the engine applies the same one on connect.\n const store = createMeetStore({ effects: effectsStorage.load() });\n const devicesHolder: { current: DeviceManager | null } = { current: null };\n const api = createMeetApi({\n baseUrl,\n organizationId: identity.organizationId,\n accessToken: (...args) => accessTokenRef.current(...args),\n });\n const repository = createMeetRepository({ client });\n // MRF-1: a GUEST's pass is kept on this device so the durable link still\n // opens the record after the meeting — `/token` will not mint another one\n // for a finished meeting, correctly, and a guest has no session to prove\n // they were here with. `createTokenClient` stores only guest passes.\n const tokens = createTokenClient({\n api,\n storage: createWebRoomTokenStorage((message, remedy) =>\n report({ level: \"warn\", message, remedy }),\n ),\n });\n const engine = createRoomEngine({\n store,\n tokens,\n identity,\n resolveMeeting: (slug) => repository.meetingBySlug(slug),\n driverFactory: (handlers) =>\n (driverRef.current ?? createLiveKitDriver)(handlers),\n onDiagnostic: report,\n effectsStorage,\n resolveDevice: (kind) => devicesHolder.current?.resolve(kind) ?? null,\n // §3.4: on every LiveKit disconnect, ask the server what happened to this connection.\n readExit: (connection) => meetings.readExit(connection),\n });\n const meetings = createMeetingSession({\n manager,\n repository,\n store,\n identity,\n api,\n onDiagnostic: report,\n // ADMISSION IS A JOIN, NOT A RE-RENDER. The lobby token grants neither\n // publish nor subscribe, so the host saying yes has to mint a new one. The\n // \"yes\" is read from the server (the state read), never a broadcast (S4).\n onAdmitted: () => {\n void engine.rejoin().catch(() => undefined);\n },\n rejoin: () => engine.rejoin(),\n // THE MEETING PASS this device holds — what the state read and `/leave` carry.\n resolvePass: () => {\n const roomName = store.snapshot().meeting?.roomName;\n const active = engine.token();\n if (active?.meetingPass) return active.meetingPass;\n return roomName === undefined ? null : (tokens.remembered(roomName)?.meetingPass ?? null);\n },\n });\n // THE AI IS PART OF THE RUNTIME, not an optional attachment. Nothing here\n // names an agent: every capability is a server-owned Mandate (D2).\n const resolveRoomToken = (): string | null => {\n const roomName = store.snapshot().meeting?.roomName;\n if (roomName === undefined) return null;\n const active = engine.token();\n // THE MEETING PASS, never the LiveKit token (CORE-DESIGN §2.5): `x-meet-pass`.\n if (active?.roomName === roomName && active.meetingPass) return active.meetingPass;\n return tokens.remembered(roomName)?.meetingPass ?? null;\n };\n const ai = createMeetAi({ api, store, resolveRoomToken });\n const inRoom = createInRoomClient({ api, store, resolveRoomToken });\n // THE SAVED CHAT IS READ ON EVERY JOIN (Meet wave 2) — that is what makes a\n // late joiner see what was already said. Once per connected room; a refusal\n // is a diagnostic with a remedy, and live messages keep arriving regardless.\n // S4: THE WAIT READS SERVER TRUTH while (and only while) this tab is in the waiting room.\n store.subscribe(() => {\n const snapshot = store.snapshot();\n if (isWaitingPhase(snapshot.phase) && snapshot.waiting !== null &&\n (snapshot.waiting.admission === \"knocking\" || snapshot.waiting.admission === \"waiting_host\")) {\n meetings.startWaiting();\n } else if (!isWaitingPhase(snapshot.phase)) {\n meetings.stopWaiting();\n }\n });\n let chatReadFor: string | null = null;\n store.subscribe(() => {\n const snapshot = store.snapshot();\n if (snapshot.phase !== \"in_call\" || snapshot.meeting === null) {\n if (snapshot.phase === \"left\" || snapshot.phase === \"prejoin\" || snapshot.phase === \"resolving\") chatReadFor = null;\n return;\n }\n if (chatReadFor === snapshot.meeting.id) return;\n chatReadFor = snapshot.meeting.id;\n // Meet wave 5: polls, questions, the whiteboard and an open breakout plan — read\n // on the same once-per-join moment, so a late joiner sees them all. A refusal is\n // written to `collab.error` by the client and reported here.\n void inRoom.loadCollab().catch((error: unknown) => {\n const normalized = error as { message?: string; remedy?: string };\n report({\n level: \"warn\",\n message: normalized.message ?? \"meet: polls, questions and the whiteboard could not be read\",\n ...(normalized.remedy !== undefined ? { remedy: normalized.remedy } : {}),\n });\n });\n void inRoom.loadChat().catch((error: unknown) => {\n const normalized = error as { message?: string; remedy?: string };\n // NEVER \"Reading…\" FOREVER: the panel says history is unavailable, and why.\n store.patch({\n chatLoaded: true,\n chatHistoryError:\n normalized.remedy ?? \"Messages sent before you joined could not be read. New messages still arrive.\",\n });\n report({\n level: \"warn\",\n message: normalized.message ?? \"meet: the saved chat could not be read\",\n ...(normalized.remedy !== undefined ? { remedy: normalized.remedy } : {}),\n });\n });\n });\n const recording = createRecordingController({\n api,\n store,\n acknowledgeConsent: async (meetingId) => {\n await repository.recordConsent({\n meetingId,\n identity: store.snapshot().localIdentity ?? identity.deviceId,\n acknowledgedAt: new Date().toISOString(),\n });\n },\n });\n const devices = createDeviceManager({\n media: (globalThis as unknown as { navigator?: { mediaDevices?: MediaDevicesLike } })\n .navigator?.mediaDevices ?? {\n // An SSR render or a browser without media devices gets an INERT\n // manager that reports the truth, rather than a crash on import.\n enumerateDevices: async () => [],\n getUserMedia: async () => ({ getTracks: () => [] }),\n },\n storage: createWebDeviceStorage(),\n });\n devicesHolder.current = devices;\n // A GUEST HAS NO CALLING SURFACE. Not \"a disabled one\" — none at all, so\n // there is nothing to render as a dead button (D6, no dead ends).\n const calls =\n identity.userId === null\n ? null\n : createCallCenter({\n manager,\n repository,\n store,\n identity,\n onIncoming: (invite) => incomingRef.current?.(invite),\n // §3.6: my call was answered — the caller joins the call's room on the same machine.\n onAnswered: (invite) => {\n void engine.join({ roomName: invite.roomName, mode: invite.mode }).catch(() => undefined);\n },\n onDiagnostic: report,\n ...(props.ringTimeoutMs !== undefined ? { ringTimeoutMs: props.ringTimeoutMs } : {}),\n });\n\n return {\n store,\n engine,\n calls,\n meetings,\n inRoom,\n recording,\n devices,\n repository,\n tokens,\n ai,\n api,\n identity,\n baseUrl,\n accessToken: (...args) => accessTokenRef.current(...args),\n report,\n };\n // 🚨 IDENTITY ONLY. See the header: adding callbacks or option objects here\n // rebuilds every channel — and drops the call in progress — on every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n ready,\n manager,\n client,\n actingUserId,\n guestName,\n organizationId,\n baseUrl,\n displayName,\n report,\n ]);\n\n useEffect(() => {\n if (host?.calls == null) return undefined;\n const center = host.calls;\n void center.start().catch((error: unknown) => {\n host.report({\n level: \"error\",\n message: `Meet could not start listening for calls: ${String(\n (error as Error)?.message ?? error,\n )}`,\n remedy:\n \"Calls will not ring on this device until this succeeds. Check the session and \" +\n \"organization, then reload the surface.\",\n });\n });\n return () => {\n center.dispose();\n };\n }, [host]);\n\n useEffect(() => {\n if (host === null) return undefined;\n return () => {\n host.meetings.dispose();\n host.devices.dispose();\n void host.engine.dispose();\n };\n }, [host]);\n\n return <MeetContext.Provider value={host}>{children}</MeetContext.Provider>;\n}\n\n/** The host, or `null` while auth/org are still resolving. */\nexport function useMeetHost(): MeetHost | null {\n return useContext(MeetContext);\n}\n\nexport function useRequiredMeetHost(): MeetHost {\n const host = useMeetHost();\n if (host === null) {\n throw new Error(\n \"[@ai-matrx/meet] No meet host. Wrap this tree in <MeetProvider client={supabase} \" +\n \"baseUrl={aidreamUrl} organizationId={orgId} accessToken={fn}> and render it only \" +\n \"once auth has resolved.\",\n );\n }\n return host;\n}\n\n/** The whole room snapshot, re-rendering only when it actually changes. */\nexport function useMeetSnapshot(): MeetSnapshot | null {\n const host = useMeetHost();\n const store = host?.store ?? null;\n const empty = useRef<MeetSnapshot | null>(null);\n return useSyncExternalStore(\n (listener) => (store === null ? () => undefined : store.subscribe(listener)),\n () => (store === null ? empty.current : store.snapshot()),\n () => (store === null ? empty.current : store.snapshot()),\n );\n}\n\nexport function useDeviceSnapshot(): DeviceSnapshot | null {\n const host = useMeetHost();\n const devices = host?.devices ?? null;\n return useSyncExternalStore(\n (listener) => (devices === null ? () => undefined : devices.subscribe(() => listener())),\n () => devices?.snapshot() ?? null,\n () => devices?.snapshot() ?? null,\n );\n}\n\n/**\n * 🚨 THE LOCAL PARTICIPANT'S ROLE — THE SERVER'S WORD, FROM BOTH ITS CARRIERS\n * (MRI-A11, feedback `7418ef78`).\n *\n * Two sources, one decision made once in `services/meet/service.py`:\n *\n * 1. `snapshot.grants.role` — the `/meet/token` response, in the store from the\n * instant the token lands, which is BEFORE any LiveKit roster exists.\n * 2. the local participant's roster entry, whose role comes from the\n * `matrx.participant.role` LiveKit attribute the same mint published.\n *\n * The stronger of the two wins, and both are server-minted, so this can never\n * be client state: there is no branch here a browser could take on its own.\n * Reading only (2) is the shipped defect this replaces — a roster entry arrives\n * a beat later than the token and, before MRI-A11, carried no role at all.\n */\nexport function useRole(): ParticipantRole {\n const snapshot = useMeetSnapshot();\n // ONE definition, shared with the code that PERFORMS the host actions\n // (`core/store.ts`). A second copy here is how the UI and the action drift.\n return snapshot === null ? \"participant\" : effectiveRole(snapshot);\n}\n\n/** Does the local participant hold host powers? ONE predicate, one definition. */\nexport function useIsHost(): boolean {\n return isHostRole(useRole());\n}\n\n/**\n * May the local participant start a recording? NOT `isHost` — the meeting's\n * recording policy is the server's, and an `always-on` meeting is recorded by\n * the server with no host control at all (MRI-A11).\n */\nexport function useCanRecord(): boolean {\n const snapshot = useMeetSnapshot();\n if (snapshot === null) return false;\n const me = snapshot.participants.find(\n (participant) => participant.identity === snapshot.localIdentity,\n );\n return snapshot.grants.canRecord || me?.canRecord === true;\n}\n\nexport function useLocalUserId(): UserId | null {\n return useMeetHost()?.identity.userId ?? null;\n}\n","/**\n * MEETINGS — the lobby, the host powers, and the durable link.\n *\n * A meeting is a room plus scheduling, lobby and host metadata (D3). Everything\n * here is about the moments OUTSIDE the media session, which is exactly why it\n * cannot ride LiveKit's data plane: a guest waiting in the lobby is not in the\n * LiveKit room yet, by construction. The lobby therefore lives on the meet-room\n * realtime channel.\n *\n * 🚨 THE THREE THINGS THAT MAKE GUEST JOIN SAFE (D6)\n *\n * 1. **The link is an identity, not a session.** A durable slug resolves before,\n * during, and after the scheduled time, and a recurring meeting keeps ONE\n * link. Nothing here mints a per-occurrence URL.\n * 2. **A guest's capability is the TOKEN, and nothing else.** They are scoped to\n * exactly one room. There is no guest row this client writes that could grant\n * anything, and no client-side \"is this person allowed\" branch.\n * 3. **Admission is a SERVER decision, recorded.** `admit` is an RPC; the\n * broadcast that tells the knocker is a NOTIFICATION of that decision, never\n * the decision itself. Two hosts clicking admit is the ordinary case, and the\n * RPC is what makes it idempotent.\n *\n * Host powers are checked against the role the SERVER minted into the token\n * (see `room.ts`), and the UI hides what an actor cannot do rather than\n * rendering a dead button (no dead ends).\n */\n\nimport { isWaitingPhase, type ExitServerWord } from \"./phase\";\nimport type { ChannelHandle, RealtimeManager } from \"@ai-matrx/realtime\";\nimport { MEET_EVENTS, meetingTopic, roomTopic } from \"./channels\";\nimport type { MeetWaiting } from \"./store\";\nimport type { MeetApi } from \"./api\";\nimport { MeetError } from \"./errors\";\nimport { readServerRefusal } from \"./ai\";\nimport type { MeetRepository } from \"./repository\";\nimport { effectiveRole, type MeetStore } from \"./store\";\nimport { MEET_ROUTES, MEETING_PASS_HEADER } from \"./tokens\";\nimport {\n asParticipantIdentity,\n isHostRole,\n isGuest,\n WAITING_ADMISSION_STATES,\n type AdmissionState,\n ROLE_RANK,\n type LobbyEntry,\n type MeetIdentity,\n type MeetParticipant,\n type MeetingAttendee,\n type MeetingId,\n type MeetingRecord,\n type ParticipantIdentity,\n type ParticipantRole,\n type RecordingState,\n type RoomName,\n type UserId,\n} from \"./types\";\n\nexport interface MeetingSessionOptions {\n manager: RealtimeManager;\n repository: MeetRepository;\n store: MeetStore;\n identity: MeetIdentity;\n /**\n * The ONE org-aware aidream client (MRI-A16) — `endForEveryone` is a SERVER\n * decision (MRI-A6), and every request it makes carries the organization.\n */\n api: MeetApi;\n onDiagnostic?: ((event: { level: \"info\" | \"warn\" | \"error\"; message: string; remedy?: string }) => void) | undefined;\n /**\n * 🚨 THE HOST ADMITTED *US*. Called exactly once per admission verdict that\n * names this participant, and only for `admitted`.\n *\n * Admission is not a UI state change: a lobby token is minted with\n * `canPublish: false` and `canSubscribe: false`, so the waiting room can only\n * become a room by minting a NEW token. Before this existed the waiting room\n * said \"you will join automatically once you are admitted\" and then waited\n * forever — a sentence on screen that was not true. The provider wires this\n * to `engine.rejoin()`.\n */\n onAdmitted?: (() => void) | undefined;\n /**\n * S4: the meeting pass this device holds (`x-meet-pass`) — what the state read and\n * `/leave` are authorized by. Null when no pass is held yet.\n */\n resolvePass?: (() => string | null) | undefined;\n /** S4: mint again (Ask to join again / Rejoin from the waiting screens). */\n rejoin?: (() => Promise<void>) | undefined;\n}\n\nexport interface MeetingSession {\n /** Subscribe to the room's out-of-band plane. Hosts see knocks; guests see verdicts. */\n watch(roomName: RoomName): void;\n /**\n * Subscribe to the MEETING RECORD's durable feed — transcript segments, notes,\n * and the meeting row itself (which is where the note-taker's state lives).\n * Reconnecting IS re-reading: the backfill door re-reads all three, because\n * realtime has no replay and a socket blip would otherwise leave a transcript\n * permanently missing the minute nobody noticed.\n */\n watchMeeting(meetingId: MeetingId): void;\n admit(entry: LobbyEntry, admitted: boolean): Promise<void>;\n /** S4: lift a denial so the person may knock again (`meet_undeny`). */\n undeny(entry: LobbyEntry): Promise<void>;\n admitAll(): Promise<void>;\n /**\n * 🚨 S4 — THE WAIT READS SERVER TRUTH (CORE-DESIGN §2.6). While this tab waits, the state\n * read (`GET /v1/meet/state`) runs every `waiting_poll_seconds`, on every\n * `mx.state.changed` hint, on `online` and on visibility return. Admitted → a fresh mint\n * (`rejoin`); denied / expired / ended are what the row says, never a broadcast's guess.\n */\n startWaiting(): void;\n stopWaiting(): void;\n /** One state read now (idempotent while one is in flight). */\n readState(): Promise<void>;\n /**\n * §3.4 ER: what the server recorded for THIS connection's exit, and the run's phase. null =\n * the read failed (offline / unreachable); undefined = no meeting or no pass to ask with.\n */\n readExit(connection: string): Promise<ExitServerWord | null | undefined>;\n /** Stop waiting: the row becomes `withdrawn` (`POST /v1/meet/leave {intent: \"withdraw\"}`). */\n withdraw(): Promise<void>;\n /** Ask again after a denial or an expired knock (only offered when allowed). */\n askAgain(): Promise<void>;\n setLock(locked: boolean): Promise<MeetingRecord>;\n /** End it for everyone, through the server (MRI-A6). */\n endForEveryone(): Promise<EndMeetingResult>;\n dispose(): void;\n}\n\n/** Can `actor` moderate `target`? A host outranks a co-host outranks everyone. */\nexport function canModerateParticipant(\n actor: MeetParticipant | undefined,\n target: MeetParticipant | undefined,\n): boolean {\n if (actor === undefined || target === undefined) return false;\n if (actor.role === \"participant\") return false;\n if (actor.identity === target.identity) return false;\n // A co-host cannot remove the host. Without this rank check, \"host controls\"\n // is a mutual-eviction button.\n return ROLE_RANK[actor.role] > ROLE_RANK[target.role];\n}\n\nexport function roleOf(\n participants: readonly MeetParticipant[],\n identity: ParticipantIdentity | null,\n): ParticipantRole {\n if (identity === null) return \"participant\";\n return participants.find((p) => p.identity === identity)?.role ?? \"participant\";\n}\n\n/**\n * What ending a meeting produced. `served: false` is the honest answer from a\n * server that has not landed `POST /v1/meet/end` yet (MRI-A6) — the control is\n * real, the refusal is a sentence, and nothing pretends the meeting ended.\n */\nexport interface EndMeetingResult {\n readonly endedAt: string | null;\n readonly served: boolean;\n}\n\n/** The sentence a server without MRI-A6 earns. Pinned so every surface says it. */\nexport const END_MEETING_NOT_SERVED =\n \"Ending a meeting is not available on this server yet\";\n\n/**\n * One durable `meet_participants` row as a lobby entry, or `null` when it is\n * not one. A row that has been decided is not a knocker; the panel shows people\n * who are WAITING, and `store.upsertLobby` keyed by identity is what lets the\n * table and the broadcast be two copies of one person rather than two rows.\n */\nfunction lobbyEntryFrom(attendee: MeetingAttendee, now: number = Date.now()): LobbyEntry | null {\n if (!WAITING_ADMISSION_STATES.includes(attendee.admissionState)) return null;\n // A knock past its expiry IS expired (the server CASes it on the knocker's next read).\n const expires = attendee.knockExpiresAt ? Date.parse(attendee.knockExpiresAt) : Number.NaN;\n if (!Number.isNaN(expires) && expires <= now) return null;\n return {\n identity: attendee.identity,\n displayName: attendee.displayName,\n avatarUrl: attendee.avatarUrl,\n userId: attendee.userId,\n state: \"knocking\",\n knockedAt: attendee.knockedAt ?? attendee.joinedAt ?? new Date().toISOString(),\n knockExpiresAt: attendee.knockExpiresAt ?? null,\n decidedAt: null,\n decidedBy: null,\n };\n}\n\n/** The row's word for a person no longer waiting, as the host's lobby list records it. */\nfunction decidedState(state: AdmissionState): AdmissionState {\n return WAITING_ADMISSION_STATES.includes(state) ? \"expired\" : state;\n}\n\n/** The server's state read, as this package reads it. */\ninterface StateRead {\n readonly admission_state?: string | null;\n readonly admitters_present?: boolean;\n readonly reknock_allowed?: boolean;\n readonly knock_expires_at?: string | null;\n readonly session_phase?: string;\n readonly meeting_phase?: string;\n readonly live_version?: number;\n readonly poll_seconds?: number;\n readonly exit_reason?: string | null;\n}\n\nexport function createMeetingSession(options: MeetingSessionOptions): MeetingSession {\n const { manager, repository, store, identity } = options;\n let channel: ChannelHandle | null = null;\n let meetingChannel: ChannelHandle | null = null;\n\n /**\n * The meeting row, for anyone who may be in it. A member or a person the\n * meeting was shared with reads it under RLS; a signed-in person from ANOTHER\n * organization who followed the link cannot, and gets the same row through\n * the link door (`meet_meeting_by_slug`) a guest uses — the server lets them\n * in by the link, so the screen must not fail them for holding it.\n */\n async function readMeeting(meetingId: MeetingId): Promise<MeetingRecord> {\n try {\n return await repository.meeting(meetingId);\n } catch (error) {\n const slug = store.snapshot().meeting?.slug;\n if (slug === undefined) throw error;\n return repository.meetingBySlug(slug);\n }\n }\n\n function requireHostUser(operation: string): UserId {\n // THE SAME PREDICATE THE UI RENDERS FROM (MRI-A11). Reading only the roster\n // here — while the control renders from the token grant — is how a host\n // control becomes a button that throws the moment it is used.\n if (identity.userId === null || !isHostRole(effectiveRole(store.snapshot()))) {\n throw new MeetError(\n \"forbidden\",\n `${operation}: this participant is not a host`,\n \"Host powers come from the server-minted token metadata and are enforced by the RPC. \" +\n \"Hide the control rather than rendering a button that will be refused.\",\n );\n }\n return identity.userId;\n }\n\n function requireMeeting(operation: string): MeetingRecord {\n const meeting = store.snapshot().meeting;\n if (meeting === null) {\n throw new MeetError(\n \"misconfigured\",\n `${operation}: no meeting is loaded`,\n \"Ad-hoc call rooms have no meeting record and therefore no lobby or lock. Use \" +\n \"joinMeeting()/scheduleMeeting() when you need those powers.\",\n );\n }\n return meeting;\n }\n\n // ── S4: the waiting supervisor ─────────────────────────────────────────\n let polling = false;\n let pollTimer: ReturnType<typeof setTimeout> | null = null;\n let reading: Promise<void> | null = null;\n let admittedOnce = false;\n let unlisten: (() => void) | null = null;\n\n function report(level: \"info\" | \"warn\" | \"error\", message: string, remedy?: string): void {\n options.onDiagnostic?.(remedy === undefined ? { level, message } : { level, message, remedy });\n }\n\n function pollMs(): number {\n const seconds = store.snapshot().waiting?.pollSeconds ?? 3;\n return Math.max(1, Math.min(60, seconds)) * 1000;\n }\n\n function schedule(ms: number): void {\n if (pollTimer !== null) clearTimeout(pollTimer);\n if (!polling) return;\n pollTimer = setTimeout(() => {\n pollTimer = null;\n void readState().finally(() => schedule(pollMs()));\n }, ms);\n }\n\n function listen(): void {\n if (unlisten !== null || typeof window === \"undefined\") return;\n const nudge = (): void => {\n if (typeof document !== \"undefined\" && document.visibilityState === \"hidden\") return;\n void readState();\n };\n // Closing the tab while waiting withdraws the knock (beacon: no headers, pass in the body).\n const goodbye = (): void => {\n const meeting = store.snapshot().meeting;\n const pass = options.resolvePass?.() ?? null;\n const waiting = store.snapshot().waiting;\n if (meeting === null || pass === null || waiting === null) return;\n if (!WAITING_ADMISSION_STATES.includes(waiting.admission)) return;\n try {\n navigator.sendBeacon?.(\n options.api.url(MEET_ROUTES.leave),\n new Blob([JSON.stringify({ meeting_id: meeting.id, intent: \"withdraw\", meeting_pass: pass })], {\n type: \"application/json\",\n }),\n );\n } catch {\n // Best effort: an unanswered knock still lapses on its own (knock_timeout_minutes).\n }\n };\n window.addEventListener(\"online\", nudge);\n window.addEventListener(\"pagehide\", goodbye);\n document.addEventListener(\"visibilitychange\", nudge);\n unlisten = () => {\n window.removeEventListener(\"online\", nudge);\n window.removeEventListener(\"pagehide\", goodbye);\n document.removeEventListener(\"visibilitychange\", nudge);\n };\n }\n\n function stopWaiting(): void {\n polling = false;\n if (pollTimer !== null) clearTimeout(pollTimer);\n pollTimer = null;\n unlisten?.();\n unlisten = null;\n }\n\n function patchWaiting(next: Partial<MeetWaiting>): void {\n const current = store.snapshot().waiting;\n const base: MeetWaiting = current ?? {\n admission: \"knocking\",\n admittersPresent: null,\n reknockAllowed: true,\n knockExpiresAt: null,\n pollSeconds: 3,\n liveVersion: 0,\n };\n store.patch({ waiting: { ...base, ...next } });\n }\n\n function apply(read: StateRead): void {\n const snapshot = store.snapshot();\n if (!isWaitingPhase(snapshot.phase)) return;\n const admission = (read.admission_state ?? null) as AdmissionState | null;\n const ended =\n admission === \"ended\" || read.session_phase === \"ended\" || read.meeting_phase === \"ended\";\n if (admission === \"admitted\" && !ended) {\n if (admittedOnce) return;\n admittedOnce = true;\n stopWaiting();\n // ADMISSION IS A JOIN: a fresh mint with the grants the server now gives.\n options.onAdmitted?.();\n return;\n }\n if (ended) {\n stopWaiting();\n patchWaiting({ admission: \"ended\" });\n const meeting = snapshot.meeting;\n if (read.meeting_phase === \"ended\" && meeting !== null && meeting.endedAt === null) {\n store.patch({ meeting: { ...meeting, endedAt: new Date().toISOString() } });\n }\n store.dispatch({ type: \"admission\", admission: \"ended\" }, { reason: \"ended\" });\n return;\n }\n if (admission === null) return;\n patchWaiting({\n admission,\n admittersPresent: read.admitters_present ?? null,\n reknockAllowed: read.reknock_allowed ?? true,\n knockExpiresAt: read.knock_expires_at ?? null,\n pollSeconds: read.poll_seconds ?? snapshot.waiting?.pollSeconds ?? 3,\n liveVersion: read.live_version ?? 0,\n });\n // THE SERVER'S WORD MOVES THE PHASE (§3.1): one machine event, never a patch.\n store.dispatch({ type: \"admission\", admission });\n // A decision that is not \"yes\" ends the wait; the screen offers what the rules allow.\n if (admission === \"denied\" || admission === \"expired\" || admission === \"withdrawn\" || admission === \"removed\") {\n stopWaiting();\n }\n }\n\n async function readState(): Promise<void> {\n if (reading !== null) return reading;\n const meeting = store.snapshot().meeting;\n const pass = options.resolvePass?.() ?? null;\n if (meeting === null || pass === null) return;\n reading = (async () => {\n try {\n const response = await options.api.request({\n path: `${MEET_ROUTES.state}?meeting_id=${encodeURIComponent(meeting.id)}`,\n method: \"GET\",\n requireAuth: false,\n operation: \"meet.state\",\n headers: { [MEETING_PASS_HEADER]: pass },\n });\n if (!response.ok) {\n let body: unknown = null;\n try {\n body = await response.json();\n } catch {\n body = null;\n }\n const refusal = readServerRefusal(body);\n report(\n \"warn\",\n refusal?.reason ?? `meet.state: the server answered ${response.status}`,\n refusal?.remedy ?? \"The waiting room keeps asking; nothing on this screen changes until the server answers.\",\n );\n return;\n }\n apply((await response.json()) as StateRead);\n } catch (error) {\n // Offline or the server is unreachable: the next tick (or `online`) asks again.\n report(\"info\", `meet.state: ${String(error)}`, \"Waiting continues; the state is read again shortly.\");\n } finally {\n reading = null;\n }\n })();\n return reading;\n }\n\n async function postLeave(intent: \"withdraw\" | \"leave\"): Promise<void> {\n const meeting = requireMeeting(`meet.${intent}`);\n const pass = options.resolvePass?.() ?? null;\n const response = await options.api.request({\n path: MEET_ROUTES.leave,\n method: \"POST\",\n requireAuth: false,\n operation: `meet.${intent}`,\n body: { meeting_id: meeting.id, intent },\n ...(pass !== null ? { headers: { [MEETING_PASS_HEADER]: pass } } : {}),\n });\n if (!response.ok) {\n let body: unknown = null;\n try {\n body = await response.json();\n } catch {\n body = null;\n }\n const refusal = readServerRefusal(body);\n throw new MeetError(\n \"transport\",\n refusal?.reason ?? `meet.${intent}: the server answered ${response.status}`,\n refusal?.remedy ?? \"You are still in the waiting room. Try again.\",\n );\n }\n }\n\n /** After a member-client admission write the CLIENT sends the hint (CORE-DESIGN §2.4). */\n function hint(): void {\n channel?.send(MEET_EVENTS.stateChanged, { v: null });\n }\n\n /** The host's second carrier: on a hint, re-read who is at the door. */\n async function refreshQueue(): Promise<void> {\n const meeting = store.snapshot().meeting;\n if (meeting === null || isGuest(identity) || !isHostRole(effectiveRole(store.snapshot()))) return;\n try {\n const attendees = await repository.attendees(meeting.id);\n store.learn(attendees);\n for (const attendee of attendees) {\n const entry = lobbyEntryFrom(attendee);\n if (entry !== null) store.upsertLobby(entry);\n else store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);\n }\n } catch {\n // Postgres Changes on the same rows is the other carrier; a miss here costs nothing.\n }\n }\n\n return {\n watch(roomName) {\n channel?.close();\n channel = manager.open({\n topic: roomTopic(roomName),\n broadcast: [\n {\n // 🚨 PAYLOAD-FREE. Every receiver reads again; a spoofed hint only causes a read.\n event: MEET_EVENTS.stateChanged,\n onMessage: () => {\n if (isWaitingPhase(store.snapshot().phase)) void readState();\n void refreshQueue();\n },\n },\n ],\n // THE BACKFILL DOOR. A socket blip must not lose a decision: re-read the meeting\n // row, and a waiting tab re-reads its own state.\n onBackfill: async () => {\n const meeting = store.snapshot().meeting;\n if (meeting === null) return;\n const fresh = isGuest(identity)\n ? await repository.meetingBySlug(meeting.slug)\n : await readMeeting(meeting.id);\n store.patch({ meeting: fresh, locked: fresh.locked });\n if (isWaitingPhase(store.snapshot().phase)) void readState();\n },\n });\n },\n\n watchMeeting(meetingId) {\n meetingChannel?.close();\n meetingChannel = null;\n if (isGuest(identity)) {\n options.onDiagnostic?.({\n level: \"info\",\n message: \"Guest notes and captions arrive through the live meeting room; saved history is not available without a session.\",\n remedy: \"Sign in with access to this meeting to reload saved notes and transcript history.\",\n });\n return;\n }\n meetingChannel = manager.open({\n topic: meetingTopic(meetingId),\n postgresChanges: [\n {\n event: \"*\",\n schema: repository.schema,\n table: repository.tables.transcripts,\n filter: `meeting_id=eq.${meetingId}`,\n rowId: (row) => (typeof row[\"id\"] === \"string\" ? row[\"id\"] : undefined),\n onChange: ({ row }) => {\n // D16: THE ROW IS THE TRANSCRIPT. It replaces the caption that\n // showed the same words a moment earlier.\n if (row !== null) store.ingestRecord(repository.projectSegment(row));\n },\n },\n {\n event: \"*\",\n schema: repository.schema,\n table: repository.tables.notes,\n filter: `meeting_id=eq.${meetingId}`,\n rowId: (row) => (typeof row[\"id\"] === \"string\" ? row[\"id\"] : undefined),\n onChange: ({ row }) => {\n // Upsert, not append: a re-run of the wrap-up rewrites the SAME\n // row ids by design, and appending would show every note twice.\n if (row !== null) store.upsertNote(repository.projectNote(row));\n },\n },\n {\n // 🚨 THE LOBBY IS A TABLE, NOT ONLY A CHANNEL (MRI-A16). A guest\n // knocks by writing `admission_state='knocking'` AND broadcasting;\n // through 0.4.7 the host's People panel rendered only the broadcast,\n // so a knocker whose packet landed before this browser opened the\n // channel — a host who joined second, reloaded, or slept a laptop —\n // was INVISIBLE while the guest sat in the waiting room forever. A\n // host can read this table; a guest cannot, and gets no lobby list\n // at all, which is correct.\n event: \"*\",\n schema: repository.schema,\n table: repository.tables.participants,\n filter: `meeting_id=eq.${meetingId}`,\n rowId: (row) => (typeof row[\"id\"] === \"string\" ? row[\"id\"] : undefined),\n onChange: ({ row }) => {\n if (row === null) return;\n const attendee = repository.projectAttendee(row);\n store.learn([attendee]);\n const entry = lobbyEntryFrom(attendee);\n if (entry !== null) {\n store.upsertLobby(entry);\n return;\n }\n // 🚨 A DECIDED ROW MUST CLEAR THE DOOR. Admitting a guest from\n // another tab writes `admitted` here, and simply ignoring the row\n // would leave this host's panel offering to admit somebody who is\n // already in the meeting — a ghost knocker who can never be\n // dismissed. The durable row is the verdict, wherever it was made.\n store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);\n },\n },\n {\n event: \"*\",\n schema: repository.schema,\n table: repository.tables.recordings,\n filter: `meeting_id=eq.${meetingId}`,\n rowId: (row) => (typeof row[\"id\"] === \"string\" ? row[\"id\"] : undefined),\n onChange: ({ row }) => {\n // 🚨 THE INDICATOR RENDERS THE DURABLE STATE, not this browser's\n // hope. Through 0.4.7 nothing subscribed here, so the only\n // transitions the store ever saw were the optimistic `starting`\n // and `stopping` THIS client patched — and the indicator sat on\n // \"Starting recording…\" for a whole meeting while a healthy\n // egress ran, then \"Stopping recording…\" forever (MRI-C8).\n if (row === null) return;\n const recording = repository.projectRecording(row);\n store.setRecording(recording.state, recording.startedBy, \"record\", recording.id);\n },\n },\n {\n event: \"*\",\n schema: repository.schema,\n table: repository.tables.meetings,\n filter: `id=eq.${meetingId}`,\n rowId: (row) => (typeof row[\"id\"] === \"string\" ? row[\"id\"] : undefined),\n onChange: ({ row }) => {\n // The note-taker's state lives on this row's metadata (D9/MRI-A2),\n // so this subscription is what keeps each server-recorded state\n // current on screen without a reload.\n if (row === null) return;\n const meeting = repository.projectMeeting(row);\n store.patch({ meeting, locked: meeting.locked });\n },\n },\n ],\n // THE BACKFILL DOOR. Realtime has no replay: everything that happened\n // while the socket was down is gone. Re-reading all three is what keeps\n // a recovered surface from looking healthy and being wrong.\n onBackfill: async () => {\n const [meeting, transcript, notes, recordings, attendees] = await Promise.all([\n readMeeting(meetingId),\n repository.transcript(meetingId),\n repository.notes(meetingId),\n repository.recordings(meetingId),\n repository.attendees(meetingId),\n ]);\n store.patch({ meeting, locked: meeting.locked });\n for (const segment of transcript) store.ingestRecord(segment);\n for (const note of notes) store.upsertNote(note);\n // A socket blip must not leave the indicator saying something the\n // database stopped agreeing with ten minutes ago. `recordings` is\n // newest first.\n const latest = recordings[0];\n if (latest !== undefined) {\n store.setRecording(latest.state, latest.startedBy, \"record\", latest.id);\n }\n // Everyone the durable rows know a name for, and every knocker still\n // waiting. A host who reloads must not lose a person at the door.\n store.learn(attendees);\n for (const attendee of attendees) {\n const entry = lobbyEntryFrom(attendee);\n if (entry !== null) {\n store.upsertLobby(entry);\n } else {\n // The same rule on the backfill door: a host who reloads must not\n // find people at the door who were admitted while they were gone.\n store.setAdmission(attendee.identity, decidedState(attendee.admissionState), null);\n }\n }\n },\n });\n // The FIRST read is the same read: a surface that joins a meeting already\n // in progress must see what was already said.\n meetingChannel.requestBackfill(\"manual\");\n },\n\n async admit(entry, admitted) {\n const by = requireHostUser(\"meet.admit\");\n const meeting = requireMeeting(\"meet.admit\");\n // THE RPC IS THE DECISION, and it returns the row as it now stands — that row (not\n // this click) is what the panel shows. Two hosts deciding at once is the ordinary\n // case: the second sees the first's verdict.\n const row = await repository.decideAdmission({\n meetingId: meeting.id,\n identity: entry.identity,\n decision: admitted ? \"admit\" : \"deny\",\n byUserId: by,\n });\n store.upsertLobby({\n ...entry,\n state: decidedState(row.admissionState),\n decidedAt: new Date().toISOString(),\n decidedBy: by,\n });\n hint();\n },\n\n async undeny(entry) {\n const by = requireHostUser(\"meet.undeny\");\n const meeting = requireMeeting(\"meet.undeny\");\n const row = await repository.decideAdmission({\n meetingId: meeting.id,\n identity: entry.identity,\n decision: \"undeny\",\n byUserId: by,\n });\n store.upsertLobby({ ...entry, state: decidedState(row.admissionState), decidedAt: new Date().toISOString(), decidedBy: by });\n hint();\n },\n\n async admitAll() {\n const by = requireHostUser(\"meet.admitAll\");\n const meeting = requireMeeting(\"meet.admitAll\");\n const rows = await repository.admitAll(meeting.id, by);\n for (const row of rows) {\n store.setAdmission(row.identity, row.admissionState, by);\n }\n hint();\n },\n\n startWaiting() {\n if (polling) return;\n polling = true;\n admittedOnce = false;\n listen();\n schedule(0);\n },\n\n stopWaiting,\n readState,\n\n async readExit(connection) {\n const meeting = store.snapshot().meeting;\n const pass = options.resolvePass?.() ?? null;\n if (meeting === null || pass === null) return undefined;\n try {\n const response = await options.api.request({\n path: `${MEET_ROUTES.state}?meeting_id=${encodeURIComponent(meeting.id)}&connection=${encodeURIComponent(connection)}`,\n method: \"GET\",\n requireAuth: false,\n operation: \"meet.state(exit)\",\n headers: { [MEETING_PASS_HEADER]: pass },\n });\n if (!response.ok) return null;\n const read = (await response.json()) as StateRead;\n return {\n exitReason: read.exit_reason ?? null,\n sessionPhase: read.session_phase ?? null,\n meetingPhase: read.meeting_phase ?? null,\n };\n } catch {\n return null;\n }\n },\n\n async withdraw() {\n stopWaiting();\n await postLeave(\"withdraw\");\n patchWaiting({ admission: \"withdrawn\" });\n store.dispatch({ type: \"admission\", admission: \"withdrawn\" });\n hint();\n },\n\n async askAgain() {\n // A new mint IS the new knock (the server upserts the person row per the re-knock rule).\n await options.rejoin?.();\n },\n\n async setLock(locked) {\n const by = requireHostUser(\"meet.setLock\");\n const meeting = requireMeeting(\"meet.setLock\");\n const updated = await repository.setLock(meeting.id, locked, by);\n store.patch({ meeting: updated, locked: updated.locked });\n // The lock is the meeting row (members see it through Postgres Changes); waiting people\n // are told to read again.\n hint();\n return updated;\n },\n\n /**\n * END IT FOR EVERYONE — a SERVER decision, not a row this client updates.\n *\n * 0.2.1 called the `meet_end_meeting` RPC directly, which ended the meeting\n * in the database while LiveKit kept the room open and the note-taker kept\n * writing into an ended meeting. `POST /v1/meet/end` re-checks the host,\n * closes the room, and triggers the wrap-up — one decision, one place\n * (MRI-A6).\n *\n * 🚨 A SERVER THAT HAS NOT LANDED THAT ROUTE ANSWERS 404, and that is\n * reported as a sentence, never as a dead control and never as a success.\n */\n async endForEveryone() {\n requireHostUser(\"meet.endForEveryone\");\n const meeting = requireMeeting(\"meet.endForEveryone\");\n const response = await options.api.request({\n path: MEET_ROUTES.meetingEnd,\n method: \"POST\",\n requireAuth: true,\n operation: \"meet.endForEveryone\",\n body: {\n meeting_id: meeting.id,\n organization_id: identity.organizationId,\n },\n });\n if (response.status === 404) {\n return { endedAt: null, served: false };\n }\n if (!response.ok) {\n let body: unknown = null;\n try {\n body = await response.json();\n } catch {\n body = null;\n }\n const refusal = readServerRefusal(body);\n throw new MeetError(\n response.status === 403 ? \"forbidden\" : \"transport\",\n refusal?.reason ?? `meet.endForEveryone: the server answered ${response.status}`,\n refusal?.remedy !== undefined && refusal.remedy.length > 0\n ? refusal.remedy\n : \"The meeting is still running. Only a host may end it, and the server \" +\n \"re-checks that — being a host on this screen is not the decision.\",\n );\n }\n let payload: unknown = null;\n try {\n payload = await response.json();\n } catch {\n payload = null;\n }\n const row =\n typeof payload === \"object\" && payload !== null\n ? (payload as Record<string, unknown>)\n : {};\n const endedAt =\n typeof row[\"ended_at\"] === \"string\" ? row[\"ended_at\"] : new Date().toISOString();\n const current = store.snapshot().meeting;\n if (current !== null) store.patch({ meeting: { ...current, endedAt } });\n return { endedAt, served: true };\n },\n\n dispose() {\n stopWaiting();\n channel?.close();\n channel = null;\n meetingChannel?.close();\n meetingChannel = null;\n },\n };\n}\n\n/**\n * A durable public slug. Lowercase, hyphenated, and deliberately readable —\n * people read these aloud on phone calls. The alphabet excludes the four\n * characters that get misheard (0/O, 1/l).\n */\nconst SLUG_ALPHABET = \"abcdefghijkmnpqrstuvwxyz23456789\";\n\nexport function meetingSlug(random: () => number = Math.random): string {\n const group = (length: number): string =>\n Array.from({ length }, () => {\n const index = Math.floor(random() * SLUG_ALPHABET.length);\n return SLUG_ALPHABET[index] ?? \"a\";\n }).join(\"\");\n return `${group(3)}-${group(4)}-${group(3)}`;\n}\n\n/** The link a host copies. ONE shape, so every surface produces the same URL. */\nexport function meetingLink(baseUrl: string, slug: string): string {\n return `${baseUrl.replace(/\\/+$/, \"\")}/meet/${slug}`;\n}\n","/**\n * THE NOTE-TAKER, AS A SURFACE MUST RENDER IT.\n *\n * 🚨 **THE PACKAGE NEVER GUESSES WHAT THE AGENT IS DOING.** Two facts decide\n * everything here and both come from outside this client: the meeting row's\n * `metadata.note_taker` (written by the server with a CAS, MRI-A2/D9) and\n * whether the agent participant is actually in the LiveKit room. A spinner —\n * \"starting…\" held open by a client-side timer — is the exact lie this file\n * exists to prevent: it survives a worker that never came, a dispatch nobody\n * claimed, and a job that died on a release.\n *\n * 🚨 **A REFUSAL IS RENDERED VERBATIM.** `/agent/start` answers a 503 whose body\n * carries `reason` and `remedy` written by the readiness gate (D7) — \"no worker\n * is registered under matrx-note-taker\", \"every worker is at capacity\",\n * \"the fleet is draining\". Those sentences are the truth about the system and\n * they reach the screen unedited. This package adds no sentence of its own on\n * top of one the server already wrote.\n *\n * The agent's identity is DETERMINISTIC — `agent:note_taker:<meeting_id>` (D9) —\n * which is what makes \"is it in the room\" answerable without a round trip, and\n * what makes a second note-taker impossible to mistake for the first.\n */\n\nimport type { JsonObject, MeetParticipant, MeetingRecord, NoteTakerState } from \"./types\";\n\n/** The one spelling of the agent's participant identity (D9). */\nexport const NOTE_TAKER_IDENTITY_PREFIX = \"agent:note_taker:\";\n\nexport function noteTakerIdentity(meetingId: string): string {\n return `${NOTE_TAKER_IDENTITY_PREFIX}${meetingId}`;\n}\n\n/**\n * The participant attribute the note-taker sets on itself so EVERY client —\n * including one that cannot read the token metadata — knows a notice is owed\n * (D10).\n */\nexport const CONSENT_NOTICE_ATTRIBUTE = \"matrx.agent.consent_notice\";\n\n/** Where the state lives on the meeting row. One spelling, one file. */\nexport const NOTE_TAKER_METADATA_KEY = \"note_taker\";\n\nconst STATES: readonly NoteTakerState[] = [\n \"absent\",\n \"joining\",\n \"transcribing\",\n \"handoff_pending\",\n \"ended\",\n];\n\n/** The server's refusal, as it wrote it. Never paraphrased. */\nexport interface NoteTakerRefusal {\n readonly reason: string;\n readonly remedy: string;\n readonly status: number;\n}\n\nexport interface NoteTakerStatus {\n readonly state: NoteTakerState;\n /** The server's own words for why it is in this state, when it gave any. */\n readonly detail: string | null;\n /** True when the agent participant is in the room RIGHT NOW. */\n readonly present: boolean;\n /** The last dispatch refusal, rendered verbatim. */\n readonly refusal: NoteTakerRefusal | null;\n}\n\nfunction readState(value: unknown): NoteTakerState | null {\n return typeof value === \"string\" && STATES.includes(value as NoteTakerState)\n ? (value as NoteTakerState)\n : null;\n}\n\n/** Is this participant the note-taker (or any agent that owes a notice, D10)? */\nexport function isNoteTaker(participant: MeetParticipant): boolean {\n return (\n participant.identity.startsWith(NOTE_TAKER_IDENTITY_PREFIX) ||\n participant.attributes[CONSENT_NOTICE_ATTRIBUTE] !== undefined ||\n participant.isAgent\n );\n}\n\n/** The agent participant in the room, if any. */\nexport function noteTakerParticipant(\n participants: readonly MeetParticipant[],\n): MeetParticipant | null {\n return participants.find(isNoteTaker) ?? null;\n}\n\n/** The exact deterministic actor whose room presence can corroborate the row. */\nfunction exactNoteTakerParticipant(\n meeting: MeetingRecord | null,\n participants: readonly MeetParticipant[],\n): MeetParticipant | null {\n if (meeting === null) return null;\n const identity = noteTakerIdentity(meeting.id);\n return participants.find((participant) => participant.identity === identity) ?? null;\n}\n\n/**\n * THE STATE, from the two facts that decide it.\n *\n * The durable row is the authority for work. Exact room presence corroborates\n * only a durable `transcribing` state; an arbitrary agent, a consent marker,\n * or a stale note-taker participant never proves speech capture.\n */\nexport function noteTakerStatus(args: {\n meeting: MeetingRecord | null;\n participants: readonly MeetParticipant[];\n refusal?: NoteTakerRefusal | null;\n}): NoteTakerStatus {\n const present = exactNoteTakerParticipant(args.meeting, args.participants) !== null;\n const raw = args.meeting?.metadata[NOTE_TAKER_METADATA_KEY];\n const row: JsonObject =\n typeof raw === \"object\" && raw !== null && !Array.isArray(raw) ? raw : {};\n const recorded = readState(row[\"state\"]);\n const detail = typeof row[\"reason\"] === \"string\" && row[\"reason\"].length > 0\n ? row[\"reason\"]\n : null;\n const refusal = args.refusal ?? null;\n\n if (recorded === null) {\n return { state: \"absent\", detail, present, refusal };\n }\n if (recorded === \"transcribing\" && !present) {\n return { state: \"handoff_pending\", detail, present, refusal };\n }\n return { state: recorded, detail, present, refusal };\n}\n\n/**\n * THE SENTENCE FOR EACH STATE. One place, so the room, the panel and any future\n * surface say the same true thing — and so a state can never render as a bare\n * enum name.\n */\nexport function noteTakerLabel(status: NoteTakerStatus): string {\n switch (status.state) {\n case \"joining\":\n return \"The note-taker is joining this meeting\";\n case \"transcribing\":\n return \"The note-taker is connected and listening for speech\";\n case \"handoff_pending\":\n return \"The note-taker's transcription is interrupted; reconnection is pending\";\n case \"ended\":\n return \"The note-taker has stopped\";\n case \"absent\":\n default:\n return status.present\n ? \"A meeting assistant is connected. Transcription is not confirmed\"\n : \"No note-taker is in this meeting\";\n }\n}\n","/**\n * THE ONE PLACE `livekit-client` IS TOUCHED.\n *\n * Everything above this file talks to `MeetRoomDriver`. That containment is\n * what lets the engine's whole behavior — ring timeouts, admission, escalation,\n * reconnection — be proven in a node test, and it is why an SDK upgrade is a\n * single-file review rather than a package-wide audit.\n *\n * 🚨 **THE IMPORT IS DYNAMIC, DELIBERATELY.** `import type` is erased, and the\n * runtime `await import(\"livekit-client\")` happens inside `connect()`. So:\n * importing `@ai-matrx/meet` during SSR, or on a page that renders a call\n * BUTTON but never a call, pulls in none of the media SDK and touches no\n * browser API (independence contract §4). A static import here would put ~400KB\n * of WebRTC into every consumer's first load and break RSC rendering.\n *\n * The mapping helpers below are exported for their own tests: an SDK enum\n * renamed upstream must fail a named test, not silently become \"unknown\" on\n * every tile.\n */\n\nimport type {\n ConnectionQuality as LiveKitQuality,\n Participant,\n Room,\n RoomOptions,\n} from \"livekit-client\";\nimport { MeetError, normalizeDeviceError, normalizeScreenShareError } from \"./errors\";\nimport {\n TRANSCRIPTION_ATTRIBUTES,\n TRANSCRIPTION_TOPIC,\n type AudioProcessing,\n type DriverConnectionState,\n type DriverParticipant,\n type DriverTrackKind,\n type MeetRoomDriver,\n type RoomDriverHandlers,\n type VideoEffect,\n} from \"./room-driver\";\nimport type { ConnectionQuality } from \"./types\";\n\n/**\n * LiveKit's quality enum → ours. Mapped explicitly rather than cast: the SDK\n * has five states and our UI has five sentences, and an unmapped one must\n * degrade to `unknown` (an honest \"we don't know\") rather than to `excellent`\n * (a lie that hides a failing call).\n */\nexport function mapQuality(quality: LiveKitQuality | string): ConnectionQuality {\n switch (quality) {\n case \"excellent\":\n return \"excellent\";\n case \"good\":\n return \"good\";\n case \"poor\":\n return \"poor\";\n case \"lost\":\n return \"lost\";\n default:\n return \"unknown\";\n }\n}\n\nexport function mapConnectionState(state: string): DriverConnectionState {\n switch (state) {\n case \"connected\":\n return \"connected\";\n case \"connecting\":\n return \"connecting\";\n case \"reconnecting\":\n case \"signalReconnecting\":\n return \"reconnecting\";\n default:\n return \"disconnected\";\n }\n}\n\n/**\n * A LiveKit participant → our snapshot.\n *\n * 🚨 `name` IS REPORTED EXACTLY AS LIVEKIT GAVE IT, empty string and all\n * (MRI-A18). Through 0.4.12 it fell back to the identity here, which meant a\n * driver — the thing furthest from any screen — invented a \"name\" that\n * `core/names.ts` then had to recognize and refuse. A driver reports; it does\n * not name. The one fallback lives in `projectParticipant`, and it is the name\n * book's honest placeholder rather than a token.\n */\nexport function snapshotParticipant(\n participant: Pick<\n Participant,\n \"identity\" | \"name\" | \"metadata\" | \"isMicrophoneEnabled\" | \"isCameraEnabled\" | \"isScreenShareEnabled\"\n > & { attributes?: Readonly<Record<string, string>> | undefined },\n isLocal: boolean,\n): DriverParticipant {\n const name = participant.name;\n return {\n identity: participant.identity,\n name: name ?? \"\",\n metadata: participant.metadata ?? null,\n // ATTRIBUTES ARE PART OF THE SNAPSHOT, not read ad hoc later: the note-taker\n // announces the consent notice here (D10) and the roster is the only place\n // this package looks at a participant.\n attributes: participant.attributes ?? {},\n isLocal,\n micEnabled: participant.isMicrophoneEnabled,\n cameraEnabled: participant.isCameraEnabled,\n screenSharing: participant.isScreenShareEnabled,\n };\n}\n\nexport interface LiveKitDriverOptions {\n /** Adaptive stream + dynacast default ON — R5 scale honesty, not a knob. */\n roomOptions?: RoomOptions | undefined;\n}\n\n/** The browser defaults every call product ships with. */\nexport const DEFAULT_AUDIO_PROCESSING: AudioProcessing = {\n noiseSuppression: true,\n echoCancellation: true,\n autoGainControl: true,\n};\n\n/**\n * Background effects run a segmentation model on the GPU (MediaPipe via\n * `@livekit/track-processors`). A browser without `OffscreenCanvas` or WebGL2\n * cannot — the control is then ABSENT, never a toggle that silently fails.\n */\nexport function videoEffectsSupported(): boolean {\n const scope = globalThis as unknown as {\n OffscreenCanvas?: unknown;\n document?: { createElement(tag: string): { getContext(kind: string): unknown } };\n };\n if (typeof scope.OffscreenCanvas === \"undefined\" || scope.document === undefined) return false;\n try {\n return scope.document.createElement(\"canvas\").getContext(\"webgl2\") !== null;\n } catch {\n return false;\n }\n}\n\n/**\n * Put the chosen effect on the local camera. The processor package is imported\n * lazily, exactly like `livekit-client` itself, so a meeting nobody blurs never\n * downloads a segmentation model.\n */\nasync function applyVideoEffect(target: Room, effect: VideoEffect): Promise<void> {\n const { Track } = await import(\"livekit-client\");\n const track = target.localParticipant.getTrackPublication(Track.Source.Camera)?.track as\n | {\n setProcessor(processor: unknown): Promise<void>;\n stopProcessor(): Promise<void>;\n }\n | undefined;\n if (track === undefined) return; // camera off: applied when it turns on.\n try {\n if (effect.kind === \"none\") {\n await track.stopProcessor();\n return;\n }\n const processors = await import(\"@livekit/track-processors\");\n const processor =\n effect.kind === \"blur\"\n ? processors.BackgroundBlur(effect.radius ?? 12)\n : processors.VirtualBackground(effect.imageUrl);\n await track.setProcessor(processor);\n } catch (error) {\n throw new MeetError(\n \"media-unsupported\",\n \"The background effect could not start on this camera\",\n \"Background effects need a browser with WebGL2 (current Chrome, Edge, Firefox or Safari). \" +\n \"Your video continues without the effect.\",\n error,\n );\n }\n}\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\nexport function createLiveKitDriver(\n handlers: RoomDriverHandlers,\n options: LiveKitDriverOptions = {},\n): MeetRoomDriver {\n let room: Room | null = null;\n /**\n * The roster projection for the CURRENT room, kept out here so `connect()`\n * can fire it once the transport is up. See the call site for why.\n */\n let projectRoster: (() => void) | null = null;\n /** What the person chose, kept so a re-enabled track gets it too. */\n let audioCapture: AudioProcessing = DEFAULT_AUDIO_PROCESSING;\n let videoEffect: VideoEffect = { kind: \"none\" };\n\n async function load(): Promise<Room> {\n if (room !== null) return room;\n let module: typeof import(\"livekit-client\");\n try {\n module = await import(\"livekit-client\");\n } catch (error) {\n throw new MeetError(\n \"misconfigured\",\n \"livekit-client could not be loaded\",\n \"`livekit-client` is a dependency of @ai-matrx/meet and is imported lazily at \" +\n \"connect time. If this failed, the bundler excluded it — remove it from any \" +\n \"`external`/`serverExternalPackages` list, or pass your own driver factory.\",\n error,\n );\n }\n const { Room: LiveKitRoom, RoomEvent, Track } = module;\n const created = new LiveKitRoom({\n // R5: large meetings degrade gracefully instead of melting a laptop.\n adaptiveStream: true,\n dynacast: true,\n ...options.roomOptions,\n });\n\n const roster = (): void => {\n const local = snapshotParticipant(created.localParticipant, true);\n const remotes = [...created.remoteParticipants.values()].map((participant) =>\n snapshotParticipant(participant, false),\n );\n // The FULL roster every time. Every one of these six events changes what\n // a tile must render, and a delta protocol across six sources drifts.\n handlers.participants([local, ...remotes]);\n };\n\n projectRoster = roster;\n\n // 🚨 A REMOTE MICROPHONE IS PLAYED HERE, OR NOWHERE. `livekit-client` never\n // plays a remote audio track by itself (`webAudioMix` is off by default): a\n // subscribed track makes sound only once `track.attach()` gives it a media\n // element. Until 0.7.10 nothing attached audio, so every meeting received\n // everyone's audio packets and played none of them (production\n // re-verification, 2026-09-29). Audio is NOT tied to tiles on purpose: the\n // stage paginates and the Board strip overflows, and a person on page two\n // must still be heard.\n created\n .on(RoomEvent.TrackSubscribed, (track) => {\n if (track.kind !== Track.Kind.Audio || typeof document === \"undefined\") return;\n const element = track.attach();\n element.setAttribute(\"data-mx-meet-audio\", \"\");\n audioSink().appendChild(element);\n })\n .on(RoomEvent.TrackUnsubscribed, (track) => {\n if (track.kind !== Track.Kind.Audio) return;\n for (const element of track.detach()) element.remove();\n });\n\n created\n .on(RoomEvent.ParticipantConnected, roster)\n .on(RoomEvent.ParticipantDisconnected, roster)\n .on(RoomEvent.TrackSubscribed, roster)\n .on(RoomEvent.TrackUnsubscribed, roster)\n .on(RoomEvent.TrackMuted, roster)\n .on(RoomEvent.TrackUnmuted, roster)\n .on(RoomEvent.LocalTrackPublished, roster)\n .on(RoomEvent.LocalTrackUnpublished, roster)\n // A SERVER DECISION CHANGED MID-MEETING (Meet wave 2): a co-host made or\n // unmade, the host handed over, a rename. The server re-stamps the\n // roster through `UpdateParticipant`; these three events carry it here.\n .on(RoomEvent.ParticipantNameChanged, roster)\n .on(RoomEvent.ParticipantAttributesChanged, roster)\n .on(RoomEvent.ParticipantMetadataChanged, roster)\n .on(RoomEvent.RoomMetadataChanged, (metadata) => {\n handlers.roomMetadata?.(metadata ?? \"\");\n })\n .on(RoomEvent.ActiveSpeakersChanged, (speakers) => {\n handlers.activeSpeakers(speakers.map((speaker) => speaker.identity));\n })\n .on(RoomEvent.ConnectionQualityChanged, (quality, participant) => {\n handlers.quality(participant.identity, mapQuality(quality));\n })\n .on(RoomEvent.ConnectionStateChanged, (state) => {\n handlers.connectionState(mapConnectionState(state));\n })\n .on(RoomEvent.DataReceived, (payload, participant, _kind, topic) => {\n handlers.data(\n topic ?? \"\",\n decoder.decode(payload),\n participant?.identity ?? null,\n );\n })\n .on(RoomEvent.Disconnected, (reason) => {\n audioContainer?.remove();\n audioContainer = null;\n // §3.4 ER reads LiveKit's reason by NAME (`DUPLICATE_IDENTITY`, `PARTICIPANT_REMOVED`, …):\n // the enum is numeric on the wire, and a number means nothing outside this file.\n const named = reason === undefined\n ? null\n : ((module?.DisconnectReason as unknown as Record<number, string> | undefined)?.[reason as unknown as number] ?? String(reason));\n handlers.disconnected(named);\n });\n\n // LIVE CAPTIONS (D16). The agent publishes one text stream per utterance on\n // `lk.transcription`, re-sending the same `lk.segment_id` as the words firm\n // up, and the FINAL flag says which one is the last. The chunks are read as\n // they arrive — waiting for `readAll()` would make captions appear only\n // once the sentence was over, which is not a caption.\n //\n // 🚨 REGISTERED BEFORE `connect()`, deliberately: LiveKit buffers data\n // packets that arrive before the room is connected and replays them, but a\n // handler registered after the first stream header lands misses that\n // utterance outright — the first thing anyone says in the meeting.\n try {\n created.registerTextStreamHandler(TRANSCRIPTION_TOPIC, (reader, participantInfo) => {\n const attributes: Record<string, string> = { ...(reader.info.attributes ?? {}) };\n const segmentId =\n attributes[TRANSCRIPTION_ATTRIBUTES.segmentId] ?? reader.info.id;\n const trackId = attributes[TRANSCRIPTION_ATTRIBUTES.trackId] ?? null;\n const final = attributes[TRANSCRIPTION_ATTRIBUTES.final] === \"true\";\n // `lk.transcribed_participant_identity` is WHO IS BEING TRANSCRIBED.\n // The SFU's publisher identity stays the note-taker, so using it here\n // would attribute the whole meeting to the agent. Do not use the\n // sender-identity override: LiveKit accepts it from an agent publisher,\n // but aidream deliberately does not set it.\n const speakerIdentity =\n attributes[TRANSCRIPTION_ATTRIBUTES.speakerIdentity] ?? participantInfo.identity;\n const at = new Date(reader.info.timestamp || Date.now()).toISOString();\n void (async () => {\n let text = \"\";\n try {\n for await (const chunk of reader) {\n text += chunk;\n // Emit as it grows: a caption that only appears when the sentence\n // ends is a transcript, not a caption.\n handlers.transcription({\n segmentId,\n speakerIdentity,\n publisherIdentity: participantInfo.identity,\n text,\n final,\n trackId,\n at,\n });\n }\n } catch (error) {\n // A caption stream that dies is a caption we do not have. It is\n // never fatal to the meeting and never silent.\n handlers.data(\n TRANSCRIPTION_TOPIC,\n JSON.stringify({ error: String((error as Error)?.message ?? error) }),\n participantInfo.identity,\n );\n }\n })();\n });\n } catch {\n // Registering twice throws in the SDK. `load()` is single-flight through\n // `room`, so this can only happen on a re-entrant remount — in which case\n // the existing handler is already delivering captions.\n }\n\n // Kept for `attach`, which needs the SDK's Source enum at call time.\n sources = {\n camera: Track.Source.Camera,\n microphone: Track.Source.Microphone,\n screen: Track.Source.ScreenShare,\n };\n trackEvents = {\n subscribed: RoomEvent.TrackSubscribed,\n localPublished: RoomEvent.LocalTrackPublished,\n };\n room = created;\n return created;\n }\n\n let sources: Record<DriverTrackKind, string> | null = null;\n /** The SDK's event names `attach` listens on, captured with `sources`. */\n let trackEvents: { subscribed: string; localPublished: string } | null = null;\n\n /** The hidden container every remote audio element of THIS room lives in. */\n let audioContainer: HTMLElement | null = null;\n function audioSink(): HTMLElement {\n if (audioContainer !== null && audioContainer.isConnected) return audioContainer;\n const container = document.createElement(\"div\");\n container.setAttribute(\"data-mx-meet-audio-sink\", \"\");\n container.setAttribute(\"aria-hidden\", \"true\");\n container.style.display = \"none\";\n document.body.appendChild(container);\n audioContainer = container;\n return container;\n }\n\n function requireRoom(operation: string): Room {\n if (room === null) {\n throw new MeetError(\n \"transport\",\n `${operation}: the room is not connected`,\n \"Call connect() first. The engine never reaches this — it gates every media call \" +\n \"on its own phase — so seeing it means a consumer drove the driver directly.\",\n );\n }\n return room;\n }\n\n return {\n async connect(serverUrl, token) {\n const target = await load();\n try {\n await target.connect(serverUrl, token);\n } catch (error) {\n // A connect failure is either media permission (user-actionable) or\n // transport. Both must reach the UI with a sentence, never a spinner.\n throw normalizeDeviceError(error, \"meet.connect\");\n }\n // 🚨 PROJECT THE ROSTER ONCE, HERE. Every other roster source is an\n // EVENT, and a participant who publishes nothing into an empty room\n // generates none of them: no `LocalTrackPublished`, no\n // `ParticipantConnected`, no track events. Until 0.3.2 the join always\n // published a microphone, so `LocalTrackPublished` fired and hid this —\n // and the moment listen-only attendance became possible, a listen-only\n // host was absent from their own People panel, had no tile, and got NO\n // host controls at all (the note-taker could not be started), because\n // host powers are read off the local participant's roster entry.\n projectRoster?.();\n // The room's metadata arrives WITH the join (the spotlight lives there),\n // and `RoomMetadataChanged` fires only for later changes.\n handlers.roomMetadata?.(target.metadata ?? \"\");\n },\n async disconnect() {\n if (room === null) return;\n const target = room;\n room = null;\n try {\n target.unregisterTextStreamHandler(TRANSCRIPTION_TOPIC);\n } catch {\n // Already gone. Teardown is idempotent by contract.\n }\n projectRoster = null;\n audioContainer?.remove();\n audioContainer = null;\n await target.disconnect();\n },\n async setMicrophoneEnabled(enabled) {\n try {\n await requireRoom(\"setMicrophoneEnabled\").localParticipant.setMicrophoneEnabled(\n enabled,\n audioCapture,\n );\n } catch (error) {\n throw normalizeDeviceError(error, \"meet.setMicrophoneEnabled\");\n }\n },\n async setCameraEnabled(enabled) {\n try {\n const target = requireRoom(\"setCameraEnabled\");\n await target.localParticipant.setCameraEnabled(enabled);\n // A REMEMBERED EFFECT FOLLOWS THE CAMERA. Turning the camera back on must\n // not quietly show the room behind someone who chose to blur it.\n if (enabled && videoEffect.kind !== \"none\") await applyVideoEffect(target, videoEffect);\n } catch (error) {\n throw normalizeDeviceError(error, \"meet.setCameraEnabled\");\n }\n },\n async setScreenShareEnabled(enabled) {\n try {\n await requireRoom(\"setScreenShareEnabled\").localParticipant.setScreenShareEnabled(enabled);\n } catch (error) {\n throw normalizeScreenShareError(error, \"meet.setScreenShareEnabled\");\n }\n },\n async publishData(topic, payload, to) {\n await requireRoom(\"publishData\").localParticipant.publishData(\n // `reliable` is not optional for a control plane: a dropped\n // \"hand lowered\" leaves a hand up on every other screen forever.\n encoder.encode(payload) as Uint8Array<ArrayBuffer>,\n to === undefined || to.length === 0\n ? { reliable: true, topic }\n : { reliable: true, topic, destinationIdentities: [...to] },\n );\n },\n async setAudioProcessing(processing) {\n audioCapture = { ...audioCapture, ...processing };\n const target = room;\n if (target === null) return;\n const { Track } = await import(\"livekit-client\");\n const publication = target.localParticipant.getTrackPublication(Track.Source.Microphone);\n const track = publication?.track;\n if (track === undefined || publication?.isMuted === true) return;\n try {\n // The browser's own processing, applied to the LIVE track: a restart\n // with new constraints, the same device, no renegotiation.\n await (track as unknown as { restartTrack(options: object): Promise<void> }).restartTrack({\n ...audioCapture,\n ...(target.getActiveDevice(\"audioinput\") !== undefined\n ? { deviceId: target.getActiveDevice(\"audioinput\") }\n : {}),\n });\n } catch (error) {\n throw normalizeDeviceError(error, \"meet.setAudioProcessing\");\n }\n },\n async setVideoEffect(effect) {\n videoEffect = effect;\n const target = room;\n if (target === null) return;\n await applyVideoEffect(target, effect);\n },\n supportsVideoEffects() {\n return videoEffectsSupported();\n },\n supportsScreenShare() {\n // iOS Safari and most phones have no `getDisplayMedia` at all.\n return (\n typeof navigator !== \"undefined\" &&\n typeof navigator.mediaDevices?.getDisplayMedia === \"function\"\n );\n },\n attach(identity, kind, element) {\n const target = requireRoom(\"attach\");\n const source = sources?.[kind];\n if (source === undefined) return () => undefined;\n // 🚨 THE TRACK MAY NOT EXIST YET, AND MAY BE REPLACED LATER. A tile\n // renders as soon as the roster says the camera is on — which is known\n // from the PUBLICATION, before the track is SUBSCRIBED — and the tile's\n // element stays mounted, so its ref never runs again. Until 0.7.10 an\n // attach that found no track returned a no-op for good: a person\n // admitted from the waiting room was a black tile for everyone already\n // inside (production re-verification, 2026-09-29). So the attach keeps\n // listening for this participant's track and (re)attaches whenever it\n // arrives or is republished; the returned cleanup stops listening too.\n let current: { detach(element: HTMLMediaElement): unknown } | null = null;\n const sync = (): void => {\n const participant =\n target.localParticipant.identity === identity\n ? target.localParticipant\n : target.remoteParticipants.get(identity);\n const track = participant?.getTrackPublication(source as never)?.track;\n if (track === undefined || track === current) return;\n current?.detach(element);\n track.attach(element);\n current = track;\n };\n const onTrack = (...args: unknown[]): void => {\n // TrackSubscribed(track, publication, participant) /\n // LocalTrackPublished(publication, participant) — the participant is\n // always the last argument.\n const who = args[args.length - 1] as { identity?: string } | undefined;\n if (who?.identity === identity) sync();\n };\n sync();\n const events = trackEvents;\n if (events !== null) {\n target.on(events.subscribed as never, onTrack as never);\n target.on(events.localPublished as never, onTrack as never);\n }\n // The DETACH IS THE RETURN VALUE — a React effect's cleanup cannot forget\n // to call something it was handed.\n return () => {\n if (events !== null) {\n target.off(events.subscribed as never, onTrack as never);\n target.off(events.localPublished as never, onTrack as never);\n }\n current?.detach(element);\n current = null;\n };\n },\n activeDevices() {\n const target = room;\n if (target === null) {\n return { audioinput: null, videoinput: null, audiooutput: null };\n }\n return {\n audioinput: target.getActiveDevice(\"audioinput\") ?? null,\n videoinput: target.getActiveDevice(\"videoinput\") ?? null,\n audiooutput: target.getActiveDevice(\"audiooutput\") ?? null,\n };\n },\n async switchDevice(kind, deviceId) {\n try {\n await requireRoom(\"switchDevice\").switchActiveDevice(kind, deviceId);\n } catch (error) {\n throw normalizeDeviceError(error, \"meet.switchDevice\");\n }\n },\n };\n}\n\n/**\n * THE PRE-JOIN CAMERA PREVIEW (Meet wave 2) — the same SDK track and the same\n * background processor the room will use, so what someone sees before joining\n * is exactly what the meeting will see. Lives here because this file is the one\n * place `livekit-client` is touched.\n */\nexport interface CameraPreview {\n attach(element: HTMLVideoElement): void;\n setEffect(effect: VideoEffect): Promise<void>;\n stop(): void;\n}\n\nexport async function createCameraPreview(\n deviceId: string | null,\n effect: VideoEffect,\n): Promise<CameraPreview> {\n let module: typeof import(\"livekit-client\");\n try {\n module = await import(\"livekit-client\");\n } catch (error) {\n throw new MeetError(\n \"misconfigured\",\n \"livekit-client could not be loaded for the camera preview\",\n \"The bundler excluded `livekit-client`; remove it from any `external` list.\",\n error,\n );\n }\n let track: Awaited<ReturnType<typeof module.createLocalVideoTrack>>;\n try {\n track = await module.createLocalVideoTrack(deviceId === null ? {} : { deviceId });\n } catch (error) {\n throw normalizeDeviceError(error, \"meet.cameraPreview\");\n }\n const target = track as unknown as {\n setProcessor(processor: unknown): Promise<void>;\n stopProcessor(): Promise<void>;\n };\n async function apply(next: VideoEffect): Promise<void> {\n if (next.kind === \"none\") {\n await target.stopProcessor();\n return;\n }\n const processors = await import(\"@livekit/track-processors\");\n await target.setProcessor(\n next.kind === \"blur\"\n ? processors.BackgroundBlur(next.radius ?? 12)\n : processors.VirtualBackground(next.imageUrl),\n );\n }\n if (effect.kind !== \"none\" && videoEffectsSupported()) {\n try {\n await apply(effect);\n } catch {\n // The preview still shows the camera; the settings say the effect failed.\n }\n }\n return {\n attach(element) {\n track.attach(element);\n },\n setEffect: apply,\n stop() {\n track.stop();\n track.detach();\n },\n };\n}\n","/**\n * PRESENTATION HELPERS specific to a CALL. Pure, tested, and shared by every\n * surface — so a roster line reads the same in the meeting, in the history\n * list, and in the notification.\n *\n * The GENERIC formatters this file used to define — `formatDuration`,\n * `getInitials`, `avatarPaletteIndex` — moved to `@ai-matrx/kit/format` on\n * 2026-09-07 (duplication census H1). They were byte-for-byte duplicated in\n * `@ai-matrx/messaging`, and because two packages claimed them, ~65 host\n * twins across six repos had no correct owner to point at: no surface should\n * take a dependency on the video-calling package to print a duration. Nothing\n * is re-exported from here — import `@ai-matrx/kit/format` directly.\n */\n\nimport type { CallState, MeetParticipant } from \"./types\";\n\n/** \"Ana, Ben and 3 others\" — the roster line, never a raw array join. */\nexport function participantSummary(participants: readonly MeetParticipant[]): string {\n const names = participants.filter((p) => !p.isLocal).map((p) => p.displayName);\n if (names.length === 0) return \"Just you\";\n if (names.length === 1) return names[0] ?? \"\";\n if (names.length === 2) return `${names[0]} and ${names[1]}`;\n return `${names[0]}, ${names[1]} and ${names.length - 2} ${names.length - 2 === 1 ? \"other\" : \"others\"}`;\n}\n\n/** What a settled call says in history. One sentence per terminal state. */\nexport function callStateLabel(state: CallState, outgoing: boolean): string {\n switch (state) {\n case \"dialing\":\n case \"ringing\":\n return outgoing ? \"Calling…\" : \"Incoming call\";\n case \"connecting\":\n return \"Connecting…\";\n case \"active\":\n return \"In call\";\n case \"declined\":\n return outgoing ? \"Declined\" : \"You declined\";\n case \"missed\":\n return outgoing ? \"No answer\" : \"Missed call\";\n case \"busy\":\n return outgoing ? \"On another call\" : \"You were busy\";\n case \"cancelled\":\n return outgoing ? \"Cancelled\" : \"Cancelled by caller\";\n case \"ended\":\n return \"Call ended\";\n case \"failed\":\n return \"Call failed\";\n default:\n return \"Call\";\n }\n}\n\n/** \"2 waiting\" for the lobby badge — plural handled once, not per surface. */\nexport function countLabel(count: number, singular: string, plural = `${singular}s`): string {\n return `${count} ${count === 1 ? singular : plural}`;\n}\n","/**\n * THE STAGE — who is big, who is small, and who is not rendered at all.\n *\n * This is pure arithmetic on purpose: layout is the part of a meeting UI that\n * everyone re-derives inside a component and gets subtly wrong, and it is\n * trivially testable once it stops living in JSX.\n *\n * R5 SCALE HONESTY lives here. A 200-person meeting does not render 200 video\n * tiles — every browser dies, and LiveKit's adaptive stream cannot save a\n * layout that asked for 200 subscriptions. The grid PAGES, and the ordering is\n * deliberate rather than \"whatever the roster map iterated\":\n *\n * 1. a screen share (or an explicit pin) owns the stage;\n * 2. then the active speakers, most recent first;\n * 3. then anyone with a hand raised — they are asking to be seen;\n * 4. then cameras on before cameras off (an avatar tile is cheap);\n * 5. then stable by join time, so tiles do not shuffle every time someone\n * breathes. Shuffling tiles is the single most disorienting thing a\n * meeting UI can do.\n */\n\nimport { isObserverRole, type MeetParticipant, type ParticipantIdentity, type StageLayout } from \"./types\";\n\nexport interface StageArgs {\n readonly participants: readonly MeetParticipant[];\n readonly layout: StageLayout;\n readonly pinned: ParticipantIdentity | null;\n readonly activeSpeakers: readonly ParticipantIdentity[];\n /**\n * The participant a HOST spotlighted for everyone (Meet wave 2). A local pin\n * outranks it on this screen; a screen share outranks both.\n */\n readonly spotlight?: ParticipantIdentity | null | undefined;\n /** How many tiles this viewport can honestly render. Default 12. */\n readonly pageSize?: number;\n readonly page?: number;\n}\n\nexport interface Stage {\n /** The large tile, when the layout has one. */\n readonly feature: MeetParticipant | null;\n /** What actually renders as a tile, already paged. */\n readonly tiles: readonly MeetParticipant[];\n /** Everyone not on this page. Rendered as \"+N others\", never dropped silently. */\n readonly overflow: number;\n readonly page: number;\n readonly pageCount: number;\n /** True when a screen share, not a person, is the reason for the big tile. */\n readonly presenting: boolean;\n}\n\nexport const DEFAULT_PAGE_SIZE = 12;\n\nexport function orderParticipants(\n participants: readonly MeetParticipant[],\n activeSpeakers: readonly ParticipantIdentity[],\n): readonly MeetParticipant[] {\n const speakerRank = new Map<ParticipantIdentity, number>();\n activeSpeakers.forEach((identity, index) => speakerRank.set(identity, index));\n\n return [...participants].sort((a, b) => {\n const speakerA = speakerRank.get(a.identity) ?? Number.MAX_SAFE_INTEGER;\n const speakerB = speakerRank.get(b.identity) ?? Number.MAX_SAFE_INTEGER;\n if (speakerA !== speakerB) return speakerA - speakerB;\n if (a.handRaised !== b.handRaised) return a.handRaised ? -1 : 1;\n if (a.cameraEnabled !== b.cameraEnabled) return a.cameraEnabled ? -1 : 1;\n // STABLE from here down. Join time, then identity — never Math.random,\n // never map order, both of which reshuffle the grid on every event.\n if (a.joinedAt !== b.joinedAt) return a.joinedAt < b.joinedAt ? -1 : 1;\n return a.identity < b.identity ? -1 : 1;\n });\n}\n\nexport function computeStage(args: StageArgs): Stage {\n const pageSize = Math.max(1, args.pageSize ?? DEFAULT_PAGE_SIZE);\n // A SILENT OBSERVER (MD-16) watches; it is never a tile — not on anyone's stage, its own included.\n const ordered = orderParticipants(\n args.participants.filter((participant) => !isObserverRole(participant.role)),\n args.activeSpeakers,\n );\n\n const sharer = ordered.find((participant) => participant.screenSharing) ?? null;\n const pinnedParticipant =\n args.pinned === null\n ? null\n : ordered.find((participant) => participant.identity === args.pinned) ?? null;\n\n // A SCREEN SHARE OUTRANKS A PIN, deliberately: someone presenting is the\n // reason everyone is looking, and a stale pin hiding a live demo is the\n // complaint this rule exists to prevent.\n const spotlit =\n args.spotlight == null\n ? null\n : ordered.find((participant) => participant.identity === args.spotlight) ?? null;\n\n const feature =\n sharer !== null\n ? sharer\n : pinnedParticipant !== null && args.layout !== \"grid\"\n ? pinnedParticipant\n : spotlit !== null\n ? spotlit\n : args.layout === \"grid\"\n ? null\n : ordered[0] ?? null;\n\n const rest =\n feature === null\n ? ordered\n : ordered.filter((participant) => participant.identity !== feature.identity);\n\n const pageCount = Math.max(1, Math.ceil(rest.length / pageSize));\n const page = Math.min(Math.max(0, args.page ?? 0), pageCount - 1);\n const tiles = rest.slice(page * pageSize, page * pageSize + pageSize);\n\n return {\n feature,\n tiles,\n overflow: rest.length - tiles.length,\n page,\n pageCount,\n presenting: sharer !== null,\n };\n}\n\n/** Column count for a grid of N tiles — the shape that keeps tiles squarest. */\nexport function gridColumns(count: number): number {\n if (count <= 1) return 1;\n if (count <= 4) return 2;\n if (count <= 9) return 3;\n if (count <= 16) return 4;\n return 5;\n}\n","/**\n * THE APP PANEL SLOT, FOR REACT (HR-360 wave 2, Meet register MD-15).\n *\n * The HOST APP registers its panels — `<MeetAppPanels panels={{ \"hr.review_360\": … }}>` around\n * the meeting — and the skin opens the one the meeting carries (`appPanelOf`) beside the call.\n * Nothing app-specific lives in this package: a panel is a key, a title and a component that\n * reads its record through the viewer's own doors.\n *\n * `useSharedFocus` is the panel's one shared \"current section\": the server keeps it per run\n * and tells the room on the existing collab packet, so every seat moves together.\n */\n\nimport { createContext, useContext, useMemo, type ComponentType, type ReactNode } from \"react\";\nimport { appPanelOf, type MeetingAppPanel } from \"../core/app-panel\";\nimport { performCollab } from \"../core/collab-state\";\nimport { isObserverRole, type MeetingRecord } from \"../core/types\";\nimport { useMeetHost, useMeetSnapshot } from \"./provider\";\n\nexport interface MeetAppPanelProps {\n /** The record the meeting carries. */\n readonly recordId: string;\n /** The meeting's organization — the panel's records are read in it. */\n readonly organizationId: string;\n /** The panel's key, e.g. `hr.review_360`. */\n readonly panelKey: string;\n}\n\nexport interface MeetAppPanelEntry {\n /** The panel's header title. */\n readonly title: string;\n readonly Component: ComponentType<MeetAppPanelProps>;\n}\n\nexport type MeetAppPanelRegistry = Readonly<Record<string, MeetAppPanelEntry>>;\n\nconst RegistryContext = createContext<MeetAppPanelRegistry>({});\n\n/** Register the host app's panels for every meeting rendered inside. */\nexport function MeetAppPanels({\n panels,\n children,\n}: {\n panels: MeetAppPanelRegistry;\n children: ReactNode;\n}): ReactNode {\n return <RegistryContext.Provider value={panels}>{children}</RegistryContext.Provider>;\n}\n\nexport function useMeetAppPanelRegistry(): MeetAppPanelRegistry {\n return useContext(RegistryContext);\n}\n\nexport interface ResolvedMeetAppPanel extends MeetAppPanelEntry {\n readonly panel: MeetingAppPanel;\n readonly organizationId: string;\n}\n\n/**\n * The panel this meeting carries AND this app registered, or null. A key the app did not\n * register resolves to null — the slot is absent, never a dead button.\n */\nexport function useMeetAppPanel(meeting?: MeetingRecord | null): ResolvedMeetAppPanel | null {\n const snapshot = useMeetSnapshot();\n const registry = useMeetAppPanelRegistry();\n const record = meeting ?? snapshot?.meeting ?? null;\n return useMemo(() => {\n const panel = appPanelOf(record);\n if (panel === null || record === null) return null;\n const entry = registry[panel.key];\n if (entry === undefined) return null;\n return { ...entry, panel, organizationId: record.organizationId };\n }, [record, registry]);\n}\n\nexport interface SharedFocus {\n /** The section everyone is on, or null before anyone moved it. */\n readonly section: string | null;\n /** Who moved it last. */\n readonly byName: string | null;\n /** False for an observer — they watch, the people in the meeting move the panel. */\n readonly canMove: boolean;\n /** Move everyone to `section`. Resolves with the server's sentence or its refusal. */\n setSection(section: string): Promise<{ ok: boolean; message: string }>;\n}\n\n/** The app panel's one shared section focus (server-kept, on the collab packet). */\nexport function useSharedFocus(panelKey: string): SharedFocus {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const focus = snapshot?.collab.focus ?? null;\n const mine = focus !== null && focus.panel === panelKey ? focus : null;\n const canMove = !isObserverRole(snapshot?.grants.role) && snapshot?.phase === \"in_call\";\n return {\n section: mine?.section || null,\n byName: mine?.byName || null,\n canMove,\n async setSection(section) {\n if (host !== null && canMove) {\n // Move this seat at once; the room packet confirms it for everyone.\n const collab = host.store.snapshot().collab;\n host.store.patch({\n collab: { ...collab, focus: { panel: panelKey, section, byName: host.identity.displayName, at: new Date().toISOString() } },\n });\n }\n return performCollab(host, { action: \"focus_set\", panel: panelKey, section });\n },\n };\n}\n","/**\n * COLLAB AND CAPTION LOGIC, HEADLESS (CORE-DESIGN §5.3, step S1).\n *\n * What used to live inside the React hooks of the in-room activities: which\n * activities a person has, when the Activities door deserves a dot, the act /\n * move-to-breakout calls with their sentences, the caption-translation work\n * queue, and the one-shot panel and language requests on the store. Pure or\n * store-shaped, so every skin reads the same answers; the `/react` hooks are\n * thin selectors over these.\n */\n\nimport type { CollabPanel, CollabSnapshot } from \"./collab\";\nimport type { CollabRequest, CollabResult } from \"./in-room\";\nimport type { MeetSnapshot } from \"./store\";\n\n/** The activities this person has, in the order Meet lists them. */\nexport function availableActivities(collab: CollabSnapshot | null, isHost: boolean): CollabPanel[] {\n const settings = collab?.settings ?? null;\n if (settings === null) return [];\n const out: CollabPanel[] = [];\n if (settings.whiteboardEnabled) out.push(\"whiteboard\");\n // A participant sees Polls once a poll exists for them; a host always (to create one).\n if (settings.pollsEnabled && (isHost || (collab?.polls.length ?? 0) > 0)) out.push(\"polls\");\n if (settings.qaEnabled) out.push(\"qa\");\n // Breakouts: hosts run them; a participant needs the panel only when rooms are open.\n if (settings.breakoutsEnabled && (isHost || collab?.breakout?.state === \"open\")) out.push(\"breakouts\");\n return out;\n}\n\n/** Something worth a dot on the Activities button: a live poll unanswered, a new question for a host. */\nexport function activityAttention(collab: CollabSnapshot | null, isHost: boolean): boolean {\n if (collab === null) return false;\n if (collab.polls.some((poll) => poll.state === \"live\" && poll.myVote.length === 0 && !isHost)) return true;\n if (isHost && collab.questions.some((q) => q.state === \"open\")) return true;\n return false;\n}\n\nexport interface CollabRuntime {\n readonly inRoom: { collab(request: CollabRequest): Promise<CollabResult> };\n readonly engine: { moveToBreakout(roomId: string | null): Promise<unknown> };\n}\n\n/** Perform one act; resolves with the server's sentence, or its refusal (never throws). */\nexport async function performCollab(\n runtime: CollabRuntime | null,\n request: CollabRequest,\n): Promise<{ ok: boolean; message: string; itemId?: string | null }> {\n if (runtime === null) return { ok: false, message: \"This meeting is not connected.\" };\n try {\n const result = await runtime.inRoom.collab(request);\n return { ok: true, message: result.message, itemId: result.itemId };\n } catch (error) {\n return { ok: false, message: (error as { message?: string }).message ?? \"That did not work.\" };\n }\n}\n\n/** Go to a breakout room (`null` = the main room), with the sentence to show. */\nexport async function moveToRoom(\n runtime: CollabRuntime | null,\n roomId: string | null,\n): Promise<{ ok: boolean; message: string }> {\n if (runtime === null) return { ok: false, message: \"This meeting is not connected.\" };\n try {\n await runtime.engine.moveToBreakout(roomId);\n return { ok: true, message: roomId === null ? \"Back in the main room.\" : \"Joined the breakout room.\" };\n } catch (error) {\n return { ok: false, message: (error as { message?: string }).message ?? \"That room could not be joined.\" };\n }\n}\n\nexport interface CollabStoreLike {\n snapshot(): MeetSnapshot;\n patch(next: Partial<MeetSnapshot>): void;\n}\n\n/** Finished caption lines that still need a translation into `language`. */\nexport function untranslatedLines(\n lines: readonly { id: string; text: string; final: boolean }[],\n translations: Readonly<Record<string, string>>,\n): { id: string; text: string }[] {\n return lines\n .filter((line) => line.final && translations[line.id] === undefined)\n .map(({ id, text }) => ({ id, text }));\n}\n\n/** The next batch to send: never a line already asked for in this language, at most 20. */\nexport function nextTranslationBatch(\n pending: readonly { id: string; text: string }[],\n language: string,\n asked: Set<string>,\n): { id: string; text: string }[] {\n const batch = pending.filter((line) => !asked.has(`${language}:${line.id}`)).slice(-20);\n for (const line of batch) asked.add(`${language}:${line.id}`);\n return batch;\n}\n\n/** The viewer chose a caption language (or `null` = as spoken): start over in it. */\nexport function setCaptionLanguage(store: CollabStoreLike, next: string | null): void {\n store.patch({\n collab: {\n ...store.snapshot().collab,\n captionLanguage: next,\n captionTranslations: {},\n captionTranslationError: null,\n },\n });\n}\n\n/** Take (and clear) the one-shot \"open this panel\" request, if any. */\nexport function takePanelRequest(store: CollabStoreLike): CollabPanel | null {\n const request = store.snapshot().collab.panelRequest;\n if (request === null) return null;\n store.patch({ collab: { ...store.snapshot().collab, panelRequest: null } });\n return request.panel;\n}\n\n/** Take (and clear) the one-shot \"move to this breakout room\" request, if any. */\nexport function takeMoveRequest(store: CollabStoreLike): { roomId: string | null } | null {\n const request = store.snapshot().collab.moveRequest;\n if (request === null) return null;\n store.patch({ collab: { ...store.snapshot().collab, moveRequest: null } });\n return { roomId: request.roomId };\n}\n","\"use client\";\n\n/**\n * THE HOOKS. Thin shells over the framework-free core — every one of them\n * exists so a consumer never writes the sequence itself.\n *\n * The rule that shapes this file: a hook returns DATA and ACTIONS, never a\n * \"you must also call this on unmount\" instruction. `useTrack` hands back a ref\n * callback whose cleanup is the detach; `useRoom` disposes with the component.\n * A correct-usage checklist in a README is a bug waiting for a deadline.\n *\n * 🚨 **NO HOOK IN THIS FILE THROWS WHEN THE PROVIDER IS INERT** (0.2.1).\n * `<MeetProvider>` is deliberately inert until a client, an organization and an\n * identity are all real — which is EVERY server render, every signed-out\n * visitor, and every moment before the active organization hydrates. Through\n * 0.2.0 these hooks called `useRequiredMeetHost()`, so the exact three-line\n * integration the README prescribes returned a 500 on every route for every\n * such visitor, and the first real consumer had to wrap our own components in\n * a host-side guard (register MRI-D1). A hook now reports an inert runtime as\n * DATA — empty lists, `canCall: false`, `available: false` — so the components\n * above render nothing or an honest sentence on their own. Only an ACTION\n * invoked with no runtime fails, and it fails loudly with a remedy; it can not\n * be reached from a surface that renders correctly.\n */\n\nimport type { MeetPhase } from \"../core/phase\";\nimport { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport { useRealtimeManager } from \"@ai-matrx/realtime/react\";\nimport { meetingTopic } from \"../core/channels\";\nimport {\n createMeetingRecordCache,\n type MeetingRecordAnswer,\n type MeetingRecordCache,\n} from \"../core/record-cache\";\nimport type { MeetAnswerCoverage } from \"../core/ai\";\nimport { IDLE_MEDIA, listenOnlyNotice, type LocalMediaState } from \"../core/attendance\";\nimport { MeetError } from \"../core/errors\";\nimport {\n noteTakerLabel,\n noteTakerParticipant,\n noteTakerStatus,\n type NoteTakerStatus,\n} from \"../core/note-taker\";\nimport { displayNameFor, type NameBook } from \"../core/names\";\nimport { readableTranscript } from \"../core/transcript\";\n\n/** A stable empty book, so `useMemo` does not re-run on every render. */\nconst EMPTY_NAMES: NameBook = {};\nimport { END_MEETING_NOT_SERVED } from \"../core/meetings\";\nimport { computeStage, DEFAULT_PAGE_SIZE, type Stage } from \"../core/layout\";\nimport { canModerateParticipant } from \"../core/meetings\";\nimport { isRecordingInFlight, recordingLabel } from \"../core/recording\";\nimport type { MeetingSession } from \"../core/meetings\";\nimport type { JoinRequest, RoomEngine } from \"../core/room\";\nimport type { ModerationRequest, ModerationResult } from \"../core/in-room\";\nimport {\n DEFAULT_EFFECTS,\n REACTION_TTL_MS,\n type MediaEffects,\n type MeetWaiting,\n type ModerationNotice,\n} from \"../core/store\";\nimport { isHostRole } from \"../core/types\";\nimport {\n guestRecordAccessSetting,\n activeWrapUpSummary,\n loadGuestMeetingRecord,\n loadMeetingRecordParts,\n setGuestRecordAccess,\n type GuestRecordAccessResolution,\n type GuestRecordAccessSetting,\n type MeetingRecordBundle,\n} from \"../core/record\";\nimport type {\n CallInvite,\n CallMode,\n LobbyEntry,\n MeetParticipant,\n MeetingId,\n MeetingNote,\n MeetingRecord,\n OrganizationId,\n ParticipantIdentity,\n RoomName,\n StageLayout,\n TranscriptSource,\n UserId,\n WrapUpAnnouncement,\n} from \"../core/types\";\nimport {\n useCanRecord,\n useIsHost,\n useRole,\n useMeetHost,\n useMeetSnapshot,\n type MeetHost,\n} from \"./provider\";\n\n/**\n * The ONE sentence every hook says when an action is invoked with no runtime.\n * It names the state and the fix; it never returns a silent no-op, because a\n * button that quietly does nothing is the failure this package exists to avoid.\n */\nconst NO_RUNTIME_REMEDY =\n \"Render this inside a <MeetProvider> that has a Supabase client, an organization id and \" +\n \"an identity (userId or guestName). The provider stays inert until all three are real, \" +\n \"and while it is inert this package's components render nothing rather than a dead control.\";\n\nfunction noRuntime(action: string): MeetError {\n return new MeetError(\n \"misconfigured\",\n `[@ai-matrx/meet] ${action}: there is no meet runtime on this tree.`,\n NO_RUNTIME_REMEDY,\n );\n}\n\nfunction requireHost(host: MeetHost | null, action: string): MeetHost {\n if (host === null) throw noRuntime(action);\n return host;\n}\n\nfunction requireEngine(engine: RoomEngine | null, action: string): RoomEngine {\n if (engine === null) throw noRuntime(action);\n return engine;\n}\n\nfunction requireSession(session: MeetingSession | null, action: string): MeetingSession {\n if (session === null) throw noRuntime(action);\n return session;\n}\n\nexport interface UseRoomResult {\n /** The contract phase, verbatim (CORE-DESIGN §3.1). */\n readonly phase: MeetPhase;\n readonly participants: readonly MeetParticipant[];\n readonly failure: { readonly message: string; readonly remedy: string } | null;\n join: (request: JoinRequest) => Promise<void>;\n leave: () => Promise<void>;\n toggleMicrophone: () => Promise<void>;\n toggleCamera: () => Promise<void>;\n toggleScreenShare: () => Promise<void>;\n /** False where the browser cannot share a screen at all (iOS): the control is absent. */\n readonly canShareScreen: boolean;\n toggleHand: () => Promise<void>;\n react: (emoji: string) => Promise<void>;\n /** To EVERYONE — saved through the server (Meet wave 2). */\n sendChat: (text: string) => Promise<void>;\n /** To ONE person — data plane only, never saved. */\n sendPrivateChat: (to: ParticipantIdentity, text: string) => Promise<void>;\n pin: (identity: ParticipantIdentity | null) => void;\n setLayout: (layout: StageLayout) => void;\n readonly local: MeetParticipant | null;\n}\n\nexport function useRoom(): UseRoomResult {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const engine = host?.engine ?? null;\n\n const local =\n snapshot?.participants.find(\n (participant) => participant.identity === snapshot.localIdentity,\n ) ?? null;\n\n return {\n phase: snapshot?.phase ?? \"resolving\",\n participants: snapshot?.participants ?? [],\n failure: snapshot?.failure ?? null,\n local,\n canShareScreen: engine?.supportsScreenShare() ?? true,\n join: useCallback(\n async (request: JoinRequest) => requireEngine(engine, \"join\").join(request),\n [engine],\n ),\n leave: useCallback(async () => requireEngine(engine, \"leave\").leave(), [engine]),\n toggleMicrophone: useCallback(\n async () =>\n requireEngine(engine, \"toggleMicrophone\").setMicrophoneEnabled(\n !(local?.micEnabled ?? false),\n ),\n [engine, local],\n ),\n toggleCamera: useCallback(\n async () =>\n requireEngine(engine, \"toggleCamera\").setCameraEnabled(!(local?.cameraEnabled ?? false)),\n [engine, local],\n ),\n toggleScreenShare: useCallback(\n async () =>\n requireEngine(engine, \"toggleScreenShare\").setScreenShareEnabled(\n !(local?.screenSharing ?? false),\n ),\n [engine, local],\n ),\n toggleHand: useCallback(\n async () => requireEngine(engine, \"toggleHand\").raiseHand(!(local?.handRaised ?? false)),\n [engine, local],\n ),\n react: useCallback(\n async (emoji: string) => requireEngine(engine, \"react\").react(emoji),\n [engine],\n ),\n sendChat: useCallback(\n async (text: string) => {\n if (host === null) throw noRuntime(\"sendChat\");\n await host.inRoom.sendChat(text);\n },\n [host],\n ),\n sendPrivateChat: useCallback(\n async (to: ParticipantIdentity, text: string) =>\n requireEngine(engine, \"sendPrivateChat\").sendPrivateChat(to, text),\n [engine],\n ),\n pin: useCallback(\n (identity: ParticipantIdentity | null) => requireEngine(engine, \"pin\").pin(identity),\n [engine],\n ),\n setLayout: useCallback(\n (layout: StageLayout) => requireEngine(engine, \"setLayout\").setLayout(layout),\n [engine],\n ),\n };\n}\n\n/** The stage, recomputed only when the roster or the layout actually changes. */\nexport function useStage(pageSize = DEFAULT_PAGE_SIZE): Stage {\n const snapshot = useMeetSnapshot();\n const [page, setPage] = useState(0);\n const stage = useMemo(\n () =>\n computeStage({\n participants: snapshot?.participants ?? [],\n layout: snapshot?.layout ?? \"grid\",\n pinned: snapshot?.pinned ?? null,\n spotlight: snapshot?.spotlight ?? null,\n activeSpeakers: snapshot?.activeSpeakers ?? [],\n pageSize,\n page,\n }),\n [snapshot, pageSize, page],\n );\n // Clamp back into range when people leave — a page that scrolled past the\n // end must not render an empty stage.\n useEffect(() => {\n if (page > stage.pageCount - 1) setPage(Math.max(0, stage.pageCount - 1));\n }, [page, stage.pageCount]);\n return stage;\n}\n\n/**\n * Attach a participant's media to an element. The returned ref callback's\n * CLEANUP is the detach — there is nothing a consumer can forget to call.\n */\nexport function useTrack(\n identity: ParticipantIdentity | null,\n kind: \"camera\" | \"microphone\" | \"screen\",\n): (element: HTMLMediaElement | null) => void {\n const host = useMeetHost();\n const detachRef = useRef<(() => void) | null>(null);\n return useCallback(\n (element: HTMLMediaElement | null) => {\n detachRef.current?.();\n detachRef.current = null;\n if (element === null || identity === null || host === null) return;\n detachRef.current = host.engine.attach(identity, kind, element);\n },\n [host, identity, kind],\n );\n}\n\nexport interface UseCallsResult {\n readonly incoming: readonly CallInvite[];\n readonly outgoing: CallInvite | null;\n /** `null` for a guest identity — the UI renders nothing rather than a dead button. */\n readonly canCall: boolean;\n call: (people: readonly UserId[], mode: CallMode) => Promise<CallInvite>;\n accept: (invite: CallInvite) => Promise<void>;\n decline: (invite: CallInvite, message?: string) => Promise<void>;\n cancel: (invite: CallInvite) => Promise<void>;\n}\n\nexport function useCalls(): UseCallsResult {\n // NOT `useRequiredMeetHost`. `<IncomingCallHost/>` is mounted app-wide by\n // contract, so it renders on every server render and for every signed-out\n // visitor — states in which this provider is correctly inert.\n const host = useMeetHost();\n const center = host?.calls ?? null;\n const [, force] = useState(0);\n\n // The call center is not a store (it is a lifecycle object), so a ring must\n // re-render this hook. A 500ms poll would be a lie about liveness; the\n // provider's onIncoming already fires, so we re-render on the same tick.\n useEffect(() => {\n if (center === null) return undefined;\n const timer = setInterval(() => force((value) => value + 1), 1_000);\n return () => clearInterval(timer);\n }, [center]);\n\n return {\n incoming: center?.incoming() ?? [],\n outgoing: center?.outgoing() ?? null,\n canCall: center !== null,\n call: useCallback(\n async (people: readonly UserId[], mode: CallMode) => {\n if (center === null) {\n throw noRuntime(\n host === null ? \"call\" : \"call (a guest identity has no calling surface, D6)\",\n );\n }\n const invite = await center.call({ people, mode });\n force((value) => value + 1);\n return invite;\n },\n [center, host],\n ),\n accept: useCallback(\n async (invite: CallInvite) => {\n if (center === null || host === null) return;\n await center.accept(invite.id, invite.mode);\n await host.engine.join({ roomName: invite.roomName, mode: invite.mode });\n force((value) => value + 1);\n },\n [center, host],\n ),\n decline: useCallback(\n async (invite: CallInvite, message?: string) => {\n if (center === null) return;\n await center.decline(invite.id, message);\n force((value) => value + 1);\n },\n [center],\n ),\n cancel: useCallback(\n async (invite: CallInvite) => {\n if (center === null) return;\n await center.cancel(invite.id);\n force((value) => value + 1);\n },\n [center],\n ),\n };\n}\n\nexport interface UseLobbyResult {\n readonly waiting: readonly LobbyEntry[];\n readonly canAdmit: boolean;\n admit: (entry: LobbyEntry) => Promise<void>;\n deny: (entry: LobbyEntry) => Promise<void>;\n admitAll: () => Promise<void>;\n /** Lift a denial (`meet_undeny`) so the person may ask again. */\n undeny: (entry: LobbyEntry) => Promise<void>;\n}\n\nexport function useLobby(): UseLobbyResult {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const isHost = useIsHost();\n const session = host?.meetings ?? null;\n return {\n waiting: (snapshot?.lobby ?? []).filter((entry) => entry.state === \"knocking\"),\n // No runtime means no host powers — `<LobbyPanel>` renders nothing rather\n // than an admit button that would throw.\n canAdmit: session !== null && isHost,\n admit: useCallback(\n async (entry: LobbyEntry) => requireSession(session, \"admit\").admit(entry, true),\n [session],\n ),\n deny: useCallback(\n async (entry: LobbyEntry) => requireSession(session, \"deny\").admit(entry, false),\n [session],\n ),\n admitAll: useCallback(\n async () => requireSession(session, \"admitAll\").admitAll(),\n [session],\n ),\n undeny: useCallback(\n async (entry: LobbyEntry) => requireSession(session, \"undeny\").undeny(entry),\n [session],\n ),\n };\n}\n\nexport interface UseWaitingResult {\n /** This person's wait as the server last said it; null outside the waiting room. */\n readonly waiting: MeetWaiting | null;\n /** Stop waiting (the row becomes `withdrawn`). */\n withdraw: () => Promise<void>;\n /** Ask again after a denial or an expired knock — offered only when allowed. */\n askAgain: () => Promise<void>;\n}\n\n/** S4: the waiting screens' one read (CORE-DESIGN §2.6). */\nexport function useWaiting(): UseWaitingResult {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const session = host?.meetings ?? null;\n return {\n waiting: snapshot?.waiting ?? null,\n withdraw: useCallback(async () => requireSession(session, \"withdraw\").withdraw(), [session]),\n askAgain: useCallback(async () => requireSession(session, \"askAgain\").askAgain(), [session]),\n };\n}\n\nexport interface UseRecordingResult {\n readonly state: string;\n readonly label: string | null;\n readonly canStart: boolean;\n readonly canStop: boolean;\n readonly consentNotice: string | null;\n start: () => Promise<void>;\n stop: () => Promise<void>;\n}\n\nexport function useRecording(): UseRecordingResult {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n // 🚨 THE SERVER'S GRANT, not `isHost` (MRI-A11). aidream computes\n // `can_record = is_host AND policy == host-controlled` and puts it on the\n // token and the roster attribute; the policy half is why a host of an\n // `always-on` meeting correctly gets NO control. The policy check below is\n // kept as the package's own agreement with that rule, never as a substitute.\n const canRecord = useCanRecord();\n const meeting = snapshot?.meeting ?? null;\n const state = snapshot?.recording ?? \"idle\";\n // 🚨 NOT `state === \"idle\"` (MRI-A16). Once the durable states actually\n // arrive, a meeting whose recording finished sits on `available`, and asking\n // for `idle` made the Start control vanish for the rest of the session.\n const canStart =\n host !== null && canRecord && host.recording.canStart(meeting, true) && !isRecordingInFlight(state);\n\n return {\n state,\n label: recordingLabel(state),\n canStart,\n // `stopping` is already stopping; the other in-flight states can be.\n canStop: host !== null && canRecord && (state === \"recording\" || state === \"starting\"),\n consentNotice: host?.recording.consentNotice(meeting) ?? null,\n start: useCallback(async () => {\n const live = requireHost(host, \"startRecording\");\n if (meeting === null || live.identity.userId === null) return;\n await live.recording.start({\n meetingId: meeting.id,\n roomName: meeting.roomName,\n by: live.identity.userId,\n });\n }, [host, meeting]),\n stop: useCallback(async () => {\n const live = requireHost(host, \"stopRecording\");\n if (meeting === null || live.identity.userId === null) return;\n await live.recording.stop({ meetingId: meeting.id, by: live.identity.userId });\n }, [host, meeting]),\n };\n}\n\n/** One line of the transcript, as a panel renders it. */\nexport interface TranscriptLine {\n readonly id: string;\n /** Who said it, as the room knows them. Surfaces group by THIS, not by name. */\n readonly identity: ParticipantIdentity;\n readonly speaker: string;\n readonly text: string;\n readonly final: boolean;\n /** `caption` is a live hint; `record` is the durable transcript (D16). */\n readonly source: TranscriptSource;\n}\n\nexport interface UseMeetAiResult {\n /** The note-taker's honest state, and the server's refusal if there was one. */\n readonly noteTaker: NoteTakerStatus;\n /** The sentence for that state. Never a bare enum, never a spinner. */\n readonly noteTakerLabel: string;\n /** True only for a host of a meeting whose AI is on. Guests get no control. */\n readonly canControlNoteTaker: boolean;\n /** Live-window notes, oldest first. */\n readonly notes: readonly MeetingNote[];\n readonly decisions: readonly MeetingNote[];\n readonly actionItems: readonly MeetingNote[];\n /** The wrap-up summary, once the meeting has produced one. */\n readonly summary: MeetingNote | null;\n /**\n * The wrap-up pass ANNOUNCED itself into this room (MRI-A12). `null` means it\n * has not been announced HERE — never that no summary exists. The ordinary\n * ending path deletes the LiveKit room before the pass runs, so this is\n * normally null and the summary is read on the post-meeting record.\n */\n readonly wrapUp: WrapUpAnnouncement | null;\n readonly transcript: readonly TranscriptLine[];\n readonly asking: boolean;\n /** The answer SO FAR. It grows while the answer streams. */\n readonly answer: string | null;\n readonly coverage: MeetAnswerCoverage | null;\n /** The server's phase while an answer is being built. */\n readonly phase: string | null;\n readonly error: { readonly message: string; readonly remedy: string } | null;\n ask: (question: string) => Promise<void>;\n startNoteTaker: () => Promise<void>;\n stopNoteTaker: () => Promise<void>;\n}\n\n/**\n * THE MEETING ASSISTANT, as a surface needs it.\n *\n * Everything below is DATA, including every failure. There is no state in which\n * this hook returns a spinner with nothing behind it: the note-taker is in one\n * of five named states or the server refused with a sentence, and an answer is\n * either arriving (visibly, token by token) or has failed with a remedy.\n */\nexport function useMeetAi(): UseMeetAiResult {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const isHost = useIsHost();\n const [asking, setAsking] = useState(false);\n const [answer, setAnswer] = useState<string | null>(null);\n const [coverage, setCoverage] = useState<MeetAnswerCoverage | null>(null);\n const [phase, setPhase] = useState<string | null>(null);\n const [error, setError] = useState<{ message: string; remedy: string } | null>(null);\n const ai = host?.ai ?? null;\n const meeting = snapshot?.meeting ?? null;\n const participants = snapshot?.participants ?? [];\n const notes = snapshot?.notes ?? [];\n const wrapUp = snapshot?.wrapUp ?? null;\n\n const status = useMemo(\n () =>\n noteTakerStatus({\n meeting,\n participants,\n refusal: snapshot?.noteTakerRefusal ?? null,\n }),\n [meeting, participants, snapshot?.noteTakerRefusal],\n );\n\n const names = snapshot?.names ?? EMPTY_NAMES;\n const transcript = useMemo(\n () =>\n readableTranscript(snapshot?.transcript ?? []).map((line) => ({\n id: line.id,\n identity: line.identity,\n // 🚨 RESOLVED, not rendered raw (MRI-A16). A line stamped before its\n // speaker's name was known — a durable row that beat the roster, a\n // caption from someone who has since left — is re-attributed the moment\n // anything learns the name, in BOTH lanes, so the host and the guest\n // never read two different names for one speaker again (MRI-C8).\n speaker: displayNameFor(names, line.identity, line.speaker),\n text: line.text,\n final: line.final,\n source: line.source,\n })),\n [snapshot?.transcript, names],\n );\n\n const dispatchNoteTaker = useCallback(\n async (run: () => Promise<unknown>) => {\n setError(null);\n try {\n await run();\n } catch (thrown) {\n // THE SERVER'S SENTENCE REACHES THE SCREEN. A dispatch that failed and\n // said nothing is a meeting everyone believes is being minuted.\n setError({\n message: (thrown as Error).message,\n remedy:\n (thrown as { remedy?: string }).remedy ??\n \"The note-taker action could not be confirmed. Check its current state and try again.\",\n });\n }\n },\n [],\n );\n\n return {\n noteTaker: status,\n noteTakerLabel: noteTakerLabel(status),\n // A guest cannot dispatch an agent, and neither can anyone in a meeting\n // whose AI is off. The control is ABSENT for them, never disabled-looking.\n canControlNoteTaker:\n ai !== null && meeting !== null && meeting.aiEnabled && isHost && host?.identity.userId !== null,\n notes: notes.filter((note) => note.kind === \"note\" && note.role === null),\n // D20: the wrap-up is the authoritative post-meeting set. A window-written\n // decision the wrap-up superseded stays in `notes` — the timeline — and is\n // not listed a second time as a decision that was made.\n decisions: notes.filter((note) => note.kind === \"decision\" && note.supersededBy === null),\n actionItems: notes.filter(\n (note) => note.kind === \"action-item\" && note.supersededBy === null,\n ),\n summary: activeWrapUpSummary(notes),\n wrapUp,\n transcript,\n asking,\n answer,\n coverage,\n phase,\n error,\n ask: useCallback(\n async (question: string) => {\n if (ai === null || meeting === null) return;\n setAsking(true);\n setError(null);\n setAnswer(null);\n setCoverage(null);\n setPhase(null);\n try {\n const result = await ai.ask({\n meetingId: meeting.id,\n question,\n // THE ANSWER RENDERS AS IT ARRIVES. Waiting for the last token to\n // show the first is a spinner wearing an answer's clothes.\n onText: (text) => setAnswer(text),\n onPhase: (value) => setPhase(value),\n });\n setAnswer(result.text);\n setCoverage(result.coverage);\n } catch (thrown) {\n setError({\n message: (thrown as Error).message,\n remedy:\n (thrown as { remedy?: string }).remedy ??\n \"The assistant could not answer. Nothing was changed by the attempt.\",\n });\n } finally {\n setAsking(false);\n setPhase(null);\n }\n },\n [ai, meeting],\n ),\n startNoteTaker: useCallback(async () => {\n if (ai === null || meeting === null) return;\n await dispatchNoteTaker(() =>\n ai.startNoteTaker({ meetingId: meeting.id, roomName: meeting.roomName }),\n );\n }, [ai, meeting, dispatchNoteTaker]),\n stopNoteTaker: useCallback(async () => {\n if (ai === null || meeting === null) return;\n await dispatchNoteTaker(() => ai.stopNoteTaker({ meetingId: meeting.id }));\n }, [ai, meeting, dispatchNoteTaker]),\n };\n}\n\n/**\n * ENDING THE MEETING FOR EVERYONE — a host control with an honest failure.\n *\n * `POST /v1/meet/end` is served by MRI-A6. A server that has not landed it\n * answers 404, and this hook reports that as the sentence\n * `END_MEETING_NOT_SERVED` rather than pretending the meeting ended or leaving\n * a button that appears to do nothing.\n */\nexport interface UseEndMeetingResult {\n readonly canEnd: boolean;\n readonly ending: boolean;\n readonly ended: boolean;\n /** The honest sentence when the attempt did not end the meeting. */\n readonly notice: string | null;\n end: () => Promise<void>;\n}\n\nexport function useEndMeeting(): UseEndMeetingResult {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const isHost = useIsHost();\n const [ending, setEnding] = useState(false);\n const [notice, setNotice] = useState<string | null>(null);\n const meeting = snapshot?.meeting ?? null;\n const session = host?.meetings ?? null;\n\n return {\n canEnd: session !== null && isHost && meeting !== null && meeting.endedAt === null,\n ending,\n ended: meeting?.endedAt !== null && meeting?.endedAt !== undefined,\n notice,\n end: useCallback(async () => {\n const live = requireSession(session, \"endForEveryone\");\n setEnding(true);\n setNotice(null);\n try {\n const result = await live.endForEveryone();\n if (!result.served) {\n // NOT A DEAD BUTTON AND NOT A LIE: the route is pinned in the\n // package's contract and this server has not landed it yet (MRI-A6).\n setNotice(END_MEETING_NOT_SERVED);\n }\n } catch (thrown) {\n const remedy = (thrown as { remedy?: string }).remedy;\n setNotice(\n remedy !== undefined\n ? `${(thrown as Error).message} ${remedy}`\n : (thrown as Error).message,\n );\n } finally {\n setEnding(false);\n }\n }, [session]),\n };\n}\n\n/**\n * THE CONSENT NOTICE (D10) — a property of this package, never a prop.\n *\n * Returns the sentence every participant must see while an agent is in the\n * room, or `null` when none is. There is deliberately no way to turn it off:\n * the org's policy decides whether a note-taker may run, and it never decides\n * whether the people in the room are told about it.\n */\nexport function useConsentNotice(): string | null {\n const snapshot = useMeetSnapshot();\n const agent = noteTakerParticipant(snapshot?.participants ?? []);\n if (agent === null) return null;\n return (\n `${agent.displayName} is in this meeting: it is transcribing what is said and ` +\n \"writing notes everyone here can read.\"\n );\n}\n\n/**\n * What the LOCAL participant is actually publishing, and why not when it is not.\n * Read this — never the roster's `micEnabled` — to decide whether to tell\n * someone they cannot be heard: the roster reflects the media plane, this\n * reflects the ATTEMPT, including one the browser refused outright (there is no\n * track to be un-muted in that case, so the roster would simply say `false`\n * with no reason attached).\n */\nexport function useLocalMedia(): LocalMediaState {\n const snapshot = useMeetSnapshot();\n return snapshot?.media ?? IDLE_MEDIA;\n}\n\n/**\n * The listen-only sentence, or `null` while the microphone is live. The remedy,\n * when the device was refused rather than declined, is on\n * `useLocalMedia().microphone.unavailable`.\n */\nexport function useListenOnlyNotice(): string | null {\n return listenOnlyNotice(useLocalMedia());\n}\n\n/** Live reactions, already expired. Rendering stale confetti is the alternative. */\nexport function useReactions(): readonly { id: string; emoji: string }[] {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const store = host?.store ?? null;\n useEffect(() => {\n if (store === null) return undefined;\n const timer = setInterval(() => store.expireReactions(Date.now()), REACTION_TTL_MS / 2);\n return () => clearInterval(timer);\n }, [store]);\n return snapshot?.reactions ?? [];\n}\n\n/** A ticking duration for the call header. One timer, not one per component. */\nexport function useElapsed(startedAt: string | null): number {\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n if (startedAt === null) return undefined;\n const timer = setInterval(() => setNow(Date.now()), 1_000);\n return () => clearInterval(timer);\n }, [startedAt]);\n if (startedAt === null) return 0;\n return Math.max(0, now - Date.parse(startedAt));\n}\n\n/** Whether the local actor may moderate a given participant. One rank check. */\nexport function useCanModerate(target: MeetParticipant | undefined): boolean {\n const snapshot = useMeetSnapshot();\n const role = useRole();\n const me = snapshot?.participants.find(\n (participant) => participant.identity === snapshot.localIdentity,\n );\n // The EFFECTIVE role (token ∪ roster attribute — MRI-A11), not whichever of\n // the two the roster happened to carry this tick.\n return canModerateParticipant(me === undefined ? undefined : { ...me, role }, target);\n}\n\n/**\n * 🚨 EVERY HOST POWER, THROUGH THE SERVER (MRI-A11 → Meet wave 2).\n *\n * `canModerate` is the rank check — a host outranks a co-host outranks\n * everyone, and NOBODY may moderate themselves — so a surface renders a control\n * only where it would actually be honoured. The act itself is a server call\n * (`core/in-room.ts`): through 0.7.x mute and remove were data packets nothing\n * listened to, which made them buttons that did nothing.\n *\n * `notice` is the server's last sentence (or refusal) for the actor — a control\n * that acted and said nothing is indistinguishable from one that did nothing.\n */\nexport interface UseHostControlsResult {\n readonly isHost: boolean;\n /** The host itself (not a co-host) — only the host hands the meeting over. */\n readonly isOwner: boolean;\n readonly notice: { readonly text: string; readonly failed: boolean } | null;\n readonly busy: boolean;\n clearNotice: () => void;\n canModerate: (target: MeetParticipant) => boolean;\n run: (request: ModerationRequest) => Promise<ModerationResult | null>;\n mute: (target: MeetParticipant) => Promise<void>;\n remove: (target: MeetParticipant) => Promise<void>;\n}\n\nexport function useHostControls(): UseHostControlsResult {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const role = useRole();\n const me = snapshot?.participants.find(\n (participant) => participant.identity === snapshot.localIdentity,\n );\n const actor = me === undefined ? undefined : { ...me, role };\n const [busy, setBusy] = useState(false);\n const inRoom = host?.inRoom ?? null;\n const store = host?.store ?? null;\n const notice = snapshot?.actionNotice ?? null;\n const setNotice = useCallback(\n (next: { text: string; failed: boolean } | null) =>\n store?.patch({ actionNotice: next === null ? null : { ...next, at: Date.now() } }),\n [store],\n );\n\n const run = useCallback(\n async (request: ModerationRequest): Promise<ModerationResult | null> => {\n if (inRoom === null) throw noRuntime(`moderate(${request.action})`);\n setBusy(true);\n try {\n const result = await inRoom.moderate(request);\n setNotice({ text: result.message, failed: false });\n return result;\n } catch (thrown) {\n const error = thrown as { message?: string; remedy?: string };\n setNotice({\n text: [error.message, error.remedy].filter(Boolean).join(\" \"),\n failed: true,\n });\n return null;\n } finally {\n setBusy(false);\n }\n },\n [inRoom, setNotice],\n );\n\n return {\n isHost: isHostRole(role),\n isOwner: role === \"host\",\n notice,\n busy,\n clearNotice: useCallback(() => setNotice(null), [setNotice]),\n canModerate: useCallback(\n (target: MeetParticipant) => canModerateParticipant(actor, target),\n [actor],\n ),\n run,\n mute: useCallback(\n async (target: MeetParticipant) => {\n await run({ action: \"mute\", identity: target.identity });\n },\n [run],\n ),\n remove: useCallback(\n async (target: MeetParticipant) => {\n await run({ action: \"remove\", identity: target.identity });\n },\n [run],\n ),\n };\n}\n\n/**\n * THE HAND QUEUE — raised hands in the order they went up (Meet wave 2).\n * First raised, first called on; a late joiner learns the order from each\n * client's re-announcement (`room.ts`).\n */\nexport function useHandQueue(): readonly MeetParticipant[] {\n const snapshot = useMeetSnapshot();\n return useMemo(\n () =>\n (snapshot?.participants ?? [])\n .filter((participant) => participant.handRaised)\n .sort((a, b) => (a.handRaisedAt ?? 0) - (b.handRaisedAt ?? 0)),\n [snapshot?.participants],\n );\n}\n\n/** This participant's noise suppression and background, and the setter. */\nexport interface UseMediaEffectsResult {\n readonly effects: MediaEffects;\n /** False = this browser cannot run a background effect; the control is absent. */\n readonly backgroundsSupported: boolean;\n set: (next: Partial<MediaEffects>) => Promise<void>;\n}\n\nexport function useMediaEffects(): UseMediaEffectsResult {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const engine = host?.engine ?? null;\n return {\n effects: snapshot?.effects ?? DEFAULT_EFFECTS,\n backgroundsSupported: engine?.supportsVideoEffects() ?? false,\n set: useCallback(\n async (next: Partial<MediaEffects>) => requireEngine(engine, \"setEffects\").setEffects(next),\n [engine],\n ),\n };\n}\n\n/** What a host last did to THIS participant, and a way to dismiss it. */\nexport function useModerationNotice(): { notice: ModerationNotice | null; dismiss: () => void } {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n return {\n notice: snapshot?.moderationNotice ?? null,\n dismiss: useCallback(() => host?.store.patch({ moderationNotice: null }), [host]),\n };\n}\n\n/**\n * A live microphone level, 0..1, for a meter. Reads the given stream through an\n * `AnalyserNode`; returns 0 while there is none. One animation frame loop,\n * closed with the stream.\n */\nexport function useAudioLevel(stream: MediaStream | null): number {\n const [level, setLevel] = useState(0);\n useEffect(() => {\n if (stream === null || stream.getAudioTracks().length === 0) {\n setLevel(0);\n return undefined;\n }\n const Context =\n (globalThis as { AudioContext?: typeof AudioContext }).AudioContext ??\n (globalThis as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (Context === undefined) return undefined;\n const context = new Context();\n const source = context.createMediaStreamSource(stream);\n const analyser = context.createAnalyser();\n analyser.fftSize = 512;\n source.connect(analyser);\n const samples = new Uint8Array(analyser.fftSize);\n let frame = 0;\n const tick = () => {\n analyser.getByteTimeDomainData(samples);\n let peak = 0;\n for (const sample of samples) peak = Math.max(peak, Math.abs(sample - 128) / 128);\n setLevel((previous) => (Math.abs(previous - peak) > 0.02 ? peak : previous));\n frame = requestAnimationFrame(tick);\n };\n frame = requestAnimationFrame(tick);\n return () => {\n cancelAnimationFrame(frame);\n source.disconnect();\n void context.close();\n };\n }, [stream]);\n return level;\n}\n\n/**\n * THE KEYBOARD SHORTCUTS (Meet wave 2) — Google Meet's own chords, so nobody\n * relearns anything: ⌘/Ctrl+D mute, ⌘/Ctrl+E camera, ⌘/Ctrl+Alt+H hand,\n * ⌘/Ctrl+Alt+C chat, ⌘/Ctrl+Alt+P people, Shift+? the shortcuts sheet. Never\n * fires while the person is typing in a field.\n */\nexport const MEET_SHORTCUTS: readonly {\n readonly id: \"mic\" | \"camera\" | \"hand\" | \"chat\" | \"people\" | \"help\";\n readonly keys: string;\n readonly label: string;\n}[] = [\n { id: \"mic\", keys: \"⌘/Ctrl + D\", label: \"Mute or unmute your microphone\" },\n { id: \"camera\", keys: \"⌘/Ctrl + E\", label: \"Turn your camera on or off\" },\n { id: \"hand\", keys: \"⌘/Ctrl + Alt + H\", label: \"Raise or lower your hand\" },\n { id: \"chat\", keys: \"⌘/Ctrl + Alt + C\", label: \"Open or close chat\" },\n { id: \"people\", keys: \"⌘/Ctrl + Alt + P\", label: \"Open or close people\" },\n { id: \"help\", keys: \"Shift + ?\", label: \"Show keyboard shortcuts\" },\n];\n\nexport function useMeetShortcuts(\n handlers: Partial<Record<(typeof MEET_SHORTCUTS)[number][\"id\"], () => void>>,\n): void {\n const ref = useRef(handlers);\n ref.current = handlers;\n useEffect(() => {\n const target = (globalThis as { window?: Window }).window;\n if (target === undefined) return undefined;\n const onKey = (event: KeyboardEvent) => {\n const element = event.target as HTMLElement | null;\n const typing =\n element !== null &&\n (element.isContentEditable ||\n element.tagName === \"INPUT\" ||\n element.tagName === \"TEXTAREA\" ||\n element.tagName === \"SELECT\");\n const mod = event.metaKey || event.ctrlKey;\n const key = event.key.toLowerCase();\n let id: (typeof MEET_SHORTCUTS)[number][\"id\"] | null = null;\n if (mod && !event.altKey && !event.shiftKey && key === \"d\") id = \"mic\";\n else if (mod && !event.altKey && !event.shiftKey && key === \"e\") id = \"camera\";\n else if (mod && event.altKey && (event.code === \"KeyH\" || key === \"h\")) id = \"hand\";\n else if (mod && event.altKey && (event.code === \"KeyC\" || key === \"c\")) id = \"chat\";\n else if (mod && event.altKey && (event.code === \"KeyP\" || key === \"p\")) id = \"people\";\n else if (!mod && !typing && event.key === \"?\") id = \"help\";\n if (id === null) return;\n const handler = ref.current[id];\n if (handler === undefined) return;\n // ⌘D is \"bookmark\" and ⌘E is \"search\" in browsers: the meeting wins while\n // it is on screen, exactly as it does in Meet.\n event.preventDefault();\n handler();\n };\n target.addEventListener(\"keydown\", onKey);\n return () => target.removeEventListener(\"keydown\", onKey);\n }, []);\n}\n\nexport function useMeeting(): MeetingRecord | null {\n return useMeetSnapshot()?.meeting ?? null;\n}\n\nexport function useMeetingId(): MeetingId | null {\n return useMeetSnapshot()?.meeting?.id ?? null;\n}\n\n/* ------------------------------------------------------ the meeting record */\n\n/**\n * THE MEETING AFTER THE MEETING (MRI-D2).\n *\n * A durable link outlives the room (R3), and until this hook existed it\n * resolved to a pre-join screen for a room nobody could enter — the summary,\n * decisions, action items, live notes, transcript and recording were reachable\n * only from SQL. This loads all of it from durable rows, states what the reader\n * is not allowed to see instead of rendering emptiness, and keeps the Q&A box\n * working against the record: `/intelligence/ask` reads the same durable rows\n * and answers a signed-in member long after the room closed.\n *\n * It is deliberately independent of the room store: nothing here needs a\n * LiveKit connection, a token, or a joined participant.\n */\nexport interface UseMeetingRecordResult {\n readonly loading: boolean;\n readonly record: MeetingRecordBundle | null;\n /** A failure that stopped the WHOLE record loading. Partial refusals are in\n * `record.restrictions` — this is null whenever a record exists. */\n readonly failure: { readonly message: string; readonly remedy: string } | null;\n /** Q&A against the durable record. Streams, exactly as it does in-room. */\n readonly asking: boolean;\n readonly answer: string | null;\n readonly coverage: MeetAnswerCoverage | null;\n readonly askError: { readonly message: string; readonly remedy: string } | null;\n ask: (question: string) => Promise<void>;\n reload: () => void;\n}\n\n/**\n * 🚨 ONE RECORD PER READER PER TAB (0.7.79) — the bundle lives in a shared\n * cache (`core/record-cache.ts`), not in this hook. The first view of a\n * meeting reads; a wake from sleep, a remount, an Undo and every second view\n * render the kept record and read NOTHING. A member's record is kept current\n * by ONE change-feed channel per meeting (notes, transcript, attendance and\n * recordings, applied row by row), held while any view is mounted and a grace\n * after. `reload` and a reconnect read again; an older answer never paints over\n * a newer one. One cache per tab, on `globalThis`, so a dual-loaded graph\n * still shares it.\n */\nconst RECORD_CACHE_KEY = Symbol.for(\"@ai-matrx/meet/meeting-record-cache\");\n\nexport function meetingRecordCache(): MeetingRecordCache {\n const holder = globalThis as unknown as Record<symbol, MeetingRecordCache | undefined>;\n let cache = holder[RECORD_CACHE_KEY];\n if (cache === undefined) {\n cache = createMeetingRecordCache();\n holder[RECORD_CACHE_KEY] = cache;\n }\n return cache;\n}\n\nconst rowIdOf = (row: Record<string, unknown>) => (typeof row[\"id\"] === \"string\" ? row[\"id\"] : undefined);\n\nexport function useMeetingRecord(meeting: MeetingRecord | null): UseMeetingRecordResult {\n const host = useMeetHost();\n const repository = host?.repository ?? null;\n const ai = host?.ai ?? null;\n const readerId = host?.identity.userId ?? null;\n const [asking, setAsking] = useState(false);\n const [answer, setAnswer] = useState<string | null>(null);\n const [coverage, setCoverage] = useState<MeetAnswerCoverage | null>(null);\n const [askError, setAskError] = useState<{ message: string; remedy: string } | null>(null);\n\n const meetingId = meeting?.id ?? null;\n\n const api = host?.api ?? null;\n const tokens = host?.tokens ?? null;\n const roomName = meeting?.roomName ?? null;\n const manager = useRealtimeManager();\n const cache = meetingRecordCache();\n // Who is reading is part of the question: a member's record and a guest's\n // share of it are two different answers.\n const key =\n repository !== null && meetingId !== null\n ? `${readerId ?? `guest:${roomName ?? \"-\"}`}|${meetingId}`\n : null;\n const state = useSyncExternalStore(\n cache.subscribe,\n () => cache.state(key),\n () => cache.state(null),\n );\n\n // The read itself, built from this render's host and meeting. Held in a ref\n // so the cache can call the latest one (Retry, a reconnect) without the view\n // re-running its effects.\n const readRef = useRef<() => Promise<MeetingRecordAnswer>>(() => Promise.reject(new Error(\"not ready\")));\n readRef.current = async (): Promise<MeetingRecordAnswer> => {\n if (repository === null || meeting === null || meetingId === null) throw new Error(\"No meeting to read.\");\n // 🚨 TWO READERS, TWO DOORS, ONE BUNDLE (MRF-1).\n //\n // A signed-in member reads the record DIRECT from Supabase under RLS, as\n // they always have. A guest has no database at all — `anon` holds no grant\n // on the four org-scoped tables and widening it is forbidden (MRI-A10) — so\n // they read what the organization shares through `GET /v1/meet/record`,\n // authorized by the pass this device kept from the meeting. Both produce\n // the same `MeetingRecordBundle`, which is what keeps the two readers\n // looking at one product instead of two.\n if (readerId === null && api !== null && tokens !== null && roomName !== null) {\n return {\n kind: \"bundle\",\n bundle: await loadGuestMeetingRecord({\n api,\n repository,\n meetingId,\n meeting,\n roomToken: tokens.remembered(roomName),\n }),\n };\n }\n const parts = await loadMeetingRecordParts(repository, meetingId, { userId: readerId });\n // A SIGNED-IN OUTSIDER IS NOT A STRANGER (2026-10-01). The database\n // answers members and invitees; a person from another organization\n // the host admitted — or a guest who later made an account — reads\n // the guest share through the server, with their session as the pass.\n if (api !== null && parts.restrictions.some((part) => part.part === \"notes\")) {\n const viaServer = await loadGuestMeetingRecord({\n api,\n repository,\n meetingId,\n meeting,\n roomToken: null,\n session: true,\n });\n if (viaServer.restrictions.length < parts.restrictions.length) return { kind: \"bundle\", bundle: viaServer };\n }\n return { kind: \"parts\", meeting, parts };\n };\n\n // Mount and wake: read unless this reader's record of this meeting is kept.\n useEffect(() => {\n if (key === null) return;\n void cache.ensure(key, () => readRef.current());\n }, [cache, key]);\n\n // The meeting row moved (a rename, it ended): the kept record is assembled\n // again around it — nothing is read.\n useEffect(() => {\n if (key !== null && meeting !== null) cache.setMeeting(key, meeting);\n }, [cache, key, meeting]);\n\n // Live: ONE channel per meeting per reader, however many views hold it. A\n // guest has no database and no change feed; their record is what the\n // server shared.\n useEffect(() => {\n if (key === null || manager === null || repository === null || meetingId === null || readerId === null) {\n return undefined;\n }\n const repo = repository;\n const binding = (\n table: string,\n apply: (row: Record<string, unknown> | null, removedId: string | undefined) => void,\n ) => ({\n event: \"*\" as const,\n schema: repo.schema,\n table,\n filter: `meeting_id=eq.${meetingId}`,\n rowId: rowIdOf,\n onChange: ({ row, payload }: { row: Record<string, unknown> | null; payload: { eventType?: string } }) => {\n if (row === null) return;\n const gone = payload.eventType === \"DELETE\";\n apply(gone ? null : row, gone ? rowIdOf(row) : undefined);\n },\n });\n return cache.hold(\n key,\n () => {\n const handle = manager.open({\n topic: meetingTopic(meetingId),\n postgresChanges: [\n binding(repo.tables.notes, (row, removedId) =>\n cache.apply(key, { part: \"notes\", row: row && repo.projectNote(row), removedId }),\n ),\n binding(repo.tables.transcripts, (row, removedId) =>\n cache.apply(key, { part: \"transcript\", row: row && repo.projectSegment(row), removedId }),\n ),\n binding(repo.tables.participants, (row, removedId) =>\n cache.apply(key, { part: \"attendance\", row: row && repo.projectAttendee(row), removedId }),\n ),\n binding(repo.tables.recordings, (row, removedId) =>\n // An archived recording is off the record (the read filters it the same way).\n row !== null && row[\"deleted_at\"] != null\n ? cache.apply(key, { part: \"recording\", row: null, removedId: rowIdOf(row) })\n : cache.apply(key, { part: \"recording\", row: row && repo.projectRecording(row), removedId }),\n ),\n ],\n // Realtime has no replay: a socket that blipped reads the record again.\n onBackfill: async () => {\n await cache.ensure(key, () => readRef.current(), { force: true });\n },\n });\n return () => handle.close();\n },\n manager,\n );\n }, [cache, key, manager, repository, meetingId, readerId]);\n\n return {\n loading: state.loading && state.failure === null,\n record: state.record,\n failure: state.failure,\n asking,\n answer,\n coverage,\n askError,\n ask: useCallback(\n async (question: string) => {\n if (ai === null || meetingId === null) return;\n setAsking(true);\n setAskError(null);\n setAnswer(null);\n setCoverage(null);\n try {\n const result = await ai.ask({\n meetingId,\n question,\n onText: (text) => setAnswer(text),\n });\n setAnswer(result.text);\n setCoverage(result.coverage);\n } catch (thrown) {\n // A guest gets the server's own 403 here (\"a guest can read the notes\n // behind the link, but cannot ask the live assistant about a finished\n // meeting. Sign in to do that.\"). It is rendered verbatim.\n setAskError({\n message: (thrown as Error).message,\n remedy:\n (thrown as { remedy?: string }).remedy ??\n \"The assistant could not answer. Nothing was changed by the attempt.\",\n });\n } finally {\n setAsking(false);\n }\n },\n [ai, meetingId],\n ),\n reload: useCallback(() => {\n if (key !== null) void cache.ensure(key, () => readRef.current(), { force: true });\n }, [cache, key]),\n };\n}\n\n/**\n * EVERY MEETING THIS ORGANIZATION HAS, newest first — including the ended ones\n * (MRI-D2). A list that shows only live meetings is a list that empties itself\n * the moment the work is done, and it is what left every wrap-up unreachable.\n */\nexport interface UseMeetingsResult {\n readonly loading: boolean;\n readonly meetings: readonly MeetingRecord[];\n readonly hasMore: boolean;\n readonly failure: { readonly message: string; readonly remedy: string } | null;\n reload: () => void;\n}\n\nexport function useMeetings(\n limit = 25,\n /**\n * The page's own organization filter. Absent/null = ALL the person's\n * organizations, which is the default on every visit. It is never the host's\n * active organization (active-org-is-never-a-list-filter.md).\n */\n organizationFilter: OrganizationId | null = null,\n): UseMeetingsResult {\n const host = useMeetHost();\n const repository = host?.repository ?? null;\n const organizationId = organizationFilter;\n const [meetings, setMeetings] = useState<readonly MeetingRecord[]>([]);\n const [hasMore, setHasMore] = useState(false);\n const [loading, setLoading] = useState(true);\n const [failure, setFailure] = useState<{ message: string; remedy: string } | null>(null);\n const [nonce, setNonce] = useState(0);\n\n useEffect(() => {\n if (repository === null) return undefined;\n let live = true;\n setLoading(true);\n setFailure(null);\n void repository\n .listMeetings({ organizationId, limit })\n .then((page) => {\n if (!live) return;\n setMeetings(page.items);\n setHasMore(page.hasMore);\n setLoading(false);\n })\n .catch((thrown: unknown) => {\n if (!live) return;\n setFailure({\n message: (thrown as Error).message,\n remedy:\n (thrown as { remedy?: string }).remedy ??\n \"The meetings could not be listed. Reload the page; nothing was changed.\",\n });\n setLoading(false);\n });\n return () => {\n live = false;\n };\n }, [repository, organizationId, limit, nonce]);\n\n return {\n loading: loading && failure === null,\n meetings,\n hasMore,\n failure,\n reload: useCallback(() => setNonce((value) => value + 1), []),\n };\n}\n\n\n/* ------------------------------------- what guests may read of this meeting */\n\n/**\n * THE HOST'S CHOICE FOR ONE MEETING (MRF-1).\n *\n * Arman ruled on 2026-09-12 that guest access to the post-meeting record is a\n * configuration and never a question: the system's default, the organization's\n * override, and this — a host deciding for the meeting in front of them,\n * because they are the only person who knows who was in the room and what was\n * said in it.\n *\n * `setting` is what the host chose (`default` means \"use my organization's\");\n * `effective` is what that resolves to RIGHT NOW, which is the only honest way\n * to render \"use my organization's setting\" — a label that does not say what\n * the organization's setting IS makes the host guess.\n *\n * The write is the SERVER's decision and it re-checks host power. This hook\n * never renders a control for a non-host; the component does that, and the\n * server refuses one anyway.\n */\nexport interface UseGuestRecordAccessResult {\n readonly setting: GuestRecordAccessSetting;\n /** Null until the server has said what `default` means for this meeting. */\n readonly effective: GuestRecordAccessResolution | null;\n readonly saving: boolean;\n readonly error: { readonly message: string; readonly remedy: string } | null;\n choose: (setting: GuestRecordAccessSetting) => Promise<void>;\n}\n\nexport function useGuestRecordAccess(\n meeting: MeetingRecord | null,\n): UseGuestRecordAccessResult {\n const host = useMeetHost();\n const api = host?.api ?? null;\n const stored = guestRecordAccessSetting(meeting);\n // The row's own value is the truth until a write of ours succeeds; this is\n // the optimistic half, and it is reset by the row arriving again.\n const [chosen, setChosen] = useState<GuestRecordAccessSetting | null>(null);\n const [effective, setEffective] = useState<GuestRecordAccessResolution | null>(null);\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<{ message: string; remedy: string } | null>(null);\n\n useEffect(() => {\n setChosen(null);\n }, [stored, meeting?.id]);\n\n return {\n setting: chosen ?? stored,\n effective,\n saving,\n error,\n choose: useCallback(\n async (next: GuestRecordAccessSetting) => {\n if (api === null || meeting === null) return;\n setSaving(true);\n setError(null);\n try {\n const result = await setGuestRecordAccess({\n api,\n meetingId: meeting.id,\n organizationId: meeting.organizationId,\n access: next,\n });\n setChosen(result.setting);\n setEffective(result.effective);\n } catch (thrown) {\n // The server's own sentence, rendered. A host who is not a host of\n // this meeting reads why, not a generic failure.\n setError({\n message: (thrown as Error).message,\n remedy:\n (thrown as { remedy?: string }).remedy ??\n \"Nothing was changed. Reload the page and try again.\",\n });\n } finally {\n setSaving(false);\n }\n },\n [api, meeting],\n ),\n };\n}\n","/**\n * THE MEETING RECORD — what a meeting IS once the room is gone (MRI-D2).\n *\n * 🚨 THE DEFECT THIS EXISTS TO KILL. Through 2026-09-08 a meeting produced a\n * wrap-up summary, decisions, owner-assigned action items, five windows of live\n * notes, thirty-two transcript segments and a recording — and then had NO\n * SURFACE AT ALL. `/meet/<slug>` fell back to `<PreJoin>` on an ended meeting,\n * so the durable link that R3 promises would outlive the meeting resolved to a\n * screen offering to join a room that no longer exists. Everything the campaign\n * built after the room closed was reachable only from SQL.\n *\n * Three rules hold this file together:\n *\n * 1. **Every read is INDEPENDENT and every refusal is NAMED.** The transcript,\n * the notes, the attendance list and the recording live in four RLS-guarded\n * tables, and a guest holding the link can read NONE of them (only\n * `meet_meeting_by_slug` is granted to `anon`). One `Promise.all` would turn\n * a permission boundary into a blank page. Known guest restrictions never issue private reads; each authenticated read that is refused\n * becomes a `MeetingRecordRestriction` with a sentence — the reader is told\n * what exists and why they cannot see it, which is the difference between a\n * record they can ask for and a product that looks broken.\n * 2. **A partial record is still a record.** A failed transcript read never\n * hides the summary somebody CAN read.\n * 3. **Nothing here computes meaning.** Grouping, ordering and formatting only.\n * The summary, decisions and action items are durable rows written by the\n * wrap-up mandate (D5/D18); this module never re-derives them from the\n * transcript, because two sources for one fact is how they start to disagree.\n */\n\nimport { formatDurationMs } from \"@ai-matrx/kit/format\";\nimport type { MeetApi } from \"./api\";\nimport { MeetError } from \"./errors\";\nimport { displayNameFor, learnNames, type NameBook } from \"./names\";\nimport type { MeetRepository } from \"./repository\";\nimport { MEET_ROUTES, MEETING_PASS_HEADER, type RoomToken } from \"./tokens\";\nimport type {\n MeetingAttendee,\n MeetingId,\n MeetingNote,\n MeetIdentity,\n MeetingRecord,\n OrganizationId,\n ParticipantIdentity,\n RecordingRecord,\n TranscriptSegment,\n} from \"./types\";\n\n/** One thing the reader could not see, and the honest reason. Never a blank. */\nexport interface MeetingRecordRestriction {\n /** Which part of the record: `transcript`, `notes`, `attendance`, `recording`. */\n readonly part: \"transcript\" | \"notes\" | \"attendance\" | \"recording\";\n readonly message: string;\n readonly remedy: string;\n}\n\n/** One visible access state, made from restrictions that share one remedy. */\nexport interface MeetingRecordRestrictionNotice {\n readonly parts: readonly MeetingRecordRestriction[\"part\"][];\n readonly message: string;\n readonly remedy: string;\n}\n\n/** A run of consecutive lines by one speaker — how a person reads a transcript. */\nexport interface TranscriptBlock {\n readonly id: string;\n /** Who spoke. Blocks break on THIS, never on the rendered name (MRI-A16). */\n readonly identity: ParticipantIdentity;\n readonly speaker: string;\n /** ISO of the first line in the block. */\n readonly startedAt: string;\n readonly lines: readonly TranscriptSegment[];\n}\n\nexport interface MeetingRecordBundle {\n readonly meeting: MeetingRecord;\n readonly attendees: readonly MeetingAttendee[];\n /** The wrap-up summary: `kind='note'` with `metadata.role='summary'` (D5). */\n readonly summary: MeetingNote | null;\n /** D20: the AUTHORITATIVE set — window rows the wrap-up superseded are out. */\n readonly decisions: readonly MeetingNote[];\n readonly actionItems: readonly MeetingNote[];\n /** The live-notes windows, in window order (D3). */\n readonly liveNotes: readonly MeetingNote[];\n readonly transcript: readonly TranscriptSegment[];\n /** The most recent recording attempt, whatever state it reached. */\n readonly recording: RecordingRecord | null;\n /** What this reader could NOT see. Rendered; never swallowed. */\n readonly restrictions: readonly MeetingRecordRestriction[];\n /**\n * Identity → name, learned from everything this read COULD see: the durable\n * attendance rows and the transcript's own `speaker` column. It is what stops\n * a raw LiveKit token reaching the screen when one of those reads was refused\n * (MRI-A16).\n */\n readonly names: NameBook;\n}\n\n/**\n * The current wrap-up summary. Repairs preserve the prior durable row for\n * history, then mark it `superseded_by`; every current-facing projection must\n * therefore make the same distinction as decisions and action items.\n */\nexport function activeWrapUpSummary(notes: readonly MeetingNote[]): MeetingNote | null {\n return notes.find((note) => note.role === \"summary\" && note.supersededBy === null) ?? null;\n}\n\n/**\n * The sentence a reader gets when the database refuses them a part of the\n * record. It is deliberately about TENANCY, not about them: `meet_notes`,\n * `meet_transcript_segments`, `meet_participants` and `meet_recordings` have no\n * `anon` policy at all, so a link-follower with no session reaches exactly the\n * meeting row and nothing else. That is the correct posture and it must be\n * SAID, not rendered as emptiness.\n */\nconst RESTRICTION_REMEDY =\n \"Open it from the meeting link in the browser you attended with, or ask the host to share it.\";\n\nconst PART_LABEL: Readonly<Record<MeetingRecordRestriction[\"part\"], string>> = {\n transcript: \"The transcript of this meeting is not readable from this account.\",\n notes: \"The notes, decisions and action items are not readable from this account.\",\n attendance: \"Who attended is not readable from this account.\",\n recording: \"The recording is not readable from this account.\",\n};\n\n/** A refusal is DATA. A transport wobble is not, and is re-thrown as itself. */\nfunction restrictionFor(\n part: MeetingRecordRestriction[\"part\"],\n error: unknown,\n): MeetingRecordRestriction {\n const code = (error as { code?: string }).code;\n const denied =\n code === \"forbidden\" || code === \"session-unavailable\" || code === \"not-found\";\n return {\n part,\n message: denied\n ? PART_LABEL[part]\n : `${PART_LABEL[part]} (${(error as Error).message ?? \"the read failed\"})`,\n remedy: denied\n ? RESTRICTION_REMEDY\n : (error as { remedy?: string }).remedy ??\n \"This is a transient read failure, not a permission decision. Reload the page.\",\n };\n}\n\n/**\n * The four durable reads behind a record, as the database returned them, plus\n * what the reader could not see. `assembleMeetingRecord` turns them into the\n * bundle; a cache that keeps them can apply a realtime row to one part and\n * assemble again, without reading the other three (0.7.79).\n */\nexport interface MeetingRecordParts {\n readonly attendees: readonly MeetingAttendee[];\n /** Every note row (all kinds, superseded included), oldest first. */\n readonly notes: readonly MeetingNote[];\n readonly transcript: readonly TranscriptSegment[];\n /** Newest first; archived recordings are not here. */\n readonly recordings: readonly RecordingRecord[];\n readonly restrictions: readonly MeetingRecordRestriction[];\n}\n\n/**\n * Load the record parts the reader may request; state all restrictions.\n *\n * `meeting` is passed in when the caller already resolved it (the slug route\n * does, through the `anon`-granted RPC) — re-reading it through the table would\n * fail for exactly the reader who most needs the page to work.\n */\nexport async function loadMeetingRecord(\n repository: MeetRepository,\n meetingId: MeetingId,\n meeting: MeetingRecord,\n reader: Pick<MeetIdentity, \"userId\"> = { userId: null },\n): Promise<MeetingRecordBundle> {\n return assembleMeetingRecord(meeting, await loadMeetingRecordParts(repository, meetingId, reader));\n}\n\n/** The four reads, each with its own verdict (see `loadMeetingRecord`). */\nexport async function loadMeetingRecordParts(\n repository: MeetRepository,\n meetingId: MeetingId,\n reader: Pick<MeetIdentity, \"userId\"> = { userId: null },\n): Promise<MeetingRecordParts> {\n const restrictions: MeetingRecordRestriction[] = [];\n\n async function attempt<T>(\n part: MeetingRecordRestriction[\"part\"],\n run: () => Promise<T>,\n empty: T,\n ): Promise<T> {\n // No anonymous policy exists for these four tables. State the known\n // restriction without probing private data or generating expected 401s.\n if (reader.userId === null) {\n restrictions.push(restrictionFor(part, { code: \"session-unavailable\" }));\n return empty;\n }\n try {\n return await run();\n } catch (error) {\n restrictions.push(restrictionFor(part, error));\n return empty;\n }\n }\n\n // Four independent reads, four independent verdicts. Deliberately NOT\n // `Promise.all` over a single try/catch — see rule 1 in the header.\n const [attendees, notes, transcript, recordings] = await Promise.all([\n attempt<readonly MeetingAttendee[]>(\"attendance\", () => repository.attendees(meetingId), []),\n attempt<readonly MeetingNote[]>(\"notes\", () => repository.notes(meetingId), []),\n attempt<readonly TranscriptSegment[]>(\n \"transcript\",\n () => repository.transcript(meetingId),\n [],\n ),\n attempt<readonly RecordingRecord[]>(\n \"recording\",\n () => repository.recordings(meetingId),\n [],\n ),\n ]);\n return { attendees, notes, transcript, recordings, restrictions };\n}\n\n/** The bundle a reader sees, from the parts. Grouping and ordering only (rule 3). */\nexport function assembleMeetingRecord(\n meeting: MeetingRecord,\n parts: MeetingRecordParts,\n): MeetingRecordBundle {\n const { attendees, notes, transcript, recordings, restrictions } = parts;\n return {\n meeting,\n attendees,\n summary: activeWrapUpSummary(notes),\n // 🚨 D20: THE WRAP-UP IS THE AUTHORITATIVE POST-MEETING SET. A window row the\n // wrap-up superseded is still in `notes` and still in the timeline below —\n // it is simply not listed a second time as a decision that was made.\n decisions: notes.filter((note) => note.kind === \"decision\" && note.supersededBy === null),\n actionItems: notes.filter(\n (note) => note.kind === \"action-item\" && note.supersededBy === null,\n ),\n liveNotes: notes\n .filter((note) => note.kind === \"note\" && note.role === null)\n // Window order, then creation order for the notes of one window.\n .slice()\n .sort((left, right) => {\n const a = left.window ?? Number.MAX_SAFE_INTEGER;\n const b = right.window ?? Number.MAX_SAFE_INTEGER;\n return a === b ? left.createdAt.localeCompare(right.createdAt) : a - b;\n }),\n transcript,\n // `recordings` is newest first; the record shows the latest attempt,\n // including a failed one — a recording that failed is a fact the reader\n // needs, not a row to hide.\n recording: recordings[0] ?? null,\n restrictions,\n names: learnNames(learnNames({}, attendees), transcript.map((line) => ({\n identity: line.identity,\n displayName: line.speaker,\n }))),\n };\n}\n\n/**\n * 🚨 **A READ THAT WAS REFUSED IS NOT AN EMPTY RESULT (MRI-A16).**\n *\n * On production 2026-09-09 a guest's record view said *\"No wrap-up was written\n * for this meeting\"* directly underneath a panel that had just told them, in\n * the same screen, that the notes are not readable from their account — and\n * *\"Nothing was recorded as a decision\"* beside a decision that exists\n * (MRI-C8). The page asserted an absence it had no way to know, which is worse\n * than saying nothing: a person who reads \"no wrap-up was written\" stops\n * looking for one.\n *\n * So no surface writes its own empty sentence any more. It asks here, and gets\n * either its absence sentence — earned, because the read SUCCEEDED and returned\n * nothing — or the refusal, said plainly.\n */\nexport const REFUSED_ABSENCE = \"This record is not shared with guests.\";\n\n/** Was this part of the record refused to this reader? */\nexport function wasRefused(\n bundle: MeetingRecordBundle,\n part: MeetingRecordRestriction[\"part\"],\n): boolean {\n return bundle.restrictions.some((restriction) => restriction.part === part);\n}\n\n/**\n * The sentence to show where a part of the record has nothing in it. NEVER an\n * absence claim over a read that was refused.\n */\nexport function absenceSentence(\n bundle: MeetingRecordBundle,\n part: MeetingRecordRestriction[\"part\"],\n absence: string,\n): string {\n return wasRefused(bundle, part) ? REFUSED_ABSENCE : absence;\n}\n\n/**\n * 🚨 **\"NO WRAP-UP WAS WRITTEN\" IS A CLAIM, AND FOR ~33 SECONDS IT WAS FALSE.**\n *\n * On production 2026-09-09 the HOST's record view said *\"No wrap-up was written\n * for this meeting\"* between 04:46:42 — the flip from the room to the record —\n * and 04:47:15, when the wrap-up landed. The read was permitted and genuinely\n * empty, so this is not the refusal class above; it is the OTHER way a screen\n * lies, by reading \"not here yet\" as \"never happened\". A person who reads *\"no\n * wrap-up was written\"* closes the tab.\n *\n * The meeting row already knows the difference, and it is not a guess: the\n * wrap-up is a durable job, `enqueue_wrap_up` stamps\n * `metadata.note_taker.intelligence.wrap_up` when it queues it, and the graph\n * action stamps `state: \"failed\"` when the pass refuses TERMINALLY. So:\n *\n * - a summary exists → `written`;\n * - the notes read was refused → `refused` (never an absence claim);\n * - the job is marked failed → `failed`, in the server's own words;\n * - the job is queued or running → `in-progress`, with a refresh affordance;\n * - nothing ever queued one → `absent`, which is the only EARNED absence.\n *\n * There is no timeout that flips `in-progress` into `absent`. A wrap-up that is\n * genuinely stuck says it is still being written — which is TRUE — rather than\n * inventing a verdict this browser has no way to reach.\n */\nexport type WrapUpStatus =\n | { readonly state: \"written\" }\n | { readonly state: \"in-progress\"; readonly message: string; readonly canRefresh: true }\n | { readonly state: \"failed\"; readonly message: string }\n /** Nobody spoke: a normal outcome, said plainly (not an alert). */\n | { readonly state: \"empty\"; readonly message: string }\n | { readonly state: \"refused\"; readonly message: string }\n | { readonly state: \"absent\"; readonly message: string };\n\n/** What `metadata.note_taker` holds, as far as this file cares. */\nfunction noteTakerMetadata(meeting: MeetingRecord): Record<string, unknown> {\n const raw = (meeting.metadata as Record<string, unknown>)[\"note_taker\"];\n return typeof raw === \"object\" && raw !== null && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : {};\n}\n\nconst WRAP_UP_IN_PROGRESS =\n \"The wrap-up is being written… The summary, the decisions and the action items are \" +\n \"produced from the whole transcript once the meeting ends, which takes a moment longer \" +\n \"than the meeting does.\";\n\nconst WRAP_UP_ABSENT =\n \"No wrap-up was written for this meeting. A summary is produced from the transcript \" +\n \"when the meeting ends and the note-taker was in the room.\";\n\n/** Where the wrap-up actually is. Never \"absent\" over a job that is running. */\nexport function wrapUpStatus(bundle: MeetingRecordBundle): WrapUpStatus {\n if (bundle.summary !== null) return { state: \"written\" };\n if (wasRefused(bundle, \"notes\")) return { state: \"refused\", message: REFUSED_ABSENCE };\n\n const noteTaker = noteTakerMetadata(bundle.meeting);\n const intelligenceRaw = noteTaker[\"intelligence\"];\n const intelligence: Record<string, unknown> =\n typeof intelligenceRaw === \"object\" && intelligenceRaw !== null && !Array.isArray(intelligenceRaw)\n ? (intelligenceRaw as Record<string, unknown>)\n : {};\n const wrapUpRaw = intelligence[\"wrap_up\"];\n const wrapUp: Record<string, unknown> =\n typeof wrapUpRaw === \"object\" && wrapUpRaw !== null && !Array.isArray(wrapUpRaw)\n ? (wrapUpRaw as Record<string, unknown>)\n : {};\n\n if (wrapUp[\"state\"] === \"empty\") {\n const reason = wrapUp[\"reason\"];\n return {\n state: \"empty\",\n message:\n typeof reason === \"string\" && reason.trim().length > 0\n ? reason.trim()\n : \"No one spoke in this meeting, so there is no summary.\",\n };\n }\n\n if (wrapUp[\"state\"] === \"failed\") {\n const reason = wrapUp[\"reason\"];\n return {\n state: \"failed\",\n // THE SERVER'S OWN SENTENCE, verbatim. It already names the remedy, and a\n // house sentence over it would hide the one thing worth reading.\n message:\n typeof reason === \"string\" && reason.trim().length > 0\n ? reason.trim()\n : \"The wrap-up pass failed and no summary was written. It is not retried \" +\n \"automatically; a host can end the meeting again to re-queue it.\",\n };\n }\n\n const queued =\n typeof wrapUp[\"run_id\"] === \"string\" ||\n typeof intelligence[\"wrap_up_run_id\"] === \"string\" ||\n wrapUp[\"state\"] === \"queued\" ||\n wrapUp[\"state\"] === \"running\";\n // A meeting the note-taker actually attended will get a wrap-up even if this\n // browser loaded the record before the enqueue stamp landed — that race is\n // the whole defect, so `ended + the note-taker was there` is enough.\n const attended =\n noteTaker[\"state\"] === \"ended\" ||\n noteTaker[\"state\"] === \"transcribing\" ||\n noteTaker[\"state\"] === \"handoff_pending\";\n\n if (hasEnded(bundle.meeting) && (queued || attended)) {\n return { state: \"in-progress\", message: WRAP_UP_IN_PROGRESS, canRefresh: true };\n }\n return { state: \"absent\", message: WRAP_UP_ABSENT };\n}\n\n/**\n * GROUP THE TRANSCRIPT THE WAY A PERSON READS IT: consecutive lines by one\n * speaker become one block with one timestamp. Reading thirty-two rows each\n * re-stamped with a name and a clock is how a transcript becomes unreadable.\n */\nexport function groupTranscript(\n segments: readonly TranscriptSegment[],\n names: NameBook = {},\n): readonly TranscriptBlock[] {\n const blocks: TranscriptBlock[] = [];\n for (const segment of segments) {\n const last = blocks[blocks.length - 1];\n // 🚨 GROUPED BY IDENTITY, not by the rendered name (MRI-A16). Two people\n // whose names nobody ever learned both render \"Unnamed guest\", and merging\n // their lines into one block would put words in the wrong mouth — the exact\n // failure the honest placeholder exists to avoid.\n if (last !== undefined && last.identity === segment.identity) {\n blocks[blocks.length - 1] = { ...last, lines: [...last.lines, segment] };\n continue;\n }\n blocks.push({\n id: segment.id,\n identity: segment.identity,\n // One resolution, shared with the live rail — so the record and the room\n // can never disagree about who said something.\n speaker: displayNameFor(names, segment.identity, segment.speaker),\n startedAt: segment.startedAt,\n lines: [segment],\n });\n }\n return blocks;\n}\n\n/**\n * WHAT THE RECORDING IS DOING, in a sentence — including the two states people\n * hit most and understand least. `processing` is not \"broken\" and `failed` is\n * never silence: both say what happened and what to do.\n */\nexport function recordingNotice(recording: RecordingRecord | null): string | null {\n if (recording === null) return null;\n switch (recording.state) {\n case \"available\":\n if (recording.retention?.phase !== \"warning\") return null;\n return `This recording expires on ${retentionDueDate(recording.retention.dueAt)}. The transcript and notes remain available.`;\n case \"expired\":\n return \"This recording has expired. The transcript and notes remain available.\";\n case \"processing\":\n case \"stopping\":\n return \"The recording is still being processed. It appears here on its own once the file lands — nothing is lost while it is being written.\";\n case \"recording\":\n case \"starting\":\n return \"This recording is still being written. It becomes playable once the meeting's egress finishes.\";\n case \"failed\":\n return recording.failureReason !== null && recording.failureReason.length > 0\n ? `The recording failed: ${recording.failureReason}`\n : \"The recording failed and no file was produced. The transcript below was written separately and is unaffected.\";\n default:\n return null;\n }\n}\n\nfunction retentionDueDate(value: string): string {\n const dueAt = new Date(value);\n return Number.isNaN(dueAt.getTime()) ? \"its scheduled retention date\" : dueAt.toLocaleString();\n}\n\n/** Only an `available` recording has a file to play. Nothing else pretends to. */\nexport function playableRecordingFileId(recording: RecordingRecord | null): string | null {\n if (recording === null) return null;\n return recording.state === \"available\" ? recording.fileId : null;\n}\n\n/**\n * A MEETING IS OVER WHEN THE SERVER SAYS SO. `ended_at` is stamped by\n * `POST /v1/meet/end` or by the auto-end evaluation (MRI-A6) — never by a\n * client noticing the room is empty.\n */\nexport function hasEnded(meeting: MeetingRecord | null): boolean {\n return meeting !== null && meeting.endedAt !== null;\n}\n\n/**\n * \"Thursday, 8 September 2026, 4:21 PM · 19 minutes\". One shape everywhere.\n *\n * THE SPAN IS `formatDurationMs`'s (2026-09-12). This body used to divide by\n * `60_000` and pluralise \"minute\" itself — a duration formatter living in a\n * PACKAGE, found the day `aidream/apps/shared` became a scan root. `@ai-matrx/\n * meet` already depends on `@ai-matrx/kit`, and `style: \"long\"` is precisely\n * this voice (\"19 minutes\", \"1 minute\"). Three things improve by adopting it:\n * a 40-second meeting read \"0 minutes\" and now reads \"40 seconds\" (the\n * confident zero the package refuses), a 19-minute-35-second meeting read\n * \"20 minutes\" and now reads \"19 minutes\" (prose asserts at-least, so it\n * floors), and a 90-minute meeting reads \"1 hour\" instead of \"90 minutes\".\n */\nexport function meetingWhen(meeting: MeetingRecord, locale?: string): string {\n const start = meeting.startedAt ?? meeting.scheduledFor;\n if (start === null) return \"Time not recorded\";\n const startedAt = new Date(start);\n if (Number.isNaN(startedAt.getTime())) return \"Time not recorded\";\n const when = startedAt.toLocaleString(locale, {\n weekday: \"long\",\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n });\n if (meeting.endedAt === null || meeting.startedAt === null) return when;\n const endedAt = new Date(meeting.endedAt);\n if (Number.isNaN(endedAt.getTime())) return when;\n const elapsedMs = endedAt.getTime() - startedAt.getTime();\n return `${when} · ${formatDurationMs(elapsedMs, { style: \"long\" })}`;\n}\n\n/* ═══════════════════════════════════════════════════════════════════════════\n * MRF-1 — WHAT A GUEST MAY READ, AND HOW THEY READ IT\n *\n * 🚨 THE DEFECT THIS CLOSES. Everything above this line is correct and was\n * still the wrong product: a guest who attended a meeting, was admitted by the\n * host, spoke, and was transcribed got FOUR refusal sentences where the record\n * should have been. `anon` has no grant on the four org-scoped tables and\n * widening it is forbidden (MRI-A10) — a meeting link is not organization\n * membership — so the honest refusals were the only thing this file could say.\n *\n * Arman ruled on 2026-09-12 that guest access to the record is a CONFIGURATION\n * and never a question: a system default, an organization override, and a\n * per-meeting setting a host chooses. The server resolves it and serves the\n * shared subset at `GET /api/v1/meet/record`, authorized by the room token this\n * device already holds. So this half does three things and no more:\n *\n * 1. reads that route and turns its answer into the SAME `MeetingRecordBundle`\n * the member lane produces — so one screen renders both readers and they can\n * never drift into two products;\n * 2. states every part the level does NOT share as a `MeetingRecordRestriction`\n * with the server's own sentence, which is what keeps \"not shared\" and\n * \"nothing happened\" different things on screen;\n * 3. gives a host the control that sets it.\n *\n * What it does NOT do is decide anything. The level, the refusals and the\n * remedies are the server's words; a client that computed its own would be a\n * second authority on who may read a transcript.\n * ═══════════════════════════════════════════════════════════════════════════ */\n\n\n/** How much of a finished meeting a GUEST may read. Pinned with the server. */\nexport type GuestRecordAccess = \"none\" | \"summary\" | \"full\";\n\n/** Widest last. The ORDER is the comparison, exactly as it is on the server. */\nexport const GUEST_RECORD_ACCESS_LEVELS: readonly GuestRecordAccess[] = [\n \"none\",\n \"summary\",\n \"full\",\n];\n\n/**\n * What a HOST may choose for one meeting. `default` is not a fourth level — it\n * is the absence of a per-meeting choice, and it is the only way to un-choose.\n */\nexport type GuestRecordAccessSetting = \"default\" | GuestRecordAccess;\n\nexport const GUEST_RECORD_ACCESS_CHOICES: readonly GuestRecordAccessSetting[] = [\n \"default\",\n \"summary\",\n \"full\",\n \"none\",\n];\n\n/** `meet_meetings.metadata.guest_record_access`. No DDL; a setting on a row. */\nexport const GUEST_RECORD_ACCESS_METADATA_KEY = \"guest_record_access\";\n\n/** What a host chose for THIS meeting, read from the row they already have. */\nexport function guestRecordAccessSetting(\n meeting: MeetingRecord | null,\n): GuestRecordAccessSetting {\n const raw = (meeting?.metadata as Record<string, unknown> | undefined)?.[\n GUEST_RECORD_ACCESS_METADATA_KEY\n ];\n return GUEST_RECORD_ACCESS_LEVELS.includes(raw as GuestRecordAccess)\n ? (raw as GuestRecordAccess)\n : \"default\";\n}\n\n/** The sentence on the host's control. Plain English, never a token. */\nexport function guestRecordAccessLabel(setting: GuestRecordAccessSetting): string {\n switch (setting) {\n case \"default\":\n return \"Use my organization's setting\";\n case \"summary\":\n return \"Summary, decisions and action items\";\n case \"full\":\n return \"Everything, including the transcript\";\n case \"none\":\n return \"Nothing — keep the record internal\";\n }\n}\n\n/** What each choice actually means for the people who were in the room. */\nexport function guestRecordAccessExplanation(setting: GuestRecordAccessSetting): string {\n switch (setting) {\n case \"default\":\n return \"Guests who attended read whatever your organization shares by default. An administrator sets that once, for every meeting nobody thinks about.\";\n case \"summary\":\n return \"Guests who attended read the wrap-up summary, the decisions, the action items, the notes taken as it happened, and who was here. They do not get the word-for-word transcript or the recording.\";\n case \"full\":\n return \"Guests who attended read everything this meeting produced, including the word-for-word transcript of what each person said.\";\n case \"none\":\n return \"Guests who attended are told plainly that this meeting's record is kept inside your organization. They see nothing of it.\";\n }\n}\n\n/** What came back from the server, so a surface never has to infer a level. */\nexport interface GuestRecordAccessResolution {\n readonly level: GuestRecordAccess;\n /** `meeting` when a host chose for this one; `organization` otherwise. */\n readonly source: \"meeting\" | \"organization\";\n}\n\n/** What the parts of the record the level does not share are called, and why. */\nconst NOT_SHARED: Readonly<Record<MeetingRecordRestriction[\"part\"], string>> = {\n transcript:\n \"The word-for-word transcript of this meeting is not shared with guests.\",\n notes: \"The notes, decisions and action items are not shared with guests.\",\n attendance: \"Who attended is not shared with guests.\",\n recording: \"The recording of this meeting is not shared with guests.\",\n};\n\nconst NOT_SHARED_REMEDY =\n \"The organization that held this meeting decided how much of its record guests can read. \" +\n \"If you need more, ask whoever invited you — they can send it to you, or change the \" +\n \"setting for this meeting.\";\n\nconst RESTRICTION_PART_NAME: Readonly<Record<MeetingRecordRestriction[\"part\"], string>> = {\n attendance: \"attendance\",\n notes: \"notes and decisions\",\n transcript: \"word-for-word transcript\",\n recording: \"recording\",\n};\n\nfunction joinPartNames(parts: readonly MeetingRecordRestriction[\"part\"][]): string {\n const names = parts.map((part) => RESTRICTION_PART_NAME[part]);\n if (names.length < 2) return names[0] ?? \"this part of the record\";\n if (names.length === 2) return `${names[0]} and ${names[1]}`;\n return `${names.slice(0, -1).join(\", \")}, and ${names[names.length - 1]}`;\n}\n\n/**\n * Repeated policy refusals are one access state, not four alarming cards.\n *\n * The individual rows remain durable data for absence handling in their own\n * sections. This projection groups only rows with the exact same remedy, so a\n * distinct transport or integrity failure remains visible on its own.\n */\nexport function summarizeMeetingRecordRestrictions(\n restrictions: readonly MeetingRecordRestriction[],\n): readonly MeetingRecordRestrictionNotice[] {\n const grouped = new Map<string, MeetingRecordRestriction[]>();\n for (const restriction of restrictions) {\n const rows = grouped.get(restriction.remedy) ?? [];\n rows.push(restriction);\n grouped.set(restriction.remedy, rows);\n }\n return [...grouped.values()].map((rows) => {\n if (rows.length === 1) {\n const row = rows[0]!;\n return { parts: [row.part], message: row.message, remedy: row.remedy };\n }\n return {\n parts: rows.map((row) => row.part),\n message: `Some parts of this meeting record are not available to this reader: ${joinPartNames(rows.map((row) => row.part))}.`,\n remedy: rows[0]!.remedy,\n };\n });\n}\n\n/**\n * Read the record the server shares with THIS guest.\n *\n * `roomToken` is the pass this device kept from the meeting\n * (`TokenClient.remembered`). It is presented in a header, never appended to\n * the URL. A guest with no pass, an expired pass, or a level of `none` gets the\n * server's refusal — as restrictions on a real bundle, so the screen says what\n * it cannot show instead of collapsing into an error page.\n */\nexport async function loadGuestMeetingRecord(args: {\n api: MeetApi;\n repository: MeetRepository;\n meetingId: MeetingId;\n meeting: MeetingRecord;\n roomToken: RoomToken | null;\n /**\n * A SIGNED-IN reader the database refused (from another organization, or a\n * guest who later created an account). Their session stands in for the\n * pass: the server reads their `user:<id>` admission or the attendance they\n * claimed, and shares what guests may read (2026-10-01).\n */\n session?: boolean;\n /**\n * THE SESSION-AWARE RECORD (CORE-DESIGN §2.3): one run of a series. Omitted = the server's\n * default (a one-off meeting's whole record; a series' latest ended run).\n */\n sessionId?: string | null;\n}): Promise<MeetingRecordBundle> {\n const { api, repository, meetingId, meeting, roomToken } = args;\n const session = args.session === true;\n\n if (roomToken?.meetingPass == null && !session) {\n // NOT A GUESS AND NOT A FETCH. This browser has no pass for this meeting,\n // which the server would answer with the same sentence — asking it first\n // would only add a round trip and a 403 in the console.\n return refusedBundle(\n meeting,\n \"This meeting's record is only readable by the people who were in it.\",\n \"Open the meeting from the link you were invited with, in the same browser you used \" +\n \"to attend — that is what identifies you to this meeting. Signing in with an \" +\n \"account in the organization that held the meeting also works.\",\n );\n }\n\n let response: Response;\n try {\n response = await api.request({\n path:\n `${MEET_ROUTES.record}?meeting_id=${encodeURIComponent(meetingId)}` +\n (args.sessionId ? `&session_id=${encodeURIComponent(args.sessionId)}` : \"\"),\n method: \"GET\",\n // A GUEST HAS NO SESSION AND IS STILL A FIRST-CLASS READER (D6).\n requireAuth: session,\n operation: \"meet.record\",\n headers:\n roomToken?.meetingPass != null ? { [MEETING_PASS_HEADER]: roomToken.meetingPass } : {},\n });\n } catch (error) {\n // A transport wobble is not a permission decision and must not read as one.\n return refusedBundle(\n meeting,\n `This meeting's record could not be read (${(error as Error).message}).`,\n (error as { remedy?: string }).remedy ??\n \"This is a connection failure, not a decision about you. Reload the page.\",\n );\n }\n\n if (!response.ok) {\n const refusal = await readRefusal(response);\n return refusedBundle(meeting, refusal.message, refusal.remedy);\n }\n\n const body = (await response.json()) as Record<string, unknown>;\n const payload = (\n typeof body[\"data\"] === \"object\" && body[\"data\"] !== null ? body[\"data\"] : body\n ) as GuestRecordPayload;\n\n const rows = (key: keyof GuestRecordPayload): Record<string, unknown>[] =>\n Array.isArray(payload[key]) ? (payload[key] as Record<string, unknown>[]) : [];\n\n const attendees = rows(\"attendees\").map((row) => repository.projectAttendee(row));\n const notes = rows(\"notes\").map((row) => repository.projectNote(row));\n const transcript = rows(\"transcript\").map((row) => repository.projectSegment(row));\n const recordings = rows(\"recordings\").map((row) => repository.projectRecording(row));\n\n // 🚨 THE RESTRICTIONS COME FROM `shared`, NOT FROM AN EMPTY ARRAY. A level of\n // `summary` and a meeting where nobody spoke both produce zero transcript\n // rows, and telling the reader they are the same thing is the MRI-A16 defect.\n const shared = (payload.shared ?? {}) as Record<string, boolean>;\n const restrictions: MeetingRecordRestriction[] = [];\n for (const part of [\"attendance\", \"notes\", \"transcript\", \"recording\"] as const) {\n if (shared[part] === false) {\n restrictions.push({ part, message: NOT_SHARED[part], remedy: NOT_SHARED_REMEDY });\n }\n }\n\n return {\n meeting,\n attendees,\n summary: activeWrapUpSummary(notes),\n decisions: notes.filter((note) => note.kind === \"decision\" && note.supersededBy === null),\n actionItems: notes.filter(\n (note) => note.kind === \"action-item\" && note.supersededBy === null,\n ),\n liveNotes: notes\n .filter((note) => note.kind === \"note\" && note.role === null)\n .slice()\n .sort((left, right) => {\n const a = left.window ?? Number.MAX_SAFE_INTEGER;\n const b = right.window ?? Number.MAX_SAFE_INTEGER;\n return a === b ? left.createdAt.localeCompare(right.createdAt) : a - b;\n }),\n transcript,\n recording: recordings[0] ?? null,\n restrictions,\n names: learnNames(\n learnNames({}, attendees),\n transcript.map((line) => ({ identity: line.identity, displayName: line.speaker })),\n ),\n };\n}\n\n/** The row shapes the route returns — the same snake_case the tables use. */\ninterface GuestRecordPayload {\n readonly access?: GuestRecordAccess;\n readonly access_source?: \"meeting\" | \"organization\";\n readonly shared?: Record<string, boolean>;\n readonly attendees?: unknown;\n readonly notes?: unknown;\n readonly transcript?: unknown;\n readonly recordings?: unknown;\n readonly recording_notice?: string;\n}\n\n/**\n * THE SERVER'S OWN WORDS, verbatim. Every meet refusal carries\n * `{message, remedy}`, and a house sentence written over it would hide the one\n * thing worth reading — including the difference between \"your pass expired,\n * open the link again\" and \"this record is not shared with guests\".\n */\nasync function readRefusal(\n response: Response,\n): Promise<{ message: string; remedy: string }> {\n try {\n const body = (await response.json()) as Record<string, unknown>;\n const detail = (\n typeof body[\"detail\"] === \"object\" && body[\"detail\"] !== null ? body[\"detail\"] : body\n ) as Record<string, unknown>;\n const message = detail[\"message\"];\n const remedy = detail[\"remedy\"];\n if (typeof message === \"string\" && message.length > 0) {\n return {\n message,\n remedy:\n typeof remedy === \"string\" && remedy.length > 0\n ? remedy\n : \"Ask whoever hosted the meeting for what you need.\",\n };\n }\n } catch {\n /* falls through to the status sentence below */\n }\n return {\n message: `This meeting's record could not be read (the server answered ${response.status}).`,\n remedy:\n \"This is not a decision about you — it is a failure to reach the record. Reload the \" +\n \"page; if it repeats, tell whoever hosted the meeting.\",\n };\n}\n\n/** A real bundle whose every part is refused, with ONE reason said once. */\nfunction refusedBundle(\n meeting: MeetingRecord,\n message: string,\n remedy: string,\n): MeetingRecordBundle {\n return {\n meeting,\n attendees: [],\n summary: null,\n decisions: [],\n actionItems: [],\n liveNotes: [],\n transcript: [],\n recording: null,\n // Every empty collection below is a refusal, never evidence of absence.\n // The record renderer asks its own part before it writes an empty-state\n // sentence, so omitting attendance here made a denied roster read as\n // “Nobody attended.” `summarizeMeetingRecordRestrictions` still presents\n // this shared server reason once at the top of the record.\n restrictions: ([\"attendance\", \"notes\", \"transcript\", \"recording\"] as const).map(\n (part) => ({ part, message, remedy }),\n ),\n names: {},\n };\n}\n\n/**\n * A HOST chooses what guests may read of THIS meeting.\n *\n * Host power is the SERVER's decision and it re-checks it; this function exists\n * so the control has one call to make, and it returns what the choice RESOLVES\n * to so the screen never has to guess what \"my organization's setting\" means\n * today.\n */\nexport async function setGuestRecordAccess(args: {\n api: MeetApi;\n meetingId: MeetingId;\n organizationId: OrganizationId;\n access: GuestRecordAccessSetting;\n}): Promise<{\n readonly setting: GuestRecordAccessSetting;\n readonly effective: GuestRecordAccessResolution;\n}> {\n const response = await args.api.request({\n path: MEET_ROUTES.recordAccess,\n method: \"POST\",\n requireAuth: true,\n operation: \"meet.record.access\",\n body: {\n meeting_id: args.meetingId,\n organization_id: args.organizationId,\n access: args.access,\n },\n });\n if (!response.ok) {\n const refusal = await readRefusal(response);\n throw new MeetError(\"forbidden\", refusal.message, refusal.remedy);\n }\n const body = (await response.json()) as Record<string, unknown>;\n const payload = (\n typeof body[\"data\"] === \"object\" && body[\"data\"] !== null ? body[\"data\"] : body\n ) as {\n setting?: GuestRecordAccessSetting;\n effective?: GuestRecordAccessResolution;\n };\n return {\n setting: payload.setting ?? args.access,\n effective: payload.effective ?? { level: \"summary\", source: \"organization\" },\n };\n}\n\n/** What a guest is told when the level shares nothing. Rendered, never hidden. */\nexport const GUEST_RECORD_NOT_SHARED = NOT_SHARED;\n","/**\n * THE COLLAB AND CAPTION HOOKS — thin selectors over the core (CORE-DESIGN\n * §5.3, step S1). The logic lives in `core/collab-state.ts`; these only bind it\n * to the provider's store so every skin gets the same answers.\n */\n\nimport { useEffect, useRef } from \"react\";\nimport type { CollabPanel, CollabSnapshot } from \"../core/collab\";\nimport {\n activityAttention,\n availableActivities,\n moveToRoom,\n nextTranslationBatch,\n performCollab,\n takePanelRequest,\n untranslatedLines,\n} from \"../core/collab-state\";\nimport type { CollabRequest } from \"../core/in-room\";\nimport { useIsHost, useMeetHost, useMeetSnapshot } from \"./provider\";\n\nexport interface UseCollabResult {\n readonly collab: CollabSnapshot | null;\n readonly isHost: boolean;\n readonly localIdentity: string | null;\n readonly breakoutRoomId: string | null;\n /** Perform one act; resolves with the server's sentence, or its refusal (never throws). */\n act(request: CollabRequest): Promise<{ ok: boolean; message: string; itemId?: string | null }>;\n /** Go to a breakout room (`null` = the main room). */\n moveTo(roomId: string | null): Promise<{ ok: boolean; message: string }>;\n}\n\nexport function useCollab(): UseCollabResult {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const isHost = useIsHost();\n return {\n collab: snapshot?.collab ?? null,\n isHost,\n localIdentity: snapshot?.localIdentity ?? null,\n breakoutRoomId: snapshot?.breakoutRoomId ?? null,\n act: (request) => performCollab(host, request),\n moveTo: (roomId) => moveToRoom(host, roomId),\n };\n}\n\n/** The activities this person has, in the order Meet lists them. */\nexport function useAvailableActivities(): CollabPanel[] {\n const { collab, isHost } = useCollab();\n return availableActivities(collab, isHost);\n}\n\n/** Something worth a dot on the Activities button. */\nexport function useActivityAttention(): boolean {\n const { collab, isHost } = useCollab();\n return activityAttention(collab, isHost);\n}\n\n/**\n * The viewer's caption-translation choice and the translator's work queue. Finished\n * lines without a translation are sent in small batches; each line is translated once\n * per language for the whole meeting (server cache).\n */\nexport function useCaptionTranslation(lines: readonly { id: string; text: string; final: boolean }[]): {\n language: string | null;\n translations: Readonly<Record<string, string>>;\n error: string | null;\n} {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const collab = snapshot?.collab ?? null;\n const language = collab?.captionLanguage ?? null;\n const asked = useRef<Set<string>>(new Set());\n const translations = collab?.captionTranslations ?? {};\n const missing = untranslatedLines(lines, translations);\n // The batch's identity: the effect below runs when it changes, never per render.\n const key = `${language ?? \"\"}|${missing.map((l) => l.id).join(\",\")}`;\n const pending = useRef(missing);\n pending.current = missing;\n useEffect(() => {\n if (host === null || language === null) return;\n const batch = nextTranslationBatch(pending.current, language, asked.current);\n if (batch.length === 0) return;\n void host.inRoom.translateCaptions(language, batch).catch(() => undefined);\n }, [host, language, key]);\n return { language, translations, error: collab?.captionTranslationError ?? null };\n}\n\n/** Which collab panel the bar should open, asked by something else (a poll launched). */\nexport function useCollabPanelRequest(onRequest: (panel: CollabPanel) => void): void {\n const host = useMeetHost();\n const at = useMeetSnapshot()?.collab.panelRequest?.at ?? null;\n const callback = useRef(onRequest);\n callback.current = onRequest;\n useEffect(() => {\n if (host === null) return;\n const panel = takePanelRequest(host.store);\n if (panel === null) return;\n callback.current(panel);\n }, [at, host]);\n}\n","/**\n * The reaction ids every skin offers (CORE-DESIGN §5.3). The id IS the emoji\n * the wire carries; a skin may draw its own art for each, never a different set.\n */\nexport const MEET_REACTIONS: readonly string[] = [\"💖\", \"👍\", \"🎉\", \"👏\", \"😂\", \"😮\", \"😢\", \"🤔\", \"👎\"];\n","\"use client\";\n\n/**\n * POLLS, Q&A, THE SHARED WHITEBOARD AND BREAKOUT ROOMS — the in-room surfaces (Meet wave 5).\n *\n * Every act goes to the server (`host.inRoom.collab`), which decides who may do it and\n * what each person sees; these components render what the store holds and say the\n * server's sentence when it refuses. A control exists only when the organization's knob\n * turns it on (`collab.settings`) — absent, never dead.\n *\n * The champions: Google Meet's Activities panel (one door to polls, Q&A, whiteboard and\n * breakouts) and Zoom's breakout-room controls (automatic / manual / let people choose,\n * timer, broadcast, move, bring everyone back).\n */\n\nimport { useEffect, useRef, useState, type PointerEvent as ReactPointerEvent, type ReactNode } from \"react\";\nimport { CAPTION_LANGUAGES, type CollabPanel, type MeetBreakoutPlan, type MeetPoll, type MeetQuestion, type MeetStroke, assignedRoom, autoAssign, breakoutRemainingMs, defaultCaptionLanguage, displayNameFor, formatDurationMs, useMeetHost, useMeetSnapshot, setCaptionLanguage, takeMoveRequest, useActivityAttention, useAvailableActivities, useCollab } from \"../../react/facade\";\n\nimport { ControlButton, MenuItem, PanelShell, Popover } from \"./components\";\nimport { ActivitiesIcon, BreakoutIcon, CloseIcon, EraserIcon, HighlighterIcon, PencilIcon, PollIcon, QuestionIcon, UndoIcon, UpvoteIcon, WhiteboardIcon } from \"./icons\";\n\nfunction useNotice(): [\n { text: string; failed: boolean } | null,\n (result: { ok: boolean; message: string }) => void,\n] {\n const [notice, setNotice] = useState<{ text: string; failed: boolean } | null>(null);\n useEffect(() => {\n if (notice === null) return undefined;\n const timer = setTimeout(() => setNotice(null), notice.failed ? 7000 : 3000);\n return () => clearTimeout(timer);\n }, [notice]);\n return [notice, (result) => setNotice({ text: result.message, failed: !result.ok })];\n}\n\nfunction Notice({ notice }: { notice: { text: string; failed: boolean } | null }): ReactNode {\n if (notice === null) return null;\n return (\n <p className={notice.failed ? \"mx-meet__panel-note mx-meet__collab-error\" : \"mx-meet__panel-note\"} role=\"status\">\n {notice.text}\n </p>\n );\n}\n\n// ── the Activities control ─────────────────────────────────────────────────\n\nconst ACTIVITY_LABELS: Record<CollabPanel, string> = {\n polls: \"Polls\",\n qa: \"Q&A\",\n whiteboard: \"Whiteboard\",\n breakouts: \"Breakout rooms\",\n};\n\nfunction ActivityIcon({ panel }: { panel: CollabPanel }): ReactNode {\n const props = { width: 16, height: 16 };\n if (panel === \"polls\") return <PollIcon {...props} />;\n if (panel === \"qa\") return <QuestionIcon {...props} />;\n if (panel === \"whiteboard\") return <WhiteboardIcon {...props} />;\n return <BreakoutIcon {...props} />;\n}\n\nexport function ActivitiesControl({\n active,\n onOpen,\n}: {\n active: CollabPanel | null;\n onOpen: (panel: CollabPanel) => void;\n}): ReactNode {\n const [open, setOpen] = useState(false);\n const available = useAvailableActivities();\n const attention = useActivityAttention();\n if (available.length === 0) return null;\n return (\n <span className=\"mx-meet__anchor\">\n <ControlButton label=\"Activities\" active={open || active !== null} onClick={() => setOpen(!open)}>\n <ActivitiesIcon />\n {attention ? <span className=\"mx-meet__dot\" aria-hidden=\"true\" /> : null}\n </ControlButton>\n {open ? (\n <Popover label=\"Activities\" onClose={() => setOpen(false)}>\n <div className=\"mx-meet__menu\" role=\"menu\">\n {available.map((panel) => (\n <MenuItem\n key={panel}\n onSelect={() => {\n setOpen(false);\n onOpen(panel);\n }}\n >\n <ActivityIcon panel={panel} /> {ACTIVITY_LABELS[panel]}\n </MenuItem>\n ))}\n </div>\n </Popover>\n ) : null}\n </span>\n );\n}\n\n// ── polls ──────────────────────────────────────────────────────────────────\n\nfunction PollComposer({ maxOptions, onDone }: { maxOptions: number; onDone: () => void }): ReactNode {\n const { act } = useCollab();\n const [question, setQuestion] = useState(\"\");\n const [options, setOptions] = useState<string[]>([\"\", \"\"]);\n const [multiple, setMultiple] = useState(false);\n const [anonymous, setAnonymous] = useState(false);\n const [busy, setBusy] = useState(false);\n const [notice, show] = useNotice();\n const filled = options.map((o) => o.trim()).filter(Boolean);\n const ready = question.trim().length > 0 && filled.length >= 2;\n\n const save = async (launch: boolean) => {\n setBusy(true);\n const created = await act({ action: \"poll_create\", question, options: filled, multiple, anonymous });\n if (!created.ok) {\n show(created);\n setBusy(false);\n return;\n }\n if (launch && created.itemId != null) {\n const launched = await act({ action: \"poll_launch\", itemId: created.itemId });\n if (!launched.ok) {\n show(launched);\n setBusy(false);\n return;\n }\n }\n setBusy(false);\n onDone();\n };\n\n return (\n <form\n className=\"mx-meet__collab-card\"\n onSubmit={(event) => {\n event.preventDefault();\n if (ready) void save(true);\n }}\n >\n <label className=\"mx-meet__field\">\n <span className=\"mx-meet__field-label\">Question</span>\n <input\n className=\"mx-meet__input\"\n value={question}\n maxLength={1000}\n onChange={(event) => setQuestion(event.target.value)}\n placeholder=\"Ask the room something\"\n autoFocus\n />\n </label>\n {options.map((option, index) => (\n <div className=\"mx-meet__composer-row\" key={index}>\n <input\n className=\"mx-meet__input\"\n value={option}\n maxLength={200}\n aria-label={`Answer ${index + 1}`}\n placeholder={`Answer ${index + 1}`}\n onChange={(event) => setOptions(options.map((o, i) => (i === index ? event.target.value : o)))}\n />\n {options.length > 2 ? (\n <ControlButton label={`Remove answer ${index + 1}`} onClick={() => setOptions(options.filter((_, i) => i !== index))}>\n <CloseIcon width={14} height={14} />\n </ControlButton>\n ) : null}\n </div>\n ))}\n {options.length < maxOptions ? (\n <button type=\"button\" className=\"mx-meet__link\" onClick={() => setOptions([...options, \"\"])}>\n Add an answer\n </button>\n ) : null}\n <label className=\"mx-meet__toggle\">\n <input type=\"checkbox\" checked={multiple} onChange={(event) => setMultiple(event.target.checked)} />\n People can pick more than one\n </label>\n <label className=\"mx-meet__toggle\">\n <input type=\"checkbox\" checked={anonymous} onChange={(event) => setAnonymous(event.target.checked)} />\n Anonymous — nobody sees who picked what\n </label>\n <Notice notice={notice} />\n <div className=\"mx-meet__collab-actions\">\n <button type=\"submit\" className=\"mx-meet__primary mx-meet__primary--small\" disabled={!ready || busy}>\n Launch\n </button>\n <button type=\"button\" className=\"mx-meet__chip\" disabled={!ready || busy} onClick={() => void save(false)}>\n Save draft\n </button>\n <button type=\"button\" className=\"mx-meet__link\" onClick={onDone}>\n Cancel\n </button>\n </div>\n </form>\n );\n}\n\nfunction PollCard({ poll }: { poll: MeetPoll }): ReactNode {\n const { act, isHost } = useCollab();\n const [picked, setPicked] = useState<string[]>(poll.myVote.slice());\n const [notice, show] = useNotice();\n useEffect(() => setPicked(poll.myVote.slice()), [poll.myVote]);\n const counts = poll.options.some((o) => o.votes !== undefined);\n const total = poll.options.reduce((sum, o) => sum + (o.votes ?? 0), 0);\n const canVote = poll.state === \"live\";\n const voted = poll.myVote.length > 0;\n const stateWord =\n poll.state === \"draft\" ? \"Draft\" : poll.state === \"live\" ? \"Open\" : \"Closed\";\n\n const toggle = (id: string) => {\n if (!canVote) return;\n setPicked(poll.multiple ? (picked.includes(id) ? picked.filter((p) => p !== id) : [...picked, id]) : [id]);\n };\n\n return (\n <article className=\"mx-meet__collab-card\" aria-label={poll.question}>\n <header className=\"mx-meet__collab-head\">\n <strong className=\"mx-meet__collab-title\">{poll.question}</strong>\n <span className=\"mx-meet__collab-state\">{stateWord}</span>\n </header>\n <p className=\"mx-meet__panel-note\">\n {poll.multiple ? \"Pick any.\" : \"Pick one.\"}\n {poll.anonymous ? \" Anonymous.\" : \"\"}\n {counts && poll.voterCount !== null ? ` ${poll.voterCount} voted.` : \"\"}\n {isHost && poll.state !== \"draft\" && !poll.resultsShared ? \" Results are visible only to hosts.\" : \"\"}\n </p>\n <ul className=\"mx-meet__poll-options\">\n {poll.options.map((option) => {\n const share = counts && total > 0 ? Math.round(((option.votes ?? 0) / total) * 100) : 0;\n const chosen = picked.includes(option.id);\n return (\n <li key={option.id}>\n <button\n type=\"button\"\n className={chosen ? \"mx-meet__poll-option mx-meet__poll-option--chosen\" : \"mx-meet__poll-option\"}\n aria-pressed={chosen}\n disabled={!canVote}\n onClick={() => toggle(option.id)}\n >\n {counts ? <span className=\"mx-meet__poll-bar\" style={{ width: `${share}%` }} aria-hidden=\"true\" /> : null}\n <span className=\"mx-meet__poll-text\">{option.text}</span>\n {counts ? (\n <span className=\"mx-meet__poll-count\">\n {option.votes ?? 0} · {share}%\n </span>\n ) : null}\n </button>\n {option.voters !== undefined && option.voters.length > 0 ? (\n <span className=\"mx-meet__poll-voters\">{option.voters.join(\", \")}</span>\n ) : null}\n </li>\n );\n })}\n </ul>\n <Notice notice={notice} />\n <div className=\"mx-meet__collab-actions\">\n {canVote ? (\n <button\n type=\"button\"\n className=\"mx-meet__primary mx-meet__primary--small\"\n disabled={picked.length === 0 || (voted && picked.join() === poll.myVote.join())}\n onClick={() => void act({ action: \"poll_vote\", itemId: poll.id, optionIds: picked }).then(show)}\n >\n {voted ? \"Change vote\" : \"Vote\"}\n </button>\n ) : null}\n {isHost && poll.state === \"draft\" ? (\n <button type=\"button\" className=\"mx-meet__primary mx-meet__primary--small\" onClick={() => void act({ action: \"poll_launch\", itemId: poll.id }).then(show)}>\n Launch\n </button>\n ) : null}\n {isHost && poll.state === \"live\" ? (\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => void act({ action: \"poll_close\", itemId: poll.id }).then(show)}>\n End poll\n </button>\n ) : null}\n {isHost && poll.state !== \"draft\" ? (\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => void act({ action: \"poll_share\", itemId: poll.id }).then(show)}>\n {poll.resultsShared ? \"Hide results\" : \"Share results\"}\n </button>\n ) : null}\n {isHost && poll.state === \"live\" ? null : isHost ? (\n <button type=\"button\" className=\"mx-meet__link\" onClick={() => void act({ action: \"poll_delete\", itemId: poll.id }).then(show)}>\n Delete\n </button>\n ) : null}\n </div>\n </article>\n );\n}\n\nexport function PollsPanel({ onClose }: { onClose: () => void }): ReactNode {\n const { collab, isHost } = useCollab();\n const [composing, setComposing] = useState(false);\n const polls = collab?.polls ?? [];\n const maxOptions = collab?.settings?.pollMaxOptions ?? 10;\n // Newest first: the poll that just launched is the one people are looking for.\n const ordered = [...polls].reverse();\n return (\n <PanelShell title=\"Polls\" onClose={onClose}>\n {collab?.error != null ? <p className=\"mx-meet__panel-note mx-meet__collab-error\">{collab.error}</p> : null}\n {isHost && !composing ? (\n <button type=\"button\" className=\"mx-meet__primary mx-meet__primary--small\" onClick={() => setComposing(true)}>\n New poll\n </button>\n ) : null}\n {isHost && composing ? <PollComposer maxOptions={maxOptions} onDone={() => setComposing(false)} /> : null}\n {ordered.length === 0 && !composing ? (\n <p className=\"mx-meet__panel-note\">\n {isHost ? \"No polls yet. Polls and their results are kept with the meeting.\" : \"No polls yet.\"}\n </p>\n ) : null}\n {ordered.map((poll) => (\n <PollCard key={poll.id} poll={poll} />\n ))}\n </PanelShell>\n );\n}\n\n// ── Q&A ────────────────────────────────────────────────────────────────────\n\nfunction QuestionCard({ question }: { question: MeetQuestion }): ReactNode {\n const { act, isHost } = useCollab();\n const [answering, setAnswering] = useState(false);\n const [reply, setReply] = useState(\"\");\n const [notice, show] = useNotice();\n return (\n <li className={`mx-meet__collab-card mx-meet__question mx-meet__question--${question.state}`}>\n <div className=\"mx-meet__question-row\">\n <button\n type=\"button\"\n className={question.upvotedByMe ? \"mx-meet__upvote mx-meet__upvote--on\" : \"mx-meet__upvote\"}\n aria-pressed={question.upvotedByMe}\n aria-label={question.upvotedByMe ? \"Remove your upvote\" : \"Upvote\"}\n disabled={question.state === \"dismissed\"}\n onClick={() => void act({ action: \"question_upvote\", itemId: question.id }).then((r) => (r.ok ? undefined : show(r)))}\n >\n <UpvoteIcon width={16} height={16} />\n <span>{question.upvotes}</span>\n </button>\n <div className=\"mx-meet__question-body\">\n <span className=\"mx-meet__chat-meta\">\n <strong>{question.mine ? \"You\" : question.askerName}</strong>\n {question.mine && question.anonymous ? <span className=\"mx-meet__chat-private\">anonymous</span> : null}\n {question.state !== \"open\" ? (\n <span className=\"mx-meet__collab-state\">{question.state === \"answered\" ? \"Answered\" : \"Dismissed\"}</span>\n ) : null}\n </span>\n <span className=\"mx-meet__chat-text\">{question.text}</span>\n {question.answer !== null ? (\n <span className=\"mx-meet__question-answer\">\n <strong>{question.answeredByName ?? \"Host\"}:</strong> {question.answer}\n </span>\n ) : null}\n </div>\n </div>\n {answering ? (\n <form\n className=\"mx-meet__composer-row\"\n onSubmit={(event) => {\n event.preventDefault();\n void act({ action: \"question_answer\", itemId: question.id, text: reply }).then((r) => {\n show(r);\n if (r.ok) {\n setAnswering(false);\n setReply(\"\");\n }\n });\n }}\n >\n <input\n className=\"mx-meet__input\"\n value={reply}\n maxLength={2000}\n onChange={(event) => setReply(event.target.value)}\n placeholder=\"Type an answer, or leave empty if you answered out loud\"\n aria-label=\"Answer\"\n autoFocus\n />\n <button type=\"submit\" className=\"mx-meet__send\">\n Done\n </button>\n </form>\n ) : null}\n <Notice notice={notice} />\n {isHost || question.mine ? (\n <div className=\"mx-meet__collab-actions\">\n {isHost && question.state === \"open\" && !answering ? (\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => setAnswering(true)}>\n Answer\n </button>\n ) : null}\n {isHost && question.state === \"open\" ? (\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => void act({ action: \"question_dismiss\", itemId: question.id }).then(show)}>\n Dismiss\n </button>\n ) : null}\n {isHost && question.state !== \"open\" ? (\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => void act({ action: \"question_reopen\", itemId: question.id }).then(show)}>\n Reopen\n </button>\n ) : null}\n {question.mine || isHost ? (\n <button type=\"button\" className=\"mx-meet__link\" onClick={() => void act({ action: \"question_delete\", itemId: question.id }).then(show)}>\n Delete\n </button>\n ) : null}\n </div>\n ) : null}\n </li>\n );\n}\n\nexport function QaPanel({ onClose }: { onClose: () => void }): ReactNode {\n const { collab, act } = useCollab();\n const [draft, setDraft] = useState(\"\");\n const [anonymous, setAnonymous] = useState(false);\n const [order, setOrder] = useState<\"popular\" | \"recent\">(\"popular\");\n const [notice, show] = useNotice();\n const allowAnonymous = collab?.settings?.qaAnonymousAllowed === true;\n const questions = [...(collab?.questions ?? [])];\n const rank = (q: MeetQuestion) => (q.state === \"open\" ? 0 : q.state === \"answered\" ? 1 : 2);\n questions.sort((a, b) =>\n rank(a) !== rank(b)\n ? rank(a) - rank(b)\n : order === \"popular\"\n ? b.upvotes - a.upvotes || (b.createdAt ?? \"\").localeCompare(a.createdAt ?? \"\")\n : (b.createdAt ?? \"\").localeCompare(a.createdAt ?? \"\"),\n );\n return (\n <PanelShell title=\"Q&A\" onClose={onClose}>\n {collab?.error != null ? <p className=\"mx-meet__panel-note mx-meet__collab-error\">{collab.error}</p> : null}\n <form\n className=\"mx-meet__composer mx-meet__composer--chat\"\n onSubmit={(event) => {\n event.preventDefault();\n const text = draft.trim();\n if (text.length === 0) return;\n void act({ action: \"question_ask\", text, anonymous: allowAnonymous && anonymous }).then((r) => {\n if (r.ok) setDraft(\"\");\n else show(r);\n });\n }}\n >\n <div className=\"mx-meet__composer-row\">\n <input\n className=\"mx-meet__input\"\n value={draft}\n maxLength={1000}\n onChange={(event) => setDraft(event.target.value)}\n placeholder=\"Ask the host a question\"\n aria-label=\"Ask the host a question\"\n />\n <button type=\"submit\" className=\"mx-meet__send\" disabled={draft.trim().length === 0}>\n Ask\n </button>\n </div>\n {allowAnonymous ? (\n <label className=\"mx-meet__toggle\">\n <input type=\"checkbox\" checked={anonymous} onChange={(event) => setAnonymous(event.target.checked)} />\n Ask anonymously\n </label>\n ) : null}\n </form>\n <Notice notice={notice} />\n {questions.length > 1 ? (\n <div className=\"mx-meet__pills\" role=\"group\" aria-label=\"Order questions\">\n {([\"popular\", \"recent\"] as const).map((value) => (\n <button\n key={value}\n type=\"button\"\n className={order === value ? \"mx-meet__pill mx-meet__pill--on\" : \"mx-meet__pill\"}\n aria-pressed={order === value}\n onClick={() => setOrder(value)}\n >\n {value === \"popular\" ? \"Popular\" : \"Recent\"}\n </button>\n ))}\n </div>\n ) : null}\n {questions.length === 0 ? (\n <p className=\"mx-meet__panel-note\">No questions yet. Questions are kept with the meeting.</p>\n ) : (\n <ul className=\"mx-meet__chat\">\n {questions.map((question) => (\n <QuestionCard key={question.id} question={question} />\n ))}\n </ul>\n )}\n </PanelShell>\n );\n}\n\n// ── the shared whiteboard ──────────────────────────────────────────────────\n\nconst BOARD_W = 1600;\nconst BOARD_H = 1000;\nconst INK = [\"#111827\", \"#dc2626\", \"#2563eb\", \"#16a34a\", \"#f59e0b\", \"#7c3aed\"];\n\ntype Tool = \"pen\" | \"highlighter\" | \"eraser\";\n\nfunction strokePath(points: readonly (readonly [number, number])[]): string {\n if (points.length === 0) return \"\";\n const [first, ...rest] = points;\n const move = `M${(first![0] * BOARD_W).toFixed(1)} ${(first![1] * BOARD_H).toFixed(1)}`;\n if (rest.length === 0) return `${move} l0.1 0`;\n return `${move} ${rest.map(([x, y]) => `L${(x * BOARD_W).toFixed(1)} ${(y * BOARD_H).toFixed(1)}`).join(\" \")}`;\n}\n\nfunction StrokeView({ stroke, onPick }: { stroke: MeetStroke; onPick?: (() => void) | undefined }): ReactNode {\n return (\n <path\n d={strokePath(stroke.points)}\n fill=\"none\"\n stroke={stroke.color}\n strokeWidth={stroke.tool === \"highlighter\" ? stroke.width * 4 : stroke.width}\n strokeOpacity={stroke.tool === \"highlighter\" ? 0.35 : stroke.pending === true ? 0.6 : 1}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n onPointerDown={onPick}\n style={onPick !== undefined ? { cursor: \"pointer\" } : undefined}\n >\n <title>{stroke.byName}</title>\n </path>\n );\n}\n\n/**\n * THE SHARED WHITEBOARD — one board per meeting, everyone draws, it is saved with the\n * meeting (every stroke is a room item). Replaces the stage while open, the way Zoom's\n * whiteboard does; the control bar stays.\n */\nexport function WhiteboardPanel({ onClose }: { onClose: () => void }): ReactNode {\n const { collab, act, isHost, localIdentity } = useCollab();\n const [tool, setTool] = useState<Tool>(\"pen\");\n const [color, setColor] = useState(INK[0]!);\n const [drawing, setDrawing] = useState<[number, number][] | null>(null);\n const [pending, setPending] = useState<MeetStroke[]>([]);\n const [notice, show] = useNotice();\n const svgRef = useRef<SVGSVGElement | null>(null);\n const strokes = collab?.strokes ?? [];\n const mine = strokes.filter((s) => s.by === localIdentity);\n\n const point = (event: ReactPointerEvent<SVGSVGElement>): [number, number] | null => {\n const svg = svgRef.current;\n if (svg === null) return null;\n const rect = svg.getBoundingClientRect();\n // The board keeps its 16:10 shape inside whatever box it gets (xMidYMid meet).\n const scale = Math.min(rect.width / BOARD_W, rect.height / BOARD_H);\n const offsetX = (rect.width - BOARD_W * scale) / 2;\n const offsetY = (rect.height - BOARD_H * scale) / 2;\n const x = (event.clientX - rect.left - offsetX) / (BOARD_W * scale);\n const y = (event.clientY - rect.top - offsetY) / (BOARD_H * scale);\n return [Math.min(1, Math.max(0, x)), Math.min(1, Math.max(0, y))];\n };\n\n const finish = () => {\n const points = drawing;\n setDrawing(null);\n if (points === null || points.length === 0 || tool === \"eraser\") return;\n // Thin the stroke: a point every ~2px of board is plenty and keeps rows small.\n const thinned = points.filter((p, i) => i === 0 || i === points.length - 1 || i % 2 === 0).slice(0, 2000);\n const stroke = {\n points: thinned.map(([x, y]) => [Number(x.toFixed(4)), Number(y.toFixed(4))] as [number, number]),\n color,\n width: tool === \"highlighter\" ? 4 : 3,\n tool: tool === \"highlighter\" ? (\"highlighter\" as const) : (\"pen\" as const),\n };\n const localId = `pending-${Date.now()}`;\n setPending((list) => [...list, { id: localId, ...stroke, by: localIdentity ?? \"\", byName: \"You\", pending: true }]);\n void act({ action: \"stroke_add\", stroke }).then((result) => {\n setPending((list) => list.filter((s) => s.id !== localId));\n if (!result.ok) show(result);\n });\n };\n\n return (\n <section className=\"mx-meet__whiteboard\" aria-label=\"Whiteboard\">\n <header className=\"mx-meet__whiteboard-bar\">\n <strong className=\"mx-meet__panel-title\">Whiteboard</strong>\n <div className=\"mx-meet__pills\" role=\"group\" aria-label=\"Tool\">\n {(\n [\n [\"pen\", \"Pen\", <PencilIcon key=\"p\" width={16} height={16} />],\n [\"highlighter\", \"Highlighter\", <HighlighterIcon key=\"h\" width={16} height={16} />],\n [\"eraser\", \"Eraser — click a stroke\", <EraserIcon key=\"e\" width={16} height={16} />],\n ] as const\n ).map(([value, label, icon]) => (\n <button\n key={value}\n type=\"button\"\n className={tool === value ? \"mx-meet__pill mx-meet__pill--on\" : \"mx-meet__pill\"}\n aria-pressed={tool === value}\n aria-label={label}\n title={label}\n onClick={() => setTool(value)}\n >\n {icon}\n </button>\n ))}\n </div>\n <div className=\"mx-meet__swatches\" role=\"group\" aria-label=\"Color\">\n {INK.map((ink) => (\n <button\n key={ink}\n type=\"button\"\n className={ink === color ? \"mx-meet__swatch mx-meet__swatch--on\" : \"mx-meet__swatch\"}\n style={{ background: ink }}\n aria-label={`Color ${ink}`}\n aria-pressed={ink === color}\n onClick={() => {\n setColor(ink);\n if (tool === \"eraser\") setTool(\"pen\");\n }}\n />\n ))}\n </div>\n <ControlButton\n label=\"Undo my last stroke\"\n onClick={() => {\n const last = mine[mine.length - 1];\n if (last !== undefined) void act({ action: \"stroke_erase\", itemId: last.id }).then((r) => (r.ok ? undefined : show(r)));\n }}\n >\n <UndoIcon width={16} height={16} />\n </ControlButton>\n {isHost && strokes.length > 0 ? (\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => void act({ action: \"whiteboard_clear\" }).then(show)}>\n Clear board\n </button>\n ) : null}\n <span className=\"mx-meet__whiteboard-note\">Saved with the meeting</span>\n <ControlButton label=\"Close whiteboard\" onClick={onClose}>\n <CloseIcon width={16} height={16} />\n </ControlButton>\n </header>\n <Notice notice={notice} />\n <svg\n ref={svgRef}\n className={tool === \"eraser\" ? \"mx-meet__board mx-meet__board--erase\" : \"mx-meet__board\"}\n viewBox={`0 0 ${BOARD_W} ${BOARD_H}`}\n preserveAspectRatio=\"xMidYMid meet\"\n role=\"img\"\n aria-label={`Shared whiteboard, ${strokes.length} strokes`}\n onPointerDown={(event) => {\n if (tool === \"eraser\") return;\n const p = point(event);\n if (p === null) return;\n (event.target as Element).setPointerCapture?.(event.pointerId);\n setDrawing([p]);\n }}\n onPointerMove={(event) => {\n if (drawing === null) return;\n const p = point(event);\n if (p !== null) setDrawing([...drawing, p]);\n }}\n onPointerUp={finish}\n onPointerCancel={finish}\n >\n <rect x={0} y={0} width={BOARD_W} height={BOARD_H} className=\"mx-meet__board-paper\" />\n {strokes.map((stroke) => (\n <StrokeView\n key={stroke.id}\n stroke={stroke}\n onPick={\n tool === \"eraser\" && (isHost || stroke.by === localIdentity)\n ? () => void act({ action: \"stroke_erase\", itemId: stroke.id }).then((r) => (r.ok ? undefined : show(r)))\n : undefined\n }\n />\n ))}\n {pending.map((stroke) => (\n <StrokeView key={stroke.id} stroke={stroke} />\n ))}\n {drawing !== null ? (\n <StrokeView\n stroke={{\n id: \"drawing\",\n points: drawing,\n color,\n width: tool === \"highlighter\" ? 4 : 3,\n tool: tool === \"highlighter\" ? \"highlighter\" : \"pen\",\n by: \"\",\n byName: \"You\",\n }}\n />\n ) : null}\n </svg>\n </section>\n );\n}\n\n// ── breakout rooms ─────────────────────────────────────────────────────────\n\n/** A countdown: half a second left still reads 0:01, so round up to the whole second. */\nfunction clock(ms: number): string {\n return formatDurationMs(Math.ceil(ms / 1000) * 1000, { style: \"clock\" });\n}\n\n/** How long a refused `breakout_expire` waits before asking again. */\nconst BREAKOUT_EXPIRE_RETRY_MS = 5000;\n\nfunction useNow(active: boolean): number {\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n if (!active) return undefined;\n const timer = setInterval(() => setNow(Date.now()), 1000);\n return () => clearInterval(timer);\n }, [active]);\n return now;\n}\n\ntype AssignMode = \"auto\" | \"manual\" | \"choose\";\n\nfunction BreakoutSetup(): ReactNode {\n const { collab, act } = useCollab();\n const snapshot = useMeetSnapshot();\n const settings = collab?.settings ?? null;\n const people = (snapshot?.participants ?? []).filter(\n (p) => !p.isAgent && p.role !== \"host\" && p.role !== \"cohost\",\n );\n const [count, setCount] = useState(Math.min(Math.max(2, Math.ceil(people.length / 4)), settings?.breakoutMaxRooms ?? 20));\n const [mode, setMode] = useState<AssignMode>(\"auto\");\n const [minutes, setMinutes] = useState(settings?.breakoutDefaultMinutes ?? 15);\n const [manual, setManual] = useState<Record<string, number>>({});\n const [busy, setBusy] = useState(false);\n const [notice, show] = useNotice();\n const max = settings?.breakoutMaxRooms ?? 20;\n const rooms = Math.min(Math.max(1, count), max);\n const assignment =\n mode === \"auto\"\n ? autoAssign(\n people.map((p) => p.identity),\n rooms,\n )\n : mode === \"manual\"\n ? Array.from({ length: rooms }, (_, index) =>\n people.filter((p) => (manual[p.identity] ?? -1) === index).map((p) => p.identity),\n )\n : Array.from({ length: rooms }, () => [] as string[]);\n const nameOf = (identity: string) => people.find((p) => p.identity === identity)?.displayName ?? identity;\n\n return (\n <div className=\"mx-meet__collab-card\">\n <label className=\"mx-meet__field\">\n <span className=\"mx-meet__field-label\">Rooms</span>\n <input\n className=\"mx-meet__input\"\n type=\"number\"\n min={1}\n max={max}\n value={count}\n onChange={(event) => setCount(Number(event.target.value) || 1)}\n />\n </label>\n <div className=\"mx-meet__pills\" role=\"group\" aria-label=\"How people are placed\">\n {(\n [\n [\"auto\", \"Automatically\"],\n [\"manual\", \"Manually\"],\n [\"choose\", \"Let people choose\"],\n ] as const\n ).map(([value, label]) => (\n <button\n key={value}\n type=\"button\"\n className={mode === value ? \"mx-meet__pill mx-meet__pill--on\" : \"mx-meet__pill\"}\n aria-pressed={mode === value}\n onClick={() => setMode(value)}\n >\n {label}\n </button>\n ))}\n </div>\n <label className=\"mx-meet__field\">\n <span className=\"mx-meet__field-label\">Timer (minutes, 0 for none)</span>\n <input\n className=\"mx-meet__input\"\n type=\"number\"\n min={0}\n max={720}\n value={minutes}\n onChange={(event) => setMinutes(Math.max(0, Number(event.target.value) || 0))}\n />\n </label>\n {mode === \"manual\" ? (\n people.length === 0 ? (\n <p className=\"mx-meet__panel-note\">Nobody else is in the meeting yet.</p>\n ) : (\n <ul className=\"mx-meet__people\">\n {people.map((person) => (\n <li className=\"mx-meet__person\" key={person.identity}>\n <span className=\"mx-meet__person-name\">{person.displayName}</span>\n <select\n className=\"mx-meet__select mx-meet__select--compact\"\n aria-label={`Room for ${person.displayName}`}\n value={manual[person.identity] ?? -1}\n onChange={(event) => setManual({ ...manual, [person.identity]: Number(event.target.value) })}\n >\n <option value={-1}>Main room</option>\n {Array.from({ length: rooms }, (_, index) => (\n <option key={index} value={index}>\n Room {index + 1}\n </option>\n ))}\n </select>\n </li>\n ))}\n </ul>\n )\n ) : mode === \"auto\" ? (\n <ul className=\"mx-meet__breakout-rooms\">\n {assignment.map((members, index) => (\n <li key={index}>\n <strong>Room {index + 1}</strong>{\" \"}\n <span className=\"mx-meet__panel-note\">{members.length === 0 ? \"empty\" : members.map(nameOf).join(\", \")}</span>\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"mx-meet__panel-note\">Everyone picks a room from their own screen.</p>\n )}\n <Notice notice={notice} />\n <button\n type=\"button\"\n className=\"mx-meet__primary mx-meet__primary--small\"\n disabled={busy}\n onClick={() => {\n setBusy(true);\n void act({\n action: \"breakout_open\",\n rooms: assignment.map((identities, index) => ({ name: `Room ${index + 1}`, identities })),\n allowChoose: mode === \"choose\",\n minutes,\n }).then((result) => {\n setBusy(false);\n show(result);\n });\n }}\n >\n Open {rooms} room{rooms === 1 ? \"\" : \"s\"}\n </button>\n </div>\n );\n}\n\nfunction BreakoutRun({ plan }: { plan: MeetBreakoutPlan }): ReactNode {\n const { act, moveTo, isHost, breakoutRoomId, localIdentity } = useCollab();\n const snapshot = useMeetSnapshot();\n const now = useNow(plan.endsAt !== null);\n const remaining = breakoutRemainingMs(plan, now);\n const [broadcast, setBroadcast] = useState(\"\");\n const [notice, show] = useNotice();\n const nameOf = (identity: string) =>\n displayNameFor(snapshot?.names ?? {}, identity, snapshot?.participants.find((p) => p.identity === identity)?.displayName);\n const mine = assignedRoom(plan, localIdentity);\n\n const join = async (roomId: string | null) => {\n const said = await act({ action: \"breakout_join\", roomId });\n if (!said.ok) {\n show(said);\n return;\n }\n show(await moveTo(roomId));\n };\n\n return (\n <div className=\"mx-meet__collab-card\">\n <p className=\"mx-meet__panel-note\">\n {plan.rooms.length} room{plan.rooms.length === 1 ? \"\" : \"s\"} open\n {remaining !== null ? ` · ${clock(remaining)} left` : \"\"}\n {plan.allowChoose ? \" · people choose their room\" : \"\"}\n </p>\n <ul className=\"mx-meet__breakout-rooms\">\n {plan.rooms.map((room) => {\n const here = breakoutRoomId === room.id;\n const mayJoin = isHost || plan.allowChoose || mine?.id === room.id;\n return (\n <li key={room.id} className={here ? \"mx-meet__breakout-room mx-meet__breakout-room--here\" : \"mx-meet__breakout-room\"}>\n <div className=\"mx-meet__collab-head\">\n <strong>{room.name}</strong>\n {here ? (\n <span className=\"mx-meet__collab-state\">You are here</span>\n ) : mayJoin ? (\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => void join(room.id)}>\n Join\n </button>\n ) : null}\n </div>\n <span className=\"mx-meet__panel-note\">\n {room.identities.length === 0 ? \"Nobody assigned\" : room.identities.map(nameOf).join(\", \")}\n </span>\n {isHost && room.identities.length > 0 ? (\n <ul className=\"mx-meet__people\">\n {room.identities.map((identity) => (\n <li className=\"mx-meet__person\" key={identity}>\n <span className=\"mx-meet__person-name\">{nameOf(identity)}</span>\n <select\n className=\"mx-meet__select mx-meet__select--compact\"\n aria-label={`Move ${nameOf(identity)}`}\n value={room.id}\n onChange={(event) =>\n void act({\n action: \"breakout_move\",\n identity,\n roomId: event.target.value === \"\" ? null : event.target.value,\n }).then(show)\n }\n >\n <option value=\"\">Main room</option>\n {plan.rooms.map((option) => (\n <option key={option.id} value={option.id}>\n {option.name}\n </option>\n ))}\n </select>\n </li>\n ))}\n </ul>\n ) : null}\n </li>\n );\n })}\n </ul>\n {breakoutRoomId !== null ? (\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => void join(null)}>\n Return to the main room\n </button>\n ) : null}\n {isHost ? (\n <form\n className=\"mx-meet__composer-row\"\n onSubmit={(event) => {\n event.preventDefault();\n const text = broadcast.trim();\n if (text.length === 0) return;\n void act({ action: \"breakout_broadcast\", text }).then((result) => {\n show(result);\n if (result.ok) setBroadcast(\"\");\n });\n }}\n >\n <input\n className=\"mx-meet__input\"\n value={broadcast}\n maxLength={500}\n onChange={(event) => setBroadcast(event.target.value)}\n placeholder=\"Message every room\"\n aria-label=\"Message every room\"\n />\n <button type=\"submit\" className=\"mx-meet__send\" disabled={broadcast.trim().length === 0}>\n Send\n </button>\n </form>\n ) : null}\n <Notice notice={notice} />\n {isHost ? (\n <button\n type=\"button\"\n className=\"mx-meet__primary mx-meet__primary--small\"\n onClick={() => void act({ action: \"breakout_close\" }).then(show)}\n >\n Bring everyone back\n </button>\n ) : null}\n </div>\n );\n}\n\nexport function BreakoutsPanel({ onClose }: { onClose: () => void }): ReactNode {\n const { collab, isHost } = useCollab();\n const plan = collab?.breakout?.state === \"open\" ? collab.breakout : null;\n return (\n <PanelShell title=\"Breakout rooms\" onClose={onClose}>\n {collab?.error != null ? <p className=\"mx-meet__panel-note mx-meet__collab-error\">{collab.error}</p> : null}\n {plan !== null ? <BreakoutRun plan={plan} /> : isHost ? <BreakoutSetup /> : (\n <p className=\"mx-meet__panel-note\">Breakout rooms are closed.</p>\n )}\n </PanelShell>\n );\n}\n\n/**\n * WHAT A PERSON IN A MEETING WITH BREAKOUTS SEES WITHOUT OPENING ANYTHING: the room they\n * were assigned to (with Join), the room they are in and its countdown (with Return),\n * a host's broadcast — and the moves: a host moving them, and everyone coming back.\n * Also asks the server to close the plan when the countdown reaches zero (the server's\n * clock decides; no schedule).\n */\nexport function BreakoutBanner(): ReactNode {\n const host = useMeetHost();\n const { collab, act, moveTo, localIdentity, breakoutRoomId } = useCollab();\n const plan = collab?.breakout ?? null;\n const open = plan?.state === \"open\" ? plan : null;\n const now = useNow(open?.endsAt != null);\n const remaining = breakoutRemainingMs(open, now);\n const assigned = assignedRoom(open, localIdentity);\n const [dismissedBroadcast, setDismissedBroadcast] = useState<string | null>(null);\n const [failure, setFailure] = useState<string | null>(null);\n const expireAsked = useRef<{ id: string; at: number } | null>(null);\n\n // Moves the server asked for: a host moved me, or the rooms closed.\n const moveAt = collab?.moveRequest?.at ?? null;\n useEffect(() => {\n if (host === null) return;\n const request = takeMoveRequest(host.store);\n if (request === null) return;\n const here = host.store.snapshot().breakoutRoomId;\n if ((here ?? null) === request.roomId) return;\n void moveTo(request.roomId).then((result) => setFailure(result.ok ? null : result.message));\n }, [moveAt, host, moveTo]);\n\n // The timer ran out on this screen: ask; the server's clock decides. A screen whose\n // clock runs ahead is refused, so it asks again every few seconds, never only once.\n useEffect(() => {\n if (open === null || remaining === null || remaining > 0) return;\n const asked = expireAsked.current;\n if (asked !== null && asked.id === open.id && now - asked.at < BREAKOUT_EXPIRE_RETRY_MS) return;\n expireAsked.current = { id: open.id, at: now };\n void act({ action: \"breakout_expire\" });\n }, [open, remaining, act, now]);\n\n const broadcast = collab?.broadcast ?? null;\n const showBroadcast = broadcast !== null && broadcast.at !== dismissedBroadcast && open !== null;\n const room = open?.rooms.find((r) => r.id === breakoutRoomId) ?? null;\n\n if (open === null && failure === null) return null;\n return (\n <div className=\"mx-meet__breakout-banner\" role=\"status\">\n {failure !== null ? <span className=\"mx-meet__collab-error\">{failure}</span> : null}\n {room !== null ? (\n <span>\n <strong>{room.name}</strong>\n {remaining !== null ? ` · ${clock(remaining)} left` : \"\"}\n <button type=\"button\" className=\"mx-meet__chip\" onClick={() => void moveTo(null)}>\n Return to main room\n </button>\n </span>\n ) : assigned !== null ? (\n <span>\n You are assigned to <strong>{assigned.name}</strong>\n {remaining !== null ? ` · ${clock(remaining)} left` : \"\"}\n <button\n type=\"button\"\n className=\"mx-meet__primary mx-meet__primary--small\"\n onClick={() =>\n void act({ action: \"breakout_join\", roomId: assigned.id }).then(async (said) => {\n const result = said.ok ? await moveTo(assigned.id) : said;\n setFailure(result.ok ? null : result.message);\n })\n }\n >\n Join\n </button>\n </span>\n ) : open?.allowChoose ? (\n <span>Breakout rooms are open — pick one under Activities.</span>\n ) : null}\n {showBroadcast ? (\n <span className=\"mx-meet__breakout-broadcast\">\n <strong>{broadcast.by}:</strong> {broadcast.text}\n <button type=\"button\" className=\"mx-meet__link\" onClick={() => setDismissedBroadcast(broadcast.at)}>\n Dismiss\n </button>\n </span>\n ) : null}\n </div>\n );\n}\n\n// ── translated captions ────────────────────────────────────────────────────\n\n/** The Settings panel's captions section: translate captions into a language, or not. */\nexport function CaptionLanguageSettings(): ReactNode {\n const host = useMeetHost();\n const snapshot = useMeetSnapshot();\n const collab = snapshot?.collab ?? null;\n const settings = collab?.settings ?? null;\n if (host === null || settings === null || !settings.captionTranslationEnabled) return null;\n const viewer =\n typeof navigator !== \"undefined\" && typeof navigator.language === \"string\" ? navigator.language.slice(0, 2) : null;\n const preferred = defaultCaptionLanguage(settings, viewer);\n const current = collab?.captionLanguage ?? \"\";\n const choices = CAPTION_LANGUAGES.some((l) => l.tag === preferred)\n ? CAPTION_LANGUAGES\n : [{ tag: preferred, name: preferred }, ...CAPTION_LANGUAGES];\n return (\n <section className=\"mx-meet__section\" aria-label=\"Captions\">\n <label className=\"mx-meet__field\">\n <span className=\"mx-meet__field-label\">Translate captions to</span>\n <select\n className=\"mx-meet__select\"\n value={current}\n onChange={(event) => {\n const next = event.target.value === \"\" ? null : event.target.value;\n setCaptionLanguage(host.store, next);\n }}\n >\n <option value=\"\">Off — as spoken</option>\n {current === \"\" ? <option value={preferred}>{choices.find((c) => c.tag === preferred)?.name ?? preferred} (yours)</option> : null}\n {choices\n .filter((choice) => current !== \"\" || choice.tag !== preferred)\n .map((choice) => (\n <option key={choice.tag} value={choice.tag}>\n {choice.name}\n </option>\n ))}\n </select>\n </label>\n {collab?.captionTranslationError != null ? (\n <p className=\"mx-meet__panel-note mx-meet__collab-error\">{collab.captionTranslationError}</p>\n ) : null}\n </section>\n );\n}\n","\"use client\";\n\n/**\n * INLINED SVGs — no icon-library dependency, ever (C19).\n *\n * The tap-target precedent: ~60 buttons, all inlined, a fraction of a percent\n * of bloat and zero version-skew risk. These are Lucide-shaped paths, adapted;\n * they inherit `currentColor` so the token contract, not an icon prop, decides\n * every color.\n */\n\nimport type { SVGProps } from \"react\";\n\ntype IconProps = SVGProps<SVGSVGElement>;\n\nfunction Icon({ children, ...props }: IconProps): React.ReactElement {\n return (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n focusable=\"false\"\n {...props}\n >\n {children}\n </svg>\n );\n}\n\nexport function MicIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z\" />\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\" />\n <path d=\"M12 19v3\" />\n </Icon>\n );\n}\n\nexport function MicOffIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M2 2l20 20\" />\n <path d=\"M9 9v3a3 3 0 0 0 5.12 2.12\" />\n <path d=\"M15 9.34V5a3 3 0 0 0-5.94-.6\" />\n <path d=\"M19 10v2a7 7 0 0 1-.11 1.23M5 10v2a7 7 0 0 0 11 5.66\" />\n <path d=\"M12 19v3\" />\n </Icon>\n );\n}\n\nexport function CameraIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"m23 7-7 5 7 5V7Z\" />\n <rect x=\"1\" y=\"5\" width=\"15\" height=\"14\" rx=\"2\" />\n </Icon>\n );\n}\n\nexport function CameraOffIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M2 2l20 20\" />\n <path d=\"M10.66 5H14a2 2 0 0 1 2 2v3.34l1 1L23 7v10\" />\n <path d=\"M16 16a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2\" />\n </Icon>\n );\n}\n\nexport function ScreenShareIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <rect x=\"2\" y=\"3\" width=\"20\" height=\"14\" rx=\"2\" />\n <path d=\"M8 21h8M12 17v4\" />\n <path d=\"M12 13V8m0 0-2.5 2.5M12 8l2.5 2.5\" />\n </Icon>\n );\n}\n\nexport function PhoneIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6A19.79 19.79 0 0 1 2.12 4.18 2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.9.34 1.85.57 2.81.7A2 2 0 0 1 22 16.92Z\" />\n </Icon>\n );\n}\n\n/**\n * \"Call end\" — the downward handset on the red leave button (the glyph Meet\n * users recognise). A FILLED shape, so it overrides the line-icon defaults.\n */\nexport function PhoneOffIcon(props: IconProps): React.ReactElement {\n return (\n <Icon fill=\"currentColor\" stroke=\"none\" {...props}>\n <path d=\"M12 9c-1.6 0-3.15.25-4.6.72v3.1c0 .39-.23.74-.56.9-.98.49-1.87 1.12-2.66 1.85-.18.18-.43.28-.7.28-.28 0-.53-.11-.71-.29L.29 13.08a.99.99 0 0 1-.29-.7c0-.28.11-.53.29-.71C3.34 8.78 7.46 7 12 7s8.66 1.78 11.71 4.67c.18.18.29.43.29.71 0 .28-.11.53-.29.71l-2.48 2.48c-.18.18-.43.29-.71.29-.27 0-.52-.11-.7-.28a11.27 11.27 0 0 0-2.67-1.85.996.996 0 0 1-.56-.9v-3.1C15.15 9.25 13.6 9 12 9Z\" />\n </Icon>\n );\n}\n\nexport function HandIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M18 11V6a2 2 0 0 0-4 0v5\" />\n <path d=\"M14 10V4a2 2 0 0 0-4 0v7\" />\n <path d=\"M10 10.5V6a2 2 0 0 0-4 0v8\" />\n <path d=\"M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2a8 8 0 0 1-8-8v-1a2 2 0 1 1 4 0\" />\n </Icon>\n );\n}\n\nexport function ChatIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2Z\" />\n </Icon>\n );\n}\n\nexport function PeopleIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\" />\n <circle cx=\"9\" cy=\"7\" r=\"4\" />\n <path d=\"M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75\" />\n </Icon>\n );\n}\n\nexport function RecordIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <circle cx=\"12\" cy=\"12\" r=\"8\" fill=\"currentColor\" stroke=\"none\" />\n </Icon>\n );\n}\n\nexport function SparklesIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M12 3l1.9 4.6L18.5 9.5l-4.6 1.9L12 16l-1.9-4.6L5.5 9.5l4.6-1.9L12 3Z\" />\n <path d=\"M19 15l.9 2.1 2.1.9-2.1.9-.9 2.1-.9-2.1-2.1-.9 2.1-.9L19 15Z\" />\n </Icon>\n );\n}\n\nexport function LockIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <rect x=\"4\" y=\"10\" width=\"16\" height=\"11\" rx=\"2\" />\n <path d=\"M8 10V7a4 4 0 0 1 8 0v3\" />\n </Icon>\n );\n}\n\nexport function LayoutIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" />\n <path d=\"M3 9h18M9 21V9\" />\n </Icon>\n );\n}\n\nexport function SettingsIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z\" />\n </Icon>\n );\n}\n\nexport function CheckIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M20 6 9 17l-5-5\" />\n </Icon>\n );\n}\n\nexport function CloseIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </Icon>\n );\n}\n\nexport function PinIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M12 17v5\" />\n <path d=\"M9 2h6l-1 6 3.5 3.5V14H6.5v-2.5L10 8 9 2Z\" />\n </Icon>\n );\n}\n\nexport function WifiLowIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M12 20h.01\" />\n <path d=\"M8.5 16.5a5 5 0 0 1 7 0\" />\n </Icon>\n );\n}\n\n/* Meet wave 2 */\n\nexport function SmileIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M8 14s1.5 2 4 2 4-2 4-2\" />\n <line x1=\"9\" x2=\"9.01\" y1=\"9\" y2=\"9\" />\n <line x1=\"15\" x2=\"15.01\" y1=\"9\" y2=\"9\" />\n </Icon>\n );\n}\n\nexport function MoreIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <circle cx=\"12\" cy=\"12\" r=\"1\" />\n <circle cx=\"19\" cy=\"12\" r=\"1\" />\n <circle cx=\"5\" cy=\"12\" r=\"1\" />\n </Icon>\n );\n}\n\nexport function SearchIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.3-4.3\" />\n </Icon>\n );\n}\n\nexport function KeyboardIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M10 8h.01M12 12h.01M14 8h.01M16 12h.01M18 8h.01M6 8h.01M7 16h10M8 12h.01\" />\n <rect width=\"20\" height=\"16\" x=\"2\" y=\"4\" rx=\"2\" />\n </Icon>\n );\n}\n\nexport function SpotlightIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M12 2 15 8.5 22 9.3 17 14 18.2 21 12 17.6 5.8 21 7 14 2 9.3 9 8.5Z\" />\n </Icon>\n );\n}\n\nexport function SignalIcon({ bars, ...props }: IconProps & { bars: 0 | 1 | 2 | 3 }): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M4 20v-2\" opacity={bars >= 1 ? 1 : 0.3} />\n <path d=\"M10 20v-6\" opacity={bars >= 2 ? 1 : 0.3} />\n <path d=\"M16 20V9\" opacity={bars >= 3 ? 1 : 0.3} />\n <path d=\"M22 20V4\" opacity={bars >= 3 ? 1 : 0.3} />\n </Icon>\n );\n}\n\nexport function SpeakerIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M11 5 6 9H2v6h4l5 4V5Z\" />\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\" />\n <path d=\"M19.07 4.93a10 10 0 0 1 0 14.14\" />\n </Icon>\n );\n}\n\nexport function FlagIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z\" />\n <line x1=\"4\" x2=\"4\" y1=\"22\" y2=\"15\" />\n </Icon>\n );\n}\n\nexport function PencilIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z\" />\n </Icon>\n );\n}\n\n/** Lucide `shapes` — the Activities menu (polls, Q&A, whiteboard, breakout rooms). */\nexport function ActivitiesIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M8.3 10a.7.7 0 0 1-.626-1.079L11.4 3a.7.7 0 0 1 1.198-.043L16.3 8.9a.7.7 0 0 1-.572 1.1Z\" />\n <rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\" />\n <circle cx=\"17.5\" cy=\"17.5\" r=\"3.5\" />\n </Icon>\n );\n}\n\n/** Lucide `chart-no-axes-column` — polls. */\nexport function PollIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <line x1=\"18\" x2=\"18\" y1=\"20\" y2=\"10\" />\n <line x1=\"12\" x2=\"12\" y1=\"20\" y2=\"4\" />\n <line x1=\"6\" x2=\"6\" y1=\"20\" y2=\"14\" />\n </Icon>\n );\n}\n\n/** Lucide `message-circle-question` — Q&A. */\nexport function QuestionIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M7.9 20A9 9 0 1 0 4 16.1L2 22Z\" />\n <path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\" />\n <path d=\"M12 17h.01\" />\n </Icon>\n );\n}\n\n/** Lucide `presentation` — the shared whiteboard. */\nexport function WhiteboardIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M2 3h20\" />\n <path d=\"M21 3v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V3\" />\n <path d=\"m7 21 5-5 5 5\" />\n </Icon>\n );\n}\n\n/** Lucide `layout-panel-left`-like split — breakout rooms. */\nexport function BreakoutIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <rect width=\"7\" height=\"18\" x=\"3\" y=\"3\" rx=\"1\" />\n <rect width=\"7\" height=\"7\" x=\"14\" y=\"3\" rx=\"1\" />\n <rect width=\"7\" height=\"7\" x=\"14\" y=\"14\" rx=\"1\" />\n </Icon>\n );\n}\n\n/** Lucide `arrow-big-up` — upvote. */\nexport function UpvoteIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M9 18v-6H5l7-7 7 7h-4v6H9z\" />\n </Icon>\n );\n}\n\n/** Lucide `eraser`. */\nexport function EraserIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21\" />\n <path d=\"M22 21H7\" />\n <path d=\"m5 11 9 9\" />\n </Icon>\n );\n}\n\n/** Lucide `highlighter`. */\nexport function HighlighterIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"m9 11-6 6v3h9l3-3\" />\n <path d=\"m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4\" />\n </Icon>\n );\n}\n\n/** Lucide `undo-2`. */\nexport function UndoIcon(props: IconProps): React.ReactElement {\n return (\n <Icon {...props}>\n <path d=\"M9 14 4 9l5-5\" />\n <path d=\"M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11\" />\n </Icon>\n );\n}\n"],"mappings":"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","names":["import_react","import_react","import_react","import_react","module","import_jsx_runtime","import_format","import_react","import_jsx_runtime","import_react","import_react","import_react","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","observe"]}
|