@rapidmx/web-client 0.20.0 → 0.22.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 (906) hide show
  1. package/README.md +415 -381
  2. package/apps/admin/_layout.tsx +2 -2
  3. package/apps/admin/audit-log/index.tsx +4 -4
  4. package/apps/admin/branding/index.tsx +39 -39
  5. package/apps/admin/data-requests/index.tsx +482 -482
  6. package/apps/admin/distribution-lists/[uid].tsx +3 -3
  7. package/apps/admin/distribution-lists/index.tsx +4 -4
  8. package/apps/admin/distribution-lists/new/index.tsx +5 -5
  9. package/apps/admin/domains/[uid].tsx +166 -166
  10. package/apps/admin/domains/index.tsx +4 -4
  11. package/apps/admin/domains/new/index.tsx +5 -5
  12. package/apps/admin/encryption-policy/index.tsx +1 -1
  13. package/apps/admin/escrow-scopes/[uid].tsx +350 -350
  14. package/apps/admin/escrow-scopes/index.tsx +4 -4
  15. package/apps/admin/escrow-scopes/new/index.tsx +5 -5
  16. package/apps/admin/index.tsx +129 -129
  17. package/apps/admin/ingest-queue/index.tsx +4 -4
  18. package/apps/admin/mailbox-policy/index.tsx +1 -1
  19. package/apps/admin/mailboxes/[uid].tsx +271 -271
  20. package/apps/admin/mailboxes/new/index.tsx +30 -30
  21. package/apps/admin/plugins/index.tsx +15 -15
  22. package/apps/admin/quarantine/index.tsx +5 -5
  23. package/apps/admin/retention-policy/index.tsx +39 -39
  24. package/apps/admin/signing-certificates/index.tsx +343 -343
  25. package/apps/admin/transport-rules/[uid].tsx +5 -5
  26. package/apps/admin/transport-rules/_transportRuleConfig.tsx +3 -3
  27. package/apps/admin/transport-rules/index.tsx +4 -4
  28. package/apps/admin/transport-rules/new/index.tsx +5 -5
  29. package/apps/escrow/_layout.tsx +1 -1
  30. package/apps/escrow/audit-log/index.tsx +196 -196
  31. package/apps/escrow/index.tsx +4 -4
  32. package/apps/escrow/matters/[uid].tsx +621 -621
  33. package/apps/escrow/matters/new/index.tsx +6 -6
  34. package/apps/shared/appearance/AppearanceForm.tsx +3 -3
  35. package/apps/shared/appearance/AppearanceHead.tsx +1 -1
  36. package/apps/shared/appearance/AppearanceProvider.tsx +3 -3
  37. package/apps/shared/appearance/appearanceCache.ts +1 -1
  38. package/apps/shared/appearance/appearanceContext.ts +1 -1
  39. package/apps/shared/appearance/theme.ts +1 -1
  40. package/apps/shared/auth/adminAccess.ts +99 -99
  41. package/apps/shared/calendar/bookingPlugin.ts +1 -1
  42. package/apps/shared/calendar/calendarLiveUpdates.ts +1 -1
  43. package/apps/shared/calendar/calendarReminders.ts +1 -1
  44. package/apps/shared/calendar/useCalendarReminders.ts +1 -1
  45. package/apps/shared/components/admin/diagnostics/DiagnosticsManager.tsx +1 -1
  46. package/apps/shared/components/admin/diagnostics/InstalledPlugins.tsx +2 -2
  47. package/apps/shared/components/admin/diagnostics/LogsPanel.tsx +2 -2
  48. package/apps/shared/components/admin/diagnostics/PackagesTable.tsx +1 -1
  49. package/apps/shared/components/admin/diagnostics/RuntimePanel.tsx +1 -1
  50. package/apps/shared/components/admin/diagnostics/SystemPanel.tsx +2 -2
  51. package/apps/shared/components/admin/diagnostics/VersionsPanel.tsx +1 -1
  52. package/apps/shared/components/admin/diagnostics/diagnosticsApi.ts +1 -1
  53. package/apps/shared/components/admin/diagnostics/format.ts +1 -1
  54. package/apps/shared/components/admin/diagnostics/logClient.ts +1 -1
  55. package/apps/shared/components/admin/distributionLists/MemberListCard.tsx +4 -4
  56. package/apps/shared/components/admin/elevation.ts +1 -1
  57. package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +3 -3
  58. package/apps/shared/components/admin/layout/AdminShell.tsx +384 -384
  59. package/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.tsx +238 -238
  60. package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +154 -152
  61. package/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.tsx +194 -194
  62. package/apps/shared/components/admin/mailboxes/MailboxTable.tsx +1 -1
  63. package/apps/shared/components/admin/mailboxes/ResourceSettingsCard.tsx +19 -14
  64. package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +5 -5
  65. package/apps/shared/components/admin/settings/BrandingForm.tsx +423 -423
  66. package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +5 -5
  67. package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +119 -119
  68. package/apps/shared/components/admin/settings/LoadedSettingsForm.tsx +2 -2
  69. package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +32 -27
  70. package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +198 -198
  71. package/apps/shared/components/admin/settings/PluginsManager.tsx +2093 -2093
  72. package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +182 -182
  73. package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +288 -288
  74. package/apps/shared/components/admin/setup/SetupWizard.tsx +450 -446
  75. package/apps/shared/components/admin/signOut.ts +1 -1
  76. package/apps/shared/components/admin/usePagedList.tsx +129 -129
  77. package/apps/shared/components/calendar/AppointmentQuickForm.tsx +3 -3
  78. package/apps/shared/components/calendar/CalendarListSidebar.tsx +3 -3
  79. package/apps/shared/components/calendar/DescriptionEditor.tsx +1 -1
  80. package/apps/shared/components/calendar/EventDescriptionView.tsx +1 -1
  81. package/apps/shared/components/calendar/EventDetails.tsx +16 -14
  82. package/apps/shared/components/calendar/EventEditor.tsx +54 -39
  83. package/apps/shared/components/calendar/EventExpandedForm.tsx +3 -3
  84. package/apps/shared/components/calendar/EventFormParts.tsx +2 -2
  85. package/apps/shared/components/calendar/EventModal.tsx +206 -206
  86. package/apps/shared/components/calendar/EventQuickForm.tsx +3 -3
  87. package/apps/shared/components/calendar/EventShell.tsx +1 -1
  88. package/apps/shared/components/calendar/FindATime.tsx +6 -4
  89. package/apps/shared/components/calendar/GuestInput.tsx +2 -2
  90. package/apps/shared/components/calendar/MonthView.tsx +185 -185
  91. package/apps/shared/components/calendar/QuickCreateFaces.tsx +1 -1
  92. package/apps/shared/components/calendar/QuickFaceFrame.tsx +1 -1
  93. package/apps/shared/components/calendar/RecurrenceEditor.tsx +227 -227
  94. package/apps/shared/components/calendar/RequestChangeForm.tsx +8 -6
  95. package/apps/shared/components/calendar/ResourcePicker.tsx +7 -5
  96. package/apps/shared/components/calendar/SplitDayView.tsx +144 -144
  97. package/apps/shared/components/calendar/TaskQuickForm.tsx +23 -18
  98. package/apps/shared/components/calendar/TimeGridView.tsx +246 -246
  99. package/apps/shared/components/calendar/allDay.ts +124 -124
  100. package/apps/shared/components/calendar/eventDialogFields.ts +3 -3
  101. package/apps/shared/components/calendar/eventForm.ts +3 -3
  102. package/apps/shared/components/calendar/eventFormat.ts +3 -3
  103. package/apps/shared/components/calendar/findTimeGrid.ts +2 -2
  104. package/apps/shared/components/calendar/layout/CalendarShell.tsx +11 -9
  105. package/apps/shared/components/contacts/ContactDetailPane.tsx +4 -4
  106. package/apps/shared/components/contacts/ContactForm.tsx +393 -383
  107. package/apps/shared/components/contacts/ContactsSidebar.tsx +7 -5
  108. package/apps/shared/components/contacts/ContactsToolbar.tsx +103 -103
  109. package/apps/shared/components/contacts/KeyChangeReview.tsx +5 -5
  110. package/apps/shared/components/contacts/contactKeys.ts +3 -3
  111. package/apps/shared/components/contacts/layout/ContactsShell.tsx +9 -7
  112. package/apps/shared/components/escrow/layout/EscrowShell.tsx +155 -155
  113. package/apps/shared/components/forms/StringListField.tsx +1 -1
  114. package/apps/shared/components/layout/AppShell.tsx +465 -461
  115. package/apps/shared/components/layout/BrandingChrome.tsx +1 -1
  116. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -398
  117. package/apps/shared/components/layout/MailboxProvisioning.tsx +170 -168
  118. package/apps/shared/components/layout/RecoveryCodeUnlock.tsx +9 -9
  119. package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -315
  120. package/apps/shared/components/layout/ThemeSwitch.tsx +84 -84
  121. package/apps/shared/components/layout/UnlockPromptProvider.tsx +6 -6
  122. package/apps/shared/components/layout/UserMenu.tsx +441 -439
  123. package/apps/shared/components/mail/ConversationList.tsx +334 -332
  124. package/apps/shared/components/mail/ConversationThreadPane.tsx +624 -613
  125. package/apps/shared/components/mail/InviteCard.tsx +11 -9
  126. package/apps/shared/components/mail/LazyReadingPane.tsx +2 -2
  127. package/apps/shared/components/mail/MailAddress.tsx +1 -1
  128. package/apps/shared/components/mail/MailListToolbar.tsx +2 -2
  129. package/apps/shared/components/mail/MailSelectionBar.tsx +240 -240
  130. package/apps/shared/components/mail/MenuButton.tsx +404 -404
  131. package/apps/shared/components/mail/MessageDetailPane.tsx +1759 -1757
  132. package/apps/shared/components/mail/MoveToFolderDialog.tsx +8 -6
  133. package/apps/shared/components/mail/OutboxRowStatus.tsx +1 -1
  134. package/apps/shared/components/mail/PermanentDeleteDialog.tsx +2 -2
  135. package/apps/shared/components/mail/compose/ComposeContext.tsx +323 -312
  136. package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -422
  137. package/apps/shared/components/mail/compose/ComposeWindow.tsx +1709 -1681
  138. package/apps/shared/components/mail/compose/ComposeWindowPlaceholder.tsx +4 -4
  139. package/apps/shared/components/mail/compose/EmojiPicker.tsx +2 -2
  140. package/apps/shared/components/mail/compose/GifPicker.tsx +3 -3
  141. package/apps/shared/components/mail/compose/RecipientInput.tsx +1 -1
  142. package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -147
  143. package/apps/shared/components/mail/compose/ScheduleSendPicker.tsx +1 -1
  144. package/apps/shared/components/mail/compose/composeFlushRegistry.ts +60 -60
  145. package/apps/shared/components/mail/compose/encryptionRequirement.ts +2 -2
  146. package/apps/shared/components/mail/compose/quotedBody.ts +161 -161
  147. package/apps/shared/components/mail/compose/recipients.ts +1 -1
  148. package/apps/shared/components/mail/invite/InviteRowChip.tsx +2 -2
  149. package/apps/shared/components/mail/invite/InviteRsvpPopover.tsx +9 -7
  150. package/apps/shared/components/mail/invite/InviteTimeline.tsx +1 -1
  151. package/apps/shared/components/mail/invite/ProposeTimeForm.tsx +2 -2
  152. package/apps/shared/components/mail/invite/inviteFormat.ts +2 -2
  153. package/apps/shared/components/mail/invite/inviteStore.ts +14 -6
  154. package/apps/shared/components/mail/labelMenu.tsx +9 -7
  155. package/apps/shared/components/mail/layout/MailShell.tsx +4 -4
  156. package/apps/shared/components/mail/listPreferences.ts +2 -2
  157. package/apps/shared/components/mail/pinnedSigners.ts +45 -19
  158. package/apps/shared/components/mail/reading/EncryptedBody.tsx +2 -2
  159. package/apps/shared/components/mail/reading/MessageBody.tsx +6 -6
  160. package/apps/shared/components/mail/reading/MessageCard.tsx +3 -3
  161. package/apps/shared/components/mail/reading/MessageMoreMenu.tsx +258 -258
  162. package/apps/shared/components/mail/reading/MessageSourceDialog.tsx +79 -79
  163. package/apps/shared/components/mail/reading/PendingMessageCard.tsx +1 -1
  164. package/apps/shared/components/mail/reading/bodyContent.ts +3 -3
  165. package/apps/shared/components/mail/reading/bodyHtml.ts +1 -1
  166. package/apps/shared/components/mail/reading/messageExport.ts +59 -59
  167. package/apps/shared/components/mail/reading/printMessage.ts +99 -99
  168. package/apps/shared/components/mail/reading/useMessageActions.ts +450 -443
  169. package/apps/shared/components/mail/unreadStyle.tsx +1 -1
  170. package/apps/shared/components/mail/verificationSeals.ts +129 -125
  171. package/apps/shared/components/mail/writableMailboxes.ts +14 -7
  172. package/apps/shared/components/rules/RuleBuilder.tsx +311 -311
  173. package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -344
  174. package/apps/shared/components/settings/layout/SettingsShell.tsx +8 -6
  175. package/apps/shared/components/sharing/PrincipalListField.tsx +1 -1
  176. package/apps/shared/components/sharing/PrincipalPicker.tsx +1 -1
  177. package/apps/shared/components/sharing/PrincipalResolver.tsx +4 -4
  178. package/apps/shared/components/tasks/TasksSidebar.tsx +7 -5
  179. package/apps/shared/components/tasks/layout/TasksShell.tsx +9 -7
  180. package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -51
  181. package/apps/shared/keyboard/ShortcutProvider.tsx +62 -62
  182. package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -84
  183. package/apps/shared/keyboard/dispatch.ts +124 -124
  184. package/apps/shared/keyboard/format.ts +89 -89
  185. package/apps/shared/keyboard/keymap.ts +114 -114
  186. package/apps/shared/keyboard/registry.ts +65 -65
  187. package/apps/shared/keyboard/targets.ts +79 -79
  188. package/apps/shared/mail/findWellKnownFolderUid.ts +6 -4
  189. package/apps/shared/mail/folderCounts.ts +8 -4
  190. package/apps/shared/mail/folderOfType.ts +51 -49
  191. package/apps/shared/mail/folderTree.ts +143 -143
  192. package/apps/shared/mail/listAllPages.ts +39 -39
  193. package/apps/shared/mail/listSnapshots.ts +2 -2
  194. package/apps/shared/mail/messageReadState.ts +6 -5
  195. package/apps/shared/mail/newMailNotifications.ts +171 -171
  196. package/apps/shared/mail/outbox/composeBridge.ts +2 -2
  197. package/apps/shared/mail/outbox/outboxState.ts +1 -1
  198. package/apps/shared/mail/outbox/outgoingReplies.ts +2 -2
  199. package/apps/shared/mail/outbox/sendDecision.ts +2 -2
  200. package/apps/shared/mail/outbox/sendJob.ts +450 -445
  201. package/apps/shared/mail/outbox/sendOutcomes.ts +160 -155
  202. package/apps/shared/mail/outbox/useOutboxStatus.ts +5 -3
  203. package/apps/shared/mail/permanentDelete.ts +8 -7
  204. package/apps/shared/mail/primaryMailbox.ts +1 -1
  205. package/apps/shared/mail/reportNotices.ts +66 -66
  206. package/apps/shared/mail/senderBlocking.ts +157 -141
  207. package/apps/shared/mail/useMailConnection.ts +207 -205
  208. package/apps/shared/mail/useMailLiveUpdates.ts +277 -277
  209. package/apps/shared/mail/useMailboxUpdateAccess.ts +84 -60
  210. package/apps/shared/mail/useMarkMessageRead.ts +54 -47
  211. package/apps/shared/mail/useNewMailNotifications.ts +178 -178
  212. package/apps/shared/mail/usePermanentDelete.tsx +5 -3
  213. package/apps/shared/navigation/pageTitle.ts +1 -1
  214. package/apps/shared/notifications/NotificationCenter.tsx +1 -1
  215. package/apps/shared/notifications/NotificationHistoryDialog.tsx +2 -2
  216. package/apps/shared/notifications/apiErrors.ts +2 -2
  217. package/apps/shared/notifications/pushStatus.ts +1 -1
  218. package/apps/shared/notifications/store.ts +560 -560
  219. package/apps/shared/notifications/systemErrors.ts +1 -1
  220. package/apps/shared/search/LocalIndexLifecycle.tsx +120 -114
  221. package/apps/shared/search/crossMailboxSearch.ts +3 -3
  222. package/apps/shared/search/localIndexBuilder.ts +493 -481
  223. package/apps/shared/search/localIndexKey.ts +1 -1
  224. package/apps/shared/search/localIndexRpcClient.ts +150 -30
  225. package/apps/shared/search/localIndexTransport.ts +84 -0
  226. package/apps/shared/search/localIndexWorker.ts +1 -1
  227. package/apps/shared/search/searchTier2.ts +3 -3
  228. package/apps/shared/signing/enrollmentStorage.ts +33 -33
  229. package/apps/shared/signing/enrollmentTracker.ts +385 -385
  230. package/apps/shared/signing/enrollmentView.ts +251 -251
  231. package/apps/shared/signing/signingInfo.ts +1 -1
  232. package/apps/shared/signing/useNow.ts +19 -19
  233. package/apps/shared/signing/useSigningEnrollmentWatcher.ts +92 -90
  234. package/apps/shared/styles/app.css +387 -396
  235. package/apps/www/_layout.tsx +2 -2
  236. package/apps/www/calendar/index.tsx +583 -581
  237. package/apps/www/contacts/[uid].tsx +114 -112
  238. package/apps/www/contacts/index.tsx +19 -17
  239. package/apps/www/index.tsx +3021 -3012
  240. package/apps/www/messages/[uid].tsx +141 -139
  241. package/apps/www/settings/auto-reply/index.tsx +138 -136
  242. package/apps/www/settings/blocked-senders/index.tsx +305 -303
  243. package/apps/www/settings/encryption/index.tsx +1296 -1290
  244. package/apps/www/settings/filters/[uid].tsx +184 -179
  245. package/apps/www/settings/filters/_mailFilterRuleConfig.tsx +2 -2
  246. package/apps/www/settings/filters/index.tsx +105 -105
  247. package/apps/www/settings/filters/new/index.tsx +167 -165
  248. package/apps/www/settings/labels/index.tsx +209 -207
  249. package/apps/www/settings/privacy/index.tsx +499 -495
  250. package/apps/www/settings/profile/index.tsx +256 -251
  251. package/apps/www/settings/read-receipts/index.tsx +155 -150
  252. package/apps/www/settings/sharing/index.tsx +259 -259
  253. package/apps/www/settings/signatures/[uid].tsx +175 -175
  254. package/apps/www/settings/signatures/index.tsx +91 -91
  255. package/apps/www/settings/signatures/new/index.tsx +138 -138
  256. package/apps/www/settings/signatures/signatureDefaults.ts +1 -1
  257. package/apps/www/tasks/index.tsx +659 -654
  258. package/dist/apps/admin/_layout.d.ts +1 -1
  259. package/dist/apps/admin/_layout.js +1 -1
  260. package/dist/apps/admin/audit-log/index.d.ts +1 -1
  261. package/dist/apps/admin/audit-log/index.js +4 -4
  262. package/dist/apps/admin/branding/index.js +3 -3
  263. package/dist/apps/admin/data-requests/index.js +8 -8
  264. package/dist/apps/admin/distribution-lists/[uid].js +3 -3
  265. package/dist/apps/admin/distribution-lists/index.js +4 -4
  266. package/dist/apps/admin/distribution-lists/new/index.js +5 -5
  267. package/dist/apps/admin/domains/[uid].js +6 -6
  268. package/dist/apps/admin/domains/index.js +4 -4
  269. package/dist/apps/admin/domains/new/index.js +5 -5
  270. package/dist/apps/admin/encryption-policy/index.js +1 -1
  271. package/dist/apps/admin/escrow-scopes/[uid].d.ts +1 -1
  272. package/dist/apps/admin/escrow-scopes/[uid].js +7 -7
  273. package/dist/apps/admin/escrow-scopes/index.js +4 -4
  274. package/dist/apps/admin/escrow-scopes/new/index.js +5 -5
  275. package/dist/apps/admin/index.js +6 -6
  276. package/dist/apps/admin/ingest-queue/index.js +4 -4
  277. package/dist/apps/admin/mailbox-policy/index.js +1 -1
  278. package/dist/apps/admin/mailboxes/[uid].js +5 -5
  279. package/dist/apps/admin/quarantine/index.js +5 -5
  280. package/dist/apps/admin/retention-policy/index.js +3 -3
  281. package/dist/apps/admin/signing-certificates/index.js +5 -5
  282. package/dist/apps/admin/transport-rules/[uid].js +5 -5
  283. package/dist/apps/admin/transport-rules/_transportRuleConfig.d.ts +1 -1
  284. package/dist/apps/admin/transport-rules/_transportRuleConfig.js +2 -2
  285. package/dist/apps/admin/transport-rules/index.js +4 -4
  286. package/dist/apps/admin/transport-rules/new/index.js +5 -5
  287. package/dist/apps/escrow/_layout.d.ts +1 -1
  288. package/dist/apps/escrow/audit-log/index.d.ts +1 -1
  289. package/dist/apps/escrow/audit-log/index.js +4 -4
  290. package/dist/apps/escrow/index.js +4 -4
  291. package/dist/apps/escrow/matters/[uid].js +10 -10
  292. package/dist/apps/escrow/matters/new/index.js +6 -6
  293. package/dist/apps/shared/appearance/AppearanceForm.js +3 -3
  294. package/dist/apps/shared/appearance/AppearanceHead.js +1 -1
  295. package/dist/apps/shared/appearance/AppearanceProvider.d.ts +1 -1
  296. package/dist/apps/shared/appearance/AppearanceProvider.js +3 -3
  297. package/dist/apps/shared/appearance/appearanceCache.d.ts +1 -1
  298. package/dist/apps/shared/appearance/appearanceCache.js +1 -1
  299. package/dist/apps/shared/appearance/appearanceContext.d.ts +1 -1
  300. package/dist/apps/shared/appearance/theme.d.ts +1 -1
  301. package/dist/apps/shared/auth/adminAccess.js +1 -1
  302. package/dist/apps/shared/calendar/bookingPlugin.js +1 -1
  303. package/dist/apps/shared/calendar/calendarLiveUpdates.d.ts +1 -1
  304. package/dist/apps/shared/calendar/calendarReminders.d.ts +1 -1
  305. package/dist/apps/shared/calendar/useCalendarReminders.js +1 -1
  306. package/dist/apps/shared/components/admin/diagnostics/DiagnosticsManager.js +1 -1
  307. package/dist/apps/shared/components/admin/diagnostics/InstalledPlugins.d.ts +1 -1
  308. package/dist/apps/shared/components/admin/diagnostics/InstalledPlugins.js +2 -2
  309. package/dist/apps/shared/components/admin/diagnostics/LogsPanel.js +2 -2
  310. package/dist/apps/shared/components/admin/diagnostics/PackagesTable.js +1 -1
  311. package/dist/apps/shared/components/admin/diagnostics/RuntimePanel.js +1 -1
  312. package/dist/apps/shared/components/admin/diagnostics/SystemPanel.js +2 -2
  313. package/dist/apps/shared/components/admin/diagnostics/VersionsPanel.js +1 -1
  314. package/dist/apps/shared/components/admin/diagnostics/diagnosticsApi.js +1 -1
  315. package/dist/apps/shared/components/admin/diagnostics/format.js +1 -1
  316. package/dist/apps/shared/components/admin/diagnostics/logClient.js +1 -1
  317. package/dist/apps/shared/components/admin/distributionLists/MemberListCard.d.ts +1 -1
  318. package/dist/apps/shared/components/admin/distributionLists/MemberListCard.js +4 -4
  319. package/dist/apps/shared/components/admin/elevation.js +1 -1
  320. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +3 -3
  321. package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
  322. package/dist/apps/shared/components/admin/layout/AdminShell.js +8 -8
  323. package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.d.ts +1 -1
  324. package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.js +6 -6
  325. package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.d.ts +1 -1
  326. package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.js +10 -8
  327. package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.js +4 -4
  328. package/dist/apps/shared/components/admin/mailboxes/MailboxTable.d.ts +1 -1
  329. package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.d.ts +1 -1
  330. package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.js +7 -5
  331. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +5 -5
  332. package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +1 -1
  333. package/dist/apps/shared/components/admin/settings/BrandingForm.js +8 -8
  334. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.d.ts +1 -1
  335. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +5 -5
  336. package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.d.ts +1 -1
  337. package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +4 -4
  338. package/dist/apps/shared/components/admin/settings/LoadedSettingsForm.js +2 -2
  339. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.d.ts +1 -1
  340. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +14 -12
  341. package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.d.ts +1 -1
  342. package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +4 -4
  343. package/dist/apps/shared/components/admin/settings/PluginsManager.js +5 -5
  344. package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.d.ts +1 -1
  345. package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +5 -5
  346. package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +7 -7
  347. package/dist/apps/shared/components/admin/setup/SetupWizard.js +26 -22
  348. package/dist/apps/shared/components/admin/signOut.js +1 -1
  349. package/dist/apps/shared/components/admin/usePagedList.js +2 -2
  350. package/dist/apps/shared/components/calendar/AppointmentQuickForm.js +3 -3
  351. package/dist/apps/shared/components/calendar/CalendarListSidebar.d.ts +1 -1
  352. package/dist/apps/shared/components/calendar/CalendarListSidebar.js +2 -2
  353. package/dist/apps/shared/components/calendar/DescriptionEditor.js +1 -1
  354. package/dist/apps/shared/components/calendar/EventDescriptionView.js +1 -1
  355. package/dist/apps/shared/components/calendar/EventDetails.d.ts +2 -2
  356. package/dist/apps/shared/components/calendar/EventDetails.js +16 -14
  357. package/dist/apps/shared/components/calendar/EventEditor.d.ts +2 -2
  358. package/dist/apps/shared/components/calendar/EventEditor.js +22 -20
  359. package/dist/apps/shared/components/calendar/EventExpandedForm.js +2 -2
  360. package/dist/apps/shared/components/calendar/EventFormParts.d.ts +1 -1
  361. package/dist/apps/shared/components/calendar/EventFormParts.js +1 -1
  362. package/dist/apps/shared/components/calendar/EventModal.d.ts +2 -2
  363. package/dist/apps/shared/components/calendar/EventModal.js +3 -3
  364. package/dist/apps/shared/components/calendar/EventQuickForm.js +3 -3
  365. package/dist/apps/shared/components/calendar/EventShell.js +1 -1
  366. package/dist/apps/shared/components/calendar/FindATime.js +6 -4
  367. package/dist/apps/shared/components/calendar/GuestInput.d.ts +2 -2
  368. package/dist/apps/shared/components/calendar/MonthView.d.ts +1 -1
  369. package/dist/apps/shared/components/calendar/MonthView.js +1 -1
  370. package/dist/apps/shared/components/calendar/QuickCreateFaces.js +1 -1
  371. package/dist/apps/shared/components/calendar/QuickFaceFrame.js +1 -1
  372. package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +1 -1
  373. package/dist/apps/shared/components/calendar/RecurrenceEditor.js +1 -1
  374. package/dist/apps/shared/components/calendar/RequestChangeForm.js +8 -6
  375. package/dist/apps/shared/components/calendar/ResourcePicker.d.ts +1 -1
  376. package/dist/apps/shared/components/calendar/ResourcePicker.js +7 -5
  377. package/dist/apps/shared/components/calendar/SplitDayView.d.ts +1 -1
  378. package/dist/apps/shared/components/calendar/TaskQuickForm.d.ts +1 -1
  379. package/dist/apps/shared/components/calendar/TaskQuickForm.js +9 -7
  380. package/dist/apps/shared/components/calendar/TimeGridView.d.ts +1 -1
  381. package/dist/apps/shared/components/calendar/TimeGridView.js +1 -1
  382. package/dist/apps/shared/components/calendar/allDay.d.ts +2 -2
  383. package/dist/apps/shared/components/calendar/allDay.js +1 -1
  384. package/dist/apps/shared/components/calendar/eventDialogFields.d.ts +2 -2
  385. package/dist/apps/shared/components/calendar/eventDialogFields.js +2 -2
  386. package/dist/apps/shared/components/calendar/eventForm.d.ts +3 -3
  387. package/dist/apps/shared/components/calendar/eventFormat.d.ts +1 -1
  388. package/dist/apps/shared/components/calendar/eventFormat.js +2 -2
  389. package/dist/apps/shared/components/calendar/findTimeGrid.d.ts +1 -1
  390. package/dist/apps/shared/components/calendar/findTimeGrid.js +1 -1
  391. package/dist/apps/shared/components/calendar/layout/CalendarShell.d.ts +1 -1
  392. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +11 -9
  393. package/dist/apps/shared/components/contacts/ContactDetailPane.d.ts +1 -1
  394. package/dist/apps/shared/components/contacts/ContactDetailPane.js +2 -2
  395. package/dist/apps/shared/components/contacts/ContactForm.d.ts +2 -2
  396. package/dist/apps/shared/components/contacts/ContactForm.js +12 -8
  397. package/dist/apps/shared/components/contacts/ContactsSidebar.d.ts +1 -1
  398. package/dist/apps/shared/components/contacts/ContactsSidebar.js +7 -5
  399. package/dist/apps/shared/components/contacts/KeyChangeReview.d.ts +1 -1
  400. package/dist/apps/shared/components/contacts/KeyChangeReview.js +5 -5
  401. package/dist/apps/shared/components/contacts/contactKeys.d.ts +2 -2
  402. package/dist/apps/shared/components/contacts/contactKeys.js +2 -2
  403. package/dist/apps/shared/components/contacts/layout/ContactsShell.d.ts +1 -1
  404. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +9 -7
  405. package/dist/apps/shared/components/escrow/layout/EscrowShell.js +7 -7
  406. package/dist/apps/shared/components/forms/StringListField.js +1 -1
  407. package/dist/apps/shared/components/layout/AppShell.d.ts +2 -2
  408. package/dist/apps/shared/components/layout/AppShell.js +14 -10
  409. package/dist/apps/shared/components/layout/BrandingChrome.d.ts +1 -1
  410. package/dist/apps/shared/components/layout/KeyEnrollmentGate.d.ts +1 -1
  411. package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +9 -9
  412. package/dist/apps/shared/components/layout/MailboxProvisioning.js +8 -6
  413. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.d.ts +1 -1
  414. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.js +9 -9
  415. package/dist/apps/shared/components/layout/UnlockPromptProvider.d.ts +2 -2
  416. package/dist/apps/shared/components/layout/UnlockPromptProvider.js +5 -5
  417. package/dist/apps/shared/components/layout/UserMenu.js +6 -4
  418. package/dist/apps/shared/components/mail/ConversationList.d.ts +2 -2
  419. package/dist/apps/shared/components/mail/ConversationList.js +6 -4
  420. package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +3 -3
  421. package/dist/apps/shared/components/mail/ConversationThreadPane.js +13 -11
  422. package/dist/apps/shared/components/mail/InviteCard.js +10 -8
  423. package/dist/apps/shared/components/mail/LazyReadingPane.js +2 -2
  424. package/dist/apps/shared/components/mail/MailAddress.d.ts +1 -1
  425. package/dist/apps/shared/components/mail/MailAddress.js +1 -1
  426. package/dist/apps/shared/components/mail/MailListToolbar.d.ts +2 -2
  427. package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +2 -2
  428. package/dist/apps/shared/components/mail/MenuButton.js +1 -1
  429. package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +3 -3
  430. package/dist/apps/shared/components/mail/MessageDetailPane.js +30 -28
  431. package/dist/apps/shared/components/mail/MoveToFolderDialog.d.ts +1 -1
  432. package/dist/apps/shared/components/mail/MoveToFolderDialog.js +8 -6
  433. package/dist/apps/shared/components/mail/OutboxRowStatus.d.ts +1 -1
  434. package/dist/apps/shared/components/mail/PermanentDeleteDialog.js +2 -2
  435. package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +10 -1
  436. package/dist/apps/shared/components/mail/compose/ComposeContext.js +4 -2
  437. package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +7 -1
  438. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +54 -31
  439. package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.js +4 -4
  440. package/dist/apps/shared/components/mail/compose/EmojiPicker.js +2 -2
  441. package/dist/apps/shared/components/mail/compose/GifPicker.js +3 -3
  442. package/dist/apps/shared/components/mail/compose/RecipientInput.d.ts +1 -1
  443. package/dist/apps/shared/components/mail/compose/RecipientInput.js +1 -1
  444. package/dist/apps/shared/components/mail/compose/ScheduleSendPicker.js +1 -1
  445. package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +2 -2
  446. package/dist/apps/shared/components/mail/compose/encryptionRequirement.js +1 -1
  447. package/dist/apps/shared/components/mail/compose/quotedBody.d.ts +3 -3
  448. package/dist/apps/shared/components/mail/compose/quotedBody.js +3 -3
  449. package/dist/apps/shared/components/mail/compose/recipients.d.ts +1 -1
  450. package/dist/apps/shared/components/mail/invite/InviteRowChip.d.ts +1 -1
  451. package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.d.ts +1 -1
  452. package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.js +9 -7
  453. package/dist/apps/shared/components/mail/invite/InviteTimeline.d.ts +1 -1
  454. package/dist/apps/shared/components/mail/invite/ProposeTimeForm.d.ts +1 -1
  455. package/dist/apps/shared/components/mail/invite/ProposeTimeForm.js +1 -1
  456. package/dist/apps/shared/components/mail/invite/inviteFormat.d.ts +2 -2
  457. package/dist/apps/shared/components/mail/invite/inviteStore.d.ts +9 -3
  458. package/dist/apps/shared/components/mail/invite/inviteStore.js +13 -6
  459. package/dist/apps/shared/components/mail/labelMenu.d.ts +1 -1
  460. package/dist/apps/shared/components/mail/labelMenu.js +9 -7
  461. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +1 -1
  462. package/dist/apps/shared/components/mail/layout/MailShell.js +3 -3
  463. package/dist/apps/shared/components/mail/listPreferences.d.ts +2 -2
  464. package/dist/apps/shared/components/mail/listPreferences.js +1 -1
  465. package/dist/apps/shared/components/mail/pinnedSigners.d.ts +12 -5
  466. package/dist/apps/shared/components/mail/pinnedSigners.js +37 -18
  467. package/dist/apps/shared/components/mail/reading/EncryptedBody.js +2 -2
  468. package/dist/apps/shared/components/mail/reading/MessageBody.d.ts +2 -2
  469. package/dist/apps/shared/components/mail/reading/MessageBody.js +4 -4
  470. package/dist/apps/shared/components/mail/reading/MessageCard.d.ts +1 -1
  471. package/dist/apps/shared/components/mail/reading/MessageCard.js +2 -2
  472. package/dist/apps/shared/components/mail/reading/MessageSourceDialog.js +3 -3
  473. package/dist/apps/shared/components/mail/reading/PendingMessageCard.js +1 -1
  474. package/dist/apps/shared/components/mail/reading/bodyContent.d.ts +2 -2
  475. package/dist/apps/shared/components/mail/reading/bodyContent.js +2 -2
  476. package/dist/apps/shared/components/mail/reading/bodyHtml.js +1 -1
  477. package/dist/apps/shared/components/mail/reading/messageExport.js +1 -1
  478. package/dist/apps/shared/components/mail/reading/printMessage.d.ts +2 -2
  479. package/dist/apps/shared/components/mail/reading/printMessage.js +11 -11
  480. package/dist/apps/shared/components/mail/reading/useMessageActions.d.ts +1 -1
  481. package/dist/apps/shared/components/mail/reading/useMessageActions.js +18 -16
  482. package/dist/apps/shared/components/mail/unreadStyle.d.ts +1 -1
  483. package/dist/apps/shared/components/mail/verificationSeals.d.ts +27 -6
  484. package/dist/apps/shared/components/mail/verificationSeals.js +15 -11
  485. package/dist/apps/shared/components/mail/writableMailboxes.d.ts +8 -3
  486. package/dist/apps/shared/components/mail/writableMailboxes.js +12 -6
  487. package/dist/apps/shared/components/rules/RuleBuilder.js +1 -1
  488. package/dist/apps/shared/components/settings/SigningCertificateCard.d.ts +1 -1
  489. package/dist/apps/shared/components/settings/SigningCertificateCard.js +3 -3
  490. package/dist/apps/shared/components/settings/layout/SettingsShell.d.ts +1 -1
  491. package/dist/apps/shared/components/settings/layout/SettingsShell.js +8 -6
  492. package/dist/apps/shared/components/sharing/PrincipalListField.d.ts +1 -1
  493. package/dist/apps/shared/components/sharing/PrincipalPicker.d.ts +1 -1
  494. package/dist/apps/shared/components/sharing/PrincipalPicker.js +1 -1
  495. package/dist/apps/shared/components/sharing/PrincipalResolver.d.ts +1 -1
  496. package/dist/apps/shared/components/sharing/PrincipalResolver.js +3 -3
  497. package/dist/apps/shared/components/tasks/TasksSidebar.d.ts +1 -1
  498. package/dist/apps/shared/components/tasks/TasksSidebar.js +7 -5
  499. package/dist/apps/shared/components/tasks/layout/TasksShell.d.ts +1 -1
  500. package/dist/apps/shared/components/tasks/layout/TasksShell.js +9 -7
  501. package/dist/apps/shared/keyboard/ShortcutsDialog.js +1 -1
  502. package/dist/apps/shared/mail/findWellKnownFolderUid.d.ts +5 -3
  503. package/dist/apps/shared/mail/findWellKnownFolderUid.js +5 -4
  504. package/dist/apps/shared/mail/folderCounts.d.ts +1 -1
  505. package/dist/apps/shared/mail/folderCounts.js +6 -4
  506. package/dist/apps/shared/mail/folderOfType.d.ts +3 -2
  507. package/dist/apps/shared/mail/folderOfType.js +4 -4
  508. package/dist/apps/shared/mail/folderTree.d.ts +1 -1
  509. package/dist/apps/shared/mail/listSnapshots.d.ts +2 -2
  510. package/dist/apps/shared/mail/messageReadState.d.ts +4 -3
  511. package/dist/apps/shared/mail/messageReadState.js +5 -5
  512. package/dist/apps/shared/mail/newMailNotifications.d.ts +1 -1
  513. package/dist/apps/shared/mail/newMailNotifications.js +1 -1
  514. package/dist/apps/shared/mail/outbox/composeBridge.d.ts +2 -2
  515. package/dist/apps/shared/mail/outbox/outboxState.d.ts +1 -1
  516. package/dist/apps/shared/mail/outbox/outgoingReplies.d.ts +2 -2
  517. package/dist/apps/shared/mail/outbox/sendDecision.d.ts +2 -2
  518. package/dist/apps/shared/mail/outbox/sendDecision.js +1 -1
  519. package/dist/apps/shared/mail/outbox/sendJob.d.ts +10 -6
  520. package/dist/apps/shared/mail/outbox/sendJob.js +39 -35
  521. package/dist/apps/shared/mail/outbox/sendOutcomes.d.ts +6 -3
  522. package/dist/apps/shared/mail/outbox/sendOutcomes.js +19 -15
  523. package/dist/apps/shared/mail/outbox/useOutboxStatus.js +5 -3
  524. package/dist/apps/shared/mail/permanentDelete.d.ts +4 -3
  525. package/dist/apps/shared/mail/permanentDelete.js +8 -8
  526. package/dist/apps/shared/mail/primaryMailbox.d.ts +1 -1
  527. package/dist/apps/shared/mail/primaryMailbox.js +1 -1
  528. package/dist/apps/shared/mail/reportNotices.d.ts +1 -1
  529. package/dist/apps/shared/mail/senderBlocking.d.ts +5 -4
  530. package/dist/apps/shared/mail/senderBlocking.js +19 -16
  531. package/dist/apps/shared/mail/useMailConnection.d.ts +1 -1
  532. package/dist/apps/shared/mail/useMailConnection.js +12 -10
  533. package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +2 -2
  534. package/dist/apps/shared/mail/useMailLiveUpdates.js +2 -2
  535. package/dist/apps/shared/mail/useMailboxUpdateAccess.d.ts +3 -2
  536. package/dist/apps/shared/mail/useMailboxUpdateAccess.js +31 -10
  537. package/dist/apps/shared/mail/useMarkMessageRead.d.ts +1 -1
  538. package/dist/apps/shared/mail/useMarkMessageRead.js +5 -3
  539. package/dist/apps/shared/mail/useNewMailNotifications.d.ts +1 -1
  540. package/dist/apps/shared/mail/usePermanentDelete.d.ts +1 -1
  541. package/dist/apps/shared/mail/usePermanentDelete.js +4 -2
  542. package/dist/apps/shared/navigation/pageTitle.d.ts +1 -1
  543. package/dist/apps/shared/notifications/NotificationCenter.js +1 -1
  544. package/dist/apps/shared/notifications/NotificationHistoryDialog.js +2 -2
  545. package/dist/apps/shared/notifications/apiErrors.js +2 -2
  546. package/dist/apps/shared/notifications/pushStatus.js +1 -1
  547. package/dist/apps/shared/notifications/systemErrors.js +1 -1
  548. package/dist/apps/shared/search/LocalIndexLifecycle.d.ts +2 -2
  549. package/dist/apps/shared/search/LocalIndexLifecycle.js +15 -10
  550. package/dist/apps/shared/search/crossMailboxSearch.d.ts +1 -1
  551. package/dist/apps/shared/search/crossMailboxSearch.js +2 -2
  552. package/dist/apps/shared/search/localIndexBuilder.d.ts +4 -3
  553. package/dist/apps/shared/search/localIndexBuilder.js +15 -12
  554. package/dist/apps/shared/search/localIndexKey.js +1 -1
  555. package/dist/apps/shared/search/localIndexRpcClient.d.ts +43 -31
  556. package/dist/apps/shared/search/localIndexRpcClient.js +113 -20
  557. package/dist/apps/shared/search/localIndexTransport.d.ts +77 -0
  558. package/dist/apps/shared/search/localIndexTransport.js +1 -0
  559. package/dist/apps/shared/search/localIndexWorker.d.ts +1 -1
  560. package/dist/apps/shared/search/searchTier2.d.ts +3 -3
  561. package/dist/apps/shared/signing/enrollmentTracker.d.ts +1 -1
  562. package/dist/apps/shared/signing/enrollmentTracker.js +3 -3
  563. package/dist/apps/shared/signing/enrollmentView.d.ts +1 -1
  564. package/dist/apps/shared/signing/signingInfo.d.ts +1 -1
  565. package/dist/apps/shared/signing/signingInfo.js +1 -1
  566. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.d.ts +1 -1
  567. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.js +5 -3
  568. package/dist/apps/shared/styles/app.css +387 -396
  569. package/dist/apps/www/_layout.d.ts +1 -1
  570. package/dist/apps/www/_layout.js +1 -1
  571. package/dist/apps/www/calendar/index.d.ts +1 -1
  572. package/dist/apps/www/calendar/index.js +20 -18
  573. package/dist/apps/www/contacts/[uid].d.ts +1 -1
  574. package/dist/apps/www/contacts/[uid].js +8 -6
  575. package/dist/apps/www/contacts/index.d.ts +1 -1
  576. package/dist/apps/www/contacts/index.js +19 -17
  577. package/dist/apps/www/index.d.ts +1 -1
  578. package/dist/apps/www/index.js +54 -50
  579. package/dist/apps/www/messages/[uid].d.ts +1 -1
  580. package/dist/apps/www/messages/[uid].js +11 -9
  581. package/dist/apps/www/settings/appearance/index.d.ts +1 -1
  582. package/dist/apps/www/settings/auto-reply/index.d.ts +1 -1
  583. package/dist/apps/www/settings/auto-reply/index.js +6 -4
  584. package/dist/apps/www/settings/blocked-senders/index.d.ts +1 -1
  585. package/dist/apps/www/settings/blocked-senders/index.js +6 -4
  586. package/dist/apps/www/settings/encryption/index.d.ts +1 -1
  587. package/dist/apps/www/settings/encryption/index.js +31 -29
  588. package/dist/apps/www/settings/filters/[uid].d.ts +1 -1
  589. package/dist/apps/www/settings/filters/[uid].js +11 -9
  590. package/dist/apps/www/settings/filters/_mailFilterRuleConfig.d.ts +2 -2
  591. package/dist/apps/www/settings/filters/index.d.ts +1 -1
  592. package/dist/apps/www/settings/filters/index.js +4 -4
  593. package/dist/apps/www/settings/filters/new/index.d.ts +1 -1
  594. package/dist/apps/www/settings/filters/new/index.js +10 -8
  595. package/dist/apps/www/settings/labels/index.d.ts +1 -1
  596. package/dist/apps/www/settings/labels/index.js +12 -10
  597. package/dist/apps/www/settings/privacy/index.d.ts +1 -1
  598. package/dist/apps/www/settings/privacy/index.js +18 -14
  599. package/dist/apps/www/settings/profile/index.d.ts +1 -1
  600. package/dist/apps/www/settings/profile/index.js +7 -5
  601. package/dist/apps/www/settings/read-receipts/index.d.ts +1 -1
  602. package/dist/apps/www/settings/read-receipts/index.js +5 -3
  603. package/dist/apps/www/settings/sharing/index.d.ts +1 -1
  604. package/dist/apps/www/settings/sharing/index.js +6 -6
  605. package/dist/apps/www/settings/signatures/[uid].d.ts +1 -1
  606. package/dist/apps/www/settings/signatures/[uid].js +5 -5
  607. package/dist/apps/www/settings/signatures/index.d.ts +1 -1
  608. package/dist/apps/www/settings/signatures/index.js +4 -4
  609. package/dist/apps/www/settings/signatures/new/index.d.ts +1 -1
  610. package/dist/apps/www/settings/signatures/new/index.js +4 -4
  611. package/dist/apps/www/settings/signatures/signatureDefaults.d.ts +1 -1
  612. package/dist/apps/www/settings/signatures/signatureDefaults.js +1 -1
  613. package/dist/apps/www/tasks/index.d.ts +1 -1
  614. package/dist/apps/www/tasks/index.js +17 -15
  615. package/dist/lib/admin/auditLogApi.d.ts +32 -0
  616. package/dist/lib/admin/auditLogApi.js +27 -0
  617. package/dist/lib/admin/distributionListsApi.d.ts +47 -0
  618. package/dist/lib/admin/distributionListsApi.js +34 -0
  619. package/dist/lib/admin/domainsApi.d.ts +75 -0
  620. package/dist/lib/admin/domainsApi.js +44 -0
  621. package/dist/lib/admin/escrowAccessRequestsApi.d.ts +79 -0
  622. package/dist/lib/admin/escrowAccessRequestsApi.js +46 -0
  623. package/dist/lib/admin/escrowAuditLogApi.d.ts +66 -0
  624. package/dist/lib/admin/escrowAuditLogApi.js +31 -0
  625. package/dist/lib/admin/escrowScopesApi.d.ts +78 -0
  626. package/dist/lib/admin/escrowScopesApi.js +50 -0
  627. package/dist/lib/admin/leftoverMailboxApi.d.ts +73 -0
  628. package/dist/lib/admin/leftoverMailboxApi.js +67 -0
  629. package/dist/lib/admin/mailboxPolicyApi.d.ts +27 -0
  630. package/dist/lib/admin/mailboxPolicyApi.js +21 -0
  631. package/dist/lib/admin/matterExportApi.d.ts +40 -0
  632. package/dist/lib/admin/matterExportApi.js +39 -0
  633. package/dist/lib/admin/matterSearchApi.d.ts +20 -0
  634. package/dist/lib/admin/matterSearchApi.js +27 -0
  635. package/dist/lib/admin/mattersApi.d.ts +56 -0
  636. package/dist/lib/admin/mattersApi.js +40 -0
  637. package/dist/lib/admin/pluginsApi.d.ts +265 -0
  638. package/dist/lib/admin/pluginsApi.js +102 -0
  639. package/dist/lib/admin/retentionPolicyApi.d.ts +32 -0
  640. package/dist/lib/admin/retentionPolicyApi.js +29 -0
  641. package/dist/lib/admin/setupApi.d.ts +23 -0
  642. package/dist/lib/admin/setupApi.js +26 -0
  643. package/dist/lib/admin/transportRulesApi.d.ts +66 -0
  644. package/dist/lib/admin/transportRulesApi.js +43 -0
  645. package/dist/lib/appearance/preferencesApi.d.ts +128 -0
  646. package/dist/lib/appearance/preferencesApi.js +257 -0
  647. package/dist/lib/auth/profileApi.d.ts +33 -0
  648. package/dist/lib/auth/profileApi.js +52 -0
  649. package/dist/lib/auth/session.d.ts +70 -0
  650. package/dist/lib/auth/session.js +210 -0
  651. package/dist/lib/branding/brandingApi.d.ts +47 -0
  652. package/dist/lib/branding/brandingApi.js +61 -0
  653. package/dist/lib/branding/useBranding.d.ts +23 -0
  654. package/dist/lib/branding/useBranding.js +65 -0
  655. package/dist/lib/calendar/calendarApi.d.ts +230 -0
  656. package/dist/lib/calendar/calendarApi.js +106 -0
  657. package/dist/lib/calendar/calendarColors.d.ts +18 -0
  658. package/dist/lib/calendar/calendarColors.js +39 -0
  659. package/dist/lib/calendar/calendarDragIds.d.ts +42 -0
  660. package/dist/lib/calendar/calendarDragIds.js +92 -0
  661. package/dist/lib/calendar/calendarMutations.d.ts +68 -0
  662. package/dist/lib/calendar/calendarMutations.js +190 -0
  663. package/dist/lib/calendar/eventDescription.d.ts +43 -0
  664. package/dist/lib/calendar/eventDescription.js +195 -0
  665. package/dist/lib/calendar/freeBusyApi.d.ts +100 -0
  666. package/dist/lib/calendar/freeBusyApi.js +119 -0
  667. package/dist/lib/calendar/inviteApi.d.ts +121 -0
  668. package/dist/lib/calendar/inviteApi.js +54 -0
  669. package/dist/lib/calendar/recurrence.d.ts +60 -0
  670. package/dist/lib/calendar/recurrence.js +225 -0
  671. package/dist/lib/components/avatar/ContactAvatar.d.ts +8 -0
  672. package/dist/lib/components/avatar/ContactAvatar.js +26 -0
  673. package/dist/lib/components/buttons/Button.d.ts +9 -0
  674. package/dist/lib/components/buttons/Button.js +14 -0
  675. package/dist/lib/components/buttons/CopyButton.d.ts +18 -0
  676. package/dist/lib/components/buttons/CopyButton.js +14 -0
  677. package/dist/lib/components/feedback/Alert.d.ts +4 -0
  678. package/dist/lib/components/feedback/Alert.js +4 -0
  679. package/dist/lib/components/feedback/Skeleton.d.ts +21 -0
  680. package/dist/lib/components/feedback/Skeleton.js +14 -0
  681. package/dist/lib/components/forms/FormField.d.ts +8 -0
  682. package/dist/lib/components/forms/FormField.js +5 -0
  683. package/dist/lib/components/navigation/BottomTabBar.d.ts +27 -0
  684. package/dist/lib/components/navigation/BottomTabBar.js +32 -0
  685. package/dist/lib/components/overlays/Drawer.d.ts +20 -0
  686. package/dist/lib/components/overlays/Drawer.js +27 -0
  687. package/dist/lib/components/overlays/Modal.d.ts +17 -0
  688. package/dist/lib/components/overlays/Modal.js +25 -0
  689. package/dist/lib/components/overlays/PopoverPortal.d.ts +23 -0
  690. package/dist/lib/components/overlays/PopoverPortal.js +53 -0
  691. package/dist/lib/components/overlays/overlayStack.d.ts +31 -0
  692. package/dist/lib/components/overlays/overlayStack.js +143 -0
  693. package/dist/lib/components/pickers/MiniDatePicker.d.ts +13 -0
  694. package/dist/lib/components/pickers/MiniDatePicker.js +42 -0
  695. package/dist/lib/contacts/contactsApi.d.ts +174 -0
  696. package/dist/lib/contacts/contactsApi.js +139 -0
  697. package/dist/lib/contacts/vcard.d.ts +23 -0
  698. package/dist/lib/contacts/vcard.js +179 -0
  699. package/dist/lib/crypto/composeSecurity.d.ts +67 -0
  700. package/dist/lib/crypto/composeSecurity.js +93 -0
  701. package/dist/lib/crypto/encoding.d.ts +9 -0
  702. package/dist/lib/crypto/encoding.js +32 -0
  703. package/dist/lib/crypto/escrowKeys.d.ts +25 -0
  704. package/dist/lib/crypto/escrowKeys.js +59 -0
  705. package/dist/lib/crypto/idleTimeout.d.ts +16 -0
  706. package/dist/lib/crypto/idleTimeout.js +47 -0
  707. package/dist/lib/crypto/keySession.d.ts +159 -0
  708. package/dist/lib/crypto/keySession.js +367 -0
  709. package/dist/lib/crypto/keys.d.ts +50 -0
  710. package/dist/lib/crypto/keys.js +89 -0
  711. package/dist/lib/crypto/keyvaultApi.d.ts +394 -0
  712. package/dist/lib/crypto/keyvaultApi.js +350 -0
  713. package/dist/lib/crypto/masterKey.d.ts +59 -0
  714. package/dist/lib/crypto/masterKey.js +104 -0
  715. package/dist/lib/crypto/masterKeyWraps.d.ts +108 -0
  716. package/dist/lib/crypto/masterKeyWraps.js +228 -0
  717. package/dist/lib/crypto/messageSecurity.d.ts +248 -0
  718. package/dist/lib/crypto/messageSecurity.js +367 -0
  719. package/dist/lib/crypto/mime.d.ts +143 -0
  720. package/dist/lib/crypto/mime.js +504 -0
  721. package/dist/lib/crypto/passkeyUnlock.d.ts +20 -0
  722. package/dist/lib/crypto/passkeyUnlock.js +79 -0
  723. package/dist/lib/crypto/passwordUnlock.d.ts +39 -0
  724. package/dist/lib/crypto/passwordUnlock.js +73 -0
  725. package/dist/lib/crypto/recoveryCode.d.ts +28 -0
  726. package/dist/lib/crypto/recoveryCode.js +86 -0
  727. package/dist/lib/crypto/signingProviderApi.d.ts +95 -0
  728. package/dist/lib/crypto/signingProviderApi.js +60 -0
  729. package/dist/lib/crypto/smime.d.ts +128 -0
  730. package/dist/lib/crypto/smime.js +402 -0
  731. package/dist/lib/crypto/smimeMessage.d.ts +132 -0
  732. package/dist/lib/crypto/smimeMessage.js +341 -0
  733. package/dist/lib/crypto/useIdleKeyTimeout.d.ts +10 -0
  734. package/dist/lib/crypto/useIdleKeyTimeout.js +163 -0
  735. package/dist/lib/crypto/verificationSeal.d.ts +56 -0
  736. package/dist/lib/crypto/verificationSeal.js +220 -0
  737. package/dist/lib/mail/compose/composeQuoting.d.ts +88 -0
  738. package/dist/lib/mail/compose/composeQuoting.js +204 -0
  739. package/dist/lib/mail/compose/emojiData.d.ts +9 -0
  740. package/dist/lib/mail/compose/emojiData.js +18 -0
  741. package/dist/lib/mail/compose/giphyApi.d.ts +17 -0
  742. package/dist/lib/mail/compose/giphyApi.js +20 -0
  743. package/dist/lib/mail/conversationsApi.d.ts +80 -0
  744. package/dist/lib/mail/conversationsApi.js +44 -0
  745. package/dist/lib/mail/dataExportApi.d.ts +50 -0
  746. package/dist/lib/mail/dataExportApi.js +40 -0
  747. package/dist/lib/mail/directoryApi.d.ts +49 -0
  748. package/dist/lib/mail/directoryApi.js +101 -0
  749. package/dist/lib/mail/erasureRequestApi.d.ts +45 -0
  750. package/dist/lib/mail/erasureRequestApi.js +43 -0
  751. package/dist/lib/mail/flaggedMessages.d.ts +22 -0
  752. package/dist/lib/mail/flaggedMessages.js +73 -0
  753. package/dist/lib/mail/focusedInboxOverridesApi.d.ts +37 -0
  754. package/dist/lib/mail/focusedInboxOverridesApi.js +34 -0
  755. package/dist/lib/mail/labelsApi.d.ts +47 -0
  756. package/dist/lib/mail/labelsApi.js +38 -0
  757. package/dist/lib/mail/mailAddress.d.ts +27 -0
  758. package/dist/lib/mail/mailAddress.js +54 -0
  759. package/dist/lib/mail/mailApi.d.ts +850 -0
  760. package/dist/lib/mail/mailApi.js +602 -0
  761. package/dist/lib/mail/mailDetailHooks.d.ts +10 -0
  762. package/dist/lib/mail/mailDetailHooks.js +53 -0
  763. package/dist/lib/mail/mailFilterRulesApi.d.ts +76 -0
  764. package/dist/lib/mail/mailFilterRulesApi.js +45 -0
  765. package/dist/lib/mail/mailSignaturesApi.d.ts +49 -0
  766. package/dist/lib/mail/mailSignaturesApi.js +42 -0
  767. package/dist/lib/mail/mailboxAccessApi.d.ts +74 -0
  768. package/dist/lib/mail/mailboxAccessApi.js +56 -0
  769. package/dist/lib/mail/mailboxImportApi.d.ts +53 -0
  770. package/dist/lib/mail/mailboxImportApi.js +51 -0
  771. package/dist/lib/mail/messageBodySanitizer.d.ts +16 -0
  772. package/dist/lib/mail/messageBodySanitizer.js +118 -0
  773. package/dist/lib/mail/pushClient.d.ts +106 -0
  774. package/dist/lib/mail/pushClient.js +317 -0
  775. package/dist/lib/mail/sendEvents.d.ts +33 -0
  776. package/dist/lib/mail/sendEvents.js +44 -0
  777. package/dist/lib/mail/sendFailure.d.ts +16 -0
  778. package/dist/lib/mail/sendFailure.js +86 -0
  779. package/dist/lib/mail/senderListsApi.d.ts +69 -0
  780. package/dist/lib/mail/senderListsApi.js +126 -0
  781. package/dist/lib/search/queryGrammar.d.ts +51 -0
  782. package/dist/lib/search/queryGrammar.js +111 -0
  783. package/dist/lib/search/searchApi.d.ts +100 -0
  784. package/dist/lib/search/searchApi.js +107 -0
  785. package/dist/lib/search/searchScoring.d.ts +34 -0
  786. package/dist/lib/search/searchScoring.js +44 -0
  787. package/dist/lib/search/searchTier3.d.ts +64 -0
  788. package/dist/lib/search/searchTier3.js +335 -0
  789. package/dist/lib/tasks/tasksApi.d.ts +85 -0
  790. package/dist/lib/tasks/tasksApi.js +55 -0
  791. package/dist/lib/util/api.d.ts +160 -0
  792. package/dist/lib/util/api.js +251 -0
  793. package/dist/lib/util/apiClientContext.d.ts +7 -0
  794. package/dist/lib/util/apiClientContext.js +32 -0
  795. package/dist/lib/util/apiQuery.d.ts +32 -0
  796. package/dist/lib/util/apiQuery.js +42 -0
  797. package/dist/lib/util/clipboard.d.ts +7 -0
  798. package/dist/lib/util/clipboard.js +56 -0
  799. package/dist/lib/util/dateInput.d.ts +6 -0
  800. package/dist/lib/util/dateInput.js +14 -0
  801. package/dist/lib/util/timeZone.d.ts +12 -0
  802. package/dist/lib/util/timeZone.js +32 -0
  803. package/dist/lib/util/useCopyToClipboard.d.ts +12 -0
  804. package/dist/lib/util/useCopyToClipboard.js +34 -0
  805. package/dist/lib/util/useIsMobile.d.ts +10 -0
  806. package/dist/lib/util/useIsMobile.js +29 -0
  807. package/dist/lib/videoconf/videoMeetingsApi.d.ts +116 -0
  808. package/dist/lib/videoconf/videoMeetingsApi.js +49 -0
  809. package/lib/admin/auditLogApi.ts +48 -0
  810. package/lib/admin/distributionListsApi.ts +77 -0
  811. package/lib/admin/domainsApi.ts +113 -0
  812. package/lib/admin/escrowAccessRequestsApi.ts +110 -0
  813. package/lib/admin/escrowAuditLogApi.ts +101 -0
  814. package/lib/admin/escrowScopesApi.ts +112 -0
  815. package/lib/admin/leftoverMailboxApi.ts +120 -0
  816. package/lib/admin/mailboxPolicyApi.ts +38 -0
  817. package/lib/admin/matterExportApi.ts +62 -0
  818. package/lib/admin/matterSearchApi.ts +35 -0
  819. package/lib/admin/mattersApi.ts +86 -0
  820. package/lib/admin/pluginsApi.ts +339 -0
  821. package/lib/admin/retentionPolicyApi.ts +46 -0
  822. package/lib/admin/setupApi.ts +40 -0
  823. package/lib/admin/transportRulesApi.ts +106 -0
  824. package/lib/appearance/preferencesApi.ts +335 -0
  825. package/lib/auth/profileApi.ts +77 -0
  826. package/lib/auth/session.ts +238 -0
  827. package/lib/branding/brandingApi.ts +97 -0
  828. package/lib/branding/useBranding.ts +81 -0
  829. package/lib/calendar/calendarApi.ts +309 -0
  830. package/lib/calendar/calendarColors.ts +45 -0
  831. package/lib/calendar/calendarDragIds.ts +112 -0
  832. package/lib/calendar/calendarMutations.ts +236 -0
  833. package/lib/calendar/eventDescription.ts +211 -0
  834. package/lib/calendar/freeBusyApi.ts +197 -0
  835. package/lib/calendar/inviteApi.ts +154 -0
  836. package/lib/calendar/recurrence.ts +285 -0
  837. package/lib/components/avatar/ContactAvatar.tsx +47 -0
  838. package/lib/components/buttons/Button.tsx +40 -0
  839. package/lib/components/buttons/CopyButton.tsx +45 -0
  840. package/lib/components/feedback/Alert.tsx +13 -0
  841. package/lib/components/feedback/Skeleton.tsx +38 -0
  842. package/lib/components/forms/FormField.tsx +23 -0
  843. package/lib/components/navigation/BottomTabBar.tsx +66 -0
  844. package/lib/components/overlays/Drawer.tsx +72 -0
  845. package/lib/components/overlays/Modal.tsx +64 -0
  846. package/lib/components/overlays/PopoverPortal.tsx +74 -0
  847. package/lib/components/overlays/overlayStack.ts +165 -0
  848. package/lib/components/pickers/MiniDatePicker.tsx +90 -0
  849. package/lib/contacts/contactsApi.ts +289 -0
  850. package/lib/contacts/vcard.ts +197 -0
  851. package/lib/crypto/composeSecurity.ts +140 -0
  852. package/lib/crypto/encoding.ts +36 -0
  853. package/lib/crypto/escrowKeys.ts +74 -0
  854. package/lib/crypto/idleTimeout.ts +49 -0
  855. package/lib/crypto/keySession.ts +483 -0
  856. package/lib/crypto/keys.ts +105 -0
  857. package/lib/crypto/keyvaultApi.ts +674 -0
  858. package/lib/crypto/masterKey.ts +134 -0
  859. package/lib/crypto/masterKeyWraps.ts +269 -0
  860. package/lib/crypto/messageSecurity.ts +600 -0
  861. package/lib/crypto/mime.ts +568 -0
  862. package/lib/crypto/passkeyUnlock.ts +103 -0
  863. package/lib/crypto/passwordUnlock.ts +98 -0
  864. package/lib/crypto/recoveryCode.ts +93 -0
  865. package/lib/crypto/signingProviderApi.ts +135 -0
  866. package/lib/crypto/smime.ts +459 -0
  867. package/lib/crypto/smimeMessage.ts +491 -0
  868. package/lib/crypto/useIdleKeyTimeout.ts +176 -0
  869. package/lib/crypto/verificationSeal.ts +288 -0
  870. package/lib/mail/compose/composeQuoting.ts +243 -0
  871. package/lib/mail/compose/emojiData.ts +34 -0
  872. package/lib/mail/compose/giphyApi.ts +31 -0
  873. package/lib/mail/conversationsApi.ts +123 -0
  874. package/lib/mail/dataExportApi.ts +73 -0
  875. package/lib/mail/directoryApi.ts +161 -0
  876. package/lib/mail/erasureRequestApi.ts +71 -0
  877. package/lib/mail/flaggedMessages.ts +80 -0
  878. package/lib/mail/focusedInboxOverridesApi.ts +64 -0
  879. package/lib/mail/labelsApi.ts +71 -0
  880. package/lib/mail/mailAddress.ts +76 -0
  881. package/lib/mail/mailApi.ts +1286 -0
  882. package/lib/mail/mailDetailHooks.ts +57 -0
  883. package/lib/mail/mailFilterRulesApi.ts +116 -0
  884. package/lib/mail/mailSignaturesApi.ts +84 -0
  885. package/lib/mail/mailboxAccessApi.ts +106 -0
  886. package/lib/mail/mailboxImportApi.ts +87 -0
  887. package/lib/mail/messageBodySanitizer.ts +129 -0
  888. package/lib/mail/pushClient.ts +375 -0
  889. package/lib/mail/sendEvents.ts +72 -0
  890. package/lib/mail/sendFailure.ts +98 -0
  891. package/lib/mail/senderListsApi.ts +161 -0
  892. package/lib/search/queryGrammar.ts +179 -0
  893. package/lib/search/searchApi.ts +199 -0
  894. package/lib/search/searchScoring.ts +46 -0
  895. package/lib/search/searchTier3.ts +406 -0
  896. package/lib/tasks/tasksApi.ts +128 -0
  897. package/lib/util/api.ts +318 -0
  898. package/lib/util/apiClientContext.ts +35 -0
  899. package/lib/util/apiQuery.ts +61 -0
  900. package/lib/util/clipboard.ts +55 -0
  901. package/lib/util/dateInput.ts +15 -0
  902. package/lib/util/timeZone.ts +33 -0
  903. package/lib/util/useCopyToClipboard.ts +48 -0
  904. package/lib/util/useIsMobile.ts +34 -0
  905. package/lib/videoconf/videoMeetingsApi.ts +144 -0
  906. package/package.json +10 -4
package/README.md CHANGED
@@ -1,381 +1,415 @@
1
- # RapidMX: Web Client
2
-
3
- [![npm version](https://img.shields.io/npm/v/@rapidmx/web-client)](https://www.npmjs.com/package/@rapidmx/web-client)
4
-
5
- RapidMX's webmail (`apps/www`), admin console (`apps/admin`) and escrow console (`apps/escrow`) React UI. The pages are
6
- served and hydrated by [`rapidmx/server`](https://github.com/RapidMX/server) through `@rapidrest/react`'s file-convention
7
- routes, and `@rapidmx/electron-client` reuses the same components. Platform-agnostic API clients, hooks and generic UI
8
- primitives live in [`@rapidmx/react-shared`](https://github.com/RapidMX/react-shared).
9
-
10
- ## Package layout
11
-
12
- The package ships the TSX sources (`apps/`) and a compiled mirror (`dist/apps/`, JavaScript plus `.d.ts` declarations).
13
- There is no root export. Every module is its own subpath, mapped by `package.json`'s `exports` from
14
- `@rapidmx/web-client/<path>.js` to `dist/apps/<path>.js`:
15
-
16
- ```ts
17
- import SettingsShell from "@rapidmx/web-client/shared/components/settings/layout/SettingsShell.js";
18
- ```
19
-
20
- `@rapidmx/web-client/shared/styles/app.css` is the Tailwind entry point and design tokens.
21
-
22
- ## Navigation without page loads
23
-
24
- All three apps use `@rapidrest/react`'s client-side router (`router = true` on the server's route, the client built with
25
- `createViteConfig({ router })`; `@rapidrest/react` is a **peer dependency** here, `>=2.1.0 <3`, because the router's state lives
26
- in a React context that this package must share with the server's one copy). The server still renders every URL, so first
27
- loads, reloads and crawlers are unchanged; after the first load a click on an ordinary `<a href>` between two pages of an app
28
- fetches the next page's props as JSON and swaps the page in. Back and forward work, the address bar always holds the real,
29
- shareable URL, and anything the router cannot be sure of (a modified click, `target`, `download`, `data-router-ignore`, another
30
- app's page - the consoles, plugin pages, other sites - or a failure) is left to the browser as a page load.
31
-
32
- The admin and escrow pages each render their own shell, so the router just swaps the page. The webmail (`apps/www`) has an **app
33
- shell**, `apps/www/_shell.tsx` (the router's persistent client layout), which the server renders around every page and the
34
- browser hydrates with it as one root:
35
-
36
- - the one `AppChrome` (the app rail, header, user menu, impersonation banner, compose windows in progress, the unlock prompt,
37
- the idle-key timer, the mail connection and the notification pop-ups) stays mounted while only the page inside it is replaced.
38
- A page's own shell (`MailShell`, `CalendarShell`, ...) still renders `AppShell`; inside the frame that is only its children;
39
- - the shell gets the current page's props - `WwwRoute.fetchProps()`'s, the same for every page - so the rail's highlighted app
40
- (from the route), the branding, the plugin navigation and the impersonation banner follow the server on every navigation. That is
41
- one request to `fetchProps()` per page change (warm for most, since the router prefetches a plain link on hover and the four rail
42
- pages' code when the browser is idle);
43
- - **a folder is a shallow navigation.** `<a data-router-shallow>` (the Mail folder links), `navigate()` to the same path with another
44
- query (`useNavigate()`, below) and `useSearchParams()`' setter keep the page instance and its state and only change the location;
45
- - after a page change the router moves focus to the content region (`#app-content`), scrolls to the top and announces the page's
46
- title in a polite live region (all set by the shell with `useNavigationEffects()`); the tab's title is each page's `title`
47
- export (`pageTitle("Calendar")`: `Acme Mail: Calendar`), rendered by the server and sent with every navigation, with the unread
48
- count kept in front of it; the content region is `aria-busy` while the next page loads.
49
-
50
- Code that decides where to go uses `useNavigate()` from `shared/navigation/index.js` and reads the location with the library's
51
- hooks:
52
-
53
- ```tsx
54
- import { useLocation, useSearchParams } from "@rapidrest/react/client";
55
- import { useNavigate } from "@rapidmx/web-client/shared/navigation/index.js";
56
-
57
- const navigate = useNavigate(); // navigate("/contacts"); navigate("/?mailboxUid=a&folderUid=b") keeps the page (same path)
58
- const { pathname, search, hash } = useLocation(); // the request's on the server and while hydrating; `hash` is empty there
59
- const [params, setParams] = useSearchParams(); // setParams({ folderUid: "x" }) is shallow by default
60
- ```
61
-
62
- `navigate()` changes the page without a load when the URL is a page of the app, and is an ordinary navigation otherwise (outside a
63
- router too). An admin or escrow page that leads to another page of its console after a save goes there with `useRouter().navigate`.
64
- Plugin pages are not part of the router (they render their own `AppShell` chrome) and are opened with a page load.
65
-
66
- The compose window, the reading pane, S/MIME and the emoji list are also chunks of their own, loaded on demand or fetched
67
- when the browser is idle, so a page's first JavaScript is React and what the first screen draws.
68
-
69
- ## Keyboard shortcuts
70
-
71
- One keyboard layer, `shared/keyboard/`, lives in the persistent app frame (`AppChrome`), so the shortcuts work in every view -
72
- Mail, Calendar, Contacts, Tasks and Settings - and a page change only changes which of them are registered. `?` (or `Ctrl+/`)
73
- opens a "Keyboard shortcuts" dialog, also reached from the account menu, that lists what is available *here* - the global
74
- shortcuts plus those of the view on screen (and of an open compose window) - with the platform's own key names. `mod` below is
75
- **Ctrl** on Windows and Linux and **Cmd** on a Mac; the navigation set is Ctrl+Shift on every platform (Cmd+Shift collides with the
76
- browsers' own, so it is not offered).
77
-
78
- | Where | Key | Does |
79
- | --- | --- | --- |
80
- | Everywhere | `Ctrl+Shift+A` | Account (auth-server's account page; only offered when one is configured) |
81
- | | `Ctrl+Shift+S` / `B` / `M` / `C` / `L` | Settings / Contacts / Mail / Calendar / To-Do |
82
- | | `?` or `Ctrl+/` | Keyboard shortcuts |
83
- | Mail | `Alt+N` | New message |
84
- | | `mod+R` / `mod+Shift+R` / `mod+Shift+F` | Reply / Reply all / Forward the selected message |
85
- | | `mod+D` or `Delete` | Delete the selected message or conversation (moves it to Deleted Items) |
86
- | | `E` or `Backspace` | Archive |
87
- | | `mod+Shift+V` | Move to folder |
88
- | | `Ctrl+Q` / `mod+U` | Mark as read / unread (`Ctrl+Q` on a Mac too: `Cmd+Q` quits) |
89
- | | `Insert` | Flag or unflag |
90
- | | `Down` or `J` / `Up` or `K` | Next / previous message (conversation) |
91
- | | `Ctrl+.` / `Ctrl+,` | Next / previous unread |
92
- | | `Enter` / `Escape` | Open the selected message on its own page / clear the selection (leave select mode, clear the search) |
93
- | | `/` or `mod+E` | Search |
94
- | Compose window | `mod+Enter` | Send |
95
- | | `mod+S` | Save draft |
96
- | | `Escape` | Close, keeping the draft (the existing "keep draft / discard" question still applies) |
97
- | | `Alt+N` | Another new message |
98
- | Calendar | `Alt+N` | New event |
99
- | | `T` | Today |
100
- | | `Left` / `Right` (or `mod+Left` / `mod+Right`) | Previous / next period |
101
- | | `Ctrl+Alt+1` / `2` / `3` / `4` | Day / work week / week / month |
102
- | Contacts | `Alt+N` | New contact |
103
- | | `/` or `mod+E` | Search |
104
- | Tasks | `Alt+N` | New task (moves to the "Add a task" field) |
105
-
106
- In the desktop client (`@rapidmx/electron-client`, which exposes `window.rapidmx`) `Ctrl+N` (`Cmd+N` on a Mac) also creates - a new
107
- message, event, contact or task in the current view - and `Ctrl+Shift+T` also goes to Tasks; browsers keep both for themselves, which is
108
- why the web client uses `Alt+N` and `Ctrl+Shift+L`.
109
-
110
- How it behaves:
111
-
112
- - **Views register only what they can do.** A view claims a shortcut with `useShortcut(SHORTCUTS.mail.reply, handler, { enabled })`
113
- for as long as it is mounted and able to do it (Reply exists only while a message is selected, Archive not for Drafts or Outbox),
114
- so there are no dead keys and the help dialog is always accurate. `SHORTCUTS` in `shared/keyboard/keymap.js` is the one key map;
115
- handlers are looked up when the key is pressed. A handler that returns `false` declines the key.
116
- - **Scopes.** `global`, the view's own (`mail`, `calendar`, `contacts`, `tasks`), `compose` (while focus is inside a compose window; it
117
- beats the view behind it) and `dialog`: while a modal dialog (`aria-modal`) is open only its own shortcuts and its own Escape work.
118
- - **Typing is never taken.** Bare keys (`J`, `E`, `?`, `Delete`) do nothing while focus is in a text field, select or the rich-text
119
- editor, and caret keys with a modifier (word jumps) stay the field's. Chords with Ctrl/Alt/Cmd work from a field, except Option on
120
- a Mac and Ctrl+Alt (AltGr) elsewhere, which type characters. Copy, Cut, Paste, Select all, Undo, Redo and Find are never
121
- bound. Enter and Space are left to a focused button or link, and an open menu keeps its own keys. Events already
122
- `defaultPrevented` (the editor's own bindings, a menu), IME composition and held-key repeats of one-shot actions are ignored, and
123
- the browser's default is prevented only when a handler actually ran.
124
- - **Layouts.** A key is matched on `event.key` (so it follows AZERTY or Dvorak) with `event.code` as the fallback when `event.key` is
125
- not a Latin character - macOS Option+N, or a Cyrillic layout.
126
- - **Hints.** Buttons that a shortcut also does carry `aria-keyshortcuts` and a tooltip such as "Reply (Ctrl+R)"; their accessible
127
- names are unchanged (`useShortcutProps()` gives a control both).
128
-
129
- Known limits: a browser keeps a few keys for itself (`Ctrl+N`, `Ctrl+T`, `Ctrl+W` are never delivered to a page, and some browsers claim
130
- `Ctrl+Shift+A/B/C/M/S`); a message body is shown in a frame of its own, which hands the keys pressed in it on to the layer, so they work there too. In the compose body the editor's own bindings win over `Ctrl+Shift+S`, `B`
131
- and `L` (strike-through, quote, align left).
132
-
133
- ## The reading pane
134
-
135
- `shared/components/mail/` draws a message as a **card** and a conversation as a stack of them, under a **subject card** that stays pinned at the top
136
- while the cards scroll (`MessageDetailPane`, `ConversationThreadPane`, and `reading/` for the pieces):
137
-
138
- - **Subject card** - the conversation's (or the message's) subject as the page's `h1`, "N messages", a note when a very long thread was cut. In a
139
- thread it is on screen at once, from what the list row already knows, with a skeleton card per message (up to three) until the messages arrive.
140
- - **Message card** - rounded, bordered and lifted, in the app's own tokens (`bg-surface`, `text-text`, `border-border`), so it follows the theme, a
141
- branding palette and the Appearance colours. A header row with the sender's initials, `Name <address>`, To/Cc/Bcc, the time and the actions (Reply,
142
- Reply All, Forward, Archive, Move to, Labels, and "View original" when it applies); the security badge and every notice (signature, key change,
143
- receipts, Outbox state); attachments; the body; a slim "You replied to this message." bar; and, on a single message and on the newest open one in a
144
- thread, Reply and Forward at its foot. An older message of a thread is a collapsed card (sender, date, first line) that expands on click or Enter
145
- (`aria-expanded`, `aria-controls`; the focus stays on the message's header button as it swaps between the two states).
146
- - **Each card is exactly as tall as its message.** The body is shown at its natural height - no inner scrollbar, no dead space, no fixed height - and
147
- follows it as images load and the width changes. A table or image wider than the card scrolls **inside** the card and never widens the pane. The
148
- pane scrolls as a whole. Everything is on screen before the body: the header card, the message card's header and a skeleton where the body will be.
149
-
150
- - **Encrypted messages** are themed card content in the same place as the body, never a frame: while the security state is being worked out, a skeleton;
151
- locked (no unlocked keys on this device), a lock, "This message is encrypted", "Unlock your keys to read it" and an **Unlock** button that asks for the unlock
152
- prompt and, once unlocked - by it or from anywhere - decrypts the message in place and moves the focus to it; unreadable with the keys in hand (not a recipient,
153
- damaged), "This message can’t be decrypted" with the reason and no button; decrypted, the message like any other. The placeholder subject `[...]` reads
154
- "Encrypted message", and list rows and collapsed cards with no preview say "Encrypted message" with a small lock.
155
-
156
- **How a body is shown.** The server sanitizes a message's HTML when it ingests it (`GET /mail/messages/:id/content`); the client then treats what it
157
- receives - and everything it recovers by decrypting or verifying, which the server never saw - as hostile. It is sanitized again with DOMPurify (scripts,
158
- event handlers, `javascript:` URLs, every remote resource), stripped of forms, frames, media, plug-ins, SVG's active elements and every link that is not
159
- `http(s)`/`mailto`/`tel`, and shown in a **sandboxed iframe without `allow-scripts`**, behind a Content-Security-Policy that allows no script, object, frame,
160
- form or fetch (images are `data:` URIs and this server's own attachment URLs for inline `cid:` images; **remote images are never loaded**). `allow-same-origin`
161
- is what lets the app measure the frame and adapt its colours, and it is safe only because nothing can run there: any one of the four layers (server
162
- sanitizer, client sanitizer, CSP, sandbox) stops a script on its own - see `.claude/NOTES.md`, 2026-09-21, for the threat model and the hostile-mail corpus it
163
- was tried against. Every link opens in a new tab without an opener. A message over 1.5 MB of markup or 20,000 elements is offered on its own page instead.
164
-
165
- **Mail takes the theme.** A message follows the app's scheme wherever its author left the colours to the reader, and is shown as authored where the
166
- author chose them. Plain-text mail and HTML with no colours at all (a typed reply) are drawn in the theme's surface and text colours, links in its link
167
- colour. Where the HTML sets a colour: text on the theme's surface is kept if it reads (4.5:1) and adapted if it doesn't (black becomes white in a dark
168
- theme, white becomes dark in a light one, a low-contrast navy is only lightened as far as it takes); anything on an authored, opaque background - a white table,
169
- a banner, a gradient - is shown exactly as authored, except text nobody coloured, which gets black or white by contrast with that background, never the theme's
170
- colour. A message that declares its own dark styles (a `color-scheme` meta or property, or a `prefers-color-scheme: dark` block) gets them when the theme is
171
- dark. "View original" (the sun, in the card's header; the moon then goes back) shows just that message exactly as authored for the rest of the session; it is
172
- offered only where adapting changes something. Known limits: a dark logo or PNG with a transparent background on a dark theme, text baked into images,
173
- `mix-blend-mode` and SVG paint are left as they are.
174
-
175
- Printing puts a message back to the colours it was written with for the print and adapts it again afterwards; the actions and the footer are not printed.
176
-
177
- ## Notifications, sending and the Outbox
178
-
179
- **One pop-up system** (`shared/notifications/`). `AppChrome` mounts `NotificationCenter` once, right under the header row (a zero-height `sticky`
180
- line that sticks just below the header - the title bar's or a branding header's height, published as `--rr-header-h` - so the stack starts below the
181
- account menu and never covers it, the header's buttons or a compose window's title bar and Send button; its height is capped by `--rr-compose-top`), and
182
- everything the app has to say goes through the framework-free store:
183
-
184
- ```ts
185
- import { notify, update, dismiss } from "@rapidmx/web-client/shared/notifications/store.js";
186
- import { notifyApiError } from "@rapidmx/web-client/shared/notifications/apiErrors.js";
187
-
188
- const id = notify({ kind: "error", title: "Couldn't archive the message", message: "The server said no.",
189
- details: ["Status: 502 (api-1)"], actions: [{ label: "Retry", onClick: retry }], dedupeKey: "archive" });
190
- update(id, { kind: "success", title: "Archived", actions: [] }); // resolve it
191
- try { await archive(); } catch (err) { notifyApiError(err, "Couldn't archive the message"); }
192
- ```
193
-
194
- `kind` is `mail`, `info`, `success`, `warning` or `error`. At most three show at once (the rest queue); errors and anything with `actions` are sticky
195
- until dismissed or resolved by `update()`/`dismiss()`, the others go after 5 to 10 seconds - clocks pause on hover, focus and a hidden tab; a repeated
196
- `dedupeKey` is one pop-up with a count; errors are announced assertively (`role="alert"` inside an `aria-live="assertive"` region), the rest politely;
197
- `details` is an expandable, monospace, copyable block. The last 30 (not new-mail ones) are kept in memory and `sessionStorage` and listed by "Recent
198
- notifications" in the account menu. `notifyApiError()` turns an `ApiRequestError` into an error pop-up with the server's message (a `401` becomes
199
- "Your session expired" with a Sign in button, anywhere in the app: the frame registers `setApiUnauthorizedObserver()`). The frame also catches unhandled
200
- errors and rejections as one "Something went wrong", and says so, subtly, when the live connection has been down for more than ten seconds.
201
-
202
- **Sending does not wait** (`shared/mail/outbox/`). Send validates what it can from what the window knows (a recipient, keys that must be unlocked, an
203
- attachment that can't be signed - shown inline, the window stays open; an attachment still uploading is waited for), then closes the window at once and
204
- hands a snapshot to `startSend()`: save, sign or encrypt in the browser if that applies, then `POST /mail/messages/:id/send` with `{ "background": true }`,
205
- which answers `202 { status: "queued", message }` and relays in the background. Failures at any stage are a sticky "This message wasn't sent" with the
206
- reason, technical details, **Retry** and **Open draft**; the server's later outcome arrives as `send-succeeded` / `send-retrying` / `send-failed` push
207
- events. The Outbox row in the folder list is a pill with the count (optimistic at the click, then the server's), animated while anything is on its way and
208
- red while one failed; rows in the Outbox list say what each message is doing.
209
-
210
- **Encryption fails open.** A message is treated as unencrypted - its draft saves, Close and Send work, nothing about encryption is shown - unless you turned
211
- encryption on (or replied to an encrypted message) or the encryption policy *was loaded* and encrypts for every recipient; a request that failed, is slow or
212
- is pending never blocks anything (`compose/encryptionRequirement.ts`).
213
-
214
- ## The header, the footer and their variables
215
-
216
- The admin console's Branding page sets a **header** and a **footer** as HTML. The server sanitizes them when they are saved and this client
217
- sanitizes them again when it shows them: no scripts, styles, forms, inputs or SVG, no `on*` handlers, only `http`, `https` and `mailto` links.
218
-
219
- **A custom header is the top of the app.** When one is set it replaces the app's own title bar (the app's name and the account menu) and the icon
220
- at the top of the icon rail: the rail then starts with the app icons, and the header stays at the top of the window while a page scrolls. The
221
- account menu moves into it. Two variables can be written in the *text* of the header or the footer:
222
-
223
- | Variable | Replaced with |
224
- | --- | --- |
225
- | `{USER_MENU}` | The account menu: the avatar button and its drop-down (Account, Settings, notifications, keyboard shortcuts, Admin Console, Sign Out). |
226
- | `{APP_TITLE}` | The name of the app on screen ("Mail", "Calendar", "Contacts", "Tasks", "Settings"), kept current as you move between apps without a page load. |
227
-
228
- ```html
229
- <div style="display:flex; align-items:center; justify-content:space-between; padding:0.5rem 1rem">
230
- <span>{APP_TITLE}</span>
231
- <span>{USER_MENU}</span>
232
- </div>
233
- ```
234
-
235
- - A variable is matched exactly - upper case, in braces, no spaces - and only in text. One in an attribute (`title="{USER_MENU}"`), a comment,
236
- a `<script>` or `<style>` is left as written or gone with them, and the author can't write the placeholders React fills in (`data-rr-slot` is
237
- stripped).
238
- - `{USER_MENU}` is replaced **once**, where it first appears in the header; if the header has none, at its first place in the footer (the menu then
239
- opens upward); if neither has one, the menu sits in a small cell at the right end of the header, so the account menu, Sign Out and the shortcuts
240
- help are never lost. A second or third copy is removed (two menus would mean duplicate ids and focus targets), and so is one inside a link or in
241
- text that can't hold an element (`<title>`, `<textarea>`).
242
- - `{APP_TITLE}` is replaced **everywhere** it appears, as text (never markup). It changes on client-side navigation, and works in the footer too.
243
- - The menu is drawn into `<body>` with `position: fixed`, so a header with `overflow: hidden`, its own `z-index` or a stacking context can neither
244
- clip it nor hide it behind the page. It follows its button when the window resizes or scrolls, and fits a 390 px window.
245
- - Put `{USER_MENU}` where your CSS doesn't hide it: a header whose stylesheet hides its `<nav>` on phones should keep the variable outside the nav.
246
- - Focus order is document order: the menu button is reached after the text before it and before what follows it.
247
- - Pages that don't use a shell (the booking pages) render `BrandingHeader` and `BrandingFooter`, which have no menu: `{USER_MENU}` renders nothing
248
- there and `{APP_TITLE}` is the `appTitle` prop, if any. The variable names are `USER_MENU_VARIABLE` and `APP_TITLE_VARIABLE` in
249
- `shared/components/layout/BrandingChrome.js`.
250
-
251
- The frame draws the header from the `branding` prop the server rendered the page with (`useBranding()`'s own fetch takes over when it answers), so
252
- the frame is the right shape - no title bar, no rail icon - from the first paint, and the icon of a frame without a custom header sits flush with the
253
- top of the window (`RailIcon`: it is measured on a canvas and raised by whatever empty margin the image file has above its artwork).
254
-
255
- ## Appearance
256
-
257
- **Settings > Appearance** (`/settings/appearance`) lets each user choose the colour scheme (System, Light or Dark), four theme colours (primary,
258
- accent, surface and text - each a colour picker with a hex field and its own "Reset"), and a background (none, a colour, or an uploaded picture with
259
- dim 0-80%, blur 0-20 px and fit cover, contain or tile). Every change is applied to the real app in the same frame - the page you are on is the preview -
260
- and saved in the background (coalesced, only what changed); a save that fails puts the change back and says so. A picture is checked before it is
261
- uploaded (PNG, JPEG, WebP or AVIF, up to 8 MB). Text on the surface, and text on an accent button, under 4.5:1 contrast gets a warning, never a block.
262
- "Reset all" returns everything to the defaults and removes the picture.
263
-
264
- **How it reaches the app.** `AppearanceProvider` (mounted by `AppChrome`, `AdminShell` and `EscrowShell`) owns the preferences and writes one
265
- `<style id="rr-appearance">` into `<head>` (`appearance/theme.js`), sets `<html data-theme="light|dark">` (for System, the operating system's scheme,
266
- live) and remembers the result in `localStorage` (`rapidmx-appearance`). The server's `_layout.tsx` renders the same stylesheet from the `appearance`
267
- page prop and a tiny inline script (`APPEARANCE_BOOT_SCRIPT`) applies the browser's copy before the first paint, so there is no flash on a load and none
268
- on a client-side navigation. The server caches a rendered page for up to a minute, so whichever copy - the page's or the browser's - was changed last wins,
269
- and the server is asked once the page is up.
270
-
271
- - **Precedence:** the app's defaults < the deployment's branding stylesheet < the user's choices. A user's colours are `--rr-color-*` custom properties
272
- declared `!important` on `<html>`, so no branding rule can beat them; the branding stylesheet's own rules (say a header's background) still decide their
273
- own elements, exactly as before. Only the colours a user chose are set: primary and accent get their full scales (darker and lighter steps mixed from the
274
- one colour) and readable text colours, surface and text get their alt, border and muted derivatives.
275
- - **The background** is two fixed pseudo-elements of `<html>` (`::before` the picture or colour, `::after` the dim, the scheme's surface colour at the
276
- chosen opacity), behind everything, with no markup, no layout shift and nothing to repaint while a list scrolls. With a background set the icon rail, the
277
- header and the content region become translucent panels (`--color-surface` and `--color-surface-alt` are the surface colour at an alpha, chosen so text
278
- and muted text stay at least 4.5:1 against the worst place the picture can be - the picture's lightest and darkest twentieth, measured on a canvas -
279
- more see-through for a plain photo or a high dim, more opaque for a busy one); menus, dialogs, drawers and pop-ups take the opaque colours back. Without a
280
- background nothing changes.
281
- - **Hooks:** `useResolvedTheme()` (`appearance/resolvedTheme.js`) is `"light"` or `"dark"`, live, needing no provider; `useAppearance()` gives
282
- `{ prefs, resolved, setPrefs(patch), uploadBackground(file), removeBackground(), reset(), saving, error, ... }`.
283
- - **API** (`@rapidmx/react-shared/appearance/preferencesApi.js`): `GET/PUT /mail/preferences/appearance` (a merge), `POST/DELETE
284
- /mail/preferences/appearance/background`, `GET .../background/:version`; a change is also pushed as `AppearancePreferences...` `update` events on
285
- the user's own uid channel.
286
-
287
- ## Toolbars, folders and the signing certificate
288
-
289
- **Toolbars that fit (`components/layout/ResponsiveToolbar.tsx`).** A bar measures its own width (`ResizeObserver`) and lays itself out from a table of actions (`ToolbarAction`: label,
290
- icon, group, `rank` - the lowest goes into "More" first - and `essential`): captions under the icons while everything fits; else icons only (each keeps a tooltip and, for New contact, its
291
- shortcut's `title` and `aria-keyshortcuts`); else as many icons as fit and the rest in a portalled `role="menu"` behind a **More** button. `layoutToolbar(actions, width)` is the pure
292
- function behind it (`width === undefined` - server render, no ResizeObserver - shows everything), and the bar is `overflow-x-clip` so it can never paint over the pane beside it. Contacts
293
- uses it; the panes around it follow one rule: a list column of `clamp(16rem, 32vw, 26rem)`, a detail pane that keeps the rest, each column scrolling by itself (`md:h-[calc(100dvh - var(--rr-header-h))]` on the
294
- page root - the frame's own height is open-ended), the side menu a drawer below `lg`, and no empty shell column for a single mailbox.
295
-
296
- **The folder tree (`mail/folderTree.ts`).** `FOLDER_ORDER` (Inbox, Drafts, Outbox, Sent Items, Deleted Items, Junk, Archive, then custom), `upsertFolder()`, `removeFolder()` and
297
- `reconcileFolders()` return the same array when nothing changed (everything that reads the tree starts over when it is a new one), never touch the counts (the overlay in `folderCounts.ts`
298
- owns them) and never remove on a listing (deletions arrive as events). `useMailConnection()` files what `Folder` create / update / delete events say and what every counts read-back lists
299
- (`useFolderCounts(mailboxes, folders, onFoldersListed)`), so a folder is found by an event, a message in it, a send outcome, the poll, a focus or a reconnect, whichever comes first. `folderRows()`
300
- is the one place a folder that has not arrived is drawn - a muted Outbox / Sent Items placeholder while a message is on its way - so it can never be drawn twice. Push channels are ordered inboxes,
301
- mailbox uids, then the other folders in sidebar order (`pushChannelsFor()`).
302
-
303
- **The signing certificate (`signing/`).** `enrollmentTracker.ts` follows one enrollment per mailbox for everybody who wants to show or react to it: a first read, then 15 s, 30 s and every 60 s
304
- while pending - only while the page is visible (a hidden tab asks nothing; coming back or a focus asks at once and restarts the backoff), stopping when it ends; `checkEnrollmentNow()` is the
305
- "Check status" (a 10 s cooldown, the server's 429 honoured, a plain read on a server without the check endpoint); a pending enrollment stays followed after the page that started it is left.
306
- `useSigningEnrollmentWatcher()` (in `AppChrome`) seeds it for the mailboxes you own and raises the "issued" / "failed" pop-up. `enrollmentView.ts` holds the wording, the steps, the percentage
307
- and the expiry maths; `SigningCertificateCard` draws them. The account menu's **Theme** row is `ThemeSwitch`, over `useAppearance().setPrefs({ mode })`.
308
-
309
- ## Plugin UI surface
310
-
311
- Server plugins can ship their own pages (see the plugin manifest's `ui` field in `@rapidmx/restapi`). The server builds
312
- them together with this package, so they share one React, one `@rapidmx/react-shared` state and one stylesheet. The
313
- modules below are the **supported surface for plugin pages**. Anything else under `apps/` is internal and may change in
314
- any release.
315
-
316
- ### Shells
317
-
318
- | Import | Use |
319
- | --- | --- |
320
- | `shared/components/layout/AppShell.js` | Chrome for webmail apps: app rail, header, user menu, impersonation banner, compose and unlock providers. `active` is a core app or the plugin's `appRail` item id. The user menu shows an "Admin Console" item to an administrator even when their session isn't elevated (it asks auth-server for the user's own roles, using the `trustedRoles` page prop) and, in Mail, the new-mail pop-up switch. |
321
- | `shared/components/settings/layout/SettingsShell.js` | Settings chrome with the section list and mailbox switcher. `active` is the plugin's `settingsSections` item id. `useSettingsShell()` gives the selected `mailboxUid` and the accessible `mailboxes`. |
322
- | `shared/components/admin/layout/AdminShell.js` | Admin console chrome, gated on administrator access - an administrator whose session isn't elevated is sent to auth-server's `/auth/elevate` page and returned. `active` is the plugin's `adminNav` item id. |
323
- | `shared/components/layout/BrandingChrome.js` | `BrandingHeader` and `BrandingFooter`, for pages that don't use a shell, such as public pages. |
324
- | `shared/plugins/pluginNav.js` | The `PluginNav`, `PluginUiNavItem` and `PluginNavProps` types. |
325
-
326
- Every www and admin page receives a `pluginNav` prop from the server. It lists the settings sections, admin sections and
327
- app rail entries of every enabled plugin whose UI built. The shells append those entries after their own, with a generic
328
- icon. An entry whose id matches a core entry is skipped, and so is one whose `href` isn't a same-origin path. Pass the
329
- page props straight to the shell so the navigation shows:
330
-
331
- ```tsx
332
- import React from "react";
333
- import SettingsShell, {
334
- SettingsShellProps,
335
- useSettingsShell,
336
- } from "@rapidmx/web-client/shared/components/settings/layout/SettingsShell.js";
337
-
338
- export default function RemindersSettingsPage(props: Omit<SettingsShellProps, "active">) {
339
- return (
340
- <SettingsShell {...props} active="reminders">
341
- <RemindersSettings />
342
- </SettingsShell>
343
- );
344
- }
345
-
346
- function RemindersSettings() {
347
- const { mailboxUid } = useSettingsShell();
348
- return <p>Settings for {mailboxUid}</p>;
349
- }
350
- ```
351
-
352
- Public and escrow pages get no `pluginNav`.
353
-
354
- ### From `@rapidmx/react-shared`
355
-
356
- Plugin pages import these directly from `@rapidmx/react-shared`, which the server resolves to the same copy the shells
357
- use:
358
-
359
- - `branding/useBranding.js`: `useBranding()`, for the branding and icon of pages outside a shell;
360
- - `auth/session.js`: `useRedirectIfUnauthenticated()`, already called by every shell;
361
- - `util/api.js`: `apiFetch()` and `ApiRequestError`, for calling the plugin's own API routes;
362
- - `mail/mailApi.js`: mailboxes and folders, such as `listMailboxes()` and `listFolders()`;
363
- - `components/buttons/Button.js`, `components/feedback/Alert.js` and `components/feedback/Skeleton.js`;
364
- - `components/forms/FormField.js`;
365
- - `components/overlays/Modal.js` and `components/overlays/Drawer.js`;
366
- - `components/pickers/MiniDatePicker.js`.
367
-
368
- ## Uninstalling a plugin with its data
369
-
370
- The admin console's Plugins page (`PluginsManager`, also embedded in the setup wizard) uninstalls a plugin through a dialog with an unchecked **Also delete all data this plugin stored** box. Ticking it lists what will be deleted (collections and tables, saved settings, cached package and pages, whatever the plugin cleans up itself), says **This can't be undone**, turns the confirm button into the red **Uninstall and delete data** and keeps it disabled until the plugin's display name is typed (case and surrounding spaces ignored). The dialog is a `Modal` (focus moves in and stays in, Escape closes, the checkbox is described by the list) and a `<form>`, so Enter in the name field confirms once it matches; at 390 px it keeps the modal's 20 px gutters and the list wraps.
371
-
372
- The request is `removePlugin(uid, { purgeData: true })` (`@rapidmx/react-shared/admin/pluginsApi.js`); the server accepts it only from an elevated administrator, and an `api-104` answer is shown as a request to reload or sign in again. Deletion happens on the servers after the last copy stops running the plugin, so `GET /api/system/plugins/status` carries each deletion as `purges` and the page lists the uninstalled plugin with its state - *Uninstalled - data will be deleted after servers restart* (and how many servers still run it), *Data deleted <date>*, or *Data deletion failed: <reason>* with the failed steps and a Retry button. It reads the status every 5 seconds while a deletion is waiting or running, and raises an `apps/shared/notifications` pop-up when one that was under way is deleted or fails, and a warning when adding a plugin cancels one. A plugin that is installed is never shown as uninstalled, whatever the server still lists about an earlier deletion.
373
-
374
- ## Development
375
-
376
- ```sh
377
- yarn install
378
- yarn test # vitest with coverage gates
379
- yarn lint
380
- yarn build # tsc into dist/apps
381
- ```
1
+ # RapidMX: Web Client
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@rapidmx/web-client)](https://www.npmjs.com/package/@rapidmx/web-client)
4
+
5
+ RapidMX's webmail (`apps/www`), admin console (`apps/admin`) and escrow console (`apps/escrow`) React UI, plus the
6
+ platform-agnostic API clients, hooks and generic UI primitives (`lib/`) that back them. The pages are served and
7
+ hydrated by [`rapidmx/server`](https://github.com/RapidMX/server) through `@rapidrest/react`'s file-convention routes,
8
+ and `@rapidmx/electron-client` reuses the same components. `lib/` was `@rapidmx/react-shared`, a separate package,
9
+ until it was merged in here on 2026-09-27 - see `.claude/NOTES.md`'s entry of that date for why (every real consumer
10
+ of react-shared already depended on this package too) and `CHANGELOG.md` for the merge's own history.
11
+
12
+ ## Package layout
13
+
14
+ The package ships the TSX/TS sources (`apps/`, `lib/`) and a compiled mirror (`dist/apps/`, `dist/lib/`, JavaScript
15
+ plus `.d.ts` declarations). There is no root export. Every module is its own subpath, mapped by `package.json`'s
16
+ `exports` from `@rapidmx/web-client/<path>.js` to `dist/apps/<path>.js`, and from `@rapidmx/web-client/lib/<path>.js`
17
+ to `dist/lib/<path>.js`:
18
+
19
+ ```ts
20
+ import SettingsShell from "@rapidmx/web-client/shared/components/settings/layout/SettingsShell.js";
21
+ import { apiFetch, configureApiBaseUrl } from "@rapidmx/web-client/lib/util/api.js";
22
+ import { getMailboxes } from "@rapidmx/web-client/lib/mail/mailApi.js";
23
+ import { useSessionRefresh } from "@rapidmx/web-client/lib/auth/session.js";
24
+ import Button from "@rapidmx/web-client/lib/components/buttons/Button.js";
25
+ ```
26
+
27
+ `@rapidmx/web-client/shared/styles/app.css` is the Tailwind entry point and design tokens.
28
+
29
+ ### `lib/` - platform-agnostic API clients, hooks and generic UI primitives
30
+
31
+ `lib/` is deliberately framework-free beyond React itself - no router, no HTTP client, no state management library -
32
+ and is organized by feature/system, mirroring `@rapidmx/restapi`'s own `src/<feature>/` convention: `mail/`,
33
+ `calendar/`, `contacts/`, `tasks/`, `admin/`, `branding/`, `search/`, `auth/`, `videoconf/`, `crypto/`, and `util/` for
34
+ the small set of things every feature depends on (`util/api.ts`'s `apiFetch()`, `util/apiQuery.ts`'s pagination
35
+ helper, `util/dateInput.ts`, `util/useIsMobile.ts`, `util/clipboard.ts`'s `copyTextToClipboard()` and its
36
+ `util/useCopyToClipboard.ts` hook). Each `*Api.ts` module is a thin `fetch` wrapper around one
37
+ [`@rapidmx/restapi`](https://github.com/RapidMX/restapi) resource, and a handful of `use*` hooks and pure utilities
38
+ (recurrence expansion, vCard/ICS helpers, emoji data, calendar color assignment, `mail/mailAddress.ts`'s
39
+ `Name <address>` formatting) back the UI components in `apps/`. `mail/pushClient.ts` is the one real-time connection a
40
+ tab keeps to the server's `/push` WebSocket (subscribe to folder/mailbox uids, reconnect with backoff, close on
41
+ sign-out) - a consumer must still poll, since events published while it was disconnected are never replayed.
42
+
43
+ `lib/components/` holds genuinely generic UI primitives - ones with no RapidMX/webmail-domain knowledge baked in -
44
+ usable by any consumer: `buttons/Button`+`CopyButton`, `feedback/Alert`+`Skeleton`, `forms/FormField`,
45
+ `overlays/Modal`+`Drawer`+`PopoverPortal`, `avatar/ContactAvatar`, `pickers/MiniDatePicker`, and
46
+ `navigation/BottomTabBar`. Domain-specific components (calendar views, mail compose, contact/task/admin UI, etc.) stay
47
+ in `apps/shared/components/`, which depends on `lib/` rather than the other way around.
48
+
49
+ `apiFetch()` targets a same-origin relative path (`/api/...`) by default, matching every consumer that's
50
+ server-rendered or otherwise served from the same origin as the API it calls. A consumer whose own origin genuinely
51
+ differs from the RapidMX server's - e.g. `@rapidmx/electron-client`'s renderer - calls `configureApiBaseUrl()` once at
52
+ startup to target an absolute origin instead; see `lib/util/api.ts` for the CORS/cookie configuration that requires
53
+ on the server side.
54
+
55
+ ## Navigation without page loads
56
+
57
+ All three apps use `@rapidrest/react`'s client-side router (`router = true` on the server's route, the client built with
58
+ `createViteConfig({ router })`; `@rapidrest/react` is a **peer dependency** here, `>=2.1.0 <3`, because the router's state lives
59
+ in a React context that this package must share with the server's one copy). The server still renders every URL, so first
60
+ loads, reloads and crawlers are unchanged; after the first load a click on an ordinary `<a href>` between two pages of an app
61
+ fetches the next page's props as JSON and swaps the page in. Back and forward work, the address bar always holds the real,
62
+ shareable URL, and anything the router cannot be sure of (a modified click, `target`, `download`, `data-router-ignore`, another
63
+ app's page - the consoles, plugin pages, other sites - or a failure) is left to the browser as a page load.
64
+
65
+ The admin and escrow pages each render their own shell, so the router just swaps the page. The webmail (`apps/www`) has an **app
66
+ shell**, `apps/www/_shell.tsx` (the router's persistent client layout), which the server renders around every page and the
67
+ browser hydrates with it as one root:
68
+
69
+ - the one `AppChrome` (the app rail, header, user menu, impersonation banner, compose windows in progress, the unlock prompt,
70
+ the idle-key timer, the mail connection and the notification pop-ups) stays mounted while only the page inside it is replaced.
71
+ A page's own shell (`MailShell`, `CalendarShell`, ...) still renders `AppShell`; inside the frame that is only its children;
72
+ - the shell gets the current page's props - `WwwRoute.fetchProps()`'s, the same for every page - so the rail's highlighted app
73
+ (from the route), the branding, the plugin navigation and the impersonation banner follow the server on every navigation. That is
74
+ one request to `fetchProps()` per page change (warm for most, since the router prefetches a plain link on hover and the four rail
75
+ pages' code when the browser is idle);
76
+ - **a folder is a shallow navigation.** `<a data-router-shallow>` (the Mail folder links), `navigate()` to the same path with another
77
+ query (`useNavigate()`, below) and `useSearchParams()`' setter keep the page instance and its state and only change the location;
78
+ - after a page change the router moves focus to the content region (`#app-content`), scrolls to the top and announces the page's
79
+ title in a polite live region (all set by the shell with `useNavigationEffects()`); the tab's title is each page's `title`
80
+ export (`pageTitle("Calendar")`: `Acme Mail: Calendar`), rendered by the server and sent with every navigation, with the unread
81
+ count kept in front of it; the content region is `aria-busy` while the next page loads.
82
+
83
+ Code that decides where to go uses `useNavigate()` from `shared/navigation/index.js` and reads the location with the library's
84
+ hooks:
85
+
86
+ ```tsx
87
+ import { useLocation, useSearchParams } from "@rapidrest/react/client";
88
+ import { useNavigate } from "@rapidmx/web-client/shared/navigation/index.js";
89
+
90
+ const navigate = useNavigate(); // navigate("/contacts"); navigate("/?mailboxUid=a&folderUid=b") keeps the page (same path)
91
+ const { pathname, search, hash } = useLocation(); // the request's on the server and while hydrating; `hash` is empty there
92
+ const [params, setParams] = useSearchParams(); // setParams({ folderUid: "x" }) is shallow by default
93
+ ```
94
+
95
+ `navigate()` changes the page without a load when the URL is a page of the app, and is an ordinary navigation otherwise (outside a
96
+ router too). An admin or escrow page that leads to another page of its console after a save goes there with `useRouter().navigate`.
97
+ Plugin pages are not part of the router (they render their own `AppShell` chrome) and are opened with a page load.
98
+
99
+ The compose window, the reading pane, S/MIME and the emoji list are also chunks of their own, loaded on demand or fetched
100
+ when the browser is idle, so a page's first JavaScript is React and what the first screen draws.
101
+
102
+ ## Keyboard shortcuts
103
+
104
+ One keyboard layer, `shared/keyboard/`, lives in the persistent app frame (`AppChrome`), so the shortcuts work in every view -
105
+ Mail, Calendar, Contacts, Tasks and Settings - and a page change only changes which of them are registered. `?` (or `Ctrl+/`)
106
+ opens a "Keyboard shortcuts" dialog, also reached from the account menu, that lists what is available *here* - the global
107
+ shortcuts plus those of the view on screen (and of an open compose window) - with the platform's own key names. `mod` below is
108
+ **Ctrl** on Windows and Linux and **Cmd** on a Mac; the navigation set is Ctrl+Shift on every platform (Cmd+Shift collides with the
109
+ browsers' own, so it is not offered).
110
+
111
+ | Where | Key | Does |
112
+ | --- | --- | --- |
113
+ | Everywhere | `Ctrl+Shift+A` | Account (auth-server's account page; only offered when one is configured) |
114
+ | | `Ctrl+Shift+S` / `B` / `M` / `C` / `L` | Settings / Contacts / Mail / Calendar / To-Do |
115
+ | | `?` or `Ctrl+/` | Keyboard shortcuts |
116
+ | Mail | `Alt+N` | New message |
117
+ | | `mod+R` / `mod+Shift+R` / `mod+Shift+F` | Reply / Reply all / Forward the selected message |
118
+ | | `mod+D` or `Delete` | Delete the selected message or conversation (moves it to Deleted Items) |
119
+ | | `E` or `Backspace` | Archive |
120
+ | | `mod+Shift+V` | Move to folder |
121
+ | | `Ctrl+Q` / `mod+U` | Mark as read / unread (`Ctrl+Q` on a Mac too: `Cmd+Q` quits) |
122
+ | | `Insert` | Flag or unflag |
123
+ | | `Down` or `J` / `Up` or `K` | Next / previous message (conversation) |
124
+ | | `Ctrl+.` / `Ctrl+,` | Next / previous unread |
125
+ | | `Enter` / `Escape` | Open the selected message on its own page / clear the selection (leave select mode, clear the search) |
126
+ | | `/` or `mod+E` | Search |
127
+ | Compose window | `mod+Enter` | Send |
128
+ | | `mod+S` | Save draft |
129
+ | | `Escape` | Close, keeping the draft (the existing "keep draft / discard" question still applies) |
130
+ | | `Alt+N` | Another new message |
131
+ | Calendar | `Alt+N` | New event |
132
+ | | `T` | Today |
133
+ | | `Left` / `Right` (or `mod+Left` / `mod+Right`) | Previous / next period |
134
+ | | `Ctrl+Alt+1` / `2` / `3` / `4` | Day / work week / week / month |
135
+ | Contacts | `Alt+N` | New contact |
136
+ | | `/` or `mod+E` | Search |
137
+ | Tasks | `Alt+N` | New task (moves to the "Add a task" field) |
138
+
139
+ In the desktop client (`@rapidmx/electron-client`, which exposes `window.rapidmx`) `Ctrl+N` (`Cmd+N` on a Mac) also creates - a new
140
+ message, event, contact or task in the current view - and `Ctrl+Shift+T` also goes to Tasks; browsers keep both for themselves, which is
141
+ why the web client uses `Alt+N` and `Ctrl+Shift+L`.
142
+
143
+ How it behaves:
144
+
145
+ - **Views register only what they can do.** A view claims a shortcut with `useShortcut(SHORTCUTS.mail.reply, handler, { enabled })`
146
+ for as long as it is mounted and able to do it (Reply exists only while a message is selected, Archive not for Drafts or Outbox),
147
+ so there are no dead keys and the help dialog is always accurate. `SHORTCUTS` in `shared/keyboard/keymap.js` is the one key map;
148
+ handlers are looked up when the key is pressed. A handler that returns `false` declines the key.
149
+ - **Scopes.** `global`, the view's own (`mail`, `calendar`, `contacts`, `tasks`), `compose` (while focus is inside a compose window; it
150
+ beats the view behind it) and `dialog`: while a modal dialog (`aria-modal`) is open only its own shortcuts and its own Escape work.
151
+ - **Typing is never taken.** Bare keys (`J`, `E`, `?`, `Delete`) do nothing while focus is in a text field, select or the rich-text
152
+ editor, and caret keys with a modifier (word jumps) stay the field's. Chords with Ctrl/Alt/Cmd work from a field, except Option on
153
+ a Mac and Ctrl+Alt (AltGr) elsewhere, which type characters. Copy, Cut, Paste, Select all, Undo, Redo and Find are never
154
+ bound. Enter and Space are left to a focused button or link, and an open menu keeps its own keys. Events already
155
+ `defaultPrevented` (the editor's own bindings, a menu), IME composition and held-key repeats of one-shot actions are ignored, and
156
+ the browser's default is prevented only when a handler actually ran.
157
+ - **Layouts.** A key is matched on `event.key` (so it follows AZERTY or Dvorak) with `event.code` as the fallback when `event.key` is
158
+ not a Latin character - macOS Option+N, or a Cyrillic layout.
159
+ - **Hints.** Buttons that a shortcut also does carry `aria-keyshortcuts` and a tooltip such as "Reply (Ctrl+R)"; their accessible
160
+ names are unchanged (`useShortcutProps()` gives a control both).
161
+
162
+ Known limits: a browser keeps a few keys for itself (`Ctrl+N`, `Ctrl+T`, `Ctrl+W` are never delivered to a page, and some browsers claim
163
+ `Ctrl+Shift+A/B/C/M/S`); a message body is shown in a frame of its own, which hands the keys pressed in it on to the layer, so they work there too. In the compose body the editor's own bindings win over `Ctrl+Shift+S`, `B`
164
+ and `L` (strike-through, quote, align left).
165
+
166
+ ## The reading pane
167
+
168
+ `shared/components/mail/` draws a message as a **card** and a conversation as a stack of them, under a **subject card** that stays pinned at the top
169
+ while the cards scroll (`MessageDetailPane`, `ConversationThreadPane`, and `reading/` for the pieces):
170
+
171
+ - **Subject card** - the conversation's (or the message's) subject as the page's `h1`, "N messages", a note when a very long thread was cut. In a
172
+ thread it is on screen at once, from what the list row already knows, with a skeleton card per message (up to three) until the messages arrive.
173
+ - **Message card** - rounded, bordered and lifted, in the app's own tokens (`bg-surface`, `text-text`, `border-border`), so it follows the theme, a
174
+ branding palette and the Appearance colours. A header row with the sender's initials, `Name <address>`, To/Cc/Bcc, the time and the actions (Reply,
175
+ Reply All, Forward, Archive, Move to, Labels, and "View original" when it applies); the security badge and every notice (signature, key change,
176
+ receipts, Outbox state); attachments; the body; a slim "You replied to this message." bar; and, on a single message and on the newest open one in a
177
+ thread, Reply and Forward at its foot. An older message of a thread is a collapsed card (sender, date, first line) that expands on click or Enter
178
+ (`aria-expanded`, `aria-controls`; the focus stays on the message's header button as it swaps between the two states).
179
+ - **Each card is exactly as tall as its message.** The body is shown at its natural height - no inner scrollbar, no dead space, no fixed height - and
180
+ follows it as images load and the width changes. A table or image wider than the card scrolls **inside** the card and never widens the pane. The
181
+ pane scrolls as a whole. Everything is on screen before the body: the header card, the message card's header and a skeleton where the body will be.
182
+
183
+ - **Encrypted messages** are themed card content in the same place as the body, never a frame: while the security state is being worked out, a skeleton;
184
+ locked (no unlocked keys on this device), a lock, "This message is encrypted", "Unlock your keys to read it" and an **Unlock** button that asks for the unlock
185
+ prompt and, once unlocked - by it or from anywhere - decrypts the message in place and moves the focus to it; unreadable with the keys in hand (not a recipient,
186
+ damaged), "This message can’t be decrypted" with the reason and no button; decrypted, the message like any other. The placeholder subject `[...]` reads
187
+ "Encrypted message", and list rows and collapsed cards with no preview say "Encrypted message" with a small lock.
188
+
189
+ **How a body is shown.** The server sanitizes a message's HTML when it ingests it (`GET /mail/messages/:id/content`); the client then treats what it
190
+ receives - and everything it recovers by decrypting or verifying, which the server never saw - as hostile. It is sanitized again with DOMPurify (scripts,
191
+ event handlers, `javascript:` URLs, every remote resource), stripped of forms, frames, media, plug-ins, SVG's active elements and every link that is not
192
+ `http(s)`/`mailto`/`tel`, and shown in a **sandboxed iframe without `allow-scripts`**, behind a Content-Security-Policy that allows no script, object, frame,
193
+ form or fetch (images are `data:` URIs and this server's own attachment URLs for inline `cid:` images; **remote images are never loaded**). `allow-same-origin`
194
+ is what lets the app measure the frame and adapt its colours, and it is safe only because nothing can run there: any one of the four layers (server
195
+ sanitizer, client sanitizer, CSP, sandbox) stops a script on its own - see `.claude/NOTES.md`, 2026-09-21, for the threat model and the hostile-mail corpus it
196
+ was tried against. Every link opens in a new tab without an opener. A message over 1.5 MB of markup or 20,000 elements is offered on its own page instead.
197
+
198
+ **Mail takes the theme.** A message follows the app's scheme wherever its author left the colours to the reader, and is shown as authored where the
199
+ author chose them. Plain-text mail and HTML with no colours at all (a typed reply) are drawn in the theme's surface and text colours, links in its link
200
+ colour. Where the HTML sets a colour: text on the theme's surface is kept if it reads (4.5:1) and adapted if it doesn't (black becomes white in a dark
201
+ theme, white becomes dark in a light one, a low-contrast navy is only lightened as far as it takes); anything on an authored, opaque background - a white table,
202
+ a banner, a gradient - is shown exactly as authored, except text nobody coloured, which gets black or white by contrast with that background, never the theme's
203
+ colour. A message that declares its own dark styles (a `color-scheme` meta or property, or a `prefers-color-scheme: dark` block) gets them when the theme is
204
+ dark. "View original" (the sun, in the card's header; the moon then goes back) shows just that message exactly as authored for the rest of the session; it is
205
+ offered only where adapting changes something. Known limits: a dark logo or PNG with a transparent background on a dark theme, text baked into images,
206
+ `mix-blend-mode` and SVG paint are left as they are.
207
+
208
+ Printing puts a message back to the colours it was written with for the print and adapts it again afterwards; the actions and the footer are not printed.
209
+
210
+ ## Notifications, sending and the Outbox
211
+
212
+ **One pop-up system** (`shared/notifications/`). `AppChrome` mounts `NotificationCenter` once, right under the header row (a zero-height `sticky`
213
+ line that sticks just below the header - the title bar's or a branding header's height, published as `--rr-header-h` - so the stack starts below the
214
+ account menu and never covers it, the header's buttons or a compose window's title bar and Send button; its height is capped by `--rr-compose-top`), and
215
+ everything the app has to say goes through the framework-free store:
216
+
217
+ ```ts
218
+ import { notify, update, dismiss } from "@rapidmx/web-client/shared/notifications/store.js";
219
+ import { notifyApiError } from "@rapidmx/web-client/shared/notifications/apiErrors.js";
220
+
221
+ const id = notify({ kind: "error", title: "Couldn't archive the message", message: "The server said no.",
222
+ details: ["Status: 502 (api-1)"], actions: [{ label: "Retry", onClick: retry }], dedupeKey: "archive" });
223
+ update(id, { kind: "success", title: "Archived", actions: [] }); // resolve it
224
+ try { await archive(); } catch (err) { notifyApiError(err, "Couldn't archive the message"); }
225
+ ```
226
+
227
+ `kind` is `mail`, `info`, `success`, `warning` or `error`. At most three show at once (the rest queue); errors and anything with `actions` are sticky
228
+ until dismissed or resolved by `update()`/`dismiss()`, the others go after 5 to 10 seconds - clocks pause on hover, focus and a hidden tab; a repeated
229
+ `dedupeKey` is one pop-up with a count; errors are announced assertively (`role="alert"` inside an `aria-live="assertive"` region), the rest politely;
230
+ `details` is an expandable, monospace, copyable block. The last 30 (not new-mail ones) are kept in memory and `sessionStorage` and listed by "Recent
231
+ notifications" in the account menu. `notifyApiError()` turns an `ApiRequestError` into an error pop-up with the server's message (a `401` becomes
232
+ "Your session expired" with a Sign in button, anywhere in the app: the frame registers `setApiUnauthorizedObserver()`). The frame also catches unhandled
233
+ errors and rejections as one "Something went wrong", and says so, subtly, when the live connection has been down for more than ten seconds.
234
+
235
+ **Sending does not wait** (`shared/mail/outbox/`). Send validates what it can from what the window knows (a recipient, keys that must be unlocked, an
236
+ attachment that can't be signed - shown inline, the window stays open; an attachment still uploading is waited for), then closes the window at once and
237
+ hands a snapshot to `startSend()`: save, sign or encrypt in the browser if that applies, then `POST /mail/messages/:id/send` with `{ "background": true }`,
238
+ which answers `202 { status: "queued", message }` and relays in the background. Failures at any stage are a sticky "This message wasn't sent" with the
239
+ reason, technical details, **Retry** and **Open draft**; the server's later outcome arrives as `send-succeeded` / `send-retrying` / `send-failed` push
240
+ events. The Outbox row in the folder list is a pill with the count (optimistic at the click, then the server's), animated while anything is on its way and
241
+ red while one failed; rows in the Outbox list say what each message is doing.
242
+
243
+ **Encryption fails open.** A message is treated as unencrypted - its draft saves, Close and Send work, nothing about encryption is shown - unless you turned
244
+ encryption on (or replied to an encrypted message) or the encryption policy *was loaded* and encrypts for every recipient; a request that failed, is slow or
245
+ is pending never blocks anything (`compose/encryptionRequirement.ts`).
246
+
247
+ ## The header, the footer and their variables
248
+
249
+ The admin console's Branding page sets a **header** and a **footer** as HTML. The server sanitizes them when they are saved and this client
250
+ sanitizes them again when it shows them: no scripts, styles, forms, inputs or SVG, no `on*` handlers, only `http`, `https` and `mailto` links.
251
+
252
+ **A custom header is the top of the app.** When one is set it replaces the app's own title bar (the app's name and the account menu) and the icon
253
+ at the top of the icon rail: the rail then starts with the app icons, and the header stays at the top of the window while a page scrolls. The
254
+ account menu moves into it. Two variables can be written in the *text* of the header or the footer:
255
+
256
+ | Variable | Replaced with |
257
+ | --- | --- |
258
+ | `{USER_MENU}` | The account menu: the avatar button and its drop-down (Account, Settings, notifications, keyboard shortcuts, Admin Console, Sign Out). |
259
+ | `{APP_TITLE}` | The name of the app on screen ("Mail", "Calendar", "Contacts", "Tasks", "Settings"), kept current as you move between apps without a page load. |
260
+
261
+ ```html
262
+ <div style="display:flex; align-items:center; justify-content:space-between; padding:0.5rem 1rem">
263
+ <span>{APP_TITLE}</span>
264
+ <span>{USER_MENU}</span>
265
+ </div>
266
+ ```
267
+
268
+ - A variable is matched exactly - upper case, in braces, no spaces - and only in text. One in an attribute (`title="{USER_MENU}"`), a comment,
269
+ a `<script>` or `<style>` is left as written or gone with them, and the author can't write the placeholders React fills in (`data-rr-slot` is
270
+ stripped).
271
+ - `{USER_MENU}` is replaced **once**, where it first appears in the header; if the header has none, at its first place in the footer (the menu then
272
+ opens upward); if neither has one, the menu sits in a small cell at the right end of the header, so the account menu, Sign Out and the shortcuts
273
+ help are never lost. A second or third copy is removed (two menus would mean duplicate ids and focus targets), and so is one inside a link or in
274
+ text that can't hold an element (`<title>`, `<textarea>`).
275
+ - `{APP_TITLE}` is replaced **everywhere** it appears, as text (never markup). It changes on client-side navigation, and works in the footer too.
276
+ - The menu is drawn into `<body>` with `position: fixed`, so a header with `overflow: hidden`, its own `z-index` or a stacking context can neither
277
+ clip it nor hide it behind the page. It follows its button when the window resizes or scrolls, and fits a 390 px window.
278
+ - Put `{USER_MENU}` where your CSS doesn't hide it: a header whose stylesheet hides its `<nav>` on phones should keep the variable outside the nav.
279
+ - Focus order is document order: the menu button is reached after the text before it and before what follows it.
280
+ - Pages that don't use a shell (the booking pages) render `BrandingHeader` and `BrandingFooter`, which have no menu: `{USER_MENU}` renders nothing
281
+ there and `{APP_TITLE}` is the `appTitle` prop, if any. The variable names are `USER_MENU_VARIABLE` and `APP_TITLE_VARIABLE` in
282
+ `shared/components/layout/BrandingChrome.js`.
283
+
284
+ The frame draws the header from the `branding` prop the server rendered the page with (`useBranding()`'s own fetch takes over when it answers), so
285
+ the frame is the right shape - no title bar, no rail icon - from the first paint, and the icon of a frame without a custom header sits flush with the
286
+ top of the window (`RailIcon`: it is measured on a canvas and raised by whatever empty margin the image file has above its artwork).
287
+
288
+ ## Appearance
289
+
290
+ **Settings > Appearance** (`/settings/appearance`) lets each user choose the colour scheme (System, Light or Dark), four theme colours (primary,
291
+ accent, surface and text - each a colour picker with a hex field and its own "Reset"), and a background (none, a colour, or an uploaded picture with
292
+ dim 0-80%, blur 0-20 px and fit cover, contain or tile). Every change is applied to the real app in the same frame - the page you are on is the preview -
293
+ and saved in the background (coalesced, only what changed); a save that fails puts the change back and says so. A picture is checked before it is
294
+ uploaded (PNG, JPEG, WebP or AVIF, up to 8 MB). Text on the surface, and text on an accent button, under 4.5:1 contrast gets a warning, never a block.
295
+ "Reset all" returns everything to the defaults and removes the picture.
296
+
297
+ **How it reaches the app.** `AppearanceProvider` (mounted by `AppChrome`, `AdminShell` and `EscrowShell`) owns the preferences and writes one
298
+ `<style id="rr-appearance">` into `<head>` (`appearance/theme.js`), sets `<html data-theme="light|dark">` (for System, the operating system's scheme,
299
+ live) and remembers the result in `localStorage` (`rapidmx-appearance`). The server's `_layout.tsx` renders the same stylesheet from the `appearance`
300
+ page prop and a tiny inline script (`APPEARANCE_BOOT_SCRIPT`) applies the browser's copy before the first paint, so there is no flash on a load and none
301
+ on a client-side navigation. The server caches a rendered page for up to a minute, so whichever copy - the page's or the browser's - was changed last wins,
302
+ and the server is asked once the page is up.
303
+
304
+ - **Precedence:** the app's defaults < the deployment's branding stylesheet < the user's choices. A user's colours are `--rr-color-*` custom properties
305
+ declared `!important` on `<html>`, so no branding rule can beat them; the branding stylesheet's own rules (say a header's background) still decide their
306
+ own elements, exactly as before. Only the colours a user chose are set: primary and accent get their full scales (darker and lighter steps mixed from the
307
+ one colour) and readable text colours, surface and text get their alt, border and muted derivatives.
308
+ - **The background** is two fixed pseudo-elements of `<html>` (`::before` the picture or colour, `::after` the dim, the scheme's surface colour at the
309
+ chosen opacity), behind everything, with no markup, no layout shift and nothing to repaint while a list scrolls. With a background set the icon rail, the
310
+ header and the content region become translucent panels (`--color-surface` and `--color-surface-alt` are the surface colour at an alpha, chosen so text
311
+ and muted text stay at least 4.5:1 against the worst place the picture can be - the picture's lightest and darkest twentieth, measured on a canvas -
312
+ more see-through for a plain photo or a high dim, more opaque for a busy one); menus, dialogs, drawers and pop-ups take the opaque colours back. Without a
313
+ background nothing changes.
314
+ - **Hooks:** `useResolvedTheme()` (`appearance/resolvedTheme.js`) is `"light"` or `"dark"`, live, needing no provider; `useAppearance()` gives
315
+ `{ prefs, resolved, setPrefs(patch), uploadBackground(file), removeBackground(), reset(), saving, error, ... }`.
316
+ - **API** (`@rapidmx/web-client/lib/appearance/preferencesApi.js`): `GET/PUT /mail/preferences/appearance` (a merge), `POST/DELETE
317
+ /mail/preferences/appearance/background`, `GET .../background/:version`; a change is also pushed as `AppearancePreferences...` `update` events on
318
+ the user's own uid channel.
319
+
320
+ ## Toolbars, folders and the signing certificate
321
+
322
+ **Toolbars that fit (`components/layout/ResponsiveToolbar.tsx`).** A bar measures its own width (`ResizeObserver`) and lays itself out from a table of actions (`ToolbarAction`: label,
323
+ icon, group, `rank` - the lowest goes into "More" first - and `essential`): captions under the icons while everything fits; else icons only (each keeps a tooltip and, for New contact, its
324
+ shortcut's `title` and `aria-keyshortcuts`); else as many icons as fit and the rest in a portalled `role="menu"` behind a **More** button. `layoutToolbar(actions, width)` is the pure
325
+ function behind it (`width === undefined` - server render, no ResizeObserver - shows everything), and the bar is `overflow-x-clip` so it can never paint over the pane beside it. Contacts
326
+ uses it; the panes around it follow one rule: a list column of `clamp(16rem, 32vw, 26rem)`, a detail pane that keeps the rest, each column scrolling by itself (`md:h-[calc(100dvh - var(--rr-header-h))]` on the
327
+ page root - the frame's own height is open-ended), the side menu a drawer below `lg`, and no empty shell column for a single mailbox.
328
+
329
+ **The folder tree (`mail/folderTree.ts`).** `FOLDER_ORDER` (Inbox, Drafts, Outbox, Sent Items, Deleted Items, Junk, Archive, then custom), `upsertFolder()`, `removeFolder()` and
330
+ `reconcileFolders()` return the same array when nothing changed (everything that reads the tree starts over when it is a new one), never touch the counts (the overlay in `folderCounts.ts`
331
+ owns them) and never remove on a listing (deletions arrive as events). `useMailConnection()` files what `Folder` create / update / delete events say and what every counts read-back lists
332
+ (`useFolderCounts(mailboxes, folders, onFoldersListed)`), so a folder is found by an event, a message in it, a send outcome, the poll, a focus or a reconnect, whichever comes first. `folderRows()`
333
+ is the one place a folder that has not arrived is drawn - a muted Outbox / Sent Items placeholder while a message is on its way - so it can never be drawn twice. Push channels are ordered inboxes,
334
+ mailbox uids, then the other folders in sidebar order (`pushChannelsFor()`).
335
+
336
+ **The signing certificate (`signing/`).** `enrollmentTracker.ts` follows one enrollment per mailbox for everybody who wants to show or react to it: a first read, then 15 s, 30 s and every 60 s
337
+ while pending - only while the page is visible (a hidden tab asks nothing; coming back or a focus asks at once and restarts the backoff), stopping when it ends; `checkEnrollmentNow()` is the
338
+ "Check status" (a 10 s cooldown, the server's 429 honoured, a plain read on a server without the check endpoint); a pending enrollment stays followed after the page that started it is left.
339
+ `useSigningEnrollmentWatcher()` (in `AppChrome`) seeds it for the mailboxes you own and raises the "issued" / "failed" pop-up. `enrollmentView.ts` holds the wording, the steps, the percentage
340
+ and the expiry maths; `SigningCertificateCard` draws them. The account menu's **Theme** row is `ThemeSwitch`, over `useAppearance().setPrefs({ mode })`.
341
+
342
+ ## Plugin UI surface
343
+
344
+ Server plugins can ship their own pages (see the plugin manifest's `ui` field in `@rapidmx/restapi`). The server builds
345
+ them together with this package, so they share one React, one `@rapidmx/web-client/lib` state and one stylesheet. The
346
+ modules below are the **supported surface for plugin pages**. Anything else under `apps/` is internal and may change in
347
+ any release.
348
+
349
+ ### Shells
350
+
351
+ | Import | Use |
352
+ | --- | --- |
353
+ | `shared/components/layout/AppShell.js` | Chrome for webmail apps: app rail, header, user menu, impersonation banner, compose and unlock providers. `active` is a core app or the plugin's `appRail` item id. The user menu shows an "Admin Console" item to an administrator even when their session isn't elevated (it asks auth-server for the user's own roles, using the `trustedRoles` page prop) and, in Mail, the new-mail pop-up switch. |
354
+ | `shared/components/settings/layout/SettingsShell.js` | Settings chrome with the section list and mailbox switcher. `active` is the plugin's `settingsSections` item id. `useSettingsShell()` gives the selected `mailboxUid` and the accessible `mailboxes`. |
355
+ | `shared/components/admin/layout/AdminShell.js` | Admin console chrome, gated on administrator access - an administrator whose session isn't elevated is sent to auth-server's `/auth/elevate` page and returned. `active` is the plugin's `adminNav` item id. |
356
+ | `shared/components/layout/BrandingChrome.js` | `BrandingHeader` and `BrandingFooter`, for pages that don't use a shell, such as public pages. |
357
+ | `shared/plugins/pluginNav.js` | The `PluginNav`, `PluginUiNavItem` and `PluginNavProps` types. |
358
+
359
+ Every www and admin page receives a `pluginNav` prop from the server. It lists the settings sections, admin sections and
360
+ app rail entries of every enabled plugin whose UI built. The shells append those entries after their own, with a generic
361
+ icon. An entry whose id matches a core entry is skipped, and so is one whose `href` isn't a same-origin path. Pass the
362
+ page props straight to the shell so the navigation shows:
363
+
364
+ ```tsx
365
+ import React from "react";
366
+ import SettingsShell, {
367
+ SettingsShellProps,
368
+ useSettingsShell,
369
+ } from "@rapidmx/web-client/shared/components/settings/layout/SettingsShell.js";
370
+
371
+ export default function RemindersSettingsPage(props: Omit<SettingsShellProps, "active">) {
372
+ return (
373
+ <SettingsShell {...props} active="reminders">
374
+ <RemindersSettings />
375
+ </SettingsShell>
376
+ );
377
+ }
378
+
379
+ function RemindersSettings() {
380
+ const { mailboxUid } = useSettingsShell();
381
+ return <p>Settings for {mailboxUid}</p>;
382
+ }
383
+ ```
384
+
385
+ Public and escrow pages get no `pluginNav`.
386
+
387
+ ### From `@rapidmx/web-client/lib`
388
+
389
+ Plugin pages import these directly from `@rapidmx/web-client/lib`, which the server resolves to the same copy the
390
+ shells use (until 2026-09-27 these were imported from the separate `@rapidmx/react-shared` package - see `lib/`'s own
391
+ section above):
392
+
393
+ - `branding/useBranding.js`: `useBranding()`, for the branding and icon of pages outside a shell;
394
+ - `auth/session.js`: `useSessionRefresh()`, already called by every shell, which renews the sign-in before its hour is up;
395
+ - `util/api.js`: `apiFetch()` and `ApiRequestError`, for calling the plugin's own API routes;
396
+ - `mail/mailApi.js`: mailboxes and folders, such as `listMailboxes()` and `listFolders()`;
397
+ - `components/buttons/Button.js`, `components/feedback/Alert.js` and `components/feedback/Skeleton.js`;
398
+ - `components/forms/FormField.js`;
399
+ - `components/overlays/Modal.js` and `components/overlays/Drawer.js`;
400
+ - `components/pickers/MiniDatePicker.js`.
401
+
402
+ ## Uninstalling a plugin with its data
403
+
404
+ The admin console's Plugins page (`PluginsManager`, also embedded in the setup wizard) uninstalls a plugin through a dialog with an unchecked **Also delete all data this plugin stored** box. Ticking it lists what will be deleted (collections and tables, saved settings, cached package and pages, whatever the plugin cleans up itself), says **This can't be undone**, turns the confirm button into the red **Uninstall and delete data** and keeps it disabled until the plugin's display name is typed (case and surrounding spaces ignored). The dialog is a `Modal` (focus moves in and stays in, Escape closes, the checkbox is described by the list) and a `<form>`, so Enter in the name field confirms once it matches; at 390 px it keeps the modal's 20 px gutters and the list wraps.
405
+
406
+ The request is `removePlugin(uid, { purgeData: true })` (`@rapidmx/web-client/lib/admin/pluginsApi.js`); the server accepts it only from an elevated administrator, and an `api-104` answer is shown as a request to reload or sign in again. Deletion happens on the servers after the last copy stops running the plugin, so `GET /api/system/plugins/status` carries each deletion as `purges` and the page lists the uninstalled plugin with its state - *Uninstalled - data will be deleted after servers restart* (and how many servers still run it), *Data deleted <date>*, or *Data deletion failed: <reason>* with the failed steps and a Retry button. It reads the status every 5 seconds while a deletion is waiting or running, and raises an `apps/shared/notifications` pop-up when one that was under way is deleted or fails, and a warning when adding a plugin cancels one. A plugin that is installed is never shown as uninstalled, whatever the server still lists about an earlier deletion.
407
+
408
+ ## Development
409
+
410
+ ```sh
411
+ yarn install
412
+ yarn test # vitest with coverage gates
413
+ yarn lint
414
+ yarn build # tsc into dist/apps and dist/lib
415
+ ```