@ai-matrx/meet 0.7.78 → 0.7.80

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.cjs CHANGED
@@ -83,6 +83,7 @@ __export(react_exports, {
83
83
  LockIcon: () => LockIcon,
84
84
  MEETING_INVITE_KIND: () => MEETING_INVITE_KIND,
85
85
  MEETING_INVITE_VERSION: () => MEETING_INVITE_VERSION,
86
+ MEETING_RECORD_GRACE_MS: () => MEETING_RECORD_GRACE_MS,
86
87
  MEET_ANSWER_CONTEXT_EVENT: () => MEET_ANSWER_CONTEXT_EVENT,
87
88
  MEET_ANSWER_EVENTS: () => MEET_ANSWER_EVENTS,
88
89
  MEET_EVENTS: () => MEET_EVENTS,
@@ -160,6 +161,7 @@ __export(react_exports, {
160
161
  asParticipantIdentity: () => asParticipantIdentity,
161
162
  asRoomName: () => asRoomName,
162
163
  asUserId: () => asUserId,
164
+ assembleMeetingRecord: () => assembleMeetingRecord,
163
165
  assignedRoom: () => assignedRoom,
164
166
  attendanceCsv: () => attendanceCsv,
165
167
  attendanceReport: () => attendanceReport,
@@ -189,6 +191,7 @@ __export(react_exports, {
189
191
  createMeetRepository: () => createMeetRepository,
190
192
  createMeetStore: () => createMeetStore,
191
193
  createMeetingInviteHandler: () => createMeetingInviteHandler,
194
+ createMeetingRecordCache: () => createMeetingRecordCache,
192
195
  createMeetingSession: () => createMeetingSession,
193
196
  createMemoryDeviceStorage: () => createMemoryDeviceStorage,
194
197
  createRecordingController: () => createRecordingController,
@@ -223,12 +226,14 @@ __export(react_exports, {
223
226
  loadMeetingActivities: () => loadMeetingActivities,
224
227
  loadMeetingChat: () => loadMeetingChat,
225
228
  loadMeetingRecord: () => loadMeetingRecord,
229
+ loadMeetingRecordParts: () => loadMeetingRecordParts,
226
230
  mapConnectionState: () => mapConnectionState,
227
231
  mapQuality: () => mapQuality,
228
232
  meetingInviteAction: () => meetingInviteAction,
229
233
  meetingLength: () => meetingLength,
230
234
  meetingLink: () => meetingLink,
231
235
  meetingPath: () => meetingPath,
236
+ meetingRecordCache: () => meetingRecordCache,
232
237
  meetingSlug: () => meetingSlug,
233
238
  meetingTopic: () => meetingTopic,
234
239
  meetingWhen: () => meetingWhen,
@@ -2205,13 +2210,13 @@ function mergeCaption(list, caption) {
2205
2210
  return list.map((entry, position) => position === index ? caption : entry);
2206
2211
  }
2207
2212
  function mergeRecord(list, record) {
2208
- const byId = list.findIndex((entry) => entry.source === "record" && entry.id === record.id);
2209
- if (byId !== -1) {
2210
- const existing = list[byId];
2213
+ const byId2 = list.findIndex((entry) => entry.source === "record" && entry.id === record.id);
2214
+ if (byId2 !== -1) {
2215
+ const existing = list[byId2];
2211
2216
  if (existing !== void 0 && existing.text === record.text && existing.final === record.final) {
2212
2217
  return list;
2213
2218
  }
2214
- return list.map((entry, position) => position === byId ? record : entry);
2219
+ return list.map((entry, position) => position === byId2 ? record : entry);
2215
2220
  }
2216
2221
  const captionIndex = list.findIndex((entry) => supersedes(record, entry));
2217
2222
  if (captionIndex === -1) return [...list, record];
@@ -5786,49 +5791,7 @@ function useLocalUserId() {
5786
5791
 
5787
5792
  // src/react/hooks.ts
5788
5793
  var import_react3 = require("react");
5789
-
5790
- // src/core/layout.ts
5791
- var DEFAULT_PAGE_SIZE = 12;
5792
- function orderParticipants(participants, activeSpeakers) {
5793
- const speakerRank = /* @__PURE__ */ new Map();
5794
- activeSpeakers.forEach((identity, index) => speakerRank.set(identity, index));
5795
- return [...participants].sort((a, b) => {
5796
- const speakerA = speakerRank.get(a.identity) ?? Number.MAX_SAFE_INTEGER;
5797
- const speakerB = speakerRank.get(b.identity) ?? Number.MAX_SAFE_INTEGER;
5798
- if (speakerA !== speakerB) return speakerA - speakerB;
5799
- if (a.handRaised !== b.handRaised) return a.handRaised ? -1 : 1;
5800
- if (a.cameraEnabled !== b.cameraEnabled) return a.cameraEnabled ? -1 : 1;
5801
- if (a.joinedAt !== b.joinedAt) return a.joinedAt < b.joinedAt ? -1 : 1;
5802
- return a.identity < b.identity ? -1 : 1;
5803
- });
5804
- }
5805
- function computeStage(args) {
5806
- const pageSize = Math.max(1, args.pageSize ?? DEFAULT_PAGE_SIZE);
5807
- const ordered = orderParticipants(args.participants, args.activeSpeakers);
5808
- const sharer = ordered.find((participant) => participant.screenSharing) ?? null;
5809
- const pinnedParticipant = args.pinned === null ? null : ordered.find((participant) => participant.identity === args.pinned) ?? null;
5810
- const spotlit = args.spotlight == null ? null : ordered.find((participant) => participant.identity === args.spotlight) ?? null;
5811
- const feature = sharer !== null ? sharer : pinnedParticipant !== null && args.layout !== "grid" ? pinnedParticipant : spotlit !== null ? spotlit : args.layout === "grid" ? null : ordered[0] ?? null;
5812
- const rest = feature === null ? ordered : ordered.filter((participant) => participant.identity !== feature.identity);
5813
- const pageCount = Math.max(1, Math.ceil(rest.length / pageSize));
5814
- const page = Math.min(Math.max(0, args.page ?? 0), pageCount - 1);
5815
- const tiles = rest.slice(page * pageSize, page * pageSize + pageSize);
5816
- return {
5817
- feature,
5818
- tiles,
5819
- overflow: rest.length - tiles.length,
5820
- page,
5821
- pageCount,
5822
- presenting: sharer !== null
5823
- };
5824
- }
5825
- function gridColumns(count2) {
5826
- if (count2 <= 1) return 1;
5827
- if (count2 <= 4) return 2;
5828
- if (count2 <= 9) return 3;
5829
- if (count2 <= 16) return 4;
5830
- return 5;
5831
- }
5794
+ var import_react4 = require("@ai-matrx/realtime/react");
5832
5795
 
5833
5796
  // src/core/record.ts
5834
5797
  var import_format = require("@ai-matrx/kit/format");
@@ -5852,6 +5815,9 @@ function restrictionFor(part, error) {
5852
5815
  };
5853
5816
  }
5854
5817
  async function loadMeetingRecord(repository, meetingId, meeting, reader = { userId: null }) {
5818
+ return assembleMeetingRecord(meeting, await loadMeetingRecordParts(repository, meetingId, reader));
5819
+ }
5820
+ async function loadMeetingRecordParts(repository, meetingId, reader = { userId: null }) {
5855
5821
  const restrictions = [];
5856
5822
  async function attempt(part, run, empty) {
5857
5823
  if (reader.userId === null) {
@@ -5879,6 +5845,10 @@ async function loadMeetingRecord(repository, meetingId, meeting, reader = { user
5879
5845
  []
5880
5846
  )
5881
5847
  ]);
5848
+ return { attendees, notes, transcript, recordings, restrictions };
5849
+ }
5850
+ function assembleMeetingRecord(meeting, parts) {
5851
+ const { attendees, notes, transcript, recordings, restrictions } = parts;
5882
5852
  return {
5883
5853
  meeting,
5884
5854
  attendees,
@@ -6224,6 +6194,253 @@ async function setGuestRecordAccess(args) {
6224
6194
  }
6225
6195
  var GUEST_RECORD_NOT_SHARED = NOT_SHARED;
6226
6196
 
6197
+ // src/core/record-cache.ts
6198
+ var MEETING_RECORD_GRACE_MS = 6e4;
6199
+ var INITIAL = { loading: true, record: null, failure: null };
6200
+ function upsert2(rows, row, removedId, order) {
6201
+ const id = row?.id ?? removedId;
6202
+ if (id === void 0) return rows;
6203
+ const rest = rows.filter((existing) => existing.id !== id);
6204
+ return row === null ? rest : [...rest, row].sort(order);
6205
+ }
6206
+ var byId = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
6207
+ var ascending = (a, b) => a === b ? 0 : a === null ? 1 : b === null ? -1 : a < b ? -1 : 1;
6208
+ function createMeetingRecordCache(options = {}) {
6209
+ const graceMs = options.graceMs ?? MEETING_RECORD_GRACE_MS;
6210
+ const entries = /* @__PURE__ */ new Map();
6211
+ const listeners = /* @__PURE__ */ new Set();
6212
+ const notify = () => {
6213
+ for (const listener of listeners) listener();
6214
+ };
6215
+ const entryOf = (key) => {
6216
+ let entry = entries.get(key);
6217
+ if (!entry) {
6218
+ entry = {
6219
+ state: INITIAL,
6220
+ meeting: null,
6221
+ parts: null,
6222
+ fresh: false,
6223
+ seq: 0,
6224
+ inflight: null,
6225
+ holders: 0,
6226
+ close: null,
6227
+ owner: null,
6228
+ grace: null
6229
+ };
6230
+ entries.set(key, entry);
6231
+ }
6232
+ return entry;
6233
+ };
6234
+ const assemble = (entry) => {
6235
+ if (entry.meeting === null || entry.parts === null) return;
6236
+ entry.state = { loading: false, failure: null, record: assembleMeetingRecord(entry.meeting, entry.parts) };
6237
+ notify();
6238
+ };
6239
+ return {
6240
+ state: (key) => key === null ? INITIAL : entries.get(key)?.state ?? INITIAL,
6241
+ subscribe(listener) {
6242
+ listeners.add(listener);
6243
+ return () => {
6244
+ listeners.delete(listener);
6245
+ };
6246
+ },
6247
+ ensure(key, read, { force = false } = {}) {
6248
+ const entry = entryOf(key);
6249
+ if (!force) {
6250
+ if (entry.inflight !== null) return entry.inflight;
6251
+ if (entry.fresh) return Promise.resolve();
6252
+ }
6253
+ const seq = ++entry.seq;
6254
+ if (entry.state.failure !== null) {
6255
+ entry.state = { ...entry.state, loading: true, failure: null };
6256
+ notify();
6257
+ }
6258
+ const request = read().then(
6259
+ (answer) => {
6260
+ if (seq !== entry.seq) return;
6261
+ entry.fresh = true;
6262
+ if (answer.kind === "parts") {
6263
+ entry.meeting = answer.meeting;
6264
+ entry.parts = answer.parts;
6265
+ assemble(entry);
6266
+ } else {
6267
+ entry.meeting = answer.bundle.meeting;
6268
+ entry.parts = null;
6269
+ entry.state = { loading: false, failure: null, record: answer.bundle };
6270
+ notify();
6271
+ }
6272
+ },
6273
+ (thrown) => {
6274
+ if (seq !== entry.seq) return;
6275
+ entry.fresh = false;
6276
+ entry.state = {
6277
+ loading: false,
6278
+ record: entry.state.record,
6279
+ failure: {
6280
+ message: thrown?.message ?? String(thrown),
6281
+ remedy: thrown?.remedy ?? "The meeting record could not be read. Reload the page; nothing was changed."
6282
+ }
6283
+ };
6284
+ notify();
6285
+ }
6286
+ );
6287
+ const tracked = request.finally(() => {
6288
+ if (entry.inflight === tracked) entry.inflight = null;
6289
+ });
6290
+ entry.inflight = tracked;
6291
+ return tracked;
6292
+ },
6293
+ setMeeting(key, meeting) {
6294
+ const entry = entries.get(key);
6295
+ if (!entry || entry.meeting === meeting) return;
6296
+ if (entry.parts !== null) {
6297
+ entry.meeting = meeting;
6298
+ assemble(entry);
6299
+ } else if (entry.state.record !== null) {
6300
+ entry.meeting = meeting;
6301
+ entry.state = { ...entry.state, record: { ...entry.state.record, meeting } };
6302
+ notify();
6303
+ }
6304
+ },
6305
+ apply(key, change) {
6306
+ const entry = entries.get(key);
6307
+ const parts = entry?.parts;
6308
+ if (!entry || !parts) return;
6309
+ const refused = parts.restrictions.some((r) => r.part === change.part);
6310
+ if (refused) return;
6311
+ switch (change.part) {
6312
+ case "transcript":
6313
+ entry.parts = {
6314
+ ...parts,
6315
+ transcript: upsert2(
6316
+ parts.transcript,
6317
+ change.row,
6318
+ change.removedId,
6319
+ (a, b) => ascending(a.startedAt, b.startedAt) || byId(a, b)
6320
+ )
6321
+ };
6322
+ break;
6323
+ case "notes":
6324
+ entry.parts = {
6325
+ ...parts,
6326
+ notes: upsert2(
6327
+ parts.notes,
6328
+ change.row,
6329
+ change.removedId,
6330
+ (a, b) => ascending(a.createdAt, b.createdAt) || byId(a, b)
6331
+ )
6332
+ };
6333
+ break;
6334
+ case "attendance":
6335
+ entry.parts = {
6336
+ ...parts,
6337
+ attendees: upsert2(
6338
+ parts.attendees,
6339
+ change.row,
6340
+ change.removedId,
6341
+ (a, b) => ascending(a.joinedAt, b.joinedAt) || byId(a, b)
6342
+ )
6343
+ };
6344
+ break;
6345
+ case "recording":
6346
+ entry.parts = {
6347
+ ...parts,
6348
+ // Newest first, as the read orders them.
6349
+ recordings: upsert2(
6350
+ parts.recordings,
6351
+ change.row,
6352
+ change.removedId,
6353
+ (a, b) => ascending(b.startedAt, a.startedAt) || byId(a, b)
6354
+ )
6355
+ };
6356
+ break;
6357
+ }
6358
+ assemble(entry);
6359
+ },
6360
+ hold(key, open, owner = null) {
6361
+ const entry = entryOf(key);
6362
+ if (entry.grace !== null) {
6363
+ clearTimeout(entry.grace);
6364
+ entry.grace = null;
6365
+ }
6366
+ if (entry.close !== null && entry.owner !== owner) {
6367
+ entry.close();
6368
+ entry.close = null;
6369
+ }
6370
+ if (entry.close === null) {
6371
+ entry.close = open();
6372
+ entry.owner = owner;
6373
+ }
6374
+ entry.holders += 1;
6375
+ let released = false;
6376
+ return () => {
6377
+ if (released) return;
6378
+ released = true;
6379
+ entry.holders -= 1;
6380
+ if (entry.holders > 0) return;
6381
+ entry.grace = setTimeout(() => {
6382
+ entry.grace = null;
6383
+ if (entry.holders > 0) return;
6384
+ entry.close?.();
6385
+ entry.close = null;
6386
+ entry.fresh = false;
6387
+ }, graceMs);
6388
+ };
6389
+ },
6390
+ reset() {
6391
+ for (const entry of entries.values()) {
6392
+ if (entry.grace !== null) clearTimeout(entry.grace);
6393
+ entry.close?.();
6394
+ }
6395
+ entries.clear();
6396
+ notify();
6397
+ }
6398
+ };
6399
+ }
6400
+
6401
+ // src/core/layout.ts
6402
+ var DEFAULT_PAGE_SIZE = 12;
6403
+ function orderParticipants(participants, activeSpeakers) {
6404
+ const speakerRank = /* @__PURE__ */ new Map();
6405
+ activeSpeakers.forEach((identity, index) => speakerRank.set(identity, index));
6406
+ return [...participants].sort((a, b) => {
6407
+ const speakerA = speakerRank.get(a.identity) ?? Number.MAX_SAFE_INTEGER;
6408
+ const speakerB = speakerRank.get(b.identity) ?? Number.MAX_SAFE_INTEGER;
6409
+ if (speakerA !== speakerB) return speakerA - speakerB;
6410
+ if (a.handRaised !== b.handRaised) return a.handRaised ? -1 : 1;
6411
+ if (a.cameraEnabled !== b.cameraEnabled) return a.cameraEnabled ? -1 : 1;
6412
+ if (a.joinedAt !== b.joinedAt) return a.joinedAt < b.joinedAt ? -1 : 1;
6413
+ return a.identity < b.identity ? -1 : 1;
6414
+ });
6415
+ }
6416
+ function computeStage(args) {
6417
+ const pageSize = Math.max(1, args.pageSize ?? DEFAULT_PAGE_SIZE);
6418
+ const ordered = orderParticipants(args.participants, args.activeSpeakers);
6419
+ const sharer = ordered.find((participant) => participant.screenSharing) ?? null;
6420
+ const pinnedParticipant = args.pinned === null ? null : ordered.find((participant) => participant.identity === args.pinned) ?? null;
6421
+ const spotlit = args.spotlight == null ? null : ordered.find((participant) => participant.identity === args.spotlight) ?? null;
6422
+ const feature = sharer !== null ? sharer : pinnedParticipant !== null && args.layout !== "grid" ? pinnedParticipant : spotlit !== null ? spotlit : args.layout === "grid" ? null : ordered[0] ?? null;
6423
+ const rest = feature === null ? ordered : ordered.filter((participant) => participant.identity !== feature.identity);
6424
+ const pageCount = Math.max(1, Math.ceil(rest.length / pageSize));
6425
+ const page = Math.min(Math.max(0, args.page ?? 0), pageCount - 1);
6426
+ const tiles = rest.slice(page * pageSize, page * pageSize + pageSize);
6427
+ return {
6428
+ feature,
6429
+ tiles,
6430
+ overflow: rest.length - tiles.length,
6431
+ page,
6432
+ pageCount,
6433
+ presenting: sharer !== null
6434
+ };
6435
+ }
6436
+ function gridColumns(count2) {
6437
+ if (count2 <= 1) return 1;
6438
+ if (count2 <= 4) return 2;
6439
+ if (count2 <= 9) return 3;
6440
+ if (count2 <= 16) return 4;
6441
+ return 5;
6442
+ }
6443
+
6227
6444
  // src/react/hooks.ts
6228
6445
  var EMPTY_NAMES = {};
6229
6446
  var NO_RUNTIME_REMEDY = "Render this inside a <MeetProvider> that has a Supabase client, an organization id and an identity (userId or guestName). The provider stays inert until all three are real, and while it is inert this package's components render nothing rather than a dead control.";
@@ -6809,16 +7026,22 @@ function useMeeting() {
6809
7026
  function useMeetingId() {
6810
7027
  return useMeetSnapshot()?.meeting?.id ?? null;
6811
7028
  }
7029
+ var RECORD_CACHE_KEY = /* @__PURE__ */ Symbol.for("@ai-matrx/meet/meeting-record-cache");
7030
+ function meetingRecordCache() {
7031
+ const holder = globalThis;
7032
+ let cache = holder[RECORD_CACHE_KEY];
7033
+ if (cache === void 0) {
7034
+ cache = createMeetingRecordCache();
7035
+ holder[RECORD_CACHE_KEY] = cache;
7036
+ }
7037
+ return cache;
7038
+ }
7039
+ var rowIdOf = (row) => typeof row["id"] === "string" ? row["id"] : void 0;
6812
7040
  function useMeetingRecord(meeting) {
6813
7041
  const host = useMeetHost();
6814
7042
  const repository = host?.repository ?? null;
6815
7043
  const ai = host?.ai ?? null;
6816
7044
  const readerId = host?.identity.userId ?? null;
6817
- const [recordReaderId, setRecordReaderId] = (0, import_react3.useState)(readerId);
6818
- const [record, setRecord] = (0, import_react3.useState)(null);
6819
- const [loading, setLoading] = (0, import_react3.useState)(true);
6820
- const [failure, setFailure] = (0, import_react3.useState)(null);
6821
- const [nonce, setNonce] = (0, import_react3.useState)(0);
6822
7045
  const [asking, setAsking] = (0, import_react3.useState)(false);
6823
7046
  const [answer, setAnswer] = (0, import_react3.useState)(null);
6824
7047
  const [coverage, setCoverage] = (0, import_react3.useState)(null);
@@ -6827,57 +7050,107 @@ function useMeetingRecord(meeting) {
6827
7050
  const api = host?.api ?? null;
6828
7051
  const tokens = host?.tokens ?? null;
6829
7052
  const roomName = meeting?.roomName ?? null;
6830
- (0, import_react3.useEffect)(() => {
6831
- if (repository === null || meeting === null || meetingId === null) return void 0;
6832
- let live = true;
6833
- setLoading(true);
6834
- setFailure(null);
6835
- setRecord(null);
6836
- const load = readerId === null && api !== null && tokens !== null && roomName !== null ? loadGuestMeetingRecord({
6837
- api,
6838
- repository,
6839
- meetingId,
6840
- meeting,
6841
- roomToken: tokens.remembered(roomName)
6842
- }) : loadMeetingRecord(repository, meetingId, meeting, { userId: readerId }).then(
6843
- (direct) => (
6844
- // A SIGNED-IN OUTSIDER IS NOT A STRANGER (2026-10-01). The database
6845
- // answers members and invitees; a person from another organization
6846
- // the host admitted — or a guest who later made an account — reads
6847
- // the guest share through the server, with their session as the pass.
6848
- api !== null && direct.restrictions.some((part) => part.part === "notes") ? loadGuestMeetingRecord({
7053
+ const manager = (0, import_react4.useRealtimeManager)();
7054
+ const cache = meetingRecordCache();
7055
+ const key = repository !== null && meetingId !== null ? `${readerId ?? `guest:${roomName ?? "-"}`}|${meetingId}` : null;
7056
+ const state = (0, import_react3.useSyncExternalStore)(
7057
+ cache.subscribe,
7058
+ () => cache.state(key),
7059
+ () => cache.state(null)
7060
+ );
7061
+ const readRef = (0, import_react3.useRef)(() => Promise.reject(new Error("not ready")));
7062
+ readRef.current = async () => {
7063
+ if (repository === null || meeting === null || meetingId === null) throw new Error("No meeting to read.");
7064
+ if (readerId === null && api !== null && tokens !== null && roomName !== null) {
7065
+ return {
7066
+ kind: "bundle",
7067
+ bundle: await loadGuestMeetingRecord({
6849
7068
  api,
6850
7069
  repository,
6851
7070
  meetingId,
6852
7071
  meeting,
6853
- roomToken: null,
6854
- session: true
6855
- }).then(
6856
- (viaServer) => viaServer.restrictions.length < direct.restrictions.length ? viaServer : direct
6857
- ) : direct
6858
- )
6859
- );
6860
- void load.then((bundle) => {
6861
- if (!live) return;
6862
- setRecordReaderId(readerId);
6863
- setRecord(bundle);
6864
- setLoading(false);
6865
- }).catch((thrown) => {
6866
- if (!live) return;
6867
- setFailure({
6868
- message: thrown.message,
6869
- remedy: thrown.remedy ?? "The meeting record could not be read. Reload the page; nothing was changed."
7072
+ roomToken: tokens.remembered(roomName)
7073
+ })
7074
+ };
7075
+ }
7076
+ const parts = await loadMeetingRecordParts(repository, meetingId, { userId: readerId });
7077
+ if (api !== null && parts.restrictions.some((part) => part.part === "notes")) {
7078
+ const viaServer = await loadGuestMeetingRecord({
7079
+ api,
7080
+ repository,
7081
+ meetingId,
7082
+ meeting,
7083
+ roomToken: null,
7084
+ session: true
6870
7085
  });
6871
- setLoading(false);
7086
+ if (viaServer.restrictions.length < parts.restrictions.length) return { kind: "bundle", bundle: viaServer };
7087
+ }
7088
+ return { kind: "parts", meeting, parts };
7089
+ };
7090
+ (0, import_react3.useEffect)(() => {
7091
+ if (key === null) return;
7092
+ void cache.ensure(key, () => readRef.current());
7093
+ }, [cache, key]);
7094
+ (0, import_react3.useEffect)(() => {
7095
+ if (key !== null && meeting !== null) cache.setMeeting(key, meeting);
7096
+ }, [cache, key, meeting]);
7097
+ (0, import_react3.useEffect)(() => {
7098
+ if (key === null || manager === null || repository === null || meetingId === null || readerId === null) {
7099
+ return void 0;
7100
+ }
7101
+ const repo = repository;
7102
+ const binding = (table, apply) => ({
7103
+ event: "*",
7104
+ schema: repo.schema,
7105
+ table,
7106
+ filter: `meeting_id=eq.${meetingId}`,
7107
+ rowId: rowIdOf,
7108
+ onChange: ({ row, payload }) => {
7109
+ if (row === null) return;
7110
+ const gone = payload.eventType === "DELETE";
7111
+ apply(gone ? null : row, gone ? rowIdOf(row) : void 0);
7112
+ }
6872
7113
  });
6873
- return () => {
6874
- live = false;
6875
- };
6876
- }, [repository, meeting, meetingId, readerId, api, tokens, roomName, nonce]);
7114
+ return cache.hold(
7115
+ key,
7116
+ () => {
7117
+ const handle = manager.open({
7118
+ topic: meetingTopic(meetingId),
7119
+ postgresChanges: [
7120
+ binding(
7121
+ repo.tables.notes,
7122
+ (row, removedId) => cache.apply(key, { part: "notes", row: row && repo.projectNote(row), removedId })
7123
+ ),
7124
+ binding(
7125
+ repo.tables.transcripts,
7126
+ (row, removedId) => cache.apply(key, { part: "transcript", row: row && repo.projectSegment(row), removedId })
7127
+ ),
7128
+ binding(
7129
+ repo.tables.participants,
7130
+ (row, removedId) => cache.apply(key, { part: "attendance", row: row && repo.projectAttendee(row), removedId })
7131
+ ),
7132
+ binding(
7133
+ repo.tables.recordings,
7134
+ (row, removedId) => (
7135
+ // An archived recording is off the record (the read filters it the same way).
7136
+ row !== null && row["deleted_at"] != null ? cache.apply(key, { part: "recording", row: null, removedId: rowIdOf(row) }) : cache.apply(key, { part: "recording", row: row && repo.projectRecording(row), removedId })
7137
+ )
7138
+ )
7139
+ ],
7140
+ // Realtime has no replay: a socket that blipped reads the record again.
7141
+ onBackfill: async () => {
7142
+ await cache.ensure(key, () => readRef.current(), { force: true });
7143
+ }
7144
+ });
7145
+ return () => handle.close();
7146
+ },
7147
+ manager
7148
+ );
7149
+ }, [cache, key, manager, repository, meetingId, readerId]);
6877
7150
  return {
6878
- loading: loading && failure === null,
6879
- record: recordReaderId === readerId ? record : null,
6880
- failure,
7151
+ loading: state.loading && state.failure === null,
7152
+ record: state.record,
7153
+ failure: state.failure,
6881
7154
  asking,
6882
7155
  answer,
6883
7156
  coverage,
@@ -6908,7 +7181,9 @@ function useMeetingRecord(meeting) {
6908
7181
  },
6909
7182
  [ai, meetingId]
6910
7183
  ),
6911
- reload: (0, import_react3.useCallback)(() => setNonce((value) => value + 1), [])
7184
+ reload: (0, import_react3.useCallback)(() => {
7185
+ if (key !== null) void cache.ensure(key, () => readRef.current(), { force: true });
7186
+ }, [cache, key])
6912
7187
  };
6913
7188
  }
6914
7189
  function useMeetings(limit = 25, organizationFilter = null) {
@@ -6995,11 +7270,12 @@ function useGuestRecordAccess(meeting) {
6995
7270
  }
6996
7271
 
6997
7272
  // src/react/components.tsx
6998
- var import_react5 = require("react");
7273
+ var import_react6 = require("react");
6999
7274
  var import_design_system = require("@ai-matrx/design-system");
7000
7275
  var import_format4 = require("@ai-matrx/kit/format");
7001
7276
 
7002
7277
  // src/core/after.ts
7278
+ var import_delimited = require("@ai-matrx/kit/delimited");
7003
7279
  var import_format2 = require("@ai-matrx/kit/format");
7004
7280
  function kindOf(person) {
7005
7281
  if (person.isAgent) return "assistant";
@@ -7138,11 +7414,6 @@ function attendanceTotals(rows) {
7138
7414
  averageMs: timed.length === 0 ? null : Math.round(timed.reduce((sum, row) => sum + (row.durationMs ?? 0), 0) / timed.length)
7139
7415
  };
7140
7416
  }
7141
- function csvCell(value) {
7142
- if (value === null) return "";
7143
- const text3 = String(value);
7144
- return /[",\n\r]/.test(text3) ? `"${text3.replace(/"/g, '""')}"` : text3;
7145
- }
7146
7417
  var ATTENDANCE_CSV_HEADER = [
7147
7418
  "Meeting",
7148
7419
  "Meeting started",
@@ -7154,11 +7425,11 @@ var ATTENDANCE_CSV_HEADER = [
7154
7425
  "Admission"
7155
7426
  ];
7156
7427
  function attendanceCsv(reports) {
7157
- const lines = [ATTENDANCE_CSV_HEADER.join(",")];
7158
- for (const { meeting, rows } of reports) {
7159
- for (const row of rows) {
7160
- lines.push(
7161
- [
7428
+ return (0, import_delimited.toDelimited)(
7429
+ [
7430
+ ATTENDANCE_CSV_HEADER,
7431
+ ...reports.flatMap(
7432
+ ({ meeting, rows }) => rows.map((row) => [
7162
7433
  meeting.title,
7163
7434
  meeting.startedAt ?? meeting.scheduledFor,
7164
7435
  row.name,
@@ -7167,12 +7438,11 @@ function attendanceCsv(reports) {
7167
7438
  row.leftAt,
7168
7439
  row.durationMs === null ? null : Math.round(row.durationMs / 6e3) / 10,
7169
7440
  row.admission
7170
- ].map(csvCell).join(",")
7171
- );
7172
- }
7173
- }
7174
- return `${lines.join("\r\n")}\r
7175
- `;
7441
+ ])
7442
+ )
7443
+ ],
7444
+ { lineEnding: "\r\n", trailingNewline: true }
7445
+ );
7176
7446
  }
7177
7447
  function recordingOffsetMs(at, recording) {
7178
7448
  if (recording === null || recording.state !== "available") return null;
@@ -7440,7 +7710,7 @@ function countLabel(count2, singular, plural = `${singular}s`) {
7440
7710
  }
7441
7711
 
7442
7712
  // src/react/collab.tsx
7443
- var import_react4 = require("react");
7713
+ var import_react5 = require("react");
7444
7714
  var import_format3 = require("@ai-matrx/kit/format");
7445
7715
 
7446
7716
  // src/react/icons.tsx
@@ -7717,8 +7987,8 @@ function useCollab() {
7717
7987
  };
7718
7988
  }
7719
7989
  function useNotice() {
7720
- const [notice, setNotice] = (0, import_react4.useState)(null);
7721
- (0, import_react4.useEffect)(() => {
7990
+ const [notice, setNotice] = (0, import_react5.useState)(null);
7991
+ (0, import_react5.useEffect)(() => {
7722
7992
  if (notice === null) return void 0;
7723
7993
  const timer = setTimeout(() => setNotice(null), notice.failed ? 7e3 : 3e3);
7724
7994
  return () => clearTimeout(timer);
@@ -7764,7 +8034,7 @@ function ActivitiesControl({
7764
8034
  active,
7765
8035
  onOpen
7766
8036
  }) {
7767
- const [open, setOpen] = (0, import_react4.useState)(false);
8037
+ const [open, setOpen] = (0, import_react5.useState)(false);
7768
8038
  const available = useAvailableActivities();
7769
8039
  const attention = useActivityAttention();
7770
8040
  if (available.length === 0) return null;
@@ -7792,11 +8062,11 @@ function ActivitiesControl({
7792
8062
  }
7793
8063
  function PollComposer({ maxOptions, onDone }) {
7794
8064
  const { act } = useCollab();
7795
- const [question, setQuestion] = (0, import_react4.useState)("");
7796
- const [options, setOptions] = (0, import_react4.useState)(["", ""]);
7797
- const [multiple, setMultiple] = (0, import_react4.useState)(false);
7798
- const [anonymous, setAnonymous] = (0, import_react4.useState)(false);
7799
- const [busy, setBusy] = (0, import_react4.useState)(false);
8065
+ const [question, setQuestion] = (0, import_react5.useState)("");
8066
+ const [options, setOptions] = (0, import_react5.useState)(["", ""]);
8067
+ const [multiple, setMultiple] = (0, import_react5.useState)(false);
8068
+ const [anonymous, setAnonymous] = (0, import_react5.useState)(false);
8069
+ const [busy, setBusy] = (0, import_react5.useState)(false);
7800
8070
  const [notice, show] = useNotice();
7801
8071
  const filled = options.map((o) => o.trim()).filter(Boolean);
7802
8072
  const ready = question.trim().length > 0 && filled.length >= 2;
@@ -7877,9 +8147,9 @@ function PollComposer({ maxOptions, onDone }) {
7877
8147
  }
7878
8148
  function PollCard({ poll }) {
7879
8149
  const { act, isHost } = useCollab();
7880
- const [picked, setPicked] = (0, import_react4.useState)(poll.myVote.slice());
8150
+ const [picked, setPicked] = (0, import_react5.useState)(poll.myVote.slice());
7881
8151
  const [notice, show] = useNotice();
7882
- (0, import_react4.useEffect)(() => setPicked(poll.myVote.slice()), [poll.myVote]);
8152
+ (0, import_react5.useEffect)(() => setPicked(poll.myVote.slice()), [poll.myVote]);
7883
8153
  const counts = poll.options.some((o) => o.votes !== void 0);
7884
8154
  const total = poll.options.reduce((sum, o) => sum + (o.votes ?? 0), 0);
7885
8155
  const canVote = poll.state === "live";
@@ -7948,7 +8218,7 @@ function PollCard({ poll }) {
7948
8218
  }
7949
8219
  function PollsPanel({ onClose }) {
7950
8220
  const { collab, isHost } = useCollab();
7951
- const [composing, setComposing] = (0, import_react4.useState)(false);
8221
+ const [composing, setComposing] = (0, import_react5.useState)(false);
7952
8222
  const polls = collab?.polls ?? [];
7953
8223
  const maxOptions = collab?.settings?.pollMaxOptions ?? 10;
7954
8224
  const ordered = [...polls].reverse();
@@ -7962,8 +8232,8 @@ function PollsPanel({ onClose }) {
7962
8232
  }
7963
8233
  function QuestionCard({ question }) {
7964
8234
  const { act, isHost } = useCollab();
7965
- const [answering, setAnswering] = (0, import_react4.useState)(false);
7966
- const [reply, setReply] = (0, import_react4.useState)("");
8235
+ const [answering, setAnswering] = (0, import_react5.useState)(false);
8236
+ const [reply, setReply] = (0, import_react5.useState)("");
7967
8237
  const [notice, show] = useNotice();
7968
8238
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { className: `mx-meet__collab-card mx-meet__question mx-meet__question--${question.state}`, children: [
7969
8239
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__question-row", children: [
@@ -8041,9 +8311,9 @@ function QuestionCard({ question }) {
8041
8311
  }
8042
8312
  function QaPanel({ onClose }) {
8043
8313
  const { collab, act } = useCollab();
8044
- const [draft, setDraft] = (0, import_react4.useState)("");
8045
- const [anonymous, setAnonymous] = (0, import_react4.useState)(false);
8046
- const [order, setOrder] = (0, import_react4.useState)("popular");
8314
+ const [draft, setDraft] = (0, import_react5.useState)("");
8315
+ const [anonymous, setAnonymous] = (0, import_react5.useState)(false);
8316
+ const [order, setOrder] = (0, import_react5.useState)("popular");
8047
8317
  const [notice, show] = useNotice();
8048
8318
  const allowAnonymous = collab?.settings?.qaAnonymousAllowed === true;
8049
8319
  const questions = [...collab?.questions ?? []];
@@ -8132,12 +8402,12 @@ function StrokeView({ stroke, onPick }) {
8132
8402
  }
8133
8403
  function WhiteboardPanel({ onClose }) {
8134
8404
  const { collab, act, isHost, localIdentity } = useCollab();
8135
- const [tool, setTool] = (0, import_react4.useState)("pen");
8136
- const [color, setColor] = (0, import_react4.useState)(INK[0]);
8137
- const [drawing, setDrawing] = (0, import_react4.useState)(null);
8138
- const [pending, setPending] = (0, import_react4.useState)([]);
8405
+ const [tool, setTool] = (0, import_react5.useState)("pen");
8406
+ const [color, setColor] = (0, import_react5.useState)(INK[0]);
8407
+ const [drawing, setDrawing] = (0, import_react5.useState)(null);
8408
+ const [pending, setPending] = (0, import_react5.useState)([]);
8139
8409
  const [notice, show] = useNotice();
8140
- const svgRef = (0, import_react4.useRef)(null);
8410
+ const svgRef = (0, import_react5.useRef)(null);
8141
8411
  const strokes = collab?.strokes ?? [];
8142
8412
  const mine = strokes.filter((s) => s.by === localIdentity);
8143
8413
  const point = (event) => {
@@ -8278,8 +8548,8 @@ function clock(ms2) {
8278
8548
  }
8279
8549
  var BREAKOUT_EXPIRE_RETRY_MS = 5e3;
8280
8550
  function useNow(active) {
8281
- const [now, setNow] = (0, import_react4.useState)(() => Date.now());
8282
- (0, import_react4.useEffect)(() => {
8551
+ const [now, setNow] = (0, import_react5.useState)(() => Date.now());
8552
+ (0, import_react5.useEffect)(() => {
8283
8553
  if (!active) return void 0;
8284
8554
  const timer = setInterval(() => setNow(Date.now()), 1e3);
8285
8555
  return () => clearInterval(timer);
@@ -8293,11 +8563,11 @@ function BreakoutSetup() {
8293
8563
  const people = (snapshot?.participants ?? []).filter(
8294
8564
  (p) => !p.isAgent && p.role !== "host" && p.role !== "cohost"
8295
8565
  );
8296
- const [count2, setCount] = (0, import_react4.useState)(Math.min(Math.max(2, Math.ceil(people.length / 4)), settings?.breakoutMaxRooms ?? 20));
8297
- const [mode, setMode] = (0, import_react4.useState)("auto");
8298
- const [minutes, setMinutes] = (0, import_react4.useState)(settings?.breakoutDefaultMinutes ?? 15);
8299
- const [manual, setManual] = (0, import_react4.useState)({});
8300
- const [busy, setBusy] = (0, import_react4.useState)(false);
8566
+ const [count2, setCount] = (0, import_react5.useState)(Math.min(Math.max(2, Math.ceil(people.length / 4)), settings?.breakoutMaxRooms ?? 20));
8567
+ const [mode, setMode] = (0, import_react5.useState)("auto");
8568
+ const [minutes, setMinutes] = (0, import_react5.useState)(settings?.breakoutDefaultMinutes ?? 15);
8569
+ const [manual, setManual] = (0, import_react5.useState)({});
8570
+ const [busy, setBusy] = (0, import_react5.useState)(false);
8301
8571
  const [notice, show] = useNotice();
8302
8572
  const max = settings?.breakoutMaxRooms ?? 20;
8303
8573
  const rooms = Math.min(Math.max(1, count2), max);
@@ -8413,7 +8683,7 @@ function BreakoutRun({ plan }) {
8413
8683
  const snapshot = useMeetSnapshot();
8414
8684
  const now = useNow(plan.endsAt !== null);
8415
8685
  const remaining = breakoutRemainingMs(plan, now);
8416
- const [broadcast, setBroadcast] = (0, import_react4.useState)("");
8686
+ const [broadcast, setBroadcast] = (0, import_react5.useState)("");
8417
8687
  const [notice, show] = useNotice();
8418
8688
  const nameOf = (identity) => displayNameFor(snapshot?.names ?? {}, identity, snapshot?.participants.find((p) => p.identity === identity)?.displayName);
8419
8689
  const mine = assignedRoom(plan, localIdentity);
@@ -8523,11 +8793,11 @@ function BreakoutBanner() {
8523
8793
  const now = useNow(open?.endsAt != null);
8524
8794
  const remaining = breakoutRemainingMs(open, now);
8525
8795
  const assigned = assignedRoom(open, localIdentity);
8526
- const [dismissedBroadcast, setDismissedBroadcast] = (0, import_react4.useState)(null);
8527
- const [failure, setFailure] = (0, import_react4.useState)(null);
8528
- const expireAsked = (0, import_react4.useRef)(null);
8796
+ const [dismissedBroadcast, setDismissedBroadcast] = (0, import_react5.useState)(null);
8797
+ const [failure, setFailure] = (0, import_react5.useState)(null);
8798
+ const expireAsked = (0, import_react5.useRef)(null);
8529
8799
  const moveAt = collab?.moveRequest?.at ?? null;
8530
- (0, import_react4.useEffect)(() => {
8800
+ (0, import_react5.useEffect)(() => {
8531
8801
  const request = host?.store.snapshot().collab.moveRequest ?? null;
8532
8802
  if (request === null || host === null) return;
8533
8803
  host.store.patch({ collab: { ...host.store.snapshot().collab, moveRequest: null } });
@@ -8535,7 +8805,7 @@ function BreakoutBanner() {
8535
8805
  if ((here ?? null) === request.roomId) return;
8536
8806
  void moveTo(request.roomId).then((result) => setFailure(result.ok ? null : result.message));
8537
8807
  }, [moveAt, host, moveTo]);
8538
- (0, import_react4.useEffect)(() => {
8808
+ (0, import_react5.useEffect)(() => {
8539
8809
  if (open === null || remaining === null || remaining > 0) return;
8540
8810
  const asked = expireAsked.current;
8541
8811
  if (asked !== null && asked.id === open.id && now - asked.at < BREAKOUT_EXPIRE_RETRY_MS) return;
@@ -8585,13 +8855,13 @@ function useCaptionTranslation(lines) {
8585
8855
  const snapshot = useMeetSnapshot();
8586
8856
  const collab = snapshot?.collab ?? null;
8587
8857
  const language = collab?.captionLanguage ?? null;
8588
- const asked = (0, import_react4.useRef)(/* @__PURE__ */ new Set());
8858
+ const asked = (0, import_react5.useRef)(/* @__PURE__ */ new Set());
8589
8859
  const translations = collab?.captionTranslations ?? {};
8590
8860
  const missing = lines.filter((line) => line.final && translations[line.id] === void 0).map(({ id, text: text3 }) => ({ id, text: text3 }));
8591
8861
  const key = `${language ?? ""}|${missing.map((l) => l.id).join(",")}`;
8592
- const pending = (0, import_react4.useRef)(missing);
8862
+ const pending = (0, import_react5.useRef)(missing);
8593
8863
  pending.current = missing;
8594
- (0, import_react4.useEffect)(() => {
8864
+ (0, import_react5.useEffect)(() => {
8595
8865
  if (host === null || language === null) return;
8596
8866
  const batch = pending.current.filter((line) => !asked.current.has(`${language}:${line.id}`)).slice(-20);
8597
8867
  if (batch.length === 0) return;
@@ -8646,9 +8916,9 @@ function CaptionLanguageSettings() {
8646
8916
  function useCollabPanelRequest(onRequest) {
8647
8917
  const host = useMeetHost();
8648
8918
  const at = useMeetSnapshot()?.collab.panelRequest?.at ?? null;
8649
- const callback = (0, import_react4.useRef)(onRequest);
8919
+ const callback = (0, import_react5.useRef)(onRequest);
8650
8920
  callback.current = onRequest;
8651
- (0, import_react4.useEffect)(() => {
8921
+ (0, import_react5.useEffect)(() => {
8652
8922
  const request = host?.store.snapshot().collab.panelRequest ?? null;
8653
8923
  if (request === null || host === null) return;
8654
8924
  host.store.patch({ collab: { ...host.store.snapshot().collab, panelRequest: null } });
@@ -8757,7 +9027,7 @@ function ParticipantTile({
8757
9027
  const host = useMeetHost();
8758
9028
  const snapshot = useMeetSnapshot();
8759
9029
  const queue = useHandQueue();
8760
- const [menu, setMenu] = (0, import_react5.useState)(false);
9030
+ const [menu, setMenu] = (0, import_react6.useState)(false);
8761
9031
  const videoRef = useTrack(
8762
9032
  participant.identity,
8763
9033
  participant.screenSharing ? "screen" : "camera"
@@ -8871,17 +9141,17 @@ var MEET_REACTIONS = ["\u{1F496}", "\u{1F44D}", "\u{1F389}", "\u{1F44F}", "\u{1F
8871
9141
  function ControlBar({ onLeave }) {
8872
9142
  const room = useRoom();
8873
9143
  const recording = useRecording();
8874
- const [panel, setPanel] = (0, import_react5.useState)("none");
8875
- const [chatTo, setChatTo] = (0, import_react5.useState)(null);
8876
- const [popover, setPopover] = (0, import_react5.useState)("none");
8877
- const [shortcuts, setShortcuts] = (0, import_react5.useState)(false);
9144
+ const [panel, setPanel] = (0, import_react6.useState)("none");
9145
+ const [chatTo, setChatTo] = (0, import_react6.useState)(null);
9146
+ const [popover, setPopover] = (0, import_react6.useState)("none");
9147
+ const [shortcuts, setShortcuts] = (0, import_react6.useState)(false);
8878
9148
  const snapshot = useMeetSnapshot();
8879
9149
  const lobby = useLobby();
8880
9150
  const controls = useHostControls();
8881
9151
  const queue = useHandQueue();
8882
9152
  const micUnavailable = useLocalMedia().microphone.unavailable;
8883
9153
  const unread = useUnreadChat(panel === "chat");
8884
- const [whiteboard, setWhiteboard] = (0, import_react5.useState)(false);
9154
+ const [whiteboard, setWhiteboard] = (0, import_react6.useState)(false);
8885
9155
  const toggle = (next) => setPanel(panel === next ? "none" : next);
8886
9156
  const openActivity = (activity) => {
8887
9157
  if (activity === "whiteboard") setWhiteboard(true);
@@ -8892,7 +9162,7 @@ function ControlBar({ onLeave }) {
8892
9162
  });
8893
9163
  const privateChatAt = snapshot?.privateChatRequest?.at ?? null;
8894
9164
  const meetStore = useMeetHost()?.store ?? null;
8895
- (0, import_react5.useEffect)(() => {
9165
+ (0, import_react6.useEffect)(() => {
8896
9166
  const request = meetStore?.snapshot().privateChatRequest ?? null;
8897
9167
  if (request === null) return;
8898
9168
  setChatTo(request.to);
@@ -9130,13 +9400,13 @@ function useUnreadChat(open) {
9130
9400
  const snapshot = useMeetSnapshot();
9131
9401
  const chat = snapshot?.chat ?? [];
9132
9402
  const local = snapshot?.localIdentity ?? null;
9133
- const [seen, setSeen] = (0, import_react5.useState)(0);
9134
- (0, import_react5.useEffect)(() => {
9403
+ const [seen, setSeen] = (0, import_react6.useState)(0);
9404
+ (0, import_react6.useEffect)(() => {
9135
9405
  if (open) setSeen(chat.length);
9136
9406
  }, [open, chat.length]);
9137
9407
  const loaded = snapshot?.chatLoaded === true;
9138
- const [baseline, setBaseline] = (0, import_react5.useState)(null);
9139
- (0, import_react5.useEffect)(() => {
9408
+ const [baseline, setBaseline] = (0, import_react6.useState)(null);
9409
+ (0, import_react6.useEffect)(() => {
9140
9410
  if (loaded && baseline === null) setBaseline(chat.length);
9141
9411
  }, [loaded, baseline, chat.length]);
9142
9412
  if (open) return 0;
@@ -9149,14 +9419,14 @@ function Popover({
9149
9419
  children,
9150
9420
  placement = "up"
9151
9421
  }) {
9152
- const ref = (0, import_react5.useRef)(null);
9153
- const [style, setStyle] = (0, import_react5.useState)({
9422
+ const ref = (0, import_react6.useRef)(null);
9423
+ const [style, setStyle] = (0, import_react6.useState)({
9154
9424
  position: "fixed",
9155
9425
  top: 0,
9156
9426
  left: 0,
9157
9427
  visibility: "hidden"
9158
9428
  });
9159
- (0, import_react5.useLayoutEffect)(() => {
9429
+ (0, import_react6.useLayoutEffect)(() => {
9160
9430
  const element = ref.current;
9161
9431
  const anchor = element?.parentElement ?? null;
9162
9432
  if (element === null || anchor === null) return;
@@ -9201,7 +9471,7 @@ function Popover({
9201
9471
  window.removeEventListener("scroll", place, true);
9202
9472
  };
9203
9473
  }, [placement]);
9204
- (0, import_react5.useEffect)(() => {
9474
+ (0, import_react6.useEffect)(() => {
9205
9475
  const onKey = (event) => {
9206
9476
  if (event.key === "Escape") onClose();
9207
9477
  };
@@ -9235,7 +9505,7 @@ function MenuItem({
9235
9505
  );
9236
9506
  }
9237
9507
  function Toast({ text: text3, failed, onDone }) {
9238
- (0, import_react5.useEffect)(() => {
9508
+ (0, import_react6.useEffect)(() => {
9239
9509
  const timer = setTimeout(onDone, failed ? 7e3 : 4e3);
9240
9510
  return () => clearTimeout(timer);
9241
9511
  }, [text3, failed, onDone]);
@@ -9274,7 +9544,7 @@ function MeetingOverlays() {
9274
9544
  ] });
9275
9545
  }
9276
9546
  function ShortcutsSheet({ onClose }) {
9277
- (0, import_react5.useEffect)(() => {
9547
+ (0, import_react6.useEffect)(() => {
9278
9548
  const onKey = (event) => {
9279
9549
  if (event.key === "Escape") onClose();
9280
9550
  };
@@ -9312,8 +9582,8 @@ function PanelShell({
9312
9582
  onClose,
9313
9583
  children
9314
9584
  }) {
9315
- const ref = (0, import_react5.useRef)(null);
9316
- (0, import_react5.useLayoutEffect)(() => {
9585
+ const ref = (0, import_react6.useRef)(null);
9586
+ (0, import_react6.useLayoutEffect)(() => {
9317
9587
  const panel = ref.current;
9318
9588
  const root = panel?.closest(".mx-meet");
9319
9589
  const header = root?.querySelector(":scope > .mx-meet__header") ?? null;
@@ -9340,17 +9610,17 @@ function ChatPanel({
9340
9610
  }) {
9341
9611
  const snapshot = useMeetSnapshot();
9342
9612
  const room = useRoom();
9343
- const [draft, setDraft] = (0, import_react5.useState)("");
9344
- const [recipient, setRecipient] = (0, import_react5.useState)(to);
9345
- const listRef = (0, import_react5.useRef)(null);
9613
+ const [draft, setDraft] = (0, import_react6.useState)("");
9614
+ const [recipient, setRecipient] = (0, import_react6.useState)(to);
9615
+ const listRef = (0, import_react6.useRef)(null);
9346
9616
  const messages = snapshot?.chat ?? [];
9347
9617
  const local = snapshot?.localIdentity ?? null;
9348
9618
  const people = (snapshot?.participants ?? []).filter(
9349
9619
  (participant) => !participant.isLocal && !participant.isAgent
9350
9620
  );
9351
9621
  const recipientName = recipient === null ? null : people.find((p) => p.identity === recipient)?.displayName ?? null;
9352
- (0, import_react5.useEffect)(() => setRecipient(to), [to]);
9353
- (0, import_react5.useEffect)(() => {
9622
+ (0, import_react6.useEffect)(() => setRecipient(to), [to]);
9623
+ (0, import_react6.useEffect)(() => {
9354
9624
  const list = listRef.current;
9355
9625
  if (list !== null) list.scrollTop = list.scrollHeight;
9356
9626
  }, [messages.length]);
@@ -9456,8 +9726,8 @@ function PeoplePanel({
9456
9726
  const snapshot = useMeetSnapshot();
9457
9727
  const controls = useHostControls();
9458
9728
  const queue = useHandQueue();
9459
- const [query, setQuery] = (0, import_react5.useState)("");
9460
- const [menuFor, setMenuFor] = (0, import_react5.useState)(null);
9729
+ const [query, setQuery] = (0, import_react6.useState)("");
9730
+ const [menuFor, setMenuFor] = (0, import_react6.useState)(null);
9461
9731
  const myRole = useRole();
9462
9732
  const local = snapshot?.localIdentity ?? null;
9463
9733
  const everyone = snapshot?.participants ?? [];
@@ -9582,14 +9852,14 @@ function PersonMenu({
9582
9852
  const host = useMeetHost();
9583
9853
  const snapshot = useMeetSnapshot();
9584
9854
  const controls = useHostControls();
9585
- const [mode, setMode] = (0, import_react5.useState)("menu");
9586
- const [text3, setText] = (0, import_react5.useState)("");
9855
+ const [mode, setMode] = (0, import_react6.useState)("menu");
9856
+ const [text3, setText] = (0, import_react6.useState)("");
9587
9857
  const self = participant.identity === snapshot?.localIdentity;
9588
9858
  const moderate = controls.canModerate(participant);
9589
9859
  const pinned = snapshot?.pinned === participant.identity;
9590
9860
  const spotlit = snapshot?.spotlight === participant.identity;
9591
9861
  const hasAccount = participant.userId !== null;
9592
- const [refused, setRefused] = (0, import_react5.useState)(false);
9862
+ const [refused, setRefused] = (0, import_react6.useState)(false);
9593
9863
  const act = (request) => {
9594
9864
  setRefused(false);
9595
9865
  void controls.run(request).then((result) => {
@@ -9805,8 +10075,8 @@ function DeviceSettings({
9805
10075
  const speaker = devices?.selected.audiooutput ?? host.devices.resolve("audiooutput");
9806
10076
  const stream = useMicrophoneStream(microphone);
9807
10077
  const level = useAudioLevel(stream);
9808
- const [testing, setTesting] = (0, import_react5.useState)(false);
9809
- (0, import_react5.useEffect)(() => {
10078
+ const [testing, setTesting] = (0, import_react6.useState)(false);
10079
+ (0, import_react6.useEffect)(() => {
9810
10080
  void host.devices.refresh();
9811
10081
  }, [host]);
9812
10082
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { className: "mx-meet__section", "aria-label": "Devices", children: [
@@ -9835,8 +10105,8 @@ function DeviceSettings({
9835
10105
  }
9836
10106
  function EffectsSettings({ preview }) {
9837
10107
  const effects = useMediaEffects();
9838
- const [error, setError] = (0, import_react5.useState)(null);
9839
- const fileRef = (0, import_react5.useRef)(null);
10108
+ const [error, setError] = (0, import_react6.useState)(null);
10109
+ const fileRef = (0, import_react6.useRef)(null);
9840
10110
  const supported = effects.backgroundsSupported || preview != null || videoEffectsSupported();
9841
10111
  const apply = (next) => {
9842
10112
  setError(null);
@@ -9958,8 +10228,8 @@ async function playTestSound(sinkId) {
9958
10228
  await context.close();
9959
10229
  }
9960
10230
  function useMicrophoneStream(deviceId2) {
9961
- const [stream, setStream] = (0, import_react5.useState)(null);
9962
- (0, import_react5.useEffect)(() => {
10231
+ const [stream, setStream] = (0, import_react6.useState)(null);
10232
+ (0, import_react6.useEffect)(() => {
9963
10233
  if (deviceId2 === "off") return void 0;
9964
10234
  let live = true;
9965
10235
  let opened = null;
@@ -10060,7 +10330,7 @@ function NoteList({
10060
10330
  }
10061
10331
  function AiPanel({ onClose }) {
10062
10332
  const ai = useMeetAi();
10063
- const [question, setQuestion] = (0, import_react5.useState)("");
10333
+ const [question, setQuestion] = (0, import_react6.useState)("");
10064
10334
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(PanelShell, { title: "Meeting assistant", onClose, children: [
10065
10335
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(NoteTakerRow, {}),
10066
10336
  ai.summary !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { className: "mx-meet__note-group", "aria-label": "Summary", children: [
@@ -10216,16 +10486,16 @@ function PreJoinBody({
10216
10486
  const recording = useRecording();
10217
10487
  const effects = useMediaEffects();
10218
10488
  const { notice } = useModerationNotice();
10219
- const [mode, setMode] = (0, import_react5.useState)("video");
10220
- const [microphone, setMicrophone] = (0, import_react5.useState)(true);
10221
- const [joining, setJoining] = (0, import_react5.useState)(false);
10489
+ const [mode, setMode] = (0, import_react6.useState)("video");
10490
+ const [microphone, setMicrophone] = (0, import_react6.useState)(true);
10491
+ const [joining, setJoining] = (0, import_react6.useState)(false);
10222
10492
  const camera = devices?.selected.videoinput ?? host.devices.resolve("videoinput");
10223
10493
  const mic = devices?.selected.audioinput ?? host.devices.resolve("audioinput");
10224
10494
  const preview = useCameraPreview(mode === "video" ? camera : void 0, effects.effects);
10225
10495
  const micStream = useMicrophoneStream(microphone ? mic : "off");
10226
10496
  const level = useAudioLevel(micStream);
10227
- const [speakerTesting, setSpeakerTesting] = (0, import_react5.useState)(false);
10228
- (0, import_react5.useEffect)(() => {
10497
+ const [speakerTesting, setSpeakerTesting] = (0, import_react6.useState)(false);
10498
+ (0, import_react6.useEffect)(() => {
10229
10499
  void host.devices.refresh();
10230
10500
  }, [host]);
10231
10501
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-meet__prejoin", children: [
@@ -10341,12 +10611,12 @@ function PreJoinBody({
10341
10611
  ] });
10342
10612
  }
10343
10613
  function useCameraPreview(deviceId2, effects) {
10344
- const [preview, setPreview] = (0, import_react5.useState)(null);
10345
- const [error, setError] = (0, import_react5.useState)(null);
10346
- const current = (0, import_react5.useRef)(null);
10347
- const initialEffect = (0, import_react5.useRef)(toVideoEffect(effects));
10614
+ const [preview, setPreview] = (0, import_react6.useState)(null);
10615
+ const [error, setError] = (0, import_react6.useState)(null);
10616
+ const current = (0, import_react6.useRef)(null);
10617
+ const initialEffect = (0, import_react6.useRef)(toVideoEffect(effects));
10348
10618
  initialEffect.current = toVideoEffect(effects);
10349
- (0, import_react5.useEffect)(() => {
10619
+ (0, import_react6.useEffect)(() => {
10350
10620
  if (deviceId2 === void 0) return void 0;
10351
10621
  let live = true;
10352
10622
  setError(null);
@@ -10432,16 +10702,16 @@ function MeetingRoomBody({
10432
10702
  const isHost = useIsHost();
10433
10703
  const elapsed = useElapsed(snapshot?.meeting?.startedAt ?? null);
10434
10704
  const phase = snapshot?.phase ?? "idle";
10435
- (0, import_react5.useEffect)(() => {
10705
+ (0, import_react6.useEffect)(() => {
10436
10706
  host.meetings.watch(roomName);
10437
10707
  }, [host, roomName]);
10438
- (0, import_react5.useEffect)(() => {
10708
+ (0, import_react6.useEffect)(() => {
10439
10709
  if (given === void 0) return;
10440
10710
  if (host.store.snapshot().meeting !== null) return;
10441
10711
  host.store.patch({ meeting: given, locked: given.locked });
10442
10712
  }, [host, given]);
10443
10713
  const watchedId = meetingId ?? snapshot?.meeting?.id ?? null;
10444
- (0, import_react5.useEffect)(() => {
10714
+ (0, import_react6.useEffect)(() => {
10445
10715
  if (watchedId === null) return;
10446
10716
  host.meetings.watchMeeting(asMeetingId(watchedId));
10447
10717
  }, [host, watchedId]);
@@ -10486,8 +10756,8 @@ function MeetingRoomBody({
10486
10756
  ] });
10487
10757
  }
10488
10758
  function WaitingRoom({ roomName, host }) {
10489
- const knocked = (0, import_react5.useRef)(false);
10490
- (0, import_react5.useEffect)(() => {
10759
+ const knocked = (0, import_react6.useRef)(false);
10760
+ (0, import_react6.useEffect)(() => {
10491
10761
  if (knocked.current) return;
10492
10762
  knocked.current = true;
10493
10763
  host.meetings.knock(roomName);
@@ -10506,7 +10776,7 @@ function HostMenu() {
10506
10776
  const host = useRequiredMeetHost();
10507
10777
  const snapshot = useMeetSnapshot();
10508
10778
  const end = useEndMeeting();
10509
- const [confirmingEnd, setConfirmingEnd] = (0, import_react5.useState)(false);
10779
+ const [confirmingEnd, setConfirmingEnd] = (0, import_react6.useState)(false);
10510
10780
  const locked = snapshot?.locked === true;
10511
10781
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mx-meet__host-menu", children: [
10512
10782
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -10612,13 +10882,13 @@ function ScheduleMeetingBody({
10612
10882
  onScheduled,
10613
10883
  host
10614
10884
  }) {
10615
- const [title, setTitle] = (0, import_react5.useState)("");
10616
- const [when2, setWhen] = (0, import_react5.useState)("");
10617
- const [kind, setKind] = (0, import_react5.useState)("scheduled");
10618
- const [busy, setBusy] = (0, import_react5.useState)(false);
10619
- const [link, setLink] = (0, import_react5.useState)(null);
10620
- const [error, setError] = (0, import_react5.useState)(null);
10621
- const disabled = (0, import_react5.useMemo)(() => title.trim().length === 0 || busy, [title, busy]);
10885
+ const [title, setTitle] = (0, import_react6.useState)("");
10886
+ const [when2, setWhen] = (0, import_react6.useState)("");
10887
+ const [kind, setKind] = (0, import_react6.useState)("scheduled");
10888
+ const [busy, setBusy] = (0, import_react6.useState)(false);
10889
+ const [link, setLink] = (0, import_react6.useState)(null);
10890
+ const [error, setError] = (0, import_react6.useState)(null);
10891
+ const disabled = (0, import_react6.useMemo)(() => title.trim().length === 0 || busy, [title, busy]);
10622
10892
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
10623
10893
  "form",
10624
10894
  {
@@ -10755,8 +11025,8 @@ function MeetingRecordBody({
10755
11025
  }) {
10756
11026
  const host = useRequiredMeetHost();
10757
11027
  const record = useMeetingRecord(meeting);
10758
- const [question, setQuestion] = (0, import_react5.useState)("");
10759
- const [tab, setTab] = (0, import_react5.useState)("summary");
11028
+ const [question, setQuestion] = (0, import_react6.useState)("");
11029
+ const [tab, setTab] = (0, import_react6.useState)("summary");
10760
11030
  if (record.failure !== null) {
10761
11031
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mx-meet mx-meet--record", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system.ErrorBox, { as: "div", className: "mx-meet__empty", error: record.failure, operation: "Open this meeting record", origin: "meet/components", children: [
10762
11032
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mx-meet__empty-title", children: "This meeting record could not be opened" }),
@@ -11011,10 +11281,10 @@ function RecordingPlayer(props) {
11011
11281
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RecordingPlayerBody, { ...props, host });
11012
11282
  }
11013
11283
  function RecordingPlayerBody({ fileId, host }) {
11014
- const [media, setMedia] = (0, import_react5.useState)(null);
11015
- const [loading, setLoading] = (0, import_react5.useState)(false);
11016
- const [error, setError] = (0, import_react5.useState)(null);
11017
- (0, import_react5.useEffect)(() => {
11284
+ const [media, setMedia] = (0, import_react6.useState)(null);
11285
+ const [loading, setLoading] = (0, import_react6.useState)(false);
11286
+ const [error, setError] = (0, import_react6.useState)(null);
11287
+ (0, import_react6.useEffect)(() => {
11018
11288
  if (media === null || media.kind !== "bytes") return void 0;
11019
11289
  const url = media.url;
11020
11290
  return () => URL.revokeObjectURL(url);