@q9labsai/chalk-react-native 5.0.1 → 5.2.0

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.
Files changed (250) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/android/build.gradle +4 -1
  3. package/dist/android/call-service.d.ts +0 -1
  4. package/dist/android/connection-service.d.ts +0 -1
  5. package/dist/avatar/FaceAvatar.d.ts +0 -1
  6. package/dist/avatar/tile-avatar-size.d.ts +0 -1
  7. package/dist/callkit/callkit.d.ts +0 -1
  8. package/dist/callkit/resolve-space-callkit-options.d.ts +0 -1
  9. package/dist/{chunk-OIBT2FNM.js → chunk-MBZRE4US.js} +2 -3
  10. package/dist/client.d.ts +0 -1
  11. package/dist/client.js +9 -5
  12. package/dist/clipboard-store.d.ts +0 -1
  13. package/dist/clipboard.d.ts +0 -1
  14. package/dist/components/EmbeddedWhiteboard.d.ts +0 -1
  15. package/dist/components/RtcVideoView.d.ts +0 -1
  16. package/dist/components/android-connection-service-controller.d.ts +0 -1
  17. package/dist/components/native-media-visibility.d.ts +0 -1
  18. package/dist/components/native-space-callkit-controller.d.ts +0 -1
  19. package/dist/components/native-space-view/screen-share-availability.d.ts +0 -1
  20. package/dist/components/rtc-video-view-availability.d.ts +0 -1
  21. package/dist/components/space-lifecycle.d.ts +0 -1
  22. package/dist/components/useAndroidCallService.d.ts +0 -1
  23. package/dist/components/useAndroidConnectionService.d.ts +0 -1
  24. package/dist/context/space-client-context.d.ts +0 -1
  25. package/dist/dev.d.ts +0 -1
  26. package/dist/diagnostics.d.ts +0 -1
  27. package/dist/dock/Dock.d.ts +0 -1
  28. package/dist/dock/LeaveMenu.d.ts +0 -1
  29. package/dist/dock/MoreSheet.d.ts +0 -1
  30. package/dist/dock/ReactionBurst.d.ts +0 -1
  31. package/dist/dock/RoutePicker.d.ts +0 -1
  32. package/dist/dock/audio-route.d.ts +0 -1
  33. package/dist/frame/CallFrame.d.ts +0 -1
  34. package/dist/frame/Chalk.d.ts +0 -1
  35. package/dist/frame/back-handler.d.ts +0 -1
  36. package/dist/frame/dev-scenario.d.ts +0 -1
  37. package/dist/frame/device-layout.d.ts +0 -1
  38. package/dist/frame/measurement.d.ts +0 -1
  39. package/dist/frame/portal.d.ts +0 -1
  40. package/dist/frame/preferences.d.ts +0 -1
  41. package/dist/frame/regions.d.ts +0 -1
  42. package/dist/haptics/haptics.d.ts +0 -1
  43. package/dist/header/CallDetailsSheet.d.ts +0 -1
  44. package/dist/header/Header.d.ts +0 -1
  45. package/dist/header/QrCode.d.ts +0 -1
  46. package/dist/hooks/external-store.d.ts +0 -1
  47. package/dist/hooks/space-hooks.d.ts +0 -1
  48. package/dist/icons/Icon.d.ts +0 -1
  49. package/dist/icons/icon-element.d.ts +0 -1
  50. package/dist/icons/icon-paths.d.ts +0 -1
  51. package/dist/index.d.ts +0 -1
  52. package/dist/index.js +45 -17
  53. package/dist/invites.d.ts +0 -1
  54. package/dist/limbo/Limbo.d.ts +0 -1
  55. package/dist/limbo/Veil.d.ts +0 -1
  56. package/dist/media/native-capture-error.d.ts +0 -1
  57. package/dist/media/native-webrtc.d.ts +0 -1
  58. package/dist/motion/MovingFrame.d.ts +0 -1
  59. package/dist/motion/motion.d.ts +0 -1
  60. package/dist/motion/presence.d.ts +0 -1
  61. package/dist/motion/press.d.ts +0 -1
  62. package/dist/platform/platform.d.ts +0 -1
  63. package/dist/primitives/BottomSheet.d.ts +0 -1
  64. package/dist/primitives/ChalkText.d.ts +0 -1
  65. package/dist/primitives/CopyButton.d.ts +0 -1
  66. package/dist/primitives/IconButton.d.ts +0 -1
  67. package/dist/primitives/Menu.d.ts +0 -1
  68. package/dist/primitives/NameTag.d.ts +0 -1
  69. package/dist/primitives/PillButton.d.ts +0 -1
  70. package/dist/primitives/SegmentedControl.d.ts +0 -1
  71. package/dist/primitives/SheetGroup.d.ts +0 -1
  72. package/dist/primitives/Switch.d.ts +0 -1
  73. package/dist/primitives/TileShell.d.ts +0 -1
  74. package/dist/primitives/announce.d.ts +0 -1
  75. package/dist/primitives/menu-placement.d.ts +0 -1
  76. package/dist/primitives/toast/Toast.d.ts +0 -1
  77. package/dist/primitives/toast/ToastStack.d.ts +0 -1
  78. package/dist/primitives/toast/toast-store.d.ts +0 -1
  79. package/dist/runtime.d.ts +0 -1
  80. package/dist/sheets/people-chat/ChatNotices.d.ts +0 -1
  81. package/dist/sheets/people-chat/ChatTab.d.ts +0 -1
  82. package/dist/sheets/people-chat/Composer.d.ts +0 -1
  83. package/dist/sheets/people-chat/PeopleChatSheet.d.ts +0 -1
  84. package/dist/sheets/people-chat/PeopleTab.d.ts +0 -1
  85. package/dist/sheets/people-chat/PhotoViewer.d.ts +0 -1
  86. package/dist/sheets/people-chat/TextField.d.ts +0 -1
  87. package/dist/sheets/people-chat/attachments.d.ts +0 -1
  88. package/dist/sheets/people-chat/chat-model.d.ts +0 -1
  89. package/dist/sheets/people-chat/people-model.d.ts +0 -1
  90. package/dist/sounds/sounds.d.ts +0 -1
  91. package/dist/space-client/create-native-space-client.d.ts +0 -1
  92. package/dist/space-client/random-uuid.d.ts +0 -1
  93. package/dist/stage/ContentTile.d.ts +0 -1
  94. package/dist/stage/ParticipantTile.d.ts +0 -1
  95. package/dist/stage/Stage.d.ts +0 -1
  96. package/dist/stage/TileSheet.d.ts +0 -1
  97. package/dist/stage/camera.d.ts +0 -1
  98. package/dist/stage/layout.d.ts +0 -1
  99. package/dist/stage/space-selector.d.ts +0 -1
  100. package/dist/stage/stage-view.d.ts +0 -1
  101. package/dist/status/CallOverlays.d.ts +1 -2
  102. package/dist/status/ChalkArrival.d.ts +0 -1
  103. package/dist/status/StatusPage.d.ts +0 -1
  104. package/dist/status/call-screen.d.ts +0 -1
  105. package/dist/theme/theme.d.ts +0 -1
  106. package/dist/theme/tokens.d.ts +0 -1
  107. package/dist/toasts/CallEventToasts.d.ts +1 -2
  108. package/dist/toasts/media-failure-copy.d.ts +2 -0
  109. package/dist/toasts/notices.d.ts +0 -1
  110. package/dist/ui/ChalkUI.d.ts +0 -1
  111. package/dist/ui.d.ts +0 -1
  112. package/dist/ui.js +1 -1
  113. package/dist/utils/build-chalk-invite-link.d.ts +0 -1
  114. package/dist/utils/ios-simulator.d.ts +0 -1
  115. package/dist/whiteboard/embedded-whiteboard-assets.d.ts +0 -1
  116. package/dist/whiteboard/embedded-whiteboard-viewport.d.ts +0 -1
  117. package/embedded/chalk-whiteboard/asset-manifest.json +2 -2
  118. package/embedded/chalk-whiteboard/renderer.js +579 -579
  119. package/package.json +8 -4
  120. package/src/components/rtc-video-view-availability.ts +1 -1
  121. package/src/dock/Dock.tsx +4 -2
  122. package/src/dock/MoreSheet.tsx +3 -2
  123. package/src/frame/CallFrame.tsx +4 -4
  124. package/src/frame/device-layout.tsx +2 -3
  125. package/src/space-client/create-native-space-client.ts +9 -5
  126. package/src/stage/TileSheet.tsx +5 -1
  127. package/src/status/CallOverlays.tsx +18 -3
  128. package/src/toasts/CallEventToasts.tsx +16 -7
  129. package/src/toasts/media-failure-copy.ts +10 -0
  130. package/android/src/test/kotlin/ai/q9labs/chalk/compatibility/UnsafeSignatures.kt +0 -29
  131. package/dist/android/call-service.d.ts.map +0 -1
  132. package/dist/android/connection-service.d.ts.map +0 -1
  133. package/dist/avatar/FaceAvatar.d.ts.map +0 -1
  134. package/dist/avatar/tile-avatar-size.d.ts.map +0 -1
  135. package/dist/callkit/callkit.d.ts.map +0 -1
  136. package/dist/callkit/resolve-space-callkit-options.d.ts.map +0 -1
  137. package/dist/client.d.ts.map +0 -1
  138. package/dist/clipboard-store.d.ts.map +0 -1
  139. package/dist/clipboard.d.ts.map +0 -1
  140. package/dist/components/EmbeddedWhiteboard.d.ts.map +0 -1
  141. package/dist/components/RtcVideoView.d.ts.map +0 -1
  142. package/dist/components/android-connection-service-controller.d.ts.map +0 -1
  143. package/dist/components/native-media-visibility.d.ts.map +0 -1
  144. package/dist/components/native-space-callkit-controller.d.ts.map +0 -1
  145. package/dist/components/native-space-view/screen-share-availability.d.ts.map +0 -1
  146. package/dist/components/rtc-video-view-availability.d.ts.map +0 -1
  147. package/dist/components/space-lifecycle.d.ts.map +0 -1
  148. package/dist/components/useAndroidCallService.d.ts.map +0 -1
  149. package/dist/components/useAndroidConnectionService.d.ts.map +0 -1
  150. package/dist/context/space-client-context.d.ts.map +0 -1
  151. package/dist/dev.d.ts.map +0 -1
  152. package/dist/diagnostics.d.ts.map +0 -1
  153. package/dist/dock/Dock.d.ts.map +0 -1
  154. package/dist/dock/LeaveMenu.d.ts.map +0 -1
  155. package/dist/dock/MoreSheet.d.ts.map +0 -1
  156. package/dist/dock/ReactionBurst.d.ts.map +0 -1
  157. package/dist/dock/RoutePicker.d.ts.map +0 -1
  158. package/dist/dock/audio-route.d.ts.map +0 -1
  159. package/dist/frame/CallFrame.d.ts.map +0 -1
  160. package/dist/frame/Chalk.d.ts.map +0 -1
  161. package/dist/frame/back-handler.d.ts.map +0 -1
  162. package/dist/frame/dev-scenario.d.ts.map +0 -1
  163. package/dist/frame/device-layout.d.ts.map +0 -1
  164. package/dist/frame/measurement.d.ts.map +0 -1
  165. package/dist/frame/portal.d.ts.map +0 -1
  166. package/dist/frame/preferences.d.ts.map +0 -1
  167. package/dist/frame/regions.d.ts.map +0 -1
  168. package/dist/haptics/haptics.d.ts.map +0 -1
  169. package/dist/header/CallDetailsSheet.d.ts.map +0 -1
  170. package/dist/header/Header.d.ts.map +0 -1
  171. package/dist/header/QrCode.d.ts.map +0 -1
  172. package/dist/hooks/external-store.d.ts.map +0 -1
  173. package/dist/hooks/space-hooks.d.ts.map +0 -1
  174. package/dist/icons/Icon.d.ts.map +0 -1
  175. package/dist/icons/icon-element.d.ts.map +0 -1
  176. package/dist/icons/icon-paths.d.ts.map +0 -1
  177. package/dist/index.d.ts.map +0 -1
  178. package/dist/invites.d.ts.map +0 -1
  179. package/dist/limbo/Limbo.d.ts.map +0 -1
  180. package/dist/limbo/Veil.d.ts.map +0 -1
  181. package/dist/media/native-capture-error.d.ts.map +0 -1
  182. package/dist/media/native-webrtc.d.ts.map +0 -1
  183. package/dist/motion/MovingFrame.d.ts.map +0 -1
  184. package/dist/motion/motion.d.ts.map +0 -1
  185. package/dist/motion/presence.d.ts.map +0 -1
  186. package/dist/motion/press.d.ts.map +0 -1
  187. package/dist/platform/platform.d.ts.map +0 -1
  188. package/dist/primitives/BottomSheet.d.ts.map +0 -1
  189. package/dist/primitives/ChalkText.d.ts.map +0 -1
  190. package/dist/primitives/CopyButton.d.ts.map +0 -1
  191. package/dist/primitives/IconButton.d.ts.map +0 -1
  192. package/dist/primitives/Menu.d.ts.map +0 -1
  193. package/dist/primitives/NameTag.d.ts.map +0 -1
  194. package/dist/primitives/PillButton.d.ts.map +0 -1
  195. package/dist/primitives/SegmentedControl.d.ts.map +0 -1
  196. package/dist/primitives/SheetGroup.d.ts.map +0 -1
  197. package/dist/primitives/Switch.d.ts.map +0 -1
  198. package/dist/primitives/TileShell.d.ts.map +0 -1
  199. package/dist/primitives/announce.d.ts.map +0 -1
  200. package/dist/primitives/menu-placement.d.ts.map +0 -1
  201. package/dist/primitives/toast/Toast.d.ts.map +0 -1
  202. package/dist/primitives/toast/ToastStack.d.ts.map +0 -1
  203. package/dist/primitives/toast/toast-store.d.ts.map +0 -1
  204. package/dist/runtime.d.ts.map +0 -1
  205. package/dist/sheets/people-chat/ChatNotices.d.ts.map +0 -1
  206. package/dist/sheets/people-chat/ChatTab.d.ts.map +0 -1
  207. package/dist/sheets/people-chat/Composer.d.ts.map +0 -1
  208. package/dist/sheets/people-chat/PeopleChatSheet.d.ts.map +0 -1
  209. package/dist/sheets/people-chat/PeopleTab.d.ts.map +0 -1
  210. package/dist/sheets/people-chat/PhotoViewer.d.ts.map +0 -1
  211. package/dist/sheets/people-chat/TextField.d.ts.map +0 -1
  212. package/dist/sheets/people-chat/attachments.d.ts.map +0 -1
  213. package/dist/sheets/people-chat/chat-model.d.ts.map +0 -1
  214. package/dist/sheets/people-chat/people-model.d.ts.map +0 -1
  215. package/dist/sounds/sounds.d.ts.map +0 -1
  216. package/dist/space-client/create-native-space-client.d.ts.map +0 -1
  217. package/dist/space-client/random-uuid.d.ts.map +0 -1
  218. package/dist/stage/ContentTile.d.ts.map +0 -1
  219. package/dist/stage/ParticipantTile.d.ts.map +0 -1
  220. package/dist/stage/Stage.d.ts.map +0 -1
  221. package/dist/stage/TileSheet.d.ts.map +0 -1
  222. package/dist/stage/camera.d.ts.map +0 -1
  223. package/dist/stage/layout.d.ts.map +0 -1
  224. package/dist/stage/space-selector.d.ts.map +0 -1
  225. package/dist/stage/stage-view.d.ts.map +0 -1
  226. package/dist/status/CallOverlays.d.ts.map +0 -1
  227. package/dist/status/ChalkArrival.d.ts.map +0 -1
  228. package/dist/status/StatusPage.d.ts.map +0 -1
  229. package/dist/status/call-screen.d.ts.map +0 -1
  230. package/dist/theme/theme.d.ts.map +0 -1
  231. package/dist/theme/tokens.d.ts.map +0 -1
  232. package/dist/toasts/CallEventToasts.d.ts.map +0 -1
  233. package/dist/toasts/notices.d.ts.map +0 -1
  234. package/dist/ui/ChalkUI.d.ts.map +0 -1
  235. package/dist/ui.d.ts.map +0 -1
  236. package/dist/utils/build-chalk-invite-link.d.ts.map +0 -1
  237. package/dist/utils/ios-simulator.d.ts.map +0 -1
  238. package/dist/whiteboard/embedded-whiteboard-assets.d.ts.map +0 -1
  239. package/dist/whiteboard/embedded-whiteboard-viewport.d.ts.map +0 -1
  240. package/src/callkit/callkit.test.ts +0 -97
  241. package/src/components/native-space-view/screen-share-availability.test.ts +0 -16
  242. package/src/frame/regions.test.ts +0 -38
  243. package/src/media/native-capture-error.test.ts +0 -19
  244. package/src/primitives/menu-placement.test.ts +0 -50
  245. package/src/sheets/people-chat/people-chat-model.test.ts +0 -98
  246. package/src/space-client/whiteboard-watchdog.test.ts +0 -23
  247. package/src/stage/layout.test.ts +0 -77
  248. package/src/status/call-screen.test.ts +0 -42
  249. package/src/toasts/notices.test.ts +0 -92
  250. package/src/whiteboard/embedded-whiteboard-viewport.test.ts +0 -13
@@ -1,98 +0,0 @@
1
- import type { ChatMessage, Participant, PendingChatSend } from "@q9labsai/chalk-client";
2
- import { describe, expect, it } from "vitest";
3
- import { groupMessages, messageSummary, mimeTypeOf, stagingError } from "./chat-model";
4
- import { nextHandOrder, peopleRows } from "./people-model";
5
-
6
- function person(participantId: string, displayName: string, handRaised = false): Participant {
7
- return {
8
- participantId,
9
- displayName,
10
- role: "participant",
11
- eligibleRoles: [],
12
- capabilities: [],
13
- handRaised,
14
- media: { microphone: "active", camera: "inactive", screenShare: "inactive" },
15
- presence: { state: "connected", speaking: false, activeSpeaker: false },
16
- } as const satisfies Participant;
17
- }
18
-
19
- function message(messageId: string, participantId: string, text: string): ChatMessage {
20
- return { messageId, clientMessageId: `c-${messageId}`, sequence: messageId, participantId, displayName: participantId, text, createdAt: "2026-10-18T10:00:00Z", attachments: [] };
21
- }
22
-
23
- const photo = { attachmentId: "a", fileName: "IMG_0412.jpg", mimeType: "image/jpeg", byteLength: 10 } as const;
24
- const pdf = { attachmentId: "b", fileName: "Q4 plan.pdf", mimeType: "application/pdf", byteLength: 10 } as const;
25
-
26
- describe("peopleRows", () => {
27
- const roster = [person("me", "Hasan"), person("n", "Nora", true), person("s", "Sam", true), person("k", "Kai")];
28
-
29
- it("puts the raised-hands queue in the order hands went up, above everyone, once two hands are up", () => {
30
- const rows = peopleRows({ roster, waiting: [], handOrder: ["s", "n"], query: "" });
31
- expect(rows.map((row) => row.key)).toEqual(["section.hands", "hand.s", "hand.n", "section.call", "person.me", "person.n", "person.s", "person.k"]);
32
- expect(rows.find((row) => row.key === "person.n")).toMatchObject({ handNumber: 2 });
33
- });
34
-
35
- it("numbers nothing and shows no queue for a single raised hand", () => {
36
- const rows = peopleRows({ roster: [person("me", "Hasan"), person("n", "Nora", true)], waiting: [], handOrder: ["n"], query: "" });
37
- expect(rows.map((row) => row.kind)).toEqual(["person", "person"]);
38
- expect(rows[1]).toMatchObject({ handNumber: null });
39
- });
40
-
41
- it("filters by name and says when no one matches", () => {
42
- expect(peopleRows({ roster, waiting: [], handOrder: ["s", "n"], query: "no" }).map((row) => row.key)).toEqual(["person.n"]);
43
- expect(peopleRows({ roster, waiting: [], handOrder: [], query: "zed" })).toEqual([{ kind: "none", key: "none", query: "zed" }]);
44
- });
45
-
46
- it("keeps the first-raised hand at the front as others raise and lower", () => {
47
- const first = nextHandOrder([], [person("n", "Nora", true)]);
48
- const second = nextHandOrder(first, [person("n", "Nora", true), person("s", "Sam", true)]);
49
- expect(second).toEqual(["n", "s"]);
50
- expect(nextHandOrder(second, [person("n", "Nora"), person("s", "Sam", true)])).toEqual(["s"]);
51
- expect(nextHandOrder(second, [person("n", "Nora", true), person("s", "Sam", true)])).toBe(second);
52
- });
53
- });
54
-
55
- describe("groupMessages", () => {
56
- const self = { participantId: "me", displayName: "Hasan" };
57
-
58
- it("groups consecutive messages from one person and marks yours", () => {
59
- const groups = groupMessages([message("1", "n", "Hi"), message("2", "n", "Ready?"), message("3", "me", "Yes")], [], self, new Set());
60
- expect(groups.map((group) => [group.participantId, group.mine, group.entries.length])).toEqual([
61
- ["n", false, 2],
62
- ["me", true, 1],
63
- ]);
64
- });
65
-
66
- it("adds your waiting sends after the confirmed messages and hides ones you retried", () => {
67
- const failed: PendingChatSend = { clientMessageId: "p1", text: "Lost", attachments: [], status: "failed", error: null };
68
- const sending: PendingChatSend = { clientMessageId: "p2", text: "Again", attachments: [], status: "sending", error: null };
69
- const groups = groupMessages([message("1", "me", "First")], [failed, sending], self, new Set(["p1"]));
70
- expect(groups).toHaveLength(1);
71
- expect(groups[0]?.entries.map((entry) => [entry.text, entry.state])).toEqual([
72
- ["First", "sent"],
73
- ["Again", "sending"],
74
- ]);
75
- });
76
- });
77
-
78
- describe("chat files", () => {
79
- it("says what a message with only attachments sent", () => {
80
- expect(messageSummary("", [photo])).toBe("Sent a photo");
81
- expect(messageSummary("", [photo, photo])).toBe("Sent 2 photos");
82
- expect(messageSummary("", [photo, pdf])).toBe("Sent 2 files");
83
- expect(messageSummary("Look", [photo])).toBe("Look");
84
- });
85
-
86
- it("refuses unknown types, files over 25 MB and a sixth file", () => {
87
- expect(stagingError({ name: "Demo.mov", mimeType: "video/quicktime", size: 10 }, 0)).toBe("Can’t send .mov files");
88
- expect(stagingError({ name: "Site photos.pdf", mimeType: "application/pdf", size: 31 * 1024 * 1024 }, 0)).toBe("Over the 25 MB limit");
89
- expect(stagingError({ name: "Agenda.pdf", mimeType: "application/pdf", size: 10 }, 5)).toBe("Only 5 files per message");
90
- expect(stagingError({ name: "Agenda.pdf", mimeType: "application/pdf", size: 10 }, 4)).toBeNull();
91
- });
92
-
93
- it("falls back to the extension when the picker reports no type", () => {
94
- expect(mimeTypeOf("Budget 2027.XLSX", undefined)).toBe("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
95
- expect(mimeTypeOf("notes", undefined)).toBe("application/octet-stream");
96
- expect(mimeTypeOf("a.png", "image/png")).toBe("image/png");
97
- });
98
- });
@@ -1,23 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import type { SpaceClientPlatform } from "@q9labsai/chalk-client/effect";
3
- const mocks = vi.hoisted(() => ({ platform: vi.fn(), whiteboard: vi.fn() }));
4
- vi.mock("react-native", () => ({ AppState: { currentState: "active", addEventListener: vi.fn(() => ({ remove: vi.fn() })) } }));
5
- vi.mock("@cloudflare/react-native-webrtc", () => ({ RTCPeerConnection: vi.fn(), mediaDevices: {} }));
6
- vi.mock("@q9labsai/chalk-client/effect", () => ({ createSpaceClientForPlatform: mocks.platform }));
7
- vi.mock("@q9labsai/chalk-client", async (original) => ({ ...(await original<typeof import("@q9labsai/chalk-client")>()), createChalkWhiteboardV1Client: mocks.whiteboard }));
8
- import { createNativeSpaceClient } from "./create-native-space-client";
9
- describe("native whiteboard watchdog adapter", () => {
10
- it("passes the shared core's replacement recorder without a journey option", () => {
11
- createNativeSpaceClient({
12
- space: "test",
13
- getAccess: async () => {
14
- throw new Error("unused");
15
- },
16
- });
17
- const platform: SpaceClientPlatform | undefined = mocks.platform.mock.calls[0]?.[1];
18
- const onWatchdogReplacement = vi.fn();
19
- platform?.dependencies?.createWhiteboardClient?.({ token: async () => "unused", onSummary: vi.fn(), onWatchdogReplacement });
20
- expect(mocks.whiteboard).toHaveBeenCalledOnce();
21
- expect(mocks.whiteboard.mock.calls[0]?.[0].onWatchdogReplacement).toBe(onWatchdogReplacement);
22
- });
23
- });
@@ -1,77 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { nextStageOrder, stageLayout, type StageInput, type StageItem } from "./layout";
3
-
4
- // The D1 portrait stage from `frameRegions`. Expected frames come from the mockup's own `layout()`.
5
- const stage = { x: 8, y: 111, width: 377, height: 625 };
6
- const ids = (count: number) => Array.from({ length: count }, (_, index) => String(index));
7
- const input = (overrides: Partial<StageInput>): StageInput => ({ stage, portrait: true, layout: "spotlight", order: ids(6), spot: "1", pinned: null, content: null, ...overrides });
8
- const rounded = (item: StageItem) => [item.frame.x, item.frame.y, item.frame.width, item.frame.height].map((value) => Math.round(value * 10) / 10);
9
-
10
- describe("stageLayout", () => {
11
- it("floats you over the other person when two are in the call (P2)", () => {
12
- const [main, you] = stageLayout(input({ order: ids(2) }));
13
- expect(main).toMatchObject({ kind: "person", id: "1", frame: stage });
14
- expect(you).toMatchObject({ kind: "person", id: "0", float: true });
15
- if (you) expect(rounded(you)).toEqual([277, 123, 96, 128]);
16
- });
17
-
18
- it("puts the speaker big and everyone else in justified rows below (P3, G1)", () => {
19
- const items = stageLayout(input({}));
20
- expect(items.map((item) => (item.kind === "person" ? item.id : item.kind))).toEqual(["1", "0", "2", "3", "4", "5"]);
21
- expect(items[0]).toMatchObject({ big: true });
22
- expect(items.map(rounded)).toEqual([
23
- [8, 111, 377, 429],
24
- [8, 548, 120.3, 90],
25
- [136.3, 548, 120.3, 90],
26
- [264.7, 548, 120.3, 90],
27
- [8, 646, 184.5, 90],
28
- [200.5, 646, 184.5, 90],
29
- ]);
30
- });
31
-
32
- it("keeps a pinned person big over the speaker", () => {
33
- expect(stageLayout(input({ pinned: "4" }))[0]).toMatchObject({ kind: "person", id: "4", big: true });
34
- });
35
-
36
- it("fills the stage with 24 tiles in Grid (P4, G2)", () => {
37
- const items = stageLayout(input({ layout: "grid", order: ids(24) }));
38
- expect(items).toHaveLength(24);
39
- expect(items.every((item) => item.kind === "person" && Math.round(item.frame.width * 10) / 10 === 88.3)).toBe(true);
40
- });
41
-
42
- it("stands a phone share at full height with people beside it (C2, S4)", () => {
43
- const [share, ...rest] = stageLayout(input({ content: { kind: "screen", aspect: 9 / 16 } }));
44
- expect(share).toMatchObject({ kind: "content", mini: false });
45
- if (share) expect(rounded(share)).toEqual([8, 111, 249, 625]);
46
- expect(rest.every((item) => item.frame.x === 265 && item.frame.width === 120)).toBe(true);
47
- });
48
-
49
- it("gives a shared board the stage with one row of people and a +N tile (C3)", () => {
50
- const items = stageLayout(input({ content: { kind: "board", aspect: null } }));
51
- expect(items[0] && rounded(items[0])).toEqual([8, 111, 377, 511]);
52
- expect(items.at(-1)).toMatchObject({ kind: "more", count: 3 });
53
- });
54
-
55
- it("shows the share as a double tile in Grid", () => {
56
- const [share] = stageLayout(input({ layout: "grid", content: { kind: "screen", aspect: 16 / 9 } }));
57
- expect(share).toMatchObject({ kind: "content", mini: true });
58
- });
59
- });
60
-
61
- describe("nextStageOrder", () => {
62
- it("swaps a new speaker with the person in the spotlight, so only those two tiles move", () => {
63
- const start = nextStageOrder({ order: [], spot: null }, ["you", "a", "b", "c"], "you", null);
64
- expect(start).toEqual({ order: ["you", "a", "b", "c"], spot: "a" });
65
- expect(nextStageOrder(start, ["you", "a", "b", "c"], "you", "c")).toEqual({ order: ["you", "c", "b", "a"], spot: "c" });
66
- });
67
-
68
- it("ignores your own speech and keeps the same object when nothing changes", () => {
69
- const start = nextStageOrder({ order: [], spot: null }, ["you", "a", "b"], "you", null);
70
- expect(nextStageOrder(start, ["you", "a", "b"], "you", "you")).toBe(start);
71
- });
72
-
73
- it("drops leavers, appends newcomers and moves the spotlight off someone who left", () => {
74
- const start = { order: ["you", "a", "b"], spot: "a" };
75
- expect(nextStageOrder(start, ["you", "b", "d"], "you", null)).toEqual({ order: ["you", "b", "d"], spot: "b" });
76
- });
77
- });
@@ -1,42 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { callScreen } from "./call-screen";
3
-
4
- type ConnectionOptions = { readonly code?: "episode.ended" | "access.invalid"; readonly episodeEnded?: boolean; readonly removedByHost?: boolean };
5
-
6
- const connection = (status: Parameters<typeof callScreen>[0]["status"], { code, episodeEnded = false, removedByHost = false }: ConnectionOptions = {}) => ({
7
- status,
8
- episode: null,
9
- lastError: code ? { code, recoverable: false, message: "" } : null,
10
- episodeEnded,
11
- removedByHost,
12
- });
13
-
14
- describe("callScreen", () => {
15
- it("covers the call while it joins or reconnects", () => {
16
- expect(callScreen(connection("joining"), false)).toEqual({ kind: "joining" });
17
- expect(callScreen(connection("reconnecting"), false)).toEqual({ kind: "reconnecting" });
18
- expect(callScreen(connection("live"), false)).toEqual({ kind: "call" });
19
- });
20
-
21
- it("shows the person's own leave as left, even when the leave call failed", () => {
22
- expect(callScreen(connection("left"), true)).toEqual({ kind: "status", outcome: "left" });
23
- expect(callScreen(connection("left", { removedByHost: true }), true)).toEqual({ kind: "status", outcome: "left" });
24
- });
25
-
26
- it("shows removed only when the server reported a host removal", () => {
27
- expect(callScreen(connection("left", { removedByHost: true }), false)).toEqual({ kind: "status", outcome: "removed" });
28
- });
29
-
30
- it("shows a neutral exit for any other exit nobody asked for", () => {
31
- expect(callScreen(connection("left"), false)).toEqual({ kind: "status", outcome: "exited" });
32
- });
33
-
34
- it("shows the call ended when the Episode ends while the person is in it", () => {
35
- expect(callScreen(connection("left", { episodeEnded: true }), false)).toEqual({ kind: "status", outcome: "ended" });
36
- expect(callScreen(connection("left", { code: "episode.ended" }), false)).toEqual({ kind: "status", outcome: "ended" });
37
- });
38
-
39
- it("shows a failure as failed", () => {
40
- expect(callScreen(connection("failed", { code: "access.invalid" }), false)).toEqual({ kind: "status", outcome: "failed" });
41
- });
42
- });
@@ -1,92 +0,0 @@
1
- import type { Capability, Participant, SpaceSnapshot } from "@q9labsai/chalk-client";
2
- import { describe, expect, it } from "vitest";
3
- import { callNotices } from "./notices";
4
-
5
- const HOST: readonly Capability[] = ["endEpisode", "muteOthers"];
6
-
7
- function person(participantId: string, displayName: string, capabilities: readonly Capability[] = [], screenShare: "active" | "inactive" = "inactive"): Participant {
8
- return {
9
- participantId,
10
- displayName,
11
- role: capabilities.length ? "host" : "guest",
12
- eligibleRoles: [],
13
- capabilities,
14
- handRaised: false,
15
- media: { microphone: "inactive", camera: "inactive", screenShare },
16
- presence: { state: "connected", speaking: false, activeSpeaker: false },
17
- };
18
- }
19
-
20
- type Shape = {
21
- readonly status?: SpaceSnapshot["connection"]["status"];
22
- readonly roster?: readonly Participant[];
23
- readonly selfCapabilities?: readonly Capability[];
24
- readonly microphone?: "enabled" | "disabled";
25
- readonly requests?: SpaceSnapshot["media"]["incomingRequests"];
26
- readonly recording?: "recording" | "stopped";
27
- };
28
-
29
- function snapshot({ status = "live", roster = [], selfCapabilities = [], microphone = "disabled", requests = [], recording }: Shape): SpaceSnapshot {
30
- const local = (state: "enabled" | "disabled") => ({ source: "microphone" as const, state, track: null });
31
- return {
32
- connection: { status, episode: null, lastError: null },
33
- self: { participantId: "me", displayName: "Hasan", role: null, capabilities: selfCapabilities, handRaised: false, can: (capability) => selfCapabilities.includes(capability) },
34
- participants: { roster: [person("me", "Hasan", selfCapabilities), ...roster], admissionQueue: [] },
35
- media: {
36
- devices: { microphones: [], cameras: [], speakers: [] },
37
- selection: { microphone: null, camera: null, speaker: null },
38
- local: { microphone: local(microphone), camera: { ...local("disabled"), source: "camera" }, screen: { ...local("disabled"), source: "screen" } },
39
- remote: [],
40
- screenShare: { ...local("disabled"), source: "screen" },
41
- incomingRequests: requests,
42
- },
43
- chat: { status: "ready", messages: [], pendingSends: [], readReceipts: [], unreadCount: 0, pagination: { cursor: null, hasOlder: false, historyTruncated: false }, lastError: null },
44
- reactions: { active: [] },
45
- recording: { current: recording ? { recordingId: "r1", status: recording, failureCode: null } : null },
46
- whiteboard: { open: false, engine: { status: "unsubscribed", sceneId: null, revision: null, presenting: false, error: null } },
47
- offline: { pending: [], decisionNeeded: false, policy: "ask" },
48
- };
49
- }
50
-
51
- const nora = person("nora", "Nora Lindqvist", HOST);
52
- const sam = person("sam", "Sam Okafor");
53
-
54
- describe("callNotices", () => {
55
- it("says nothing while the roster arrives on join or empties on leave", () => {
56
- expect(callNotices(snapshot({ status: "joining" }), snapshot({ roster: [nora, sam] }), false)).toEqual([]);
57
- expect(callNotices(snapshot({ roster: [nora, sam] }), snapshot({ status: "left" }), false)).toEqual([]);
58
- });
59
-
60
- it("names people who join and leave, but never you", () => {
61
- expect(callNotices(snapshot({ roster: [nora] }), snapshot({ roster: [nora, sam] }), false)).toEqual([{ kind: "joined", name: "Sam Okafor" }]);
62
- expect(callNotices(snapshot({ roster: [nora, sam] }), snapshot({ roster: [nora] }), false)).toEqual([{ kind: "left", name: "Sam Okafor" }]);
63
- });
64
-
65
- it("tells you the host left and you're the host now, instead of a plain leave", () => {
66
- const notices = callNotices(snapshot({ roster: [nora, sam] }), snapshot({ roster: [sam], selfCapabilities: HOST }), false);
67
- expect(notices).toEqual([{ kind: "newHost", previousHost: "Nora" }]);
68
- });
69
-
70
- it("reports a host mute only when you didn't mute yourself", () => {
71
- const before = snapshot({ roster: [nora, sam], microphone: "enabled" });
72
- const after = snapshot({ roster: [nora, sam], microphone: "disabled" });
73
- expect(callNotices(before, after, false)).toEqual([{ kind: "mutedByHost", by: "Nora" }]);
74
- expect(callNotices(before, after, true)).toEqual([]);
75
- });
76
-
77
- it("reports each new request to unmute once", () => {
78
- const request = { requestId: "q1", kind: "unmute" as const, actorParticipantId: "nora", actorDisplayName: "Nora Lindqvist", expiresAt: "2026-10-18T12:00:00Z" };
79
- expect(callNotices(snapshot({ roster: [nora] }), snapshot({ roster: [nora], requests: [request] }), false)).toEqual([{ kind: "mediaRequest", request }]);
80
- expect(callNotices(snapshot({ roster: [nora], requests: [request] }), snapshot({ roster: [nora], requests: [request] }), false)).toEqual([]);
81
- });
82
-
83
- it("reports when someone else stops sharing", () => {
84
- const sharing = person("sam", "Sam Okafor", [], "active");
85
- expect(callNotices(snapshot({ roster: [sharing] }), snapshot({ roster: [sam] }), false)).toEqual([{ kind: "shareStopped", name: "Sam" }]);
86
- });
87
-
88
- it("reports recording starting and stopping", () => {
89
- expect(callNotices(snapshot({}), snapshot({ recording: "recording" }), false)).toEqual([{ kind: "recordingStarted" }]);
90
- expect(callNotices(snapshot({ recording: "recording" }), snapshot({ recording: "stopped" }), false)).toEqual([{ kind: "recordingStopped" }]);
91
- });
92
- });
@@ -1,13 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import { createEmbeddedWhiteboardViewport } from "./embedded-whiteboard-viewport";
4
-
5
- describe("native embedded whiteboard viewport", () => {
6
- it("uses the measured Board rectangle rather than the device window", () => {
7
- expect(createEmbeddedWhiteboardViewport(640, 420, 2)).toEqual({ width: 640, height: 420, scale: 2 });
8
- });
9
-
10
- it("waits for a usable layout", () => {
11
- expect(createEmbeddedWhiteboardViewport(0, 420, 2)).toBeNull();
12
- });
13
- });