@ai-matrx/meet 0.7.73 → 0.7.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.js CHANGED
@@ -219,6 +219,12 @@ var MEET_ROUTES = {
219
219
  record: "/api/v1/meet/record",
220
220
  /** A host chooses what guests may read of ONE meeting (MRF-1). */
221
221
  recordAccess: "/api/v1/meet/record/access",
222
+ /**
223
+ * KEEP YOUR NOTES (2026-10-01): a guest who just created an account presents
224
+ * their kept pass in `x-meet-room-token` with their new session, and the
225
+ * meeting they attended becomes theirs to read. Idempotent; never a grant.
226
+ */
227
+ claim: "/api/v1/meet/claim",
222
228
  /**
223
229
  * Meet wave 1 (0.8): bring every invitee up to date after a write — sends the
224
230
  * invitation / update / cancellation emails with their `.ics` and re-queues
@@ -5525,7 +5531,7 @@ import { formatDurationMs } from "@ai-matrx/kit/format";
5525
5531
  function activeWrapUpSummary(notes) {
5526
5532
  return notes.find((note) => note.role === "summary" && note.supersededBy === null) ?? null;
5527
5533
  }
5528
- var RESTRICTION_REMEDY = "The people in this meeting's organization can read it. Sign in with an account in that organization \u2014 following the link is enough to join a meeting, but not to read its record.";
5534
+ var RESTRICTION_REMEDY = "Open it from the meeting link in the browser you attended with, or ask the host to share it.";
5529
5535
  var PART_LABEL = {
5530
5536
  transcript: "The transcript of this meeting is not readable from this account.",
5531
5537
  notes: "The notes, decisions and action items are not readable from this account.",
@@ -5784,7 +5790,8 @@ function summarizeMeetingRecordRestrictions(restrictions) {
5784
5790
  }
5785
5791
  async function loadGuestMeetingRecord(args) {
5786
5792
  const { api, repository, meetingId, meeting, roomToken } = args;
5787
- if (roomToken === null) {
5793
+ const session = args.session === true;
5794
+ if (roomToken === null && !session) {
5788
5795
  return refusedBundle(
5789
5796
  meeting,
5790
5797
  "This meeting's record is only readable by the people who were in it.",
@@ -5797,9 +5804,9 @@ async function loadGuestMeetingRecord(args) {
5797
5804
  path: `${MEET_ROUTES.record}?meeting_id=${encodeURIComponent(meetingId)}`,
5798
5805
  method: "GET",
5799
5806
  // A GUEST HAS NO SESSION AND IS STILL A FIRST-CLASS READER (D6).
5800
- requireAuth: false,
5807
+ requireAuth: session,
5801
5808
  operation: "meet.record",
5802
- headers: { [ROOM_TOKEN_HEADER]: roomToken.token }
5809
+ headers: roomToken === null ? {} : { [ROOM_TOKEN_HEADER]: roomToken.token }
5803
5810
  });
5804
5811
  } catch (error) {
5805
5812
  return refusedBundle(
@@ -6528,7 +6535,24 @@ function useMeetingRecord(meeting) {
6528
6535
  meetingId,
6529
6536
  meeting,
6530
6537
  roomToken: tokens.remembered(roomName)
6531
- }) : loadMeetingRecord(repository, meetingId, meeting, { userId: readerId });
6538
+ }) : loadMeetingRecord(repository, meetingId, meeting, { userId: readerId }).then(
6539
+ (direct) => (
6540
+ // A SIGNED-IN OUTSIDER IS NOT A STRANGER (2026-10-01). The database
6541
+ // answers members and invitees; a person from another organization
6542
+ // the host admitted — or a guest who later made an account — reads
6543
+ // the guest share through the server, with their session as the pass.
6544
+ api !== null && direct.restrictions.some((part) => part.part === "notes") ? loadGuestMeetingRecord({
6545
+ api,
6546
+ repository,
6547
+ meetingId,
6548
+ meeting,
6549
+ roomToken: null,
6550
+ session: true
6551
+ }).then(
6552
+ (viaServer) => viaServer.restrictions.length < direct.restrictions.length ? viaServer : direct
6553
+ ) : direct
6554
+ )
6555
+ );
6532
6556
  void load.then((bundle) => {
6533
6557
  if (!live) return;
6534
6558
  setRecordReaderId(readerId);
@@ -6668,7 +6692,20 @@ function useGuestRecordAccess(meeting) {
6668
6692
 
6669
6693
  // src/react/components.tsx
6670
6694
  import { useEffect as useEffect4, useLayoutEffect, useMemo as useMemo3, useRef as useRef4, useState as useState4 } from "react";
6671
- import { ErrorBox } from "@ai-matrx/design-system";
6695
+ import {
6696
+ Alert,
6697
+ AlertDescription,
6698
+ Button,
6699
+ Checkbox,
6700
+ ErrorBox,
6701
+ Label,
6702
+ SegmentedControl,
6703
+ Select,
6704
+ SelectContent,
6705
+ SelectItem,
6706
+ SelectTrigger,
6707
+ SelectValue
6708
+ } from "@ai-matrx/design-system";
6672
6709
  import { avatarPaletteIndex, formatDurationMs as formatDurationMs4, getInitials } from "@ai-matrx/kit/format";
6673
6710
 
6674
6711
  // src/core/after.ts
@@ -7948,6 +7985,7 @@ function WhiteboardPanel({ onClose }) {
7948
7985
  function clock(ms2) {
7949
7986
  return formatDurationMs3(Math.ceil(ms2 / 1e3) * 1e3, { style: "clock" });
7950
7987
  }
7988
+ var BREAKOUT_EXPIRE_RETRY_MS = 5e3;
7951
7989
  function useNow(active) {
7952
7990
  const [now, setNow] = useState3(() => Date.now());
7953
7991
  useEffect3(() => {
@@ -8208,10 +8246,11 @@ function BreakoutBanner() {
8208
8246
  }, [moveAt, host, moveTo]);
8209
8247
  useEffect3(() => {
8210
8248
  if (open === null || remaining === null || remaining > 0) return;
8211
- if (expireAsked.current === open.id) return;
8212
- expireAsked.current = open.id;
8249
+ const asked = expireAsked.current;
8250
+ if (asked !== null && asked.id === open.id && now - asked.at < BREAKOUT_EXPIRE_RETRY_MS) return;
8251
+ expireAsked.current = { id: open.id, at: now };
8213
8252
  void act({ action: "breakout_expire" });
8214
- }, [open, remaining, act]);
8253
+ }, [open, remaining, act, now]);
8215
8254
  const broadcast = collab?.broadcast ?? null;
8216
8255
  const showBroadcast = broadcast !== null && broadcast.at !== dismissedBroadcast && open !== null;
8217
8256
  const room = open?.rooms.find((r) => r.id === breakoutRoomId) ?? null;
@@ -9519,38 +9558,40 @@ function EffectsSettings({ preview }) {
9519
9558
  }
9520
9559
  };
9521
9560
  return /* @__PURE__ */ jsxs3("section", { className: "mx-meet__section", "aria-label": "Effects", children: [
9522
- /* @__PURE__ */ jsxs3("label", { className: "mx-meet__toggle", children: [
9561
+ /* @__PURE__ */ jsxs3("div", { className: "mx-meet__check", children: [
9523
9562
  /* @__PURE__ */ jsx4(
9524
- "input",
9563
+ Checkbox,
9525
9564
  {
9526
- type: "checkbox",
9565
+ id: "mx-meet-noise",
9527
9566
  checked: effects.effects.noiseSuppression,
9528
- onChange: (event) => apply({ noiseSuppression: event.target.checked })
9567
+ onCheckedChange: (checked) => apply({ noiseSuppression: checked === true })
9529
9568
  }
9530
9569
  ),
9531
- "Noise suppression",
9532
- /* @__PURE__ */ jsx4("span", { className: "mx-meet__hint", children: "Filters keyboards, fans and background chatter." })
9570
+ /* @__PURE__ */ jsxs3("div", { className: "mx-meet__check-text", children: [
9571
+ /* @__PURE__ */ jsx4(Label, { htmlFor: "mx-meet-noise", children: "Noise suppression" }),
9572
+ /* @__PURE__ */ jsx4("span", { className: "mx-meet__hint", children: "Filters keyboards, fans and background chatter." })
9573
+ ] })
9533
9574
  ] }),
9534
9575
  supported ? /* @__PURE__ */ jsxs3("div", { className: "mx-meet__field", children: [
9535
- /* @__PURE__ */ jsx4("span", { className: "mx-meet__field-label", children: "Background" }),
9536
- /* @__PURE__ */ jsx4("div", { className: "mx-meet__pills", role: "radiogroup", "aria-label": "Background", children: ["none", "blur", "image"].map((kind) => /* @__PURE__ */ jsx4(
9537
- "button",
9576
+ /* @__PURE__ */ jsx4(Label, { children: "Background" }),
9577
+ /* @__PURE__ */ jsx4(
9578
+ SegmentedControl,
9538
9579
  {
9539
- type: "button",
9540
- role: "radio",
9541
- "aria-checked": effects.effects.background === kind,
9542
- className: effects.effects.background === kind ? "mx-meet__pill mx-meet__pill--on" : "mx-meet__pill",
9543
- onClick: () => {
9580
+ value: effects.effects.background,
9581
+ onValueChange: (kind) => {
9544
9582
  if (kind === "image") {
9545
9583
  fileRef.current?.click();
9546
9584
  return;
9547
9585
  }
9548
9586
  apply({ background: kind });
9549
9587
  },
9550
- children: kind === "none" ? "None" : kind === "blur" ? "Blur" : "Picture\u2026"
9551
- },
9552
- kind
9553
- )) }),
9588
+ data: [
9589
+ { value: "none", label: "None" },
9590
+ { value: "blur", label: "Blur" },
9591
+ { value: "image", label: "Picture\u2026" }
9592
+ ]
9593
+ }
9594
+ ),
9554
9595
  /* @__PURE__ */ jsx4(
9555
9596
  "input",
9556
9597
  {
@@ -9934,10 +9975,10 @@ function PreJoinBody({
9934
9975
  /* @__PURE__ */ jsxs3("div", { className: "mx-meet__field-row", children: [
9935
9976
  /* @__PURE__ */ jsx4(DevicePicker, { kind: "audiooutput", label: "Speaker" }),
9936
9977
  /* @__PURE__ */ jsxs3(
9937
- "button",
9978
+ Button,
9938
9979
  {
9939
9980
  type: "button",
9940
- className: "mx-meet__chip",
9981
+ variant: "outline",
9941
9982
  disabled: speakerTesting,
9942
9983
  onClick: () => {
9943
9984
  setSpeakerTesting(true);
@@ -9946,7 +9987,7 @@ function PreJoinBody({
9946
9987
  );
9947
9988
  },
9948
9989
  children: [
9949
- /* @__PURE__ */ jsx4(SpeakerIcon, { width: 14, height: 14 }),
9990
+ /* @__PURE__ */ jsx4(SpeakerIcon, {}),
9950
9991
  " ",
9951
9992
  speakerTesting ? "Playing\u2026" : "Test"
9952
9993
  ]
@@ -9955,35 +9996,36 @@ function PreJoinBody({
9955
9996
  ] }),
9956
9997
  /* @__PURE__ */ jsx4(DevicePicker, { kind: "videoinput", label: "Camera" }),
9957
9998
  /* @__PURE__ */ jsx4(EffectsSettings, { preview: preview.preview }),
9958
- recording.consentNotice !== null ? /* @__PURE__ */ jsx4("p", { className: "mx-meet__consent", children: recording.consentNotice }) : null,
9999
+ recording.consentNotice !== null ? /* @__PURE__ */ jsx4(Alert, { variant: "info", children: /* @__PURE__ */ jsx4(AlertDescription, { children: recording.consentNotice }) }) : null,
9959
10000
  /* @__PURE__ */ jsxs3("div", { className: "mx-meet__prejoin-actions", children: [
9960
- /* @__PURE__ */ jsxs3("label", { className: "mx-meet__toggle", children: [
10001
+ /* @__PURE__ */ jsxs3("div", { className: "mx-meet__check", children: [
9961
10002
  /* @__PURE__ */ jsx4(
9962
- "input",
10003
+ Checkbox,
9963
10004
  {
9964
- type: "checkbox",
10005
+ id: "mx-meet-join-video",
9965
10006
  checked: mode === "video",
9966
- onChange: (event) => setMode(event.target.checked ? "video" : "audio")
10007
+ onCheckedChange: (checked) => setMode(checked === true ? "video" : "audio")
9967
10008
  }
9968
10009
  ),
9969
- "Join with video"
10010
+ /* @__PURE__ */ jsx4(Label, { htmlFor: "mx-meet-join-video", children: "Join with video" })
9970
10011
  ] }),
9971
- /* @__PURE__ */ jsxs3("label", { className: "mx-meet__toggle", children: [
10012
+ /* @__PURE__ */ jsxs3("div", { className: "mx-meet__check", children: [
9972
10013
  /* @__PURE__ */ jsx4(
9973
- "input",
10014
+ Checkbox,
9974
10015
  {
9975
- type: "checkbox",
10016
+ id: "mx-meet-join-mic",
9976
10017
  checked: microphone,
9977
- onChange: (event) => setMicrophone(event.target.checked)
10018
+ onCheckedChange: (checked) => setMicrophone(checked === true)
9978
10019
  }
9979
10020
  ),
9980
- "Join with microphone"
10021
+ /* @__PURE__ */ jsx4(Label, { htmlFor: "mx-meet-join-mic", children: "Join with microphone" })
9981
10022
  ] }),
9982
10023
  /* @__PURE__ */ jsx4(
9983
- "button",
10024
+ Button,
9984
10025
  {
9985
10026
  type: "button",
9986
- className: "mx-meet__primary",
10027
+ className: "mx-meet__join",
10028
+ disabled: joining,
9987
10029
  onClick: () => {
9988
10030
  setJoining(true);
9989
10031
  preview.release();
@@ -10056,18 +10098,24 @@ function DevicePicker({
10056
10098
  const devices = useDeviceSnapshot();
10057
10099
  const list = devices?.devices[kind] ?? [];
10058
10100
  if (list.length === 0) return null;
10059
- return /* @__PURE__ */ jsxs3("label", { className: "mx-meet__field", children: [
10060
- /* @__PURE__ */ jsx4("span", { className: "mx-meet__field-label", children: label }),
10061
- /* @__PURE__ */ jsx4(
10062
- "select",
10101
+ const triggerId = `mx-meet-device-${kind}`;
10102
+ const valueOf = (deviceId2) => deviceId2.length > 0 ? deviceId2 : "default";
10103
+ const current = devices?.selected[kind] ?? host.devices.resolve(kind) ?? "";
10104
+ return /* @__PURE__ */ jsxs3("div", { className: "mx-meet__field", children: [
10105
+ /* @__PURE__ */ jsx4(Label, { htmlFor: triggerId, children: label }),
10106
+ /* @__PURE__ */ jsxs3(
10107
+ Select,
10063
10108
  {
10064
- className: "mx-meet__select",
10065
- value: devices?.selected[kind] ?? host.devices.resolve(kind) ?? "",
10066
- onChange: (event) => {
10067
- if (onPick !== void 0) onPick(kind, event.target.value);
10068
- else host.devices.select(kind, event.target.value);
10109
+ ...current.length > 0 ? { value: valueOf(current) } : {},
10110
+ onValueChange: (value) => {
10111
+ const deviceId2 = list.find((device) => valueOf(device.deviceId) === value)?.deviceId ?? value;
10112
+ if (onPick !== void 0) onPick(kind, deviceId2);
10113
+ else host.devices.select(kind, deviceId2);
10069
10114
  },
10070
- children: list.map((device) => /* @__PURE__ */ jsx4("option", { value: device.deviceId, children: device.label.length > 0 ? device.label : `${label} (name hidden)` }, device.deviceId))
10115
+ children: [
10116
+ /* @__PURE__ */ jsx4(SelectTrigger, { id: triggerId, "aria-label": label, children: /* @__PURE__ */ jsx4(SelectValue, { placeholder: label }) }),
10117
+ /* @__PURE__ */ jsx4(SelectContent, { children: list.map((device) => /* @__PURE__ */ jsx4(SelectItem, { value: valueOf(device.deviceId), children: device.label.length > 0 ? device.label : `${label} (name hidden)` }, device.deviceId || "default")) })
10118
+ ]
10071
10119
  }
10072
10120
  )
10073
10121
  ] });