@rapidmx/web-client 0.9.0 → 0.11.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 (443) hide show
  1. package/README.md +373 -88
  2. package/apps/admin/_layout.tsx +8 -2
  3. package/apps/admin/index.tsx +7 -1
  4. package/apps/admin/ingest-queue/index.tsx +2 -1
  5. package/apps/admin/mailboxes/[uid].tsx +23 -10
  6. package/apps/admin/quarantine/index.tsx +2 -1
  7. package/apps/admin/signing-certificates/index.tsx +343 -0
  8. package/apps/escrow/_layout.tsx +8 -2
  9. package/apps/shared/appearance/AppearanceForm.tsx +392 -0
  10. package/apps/shared/appearance/AppearanceHead.tsx +49 -0
  11. package/apps/shared/appearance/AppearanceProvider.tsx +470 -0
  12. package/apps/shared/appearance/ColorField.tsx +107 -0
  13. package/apps/shared/appearance/appearanceCache.ts +106 -0
  14. package/apps/shared/appearance/appearanceContext.ts +77 -0
  15. package/apps/shared/appearance/bootScript.ts +35 -0
  16. package/apps/shared/appearance/color.ts +169 -0
  17. package/apps/shared/appearance/photo.ts +88 -0
  18. package/apps/shared/appearance/resolvedTheme.ts +67 -0
  19. package/apps/shared/appearance/theme.ts +355 -0
  20. package/apps/shared/auth/accountUrl.ts +9 -0
  21. package/apps/shared/auth/adminAccess.ts +99 -0
  22. package/apps/shared/components/admin/layout/AdminShell.tsx +22 -10
  23. package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +107 -60
  24. package/apps/shared/components/admin/settings/BrandingForm.tsx +24 -0
  25. package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +2 -1
  26. package/apps/shared/components/admin/settings/PluginsManager.tsx +228 -25
  27. package/apps/shared/components/admin/setup/SetupWizard.tsx +2 -1
  28. package/apps/shared/components/calendar/CalendarListSidebar.tsx +4 -5
  29. package/apps/shared/components/calendar/layout/CalendarShell.tsx +8 -2
  30. package/apps/shared/components/contacts/ContactForm.tsx +11 -5
  31. package/apps/shared/components/contacts/ContactsSidebar.tsx +5 -3
  32. package/apps/shared/components/contacts/ContactsToolbar.tsx +98 -115
  33. package/apps/shared/components/contacts/layout/ContactsShell.tsx +25 -8
  34. package/apps/shared/components/escrow/layout/EscrowShell.tsx +13 -6
  35. package/apps/shared/components/layout/AppShell.tsx +477 -288
  36. package/apps/shared/components/layout/BrandingChrome.tsx +200 -13
  37. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -389
  38. package/apps/shared/components/layout/MailboxProvisioning.tsx +168 -164
  39. package/apps/shared/components/layout/RailIcon.tsx +100 -0
  40. package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -0
  41. package/apps/shared/components/layout/ThemeSwitch.tsx +84 -0
  42. package/apps/shared/components/layout/UserMenu.tsx +372 -185
  43. package/apps/shared/components/mail/ConversationList.tsx +271 -262
  44. package/apps/shared/components/mail/ConversationThreadPane.tsx +147 -94
  45. package/apps/shared/components/mail/LazyReadingPane.tsx +113 -0
  46. package/apps/shared/components/mail/MailSelectionBar.tsx +227 -222
  47. package/apps/shared/components/mail/MessageDetailPane.tsx +1537 -1387
  48. package/apps/shared/components/mail/OutboxBadge.tsx +44 -0
  49. package/apps/shared/components/mail/OutboxRowStatus.tsx +31 -0
  50. package/apps/shared/components/mail/compose/ComposeContext.tsx +312 -160
  51. package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -418
  52. package/apps/shared/components/mail/compose/ComposeWindow.tsx +1644 -1736
  53. package/apps/shared/components/mail/compose/ComposeWindowPlaceholder.tsx +111 -0
  54. package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -118
  55. package/apps/shared/components/mail/compose/composePerf.ts +46 -0
  56. package/apps/shared/components/mail/compose/encryptionRequirement.ts +86 -0
  57. package/apps/shared/components/mail/compose/quotedBody.ts +161 -100
  58. package/apps/shared/components/mail/layout/MailShell.tsx +490 -472
  59. package/apps/shared/components/mail/reading/EncryptedBody.tsx +65 -0
  60. package/apps/shared/components/mail/reading/EncryptedPreview.tsx +35 -0
  61. package/apps/shared/components/mail/reading/MessageBody.tsx +240 -0
  62. package/apps/shared/components/mail/reading/MessageCard.tsx +174 -0
  63. package/apps/shared/components/mail/reading/bodyContent.ts +100 -0
  64. package/apps/shared/components/mail/reading/bodyHtml.ts +298 -0
  65. package/apps/shared/components/mail/reading/color.ts +245 -0
  66. package/apps/shared/components/mail/reading/frameControl.ts +142 -0
  67. package/apps/shared/components/mail/reading/frameDocument.ts +123 -0
  68. package/apps/shared/components/mail/reading/safeDocument.ts +24 -0
  69. package/apps/shared/components/mail/reading/themeAdaptation.ts +221 -0
  70. package/apps/shared/components/mail/reading/themeSurface.ts +90 -0
  71. package/apps/shared/components/mail/reading/viewOriginal.ts +38 -0
  72. package/apps/shared/components/mail/unreadStyle.tsx +75 -0
  73. package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -0
  74. package/apps/shared/components/settings/layout/SettingsShell.tsx +253 -240
  75. package/apps/shared/components/sharing/PrincipalPicker.tsx +145 -0
  76. package/apps/shared/components/tasks/TasksSidebar.tsx +5 -3
  77. package/apps/shared/components/tasks/layout/TasksShell.tsx +24 -8
  78. package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -0
  79. package/apps/shared/keyboard/ShortcutProvider.tsx +62 -0
  80. package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -0
  81. package/apps/shared/keyboard/dispatch.ts +124 -0
  82. package/apps/shared/keyboard/format.ts +89 -0
  83. package/apps/shared/keyboard/keymap.ts +114 -0
  84. package/apps/shared/keyboard/match.ts +44 -0
  85. package/apps/shared/keyboard/parse.ts +136 -0
  86. package/apps/shared/keyboard/platform.ts +34 -0
  87. package/apps/shared/keyboard/registry.ts +65 -0
  88. package/apps/shared/keyboard/targets.ts +79 -0
  89. package/apps/shared/keyboard/useShortcut.ts +50 -0
  90. package/apps/shared/keyboard/useShortcutProps.ts +17 -0
  91. package/apps/shared/mail/folderCounts.ts +308 -0
  92. package/apps/shared/mail/folderTree.ts +143 -0
  93. package/apps/shared/mail/listSnapshots.ts +87 -0
  94. package/apps/shared/mail/messageReadState.ts +85 -0
  95. package/apps/shared/mail/newMailNotifications.ts +183 -0
  96. package/apps/shared/mail/outbox/UnlockBridge.tsx +23 -0
  97. package/apps/shared/mail/outbox/composeBridge.ts +69 -0
  98. package/apps/shared/mail/outbox/outboxState.ts +88 -0
  99. package/apps/shared/mail/outbox/pendingSends.ts +146 -0
  100. package/apps/shared/mail/outbox/sendDecision.ts +157 -0
  101. package/apps/shared/mail/outbox/sendJob.ts +427 -0
  102. package/apps/shared/mail/outbox/sendOutcomes.ts +146 -0
  103. package/apps/shared/mail/outbox/sendState.ts +35 -0
  104. package/apps/shared/mail/outbox/useOutboxStatus.ts +61 -0
  105. package/apps/shared/mail/useMailConnection.ts +203 -0
  106. package/apps/shared/mail/useMailLiveUpdates.ts +277 -224
  107. package/apps/shared/mail/useMarkMessageRead.ts +47 -0
  108. package/apps/shared/mail/useNewMailNotifications.ts +178 -0
  109. package/apps/shared/mail/useUnreadTitle.ts +42 -0
  110. package/apps/shared/navigation/AppRouter.tsx +300 -0
  111. package/apps/shared/navigation/appHrefs.ts +23 -0
  112. package/apps/shared/navigation/frameContext.tsx +35 -0
  113. package/apps/shared/navigation/idle.ts +45 -0
  114. package/apps/shared/navigation/routerContext.tsx +83 -0
  115. package/apps/shared/navigation/routes.ts +72 -0
  116. package/apps/shared/notifications/NotificationCenter.tsx +215 -0
  117. package/apps/shared/notifications/NotificationHistoryDialog.tsx +90 -0
  118. package/apps/shared/notifications/apiErrors.ts +98 -0
  119. package/apps/shared/notifications/headerOffset.ts +35 -0
  120. package/apps/shared/notifications/pushStatus.ts +56 -0
  121. package/apps/shared/notifications/store.ts +549 -0
  122. package/apps/shared/notifications/systemErrors.ts +63 -0
  123. package/apps/shared/notifications/useNotifications.ts +41 -0
  124. package/apps/shared/search/LocalIndexLifecycle.tsx +114 -98
  125. package/apps/shared/signing/enrollmentStorage.ts +33 -0
  126. package/apps/shared/signing/enrollmentTracker.ts +385 -0
  127. package/apps/shared/signing/enrollmentView.ts +251 -0
  128. package/apps/shared/signing/signingInfo.ts +58 -0
  129. package/apps/shared/signing/useNow.ts +19 -0
  130. package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -0
  131. package/apps/shared/styles/app.css +108 -10
  132. package/apps/www/_layout.tsx +8 -2
  133. package/apps/www/_routedPage.tsx +24 -0
  134. package/apps/www/_routes.ts +36 -0
  135. package/apps/www/calendar/index.tsx +64 -33
  136. package/apps/www/contacts/[uid].tsx +109 -107
  137. package/apps/www/contacts/index.tsx +62 -42
  138. package/apps/www/index.tsx +2291 -1917
  139. package/apps/www/messages/[uid].tsx +108 -101
  140. package/apps/www/settings/appearance/index.tsx +21 -0
  141. package/apps/www/settings/auto-reply/index.tsx +7 -8
  142. package/apps/www/settings/encryption/index.tsx +1287 -1249
  143. package/apps/www/settings/filters/[uid].tsx +7 -2
  144. package/apps/www/settings/filters/index.tsx +102 -99
  145. package/apps/www/settings/filters/new/index.tsx +140 -133
  146. package/apps/www/settings/labels/index.tsx +204 -201
  147. package/apps/www/settings/privacy/index.tsx +7 -9
  148. package/apps/www/settings/read-receipts/index.tsx +7 -8
  149. package/apps/www/settings/sharing/index.tsx +256 -274
  150. package/apps/www/settings/signatures/[uid].tsx +172 -167
  151. package/apps/www/settings/signatures/index.tsx +88 -85
  152. package/apps/www/settings/signatures/new/index.tsx +135 -129
  153. package/apps/www/tasks/index.tsx +29 -18
  154. package/dist/apps/admin/_layout.d.ts +5 -1
  155. package/dist/apps/admin/_layout.js +3 -2
  156. package/dist/apps/admin/index.js +3 -2
  157. package/dist/apps/admin/ingest-queue/index.js +2 -1
  158. package/dist/apps/admin/mailboxes/[uid].js +9 -6
  159. package/dist/apps/admin/quarantine/index.js +2 -1
  160. package/dist/apps/admin/signing-certificates/index.d.ts +3 -0
  161. package/dist/apps/admin/signing-certificates/index.js +161 -0
  162. package/dist/apps/escrow/_layout.d.ts +5 -1
  163. package/dist/apps/escrow/_layout.js +3 -2
  164. package/dist/apps/shared/appearance/AppearanceForm.d.ts +7 -0
  165. package/dist/apps/shared/appearance/AppearanceForm.js +131 -0
  166. package/dist/apps/shared/appearance/AppearanceHead.d.ts +15 -0
  167. package/dist/apps/shared/appearance/AppearanceHead.js +31 -0
  168. package/dist/apps/shared/appearance/AppearanceProvider.d.ts +32 -0
  169. package/dist/apps/shared/appearance/AppearanceProvider.js +390 -0
  170. package/dist/apps/shared/appearance/ColorField.d.ts +22 -0
  171. package/dist/apps/shared/appearance/ColorField.js +40 -0
  172. package/dist/apps/shared/appearance/appearanceCache.d.ts +45 -0
  173. package/dist/apps/shared/appearance/appearanceCache.js +80 -0
  174. package/dist/apps/shared/appearance/appearanceContext.d.ts +45 -0
  175. package/dist/apps/shared/appearance/appearanceContext.js +29 -0
  176. package/dist/apps/shared/appearance/bootScript.d.ts +16 -0
  177. package/dist/apps/shared/appearance/bootScript.js +33 -0
  178. package/dist/apps/shared/appearance/color.d.ts +65 -0
  179. package/dist/apps/shared/appearance/color.js +132 -0
  180. package/dist/apps/shared/appearance/photo.d.ts +17 -0
  181. package/dist/apps/shared/appearance/photo.js +79 -0
  182. package/dist/apps/shared/appearance/resolvedTheme.d.ts +21 -0
  183. package/dist/apps/shared/appearance/resolvedTheme.js +55 -0
  184. package/dist/apps/shared/appearance/theme.d.ts +111 -0
  185. package/dist/apps/shared/appearance/theme.js +251 -0
  186. package/dist/apps/shared/auth/accountUrl.d.ts +2 -0
  187. package/dist/apps/shared/auth/accountUrl.js +8 -0
  188. package/dist/apps/shared/auth/adminAccess.d.ts +30 -0
  189. package/dist/apps/shared/auth/adminAccess.js +89 -0
  190. package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
  191. package/dist/apps/shared/components/admin/layout/AdminShell.js +16 -6
  192. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.d.ts +10 -5
  193. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +49 -34
  194. package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +2 -0
  195. package/dist/apps/shared/components/admin/settings/BrandingForm.js +7 -1
  196. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +1 -1
  197. package/dist/apps/shared/components/admin/settings/PluginsManager.js +106 -20
  198. package/dist/apps/shared/components/admin/setup/SetupWizard.js +2 -1
  199. package/dist/apps/shared/components/calendar/CalendarListSidebar.js +8 -8
  200. package/dist/apps/shared/components/calendar/layout/CalendarShell.d.ts +1 -1
  201. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +8 -4
  202. package/dist/apps/shared/components/contacts/ContactForm.js +1 -1
  203. package/dist/apps/shared/components/contacts/ContactsSidebar.js +4 -2
  204. package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +6 -2
  205. package/dist/apps/shared/components/contacts/ContactsToolbar.js +28 -10
  206. package/dist/apps/shared/components/contacts/layout/ContactsShell.d.ts +1 -1
  207. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +19 -6
  208. package/dist/apps/shared/components/escrow/layout/EscrowShell.js +7 -4
  209. package/dist/apps/shared/components/layout/AppShell.d.ts +34 -4
  210. package/dist/apps/shared/components/layout/AppShell.js +122 -17
  211. package/dist/apps/shared/components/layout/BrandingChrome.d.ts +69 -10
  212. package/dist/apps/shared/components/layout/BrandingChrome.js +140 -12
  213. package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +10 -9
  214. package/dist/apps/shared/components/layout/MailboxProvisioning.js +3 -1
  215. package/dist/apps/shared/components/layout/RailIcon.d.ts +21 -0
  216. package/dist/apps/shared/components/layout/RailIcon.js +85 -0
  217. package/dist/apps/shared/components/layout/ResponsiveToolbar.d.ts +55 -0
  218. package/dist/apps/shared/components/layout/ResponsiveToolbar.js +199 -0
  219. package/dist/apps/shared/components/layout/ThemeSwitch.d.ts +9 -0
  220. package/dist/apps/shared/components/layout/ThemeSwitch.js +54 -0
  221. package/dist/apps/shared/components/layout/UserMenu.d.ts +25 -3
  222. package/dist/apps/shared/components/layout/UserMenu.js +88 -7
  223. package/dist/apps/shared/components/mail/ConversationList.js +5 -11
  224. package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +6 -2
  225. package/dist/apps/shared/components/mail/ConversationThreadPane.js +87 -40
  226. package/dist/apps/shared/components/mail/LazyReadingPane.d.ts +9 -0
  227. package/dist/apps/shared/components/mail/LazyReadingPane.js +83 -0
  228. package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +6 -4
  229. package/dist/apps/shared/components/mail/MailSelectionBar.js +11 -4
  230. package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +25 -8
  231. package/dist/apps/shared/components/mail/MessageDetailPane.js +167 -73
  232. package/dist/apps/shared/components/mail/OutboxBadge.d.ts +17 -0
  233. package/dist/apps/shared/components/mail/OutboxBadge.js +18 -0
  234. package/dist/apps/shared/components/mail/OutboxRowStatus.d.ts +10 -0
  235. package/dist/apps/shared/components/mail/OutboxRowStatus.js +19 -0
  236. package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +38 -0
  237. package/dist/apps/shared/components/mail/compose/ComposeContext.js +97 -6
  238. package/dist/apps/shared/components/mail/compose/ComposeToolbar.js +4 -3
  239. package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +3 -2
  240. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +316 -339
  241. package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.d.ts +19 -0
  242. package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.js +33 -0
  243. package/dist/apps/shared/components/mail/compose/RichTextEditor.d.ts +9 -1
  244. package/dist/apps/shared/components/mail/compose/RichTextEditor.js +20 -2
  245. package/dist/apps/shared/components/mail/compose/composePerf.d.ts +16 -0
  246. package/dist/apps/shared/components/mail/compose/composePerf.js +41 -0
  247. package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +49 -0
  248. package/dist/apps/shared/components/mail/compose/encryptionRequirement.js +35 -0
  249. package/dist/apps/shared/components/mail/compose/quotedBody.d.ts +13 -0
  250. package/dist/apps/shared/components/mail/compose/quotedBody.js +67 -11
  251. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +14 -4
  252. package/dist/apps/shared/components/mail/layout/MailShell.js +90 -104
  253. package/dist/apps/shared/components/mail/reading/EncryptedBody.d.ts +21 -0
  254. package/dist/apps/shared/components/mail/reading/EncryptedBody.js +22 -0
  255. package/dist/apps/shared/components/mail/reading/EncryptedPreview.d.ts +18 -0
  256. package/dist/apps/shared/components/mail/reading/EncryptedPreview.js +22 -0
  257. package/dist/apps/shared/components/mail/reading/MessageBody.d.ts +32 -0
  258. package/dist/apps/shared/components/mail/reading/MessageBody.js +136 -0
  259. package/dist/apps/shared/components/mail/reading/MessageCard.d.ts +62 -0
  260. package/dist/apps/shared/components/mail/reading/MessageCard.js +58 -0
  261. package/dist/apps/shared/components/mail/reading/bodyContent.d.ts +38 -0
  262. package/dist/apps/shared/components/mail/reading/bodyContent.js +84 -0
  263. package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +47 -0
  264. package/dist/apps/shared/components/mail/reading/bodyHtml.js +267 -0
  265. package/dist/apps/shared/components/mail/reading/color.d.ts +63 -0
  266. package/dist/apps/shared/components/mail/reading/color.js +201 -0
  267. package/dist/apps/shared/components/mail/reading/frameControl.d.ts +33 -0
  268. package/dist/apps/shared/components/mail/reading/frameControl.js +120 -0
  269. package/dist/apps/shared/components/mail/reading/frameDocument.d.ts +52 -0
  270. package/dist/apps/shared/components/mail/reading/frameDocument.js +98 -0
  271. package/dist/apps/shared/components/mail/reading/safeDocument.d.ts +3 -0
  272. package/dist/apps/shared/components/mail/reading/safeDocument.js +12 -0
  273. package/dist/apps/shared/components/mail/reading/themeAdaptation.d.ts +56 -0
  274. package/dist/apps/shared/components/mail/reading/themeAdaptation.js +144 -0
  275. package/dist/apps/shared/components/mail/reading/themeSurface.d.ts +17 -0
  276. package/dist/apps/shared/components/mail/reading/themeSurface.js +83 -0
  277. package/dist/apps/shared/components/mail/reading/viewOriginal.d.ts +9 -0
  278. package/dist/apps/shared/components/mail/reading/viewOriginal.js +32 -0
  279. package/dist/apps/shared/components/mail/unreadStyle.d.ts +45 -0
  280. package/dist/apps/shared/components/mail/unreadStyle.js +61 -0
  281. package/dist/apps/shared/components/settings/SigningCertificateCard.d.ts +32 -0
  282. package/dist/apps/shared/components/settings/SigningCertificateCard.js +98 -0
  283. package/dist/apps/shared/components/settings/layout/SettingsShell.d.ts +1 -1
  284. package/dist/apps/shared/components/settings/layout/SettingsShell.js +16 -5
  285. package/dist/apps/shared/components/sharing/PrincipalPicker.d.ts +26 -0
  286. package/dist/apps/shared/components/sharing/PrincipalPicker.js +77 -0
  287. package/dist/apps/shared/components/tasks/TasksSidebar.js +4 -2
  288. package/dist/apps/shared/components/tasks/layout/TasksShell.d.ts +1 -1
  289. package/dist/apps/shared/components/tasks/layout/TasksShell.js +18 -6
  290. package/dist/apps/shared/keyboard/GlobalShortcuts.d.ts +14 -0
  291. package/dist/apps/shared/keyboard/GlobalShortcuts.js +37 -0
  292. package/dist/apps/shared/keyboard/ShortcutProvider.d.ts +20 -0
  293. package/dist/apps/shared/keyboard/ShortcutProvider.js +52 -0
  294. package/dist/apps/shared/keyboard/ShortcutsDialog.d.ts +14 -0
  295. package/dist/apps/shared/keyboard/ShortcutsDialog.js +42 -0
  296. package/dist/apps/shared/keyboard/dispatch.d.ts +16 -0
  297. package/dist/apps/shared/keyboard/dispatch.js +109 -0
  298. package/dist/apps/shared/keyboard/format.d.ts +12 -0
  299. package/dist/apps/shared/keyboard/format.js +74 -0
  300. package/dist/apps/shared/keyboard/keymap.d.ts +294 -0
  301. package/dist/apps/shared/keyboard/keymap.js +84 -0
  302. package/dist/apps/shared/keyboard/match.d.ts +20 -0
  303. package/dist/apps/shared/keyboard/match.js +31 -0
  304. package/dist/apps/shared/keyboard/parse.d.ts +31 -0
  305. package/dist/apps/shared/keyboard/parse.js +107 -0
  306. package/dist/apps/shared/keyboard/platform.d.ts +15 -0
  307. package/dist/apps/shared/keyboard/platform.js +22 -0
  308. package/dist/apps/shared/keyboard/registry.d.ts +39 -0
  309. package/dist/apps/shared/keyboard/registry.js +33 -0
  310. package/dist/apps/shared/keyboard/targets.d.ts +14 -0
  311. package/dist/apps/shared/keyboard/targets.js +67 -0
  312. package/dist/apps/shared/keyboard/useShortcut.d.ts +19 -0
  313. package/dist/apps/shared/keyboard/useShortcut.js +35 -0
  314. package/dist/apps/shared/keyboard/useShortcutProps.d.ts +10 -0
  315. package/dist/apps/shared/keyboard/useShortcutProps.js +15 -0
  316. package/dist/apps/shared/mail/folderCounts.d.ts +78 -0
  317. package/dist/apps/shared/mail/folderCounts.js +218 -0
  318. package/dist/apps/shared/mail/folderTree.d.ts +55 -0
  319. package/dist/apps/shared/mail/folderTree.js +116 -0
  320. package/dist/apps/shared/mail/listSnapshots.d.ts +46 -0
  321. package/dist/apps/shared/mail/listSnapshots.js +43 -0
  322. package/dist/apps/shared/mail/messageReadState.d.ts +32 -0
  323. package/dist/apps/shared/mail/messageReadState.js +63 -0
  324. package/dist/apps/shared/mail/newMailNotifications.d.ts +62 -0
  325. package/dist/apps/shared/mail/newMailNotifications.js +138 -0
  326. package/dist/apps/shared/mail/outbox/UnlockBridge.d.ts +5 -0
  327. package/dist/apps/shared/mail/outbox/UnlockBridge.js +18 -0
  328. package/dist/apps/shared/mail/outbox/composeBridge.d.ts +34 -0
  329. package/dist/apps/shared/mail/outbox/composeBridge.js +32 -0
  330. package/dist/apps/shared/mail/outbox/outboxState.d.ts +41 -0
  331. package/dist/apps/shared/mail/outbox/outboxState.js +42 -0
  332. package/dist/apps/shared/mail/outbox/pendingSends.d.ts +39 -0
  333. package/dist/apps/shared/mail/outbox/pendingSends.js +109 -0
  334. package/dist/apps/shared/mail/outbox/sendDecision.d.ts +80 -0
  335. package/dist/apps/shared/mail/outbox/sendDecision.js +94 -0
  336. package/dist/apps/shared/mail/outbox/sendJob.d.ts +68 -0
  337. package/dist/apps/shared/mail/outbox/sendJob.js +319 -0
  338. package/dist/apps/shared/mail/outbox/sendOutcomes.d.ts +3 -0
  339. package/dist/apps/shared/mail/outbox/sendOutcomes.js +141 -0
  340. package/dist/apps/shared/mail/outbox/sendState.d.ts +25 -0
  341. package/dist/apps/shared/mail/outbox/sendState.js +8 -0
  342. package/dist/apps/shared/mail/outbox/useOutboxStatus.d.ts +11 -0
  343. package/dist/apps/shared/mail/outbox/useOutboxStatus.js +49 -0
  344. package/dist/apps/shared/mail/useMailConnection.d.ts +56 -0
  345. package/dist/apps/shared/mail/useMailConnection.js +138 -0
  346. package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +29 -9
  347. package/dist/apps/shared/mail/useMailLiveUpdates.js +60 -32
  348. package/dist/apps/shared/mail/useMarkMessageRead.d.ts +12 -0
  349. package/dist/apps/shared/mail/useMarkMessageRead.js +44 -0
  350. package/dist/apps/shared/mail/useNewMailNotifications.d.ts +35 -0
  351. package/dist/apps/shared/mail/useNewMailNotifications.js +130 -0
  352. package/dist/apps/shared/mail/useUnreadTitle.d.ts +18 -0
  353. package/dist/apps/shared/mail/useUnreadTitle.js +31 -0
  354. package/dist/apps/shared/navigation/AppRouter.d.ts +52 -0
  355. package/dist/apps/shared/navigation/AppRouter.js +242 -0
  356. package/dist/apps/shared/navigation/appHrefs.d.ts +14 -0
  357. package/dist/apps/shared/navigation/appHrefs.js +20 -0
  358. package/dist/apps/shared/navigation/frameContext.d.ts +19 -0
  359. package/dist/apps/shared/navigation/frameContext.js +24 -0
  360. package/dist/apps/shared/navigation/idle.d.ts +14 -0
  361. package/dist/apps/shared/navigation/idle.js +43 -0
  362. package/dist/apps/shared/navigation/routerContext.d.ts +37 -0
  363. package/dist/apps/shared/navigation/routerContext.js +56 -0
  364. package/dist/apps/shared/navigation/routes.d.ts +32 -0
  365. package/dist/apps/shared/navigation/routes.js +37 -0
  366. package/dist/apps/shared/notifications/NotificationCenter.d.ts +19 -0
  367. package/dist/apps/shared/notifications/NotificationCenter.js +90 -0
  368. package/dist/apps/shared/notifications/NotificationHistoryDialog.d.ts +10 -0
  369. package/dist/apps/shared/notifications/NotificationHistoryDialog.js +36 -0
  370. package/dist/apps/shared/notifications/apiErrors.d.ts +19 -0
  371. package/dist/apps/shared/notifications/apiErrors.js +86 -0
  372. package/dist/apps/shared/notifications/headerOffset.d.ts +8 -0
  373. package/dist/apps/shared/notifications/headerOffset.js +33 -0
  374. package/dist/apps/shared/notifications/pushStatus.d.ts +9 -0
  375. package/dist/apps/shared/notifications/pushStatus.js +54 -0
  376. package/dist/apps/shared/notifications/store.d.ts +136 -0
  377. package/dist/apps/shared/notifications/store.js +416 -0
  378. package/dist/apps/shared/notifications/systemErrors.d.ts +9 -0
  379. package/dist/apps/shared/notifications/systemErrors.js +58 -0
  380. package/dist/apps/shared/notifications/useNotifications.d.ts +17 -0
  381. package/dist/apps/shared/notifications/useNotifications.js +18 -0
  382. package/dist/apps/shared/search/LocalIndexLifecycle.js +17 -3
  383. package/dist/apps/shared/signing/enrollmentStorage.d.ts +10 -0
  384. package/dist/apps/shared/signing/enrollmentStorage.js +33 -0
  385. package/dist/apps/shared/signing/enrollmentTracker.d.ts +73 -0
  386. package/dist/apps/shared/signing/enrollmentTracker.js +307 -0
  387. package/dist/apps/shared/signing/enrollmentView.d.ts +57 -0
  388. package/dist/apps/shared/signing/enrollmentView.js +214 -0
  389. package/dist/apps/shared/signing/signingInfo.d.ts +11 -0
  390. package/dist/apps/shared/signing/signingInfo.js +48 -0
  391. package/dist/apps/shared/signing/useNow.d.ts +2 -0
  392. package/dist/apps/shared/signing/useNow.js +18 -0
  393. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.d.ts +16 -0
  394. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.js +87 -0
  395. package/dist/apps/shared/styles/app.css +108 -10
  396. package/dist/apps/www/_layout.d.ts +5 -1
  397. package/dist/apps/www/_layout.js +3 -2
  398. package/dist/apps/www/_routedPage.d.ts +12 -0
  399. package/dist/apps/www/_routedPage.js +19 -0
  400. package/dist/apps/www/_routes.d.ts +11 -0
  401. package/dist/apps/www/_routes.js +30 -0
  402. package/dist/apps/www/calendar/index.d.ts +2 -2
  403. package/dist/apps/www/calendar/index.js +41 -11
  404. package/dist/apps/www/contacts/[uid].d.ts +3 -9
  405. package/dist/apps/www/contacts/[uid].js +11 -9
  406. package/dist/apps/www/contacts/index.d.ts +2 -2
  407. package/dist/apps/www/contacts/index.js +30 -24
  408. package/dist/apps/www/index.d.ts +2 -2
  409. package/dist/apps/www/index.js +332 -48
  410. package/dist/apps/www/messages/[uid].d.ts +3 -7
  411. package/dist/apps/www/messages/[uid].js +10 -5
  412. package/dist/apps/www/settings/appearance/index.d.ts +4 -0
  413. package/dist/apps/www/settings/appearance/index.js +13 -0
  414. package/dist/apps/www/settings/auto-reply/index.d.ts +2 -2
  415. package/dist/apps/www/settings/auto-reply/index.js +6 -7
  416. package/dist/apps/www/settings/encryption/index.d.ts +2 -2
  417. package/dist/apps/www/settings/encryption/index.js +106 -96
  418. package/dist/apps/www/settings/filters/[uid].d.ts +2 -2
  419. package/dist/apps/www/settings/filters/[uid].js +6 -2
  420. package/dist/apps/www/settings/filters/index.d.ts +2 -2
  421. package/dist/apps/www/settings/filters/index.js +3 -1
  422. package/dist/apps/www/settings/filters/new/index.d.ts +2 -2
  423. package/dist/apps/www/settings/filters/new/index.js +9 -3
  424. package/dist/apps/www/settings/labels/index.d.ts +2 -2
  425. package/dist/apps/www/settings/labels/index.js +3 -1
  426. package/dist/apps/www/settings/privacy/index.d.ts +2 -2
  427. package/dist/apps/www/settings/privacy/index.js +8 -9
  428. package/dist/apps/www/settings/read-receipts/index.d.ts +2 -2
  429. package/dist/apps/www/settings/read-receipts/index.js +6 -7
  430. package/dist/apps/www/settings/sharing/index.d.ts +2 -2
  431. package/dist/apps/www/settings/sharing/index.js +22 -34
  432. package/dist/apps/www/settings/signatures/[uid].d.ts +2 -2
  433. package/dist/apps/www/settings/signatures/[uid].js +6 -2
  434. package/dist/apps/www/settings/signatures/index.d.ts +2 -2
  435. package/dist/apps/www/settings/signatures/index.js +3 -1
  436. package/dist/apps/www/settings/signatures/new/index.d.ts +2 -2
  437. package/dist/apps/www/settings/signatures/new/index.js +9 -4
  438. package/dist/apps/www/tasks/index.d.ts +2 -2
  439. package/dist/apps/www/tasks/index.js +22 -16
  440. package/package.json +2 -2
  441. package/apps/shared/components/mail/compose/SendFailureAlert.tsx +0 -48
  442. package/dist/apps/shared/components/mail/compose/SendFailureAlert.d.ts +0 -14
  443. package/dist/apps/shared/components/mail/compose/SendFailureAlert.js +0 -11
@@ -0,0 +1,251 @@
1
+ import { AA_CONTRAST, BLACK, DARK_TEXT, WHITE, contrastRatio, deriveScale, ensureContrast, mix, parseHex, readableOn, relativeLuminance, toHex, } from "./color.js";
2
+ export const DEFAULT_PALETTE = {
3
+ light: { surface: "#ffffff", text: "#1c2526", muted: "#5a6b6e" },
4
+ dark: { surface: "#1b2022", text: "#eef2f3", muted: "#9fb0b3" },
5
+ };
6
+ /** How far the text colour is mixed toward the surface to get the muted text colour. */
7
+ const MUTED_MIX = { light: 0.3, dark: 0.35 };
8
+ /** How far the surface is mixed toward the text colour to get `surface-alt`. */
9
+ const ALT_MIX = { light: 0.04, dark: 0.03 };
10
+ const BORDER_MIX = 0.12;
11
+ /** How far the text colour is mixed toward the surface to get the muted text colour while a background is set - less than usual, so the panels can be more see-through. */
12
+ const BACKGROUND_MUTED_MIX = 0.18;
13
+ /** `#rrggbb` known valid (`normalizeAppearance()` and the colour inputs guarantee it). */
14
+ function rgbOf(hex) {
15
+ return parseHex(hex);
16
+ }
17
+ export function effectivePalette(colors, scheme) {
18
+ const defaults = DEFAULT_PALETTE[scheme];
19
+ const surface = rgbOf(colors?.surface ?? defaults.surface);
20
+ let text = rgbOf(colors?.text ?? defaults.text);
21
+ let textAdjusted = colors?.text !== undefined;
22
+ if (!textAdjusted && colors?.surface !== undefined && contrastRatio(text, surface) < AA_CONTRAST) {
23
+ // A surface the app's own text can't be read on: the text follows it, rather than leaving the page unreadable.
24
+ text = readableOn(surface, DARK_TEXT);
25
+ textAdjusted = true;
26
+ }
27
+ const customised = colors?.surface !== undefined || textAdjusted;
28
+ const muted = customised
29
+ ? ensureContrast(mix(text, surface, MUTED_MIX[scheme]), surface, AA_CONTRAST)
30
+ : rgbOf(defaults.muted);
31
+ return { surface, text, muted, surfaceChosen: colors?.surface !== undefined, textAdjusted };
32
+ }
33
+ /**
34
+ * The custom properties a user's colours set for one scheme - only those they changed (and what derives from them): the primary
35
+ * colour's seven-step scale and the text on it, the accent and its shades, and the surface / text family. Empty when they chose
36
+ * no colours.
37
+ */
38
+ export function colorVariables(colors, scheme) {
39
+ const vars = {};
40
+ if (!colors) {
41
+ return vars;
42
+ }
43
+ const primary = colors.primary ? deriveScale(colors.primary) : undefined;
44
+ if (primary) {
45
+ vars["--rr-color-primary"] = primary.base;
46
+ vars["--rr-color-primary-dark"] = primary.dark;
47
+ vars["--rr-color-primary-darker"] = primary.darker;
48
+ vars["--rr-color-primary-darkest"] = primary.darkest;
49
+ vars["--rr-color-primary-light"] = primary.light;
50
+ vars["--rr-color-primary-lighter"] = primary.lighter;
51
+ vars["--rr-color-primary-lightest"] = primary.lightest;
52
+ vars["--rr-color-text-on-primary"] = primary.onBase;
53
+ }
54
+ const accent = colors.accent ? parseHex(colors.accent) : undefined;
55
+ if (accent) {
56
+ vars["--rr-color-accent"] = toHex(accent);
57
+ vars["--rr-color-accent-dark"] = toHex(mix(accent, BLACK, 0.25));
58
+ vars["--rr-color-accent-soft"] = toHex(mix(accent, WHITE, 0.2));
59
+ vars["--rr-color-accent-pale"] = toHex(mix(accent, WHITE, 0.8));
60
+ vars["--rr-color-text-on-accent"] = toHex(readableOn(accent, DARK_TEXT));
61
+ }
62
+ const palette = effectivePalette(colors, scheme);
63
+ if (palette.surfaceChosen || palette.textAdjusted) {
64
+ vars["--rr-color-border"] = toHex(mix(palette.surface, palette.text, BORDER_MIX));
65
+ vars["--rr-color-text-muted"] = toHex(palette.muted);
66
+ }
67
+ if (palette.surfaceChosen) {
68
+ vars["--rr-color-surface"] = toHex(palette.surface);
69
+ vars["--rr-color-surface-alt"] = toHex(mix(palette.surface, palette.text, ALT_MIX[scheme]));
70
+ // The page behind the panels: the surface itself, except that a dark surface sits on a slightly deeper page, as the defaults do.
71
+ vars["--rr-color-bg"] = toHex(relativeLuminance(palette.surface) < 0.18 ? mix(palette.surface, BLACK, 0.25) : palette.surface);
72
+ }
73
+ if (palette.textAdjusted) {
74
+ vars["--rr-color-text"] = toHex(palette.text);
75
+ }
76
+ return vars;
77
+ }
78
+ /** What is assumed of a photo nobody has measured: black in one corner and white in another. */
79
+ export const WORST_CASE_PHOTO = { lo: BLACK, hi: WHITE };
80
+ /** The panels are never more see-through than this ... */
81
+ export const PANEL_ALPHA_MIN = 0.5;
82
+ /** ... nor less than this (still a hint of the picture). */
83
+ export const PANEL_ALPHA_MAX = 0.96;
84
+ /** Contrast the panels are made to keep, a little over AA to leave room for the compression of a photo. */
85
+ const PANEL_CONTRAST = 4.6;
86
+ /**
87
+ * The opacity (0.5 to 0.96) of the surface panels over a background, the lowest for which text and muted text keep AA contrast
88
+ * against the worst place the picture can be. What is behind a panel is the picture under the dim (`dim` of the surface colour laid
89
+ * over it); the panel is `alpha` of the surface over that, so the picture's share of what the text sits on is `(1 - alpha) *
90
+ * (1 - dim)`. A wide-ranging (busy) photo therefore gets more opaque panels than a plain one, and more dim allows less opaque
91
+ * panels.
92
+ */
93
+ export function panelAlpha(palette, dim, photo) {
94
+ for (let alpha = PANEL_ALPHA_MIN; alpha < PANEL_ALPHA_MAX; alpha = Math.round((alpha + 0.01) * 100) / 100) {
95
+ const share = (1 - alpha) * (1 - dim);
96
+ const readable = [photo.lo, photo.hi].every((behind) => {
97
+ const under = mix(palette.surface, behind, share);
98
+ return contrastRatio(palette.text, under) >= PANEL_CONTRAST && contrastRatio(palette.muted, under) >= PANEL_CONTRAST;
99
+ });
100
+ if (readable) {
101
+ return alpha;
102
+ }
103
+ }
104
+ return PANEL_ALPHA_MAX;
105
+ }
106
+ /** What a background shows behind the panels - `undefined` for none: a colour, or an image of unknown or measured lightness. */
107
+ export function backgroundPhoto(background, measured) {
108
+ if (!background || background.kind === "none") {
109
+ return undefined;
110
+ }
111
+ if (background.kind === "color") {
112
+ const colour = rgbOf(background.color);
113
+ return { lo: colour, hi: colour };
114
+ }
115
+ return measured ?? WORST_CASE_PHOTO;
116
+ }
117
+ /** The dim actually laid over the background: a colour has no picture to dim. */
118
+ export function effectiveDim(background) {
119
+ return background.kind === "image" ? background.dim : 0;
120
+ }
121
+ /**
122
+ * The custom properties for one scheme: the user's colours, plus - when a background is set - the translucent panel colours and the
123
+ * transparent frame. `measured` is the image's measured lightness range, when known (`photo.ts`).
124
+ */
125
+ export function themeVariables(prefs, scheme, measured) {
126
+ const vars = colorVariables(prefs.colors, scheme);
127
+ const photo = backgroundPhoto(prefs.background, measured);
128
+ if (photo && prefs.background) {
129
+ const palette = effectivePalette(prefs.colors, scheme);
130
+ // Muted text is what limits how see-through the panels can be, so over a background it is a shade stronger.
131
+ const muted = ensureContrast(mix(palette.text, palette.surface, BACKGROUND_MUTED_MIX), palette.surface, AA_CONTRAST);
132
+ const alpha = panelAlpha({ surface: palette.surface, text: palette.text, muted }, effectiveDim(prefs.background), photo);
133
+ const percent = `${Math.round(alpha * 100)}%`;
134
+ vars["--rr-panel"] = `color-mix(in srgb, var(--rr-color-surface) ${percent}, transparent)`;
135
+ vars["--color-surface"] = "var(--rr-panel)";
136
+ vars["--color-surface-alt"] = `color-mix(in srgb, var(--rr-color-surface-alt) ${percent}, transparent)`;
137
+ vars["--rr-color-text-muted"] = toHex(muted);
138
+ vars["--rr-panel-alpha"] = String(alpha);
139
+ vars["--rr-frame-bg"] = "transparent";
140
+ }
141
+ return vars;
142
+ }
143
+ function declarations(vars) {
144
+ return Object.entries(vars)
145
+ .map(([name, value]) => `${name}:${value} !important;`)
146
+ .join("");
147
+ }
148
+ /** The element `appearanceCss()` writes into `<head>`, so an update replaces rather than duplicates it. */
149
+ export const APPEARANCE_STYLE_ID = "rr-appearance";
150
+ /** A string as a CSS `url()` argument: quoted, with what could end the string or the declaration escaped. */
151
+ export function cssUrl(url) {
152
+ const escaped = url.replace(/[\\"\p{Cc}]/gu, (char) => "\\" + char.charCodeAt(0).toString(16) + " ");
153
+ return `url("${escaped}")`;
154
+ }
155
+ /**
156
+ * The rules for the background layer, which is two pseudo-elements of `<html>` rather than any markup - so it exists from the
157
+ * first byte of a server-rendered page (no flash), follows a live preview by rewriting one stylesheet, and needs no component.
158
+ * `::before` is the picture (or colour): `position: fixed`, behind everything (`z-index: -1` puts it above the page's own canvas
159
+ * colour and below every in-flow box), its box grown by twice the blur so the blurred edge falls outside the window. `::after` is the
160
+ * dim: the scheme's surface colour at the chosen opacity. Neither takes a click, and neither prints.
161
+ */
162
+ export function backgroundCss(background, imageUrl) {
163
+ if (!background || background.kind === "none") {
164
+ return "";
165
+ }
166
+ const layer = "content:\"\";position:fixed;z-index:-1;pointer-events:none;";
167
+ if (background.kind === "color") {
168
+ return [`html::before{${layer}inset:0;background-color:${background.color};}`, "@media print{html::before{display:none}}"].join("\n");
169
+ }
170
+ const grow = -Math.round(background.blur * 2);
171
+ const size = background.fit === "cover"
172
+ ? "background-size:cover;background-repeat:no-repeat;background-position:center;"
173
+ : background.fit === "contain"
174
+ ? "background-size:contain;background-repeat:no-repeat;background-position:center;"
175
+ : "background-size:auto;background-repeat:repeat;background-position:center;";
176
+ const image = imageUrl ? `background-image:${cssUrl(imageUrl)};` : "";
177
+ const color = background.color ?? "var(--rr-color-surface-alt)";
178
+ const blur = background.blur > 0 ? `filter:blur(${background.blur}px);` : "";
179
+ return [
180
+ `html::before{${layer}inset:${grow}px;background-color:${color};${image}${size}${blur}}`,
181
+ `html::after{${layer}inset:0;background-color:var(--rr-color-surface);opacity:${background.dim};}`,
182
+ "@media print{html::before,html::after{display:none}}",
183
+ ].join("\n");
184
+ }
185
+ /**
186
+ * The stylesheet text for the user's preferences, or `""` when they change nothing. Three rules per set of properties so each
187
+ * scheme is right in every case: the light set for everything except an explicit dark (`data-theme="dark"`) - which also covers
188
+ * a page with no `data-theme` at all; the dark set for a dark operating system on a page with no `data-theme` yet (before the
189
+ * provider ran, or a server-rendered page following "System"); and the dark set for an explicit `data-theme="dark"`. All three are
190
+ * `html:root...` (0,2,1) so the later dark rules win the equal-specificity tie against the light one. Then the background layer
191
+ * (`backgroundCss()`).
192
+ */
193
+ export function appearanceCss(prefs, options = {}) {
194
+ if (!prefs) {
195
+ return "";
196
+ }
197
+ const light = declarations(themeVariables(prefs, "light", options.measured));
198
+ const dark = declarations(themeVariables(prefs, "dark", options.measured));
199
+ const rules = [];
200
+ if (light || dark) {
201
+ rules.push(`html:root:not([data-theme="dark"]){${light}}`, `@media (prefers-color-scheme:dark){html:root:not([data-theme]){${dark}}}`, `html:root[data-theme="dark"]{${dark}}`);
202
+ }
203
+ if (prefs.colors?.primary) {
204
+ // Several components write `bg-primary text-white`; on a primary colour the user picked, white may be unreadable.
205
+ rules.push(".bg-primary.text-white{color:var(--rr-color-text-on-primary)}");
206
+ }
207
+ const background = backgroundCss(prefs.background, options.imageUrl);
208
+ if (background) {
209
+ // The content region is one translucent panel; what is inside it (`bg-surface`) shows it instead of adding a second layer.
210
+ rules.push("#app-content{background-color:var(--rr-panel);--color-surface:transparent;}",
211
+ // Fields keep a solid surface: a box that is see-through has no edge to find.
212
+ "#app-content :is(input,select,textarea){--color-surface:var(--rr-color-surface);}",
213
+ // Someone who asked their system for less transparency gets solid panels.
214
+ "@media (prefers-reduced-transparency:reduce){html:root{--rr-panel:var(--rr-color-surface) !important;--color-surface-alt:var(--rr-color-surface-alt) !important;}}", background);
215
+ }
216
+ return rules.join("\n");
217
+ }
218
+ /**
219
+ * The colour pairs the user's choices make hard to read (contrast below 4.5:1, WCAG AA for normal text) in `scheme`: the text on the
220
+ * surface, and the text on an accent-coloured button. A warning, never a block - it is their app. Nothing for a pair they haven't
221
+ * touched (the app's own pairs are the app's business).
222
+ */
223
+ export function contrastWarnings(colors, scheme) {
224
+ const warnings = [];
225
+ if (!colors) {
226
+ return warnings;
227
+ }
228
+ if (colors.surface !== undefined || colors.text !== undefined) {
229
+ const palette = effectivePalette(colors, scheme);
230
+ const ratio = contrastRatio(palette.text, palette.surface);
231
+ if (ratio < AA_CONTRAST) {
232
+ warnings.push({
233
+ id: "text-surface",
234
+ ratio,
235
+ message: `Text on the surface colour has a contrast of ${ratio.toFixed(1)}:1; 4.5:1 or more is easier to read.`,
236
+ });
237
+ }
238
+ }
239
+ if (colors.accent !== undefined) {
240
+ const accent = rgbOf(colors.accent);
241
+ const ratio = contrastRatio(readableOn(accent, DARK_TEXT), accent);
242
+ if (ratio < AA_CONTRAST) {
243
+ warnings.push({
244
+ id: "accent-text",
245
+ ratio,
246
+ message: `Text on buttons in the accent colour has a contrast of ${ratio.toFixed(1)}:1; 4.5:1 or more is easier to read.`,
247
+ });
248
+ }
249
+ }
250
+ return warnings;
251
+ }
@@ -0,0 +1,2 @@
1
+ /** auth-server's account page, or `undefined` when no auth-server is configured. Tolerates a configured URL with a trailing slash. */
2
+ export declare function accountUrlOf(authServerUrl: string | undefined): string | undefined;
@@ -0,0 +1,8 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ /** auth-server's account page, or `undefined` when no auth-server is configured. Tolerates a configured URL with a trailing slash. */
6
+ export function accountUrlOf(authServerUrl) {
7
+ return authServerUrl ? `${authServerUrl.replace(/\/+$/, "")}/account` : undefined;
8
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Whether the signed-in user holds an administrator role, learned without elevating.
3
+ *
4
+ * The JWT the browser carries can't say: auth-server strips every trusted role from a token that isn't elevated
5
+ * (`TokenUtils.resolveTokenUser()`), so `trusted` (from the JWT) is false for a real administrator until they confirm
6
+ * their identity on auth-server's elevation page. The role itself lives on the caller's own `User` record, which they may
7
+ * always read - `GET {authServerUrl}/api/users/me` (the same call auth-server's own UI makes "to check for admin access") -
8
+ * so the answer is read from there, with the same cross-origin credentials the profile lookups use.
9
+ *
10
+ * This only decides whether a menu item is shown. The `/admin` pages and API stay authoritative: they check the role and
11
+ * the elevation themselves, and the link only navigates (`/admin` then runs the elevation redirect), so a wrong answer
12
+ * - a stale cache, a tampered `sessionStorage` - can show a link that leads to "no administrator access", never more.
13
+ */
14
+ /** The roles the server treats as trusted when it isn't configured otherwise (`trusted_roles`, default `["admin"]`). */
15
+ export declare const DEFAULT_TRUSTED_ROLES: readonly string[];
16
+ /** `sessionStorage` key prefix of a remembered answer; the user's uid follows, so another account signing in in the same
17
+ * tab never inherits it. */
18
+ export declare const ADMIN_ACCESS_KEY_PREFIX = "rapidmx-admin-access:";
19
+ /** How long a remembered answer is trusted - long enough that navigating between the apps costs no request, short enough
20
+ * that a role granted or removed shows up within the sitting. */
21
+ export declare const ADMIN_ACCESS_TTL_MS: number;
22
+ /** Forgets the lookups in flight (tests). */
23
+ export declare function resetAdminAccessLookups(): void;
24
+ /**
25
+ * Resolves `true` when `userUid`'s own `User` record on auth-server carries one of `trustedRoles`, `false` when it
26
+ * doesn't, and `undefined` when that couldn't be established (unreachable, blocked by CORS, not signed in, an unexpected
27
+ * body, or a record that isn't this user's). Only a real answer is remembered; a failure is asked again on the next page.
28
+ * Never rejects.
29
+ */
30
+ export declare function lookUpAdminAccess(authServerUrl: string, userUid: string, trustedRoles?: readonly string[], now?: number): Promise<boolean | undefined>;
@@ -0,0 +1,89 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { authApiFetch } from "@rapidmx/react-shared/util/api.js";
6
+ /**
7
+ * Whether the signed-in user holds an administrator role, learned without elevating.
8
+ *
9
+ * The JWT the browser carries can't say: auth-server strips every trusted role from a token that isn't elevated
10
+ * (`TokenUtils.resolveTokenUser()`), so `trusted` (from the JWT) is false for a real administrator until they confirm
11
+ * their identity on auth-server's elevation page. The role itself lives on the caller's own `User` record, which they may
12
+ * always read - `GET {authServerUrl}/api/users/me` (the same call auth-server's own UI makes "to check for admin access") -
13
+ * so the answer is read from there, with the same cross-origin credentials the profile lookups use.
14
+ *
15
+ * This only decides whether a menu item is shown. The `/admin` pages and API stay authoritative: they check the role and
16
+ * the elevation themselves, and the link only navigates (`/admin` then runs the elevation redirect), so a wrong answer
17
+ * - a stale cache, a tampered `sessionStorage` - can show a link that leads to "no administrator access", never more.
18
+ */
19
+ /** The roles the server treats as trusted when it isn't configured otherwise (`trusted_roles`, default `["admin"]`). */
20
+ export const DEFAULT_TRUSTED_ROLES = ["admin"];
21
+ /** `sessionStorage` key prefix of a remembered answer; the user's uid follows, so another account signing in in the same
22
+ * tab never inherits it. */
23
+ export const ADMIN_ACCESS_KEY_PREFIX = "rapidmx-admin-access:";
24
+ /** How long a remembered answer is trusted - long enough that navigating between the apps costs no request, short enough
25
+ * that a role granted or removed shows up within the sitting. */
26
+ export const ADMIN_ACCESS_TTL_MS = 30 * 60 * 1000;
27
+ /** Lookups in flight, by key, so two components asking at once make one request. */
28
+ const lookups = new Map();
29
+ /** Forgets the lookups in flight (tests). */
30
+ export function resetAdminAccessLookups() {
31
+ lookups.clear();
32
+ }
33
+ function readRemembered(key, now) {
34
+ try {
35
+ const stored = JSON.parse(sessionStorage.getItem(key) ?? "null");
36
+ if (typeof stored?.admin === "boolean" && typeof stored.at === "number" && now - stored.at >= 0 && now - stored.at < ADMIN_ACCESS_TTL_MS) {
37
+ return stored.admin;
38
+ }
39
+ }
40
+ catch {
41
+ // Unreadable, or storage unavailable - ask again.
42
+ }
43
+ return undefined;
44
+ }
45
+ function remember(key, admin, now) {
46
+ try {
47
+ sessionStorage.setItem(key, JSON.stringify({ admin, at: now }));
48
+ }
49
+ catch {
50
+ // Storage unavailable - the next page load asks again.
51
+ }
52
+ }
53
+ /**
54
+ * Resolves `true` when `userUid`'s own `User` record on auth-server carries one of `trustedRoles`, `false` when it
55
+ * doesn't, and `undefined` when that couldn't be established (unreachable, blocked by CORS, not signed in, an unexpected
56
+ * body, or a record that isn't this user's). Only a real answer is remembered; a failure is asked again on the next page.
57
+ * Never rejects.
58
+ */
59
+ export function lookUpAdminAccess(authServerUrl, userUid, trustedRoles = DEFAULT_TRUSTED_ROLES, now = Date.now()) {
60
+ const key = `${ADMIN_ACCESS_KEY_PREFIX}${userUid}`;
61
+ const remembered = readRemembered(key, now);
62
+ if (remembered !== undefined) {
63
+ return Promise.resolve(remembered);
64
+ }
65
+ const inFlight = lookups.get(key);
66
+ if (inFlight) {
67
+ return inFlight;
68
+ }
69
+ const lookup = (async () => {
70
+ try {
71
+ const user = await authApiFetch(authServerUrl, "/users/me");
72
+ if (!user || user.uid !== userUid || !Array.isArray(user.roles)) {
73
+ return undefined;
74
+ }
75
+ const admin = user.roles.some((role) => typeof role === "string" && trustedRoles.includes(role));
76
+ remember(key, admin, now);
77
+ return admin;
78
+ }
79
+ catch {
80
+ return undefined;
81
+ }
82
+ finally {
83
+ // Answered: what was learned is in storage; a failure may be asked again.
84
+ lookups.delete(key);
85
+ }
86
+ })();
87
+ lookups.set(key, lookup);
88
+ return lookup;
89
+ }
@@ -2,7 +2,7 @@ import "../../../styles/app.css";
2
2
  import React, { PropsWithChildren } from "react";
3
3
  import { NavItem } from "@rapidmx/react-shared/components/navigation/BottomTabBar.js";
4
4
  import { PluginNav, PluginNavProps } from "../../../plugins/pluginNav.js";
5
- export type AdminSection = "mailboxes" | "quarantine" | "ingestQueue" | "domains" | "auditLog" | "distributionLists" | "transportRules" | "escrowScopes" | "retentionPolicy" | "encryptionPolicy" | "mailboxPolicy" | "setup" | "dataRequests" | "plugins" | "branding";
5
+ export type AdminSection = "mailboxes" | "quarantine" | "ingestQueue" | "domains" | "auditLog" | "distributionLists" | "transportRules" | "escrowScopes" | "retentionPolicy" | "encryptionPolicy" | "mailboxPolicy" | "setup" | "dataRequests" | "plugins" | "branding" | "signingCertificates";
6
6
  /** A core `AdminSection`, or a plugin's `adminNav` item id (see `PluginNav`). */
7
7
  export type AdminShellActive = AdminSection | (string & {});
8
8
  export interface AdminShellProps extends PluginNavProps {
@@ -1,11 +1,11 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  ///////////////////////////////////////////////////////////////////////////////
3
3
  // Copyright (C) 2026 Jean-Philippe Steinmetz
4
4
  // SPDX-License-Identifier: MPL-2.0
5
5
  ///////////////////////////////////////////////////////////////////////////////
6
6
  import "../../../styles/app.css";
7
7
  import { useEffect, useState } from "react";
8
- import { HiOutlineClipboardDocumentList, HiOutlineClock, HiOutlineDocumentArrowDown, HiOutlineGlobeAlt, HiOutlineInboxStack, HiOutlineKey, HiOutlineLockClosed, HiOutlinePaintBrush, HiOutlinePuzzlePiece, HiOutlineQueueList, HiOutlineRocketLaunch, HiOutlineShieldCheck, HiOutlineShieldExclamation, HiOutlineUserGroup, HiOutlineWrenchScrewdriver, } from "react-icons/hi2";
8
+ import { HiOutlineClipboardDocumentList, HiOutlineClock, HiOutlineDocumentArrowDown, HiOutlineDocumentCheck, HiOutlineGlobeAlt, HiOutlineInboxStack, HiOutlineKey, HiOutlineLockClosed, HiOutlinePaintBrush, HiOutlinePuzzlePiece, HiOutlineQueueList, HiOutlineRocketLaunch, HiOutlineShieldCheck, HiOutlineShieldExclamation, HiOutlineUserGroup, HiOutlineWrenchScrewdriver, } from "react-icons/hi2";
9
9
  import { apiFetch, ApiRequestError } from "@rapidmx/react-shared/util/api.js";
10
10
  import { getSetupStatus } from "@rapidmx/react-shared/admin/setupApi.js";
11
11
  import { useRedirectIfUnauthenticated } from "@rapidmx/react-shared/auth/session.js";
@@ -13,7 +13,9 @@ import useBranding from "@rapidmx/react-shared/branding/useBranding.js";
13
13
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
14
14
  import Button from "@rapidmx/react-shared/components/buttons/Button.js";
15
15
  import BottomTabBar from "@rapidmx/react-shared/components/navigation/BottomTabBar.js";
16
- import { BrandingFooter } from "../../layout/BrandingChrome.js";
16
+ import { FrameBrandingFooter, useBrandingHtml } from "../../layout/BrandingChrome.js";
17
+ import RailIcon from "../../layout/RailIcon.js";
18
+ import AppearanceProvider from "../../../appearance/AppearanceProvider.js";
17
19
  import UserMenu from "../../layout/UserMenu.js";
18
20
  import { clearElevationAttempt, elevationAttemptedRecently, elevationUrl, isElevationRequired, recordElevationAttempt, } from "../elevation.js";
19
21
  import { signOutOfConsole } from "../signOut.js";
@@ -68,6 +70,12 @@ const NAV_ITEMS = [
68
70
  label: "Data Requests",
69
71
  icon: HiOutlineDocumentArrowDown,
70
72
  },
73
+ {
74
+ id: "signingCertificates",
75
+ href: "/admin/signing-certificates",
76
+ label: "Signing Certificates",
77
+ icon: HiOutlineDocumentCheck,
78
+ },
71
79
  { id: "plugins", href: "/admin/plugins", label: "Plugins", icon: HiOutlinePuzzlePiece },
72
80
  { id: "branding", href: "/admin/branding", label: "Branding", icon: HiOutlinePaintBrush },
73
81
  ];
@@ -105,6 +113,7 @@ export default function AdminShell({ active, userUid, authServerUrl, pluginNav,
105
113
  // `branding` only feeds the footer below: the admin-configured header is for the webmail and public pages, not the
106
114
  // console. `useBranding()` is still what injects the custom stylesheet and supplies the rail's icon.
107
115
  const { branding, iconSrc } = useBranding();
116
+ const footer = useBrandingHtml(branding?.footerHtml);
108
117
  useRedirectIfUnauthenticated(userUid, authServerUrl);
109
118
  useEffect(() => {
110
119
  if (!userUid) {
@@ -184,12 +193,13 @@ export default function AdminShell({ active, userUid, authServerUrl, pluginNav,
184
193
  else {
185
194
  const navItems = adminNavItems(pluginNav);
186
195
  const activeItem = [...navItems, ...OFF_RAIL_ITEMS].find((item) => item.id === active);
187
- content = (_jsx("div", { className: "min-h-screen flex flex-col bg-surface-alt", children: _jsxs("div", { className: "flex-1 flex min-h-0", children: [_jsxs("nav", { "aria-label": "Admin sections", className: "hidden md:flex w-16 shrink-0 bg-surface border-r border-border flex-col items-center py-3 gap-1", children: [_jsx("img", { src: iconSrc, width: "96", height: "96", alt: "", className: "mb-3" }), navItems.map(({ id, href, label, icon: Icon }) => (_jsx("a", { href: href, "aria-label": label, "aria-current": id === active ? "page" : undefined, title: label, className: [
196
+ content = (_jsx("div", { className: "rr-frame-bg min-h-screen flex flex-col", children: _jsxs("div", { className: "flex-1 flex min-h-0", children: [_jsxs("nav", { "aria-label": "Admin sections", className: "hidden md:flex w-16 shrink-0 bg-surface border-r border-border flex-col items-center pb-3 gap-1", children: [_jsx(RailIcon, { src: iconSrc }), navItems.map(({ id, href, label, icon: Icon }) => (_jsx("a", { href: href, "aria-label": label, "aria-current": id === active ? "page" : undefined, title: label, className: [
188
197
  "w-10 h-10 flex items-center justify-center rounded-sm",
189
198
  id === active
190
199
  ? "bg-primary/10 text-primary-dark"
191
200
  : "text-text-muted hover:bg-surface-alt hover:text-text",
192
- ].join(" "), children: _jsx(Icon, { size: 20, "aria-hidden": "true" }) }, id)))] }), _jsx(BottomTabBar, { apps: navItems, active: active }), _jsxs("div", { className: "flex-1 flex flex-col min-w-0", children: [_jsxs("header", { className: "h-16 shrink-0 bg-surface border-b border-border flex items-center justify-between gap-4 px-6", children: [_jsx("span", { className: "font-display font-bold text-lg uppercase tracking-wide", children: activeItem?.label }), _jsx(UserMenu, { userUid: userUid, authServerUrl: authServerUrl, onSignOut: handleSignOut })] }), _jsx("div", { className: "flex-1 pb-14 md:pb-0", children: _jsx("main", { className: "max-w-6xl mx-auto px-6 py-8", children: children }) })] })] }) }));
201
+ ].join(" "), children: _jsx(Icon, { size: 20, "aria-hidden": "true" }) }, id)))] }), _jsx(BottomTabBar, { apps: navItems, active: active }), _jsxs("div", { className: "flex-1 flex flex-col min-w-0", children: [_jsxs("header", { className: "rr-solid sticky top-0 z-30 h-16 shrink-0 bg-surface border-b border-border flex items-center justify-between gap-4 px-6", children: [_jsx("span", { className: "font-display font-bold text-lg uppercase tracking-wide", children: activeItem?.label }), _jsx(UserMenu, { userUid: userUid, authServerUrl: authServerUrl, onSignOut: handleSignOut })] }), _jsx("div", { id: "app-content", className: "flex-1 pb-14 md:pb-0", children: _jsx("main", { className: "max-w-6xl mx-auto px-6 py-8", children: children }) })] })] }) }));
193
202
  }
194
- return (_jsxs(_Fragment, { children: [content, _jsx(BrandingFooter, { branding: branding })] }));
203
+ // The user's own colours and background (`AppearanceProvider`), from what the last webmail page cached in this browser: the console never asks the server.
204
+ return (_jsxs(AppearanceProvider, { userUid: userUid, lookUp: false, children: [content, _jsx(FrameBrandingFooter, { parsed: footer, appTitle: [...adminNavItems(pluginNav), ...OFF_RAIL_ITEMS].find((item) => item.id === active)?.label })] }));
195
205
  }
@@ -1,12 +1,17 @@
1
1
  import React from "react";
2
2
  export interface ShareAccessCardProps {
3
3
  mailboxUid: string;
4
- /** The mailbox's owner - their own grant is shown but can't be revoked from here. */
4
+ /** The mailbox's owner: with one, only the owner can grant access to it. Without, the mailbox is shared and an
5
+ * administrator grants access here - to themselves included. */
5
6
  ownerUserUid?: string;
7
+ /** The signed-in administrator, for "Add me" on a shared mailbox. */
8
+ currentUserUid?: string;
6
9
  }
7
10
  /**
8
- * Lets an admin (or, eventually, the mailbox owner) view and edit a mailbox's delegate access — the
9
- * mechanism behind Exchange-style shared mailboxes. `records` on a mailbox's own ACL doubles as both the
10
- * owner's grant (if any) and every delegate's — see `BaseMailboxRoute`'s doc comment in `@rapidmx/restapi`.
11
+ * A mailbox's members - the mechanism behind Exchange-style shared mailboxes - through the Sharing endpoints
12
+ * (`/mail/mailboxes/:id/access`), the audited way an administrator reaches a mailbox they hold no grant on. An
13
+ * administrator can review any mailbox's members and revoke any of them; they can grant access only on a shared
14
+ * (ownerless) mailbox - themselves included, which is how it appears in their own mail client. A personal mailbox is
15
+ * shared by its owner (or by an administrator impersonating them).
11
16
  */
12
- export default function ShareAccessCard({ mailboxUid, ownerUserUid }: ShareAccessCardProps): React.JSX.Element;
17
+ export default function ShareAccessCard({ mailboxUid, ownerUserUid, currentUserUid }: ShareAccessCardProps): React.JSX.Element;