@ai-matrx/meet 0.10.0 → 0.10.1

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/core.cjs CHANGED
@@ -80,6 +80,7 @@ __export(core_exports, {
80
80
  activeWrapUpSummary: () => activeWrapUpSummary,
81
81
  activityAttention: () => activityAttention,
82
82
  announceMeeting: () => announceMeeting,
83
+ appPanelOf: () => appPanelOf,
83
84
  applyCollabPacket: () => applyCollabPacket,
84
85
  applyRoomEvent: () => applyRoomEvent,
85
86
  asCallId: () => asCallId,
@@ -131,6 +132,7 @@ __export(core_exports, {
131
132
  defaultCaptionLanguage: () => defaultCaptionLanguage,
132
133
  displayNameFor: () => displayNameFor,
133
134
  fileDownloadPath: () => fileDownloadPath,
135
+ focusFromRow: () => focusFromRow,
134
136
  gridColumns: () => gridColumns,
135
137
  groupTranscript: () => groupTranscript,
136
138
  guestRecordAccessExplanation: () => guestRecordAccessExplanation,
@@ -145,6 +147,7 @@ __export(core_exports, {
145
147
  isLinkFollower: () => isLinkFollower,
146
148
  isListenOnly: () => isListenOnly,
147
149
  isNoteTaker: () => isNoteTaker,
150
+ isObserverRole: () => isObserverRole,
148
151
  isRealName: () => isRealName,
149
152
  isRoomEventTopic: () => isRoomEventTopic,
150
153
  learnName: () => learnName,
@@ -209,6 +212,7 @@ __export(core_exports, {
209
212
  roleOf: () => roleOf,
210
213
  roomTopic: () => roomTopic,
211
214
  rootAttributes: () => rootAttributes,
215
+ scheduleSettingsPatch: () => scheduleSettingsPatch,
212
216
  searchTranscript: () => searchTranscript,
213
217
  sendFollowUp: () => sendFollowUp,
214
218
  setCaptionLanguage: () => setCaptionLanguage,
@@ -620,7 +624,8 @@ function parseRoomToken(body, roomName) {
620
624
  lobby: readBoolean(payload, "lobby", false),
621
625
  admissionState: readString(payload, "admission_state") ?? void 0,
622
626
  pollSeconds: typeof payload["poll_seconds"] === "number" ? payload["poll_seconds"] : void 0,
623
- breakoutRoomId: readString(payload, "breakout_room_id") ?? null
627
+ breakoutRoomId: readString(payload, "breakout_room_id") ?? null,
628
+ observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone"
624
629
  };
625
630
  }
626
631
  function createTokenClient(options) {
@@ -669,6 +674,7 @@ function createTokenClient(options) {
669
674
  ...input.meetingId !== void 0 ? { meeting_id: input.meetingId } : {},
670
675
  ...input.slug !== void 0 ? { slug: input.slug } : {},
671
676
  ...input.breakoutRoomId != null ? { breakout_room_id: input.breakoutRoomId } : {},
677
+ ...input.observe === true ? { observe: true } : {},
672
678
  organization_id: input.identity.organizationId,
673
679
  display_name: input.identity.displayName,
674
680
  device_id: input.identity.deviceId,
@@ -740,7 +746,8 @@ function createTokenClient(options) {
740
746
  return {
741
747
  get(input) {
742
748
  const entry = cache.get(input.roomName);
743
- const sameRoom = entry !== void 0 && (entry.token.breakoutRoomId ?? null) === (input.breakoutRoomId ?? null);
749
+ const sameRoom = entry !== void 0 && (entry.token.breakoutRoomId ?? null) === (input.breakoutRoomId ?? null) && // An observer's pass is not a participant's, and back (MD-16).
750
+ entry.token.role === "viewer" === (input.observe === true);
744
751
  if (entry !== void 0 && sameRoom) {
745
752
  if (entry.inFlight !== null) return entry.inFlight;
746
753
  if (!expired(entry)) return Promise.resolve(entry.token);
@@ -1266,6 +1273,9 @@ function isLinkFollower(standing) {
1266
1273
  function isHostRole(role) {
1267
1274
  return role === "host" || role === "cohost";
1268
1275
  }
1276
+ function isObserverRole(role) {
1277
+ return role === "viewer";
1278
+ }
1269
1279
  var WAITING_ADMISSION_STATES = ["waiting_host", "knocking"];
1270
1280
 
1271
1281
  // src/core/calls.ts
@@ -1470,388 +1480,49 @@ function createCallCenter(options) {
1470
1480
  );
1471
1481
  return invite;
1472
1482
  },
1473
- async accept(id, mode) {
1474
- const held = incoming.get(id);
1475
- if (held !== void 0 && !canTransition(held.state, "connecting")) {
1476
- throw new MeetError(
1477
- "conflict",
1478
- `meet.calls.accept: this call is already ${held.state}`,
1479
- "A late accept must not resurrect a settled call. Close the ringing UI; the caller has already moved on."
1480
- );
1481
- }
1482
- const settled = await repository.settleCallInvite(id, "connecting", null);
1483
- settleLocal(settled);
1484
- channel?.send(MEET_EVENTS.settled, settled);
1485
- to(settled.callerUserId).send(MEET_EVENTS.settled, { ...settled, mode });
1486
- return settled;
1487
- },
1488
- async decline(id, message) {
1489
- const settled = await repository.settleCallInvite(id, "declined", message ?? null);
1490
- settleLocal(settled);
1491
- channel?.send(MEET_EVENTS.settled, settled);
1492
- to(settled.callerUserId).send(MEET_EVENTS.settled, settled);
1493
- return settled;
1494
- },
1495
- async cancel(id) {
1496
- const settled = await repository.settleCallInvite(id, "cancelled", null);
1497
- clearRing(id);
1498
- if (outgoing?.id === id) outgoing = settled;
1499
- for (const callee of settled.calleeUserIds) {
1500
- to(callee).send(MEET_EVENTS.cancel, settled);
1501
- }
1502
- return settled;
1503
- },
1504
- incoming: () => [...incoming.values()],
1505
- outgoing: () => outgoing,
1506
- ingest,
1507
- dispose() {
1508
- for (const id of [...timeouts.keys()]) clearRing(asCallId(id));
1509
- incoming.clear();
1510
- for (const handle of outbound.values()) handle.close();
1511
- outbound.clear();
1512
- channel?.close();
1513
- channel = null;
1514
- }
1515
- };
1516
- }
1517
-
1518
- // src/core/collab.ts
1519
- var EMPTY_COLLAB = {
1520
- loaded: false,
1521
- error: null,
1522
- settings: null,
1523
- polls: [],
1524
- questions: [],
1525
- strokes: [],
1526
- breakout: null,
1527
- broadcast: null,
1528
- moveRequest: null,
1529
- panelRequest: null,
1530
- captionLanguage: null,
1531
- captionTranslations: {},
1532
- captionTranslationError: null
1533
- };
1534
- function obj(value) {
1535
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
1536
- }
1537
- function str(value) {
1538
- return typeof value === "string" ? value : null;
1539
- }
1540
- function num(value) {
1541
- return typeof value === "number" && Number.isFinite(value) ? value : null;
1542
- }
1543
- function strings(value) {
1544
- return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
1545
- }
1546
- function pollFromRow(row) {
1547
- const id = str(row["id"]);
1548
- if (id === null) return null;
1549
- const options = [];
1550
- for (const raw of Array.isArray(row["options"]) ? row["options"] : []) {
1551
- const option = obj(raw);
1552
- const optionId = option === null ? null : str(option["id"]);
1553
- if (option === null || optionId === null) continue;
1554
- const votes = num(option["votes"]);
1555
- options.push({
1556
- id: optionId,
1557
- text: str(option["text"]) ?? "",
1558
- ...votes !== null ? { votes } : {},
1559
- ...Array.isArray(option["voters"]) ? { voters: strings(option["voters"]) } : {}
1560
- });
1561
- }
1562
- const state = str(row["state"]);
1563
- return {
1564
- id,
1565
- question: str(row["question"]) ?? "",
1566
- options,
1567
- multiple: row["multiple"] === true,
1568
- anonymous: row["anonymous"] === true,
1569
- state: state === "live" || state === "closed" ? state : "draft",
1570
- resultsShared: row["results_shared"] === true,
1571
- voterCount: num(row["voter_count"]),
1572
- myVote: strings(row["my_vote"]),
1573
- createdByName: str(row["created_by_name"]) ?? "",
1574
- createdAt: str(row["created_at"])
1575
- };
1576
- }
1577
- function questionFromRow(row) {
1578
- const id = str(row["id"]);
1579
- if (id === null) return null;
1580
- const state = str(row["state"]);
1581
- return {
1582
- id,
1583
- text: str(row["text"]) ?? "",
1584
- askerName: str(row["asker_name"]) ?? "",
1585
- anonymous: row["anonymous"] === true,
1586
- state: state === "answered" || state === "dismissed" ? state : "open",
1587
- answer: str(row["answer"]),
1588
- answeredByName: str(row["answered_by_name"]),
1589
- upvotes: num(row["upvotes"]) ?? 0,
1590
- upvotedByMe: row["upvoted_by_me"] === true,
1591
- mine: row["mine"] === true,
1592
- createdAt: str(row["created_at"])
1593
- };
1594
- }
1595
- function strokeFromRow(row) {
1596
- const id = str(row["id"]);
1597
- if (id === null || !Array.isArray(row["points"])) return null;
1598
- const points = [];
1599
- for (const raw of row["points"]) {
1600
- if (Array.isArray(raw) && typeof raw[0] === "number" && typeof raw[1] === "number") {
1601
- points.push([raw[0], raw[1]]);
1602
- }
1603
- }
1604
- return {
1605
- id,
1606
- points,
1607
- color: str(row["color"]) ?? "#111827",
1608
- width: num(row["width"]) ?? 3,
1609
- tool: row["tool"] === "highlighter" ? "highlighter" : "pen",
1610
- by: str(row["by"]) ?? "",
1611
- byName: str(row["by_name"]) ?? ""
1612
- };
1613
- }
1614
- function broadcastFrom(value) {
1615
- const row = obj(value);
1616
- const text3 = row === null ? null : str(row["text"]);
1617
- if (row === null || text3 === null) return null;
1618
- return { text: text3, by: str(row["by"]) ?? "", at: str(row["at"]) ?? "" };
1619
- }
1620
- function planFromRow(row) {
1621
- const id = str(row["id"]);
1622
- if (id === null) return null;
1623
- const rooms = [];
1624
- for (const raw of Array.isArray(row["rooms"]) ? row["rooms"] : []) {
1625
- const room = obj(raw);
1626
- const roomId = room === null ? null : str(room["id"]);
1627
- if (room === null || roomId === null) continue;
1628
- rooms.push({ id: roomId, name: str(room["name"]) ?? roomId, identities: strings(room["identities"]) });
1629
- }
1630
- return {
1631
- id,
1632
- state: row["state"] === "open" ? "open" : "closed",
1633
- rooms,
1634
- allowChoose: row["allow_choose"] === true,
1635
- minutes: num(row["minutes"]) ?? 0,
1636
- openedAt: str(row["opened_at"]),
1637
- endsAt: str(row["ends_at"]),
1638
- closedAt: str(row["closed_at"]),
1639
- broadcasts: (Array.isArray(row["broadcasts"]) ? row["broadcasts"] : []).map(broadcastFrom).filter((b) => b !== null)
1640
- };
1641
- }
1642
- function settingsFromRow(row) {
1643
- if (row === null) return null;
1644
- return {
1645
- pollsEnabled: row["polls_enabled"] === true,
1646
- pollMaxOptions: num(row["poll_max_options"]) ?? 10,
1647
- qaEnabled: row["qa_enabled"] === true,
1648
- qaAnonymousAllowed: row["qa_anonymous_allowed"] === true,
1649
- whiteboardEnabled: row["whiteboard_enabled"] === true,
1650
- breakoutsEnabled: row["breakouts_enabled"] === true,
1651
- breakoutMaxRooms: num(row["breakout_max_rooms"]) ?? 20,
1652
- breakoutDefaultMinutes: num(row["breakout_default_minutes"]) ?? 15,
1653
- captionTranslationEnabled: row["caption_translation_enabled"] === true,
1654
- captionTranslationLanguage: str(row["caption_translation_language"]) ?? ""
1655
- };
1656
- }
1657
- function collabFromState(body, previous) {
1658
- const list = (key, parse) => (Array.isArray(body[key]) ? body[key] : []).map((raw) => {
1659
- const row = obj(raw);
1660
- return row === null ? null : parse(row);
1661
- }).filter((item) => item !== null);
1662
- const breakoutRow = obj(body["breakout"]);
1663
- return {
1664
- ...previous,
1665
- loaded: true,
1666
- error: null,
1667
- settings: settingsFromRow(obj(body["settings"])),
1668
- polls: list("polls", pollFromRow),
1669
- questions: list("questions", questionFromRow),
1670
- strokes: list("strokes", strokeFromRow),
1671
- breakout: breakoutRow === null ? null : planFromRow(breakoutRow)
1672
- };
1673
- }
1674
- function upsert(items, next) {
1675
- const index = items.findIndex((item) => item.id === next.id);
1676
- if (index === -1) return [...items, next];
1677
- const copy = items.slice();
1678
- copy[index] = next;
1679
- return copy;
1680
- }
1681
- function withItem(collab, row, fromRoom = false) {
1682
- const kind = str(row["kind"]);
1683
- if (kind === "poll") {
1684
- const poll = pollFromRow(row);
1685
- if (poll === null) return collab;
1686
- const known = collab.polls.find((p) => p.id === poll.id);
1687
- let merged = fromRoom && known !== void 0 ? { ...poll, myVote: known.myVote } : poll;
1688
- if (fromRoom && known !== void 0 && !poll.options.some((o) => o.votes !== void 0)) {
1689
- const counted = new Map(known.options.map((o) => [o.id, o]));
1690
- merged = {
1691
- ...merged,
1692
- voterCount: known.voterCount,
1693
- options: merged.options.map((o) => {
1694
- const had = counted.get(o.id);
1695
- return had === void 0 ? o : { ...o, votes: had.votes, voters: had.voters };
1696
- })
1697
- };
1698
- }
1699
- return { ...collab, polls: upsert(collab.polls, merged) };
1700
- }
1701
- if (kind === "question") {
1702
- const question = questionFromRow(row);
1703
- if (question === null) return collab;
1704
- const known = collab.questions.find((q) => q.id === question.id);
1705
- const merged = fromRoom && known !== void 0 ? { ...question, mine: known.mine, upvotedByMe: known.upvotedByMe } : question;
1706
- return { ...collab, questions: upsert(collab.questions, merged) };
1707
- }
1708
- if (kind === "stroke") {
1709
- const stroke = strokeFromRow(row);
1710
- if (stroke === null) return collab;
1711
- return { ...collab, strokes: upsert(collab.strokes, stroke) };
1712
- }
1713
- if (kind === "breakout") {
1714
- const plan = planFromRow(row);
1715
- if (plan === null) return collab;
1716
- return { ...collab, breakout: plan };
1717
- }
1718
- return collab;
1719
- }
1720
- function without(collab, ids) {
1721
- const gone = new Set(ids);
1722
- return {
1723
- ...collab,
1724
- polls: collab.polls.filter((p) => !gone.has(p.id)),
1725
- questions: collab.questions.filter((q) => !gone.has(q.id)),
1726
- strokes: collab.strokes.filter((s) => !gone.has(s.id))
1727
- };
1728
- }
1729
- function applyCollabPacket(collab, payload, context) {
1730
- const item = obj(payload["item"]);
1731
- if (item !== null) {
1732
- const kind = str(item["kind"]);
1733
- let next = withItem(collab, item, true);
1734
- if (kind === "poll" && str(item["state"]) === "live") {
1735
- const before = collab.polls.find((p) => p.id === str(item["id"]));
1736
- if (before === void 0 || before.state !== "live") {
1737
- next = { ...next, panelRequest: { panel: "polls", at: context.now } };
1738
- }
1739
- }
1740
- if (kind === "breakout") {
1741
- const plan = planFromRow(item);
1742
- if (plan !== null && plan.state === "closed") {
1743
- next = { ...next, moveRequest: { roomId: null, at: context.now } };
1744
- }
1745
- }
1746
- const move = obj(payload["move"]);
1747
- if (move !== null && str(move["identity"]) === context.localIdentity) {
1748
- next = { ...next, moveRequest: { roomId: str(move["room_id"]), at: context.now } };
1749
- }
1750
- return { next };
1751
- }
1752
- if (Array.isArray(payload["removed"])) {
1753
- return { next: without(collab, strings(payload["removed"])) };
1754
- }
1755
- if (Array.isArray(payload["hidden"])) {
1756
- return context.isHost ? { next: null, dropped: "hosts keep dismissed questions" } : { next: without(collab, strings(payload["hidden"])) };
1757
- }
1758
- if (payload["cleared"] === "whiteboard") {
1759
- return { next: { ...collab, strokes: [] } };
1760
- }
1761
- const broadcast = broadcastFrom(payload["broadcast"]);
1762
- if (broadcast !== null) {
1763
- return { next: { ...collab, broadcast } };
1764
- }
1765
- return { next: null, dropped: "a collab packet with nothing this version understands" };
1766
- }
1767
- function assignedRoom(plan, identity) {
1768
- if (plan === null || plan.state !== "open" || identity === null) return null;
1769
- return plan.rooms.find((room) => room.identities.includes(identity)) ?? null;
1770
- }
1771
- function autoAssign(identities, rooms, exclude = []) {
1772
- const count2 = Math.max(1, Math.floor(rooms));
1773
- const out = Array.from({ length: count2 }, () => []);
1774
- const skip = new Set(exclude);
1775
- let index = 0;
1776
- for (const identity of identities) {
1777
- if (skip.has(identity)) continue;
1778
- out[index % count2].push(identity);
1779
- index += 1;
1780
- }
1781
- return out;
1782
- }
1783
- function breakoutRemainingMs(plan, now) {
1784
- if (plan === null || plan.state !== "open" || plan.endsAt === null) return null;
1785
- const end = Date.parse(plan.endsAt);
1786
- return Number.isNaN(end) ? null : Math.max(0, end - now);
1787
- }
1788
- function defaultCaptionLanguage(settings, viewerLanguage) {
1789
- const configured = settings?.captionTranslationLanguage.trim() ?? "";
1790
- if (configured.length > 0) return configured;
1791
- const own = (viewerLanguage ?? "").trim();
1792
- return own.length > 0 ? own : "en";
1793
- }
1794
- var CAPTION_LANGUAGES = [
1795
- { tag: "en", name: "English" },
1796
- { tag: "es", name: "Spanish" },
1797
- { tag: "fr", name: "French" },
1798
- { tag: "de", name: "German" },
1799
- { tag: "it", name: "Italian" },
1800
- { tag: "pt", name: "Portuguese" },
1801
- { tag: "nl", name: "Dutch" },
1802
- { tag: "pl", name: "Polish" },
1803
- { tag: "ru", name: "Russian" },
1804
- { tag: "uk", name: "Ukrainian" },
1805
- { tag: "tr", name: "Turkish" },
1806
- { tag: "ar", name: "Arabic" },
1807
- { tag: "he", name: "Hebrew" },
1808
- { tag: "fa", name: "Persian" },
1809
- { tag: "hi", name: "Hindi" },
1810
- { tag: "bn", name: "Bengali" },
1811
- { tag: "zh", name: "Chinese" },
1812
- { tag: "ja", name: "Japanese" },
1813
- { tag: "ko", name: "Korean" },
1814
- { tag: "vi", name: "Vietnamese" },
1815
- { tag: "th", name: "Thai" },
1816
- { tag: "id", name: "Indonesian" },
1817
- { tag: "sv", name: "Swedish" }
1818
- ];
1819
- async function loadMeetingActivities(api, meetingId, meetingPass = null) {
1820
- let response;
1821
- try {
1822
- response = await api.request({
1823
- path: `${MEET_ROUTES.collab}?meeting_id=${encodeURIComponent(meetingId)}`,
1824
- method: "GET",
1825
- operation: "meet.loadMeetingActivities",
1826
- requireAuth: meetingPass === null,
1827
- ...meetingPass !== null ? { headers: { [MEETING_PASS_HEADER]: meetingPass } } : {}
1828
- });
1829
- } catch (error) {
1830
- return {
1831
- collab: null,
1832
- refusal: {
1833
- message: `Polls, questions and the whiteboard could not be read (${error.message}).`,
1834
- remedy: error.remedy ?? "Reload the page."
1483
+ async accept(id, mode) {
1484
+ const held = incoming.get(id);
1485
+ if (held !== void 0 && !canTransition(held.state, "connecting")) {
1486
+ throw new MeetError(
1487
+ "conflict",
1488
+ `meet.calls.accept: this call is already ${held.state}`,
1489
+ "A late accept must not resurrect a settled call. Close the ringing UI; the caller has already moved on."
1490
+ );
1835
1491
  }
1836
- };
1837
- }
1838
- if (!response.ok) {
1839
- let body = null;
1840
- try {
1841
- body = await response.json();
1842
- } catch {
1843
- body = null;
1844
- }
1845
- const said = readServerRefusal(body);
1846
- return {
1847
- collab: null,
1848
- refusal: {
1849
- message: said?.reason ?? `Polls and questions could not be read (the server answered ${response.status}).`,
1850
- remedy: said?.remedy || "Reload the page."
1492
+ const settled = await repository.settleCallInvite(id, "connecting", null);
1493
+ settleLocal(settled);
1494
+ channel?.send(MEET_EVENTS.settled, settled);
1495
+ to(settled.callerUserId).send(MEET_EVENTS.settled, { ...settled, mode });
1496
+ return settled;
1497
+ },
1498
+ async decline(id, message) {
1499
+ const settled = await repository.settleCallInvite(id, "declined", message ?? null);
1500
+ settleLocal(settled);
1501
+ channel?.send(MEET_EVENTS.settled, settled);
1502
+ to(settled.callerUserId).send(MEET_EVENTS.settled, settled);
1503
+ return settled;
1504
+ },
1505
+ async cancel(id) {
1506
+ const settled = await repository.settleCallInvite(id, "cancelled", null);
1507
+ clearRing(id);
1508
+ if (outgoing?.id === id) outgoing = settled;
1509
+ for (const callee of settled.calleeUserIds) {
1510
+ to(callee).send(MEET_EVENTS.cancel, settled);
1851
1511
  }
1852
- };
1853
- }
1854
- return { collab: collabFromState(await response.json(), EMPTY_COLLAB), refusal: null };
1512
+ return settled;
1513
+ },
1514
+ incoming: () => [...incoming.values()],
1515
+ outgoing: () => outgoing,
1516
+ ingest,
1517
+ dispose() {
1518
+ for (const id of [...timeouts.keys()]) clearRing(asCallId(id));
1519
+ incoming.clear();
1520
+ for (const handle of outbound.values()) handle.close();
1521
+ outbound.clear();
1522
+ channel?.close();
1523
+ channel = null;
1524
+ }
1525
+ };
1855
1526
  }
1856
1527
 
1857
1528
  // src/core/repository.ts
@@ -2046,7 +1717,7 @@ var OCCURRENCE_STATES = ["scheduled", "moved", "cancelled"];
2046
1717
  function rsvpState(value) {
2047
1718
  return RSVP_STATES.includes(value) ? value : "needs_action";
2048
1719
  }
2049
- function num2(row, key) {
1720
+ function num(row, key) {
2050
1721
  const value = row[key];
2051
1722
  return typeof value === "number" ? value : null;
2052
1723
  }
@@ -2068,7 +1739,7 @@ function projectInvitee(row) {
2068
1739
  rsvpState: rsvpState(text(row, "rsvp_state")),
2069
1740
  respondedAt: when(row, "responded_at"),
2070
1741
  rsvpNote: text(row, "rsvp_note"),
2071
- lastSentSequence: num2(row, "last_sent_sequence"),
1742
+ lastSentSequence: num(row, "last_sent_sequence"),
2072
1743
  lastSentAt: when(row, "last_sent_at")
2073
1744
  };
2074
1745
  }
@@ -2079,10 +1750,10 @@ function projectOccurrence(row) {
2079
1750
  meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
2080
1751
  originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
2081
1752
  occurrenceStart: when(row, "occurrence_start") ?? requireText(row, "occurrence_start", operation),
2082
- durationMinutes: num2(row, "duration_minutes"),
1753
+ durationMinutes: num(row, "duration_minutes"),
2083
1754
  // An unknown state is shown as scheduled, never dropped: the row is real.
2084
1755
  state: OCCURRENCE_STATES.includes(state) ? state : "scheduled",
2085
- sequence: num2(row, "sequence") ?? 0
1756
+ sequence: num(row, "sequence") ?? 0
2086
1757
  };
2087
1758
  }
2088
1759
  function projectUpcoming(row) {
@@ -2111,9 +1782,9 @@ function projectOverride(row) {
2111
1782
  originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
2112
1783
  state: text(row, "state") === "moved" ? "moved" : "cancelled",
2113
1784
  newStart: when(row, "new_start"),
2114
- newDurationMinutes: num2(row, "new_duration_minutes"),
1785
+ newDurationMinutes: num(row, "new_duration_minutes"),
2115
1786
  reason: text(row, "reason"),
2116
- calendarSequence: num2(row, "calendar_sequence") ?? 0,
1787
+ calendarSequence: num(row, "calendar_sequence") ?? 0,
2117
1788
  deletedAt: when(row, "deleted_at")
2118
1789
  };
2119
1790
  }
@@ -2137,7 +1808,7 @@ function projectRsvpInvitation(body) {
2137
1808
  title: text(meeting, "title") ?? "Meeting",
2138
1809
  slug: requireText(meeting, "slug", operation),
2139
1810
  scheduledFor: when(meeting, "scheduled_for"),
2140
- durationMinutes: num2(meeting, "duration_minutes"),
1811
+ durationMinutes: num(meeting, "duration_minutes"),
2141
1812
  timeZone: text(meeting, "time_zone") ?? "UTC",
2142
1813
  agenda: text(meeting, "agenda"),
2143
1814
  recurrenceRule: text(meeting, "recurrence_rule"),
@@ -2365,7 +2036,7 @@ function createMeetRepository(options) {
2365
2036
  p_duration_minutes: input.durationMinutes ?? null,
2366
2037
  p_agenda: input.agenda ?? null,
2367
2038
  p_recurrence_rule: input.recurrenceRule ?? null,
2368
- p_settings: settingsPatch(input.settings ?? {})
2039
+ p_settings: scheduleSettingsPatch(input.settings ?? {}, input.appPanel, input.observers)
2369
2040
  })
2370
2041
  );
2371
2042
  return projectMeeting(row);
@@ -2700,74 +2371,447 @@ function createMeetRepository(options) {
2700
2371
  }
2701
2372
  };
2702
2373
  }
2703
- var LIBRARY_STATES = [
2704
- "idle",
2705
- "starting",
2706
- "recording",
2707
- "stopping",
2708
- "processing",
2709
- "available",
2710
- "expired",
2711
- "failed"
2712
- ];
2713
- function projectLibraryRecording(row) {
2714
- const operation = "meet.projectLibraryRecording";
2715
- const state = text(row, "state");
2716
- const role = text(row, "my_role");
2374
+ var LIBRARY_STATES = [
2375
+ "idle",
2376
+ "starting",
2377
+ "recording",
2378
+ "stopping",
2379
+ "processing",
2380
+ "available",
2381
+ "expired",
2382
+ "failed"
2383
+ ];
2384
+ function projectLibraryRecording(row) {
2385
+ const operation = "meet.projectLibraryRecording";
2386
+ const state = text(row, "state");
2387
+ const role = text(row, "my_role");
2388
+ return {
2389
+ id: requireText(row, "recording_id", operation),
2390
+ meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
2391
+ meetingTitle: text(row, "meeting_title") ?? "Meeting",
2392
+ meetingSlug: requireText(row, "meeting_slug", operation),
2393
+ role: role === "host" || role === "cohost" || role === "invitee" ? role : "attendee",
2394
+ state: LIBRARY_STATES.includes(state) ? state : "failed",
2395
+ title: text(row, "recording_title"),
2396
+ fileId: text(row, "file_id"),
2397
+ startedAt: text(row, "started_at"),
2398
+ endedAt: text(row, "ended_at"),
2399
+ durationMs: num(row, "duration_ms"),
2400
+ sizeBytes: num(row, "size_bytes"),
2401
+ failureReason: text(row, "failure_reason"),
2402
+ archivedAt: text(row, "deleted_at"),
2403
+ organizationId: asOrganizationId(requireText(row, "organization_id", operation))
2404
+ };
2405
+ }
2406
+ var SEARCH_SOURCES = [
2407
+ "title",
2408
+ "summary",
2409
+ "decision",
2410
+ "action_item",
2411
+ "note",
2412
+ "transcript"
2413
+ ];
2414
+ function projectSearchHit(row) {
2415
+ const operation = "meet.projectSearchHit";
2416
+ const source = text(row, "source");
2417
+ return {
2418
+ meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
2419
+ meetingTitle: text(row, "meeting_title") ?? "Meeting",
2420
+ meetingSlug: requireText(row, "meeting_slug", operation),
2421
+ meetingAt: text(row, "meeting_at"),
2422
+ source: SEARCH_SOURCES.includes(source) ? source : "note",
2423
+ refId: requireText(row, "ref_id", operation),
2424
+ speaker: text(row, "speaker"),
2425
+ snippet: text(row, "snippet") ?? "",
2426
+ at: text(row, "said_at"),
2427
+ rank: num(row, "rank") ?? 0
2428
+ };
2429
+ }
2430
+ function snippetRuns(snippet) {
2431
+ const runs = [];
2432
+ const pattern = /⟦([^⟧]*)⟧/g;
2433
+ let last = 0;
2434
+ for (const match of snippet.matchAll(pattern)) {
2435
+ const start = match.index ?? 0;
2436
+ if (start > last) runs.push({ text: snippet.slice(last, start), hit: false });
2437
+ runs.push({ text: match[1] ?? "", hit: true });
2438
+ last = start + match[0].length;
2439
+ }
2440
+ if (last < snippet.length) runs.push({ text: snippet.slice(last), hit: false });
2441
+ return runs;
2442
+ }
2443
+
2444
+ // src/core/app-panel.ts
2445
+ function appPanelOf(meeting) {
2446
+ const raw = meeting?.metadata?.["app_panel"];
2447
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
2448
+ const key = raw["key"];
2449
+ const recordId = raw["record_id"];
2450
+ if (typeof key !== "string" || key.length === 0) return null;
2451
+ if (typeof recordId !== "string" || recordId.length === 0) return null;
2452
+ return { key, recordId };
2453
+ }
2454
+ function scheduleSettingsPatch(settings, appPanel, observers) {
2455
+ const patch = settingsPatch(settings);
2456
+ if (appPanel !== void 0) patch["app_panel"] = { key: appPanel.key, record_id: appPanel.recordId };
2457
+ if (observers !== void 0 && observers.length > 0) patch["observers"] = [...observers];
2458
+ return patch;
2459
+ }
2460
+ function focusFromRow(value) {
2461
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
2462
+ const row = value;
2463
+ if (typeof row["panel"] !== "string") return null;
2464
+ return {
2465
+ panel: row["panel"],
2466
+ section: typeof row["section"] === "string" ? row["section"] : "",
2467
+ byName: typeof row["by_name"] === "string" ? row["by_name"] : "",
2468
+ at: typeof row["at"] === "string" ? row["at"] : ""
2469
+ };
2470
+ }
2471
+
2472
+ // src/core/collab.ts
2473
+ var EMPTY_COLLAB = {
2474
+ loaded: false,
2475
+ error: null,
2476
+ settings: null,
2477
+ polls: [],
2478
+ questions: [],
2479
+ strokes: [],
2480
+ breakout: null,
2481
+ broadcast: null,
2482
+ moveRequest: null,
2483
+ panelRequest: null,
2484
+ captionLanguage: null,
2485
+ captionTranslations: {},
2486
+ captionTranslationError: null,
2487
+ focus: null
2488
+ };
2489
+ function obj(value) {
2490
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
2491
+ }
2492
+ function str(value) {
2493
+ return typeof value === "string" ? value : null;
2494
+ }
2495
+ function num2(value) {
2496
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
2497
+ }
2498
+ function strings(value) {
2499
+ return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
2500
+ }
2501
+ function pollFromRow(row) {
2502
+ const id = str(row["id"]);
2503
+ if (id === null) return null;
2504
+ const options = [];
2505
+ for (const raw of Array.isArray(row["options"]) ? row["options"] : []) {
2506
+ const option = obj(raw);
2507
+ const optionId = option === null ? null : str(option["id"]);
2508
+ if (option === null || optionId === null) continue;
2509
+ const votes = num2(option["votes"]);
2510
+ options.push({
2511
+ id: optionId,
2512
+ text: str(option["text"]) ?? "",
2513
+ ...votes !== null ? { votes } : {},
2514
+ ...Array.isArray(option["voters"]) ? { voters: strings(option["voters"]) } : {}
2515
+ });
2516
+ }
2517
+ const state = str(row["state"]);
2518
+ return {
2519
+ id,
2520
+ question: str(row["question"]) ?? "",
2521
+ options,
2522
+ multiple: row["multiple"] === true,
2523
+ anonymous: row["anonymous"] === true,
2524
+ state: state === "live" || state === "closed" ? state : "draft",
2525
+ resultsShared: row["results_shared"] === true,
2526
+ voterCount: num2(row["voter_count"]),
2527
+ myVote: strings(row["my_vote"]),
2528
+ createdByName: str(row["created_by_name"]) ?? "",
2529
+ createdAt: str(row["created_at"])
2530
+ };
2531
+ }
2532
+ function questionFromRow(row) {
2533
+ const id = str(row["id"]);
2534
+ if (id === null) return null;
2535
+ const state = str(row["state"]);
2536
+ return {
2537
+ id,
2538
+ text: str(row["text"]) ?? "",
2539
+ askerName: str(row["asker_name"]) ?? "",
2540
+ anonymous: row["anonymous"] === true,
2541
+ state: state === "answered" || state === "dismissed" ? state : "open",
2542
+ answer: str(row["answer"]),
2543
+ answeredByName: str(row["answered_by_name"]),
2544
+ upvotes: num2(row["upvotes"]) ?? 0,
2545
+ upvotedByMe: row["upvoted_by_me"] === true,
2546
+ mine: row["mine"] === true,
2547
+ createdAt: str(row["created_at"])
2548
+ };
2549
+ }
2550
+ function strokeFromRow(row) {
2551
+ const id = str(row["id"]);
2552
+ if (id === null || !Array.isArray(row["points"])) return null;
2553
+ const points = [];
2554
+ for (const raw of row["points"]) {
2555
+ if (Array.isArray(raw) && typeof raw[0] === "number" && typeof raw[1] === "number") {
2556
+ points.push([raw[0], raw[1]]);
2557
+ }
2558
+ }
2559
+ return {
2560
+ id,
2561
+ points,
2562
+ color: str(row["color"]) ?? "#111827",
2563
+ width: num2(row["width"]) ?? 3,
2564
+ tool: row["tool"] === "highlighter" ? "highlighter" : "pen",
2565
+ by: str(row["by"]) ?? "",
2566
+ byName: str(row["by_name"]) ?? ""
2567
+ };
2568
+ }
2569
+ function broadcastFrom(value) {
2570
+ const row = obj(value);
2571
+ const text3 = row === null ? null : str(row["text"]);
2572
+ if (row === null || text3 === null) return null;
2573
+ return { text: text3, by: str(row["by"]) ?? "", at: str(row["at"]) ?? "" };
2574
+ }
2575
+ function planFromRow(row) {
2576
+ const id = str(row["id"]);
2577
+ if (id === null) return null;
2578
+ const rooms = [];
2579
+ for (const raw of Array.isArray(row["rooms"]) ? row["rooms"] : []) {
2580
+ const room = obj(raw);
2581
+ const roomId = room === null ? null : str(room["id"]);
2582
+ if (room === null || roomId === null) continue;
2583
+ rooms.push({ id: roomId, name: str(room["name"]) ?? roomId, identities: strings(room["identities"]) });
2584
+ }
2585
+ return {
2586
+ id,
2587
+ state: row["state"] === "open" ? "open" : "closed",
2588
+ rooms,
2589
+ allowChoose: row["allow_choose"] === true,
2590
+ minutes: num2(row["minutes"]) ?? 0,
2591
+ openedAt: str(row["opened_at"]),
2592
+ endsAt: str(row["ends_at"]),
2593
+ closedAt: str(row["closed_at"]),
2594
+ broadcasts: (Array.isArray(row["broadcasts"]) ? row["broadcasts"] : []).map(broadcastFrom).filter((b) => b !== null)
2595
+ };
2596
+ }
2597
+ function settingsFromRow(row) {
2598
+ if (row === null) return null;
2599
+ return {
2600
+ pollsEnabled: row["polls_enabled"] === true,
2601
+ pollMaxOptions: num2(row["poll_max_options"]) ?? 10,
2602
+ qaEnabled: row["qa_enabled"] === true,
2603
+ qaAnonymousAllowed: row["qa_anonymous_allowed"] === true,
2604
+ whiteboardEnabled: row["whiteboard_enabled"] === true,
2605
+ breakoutsEnabled: row["breakouts_enabled"] === true,
2606
+ breakoutMaxRooms: num2(row["breakout_max_rooms"]) ?? 20,
2607
+ breakoutDefaultMinutes: num2(row["breakout_default_minutes"]) ?? 15,
2608
+ captionTranslationEnabled: row["caption_translation_enabled"] === true,
2609
+ captionTranslationLanguage: str(row["caption_translation_language"]) ?? ""
2610
+ };
2611
+ }
2612
+ function collabFromState(body, previous) {
2613
+ const list = (key, parse) => (Array.isArray(body[key]) ? body[key] : []).map((raw) => {
2614
+ const row = obj(raw);
2615
+ return row === null ? null : parse(row);
2616
+ }).filter((item) => item !== null);
2617
+ const breakoutRow = obj(body["breakout"]);
2717
2618
  return {
2718
- id: requireText(row, "recording_id", operation),
2719
- meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
2720
- meetingTitle: text(row, "meeting_title") ?? "Meeting",
2721
- meetingSlug: requireText(row, "meeting_slug", operation),
2722
- role: role === "host" || role === "cohost" || role === "invitee" ? role : "attendee",
2723
- state: LIBRARY_STATES.includes(state) ? state : "failed",
2724
- title: text(row, "recording_title"),
2725
- fileId: text(row, "file_id"),
2726
- startedAt: text(row, "started_at"),
2727
- endedAt: text(row, "ended_at"),
2728
- durationMs: num2(row, "duration_ms"),
2729
- sizeBytes: num2(row, "size_bytes"),
2730
- failureReason: text(row, "failure_reason"),
2731
- archivedAt: text(row, "deleted_at"),
2732
- organizationId: asOrganizationId(requireText(row, "organization_id", operation))
2619
+ ...previous,
2620
+ loaded: true,
2621
+ error: null,
2622
+ settings: settingsFromRow(obj(body["settings"])),
2623
+ polls: list("polls", pollFromRow),
2624
+ questions: list("questions", questionFromRow),
2625
+ strokes: list("strokes", strokeFromRow),
2626
+ breakout: breakoutRow === null ? null : planFromRow(breakoutRow),
2627
+ focus: focusFromRow(body["focus"])
2733
2628
  };
2734
2629
  }
2735
- var SEARCH_SOURCES = [
2736
- "title",
2737
- "summary",
2738
- "decision",
2739
- "action_item",
2740
- "note",
2741
- "transcript"
2742
- ];
2743
- function projectSearchHit(row) {
2744
- const operation = "meet.projectSearchHit";
2745
- const source = text(row, "source");
2630
+ function upsert(items, next) {
2631
+ const index = items.findIndex((item) => item.id === next.id);
2632
+ if (index === -1) return [...items, next];
2633
+ const copy = items.slice();
2634
+ copy[index] = next;
2635
+ return copy;
2636
+ }
2637
+ function withItem(collab, row, fromRoom = false) {
2638
+ const kind = str(row["kind"]);
2639
+ if (kind === "poll") {
2640
+ const poll = pollFromRow(row);
2641
+ if (poll === null) return collab;
2642
+ const known = collab.polls.find((p) => p.id === poll.id);
2643
+ let merged = fromRoom && known !== void 0 ? { ...poll, myVote: known.myVote } : poll;
2644
+ if (fromRoom && known !== void 0 && !poll.options.some((o) => o.votes !== void 0)) {
2645
+ const counted = new Map(known.options.map((o) => [o.id, o]));
2646
+ merged = {
2647
+ ...merged,
2648
+ voterCount: known.voterCount,
2649
+ options: merged.options.map((o) => {
2650
+ const had = counted.get(o.id);
2651
+ return had === void 0 ? o : { ...o, votes: had.votes, voters: had.voters };
2652
+ })
2653
+ };
2654
+ }
2655
+ return { ...collab, polls: upsert(collab.polls, merged) };
2656
+ }
2657
+ if (kind === "question") {
2658
+ const question = questionFromRow(row);
2659
+ if (question === null) return collab;
2660
+ const known = collab.questions.find((q) => q.id === question.id);
2661
+ const merged = fromRoom && known !== void 0 ? { ...question, mine: known.mine, upvotedByMe: known.upvotedByMe } : question;
2662
+ return { ...collab, questions: upsert(collab.questions, merged) };
2663
+ }
2664
+ if (kind === "stroke") {
2665
+ const stroke = strokeFromRow(row);
2666
+ if (stroke === null) return collab;
2667
+ return { ...collab, strokes: upsert(collab.strokes, stroke) };
2668
+ }
2669
+ if (kind === "breakout") {
2670
+ const plan = planFromRow(row);
2671
+ if (plan === null) return collab;
2672
+ return { ...collab, breakout: plan };
2673
+ }
2674
+ return collab;
2675
+ }
2676
+ function without(collab, ids) {
2677
+ const gone = new Set(ids);
2746
2678
  return {
2747
- meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
2748
- meetingTitle: text(row, "meeting_title") ?? "Meeting",
2749
- meetingSlug: requireText(row, "meeting_slug", operation),
2750
- meetingAt: text(row, "meeting_at"),
2751
- source: SEARCH_SOURCES.includes(source) ? source : "note",
2752
- refId: requireText(row, "ref_id", operation),
2753
- speaker: text(row, "speaker"),
2754
- snippet: text(row, "snippet") ?? "",
2755
- at: text(row, "said_at"),
2756
- rank: num2(row, "rank") ?? 0
2679
+ ...collab,
2680
+ polls: collab.polls.filter((p) => !gone.has(p.id)),
2681
+ questions: collab.questions.filter((q) => !gone.has(q.id)),
2682
+ strokes: collab.strokes.filter((s) => !gone.has(s.id))
2757
2683
  };
2758
2684
  }
2759
- function snippetRuns(snippet) {
2760
- const runs = [];
2761
- const pattern = /⟦([^⟧]*)⟧/g;
2762
- let last = 0;
2763
- for (const match of snippet.matchAll(pattern)) {
2764
- const start = match.index ?? 0;
2765
- if (start > last) runs.push({ text: snippet.slice(last, start), hit: false });
2766
- runs.push({ text: match[1] ?? "", hit: true });
2767
- last = start + match[0].length;
2685
+ function applyCollabPacket(collab, payload, context) {
2686
+ const item = obj(payload["item"]);
2687
+ if (item !== null) {
2688
+ const kind = str(item["kind"]);
2689
+ let next = withItem(collab, item, true);
2690
+ if (kind === "poll" && str(item["state"]) === "live") {
2691
+ const before = collab.polls.find((p) => p.id === str(item["id"]));
2692
+ if (before === void 0 || before.state !== "live") {
2693
+ next = { ...next, panelRequest: { panel: "polls", at: context.now } };
2694
+ }
2695
+ }
2696
+ if (kind === "breakout") {
2697
+ const plan = planFromRow(item);
2698
+ if (plan !== null && plan.state === "closed") {
2699
+ next = { ...next, moveRequest: { roomId: null, at: context.now } };
2700
+ }
2701
+ }
2702
+ const move = obj(payload["move"]);
2703
+ if (move !== null && str(move["identity"]) === context.localIdentity) {
2704
+ next = { ...next, moveRequest: { roomId: str(move["room_id"]), at: context.now } };
2705
+ }
2706
+ return { next };
2768
2707
  }
2769
- if (last < snippet.length) runs.push({ text: snippet.slice(last), hit: false });
2770
- return runs;
2708
+ if (payload["focus"] !== void 0) {
2709
+ const focus = focusFromRow(payload["focus"]);
2710
+ return focus === null ? { next: null, dropped: "a focus packet without a panel" } : { next: { ...collab, focus } };
2711
+ }
2712
+ if (Array.isArray(payload["removed"])) {
2713
+ return { next: without(collab, strings(payload["removed"])) };
2714
+ }
2715
+ if (Array.isArray(payload["hidden"])) {
2716
+ return context.isHost ? { next: null, dropped: "hosts keep dismissed questions" } : { next: without(collab, strings(payload["hidden"])) };
2717
+ }
2718
+ if (payload["cleared"] === "whiteboard") {
2719
+ return { next: { ...collab, strokes: [] } };
2720
+ }
2721
+ const broadcast = broadcastFrom(payload["broadcast"]);
2722
+ if (broadcast !== null) {
2723
+ return { next: { ...collab, broadcast } };
2724
+ }
2725
+ return { next: null, dropped: "a collab packet with nothing this version understands" };
2726
+ }
2727
+ function assignedRoom(plan, identity) {
2728
+ if (plan === null || plan.state !== "open" || identity === null) return null;
2729
+ return plan.rooms.find((room) => room.identities.includes(identity)) ?? null;
2730
+ }
2731
+ function autoAssign(identities, rooms, exclude = []) {
2732
+ const count2 = Math.max(1, Math.floor(rooms));
2733
+ const out = Array.from({ length: count2 }, () => []);
2734
+ const skip = new Set(exclude);
2735
+ let index = 0;
2736
+ for (const identity of identities) {
2737
+ if (skip.has(identity)) continue;
2738
+ out[index % count2].push(identity);
2739
+ index += 1;
2740
+ }
2741
+ return out;
2742
+ }
2743
+ function breakoutRemainingMs(plan, now) {
2744
+ if (plan === null || plan.state !== "open" || plan.endsAt === null) return null;
2745
+ const end = Date.parse(plan.endsAt);
2746
+ return Number.isNaN(end) ? null : Math.max(0, end - now);
2747
+ }
2748
+ function defaultCaptionLanguage(settings, viewerLanguage) {
2749
+ const configured = settings?.captionTranslationLanguage.trim() ?? "";
2750
+ if (configured.length > 0) return configured;
2751
+ const own = (viewerLanguage ?? "").trim();
2752
+ return own.length > 0 ? own : "en";
2753
+ }
2754
+ var CAPTION_LANGUAGES = [
2755
+ { tag: "en", name: "English" },
2756
+ { tag: "es", name: "Spanish" },
2757
+ { tag: "fr", name: "French" },
2758
+ { tag: "de", name: "German" },
2759
+ { tag: "it", name: "Italian" },
2760
+ { tag: "pt", name: "Portuguese" },
2761
+ { tag: "nl", name: "Dutch" },
2762
+ { tag: "pl", name: "Polish" },
2763
+ { tag: "ru", name: "Russian" },
2764
+ { tag: "uk", name: "Ukrainian" },
2765
+ { tag: "tr", name: "Turkish" },
2766
+ { tag: "ar", name: "Arabic" },
2767
+ { tag: "he", name: "Hebrew" },
2768
+ { tag: "fa", name: "Persian" },
2769
+ { tag: "hi", name: "Hindi" },
2770
+ { tag: "bn", name: "Bengali" },
2771
+ { tag: "zh", name: "Chinese" },
2772
+ { tag: "ja", name: "Japanese" },
2773
+ { tag: "ko", name: "Korean" },
2774
+ { tag: "vi", name: "Vietnamese" },
2775
+ { tag: "th", name: "Thai" },
2776
+ { tag: "id", name: "Indonesian" },
2777
+ { tag: "sv", name: "Swedish" }
2778
+ ];
2779
+ async function loadMeetingActivities(api, meetingId, meetingPass = null) {
2780
+ let response;
2781
+ try {
2782
+ response = await api.request({
2783
+ path: `${MEET_ROUTES.collab}?meeting_id=${encodeURIComponent(meetingId)}`,
2784
+ method: "GET",
2785
+ operation: "meet.loadMeetingActivities",
2786
+ requireAuth: meetingPass === null,
2787
+ ...meetingPass !== null ? { headers: { [MEETING_PASS_HEADER]: meetingPass } } : {}
2788
+ });
2789
+ } catch (error) {
2790
+ return {
2791
+ collab: null,
2792
+ refusal: {
2793
+ message: `Polls, questions and the whiteboard could not be read (${error.message}).`,
2794
+ remedy: error.remedy ?? "Reload the page."
2795
+ }
2796
+ };
2797
+ }
2798
+ if (!response.ok) {
2799
+ let body = null;
2800
+ try {
2801
+ body = await response.json();
2802
+ } catch {
2803
+ body = null;
2804
+ }
2805
+ const said = readServerRefusal(body);
2806
+ return {
2807
+ collab: null,
2808
+ refusal: {
2809
+ message: said?.reason ?? `Polls and questions could not be read (the server answered ${response.status}).`,
2810
+ remedy: said?.remedy || "Reload the page."
2811
+ }
2812
+ };
2813
+ }
2814
+ return { collab: collabFromState(await response.json(), EMPTY_COLLAB), refusal: null };
2771
2815
  }
2772
2816
 
2773
2817
  // src/core/names.ts
@@ -3840,7 +3884,10 @@ function orderParticipants(participants, activeSpeakers) {
3840
3884
  }
3841
3885
  function computeStage(args) {
3842
3886
  const pageSize = Math.max(1, args.pageSize ?? DEFAULT_PAGE_SIZE);
3843
- const ordered = orderParticipants(args.participants, args.activeSpeakers);
3887
+ const ordered = orderParticipants(
3888
+ args.participants.filter((participant) => !isObserverRole(participant.role)),
3889
+ args.activeSpeakers
3890
+ );
3844
3891
  const sharer = ordered.find((participant) => participant.screenSharing) ?? null;
3845
3892
  const pinnedParticipant = args.pinned === null ? null : ordered.find((participant) => participant.identity === args.pinned) ?? null;
3846
3893
  const spotlit = args.spotlight == null ? null : ordered.find((participant) => participant.identity === args.spotlight) ?? null;
@@ -5616,7 +5663,8 @@ function createRoomEngine(options) {
5616
5663
  identity,
5617
5664
  ...request.meetingId !== void 0 ? { meetingId: request.meetingId } : {},
5618
5665
  ...request.slug !== void 0 ? { slug: request.slug } : {},
5619
- ...request.breakoutRoomId != null ? { breakoutRoomId: request.breakoutRoomId } : {}
5666
+ ...request.breakoutRoomId != null ? { breakoutRoomId: request.breakoutRoomId } : {},
5667
+ ...request.observe === true ? { observe: true } : {}
5620
5668
  });
5621
5669
  current = token;
5622
5670
  store.patch({
@@ -5627,7 +5675,8 @@ function createRoomEngine(options) {
5627
5675
  standing: token.standing,
5628
5676
  canRecord: token.canRecord,
5629
5677
  canPublish: token.canPublish,
5630
- canSubscribe: token.canSubscribe
5678
+ canSubscribe: token.canSubscribe,
5679
+ observersVisibleTo: token.observersVisibleTo ?? "everyone"
5631
5680
  }
5632
5681
  });
5633
5682
  if (token.lobby || !token.canSubscribe) {
@@ -6127,7 +6176,9 @@ function createInRoomClient(options) {
6127
6176
  ...request.allowChoose !== void 0 ? { allow_choose: request.allowChoose } : {},
6128
6177
  ...request.minutes !== void 0 ? { minutes: request.minutes } : {},
6129
6178
  ...request.identity !== void 0 ? { identity: request.identity } : {},
6130
- ...request.roomId !== void 0 ? { room_id: request.roomId } : {}
6179
+ ...request.roomId !== void 0 ? { room_id: request.roomId } : {},
6180
+ ...request.panel !== void 0 ? { panel: request.panel } : {},
6181
+ ...request.section !== void 0 ? { section: request.section } : {}
6131
6182
  },
6132
6183
  ...hdrs !== void 0 ? { headers: hdrs } : {}
6133
6184
  });