@ai-matrx/meet 0.7.6 → 0.7.8

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
@@ -212,6 +212,12 @@ var MEET_ROUTES = {
212
212
  meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce",
213
213
  /** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
214
214
  meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar",
215
+ /**
216
+ * Meet wave 3: the recap email after the meeting. `GET` = the draft (subject,
217
+ * body from the wrap-up, who it can go to); `POST` = send what the host
218
+ * reviewed (`dry_run` renders and queues nothing). Never sent automatically.
219
+ */
220
+ meetingFollowUp: "/api/v1/meet/meetings/{meeting_id}/follow-up",
215
221
  /**
216
222
  * Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
217
223
  * unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
@@ -341,10 +347,11 @@ function createTokenClient(options) {
341
347
  throw error;
342
348
  }
343
349
  if (response.status === 401 || response.status === 403) {
350
+ const said = await serverSentence(response);
344
351
  throw new MeetError(
345
352
  "forbidden",
346
- `meet.token: the server refused this join (${response.status})`,
347
- "Authorization is the server's (R5). The joiner is not a member of this meeting's organization, or the meeting is locked \u2014 never mint around it on the client."
353
+ said?.message ?? "You can't join this meeting.",
354
+ said?.remedy ?? "Ask the person who shared the link to let you in, then open the link again."
348
355
  );
349
356
  }
350
357
  if (response.status === 404) {
@@ -355,12 +362,7 @@ function createTokenClient(options) {
355
362
  );
356
363
  }
357
364
  if (response.status === 410) {
358
- let reason = "This meeting was cancelled.";
359
- try {
360
- const body = await response.json();
361
- if (typeof body?.detail?.message === "string") reason = body.detail.message;
362
- } catch {
363
- }
365
+ const reason = (await serverSentence(response))?.message ?? "This meeting was cancelled.";
364
366
  throw new MeetError(
365
367
  "cancelled",
366
368
  reason,
@@ -368,10 +370,11 @@ function createTokenClient(options) {
368
370
  );
369
371
  }
370
372
  if (response.status === 423) {
373
+ const said = await serverSentence(response);
371
374
  throw new MeetError(
372
375
  "room-locked",
373
- "meet.token: the meeting is locked",
374
- "The host locked the room. Ask them to unlock or admit you; this is a normal state, not a failure."
376
+ said?.message ?? "The meeting is locked.",
377
+ said?.remedy ?? "The host locked the room. Ask them to unlock it, then join again."
375
378
  );
376
379
  }
377
380
  if (response.status === 503) {
@@ -442,6 +445,19 @@ function placeholder(roomName) {
442
445
  lobby: false
443
446
  };
444
447
  }
448
+ async function serverSentence(response) {
449
+ try {
450
+ const body = await response.json();
451
+ const source = body !== null && typeof body.detail === "object" && body.detail !== null ? body.detail : body;
452
+ if (source === null) return null;
453
+ const message = typeof source.user_message === "string" && source.user_message.trim() !== "" ? source.user_message : typeof source.message === "string" && source.message.trim() !== "" ? source.message : null;
454
+ if (message === null) return null;
455
+ const remedy = typeof source.remedy === "string" && source.remedy.trim() !== "" ? source.remedy : void 0;
456
+ return { message, remedy };
457
+ } catch {
458
+ return null;
459
+ }
460
+ }
445
461
 
446
462
  // src/core/ai.ts
447
463
  var MEET_ANSWER_EVENTS = {
@@ -486,6 +502,29 @@ function readText(data) {
486
502
  }
487
503
  return "";
488
504
  }
505
+ var SERVER_FAILED_SENTENCE = "The server hit an unexpected problem and nothing was changed.";
506
+ function isRawServerText(value) {
507
+ const text3 = value.trim();
508
+ return /^internal server error\b/i.test(text3) || /^[A-Z][A-Za-z0-9]*(Error|Exception)$/.test(text3) || /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/.test(text3);
509
+ }
510
+ async function refusalFromResponse(response, operation, fallbackRemedy) {
511
+ let body = null;
512
+ try {
513
+ body = await response.json();
514
+ } catch {
515
+ body = null;
516
+ }
517
+ const said = readServerRefusal(body);
518
+ const code = response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport";
519
+ const error = new MeetError(
520
+ code,
521
+ said?.reason ?? SERVER_FAILED_SENTENCE,
522
+ said !== null && said.remedy.length > 0 ? said.remedy : fallbackRemedy
523
+ );
524
+ error.operation = operation;
525
+ error.status = response.status;
526
+ return error;
527
+ }
489
528
  function readServerRefusal(body) {
490
529
  if (typeof body === "string") {
491
530
  return body.trim().length === 0 ? null : { reason: body.trim(), remedy: "" };
@@ -504,7 +543,8 @@ function readServerRefusal(body) {
504
543
  return messages.length === 0 ? null : { reason: messages.join("; "), remedy: "" };
505
544
  }
506
545
  if (typeof detail === "object" && detail !== null) return readServerRefusal(detail);
507
- const reason = typeof row["reason"] === "string" ? row["reason"] : typeof row["message"] === "string" ? row["message"] : typeof detail === "string" ? detail : typeof row["error"] === "string" ? row["error"] : null;
546
+ const humanOrNull = (value) => typeof value === "string" && value.trim().length > 0 && !isRawServerText(value) ? value.trim() : null;
547
+ const reason = humanOrNull(row["reason"]) ?? humanOrNull(row["message"]) ?? humanOrNull(row["user_message"]) ?? (typeof detail === "string" ? humanOrNull(detail) : null) ?? (typeof row["message"] === "string" || typeof row["error"] === "string" || typeof detail === "string" ? SERVER_FAILED_SENTENCE : null);
508
548
  if (reason === null) return null;
509
549
  const remedy = typeof row["remedy"] === "string" ? row["remedy"] : "";
510
550
  if (remedy.length > 0) return { reason, remedy };
@@ -885,7 +925,7 @@ var DEFAULT_RING_TIMEOUT_MS = 45e3;
885
925
  function createCallCenter(options) {
886
926
  const { manager, repository, identity } = options;
887
927
  const timers = options.timers ?? {
888
- setTimeout: (run, ms) => setTimeout(run, ms),
928
+ setTimeout: (run, ms2) => setTimeout(run, ms2),
889
929
  clearTimeout: (handle) => clearTimeout(handle),
890
930
  now: () => Date.now()
891
931
  };
@@ -1558,6 +1598,7 @@ var EMPTY = {
1558
1598
  spotlight: null,
1559
1599
  moderationNotice: null,
1560
1600
  actionNotice: null,
1601
+ privateChatRequest: null,
1561
1602
  effects: DEFAULT_EFFECTS,
1562
1603
  transcript: [],
1563
1604
  notes: [],
@@ -1823,7 +1864,9 @@ function createMeetingSession(options) {
1823
1864
  return identity.userId;
1824
1865
  }
1825
1866
  function knockIdentity() {
1826
- return asParticipantIdentity(identity.userId ?? `guest:${identity.deviceId}`);
1867
+ return asParticipantIdentity(
1868
+ identity.userId !== null ? `user:${identity.userId}` : `guest:${identity.deviceId}`
1869
+ );
1827
1870
  }
1828
1871
  function requireMeeting(operation) {
1829
1872
  const meeting = store.snapshot().meeting;
@@ -1858,8 +1901,9 @@ function createMeetingSession(options) {
1858
1901
  verdict.admitted ? "admitted" : "denied",
1859
1902
  verdict.by === null ? null : verdict.by
1860
1903
  );
1861
- if (verdict.admitted && decided === knockIdentity()) {
1862
- options.onAdmitted?.();
1904
+ if (decided === knockIdentity()) {
1905
+ if (verdict.admitted) options.onAdmitted?.();
1906
+ else options.onDenied?.();
1863
1907
  }
1864
1908
  }
1865
1909
  },
@@ -2196,7 +2240,16 @@ var RPCS = {
2196
2240
  /** One meeting's occurrences between two instants (INVOKER — the reader's RLS). */
2197
2241
  meetingOccurrences: "meet_meeting_occurrences",
2198
2242
  /** Every occurrence the reader can see between two instants — the Upcoming list. */
2199
- occurrencesBetween: "meet_occurrences_between"
2243
+ occurrencesBetween: "meet_occurrences_between",
2244
+ /*
2245
+ * ── MEET WAVE 3 — after the meeting. Both INVOKER (the reader's RLS) and both
2246
+ * scoped to the reader's OWN meetings (host, invitee, attended) — RLS is the
2247
+ * ceiling, never the view.
2248
+ */
2249
+ /** The recordings library. */
2250
+ myRecordings: "meet_my_recordings",
2251
+ /** Titles, agendas, current notes and final transcript lines; snippets mark hits with ⟦ ⟧. */
2252
+ search: "meet_search"
2200
2253
  };
2201
2254
  var VOID_RPCS = [RPCS.admit, RPCS.recordConsent];
2202
2255
  var MEETING_COLUMNS = "id,organization_id,room_name,slug,title,kind,host_user_id,scheduled_for,scheduled_duration_minutes,started_at,ended_at,locked,lobby_enabled,recording_policy,ai_enabled,metadata,time_zone,agenda,recurrence_rule,join_before_host,cancelled_at,cancellation_reason,deleted_at,calendar_sequence,version";
@@ -2901,12 +2954,117 @@ function createMeetRepository(options) {
2901
2954
  );
2902
2955
  },
2903
2956
  async recordings(meetingId) {
2904
- const response = await db.from(TABLES.recordings).select(RECORDING_COLUMNS).eq("meeting_id", meetingId).order("started_at", { ascending: false });
2957
+ const response = await db.from(TABLES.recordings).select(RECORDING_COLUMNS).eq("meeting_id", meetingId).is("deleted_at", null).order("started_at", { ascending: false });
2905
2958
  if (response.error !== null) throw normalizeMeetError(response.error, "meet.recordings");
2906
2959
  return (response.data ?? []).map(projectRecording);
2960
+ },
2961
+ async myRecordings({ includeArchived = false, limit = 200 } = {}) {
2962
+ const rows = await guard(
2963
+ "meet.myRecordings",
2964
+ () => db.rpc(RPCS.myRecordings, { p_include_archived: includeArchived, p_limit: limit })
2965
+ );
2966
+ return rows.map(projectLibraryRecording);
2967
+ },
2968
+ async searchMeetings(query, limit = 60) {
2969
+ if (query.trim().length === 0) return [];
2970
+ const rows = await guard(
2971
+ "meet.searchMeetings",
2972
+ () => db.rpc(RPCS.search, { p_query: query, p_limit: limit })
2973
+ );
2974
+ return rows.map(projectSearchHit);
2975
+ },
2976
+ async renameRecording(recordingId, title) {
2977
+ const clean = title === null ? null : title.trim().slice(0, 200) || null;
2978
+ const response = await db.from(TABLES.recordings).update({ title: clean }).eq("id", recordingId).select("id");
2979
+ if (response.error !== null) throw normalizeMeetError(response.error, "meet.renameRecording");
2980
+ if ((response.data ?? []).length === 0) {
2981
+ throw normalizeMeetError(
2982
+ { message: "permission denied: only the meeting's host or an editor can rename it", code: "42501" },
2983
+ "meet.renameRecording"
2984
+ );
2985
+ }
2986
+ },
2987
+ async setRecordingArchived(recordingId, archived) {
2988
+ const response = await db.from(TABLES.recordings).update({ deleted_at: archived ? (/* @__PURE__ */ new Date()).toISOString() : null }).eq("id", recordingId).select("id");
2989
+ if (response.error !== null) throw normalizeMeetError(response.error, "meet.setRecordingArchived");
2990
+ if ((response.data ?? []).length === 0) {
2991
+ throw normalizeMeetError(
2992
+ { message: "permission denied: only the meeting's host or an editor can archive it", code: "42501" },
2993
+ "meet.setRecordingArchived"
2994
+ );
2995
+ }
2907
2996
  }
2908
2997
  };
2909
2998
  }
2999
+ var LIBRARY_STATES = [
3000
+ "idle",
3001
+ "starting",
3002
+ "recording",
3003
+ "stopping",
3004
+ "processing",
3005
+ "available",
3006
+ "expired",
3007
+ "failed"
3008
+ ];
3009
+ function projectLibraryRecording(row) {
3010
+ const operation = "meet.projectLibraryRecording";
3011
+ const state = text(row, "state");
3012
+ const role = text(row, "my_role");
3013
+ return {
3014
+ id: requireText(row, "recording_id", operation),
3015
+ meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
3016
+ meetingTitle: text(row, "meeting_title") ?? "Meeting",
3017
+ meetingSlug: requireText(row, "meeting_slug", operation),
3018
+ role: role === "host" || role === "cohost" || role === "invitee" ? role : "attendee",
3019
+ state: LIBRARY_STATES.includes(state) ? state : "failed",
3020
+ title: text(row, "recording_title"),
3021
+ fileId: text(row, "file_id"),
3022
+ startedAt: text(row, "started_at"),
3023
+ endedAt: text(row, "ended_at"),
3024
+ durationMs: num(row, "duration_ms"),
3025
+ sizeBytes: num(row, "size_bytes"),
3026
+ failureReason: text(row, "failure_reason"),
3027
+ archivedAt: text(row, "deleted_at"),
3028
+ organizationId: asOrganizationId(requireText(row, "organization_id", operation))
3029
+ };
3030
+ }
3031
+ var SEARCH_SOURCES = [
3032
+ "title",
3033
+ "summary",
3034
+ "decision",
3035
+ "action_item",
3036
+ "note",
3037
+ "transcript"
3038
+ ];
3039
+ function projectSearchHit(row) {
3040
+ const operation = "meet.projectSearchHit";
3041
+ const source = text(row, "source");
3042
+ return {
3043
+ meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
3044
+ meetingTitle: text(row, "meeting_title") ?? "Meeting",
3045
+ meetingSlug: requireText(row, "meeting_slug", operation),
3046
+ meetingAt: text(row, "meeting_at"),
3047
+ source: SEARCH_SOURCES.includes(source) ? source : "note",
3048
+ refId: requireText(row, "ref_id", operation),
3049
+ speaker: text(row, "speaker"),
3050
+ snippet: text(row, "snippet") ?? "",
3051
+ at: text(row, "said_at"),
3052
+ rank: num(row, "rank") ?? 0
3053
+ };
3054
+ }
3055
+ function snippetRuns(snippet) {
3056
+ const runs = [];
3057
+ const pattern = /⟦([^⟧]*)⟧/g;
3058
+ let last = 0;
3059
+ for (const match of snippet.matchAll(pattern)) {
3060
+ const start = match.index ?? 0;
3061
+ if (start > last) runs.push({ text: snippet.slice(last, start), hit: false });
3062
+ runs.push({ text: match[1] ?? "", hit: true });
3063
+ last = start + match[0].length;
3064
+ }
3065
+ if (last < snippet.length) runs.push({ text: snippet.slice(last), hit: false });
3066
+ return runs;
3067
+ }
2910
3068
 
2911
3069
  // src/core/session-identity.ts
2912
3070
  var RESOLVING = Object.freeze({
@@ -3630,6 +3788,10 @@ function createRoomEngine(options) {
3630
3788
  }
3631
3789
  },
3632
3790
  disconnected(reason) {
3791
+ if (current !== null) {
3792
+ tokens.invalidate(current.roomName);
3793
+ current = null;
3794
+ }
3633
3795
  store.patch({ phase: "left" });
3634
3796
  options.onLeft?.(reason);
3635
3797
  }
@@ -3932,19 +4094,8 @@ function createInRoomClient(options) {
3932
4094
  const token = options.resolveRoomToken();
3933
4095
  return token === null ? void 0 : { [ROOM_TOKEN_HEADER]: token };
3934
4096
  }
3935
- async function refusal2(response, operation, remedy) {
3936
- let body = null;
3937
- try {
3938
- body = await response.json();
3939
- } catch {
3940
- body = null;
3941
- }
3942
- const said = readServerRefusal(body);
3943
- return new MeetError(
3944
- response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
3945
- said?.reason ?? `${operation}: the server answered ${response.status}`,
3946
- said?.remedy !== void 0 && said.remedy.length > 0 ? said.remedy : remedy
3947
- );
4097
+ function refusal2(response, operation, remedy) {
4098
+ return refusalFromResponse(response, operation, remedy);
3948
4099
  }
3949
4100
  return {
3950
4101
  async moderate(request) {
@@ -4537,6 +4688,11 @@ function MeetRuntime(props) {
4537
4688
  // publish nor subscribe, so the host saying yes has to mint a new one.
4538
4689
  onAdmitted: () => {
4539
4690
  void engine.rejoin();
4691
+ },
4692
+ // A "no" is ALSO a mint: the server refuses it with its own sentence, which
4693
+ // turns the waiting room into the pre-join screen showing that sentence.
4694
+ onDenied: () => {
4695
+ void engine.rejoin().catch(() => void 0);
4540
4696
  }
4541
4697
  });
4542
4698
  const resolveRoomToken = () => {
@@ -5899,7 +6055,408 @@ function useGuestRecordAccess(meeting) {
5899
6055
  // src/react/components.tsx
5900
6056
  import { useEffect as useEffect3, useLayoutEffect, useMemo as useMemo3, useRef as useRef3, useState as useState3 } from "react";
5901
6057
  import { ErrorBox } from "@ai-matrx/design-system";
5902
- import { avatarPaletteIndex, formatDurationMs as formatDurationMs2, getInitials } from "@ai-matrx/kit/format";
6058
+ import { avatarPaletteIndex, formatDurationMs as formatDurationMs3, getInitials } from "@ai-matrx/kit/format";
6059
+
6060
+ // src/core/after.ts
6061
+ import { formatDurationMs as formatDurationMs2 } from "@ai-matrx/kit/format";
6062
+ function kindOf(person) {
6063
+ if (person.isAgent) return "assistant";
6064
+ if (person.role === "host") return "host";
6065
+ if (person.role === "cohost") return "cohost";
6066
+ if (person.role === "guest") return person.userId !== null ? "signed-in guest" : "guest";
6067
+ return "member";
6068
+ }
6069
+ function ms(iso) {
6070
+ if (iso === null) return null;
6071
+ const at = new Date(iso).getTime();
6072
+ return Number.isNaN(at) ? null : at;
6073
+ }
6074
+ function attendeeKey(person, names = {}) {
6075
+ if (person.userId !== null) return `user:${person.userId}`;
6076
+ if (person.isAgent) return `agent:${person.identity}`;
6077
+ const name = displayNameFor(names, person.identity, person.displayName).trim().toLowerCase();
6078
+ return name.length > 0 ? `guest-name:${name}` : `identity:${person.identity}`;
6079
+ }
6080
+ function uniqueAttendees(attendees, names = {}) {
6081
+ const byKey = /* @__PURE__ */ new Map();
6082
+ for (const person of attendees) {
6083
+ const key = attendeeKey(person, names);
6084
+ const kept = byKey.get(key);
6085
+ if (kept === void 0) {
6086
+ byKey.set(key, person);
6087
+ continue;
6088
+ }
6089
+ const keptJoin = ms(kept.joinedAt);
6090
+ const join = ms(person.joinedAt);
6091
+ if (join !== null && (keptJoin === null || join < keptJoin)) byKey.set(key, person);
6092
+ }
6093
+ return [...byKey.values()];
6094
+ }
6095
+ var KIND_RANK = {
6096
+ host: 0,
6097
+ cohost: 1,
6098
+ member: 2,
6099
+ "signed-in guest": 3,
6100
+ guest: 4,
6101
+ assistant: 5
6102
+ };
6103
+ function coveredMs(intervals) {
6104
+ const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
6105
+ let total = 0;
6106
+ let from = -Infinity;
6107
+ let to = -Infinity;
6108
+ for (const [start, end] of sorted) {
6109
+ if (start > to) {
6110
+ if (to > from) total += to - from;
6111
+ from = start;
6112
+ to = end;
6113
+ } else if (end > to) {
6114
+ to = end;
6115
+ }
6116
+ }
6117
+ if (to > from) total += to - from;
6118
+ return total;
6119
+ }
6120
+ function attendanceReport(meeting, attendees, names = {}) {
6121
+ const end = ms(meeting.endedAt);
6122
+ const perJoin = attendees.map((person) => {
6123
+ const joined = ms(person.joinedAt);
6124
+ const left = ms(person.leftAt);
6125
+ const until = left ?? end;
6126
+ const duration = joined !== null && until !== null && until >= joined ? until - joined : null;
6127
+ return {
6128
+ id: person.id,
6129
+ identity: person.identity,
6130
+ name: displayNameFor(names, person.identity, person.displayName),
6131
+ userId: person.userId,
6132
+ kind: kindOf(person),
6133
+ joinedAt: person.joinedAt,
6134
+ leftAt: person.leftAt,
6135
+ durationMs: duration,
6136
+ durationToEnd: duration !== null && left === null,
6137
+ admission: person.admissionState,
6138
+ joins: 1
6139
+ };
6140
+ });
6141
+ const groups = /* @__PURE__ */ new Map();
6142
+ attendees.forEach((person, index) => {
6143
+ const key = attendeeKey(person, names);
6144
+ const group = groups.get(key) ?? { rows: [], people: [] };
6145
+ group.rows.push(perJoin[index]);
6146
+ group.people.push(person);
6147
+ groups.set(key, group);
6148
+ });
6149
+ const rows = [...groups.values()].map(({ rows: joins }) => {
6150
+ if (joins.length === 1) return joins[0];
6151
+ const came = joins.filter((row) => row.joinedAt !== null);
6152
+ const first = [...came].sort((a, b) => (ms(a.joinedAt) ?? 0) - (ms(b.joinedAt) ?? 0))[0] ?? joins[0];
6153
+ const kind = joins.reduce(
6154
+ (best, row) => KIND_RANK[row.kind] < KIND_RANK[best] ? row.kind : best,
6155
+ first.kind
6156
+ );
6157
+ const open = came.some((row) => row.leftAt === null);
6158
+ const lastLeave = came.reduce((latest, row) => {
6159
+ const left = ms(row.leftAt);
6160
+ return left !== null && (latest === null || left > latest) ? left : latest;
6161
+ }, null);
6162
+ const intervals = came.flatMap((row) => {
6163
+ const joined = ms(row.joinedAt);
6164
+ return joined !== null && row.durationMs !== null ? [[joined, joined + row.durationMs]] : [];
6165
+ });
6166
+ return {
6167
+ ...first,
6168
+ kind,
6169
+ leftAt: open ? null : lastLeave === null ? null : new Date(lastLeave).toISOString(),
6170
+ durationMs: intervals.length === 0 ? null : coveredMs(intervals),
6171
+ durationToEnd: came.some((row) => row.durationToEnd),
6172
+ joins: came.length === 0 ? joins.length : came.length
6173
+ };
6174
+ });
6175
+ const rank = KIND_RANK;
6176
+ return rows.sort((a, b) => {
6177
+ if (rank[a.kind] !== rank[b.kind]) return rank[a.kind] - rank[b.kind];
6178
+ return (ms(a.joinedAt) ?? Number.MAX_SAFE_INTEGER) - (ms(b.joinedAt) ?? Number.MAX_SAFE_INTEGER);
6179
+ });
6180
+ }
6181
+ function attendanceTotals(rows) {
6182
+ const humans = rows.filter((row) => row.kind !== "assistant");
6183
+ const attended = humans.filter((row) => row.joinedAt !== null);
6184
+ const timed = attended.filter((row) => row.durationMs !== null);
6185
+ return {
6186
+ people: attended.length,
6187
+ guests: attended.filter((row) => row.kind === "guest" || row.kind === "signed-in guest").length,
6188
+ neverAdmitted: humans.filter((row) => row.joinedAt === null).length,
6189
+ averageMs: timed.length === 0 ? null : Math.round(timed.reduce((sum, row) => sum + (row.durationMs ?? 0), 0) / timed.length)
6190
+ };
6191
+ }
6192
+ function csvCell(value) {
6193
+ if (value === null) return "";
6194
+ const text3 = String(value);
6195
+ return /[",\n\r]/.test(text3) ? `"${text3.replace(/"/g, '""')}"` : text3;
6196
+ }
6197
+ var ATTENDANCE_CSV_HEADER = [
6198
+ "Meeting",
6199
+ "Meeting started",
6200
+ "Name",
6201
+ "Attended as",
6202
+ "Joined",
6203
+ "Left",
6204
+ "Minutes in the meeting",
6205
+ "Admission"
6206
+ ];
6207
+ function attendanceCsv(reports) {
6208
+ const lines = [ATTENDANCE_CSV_HEADER.join(",")];
6209
+ for (const { meeting, rows } of reports) {
6210
+ for (const row of rows) {
6211
+ lines.push(
6212
+ [
6213
+ meeting.title,
6214
+ meeting.startedAt ?? meeting.scheduledFor,
6215
+ row.name,
6216
+ row.kind,
6217
+ row.joinedAt,
6218
+ row.leftAt,
6219
+ row.durationMs === null ? null : Math.round(row.durationMs / 6e3) / 10,
6220
+ row.admission
6221
+ ].map(csvCell).join(",")
6222
+ );
6223
+ }
6224
+ }
6225
+ return `${lines.join("\r\n")}\r
6226
+ `;
6227
+ }
6228
+ function recordingOffsetMs(at, recording) {
6229
+ if (recording === null || recording.state !== "available") return null;
6230
+ const start = ms(recording.startedAt);
6231
+ const moment = ms(at);
6232
+ if (start === null || moment === null || moment < start) return null;
6233
+ const end = ms(recording.endedAt);
6234
+ if (end !== null && moment > end) return null;
6235
+ return moment - start;
6236
+ }
6237
+ function clockPosition(offsetMs) {
6238
+ const total = Math.max(0, Math.floor(offsetMs / 1e3));
6239
+ const h = Math.floor(total / 3600);
6240
+ const m = Math.floor(total % 3600 / 60);
6241
+ const s = total % 60;
6242
+ const ss = String(s).padStart(2, "0");
6243
+ return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
6244
+ }
6245
+ function searchTranscript(segments, query, names = {}) {
6246
+ const needle = fold(query.trim());
6247
+ if (needle.length === 0) return [];
6248
+ const out = [];
6249
+ for (const segment of segments) {
6250
+ const hay = fold(segment.text);
6251
+ const ranges = [];
6252
+ let from = 0;
6253
+ for (; ; ) {
6254
+ const at = hay.indexOf(needle, from);
6255
+ if (at < 0) break;
6256
+ ranges.push([at, at + needle.length]);
6257
+ from = at + needle.length;
6258
+ }
6259
+ const speaker = fold(displayNameFor(names, segment.identity, segment.speaker));
6260
+ if (ranges.length > 0 || speaker.includes(needle)) {
6261
+ out.push({ segmentId: segment.id, ranges });
6262
+ }
6263
+ }
6264
+ return out;
6265
+ }
6266
+ function fold(value) {
6267
+ return value.normalize("NFD").replace(/[̀-ͯ]/g, (mark) => mark.length === 1 ? "" : mark).toLowerCase();
6268
+ }
6269
+ function clockOfDay(iso, options) {
6270
+ const at = new Date(iso);
6271
+ if (Number.isNaN(at.getTime())) return "";
6272
+ return at.toLocaleTimeString(options.locale, {
6273
+ hour: "numeric",
6274
+ minute: "2-digit",
6275
+ second: "2-digit",
6276
+ ...options.timeZone !== void 0 ? { timeZone: options.timeZone } : {}
6277
+ });
6278
+ }
6279
+ function vttStamp(offsetMs) {
6280
+ const total = Math.max(0, Math.round(offsetMs));
6281
+ const h = Math.floor(total / 36e5);
6282
+ const m = Math.floor(total % 36e5 / 6e4);
6283
+ const s = Math.floor(total % 6e4 / 1e3);
6284
+ const milli = total % 1e3;
6285
+ return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}.${String(milli).padStart(3, "0")}`;
6286
+ }
6287
+ function transcriptDocument(bundle, format, options = {}) {
6288
+ const names = options.names ?? bundle.names;
6289
+ const lines = bundle.transcript.filter((line) => line.text.trim().length > 0);
6290
+ const speaker = (line) => displayNameFor(names, line.identity, line.speaker);
6291
+ if (format === "vtt") {
6292
+ const zeroIso = bundle.recording?.state === "available" && bundle.recording.startedAt !== null ? bundle.recording.startedAt : bundle.meeting.startedAt ?? lines[0]?.startedAt ?? null;
6293
+ const zero = ms(zeroIso) ?? 0;
6294
+ const cues = ["WEBVTT", ""];
6295
+ lines.forEach((line, index) => {
6296
+ const start = (ms(line.startedAt) ?? zero) - zero;
6297
+ if (start < 0) return;
6298
+ const next = lines[index + 1];
6299
+ const nextStart = next !== void 0 ? (ms(next.startedAt) ?? zero) - zero : start + 4e3;
6300
+ const end = Math.max(start + 500, Math.min(nextStart, start + 1e4));
6301
+ cues.push(`${vttStamp(start)} --> ${vttStamp(end)}`);
6302
+ cues.push(`<v ${speaker(line).replace(/[<>]/g, "")}>${line.text.replace(/-->/g, "->")}`);
6303
+ cues.push("");
6304
+ });
6305
+ return cues.join("\n");
6306
+ }
6307
+ const out = [];
6308
+ const title = bundle.meeting.title;
6309
+ out.push(format === "md" ? `# ${title} \u2014 transcript` : `${title} \u2014 transcript`);
6310
+ out.push("");
6311
+ let last = null;
6312
+ for (const line of lines) {
6313
+ if (line.identity !== last) {
6314
+ if (last !== null) out.push("");
6315
+ const who = speaker(line);
6316
+ const clock = clockOfDay(line.startedAt, options);
6317
+ out.push(format === "md" ? `**${who}** \xB7 ${clock}` : `${who} (${clock})`);
6318
+ last = line.identity;
6319
+ }
6320
+ out.push(line.text.trim());
6321
+ }
6322
+ if (lines.length === 0) out.push("No transcript was captured for this meeting.");
6323
+ return `${out.join("\n")}
6324
+ `;
6325
+ }
6326
+ function bullet(note) {
6327
+ const owner = note.assigneeDisplayName !== null ? ` \u2014 ${note.assigneeDisplayName}` : "";
6328
+ return `- ${note.text.trim()}${owner}`;
6329
+ }
6330
+ function notesDocument(bundle, options = {}) {
6331
+ const out = [`# ${bundle.meeting.title}`, ""];
6332
+ if (options.when) out.push(`_${options.when}_`, "");
6333
+ const people = uniqueAttendees(
6334
+ bundle.attendees.filter((person) => !person.isAgent && person.joinedAt !== null),
6335
+ bundle.names
6336
+ ).map((person) => displayNameFor(bundle.names, person.identity, person.displayName));
6337
+ if (people.length > 0) out.push(`**Attended:** ${people.join(", ")}`, "");
6338
+ out.push("## Summary", "", bundle.summary?.text.trim() || "No summary was written.", "");
6339
+ out.push("## Decisions", "");
6340
+ out.push(...bundle.decisions.length > 0 ? bundle.decisions.map(bullet) : ["None recorded."], "");
6341
+ out.push("## Action items", "");
6342
+ out.push(
6343
+ ...bundle.actionItems.length > 0 ? bundle.actionItems.map(bullet) : ["None recorded."],
6344
+ ""
6345
+ );
6346
+ if (options.link) out.push(`[Open the meeting record](${options.link})`, "");
6347
+ return out.join("\n");
6348
+ }
6349
+ function meetingLength(meeting) {
6350
+ const start = ms(meeting.startedAt);
6351
+ const end = ms(meeting.endedAt);
6352
+ if (start === null || end === null || end < start) return null;
6353
+ return formatDurationMs2(end - start, { style: "long" });
6354
+ }
6355
+ async function loadMeetingChat(api, meetingId, roomToken = null) {
6356
+ const operation = "meet.loadMeetingChat";
6357
+ let response;
6358
+ try {
6359
+ response = await api.request({
6360
+ path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(meetingId)}`,
6361
+ method: "GET",
6362
+ operation,
6363
+ requireAuth: roomToken === null,
6364
+ ...roomToken !== null ? { headers: { [ROOM_TOKEN_HEADER]: roomToken } } : {}
6365
+ });
6366
+ } catch (error) {
6367
+ return {
6368
+ messages: [],
6369
+ refusal: {
6370
+ message: `The chat could not be read (${error.message}).`,
6371
+ remedy: error.remedy ?? "Reload the page."
6372
+ }
6373
+ };
6374
+ }
6375
+ if (!response.ok) {
6376
+ let body = null;
6377
+ try {
6378
+ body = await response.json();
6379
+ } catch {
6380
+ body = null;
6381
+ }
6382
+ const said = readServerRefusal(body);
6383
+ return {
6384
+ messages: [],
6385
+ refusal: {
6386
+ message: said?.reason ?? `The chat could not be read (the server answered ${response.status}).`,
6387
+ remedy: said?.remedy || "Reload the page."
6388
+ }
6389
+ };
6390
+ }
6391
+ const payload = await response.json();
6392
+ const rows = Array.isArray(payload.messages) ? payload.messages : [];
6393
+ return {
6394
+ messages: rows.map(
6395
+ (row) => typeof row === "object" && row !== null ? chatFromRow(row) : null
6396
+ ).filter((message) => message !== null),
6397
+ refusal: null
6398
+ };
6399
+ }
6400
+ function refusalOf(response, operation) {
6401
+ return refusalFromResponse(response, operation, "Nothing was sent. Try again.");
6402
+ }
6403
+ function asRecipient(row) {
6404
+ const sources = Array.isArray(row["sources"]) ? row["sources"] : [];
6405
+ return {
6406
+ key: String(row["key"] ?? ""),
6407
+ name: String(row["name"] ?? ""),
6408
+ email: typeof row["email"] === "string" ? row["email"] : null,
6409
+ userId: typeof row["user_id"] === "string" ? row["user_id"] : null,
6410
+ sources: sources.filter((s) => s === "invitee" || s === "attendee"),
6411
+ reachable: row["reachable"] !== false
6412
+ };
6413
+ }
6414
+ async function loadFollowUpDraft(api, meetingId) {
6415
+ const operation = "meet.loadFollowUpDraft";
6416
+ const response = await api.request({
6417
+ path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
6418
+ method: "GET",
6419
+ requireAuth: true,
6420
+ operation
6421
+ });
6422
+ if (!response.ok) throw await refusalOf(response, operation);
6423
+ const body = await response.json();
6424
+ const last = body["last_sent"];
6425
+ return {
6426
+ subject: String(body["subject"] ?? ""),
6427
+ body: String(body["body"] ?? ""),
6428
+ recipients: (Array.isArray(body["recipients"]) ? body["recipients"] : []).map(
6429
+ (row) => asRecipient(row)
6430
+ ),
6431
+ wrapUpWritten: body["wrap_up_written"] === true,
6432
+ lastSent: last && typeof last["at"] === "string" ? { at: last["at"], count: typeof last["count"] === "number" ? last["count"] : 0 } : null
6433
+ };
6434
+ }
6435
+ async function sendFollowUp(api, meetingId, input) {
6436
+ const operation = "meet.sendFollowUp";
6437
+ const response = await api.request({
6438
+ path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
6439
+ method: "POST",
6440
+ requireAuth: true,
6441
+ operation,
6442
+ body: {
6443
+ subject: input.subject,
6444
+ body: input.body,
6445
+ recipient_keys: [...input.recipientKeys],
6446
+ dry_run: input.dryRun === true
6447
+ }
6448
+ });
6449
+ if (!response.ok) throw await refusalOf(response, operation);
6450
+ const body = await response.json();
6451
+ return {
6452
+ dryRun: body["dry_run"] === true,
6453
+ queued: Array.isArray(body["queued"]) ? body["queued"] : [],
6454
+ skipped: Array.isArray(body["skipped"]) ? body["skipped"].map((row) => ({
6455
+ key: String(row["key"] ?? ""),
6456
+ reason: String(row["reason"] ?? "")
6457
+ })) : []
6458
+ };
6459
+ }
5903
6460
 
5904
6461
  // src/core/format.ts
5905
6462
  function participantSummary(participants) {
@@ -6349,6 +6906,15 @@ function ControlBar({ onLeave }) {
6349
6906
  const micUnavailable = useLocalMedia().microphone.unavailable;
6350
6907
  const unread = useUnreadChat(panel === "chat");
6351
6908
  const toggle = (next) => setPanel(panel === next ? "none" : next);
6909
+ const privateChatAt = snapshot?.privateChatRequest?.at ?? null;
6910
+ const meetStore = useMeetHost()?.store ?? null;
6911
+ useEffect3(() => {
6912
+ const request = meetStore?.snapshot().privateChatRequest ?? null;
6913
+ if (request === null) return;
6914
+ setChatTo(request.to);
6915
+ setPanel("chat");
6916
+ meetStore?.patch({ privateChatRequest: null });
6917
+ }, [privateChatAt, meetStore]);
6352
6918
  useMeetShortcuts({
6353
6919
  mic: () => void room.toggleMicrophone(),
6354
6920
  camera: () => void room.toggleCamera(),
@@ -6588,7 +7154,12 @@ function Popover({
6588
7154
  placement = "up"
6589
7155
  }) {
6590
7156
  const ref = useRef3(null);
6591
- const [style, setStyle] = useState3({ visibility: "hidden" });
7157
+ const [style, setStyle] = useState3({
7158
+ position: "fixed",
7159
+ top: 0,
7160
+ left: 0,
7161
+ visibility: "hidden"
7162
+ });
6592
7163
  useLayoutEffect(() => {
6593
7164
  const element = ref.current;
6594
7165
  const anchor = element?.parentElement ?? null;
@@ -6604,14 +7175,35 @@ function Popover({
6604
7175
  );
6605
7176
  const fitsBelow = rect.bottom + gutter + height <= window.innerHeight;
6606
7177
  const fitsAbove = rect.top - gutter - height >= 0;
7178
+ if (!fitsBelow && !fitsAbove) {
7179
+ const top = Math.max(gutter, window.innerHeight - height - gutter);
7180
+ setStyle({
7181
+ position: "fixed",
7182
+ left,
7183
+ top,
7184
+ maxHeight: window.innerHeight - top - gutter,
7185
+ overflowY: "auto"
7186
+ });
7187
+ return;
7188
+ }
6607
7189
  const down = placement === "down" ? fitsBelow || !fitsAbove : !fitsAbove && fitsBelow;
6608
7190
  setStyle(
6609
7191
  down ? { position: "fixed", left, top: rect.bottom + gutter } : { position: "fixed", left, bottom: window.innerHeight - rect.top + gutter }
6610
7192
  );
6611
7193
  };
6612
7194
  place();
7195
+ const frame = requestAnimationFrame(place);
7196
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => place());
7197
+ observer?.observe(document.body);
7198
+ observer?.observe(element);
6613
7199
  window.addEventListener("resize", place);
6614
- return () => window.removeEventListener("resize", place);
7200
+ window.addEventListener("scroll", place, true);
7201
+ return () => {
7202
+ cancelAnimationFrame(frame);
7203
+ observer?.disconnect();
7204
+ window.removeEventListener("resize", place);
7205
+ window.removeEventListener("scroll", place, true);
7206
+ };
6615
7207
  }, [placement]);
6616
7208
  useEffect3(() => {
6617
7209
  const onKey = (event) => {
@@ -6662,22 +7254,27 @@ function MeetingOverlays() {
6662
7254
  const room = useRoom();
6663
7255
  return /* @__PURE__ */ jsxs2(Fragment, { children: [
6664
7256
  /* @__PURE__ */ jsx3(ReactionOverlay, { reactions }),
6665
- notice !== null && notice.kind !== "removed" ? /* @__PURE__ */ jsxs2("div", { className: "mx-meet__notice-bar", role: "alert", children: [
6666
- /* @__PURE__ */ jsx3("span", { children: notice.text }),
6667
- notice.kind === "ask-unmute" && room.local?.micEnabled !== true ? /* @__PURE__ */ jsx3(
6668
- "button",
6669
- {
6670
- type: "button",
6671
- className: "mx-meet__primary mx-meet__primary--small",
6672
- onClick: () => {
6673
- dismiss();
6674
- void room.toggleMicrophone();
6675
- },
6676
- children: "Unmute"
6677
- }
6678
- ) : null,
6679
- /* @__PURE__ */ jsx3("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismiss, children: /* @__PURE__ */ jsx3(CloseIcon, { width: 14, height: 14 }) })
6680
- ] }) : null
7257
+ notice !== null && notice.kind !== "removed" ? (
7258
+ // A host's request ("the host is asking you to unmute"), not an error:
7259
+ // an assertive status, so it is announced at once without being filed
7260
+ // as a failure (design-system error-box census).
7261
+ /* @__PURE__ */ jsxs2("div", { className: "mx-meet__notice-bar", role: "status", "aria-live": "assertive", children: [
7262
+ /* @__PURE__ */ jsx3("span", { children: notice.text }),
7263
+ notice.kind === "ask-unmute" && room.local?.micEnabled !== true ? /* @__PURE__ */ jsx3(
7264
+ "button",
7265
+ {
7266
+ type: "button",
7267
+ className: "mx-meet__primary mx-meet__primary--small",
7268
+ onClick: () => {
7269
+ dismiss();
7270
+ void room.toggleMicrophone();
7271
+ },
7272
+ children: "Unmute"
7273
+ }
7274
+ ) : null,
7275
+ /* @__PURE__ */ jsx3("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismiss, children: /* @__PURE__ */ jsx3(CloseIcon, { width: 14, height: 14 }) })
7276
+ ] })
7277
+ ) : null
6681
7278
  ] });
6682
7279
  }
6683
7280
  function ShortcutsSheet({ onClose }) {
@@ -6983,11 +7580,15 @@ function PersonMenu({
6983
7580
  const pinned = snapshot?.pinned === participant.identity;
6984
7581
  const spotlit = snapshot?.spotlight === participant.identity;
6985
7582
  const hasAccount = participant.userId !== null;
7583
+ const [refused, setRefused] = useState3(false);
6986
7584
  const act = (request) => {
7585
+ setRefused(false);
6987
7586
  void controls.run(request).then((result) => {
6988
7587
  if (result !== null) onClose();
7588
+ else setRefused(true);
6989
7589
  });
6990
7590
  };
7591
+ const refusal2 = refused && controls.notice?.failed === true ? /* @__PURE__ */ jsx3(ErrorBox, { as: "p", className: "mx-meet__error", message: controls.notice.text, operation: "Change this participant", origin: "meet/components", children: controls.notice.text }) : null;
6991
7592
  if (mode === "rename" || mode === "report") {
6992
7593
  const renaming = mode === "rename";
6993
7594
  return /* @__PURE__ */ jsx3(Popover, { label: renaming ? "Rename" : "Report", onClose, placement, children: /* @__PURE__ */ jsxs2(
@@ -7025,7 +7626,7 @@ function PersonMenu({
7025
7626
  }
7026
7627
  )
7027
7628
  ] }),
7028
- controls.notice?.failed === true ? /* @__PURE__ */ jsx3("p", { className: "mx-meet__error", children: controls.notice.text }) : null,
7629
+ refusal2,
7029
7630
  /* @__PURE__ */ jsxs2("div", { className: "mx-meet__menu-actions", children: [
7030
7631
  /* @__PURE__ */ jsx3("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Back" }),
7031
7632
  /* @__PURE__ */ jsx3("button", { type: "submit", className: "mx-meet__send", disabled: text3.trim().length === 0 || controls.busy, children: renaming ? "Rename" : "Report" })
@@ -7038,6 +7639,7 @@ function PersonMenu({
7038
7639
  const removing = mode === "remove";
7039
7640
  return /* @__PURE__ */ jsx3(Popover, { label: removing ? "Remove" : "Make host", onClose, placement, children: /* @__PURE__ */ jsxs2("div", { className: "mx-meet__menu-form", children: [
7040
7641
  /* @__PURE__ */ jsx3("p", { className: "mx-meet__confirm-text", children: removing ? `Remove ${participant.displayName}? They leave immediately and cannot rejoin this meeting.` : `Make ${participant.displayName} the host? They get every host control and own this meeting. You stay on as a co-host.` }),
7642
+ refusal2,
7041
7643
  /* @__PURE__ */ jsxs2("div", { className: "mx-meet__menu-actions", children: [
7042
7644
  /* @__PURE__ */ jsx3("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Cancel" }),
7043
7645
  /* @__PURE__ */ jsx3(
@@ -7068,11 +7670,15 @@ function PersonMenu({
7068
7670
  ]
7069
7671
  }
7070
7672
  ),
7071
- !self && !participant.isAgent && onMessage !== void 0 ? /* @__PURE__ */ jsxs2(
7673
+ !self && !participant.isAgent && (onMessage !== void 0 || host !== null) ? /* @__PURE__ */ jsxs2(
7072
7674
  MenuItem,
7073
7675
  {
7074
7676
  onSelect: () => {
7075
- onMessage(participant.identity);
7677
+ if (onMessage !== void 0) onMessage(participant.identity);
7678
+ else
7679
+ host?.store.patch({
7680
+ privateChatRequest: { to: participant.identity, at: Date.now() }
7681
+ });
7076
7682
  onClose();
7077
7683
  },
7078
7684
  children: [
@@ -7699,7 +8305,7 @@ function PreJoinBody({
7699
8305
  microphone,
7700
8306
  ...meetingId !== void 0 ? { meetingId } : {},
7701
8307
  ...slug !== void 0 ? { slug } : {}
7702
- }).then(() => onJoined?.()).finally(() => setJoining(false));
8308
+ }).then(() => onJoined?.()).catch(() => void 0).finally(() => setJoining(false));
7703
8309
  },
7704
8310
  children: joining ? "Joining\u2026" : "Join now"
7705
8311
  }
@@ -7835,7 +8441,7 @@ function MeetingRoomBody({
7835
8441
  /* @__PURE__ */ jsx3("h1", { className: "mx-meet__title", children: snapshot?.meeting?.title ?? "Meeting" }),
7836
8442
  /* @__PURE__ */ jsxs2("span", { className: "mx-meet__meta", children: [
7837
8443
  participantSummary(snapshot?.participants ?? []),
7838
- elapsed > 0 ? ` \xB7 ${formatDurationMs2(elapsed)}` : ""
8444
+ elapsed > 0 ? ` \xB7 ${formatDurationMs3(elapsed)}` : ""
7839
8445
  ] }),
7840
8446
  /* @__PURE__ */ jsx3(RecordingIndicator, {}),
7841
8447
  snapshot?.locked === true ? /* @__PURE__ */ jsxs2("span", { className: "mx-meet__badge", children: [
@@ -8141,7 +8747,10 @@ function MeetingRecordBody({
8141
8747
  const blocks = groupTranscript(bundle.transcript, bundle.names);
8142
8748
  const notice = recordingNotice(bundle.recording);
8143
8749
  const fileId = playableRecordingFileId(bundle.recording);
8144
- const attended = bundle.attendees.filter((person) => person.admissionState !== "denied");
8750
+ const attended = uniqueAttendees(
8751
+ bundle.attendees.filter((person) => person.admissionState !== "denied"),
8752
+ bundle.names
8753
+ );
8145
8754
  const restrictionNotices = summarizeMeetingRecordRestrictions(bundle.restrictions);
8146
8755
  const guestFinishedMeeting = host.identity.userId === null && meeting.endedAt !== null;
8147
8756
  const everyRecordPartRefused = ["attendance", "notes", "transcript", "recording"].every(
@@ -8611,18 +9220,11 @@ function createMeetingInviteHandler(options) {
8611
9220
  }
8612
9221
 
8613
9222
  // src/core/management.ts
8614
- async function refusal(response, operation) {
8615
- let body = null;
8616
- try {
8617
- body = await response.json();
8618
- } catch {
8619
- body = null;
8620
- }
8621
- const said = readServerRefusal(body);
8622
- return new MeetError(
8623
- response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
8624
- `${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
8625
- said?.remedy || "The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
9223
+ function refusal(response, operation) {
9224
+ return refusalFromResponse(
9225
+ response,
9226
+ operation,
9227
+ "The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
8626
9228
  );
8627
9229
  }
8628
9230
  function count(body, key) {
@@ -8661,6 +9263,7 @@ async function pushMeetingToGoogleCalendar(api, meetingId, target) {
8661
9263
  return await response.json();
8662
9264
  }
8663
9265
  export {
9266
+ ATTENDANCE_CSV_HEADER,
8664
9267
  AiPanel,
8665
9268
  AttendanceNotice,
8666
9269
  Avatar,
@@ -8764,6 +9367,7 @@ export {
8764
9367
  TRANSCRIPTION_TOPIC,
8765
9368
  WifiLowIcon,
8766
9369
  absenceSentence,
9370
+ activeWrapUpSummary,
8767
9371
  announceMeeting,
8768
9372
  applyRoomEvent,
8769
9373
  asCallId,
@@ -8772,6 +9376,10 @@ export {
8772
9376
  asParticipantIdentity,
8773
9377
  asRoomName,
8774
9378
  asUserId,
9379
+ attendanceCsv,
9380
+ attendanceReport,
9381
+ attendanceTotals,
9382
+ attendeeKey,
8775
9383
  callInviteAction,
8776
9384
  callInviteText,
8777
9385
  callStateLabel,
@@ -8780,6 +9388,7 @@ export {
8780
9388
  canTransition,
8781
9389
  captionSegment,
8782
9390
  changesPatch,
9391
+ clockPosition,
8783
9392
  computeStage,
8784
9393
  countLabel,
8785
9394
  createCallCenter,
@@ -8820,11 +9429,14 @@ export {
8820
9429
  learnName,
8821
9430
  learnNames,
8822
9431
  listenOnlyNotice,
9432
+ loadFollowUpDraft,
8823
9433
  loadGuestMeetingRecord,
9434
+ loadMeetingChat,
8824
9435
  loadMeetingRecord,
8825
9436
  mapConnectionState,
8826
9437
  mapQuality,
8827
9438
  meetingInviteAction,
9439
+ meetingLength,
8828
9440
  meetingLink,
8829
9441
  meetingPath,
8830
9442
  meetingSlug,
@@ -8839,16 +9451,19 @@ export {
8839
9451
  noteTakerLabel,
8840
9452
  noteTakerParticipant,
8841
9453
  noteTakerStatus,
9454
+ notesDocument,
8842
9455
  orderParticipants,
8843
9456
  parseRoomToken,
8844
9457
  participantSummary,
8845
9458
  playableRecordingFileId,
8846
9459
  projectInvitee,
9460
+ projectLibraryRecording,
8847
9461
  projectNote,
8848
9462
  projectOccurrence,
8849
9463
  projectOverride,
8850
9464
  projectParticipant,
8851
9465
  projectRsvpInvitation,
9466
+ projectSearchHit,
8852
9467
  projectSegment,
8853
9468
  projectUpcoming,
8854
9469
  pushMeetingToGoogleCalendar,
@@ -8856,12 +9471,19 @@ export {
8856
9471
  readableTranscript,
8857
9472
  recordingLabel,
8858
9473
  recordingNotice,
9474
+ recordingOffsetMs,
8859
9475
  roleOf,
8860
9476
  roomTopic,
9477
+ searchTranscript,
9478
+ sendFollowUp,
8861
9479
  setGuestRecordAccess,
8862
9480
  settingsPatch,
8863
9481
  snapshotParticipant,
9482
+ snippetRuns,
9483
+ summarizeMeetingRecordRestrictions,
9484
+ transcriptDocument,
8864
9485
  unavailableFrom,
9486
+ uniqueAttendees,
8865
9487
  useAudioLevel,
8866
9488
  useCalls,
8867
9489
  useCanModerate,
@@ -8896,6 +9518,7 @@ export {
8896
9518
  useStage,
8897
9519
  useTrack,
8898
9520
  userTopic,
8899
- wasRefused
9521
+ wasRefused,
9522
+ wrapUpStatus
8900
9523
  };
8901
9524
  //# sourceMappingURL=react.js.map