@rapidmx/web-client 0.29.0 → 0.31.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 (407) hide show
  1. package/apps/admin/audit-log/index.tsx +3 -3
  2. package/apps/admin/branding/index.tsx +3 -3
  3. package/apps/admin/data-requests/index.tsx +18 -7
  4. package/apps/admin/distribution-lists/[uid].tsx +3 -3
  5. package/apps/admin/distribution-lists/index.tsx +3 -3
  6. package/apps/admin/distribution-lists/new/index.tsx +3 -3
  7. package/apps/admin/domains/[uid].tsx +6 -5
  8. package/apps/admin/domains/index.tsx +3 -3
  9. package/apps/admin/domains/new/index.tsx +3 -3
  10. package/apps/admin/escrow-scopes/[uid].tsx +11 -5
  11. package/apps/admin/escrow-scopes/index.tsx +3 -3
  12. package/apps/admin/escrow-scopes/new/index.tsx +9 -3
  13. package/apps/admin/index.tsx +4 -4
  14. package/apps/admin/ingest-queue/index.tsx +3 -3
  15. package/apps/admin/mailboxes/[uid].tsx +15 -7
  16. package/apps/admin/quarantine/index.tsx +4 -4
  17. package/apps/admin/retention-policy/index.tsx +3 -3
  18. package/apps/admin/signing-certificates/index.tsx +3 -3
  19. package/apps/admin/transport-rules/[uid].tsx +4 -4
  20. package/apps/admin/transport-rules/index.tsx +3 -3
  21. package/apps/admin/transport-rules/new/index.tsx +3 -3
  22. package/apps/escrow/matters/[uid].tsx +26 -2
  23. package/apps/escrow/matters/new/index.tsx +4 -0
  24. package/apps/shared/calendar/calendarLiveUpdates.ts +11 -10
  25. package/apps/shared/calendar/useCalendarReminders.ts +2 -0
  26. package/apps/shared/components/admin/ActionAlert.tsx +32 -0
  27. package/apps/shared/components/admin/diagnostics/InformationPanel.tsx +1 -1
  28. package/apps/shared/components/admin/diagnostics/InstalledPlugins.tsx +1 -1
  29. package/apps/shared/components/admin/diagnostics/LogsPanel.tsx +1 -1
  30. package/apps/shared/components/admin/diagnostics/RuntimePanel.tsx +1 -1
  31. package/apps/shared/components/admin/diagnostics/SystemPanel.tsx +1 -1
  32. package/apps/shared/components/admin/distributionLists/MemberListCard.tsx +4 -4
  33. package/apps/shared/components/admin/elevation.ts +19 -0
  34. package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +58 -3
  35. package/apps/shared/components/admin/layout/AdminShell.tsx +6 -1
  36. package/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.tsx +3 -3
  37. package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +4 -4
  38. package/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.tsx +4 -4
  39. package/apps/shared/components/admin/mailboxes/ResourceSettingsCard.tsx +3 -3
  40. package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +6 -6
  41. package/apps/shared/components/admin/settings/BrandingForm.tsx +22 -9
  42. package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +4 -4
  43. package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +33 -4
  44. package/apps/shared/components/admin/settings/LoadedSettingsForm.tsx +3 -3
  45. package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +8 -3
  46. package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +3 -3
  47. package/apps/shared/components/admin/settings/PluginsManager.tsx +39 -17
  48. package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +25 -12
  49. package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +40 -7
  50. package/apps/shared/components/admin/setup/SetupWizard.tsx +8 -6
  51. package/apps/shared/components/admin/signOut.ts +3 -0
  52. package/apps/shared/components/admin/usePagedList.tsx +2 -2
  53. package/apps/shared/components/calendar/DescriptionEditor.tsx +2 -1
  54. package/apps/shared/components/calendar/EventDetails.tsx +34 -8
  55. package/apps/shared/components/calendar/EventEditor.tsx +90 -40
  56. package/apps/shared/components/calendar/EventFormParts.tsx +5 -2
  57. package/apps/shared/components/calendar/EventMatchNav.tsx +14 -2
  58. package/apps/shared/components/calendar/EventModal.tsx +25 -1
  59. package/apps/shared/components/calendar/FindATime.tsx +10 -2
  60. package/apps/shared/components/calendar/ListView.tsx +99 -25
  61. package/apps/shared/components/calendar/RecurrenceEditor.tsx +19 -8
  62. package/apps/shared/components/calendar/SplitDayView.tsx +14 -18
  63. package/apps/shared/components/calendar/TaskQuickForm.tsx +3 -2
  64. package/apps/shared/components/calendar/TimeGridView.tsx +35 -19
  65. package/apps/shared/components/calendar/gridLayout.ts +94 -0
  66. package/apps/shared/components/calendar/layout/CalendarShell.tsx +1 -1
  67. package/apps/shared/components/calendar/timePicker.ts +5 -2
  68. package/apps/shared/components/contacts/ContactCard.tsx +15 -4
  69. package/apps/shared/components/contacts/ContactDetailPane.tsx +54 -15
  70. package/apps/shared/components/contacts/ContactForm.tsx +61 -17
  71. package/apps/shared/components/contacts/ContactPhotoEditor.tsx +88 -0
  72. package/apps/shared/components/contacts/ContactPhotoField.tsx +36 -39
  73. package/apps/shared/components/contacts/layout/ContactsShell.tsx +1 -1
  74. package/apps/shared/components/contacts/participantDetails.ts +18 -1
  75. package/apps/shared/components/layout/AppShell.tsx +9 -0
  76. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +9 -4
  77. package/apps/shared/components/layout/MailboxProvisioning.tsx +31 -3
  78. package/apps/shared/components/layout/RecoveryCodeUnlock.tsx +4 -2
  79. package/apps/shared/components/mail/ConversationList.tsx +58 -8
  80. package/apps/shared/components/mail/ConversationThreadPane.tsx +1 -1
  81. package/apps/shared/components/mail/MenuButton.tsx +3 -400
  82. package/apps/shared/components/mail/MessageDetailPane.tsx +16 -13
  83. package/apps/shared/components/mail/SwipeRow.tsx +26 -4
  84. package/apps/shared/components/mail/compose/ComposeToolbar.tsx +11 -1
  85. package/apps/shared/components/mail/compose/ComposeWindow.tsx +57 -11
  86. package/apps/shared/components/mail/compose/GifPicker.tsx +10 -4
  87. package/apps/shared/components/mail/compose/RecipientInput.tsx +9 -7
  88. package/apps/shared/components/mail/compose/composeFlushRegistry.ts +14 -1
  89. package/apps/shared/components/mail/compose/encryptionRequirement.ts +5 -0
  90. package/apps/shared/components/mail/compose/quotedBody.ts +31 -6
  91. package/apps/shared/components/mail/layout/MailShell.tsx +1 -1
  92. package/apps/shared/components/mail/reading/MessageBody.tsx +13 -7
  93. package/apps/shared/components/mail/reading/bodyContent.ts +2 -0
  94. package/apps/shared/components/mail/reading/bodyHtml.ts +25 -2
  95. package/apps/shared/components/mail/writableMailboxes.ts +2 -0
  96. package/apps/shared/components/rules/RuleBuilder.tsx +11 -2
  97. package/apps/shared/components/settings/GravatarSetting.tsx +27 -0
  98. package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -1
  99. package/apps/shared/components/tasks/dueDate.ts +29 -0
  100. package/apps/shared/components/tasks/layout/TasksShell.tsx +1 -1
  101. package/apps/shared/gestures/useLongPress.ts +138 -0
  102. package/apps/shared/mail/decryptedMessages.ts +51 -2
  103. package/apps/shared/mail/listSnapshots.ts +2 -0
  104. package/apps/shared/mail/outbox/sendDecision.ts +14 -3
  105. package/apps/shared/mail/outbox/sendJob.ts +5 -0
  106. package/apps/shared/mail/useMailLiveUpdates.ts +7 -1
  107. package/apps/shared/mail/usePermanentDelete.tsx +3 -1
  108. package/apps/shared/notifications/apiErrors.ts +2 -0
  109. package/apps/shared/notifications/preferences.ts +1 -1
  110. package/apps/shared/notifications/store.ts +3 -1
  111. package/apps/shared/plugins/pluginNav.ts +4 -2
  112. package/apps/www/calendar/index.tsx +24 -12
  113. package/apps/www/contacts/[uid].tsx +6 -1
  114. package/apps/www/contacts/index.tsx +66 -12
  115. package/apps/www/index.tsx +139 -43
  116. package/apps/www/messages/[uid].tsx +8 -3
  117. package/apps/www/settings/auto-reply/index.tsx +13 -0
  118. package/apps/www/settings/blocked-senders/index.tsx +4 -0
  119. package/apps/www/settings/encryption/index.tsx +66 -4
  120. package/apps/www/settings/privacy/index.tsx +4 -1
  121. package/apps/www/settings/profile/index.tsx +12 -1
  122. package/apps/www/settings/sharing/index.tsx +11 -1
  123. package/apps/www/settings/signatures/[uid].tsx +6 -4
  124. package/apps/www/tasks/index.tsx +97 -29
  125. package/dist/apps/admin/audit-log/index.js +3 -3
  126. package/dist/apps/admin/branding/index.js +3 -3
  127. package/dist/apps/admin/data-requests/index.js +17 -7
  128. package/dist/apps/admin/distribution-lists/[uid].js +3 -3
  129. package/dist/apps/admin/distribution-lists/index.js +3 -3
  130. package/dist/apps/admin/distribution-lists/new/index.js +3 -3
  131. package/dist/apps/admin/domains/[uid].js +6 -5
  132. package/dist/apps/admin/domains/index.js +3 -3
  133. package/dist/apps/admin/domains/new/index.js +3 -3
  134. package/dist/apps/admin/escrow-scopes/[uid].js +11 -6
  135. package/dist/apps/admin/escrow-scopes/index.js +3 -3
  136. package/dist/apps/admin/escrow-scopes/new/index.js +9 -4
  137. package/dist/apps/admin/index.js +4 -4
  138. package/dist/apps/admin/ingest-queue/index.js +3 -3
  139. package/dist/apps/admin/mailboxes/[uid].js +15 -7
  140. package/dist/apps/admin/quarantine/index.js +4 -4
  141. package/dist/apps/admin/retention-policy/index.js +3 -3
  142. package/dist/apps/admin/signing-certificates/index.js +3 -3
  143. package/dist/apps/admin/transport-rules/[uid].js +4 -4
  144. package/dist/apps/admin/transport-rules/index.js +3 -3
  145. package/dist/apps/admin/transport-rules/new/index.js +3 -3
  146. package/dist/apps/escrow/matters/[uid].js +8 -3
  147. package/dist/apps/escrow/matters/new/index.js +4 -0
  148. package/dist/apps/shared/calendar/calendarLiveUpdates.d.ts +11 -7
  149. package/dist/apps/shared/calendar/calendarLiveUpdates.js +10 -9
  150. package/dist/apps/shared/calendar/useCalendarReminders.js +2 -0
  151. package/dist/apps/shared/components/admin/ActionAlert.d.ts +13 -0
  152. package/dist/apps/shared/components/admin/ActionAlert.js +24 -0
  153. package/dist/apps/shared/components/admin/diagnostics/InformationPanel.js +1 -1
  154. package/dist/apps/shared/components/admin/diagnostics/InstalledPlugins.js +1 -1
  155. package/dist/apps/shared/components/admin/diagnostics/LogsPanel.js +1 -1
  156. package/dist/apps/shared/components/admin/diagnostics/RuntimePanel.js +1 -1
  157. package/dist/apps/shared/components/admin/diagnostics/SystemPanel.js +1 -1
  158. package/dist/apps/shared/components/admin/distributionLists/MemberListCard.js +4 -4
  159. package/dist/apps/shared/components/admin/elevation.d.ts +9 -0
  160. package/dist/apps/shared/components/admin/elevation.js +16 -0
  161. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.d.ts +2 -0
  162. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +41 -1
  163. package/dist/apps/shared/components/admin/layout/AdminShell.js +2 -1
  164. package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.js +3 -3
  165. package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.js +4 -4
  166. package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.js +4 -4
  167. package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.js +3 -3
  168. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +6 -6
  169. package/dist/apps/shared/components/admin/settings/BrandingForm.js +17 -7
  170. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +4 -4
  171. package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +17 -5
  172. package/dist/apps/shared/components/admin/settings/LoadedSettingsForm.js +3 -3
  173. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +8 -3
  174. package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +3 -3
  175. package/dist/apps/shared/components/admin/settings/PluginsManager.js +26 -12
  176. package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +17 -10
  177. package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +18 -8
  178. package/dist/apps/shared/components/admin/setup/SetupWizard.js +8 -6
  179. package/dist/apps/shared/components/admin/signOut.js +3 -0
  180. package/dist/apps/shared/components/admin/usePagedList.js +2 -2
  181. package/dist/apps/shared/components/calendar/DescriptionEditor.js +2 -1
  182. package/dist/apps/shared/components/calendar/EventDetails.d.ts +3 -1
  183. package/dist/apps/shared/components/calendar/EventDetails.js +7 -4
  184. package/dist/apps/shared/components/calendar/EventEditor.js +81 -33
  185. package/dist/apps/shared/components/calendar/EventFormParts.d.ts +3 -1
  186. package/dist/apps/shared/components/calendar/EventFormParts.js +3 -3
  187. package/dist/apps/shared/components/calendar/EventMatchNav.d.ts +1 -1
  188. package/dist/apps/shared/components/calendar/EventMatchNav.js +12 -2
  189. package/dist/apps/shared/components/calendar/EventModal.js +10 -2
  190. package/dist/apps/shared/components/calendar/FindATime.js +4 -3
  191. package/dist/apps/shared/components/calendar/ListView.d.ts +10 -4
  192. package/dist/apps/shared/components/calendar/ListView.js +92 -27
  193. package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +4 -1
  194. package/dist/apps/shared/components/calendar/RecurrenceEditor.js +15 -10
  195. package/dist/apps/shared/components/calendar/SplitDayView.js +11 -17
  196. package/dist/apps/shared/components/calendar/TaskQuickForm.js +3 -2
  197. package/dist/apps/shared/components/calendar/TimeGridView.js +23 -21
  198. package/dist/apps/shared/components/calendar/gridLayout.d.ts +41 -0
  199. package/dist/apps/shared/components/calendar/gridLayout.js +83 -0
  200. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +1 -1
  201. package/dist/apps/shared/components/calendar/timePicker.js +5 -2
  202. package/dist/apps/shared/components/contacts/ContactCard.js +9 -3
  203. package/dist/apps/shared/components/contacts/ContactDetailPane.d.ts +5 -3
  204. package/dist/apps/shared/components/contacts/ContactDetailPane.js +11 -5
  205. package/dist/apps/shared/components/contacts/ContactForm.js +54 -16
  206. package/dist/apps/shared/components/contacts/ContactPhotoEditor.d.ts +21 -0
  207. package/dist/apps/shared/components/contacts/ContactPhotoEditor.js +56 -0
  208. package/dist/apps/shared/components/contacts/ContactPhotoField.d.ts +7 -4
  209. package/dist/apps/shared/components/contacts/ContactPhotoField.js +19 -21
  210. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +1 -1
  211. package/dist/apps/shared/components/contacts/participantDetails.d.ts +4 -1
  212. package/dist/apps/shared/components/contacts/participantDetails.js +17 -1
  213. package/dist/apps/shared/components/layout/AppShell.js +9 -0
  214. package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +2 -3
  215. package/dist/apps/shared/components/layout/MailboxProvisioning.d.ts +4 -1
  216. package/dist/apps/shared/components/layout/MailboxProvisioning.js +24 -2
  217. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.d.ts +4 -2
  218. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.js +4 -2
  219. package/dist/apps/shared/components/mail/ConversationList.d.ts +8 -3
  220. package/dist/apps/shared/components/mail/ConversationList.js +51 -8
  221. package/dist/apps/shared/components/mail/ConversationThreadPane.js +1 -1
  222. package/dist/apps/shared/components/mail/MenuButton.d.ts +2 -78
  223. package/dist/apps/shared/components/mail/MenuButton.js +2 -211
  224. package/dist/apps/shared/components/mail/MessageDetailPane.js +10 -6
  225. package/dist/apps/shared/components/mail/SwipeRow.d.ts +5 -1
  226. package/dist/apps/shared/components/mail/SwipeRow.js +17 -3
  227. package/dist/apps/shared/components/mail/compose/ComposeToolbar.d.ts +3 -0
  228. package/dist/apps/shared/components/mail/compose/ComposeToolbar.js +10 -1
  229. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +55 -10
  230. package/dist/apps/shared/components/mail/compose/GifPicker.js +10 -4
  231. package/dist/apps/shared/components/mail/compose/RecipientInput.js +9 -7
  232. package/dist/apps/shared/components/mail/compose/composeFlushRegistry.d.ts +7 -1
  233. package/dist/apps/shared/components/mail/compose/composeFlushRegistry.js +13 -1
  234. package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +5 -0
  235. package/dist/apps/shared/components/mail/compose/quotedBody.js +28 -5
  236. package/dist/apps/shared/components/mail/layout/MailShell.js +1 -1
  237. package/dist/apps/shared/components/mail/reading/MessageBody.js +11 -6
  238. package/dist/apps/shared/components/mail/reading/bodyContent.js +2 -0
  239. package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +5 -1
  240. package/dist/apps/shared/components/mail/reading/bodyHtml.js +23 -2
  241. package/dist/apps/shared/components/mail/writableMailboxes.js +2 -0
  242. package/dist/apps/shared/components/rules/RuleBuilder.js +9 -1
  243. package/dist/apps/shared/components/settings/GravatarSetting.d.ts +6 -0
  244. package/dist/apps/shared/components/settings/GravatarSetting.js +15 -0
  245. package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -1
  246. package/dist/apps/shared/components/tasks/dueDate.d.ts +12 -0
  247. package/dist/apps/shared/components/tasks/dueDate.js +26 -0
  248. package/dist/apps/shared/components/tasks/layout/TasksShell.js +1 -1
  249. package/dist/apps/shared/gestures/useLongPress.d.ts +41 -0
  250. package/dist/apps/shared/gestures/useLongPress.js +112 -0
  251. package/dist/apps/shared/mail/decryptedMessages.d.ts +8 -0
  252. package/dist/apps/shared/mail/decryptedMessages.js +46 -2
  253. package/dist/apps/shared/mail/listSnapshots.js +2 -0
  254. package/dist/apps/shared/mail/outbox/sendDecision.d.ts +6 -3
  255. package/dist/apps/shared/mail/outbox/sendDecision.js +13 -3
  256. package/dist/apps/shared/mail/outbox/sendJob.d.ts +1 -0
  257. package/dist/apps/shared/mail/outbox/sendJob.js +5 -1
  258. package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +3 -0
  259. package/dist/apps/shared/mail/useMailLiveUpdates.js +6 -1
  260. package/dist/apps/shared/mail/usePermanentDelete.d.ts +2 -0
  261. package/dist/apps/shared/mail/usePermanentDelete.js +1 -1
  262. package/dist/apps/shared/notifications/apiErrors.js +2 -0
  263. package/dist/apps/shared/notifications/preferences.d.ts +1 -1
  264. package/dist/apps/shared/notifications/preferences.js +1 -1
  265. package/dist/apps/shared/notifications/store.js +3 -1
  266. package/dist/apps/shared/plugins/pluginNav.d.ts +2 -1
  267. package/dist/apps/shared/plugins/pluginNav.js +4 -2
  268. package/dist/apps/www/calendar/index.js +24 -12
  269. package/dist/apps/www/contacts/[uid].js +6 -2
  270. package/dist/apps/www/contacts/index.js +62 -11
  271. package/dist/apps/www/index.js +124 -40
  272. package/dist/apps/www/messages/[uid].js +8 -3
  273. package/dist/apps/www/settings/auto-reply/index.js +13 -1
  274. package/dist/apps/www/settings/blocked-senders/index.js +4 -0
  275. package/dist/apps/www/settings/encryption/index.js +29 -5
  276. package/dist/apps/www/settings/privacy/index.js +3 -2
  277. package/dist/apps/www/settings/profile/index.js +4 -1
  278. package/dist/apps/www/settings/sharing/index.js +10 -1
  279. package/dist/apps/www/settings/signatures/[uid].js +6 -4
  280. package/dist/apps/www/tasks/index.js +94 -32
  281. package/dist/lib/admin/auditLogApi.js +2 -2
  282. package/dist/lib/admin/distributionListsApi.js +2 -2
  283. package/dist/lib/admin/domainsApi.js +2 -2
  284. package/dist/lib/admin/escrowAuditLogApi.js +2 -2
  285. package/dist/lib/admin/escrowScopesApi.js +2 -2
  286. package/dist/lib/admin/pluginsApi.d.ts +13 -2
  287. package/dist/lib/admin/pluginsApi.js +8 -0
  288. package/dist/lib/admin/retentionPolicyApi.d.ts +3 -0
  289. package/dist/lib/admin/retentionPolicyApi.js +3 -0
  290. package/dist/lib/admin/transportRulesApi.js +2 -2
  291. package/dist/lib/appearance/preferencesApi.d.ts +1 -1
  292. package/dist/lib/appearance/preferencesApi.js +3 -14
  293. package/dist/lib/auth/session.d.ts +51 -12
  294. package/dist/lib/auth/session.js +214 -38
  295. package/dist/lib/branding/brandingApi.d.ts +3 -3
  296. package/dist/lib/branding/brandingApi.js +9 -21
  297. package/dist/lib/calendar/calendarApi.js +3 -1
  298. package/dist/lib/calendar/eventSearch.js +43 -6
  299. package/dist/lib/calendar/freeBusyApi.d.ts +2 -1
  300. package/dist/lib/calendar/freeBusyApi.js +13 -5
  301. package/dist/lib/calendar/recurrence.js +51 -3
  302. package/dist/lib/calendar/useDayKey.d.ts +7 -0
  303. package/dist/lib/calendar/useDayKey.js +43 -0
  304. package/dist/lib/components/avatar/ContactAvatar.d.ts +6 -2
  305. package/dist/lib/components/avatar/ContactAvatar.js +37 -8
  306. package/dist/lib/components/avatar/ContactPhotoAvatar.d.ts +11 -0
  307. package/dist/lib/components/avatar/ContactPhotoAvatar.js +18 -0
  308. package/dist/lib/components/forms/CameraCaptureModal.d.ts +17 -0
  309. package/dist/lib/components/forms/CameraCaptureModal.js +88 -0
  310. package/dist/lib/components/forms/ImageEditBadge.d.ts +28 -0
  311. package/dist/lib/components/forms/ImageEditBadge.js +62 -0
  312. package/dist/lib/components/menus/MenuButton.d.ts +78 -0
  313. package/dist/lib/components/menus/MenuButton.js +219 -0
  314. package/dist/lib/components/navigation/BottomTabBar.d.ts +3 -1
  315. package/dist/lib/components/navigation/BottomTabBar.js +22 -3
  316. package/dist/lib/components/overlays/PopoverPortal.d.ts +2 -1
  317. package/dist/lib/components/overlays/PopoverPortal.js +3 -2
  318. package/dist/lib/components/pickers/TimeZonePicker.js +13 -1
  319. package/dist/lib/contacts/contactsApi.d.ts +5 -2
  320. package/dist/lib/contacts/contactsApi.js +16 -19
  321. package/dist/lib/contacts/gravatar.d.ts +14 -3
  322. package/dist/lib/contacts/gravatar.js +42 -5
  323. package/dist/lib/contacts/preparePhoto.d.ts +24 -0
  324. package/dist/lib/contacts/preparePhoto.js +250 -0
  325. package/dist/lib/contacts/useContactPhotoSrc.d.ts +19 -0
  326. package/dist/lib/contacts/useContactPhotoSrc.js +153 -0
  327. package/dist/lib/contacts/vcard.d.ts +3 -0
  328. package/dist/lib/contacts/vcard.js +59 -7
  329. package/dist/lib/crypto/escrowKeys.d.ts +8 -0
  330. package/dist/lib/crypto/escrowKeys.js +20 -1
  331. package/dist/lib/crypto/messageSecurity.d.ts +7 -4
  332. package/dist/lib/crypto/messageSecurity.js +23 -5
  333. package/dist/lib/crypto/passwordUnlock.d.ts +6 -1
  334. package/dist/lib/crypto/passwordUnlock.js +13 -2
  335. package/dist/lib/crypto/smime.js +12 -2
  336. package/dist/lib/mail/compose/composeQuoting.js +3 -2
  337. package/dist/lib/mail/compose/quotedImages.d.ts +6 -3
  338. package/dist/lib/mail/compose/quotedImages.js +35 -9
  339. package/dist/lib/mail/labelsApi.js +2 -2
  340. package/dist/lib/mail/mailApi.d.ts +8 -5
  341. package/dist/lib/mail/mailApi.js +73 -39
  342. package/dist/lib/mail/mailDetailHooks.js +11 -3
  343. package/dist/lib/mail/mailFilterRulesApi.js +2 -2
  344. package/dist/lib/mail/mailSignaturesApi.js +2 -2
  345. package/dist/lib/mail/mailboxImportApi.d.ts +1 -1
  346. package/dist/lib/mail/mailboxImportApi.js +3 -15
  347. package/dist/lib/mail/messageBodySanitizer.d.ts +6 -0
  348. package/dist/lib/mail/messageBodySanitizer.js +44 -1
  349. package/dist/lib/search/queryGrammar.js +12 -4
  350. package/dist/lib/search/searchTier3.d.ts +6 -1
  351. package/dist/lib/search/searchTier3.js +54 -25
  352. package/dist/lib/tasks/tasksApi.js +3 -3
  353. package/dist/lib/util/api.d.ts +26 -4
  354. package/dist/lib/util/api.js +73 -16
  355. package/dist/lib/util/apiQuery.d.ts +11 -0
  356. package/dist/lib/util/apiQuery.js +11 -0
  357. package/dist/lib/util/settleWithLimit.d.ts +5 -0
  358. package/dist/lib/util/settleWithLimit.js +25 -0
  359. package/lib/admin/auditLogApi.ts +2 -2
  360. package/lib/admin/distributionListsApi.ts +2 -2
  361. package/lib/admin/domainsApi.ts +2 -2
  362. package/lib/admin/escrowAuditLogApi.ts +2 -2
  363. package/lib/admin/escrowScopesApi.ts +2 -2
  364. package/lib/admin/pluginsApi.ts +21 -2
  365. package/lib/admin/retentionPolicyApi.ts +4 -0
  366. package/lib/admin/transportRulesApi.ts +2 -2
  367. package/lib/appearance/preferencesApi.ts +3 -14
  368. package/lib/auth/session.ts +239 -39
  369. package/lib/branding/brandingApi.ts +9 -21
  370. package/lib/calendar/calendarApi.ts +3 -1
  371. package/lib/calendar/eventSearch.ts +46 -6
  372. package/lib/calendar/freeBusyApi.ts +14 -6
  373. package/lib/calendar/recurrence.ts +59 -8
  374. package/lib/calendar/useDayKey.ts +45 -0
  375. package/lib/components/avatar/ContactAvatar.tsx +46 -8
  376. package/lib/components/avatar/ContactPhotoAvatar.tsx +26 -0
  377. package/lib/components/forms/CameraCaptureModal.tsx +133 -0
  378. package/lib/components/forms/ImageEditBadge.tsx +121 -0
  379. package/lib/components/menus/MenuButton.tsx +408 -0
  380. package/lib/components/navigation/BottomTabBar.tsx +30 -8
  381. package/lib/components/overlays/PopoverPortal.tsx +3 -2
  382. package/lib/components/pickers/TimeZonePicker.tsx +17 -1
  383. package/lib/contacts/contactsApi.ts +18 -19
  384. package/lib/contacts/gravatar.ts +47 -5
  385. package/lib/contacts/preparePhoto.ts +273 -0
  386. package/lib/contacts/useContactPhotoSrc.ts +177 -0
  387. package/lib/contacts/vcard.ts +60 -7
  388. package/lib/crypto/escrowKeys.ts +21 -1
  389. package/lib/crypto/messageSecurity.ts +33 -9
  390. package/lib/crypto/passwordUnlock.ts +14 -2
  391. package/lib/crypto/smime.ts +13 -2
  392. package/lib/mail/compose/composeQuoting.ts +3 -2
  393. package/lib/mail/compose/quotedImages.ts +39 -9
  394. package/lib/mail/labelsApi.ts +2 -2
  395. package/lib/mail/mailApi.ts +96 -39
  396. package/lib/mail/mailDetailHooks.ts +11 -3
  397. package/lib/mail/mailFilterRulesApi.ts +2 -2
  398. package/lib/mail/mailSignaturesApi.ts +2 -2
  399. package/lib/mail/mailboxImportApi.ts +9 -15
  400. package/lib/mail/messageBodySanitizer.ts +47 -1
  401. package/lib/search/queryGrammar.ts +13 -4
  402. package/lib/search/searchTier3.ts +65 -32
  403. package/lib/tasks/tasksApi.ts +3 -3
  404. package/lib/util/api.ts +99 -16
  405. package/lib/util/apiQuery.ts +13 -0
  406. package/lib/util/settleWithLimit.ts +25 -0
  407. package/package.json +1 -1
@@ -16,6 +16,7 @@ import Alert from "../../../../lib/components/feedback/Alert.js";
16
16
  import Button from "../../../../lib/components/buttons/Button.js";
17
17
  import Modal from "../../../../lib/components/overlays/Modal.js";
18
18
  import { notifyApiError } from "../../../shared/notifications/apiErrors.js";
19
+ import GravatarSetting from "../../../shared/components/settings/GravatarSetting.js";
19
20
  const NON_MAIL_FOLDER_TYPES = new Set(["calendar", "contacts", "tasks", "notes"]);
20
21
  function importFormatFromFilename(filename) {
21
22
  return filename.toLowerCase().endsWith(".pst") ? "pst" : "mbox";
@@ -49,7 +50,7 @@ function PrivacyContent({ userUid }) {
49
50
  const ownedMailboxes = mailboxes.filter((mb) => mb.ownerUserUid !== undefined && mb.ownerUserUid === userUid);
50
51
  const ownMailbox = ownedMailboxes.length === 1 ? ownedMailboxes[0] : undefined;
51
52
  const selectedIsOwn = selected.uid === ownMailbox?.uid;
52
- return (_jsx("div", { className: "flex-1 min-w-0 overflow-y-auto p-6", children: _jsxs("div", { className: "max-w-xl flex flex-col gap-8", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-lg font-bold tracking-tight mb-1", children: "Privacy & Data" }), _jsx("p", { className: "text-sm text-text-muted", children: "Export or manage the data associated with this mailbox." })] }), selectedIsOwn ? (_jsxs(_Fragment, { children: [_jsx(ExportSection, { mailboxUid: mailboxUid, mailboxLabel: `${selected.displayName} (${selected.primarySmtpAddress})` }), _jsx(ImportSection, { mailboxUid: mailboxUid }), _jsx(ErasureSection, { mailboxLabel: `${selected.displayName} (${selected.primarySmtpAddress})` })] })) : (_jsx(OwnMailboxOnlyNotice, { ownedMailboxCount: ownedMailboxes.length, ownMailboxLabel: ownMailbox && `${ownMailbox.displayName} (${ownMailbox.primarySmtpAddress})` }))] }) }));
53
+ return (_jsx("div", { className: "flex-1 min-w-0 overflow-y-auto p-6", children: _jsxs("div", { className: "max-w-xl flex flex-col gap-8", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-lg font-bold tracking-tight mb-1", children: "Privacy & Data" }), _jsx("p", { className: "text-sm text-text-muted", children: "Export or manage the data associated with this mailbox." })] }), _jsx(GravatarSetting, {}), selectedIsOwn ? (_jsxs(_Fragment, { children: [_jsx(ExportSection, { mailboxUid: mailboxUid, mailboxLabel: `${selected.displayName} (${selected.primarySmtpAddress})` }), _jsx(ImportSection, { mailboxUid: mailboxUid }), _jsx(ErasureSection, { mailboxLabel: `${selected.displayName} (${selected.primarySmtpAddress})` })] })) : (_jsx(OwnMailboxOnlyNotice, { ownedMailboxCount: ownedMailboxes.length, ownMailboxLabel: ownMailbox && `${ownMailbox.displayName} (${ownMailbox.primarySmtpAddress})` }))] }) }));
53
54
  }
54
55
  function OwnMailboxOnlyNotice({ ownedMailboxCount, ownMailboxLabel }) {
55
56
  let detail;
@@ -158,7 +159,7 @@ function ImportSection({ mailboxUid }) {
158
159
  return;
159
160
  }
160
161
  setUploading(true);
161
- uploadMailboxImport(file, { format: importFormatFromFilename(file.name), targetFolderUid })
162
+ uploadMailboxImport(file, { format: importFormatFromFilename(file.name), targetFolderUid, mailboxUid: mailboxUid }, client)
162
163
  .then(() => loadRequests())
163
164
  .catch((err) => notifyApiError(err, "Couldn't upload the file"))
164
165
  .finally(() => setUploading(false));
@@ -119,7 +119,10 @@ function ProfileContent() {
119
119
  }, "aria-describedby": "profile-time-zone-help" }), timezone !== deviceZone && (_jsxs(Button, { type: "button", variant: "text", className: "!w-auto", onClick: () => {
120
120
  setTimezone(deviceZone);
121
121
  setSaved(false);
122
- }, children: ["Use this device\u2019s time zone (", describeTimeZone(deviceZone), ")"] }))] }), zoneWasPreselected && (_jsx("p", { className: "mt-1 text-xs text-text-muted", children: "Your mailbox has no time zone chosen yet; this device\u2019s is preselected. Save to keep it." })), _jsx("p", { id: "profile-time-zone-help", className: "mt-1 text-xs text-text-muted", children: "How calendar times and reminders are shown and scheduled for this mailbox, such as when an event or a reminder falls." })] }), _jsxs(FormField, { label: "Free/busy visibility", htmlFor: "profile-free-busy", children: [_jsx("select", { id: "profile-free-busy", className: SELECT_CLASS, value: freeBusy, disabled: !canChangeFreeBusy, onChange: (e) => {
122
+ }, children: ["Use this device\u2019s time zone (", describeTimeZone(deviceZone), ")"] }))] }), zoneWasPreselected && (_jsxs("p", { className: "mt-1 text-xs text-text-muted", children: ["Your mailbox has no time zone chosen yet (it uses UTC); this device\u2019s is preselected, and Save sets it - whatever else you change.", " ", _jsx(Button, { type: "button", variant: "text", className: "!w-auto !inline !p-0 !text-xs", onClick: () => {
123
+ setTimezone(DEFAULT_TIME_ZONE);
124
+ setSaved(false);
125
+ }, children: "Keep UTC" })] })), _jsx("p", { id: "profile-time-zone-help", className: "mt-1 text-xs text-text-muted", children: "How calendar times and reminders are shown and scheduled for this mailbox, such as when an event or a reminder falls." })] }), _jsxs(FormField, { label: "Free/busy visibility", htmlFor: "profile-free-busy", children: [_jsx("select", { id: "profile-free-busy", className: SELECT_CLASS, value: freeBusy, disabled: !canChangeFreeBusy, onChange: (e) => {
123
126
  setFreeBusy(e.target.value);
124
127
  setSaved(false);
125
128
  }, "aria-describedby": "profile-free-busy-help", children: FREE_BUSY_CHOICES.map((choice) => (_jsx("option", { value: choice.value, children: choice.label }, choice.value))) }), _jsx("p", { id: "profile-free-busy-help", className: "mt-1 text-xs text-text-muted", children: FREE_BUSY_CHOICES.find((choice) => choice.value === freeBusy).help }), _jsx("p", { className: "mt-1 text-xs text-text-muted", children: "Who can see the hours you are busy when they look for a time to meet - never the title, place or guests of an event." }), !canChangeFreeBusy && (_jsx("p", { className: "mt-1 text-xs text-text-muted", children: "This mailbox is shared with you: only its owner can change who sees its free/busy times." }))] }), _jsx("div", { children: _jsx(Button, { type: "submit", loading: saving, disabled: saving, className: "!w-auto", children: "Save" }) })] })] }) }));
@@ -4,7 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
4
4
  // SPDX-License-Identifier: MPL-2.0
5
5
  ///////////////////////////////////////////////////////////////////////////////
6
6
  import { pageTitle } from "../../../shared/navigation/pageTitle.js";
7
- import { useEffect, useState } from "react";
7
+ import { useEffect, useRef, useState } from "react";
8
8
  import { ApiRequestError } from "../../../../lib/util/api.js";
9
9
  import { listMailboxAccess, removeMailboxAccess, setMailboxAccess, } from "../../../../lib/mail/mailboxAccessApi.js";
10
10
  import SettingsShell, { useSettingsShell } from "../../../shared/components/settings/layout/SettingsShell.js";
@@ -40,15 +40,24 @@ function SharingContent() {
40
40
  // `SettingsShell` only ever renders its children once `mailboxUid` has resolved (a mailbox-less caller
41
41
  // gets `<MailboxProvisioning />` instead) - same established precedent as the Labels/Focused Inbox
42
42
  // settings pages, so none of the handlers below need their own `!mailboxUid` guard.
43
+ // Only the latest request's answer is shown (a reload after a change can overtake a slower earlier one).
44
+ const latestLoad = useRef(0);
43
45
  function reload() {
46
+ const load = ++latestLoad.current;
44
47
  setStatus("loading");
45
48
  setError(null);
46
49
  listMailboxAccess(mailboxUid)
47
50
  .then((result) => {
51
+ if (load !== latestLoad.current) {
52
+ return;
53
+ }
48
54
  setMembers(result);
49
55
  setStatus("ready");
50
56
  })
51
57
  .catch((err) => {
58
+ if (load !== latestLoad.current) {
59
+ return;
60
+ }
52
61
  // A 403 here is the live, server-verified "can this caller manage this mailbox's sharing"
53
62
  // signal - the same permission threshold (ACLAction.UPDATE) this route itself requires, so
54
63
  // no separate probe call is needed to decide which state to render.
@@ -63,10 +63,7 @@ function SignatureDetailContent({ uid }) {
63
63
  setSaving(true);
64
64
  setSaved(false);
65
65
  try {
66
- if (isDefaultForNewMessages || isDefaultForReplyForward) {
67
- const existing = await listMailSignatures(mailboxUid, { limit: 100 });
68
- await clearPreviousDefaults(existing, original.uid, isDefaultForNewMessages, isDefaultForReplyForward);
69
- }
66
+ // This signature is saved first: were it refused (a 409, a 400) after the others had been cleared, the mailbox would be left with no default at all.
70
67
  const updated = await updateMailSignature({
71
68
  uid: original.uid,
72
69
  version: original.version,
@@ -76,6 +73,11 @@ function SignatureDetailContent({ uid }) {
76
73
  isDefaultForReplyForward,
77
74
  });
78
75
  setOriginal(updated);
76
+ if (isDefaultForNewMessages || isDefaultForReplyForward) {
77
+ // The signature's own mailbox, not the one the page happens to have selected.
78
+ const existing = await listMailSignatures(original.mailboxUid, { limit: 100 });
79
+ await clearPreviousDefaults(existing, original.uid, isDefaultForNewMessages, isDefaultForReplyForward);
80
+ }
79
81
  setSaved(true);
80
82
  }
81
83
  catch (err) {
@@ -5,11 +5,13 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
5
5
  ///////////////////////////////////////////////////////////////////////////////
6
6
  import { pageTitle } from "../../shared/navigation/pageTitle.js";
7
7
  import { useEffect, useMemo, useRef, useState } from "react";
8
- import { endOfWeek, isAfter, isBefore, isToday, parseISO, startOfDay } from "date-fns";
8
+ import { endOfWeek, isAfter, isBefore, isToday, startOfDay } from "date-fns";
9
9
  import { ApiRequestError } from "../../../lib/util/api.js";
10
10
  import { createTask, deleteTask, listTasks, setTaskCompleted, setTaskMyDay, } from "../../../lib/tasks/tasksApi.js";
11
11
  import { listFlaggedMessages } from "../../../lib/mail/flaggedMessages.js";
12
12
  import { useApiClient } from "../../../lib/util/apiClientContext.js";
13
+ import { useDayKey } from "../../../lib/calendar/useDayKey.js";
14
+ import { dueDateInstant, parseDueDate } from "../../shared/components/tasks/dueDate.js";
13
15
  import TasksShell, { useTasksShell } from "../../shared/components/tasks/layout/TasksShell.js";
14
16
  import TasksSidebar from "../../shared/components/tasks/TasksSidebar.js";
15
17
  import TasksToolbar from "../../shared/components/tasks/TasksToolbar.js";
@@ -27,14 +29,6 @@ const INPUT_CLASS = "text-sm py-1.5 px-2 border border-border rounded-sm bg-surf
27
29
  function TasksPage(props) {
28
30
  return (_jsx(TasksShell, { ...props, children: _jsx(TasksContent, {}) }));
29
31
  }
30
- /**
31
- * Parses a task's `dueDate` - `parseISO()` rather than `new Date()`, since `new Date("2026-06-16")` reads a
32
- * date-only value as UTC midnight, which is the *previous* local day anywhere west of UTC. A full
33
- * timestamp parses identically either way.
34
- */
35
- function parseDueDate(value) {
36
- return parseISO(value);
37
- }
38
32
  const BUCKET_ORDER = ["Overdue", "Today", "This Week", "Later", "No due date"];
39
33
  /** Buckets a task by its due date relative to `now`, matching the Outlook/To-Do convention this list mirrors. */
40
34
  function bucketFor(task, now) {
@@ -94,7 +88,10 @@ function TasksContent() {
94
88
  // Registered only while the form is rendered (`canAddTask`), so its field is there.
95
89
  useShortcut(SHORTCUTS.tasks.create, () => addTaskRef.current.focus(), { enabled: canAddTask });
96
90
  const addTaskHint = useShortcutProps("Add a task", SHORTCUTS.tasks.create, canAddTask);
91
+ // Which load is the latest: a slower, older one (another folder's, from before the mailbox changed) must not replace the list a newer one set.
92
+ const loadRun = useRef(0);
97
93
  function reload() {
94
+ const run = ++loadRun.current;
98
95
  if (!folderUid) {
99
96
  setTasks([]);
100
97
  setTruncated(false);
@@ -105,11 +102,21 @@ function TasksContent() {
105
102
  setError(null);
106
103
  return listAllPages((page) => listTasks(folderUid, { limit: LIST_PAGE_SIZE, page }, client))
107
104
  .then((result) => {
108
- setTasks(result.items);
109
- setTruncated(result.truncated);
105
+ if (run === loadRun.current) {
106
+ setTasks(result.items);
107
+ setTruncated(result.truncated);
108
+ }
109
+ })
110
+ .catch((err) => {
111
+ if (run === loadRun.current) {
112
+ setError(err instanceof ApiRequestError ? err.message : "Could not load tasks.");
113
+ }
110
114
  })
111
- .catch((err) => setError(err instanceof ApiRequestError ? err.message : "Could not load tasks."))
112
- .finally(() => setLoading(false));
115
+ .finally(() => {
116
+ if (run === loadRun.current) {
117
+ setLoading(false);
118
+ }
119
+ });
113
120
  }
114
121
  useEffect(() => {
115
122
  void reload();
@@ -122,12 +129,29 @@ function TasksContent() {
122
129
  if (view.type !== "flagged" || !mailboxUid) {
123
130
  return;
124
131
  }
132
+ // Set once the view or mailbox this load was for is no longer the one shown, so its answer is dropped.
133
+ let stale = false;
125
134
  setFlaggedLoading(true);
126
135
  setFlaggedError(null);
127
136
  listFlaggedMessages(mailboxUid, client)
128
- .then(setFlaggedMessages)
129
- .catch((err) => setFlaggedError(err instanceof ApiRequestError ? err.message : "Could not load flagged email."))
130
- .finally(() => setFlaggedLoading(false));
137
+ .then((messages) => {
138
+ if (!stale) {
139
+ setFlaggedMessages(messages);
140
+ }
141
+ })
142
+ .catch((err) => {
143
+ if (!stale) {
144
+ setFlaggedError(err instanceof ApiRequestError ? err.message : "Could not load flagged email.");
145
+ }
146
+ })
147
+ .finally(() => {
148
+ if (!stale) {
149
+ setFlaggedLoading(false);
150
+ }
151
+ });
152
+ return () => {
153
+ stale = true;
154
+ };
131
155
  }, [view, mailboxUid]);
132
156
  async function handleCreate(e) {
133
157
  e.preventDefault();
@@ -151,8 +175,8 @@ function TasksContent() {
151
175
  mailboxUid: target,
152
176
  folderUid: targetFolderUid,
153
177
  title: title.trim(),
154
- // The date input's "yyyy-MM-dd" is the user's *local* day - see `parseDueDate()`.
155
- dueDate: dueDate ? parseDueDate(dueDate).toISOString() : undefined,
178
+ // The date input's "yyyy-MM-dd" is stored as that day's UTC midnight - see `dueDateInstant()`.
179
+ dueDate: dueDate ? dueDateInstant(dueDate) : undefined,
156
180
  priority,
157
181
  }, client);
158
182
  if (target === mailboxUid) {
@@ -174,7 +198,24 @@ function TasksContent() {
174
198
  setCreating(false);
175
199
  }
176
200
  }
201
+ // The tasks whose completion is being saved. The control is disabled meanwhile, and the ref (current at once, unlike state) turns away a second click that
202
+ // lands before the page has re-rendered: it would send the same stale `version` and be refused, rolling the task back to a state the server has left.
203
+ const togglingRef = useRef(new Set());
204
+ const [toggling, setToggling] = useState(new Set());
205
+ function setTogglePending(uid, pending) {
206
+ if (pending) {
207
+ togglingRef.current.add(uid);
208
+ }
209
+ else {
210
+ togglingRef.current.delete(uid);
211
+ }
212
+ setToggling(new Set(togglingRef.current));
213
+ }
177
214
  async function handleToggle(task) {
215
+ if (togglingRef.current.has(task.uid)) {
216
+ return;
217
+ }
218
+ setTogglePending(task.uid, true);
178
219
  const nextCompleted = !task.completed;
179
220
  setTasks((prev) => prev.map((t) => (t.uid === task.uid ? { ...t, completed: nextCompleted } : t)));
180
221
  try {
@@ -185,6 +226,9 @@ function TasksContent() {
185
226
  setTasks((prev) => prev.map((t) => (t.uid === task.uid ? task : t)));
186
227
  notifyApiError(err, "Couldn't update the task");
187
228
  }
229
+ finally {
230
+ setTogglePending(task.uid, false);
231
+ }
188
232
  }
189
233
  async function handleDelete(task) {
190
234
  try {
@@ -230,8 +274,24 @@ function TasksContent() {
230
274
  }
231
275
  }, [tasks, view, userUid]);
232
276
  const checkedTasks = viewFiltered.filter((t) => checkedUids.has(t.uid));
277
+ // A bulk action sends every checked row's own version: pressed again before it finishes, it would send stale ones and raise a stream of 409s.
278
+ const bulkRunning = useRef(false);
279
+ function singleFlight(run) {
280
+ return async () => {
281
+ if (bulkRunning.current) {
282
+ return;
283
+ }
284
+ bulkRunning.current = true;
285
+ try {
286
+ await run();
287
+ }
288
+ finally {
289
+ bulkRunning.current = false;
290
+ }
291
+ };
292
+ }
233
293
  // The bulk handlers try every task and report each failure: the same failure repeated is one pop-up with a count.
234
- async function handleBulkComplete() {
294
+ const handleBulkComplete = singleFlight(async () => {
235
295
  for (const task of checkedTasks) {
236
296
  try {
237
297
  await setTaskCompleted(task, true, client);
@@ -242,8 +302,8 @@ function TasksContent() {
242
302
  }
243
303
  setCheckedUids(new Set());
244
304
  await reload();
245
- }
246
- async function handleBulkAddToMyDay() {
305
+ });
306
+ const handleBulkAddToMyDay = singleFlight(async () => {
247
307
  for (const task of checkedTasks) {
248
308
  try {
249
309
  await setTaskMyDay(task, true, client);
@@ -254,7 +314,7 @@ function TasksContent() {
254
314
  }
255
315
  setCheckedUids(new Set());
256
316
  await reload();
257
- }
317
+ });
258
318
  async function handleBulkDelete() {
259
319
  for (const task of checkedTasks) {
260
320
  try {
@@ -280,6 +340,8 @@ function TasksContent() {
280
340
  void handleBulkDelete();
281
341
  }
282
342
  }
343
+ // Re-worked when the day changes, so a page left open past midnight moves Today's tasks to Overdue.
344
+ const dayKey = useDayKey();
283
345
  const grouped = useMemo(() => {
284
346
  const now = new Date();
285
347
  const map = new Map(BUCKET_ORDER.map((b) => [b, []]));
@@ -290,17 +352,17 @@ function TasksContent() {
290
352
  map.get(bucketFor(task, now)).push(task);
291
353
  }
292
354
  return map;
293
- }, [viewFiltered]);
355
+ }, [viewFiltered, dayKey]);
294
356
  const completedTasks = viewFiltered.filter((t) => t.completed);
295
357
  return (_jsxs("div", { className: "flex-1 min-w-0 min-h-0 flex flex-col lg:flex-row", children: [_jsx(TasksSidebar, { mailboxUid: mailboxUid, tasks: tasks, userUid: userUid, active: view, onSelect: handleSelectView }), _jsxs("div", { className: "flex-1 min-w-0 flex flex-col overflow-y-auto", children: [_jsx(TasksToolbar, { viewMode: viewMode, onViewModeChange: setViewMode, selectedCount: checkedTasks.length, onComplete: handleBulkComplete, onDelete: () => setPendingDelete({ type: "bulk" }), onAddToMyDay: handleBulkAddToMyDay }), _jsxs("div", { role: "region", "aria-label": "Tasks list", className: "max-w-2xl mx-auto w-full flex flex-col gap-6 p-6", children: [_jsx("h1", { className: "text-xl font-bold uppercase tracking-wide", children: "Tasks" }), error && _jsx(Alert, { children: error }), truncated && view.type !== "flagged" && (_jsxs(Alert, { children: ["This folder has more tasks than can be shown at once - only the first ", LIST_PAGE_SIZE * MAX_LIST_PAGES, " are listed."] })), view.type === "flagged" ? (_jsx(FlaggedEmailList, { loading: flaggedLoading, error: flaggedError, messages: flaggedMessages })) : (_jsxs(_Fragment, { children: [_jsxs("form", { onSubmit: handleCreate, className: "flex flex-wrap items-center gap-2 bg-surface border border-border rounded-md p-2", children: [_jsx("input", { ref: addTaskRef, type: "text", "aria-label": "Add a task", ...addTaskHint, className: `${INPUT_CLASS} min-w-40 basis-40 flex-1`, value: title, onChange: (e) => setTitle(e.target.value), placeholder: "Add a task" }), _jsx("input", { type: "date", "aria-label": "Due date", className: INPUT_CLASS, value: dueDate, onChange: (e) => setDueDate(e.target.value) }), _jsxs("select", { "aria-label": "Priority", className: INPUT_CLASS, value: priority, onChange: (e) => setPriority(e.target.value), children: [_jsx("option", { value: "low", children: "Low" }), _jsx("option", { value: "normal", children: "Normal" }), _jsx("option", { value: "high", children: "High" })] }), writableMailboxes.length > 1 && (_jsx("select", { "aria-label": "Task mailbox", className: `${INPUT_CLASS} max-w-40`,
296
358
  // `writableMailboxes` only has entries once `mailboxUid` has resolved.
297
- value: targetMailboxUid ?? mailboxUid, onChange: (e) => setTargetMailboxUid(e.target.value), children: writableMailboxes.map((mb) => (_jsxs("option", { value: mb.uid, children: [mb.displayName, mb.ownerUserUid === userUid ? "" : " (shared)"] }, mb.uid))) })), _jsx(Button, { type: "submit", loading: creating, disabled: creating, className: "!w-auto shrink-0", children: "Add" })] }), createError && _jsx(Alert, { children: createError }), addedElsewhere && (_jsxs("p", { role: "status", className: "text-sm py-2 px-3 rounded-sm bg-surface-alt text-text", children: ["\u201C", addedElsewhere.title, "\u201D was added to", " ", mailboxes.find((mb) => mb.uid === addedElsewhere.mailboxUid)?.displayName ?? "another mailbox", ".", " ", _jsx("a", { href: `/tasks?mailboxUid=${encodeURIComponent(addedElsewhere.mailboxUid)}`, "data-router-shallow": "", "data-router-prefetch": "false", className: "font-medium text-primary-dark hover:underline", children: "View that mailbox\u2019s tasks" })] })), loading ? (_jsx("p", { className: "text-sm text-text-muted", children: "Loading\u2026" })) : viewFiltered.length === 0 ? (_jsx("p", { className: "text-sm text-text-muted", children: "No tasks yet." })) : viewMode === "grid" ? (_jsx(TaskTable, { tasks: viewFiltered, checkedUids: checkedUids, onToggleChecked: toggleChecked, onToggle: handleToggle, onDelete: requestDelete })) : (_jsxs(_Fragment, { children: [BUCKET_ORDER.map((bucket) => {
359
+ value: targetMailboxUid ?? mailboxUid, onChange: (e) => setTargetMailboxUid(e.target.value), children: writableMailboxes.map((mb) => (_jsxs("option", { value: mb.uid, children: [mb.displayName, mb.ownerUserUid === userUid ? "" : " (shared)"] }, mb.uid))) })), _jsx(Button, { type: "submit", loading: creating, disabled: creating, className: "!w-auto shrink-0", children: "Add" })] }), createError && _jsx(Alert, { children: createError }), addedElsewhere && (_jsxs("p", { role: "status", className: "text-sm py-2 px-3 rounded-sm bg-surface-alt text-text", children: ["\u201C", addedElsewhere.title, "\u201D was added to", " ", mailboxes.find((mb) => mb.uid === addedElsewhere.mailboxUid)?.displayName ?? "another mailbox", ".", " ", _jsx("a", { href: `/tasks?mailboxUid=${encodeURIComponent(addedElsewhere.mailboxUid)}`, "data-router-shallow": "", "data-router-prefetch": "false", className: "font-medium text-primary-dark hover:underline", children: "View that mailbox\u2019s tasks" })] })), loading ? (_jsx("p", { className: "text-sm text-text-muted", children: "Loading\u2026" })) : viewFiltered.length === 0 ? (_jsx("p", { className: "text-sm text-text-muted", children: "No tasks yet." })) : viewMode === "grid" ? (_jsx(TaskTable, { tasks: viewFiltered, checkedUids: checkedUids, onToggleChecked: toggleChecked, onToggle: handleToggle, toggling: toggling, onDelete: requestDelete })) : (_jsxs(_Fragment, { children: [BUCKET_ORDER.map((bucket) => {
298
360
  const items = grouped.get(bucket);
299
361
  if (items.length === 0) {
300
362
  return null;
301
363
  }
302
- return (_jsx(TaskGroup, { label: bucket, tasks: items, checkedUids: checkedUids, onToggleChecked: toggleChecked, onToggle: handleToggle, onDelete: requestDelete }, bucket));
303
- }), completedTasks.length > 0 && (_jsx(TaskGroup, { label: "Completed", tasks: completedTasks, checkedUids: checkedUids, onToggleChecked: toggleChecked, onToggle: handleToggle, onDelete: requestDelete }))] }))] }))] })] }), _jsxs(Modal, { open: pendingDelete !== null, onClose: () => setPendingDelete(null), title: "Delete tasks", children: [_jsx("p", { className: "text-sm mb-5", children: pendingDelete?.type === "single"
364
+ return (_jsx(TaskGroup, { label: bucket, tasks: items, checkedUids: checkedUids, onToggleChecked: toggleChecked, onToggle: handleToggle, toggling: toggling, onDelete: requestDelete }, bucket));
365
+ }), completedTasks.length > 0 && (_jsx(TaskGroup, { label: "Completed", tasks: completedTasks, checkedUids: checkedUids, onToggleChecked: toggleChecked, onToggle: handleToggle, toggling: toggling, onDelete: requestDelete }))] }))] }))] })] }), _jsxs(Modal, { open: pendingDelete !== null, onClose: () => setPendingDelete(null), title: "Delete tasks", children: [_jsx("p", { className: "text-sm mb-5", children: pendingDelete?.type === "single"
304
366
  ? `Delete "${pendingDelete.task.title}"?`
305
367
  : `Delete ${checkedTasks.length} selected ${checkedTasks.length === 1 ? "task" : "tasks"}?` }), _jsxs("div", { className: "flex gap-3 justify-end", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: () => setPendingDelete(null), children: "Cancel" }), _jsx(Button, { type: "button", className: "!w-auto !bg-none !bg-danger !border-danger hover:!bg-danger", onClick: handleConfirmDelete, children: "Delete" })] })] })] }));
306
368
  }
@@ -318,16 +380,16 @@ function FlaggedEmailList({ loading, error, messages }) {
318
380
  }
319
381
  /** A circular completion-toggle control matching Outlook's visual language (not a native checkbox), while
320
382
  * staying a real accessible toggle. */
321
- function CompletionToggle({ task, onToggle }) {
322
- return (_jsx("button", { type: "button", role: "checkbox", "aria-checked": task.completed, "aria-label": `Mark "${task.title}" as ${task.completed ? "not complete" : "complete"}`, onClick: () => onToggle(task), className: [
383
+ function CompletionToggle({ task, onToggle, pending }) {
384
+ return (_jsx("button", { type: "button", role: "checkbox", "aria-checked": task.completed, disabled: pending, "aria-label": `Mark "${task.title}" as ${task.completed ? "not complete" : "complete"}`, onClick: () => onToggle(task), className: [
323
385
  "w-4 h-4 rounded-full border shrink-0 flex items-center justify-center text-[10px]",
324
386
  task.completed ? "bg-primary border-primary text-white" : "border-border text-transparent",
325
387
  ].join(" "), children: "\u2713" }));
326
388
  }
327
- function TaskGroup({ label, tasks, checkedUids, onToggleChecked, onToggle, onDelete }) {
328
- return (_jsxs("div", { children: [_jsx("h2", { className: "text-xs font-bold uppercase tracking-wide text-text-muted mb-2", children: label }), _jsx("ul", { className: "bg-surface border border-border rounded-md divide-y divide-border", children: tasks.map((task) => (_jsxs("li", { className: "flex items-center gap-3 py-2.5 px-3", children: [_jsx("input", { type: "checkbox", "aria-label": `Select ${task.title}`, checked: checkedUids.has(task.uid), onChange: () => onToggleChecked(task.uid) }), _jsx(CompletionToggle, { task: task, onToggle: onToggle }), _jsx("span", { className: ["flex-1 text-sm", task.completed ? "line-through text-text-muted" : ""].join(" "), children: task.title }), task.priority !== "normal" && (_jsx("span", { className: ["text-xs font-medium", PRIORITY_CLASS[task.priority]].join(" "), children: PRIORITY_LABEL[task.priority] })), task.dueDate && (_jsx("span", { className: "text-xs text-text-muted shrink-0", children: parseDueDate(task.dueDate).toLocaleDateString() })), _jsx("button", { type: "button", onClick: () => onDelete(task), "aria-label": `Delete "${task.title}"`, className: "text-text-muted hover:text-danger text-sm px-1", children: "\u00D7" })] }, task.uid))) })] }));
389
+ function TaskGroup({ label, tasks, checkedUids, onToggleChecked, onToggle, toggling, onDelete }) {
390
+ return (_jsxs("div", { children: [_jsx("h2", { className: "text-xs font-bold uppercase tracking-wide text-text-muted mb-2", children: label }), _jsx("ul", { className: "bg-surface border border-border rounded-md divide-y divide-border", children: tasks.map((task) => (_jsxs("li", { className: "flex items-center gap-3 py-2.5 px-3", children: [_jsx("input", { type: "checkbox", "aria-label": `Select ${task.title}`, checked: checkedUids.has(task.uid), onChange: () => onToggleChecked(task.uid) }), _jsx(CompletionToggle, { task: task, onToggle: onToggle, pending: toggling.has(task.uid) }), _jsx("span", { className: ["flex-1 text-sm", task.completed ? "line-through text-text-muted" : ""].join(" "), children: task.title }), task.priority !== "normal" && (_jsx("span", { className: ["text-xs font-medium", PRIORITY_CLASS[task.priority]].join(" "), children: PRIORITY_LABEL[task.priority] })), task.dueDate && (_jsx("span", { className: "text-xs text-text-muted shrink-0", children: parseDueDate(task.dueDate).toLocaleDateString() })), _jsx("button", { type: "button", onClick: () => onDelete(task), "aria-label": `Delete "${task.title}"`, className: "text-text-muted hover:text-danger text-sm px-1", children: "\u00D7" })] }, task.uid))) })] }));
329
391
  }
330
- function TaskTable({ tasks, checkedUids, onToggleChecked, onToggle, onDelete }) {
392
+ function TaskTable({ tasks, checkedUids, onToggleChecked, onToggle, toggling, onDelete }) {
331
393
  const allChecked = tasks.length > 0 && tasks.every((t) => checkedUids.has(t.uid));
332
394
  function toggleAll() {
333
395
  if (allChecked) {
@@ -345,7 +407,7 @@ function TaskTable({ tasks, checkedUids, onToggleChecked, onToggle, onDelete })
345
407
  }
346
408
  }
347
409
  }
348
- return (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm bg-surface border border-border rounded-md", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b border-border text-left", children: [_jsx("th", { className: "w-8 px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": "Select all tasks", checked: allChecked, onChange: toggleAll }) }), _jsx("th", { className: "w-8 px-3 py-2" }), _jsx("th", { className: "px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted", children: "Title" }), _jsx("th", { className: "px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted", children: "Due Date" }), _jsx("th", { className: "px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted", children: "Importance" }), _jsx("th", { className: "w-8 px-3 py-2" })] }) }), _jsx("tbody", { children: tasks.map((task) => (_jsxs("tr", { className: "border-b border-border last:border-0", children: [_jsx("td", { className: "px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": `Select ${task.title}`, checked: checkedUids.has(task.uid), onChange: () => onToggleChecked(task.uid) }) }), _jsx("td", { className: "px-3 py-2", children: _jsx(CompletionToggle, { task: task, onToggle: onToggle }) }), _jsx("td", { className: ["px-3 py-2", task.completed ? "line-through text-text-muted" : ""].join(" "), children: task.title }), _jsx("td", { className: "px-3 py-2 text-text-muted", children: task.dueDate ? parseDueDate(task.dueDate).toLocaleDateString() : "" }), _jsx("td", { className: ["px-3 py-2", PRIORITY_CLASS[task.priority]].join(" "), children: PRIORITY_LABEL[task.priority] }), _jsx("td", { className: "px-3 py-2", children: _jsx("button", { type: "button", onClick: () => onDelete(task), "aria-label": `Delete "${task.title}"`, className: "text-text-muted hover:text-danger text-sm px-1", children: "\u00D7" }) })] }, task.uid))) })] }) }));
410
+ return (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm bg-surface border border-border rounded-md", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b border-border text-left", children: [_jsx("th", { className: "w-8 px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": "Select all tasks", checked: allChecked, onChange: toggleAll }) }), _jsx("th", { className: "w-8 px-3 py-2" }), _jsx("th", { className: "px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted", children: "Title" }), _jsx("th", { className: "px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted", children: "Due Date" }), _jsx("th", { className: "px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted", children: "Importance" }), _jsx("th", { className: "w-8 px-3 py-2" })] }) }), _jsx("tbody", { children: tasks.map((task) => (_jsxs("tr", { className: "border-b border-border last:border-0", children: [_jsx("td", { className: "px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": `Select ${task.title}`, checked: checkedUids.has(task.uid), onChange: () => onToggleChecked(task.uid) }) }), _jsx("td", { className: "px-3 py-2", children: _jsx(CompletionToggle, { task: task, onToggle: onToggle, pending: toggling.has(task.uid) }) }), _jsx("td", { className: ["px-3 py-2", task.completed ? "line-through text-text-muted" : ""].join(" "), children: task.title }), _jsx("td", { className: "px-3 py-2 text-text-muted", children: task.dueDate ? parseDueDate(task.dueDate).toLocaleDateString() : "" }), _jsx("td", { className: ["px-3 py-2", PRIORITY_CLASS[task.priority]].join(" "), children: PRIORITY_LABEL[task.priority] }), _jsx("td", { className: "px-3 py-2", children: _jsx("button", { type: "button", onClick: () => onDelete(task), "aria-label": `Delete "${task.title}"`, className: "text-text-muted hover:text-danger text-sm px-1", children: "\u00D7" }) })] }, task.uid))) })] }) }));
349
411
  }
350
412
  export default TasksPage;
351
413
  /** The tab's title: `Brand: Tasks` (see `pageTitle()`). */
@@ -9,7 +9,7 @@
9
9
  * own server-internal `recordAuditLog()`.
10
10
  */
11
11
  import { withClient } from "../util/api.js";
12
- import { buildQuery } from "../util/apiQuery.js";
12
+ import { buildQuery, sortQuery } from "../util/apiQuery.js";
13
13
  /** `client`, given below, is an explicit `ApiClient` from `createApiClient()` (e.g. one account of a multi-account
14
14
  * app) to call instead of the default global `apiFetch()` - see `withClient()`'s own doc comment in `util/api.ts`.
15
15
  * Omitted (the default), this behaves exactly as before. */
@@ -23,5 +23,5 @@ export function listAuditLog(filters = {}, params = {}, client) {
23
23
  extra.action = filters.action;
24
24
  if (filters.targetType)
25
25
  extra.targetType = filters.targetType;
26
- return withClient(client, `/mail/audit-log?${buildQuery(params, extra)}`);
26
+ return withClient(client, `/mail/audit-log?${buildQuery(params, { ...extra, ...sortQuery({ dateCreated: "DESC", uid: "ASC" }) })}`);
27
27
  }
@@ -7,12 +7,12 @@
7
7
  * trusted-role-only, no self-service creation or per-list delegated ownership (v1).
8
8
  */
9
9
  import { withClient } from "../util/api.js";
10
- import { buildQuery } from "../util/apiQuery.js";
10
+ import { buildQuery, sortQuery } from "../util/apiQuery.js";
11
11
  /** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
12
12
  * account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
13
13
  * own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
14
14
  export function listDistributionLists(params = {}, client) {
15
- return withClient(client, `/mail/distribution-lists?${buildQuery(params)}`);
15
+ return withClient(client, `/mail/distribution-lists?${buildQuery(params, sortQuery({ uid: "ASC" }))}`);
16
16
  }
17
17
  export function getDistributionList(uid, client) {
18
18
  return withClient(client, `/mail/distribution-lists/${encodeURIComponent(uid)}`);
@@ -8,12 +8,12 @@
8
8
  * admin-scoped entities.
9
9
  */
10
10
  import { withClient } from "../util/api.js";
11
- import { buildQuery } from "../util/apiQuery.js";
11
+ import { buildQuery, sortQuery } from "../util/apiQuery.js";
12
12
  /** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
13
13
  * account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
14
14
  * own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
15
15
  export function listDomains(params = {}, client) {
16
- return withClient(client, `/mail/domains?${buildQuery(params)}`);
16
+ return withClient(client, `/mail/domains?${buildQuery(params, sortQuery({ uid: "ASC" }))}`);
17
17
  }
18
18
  export function getDomain(uid, client) {
19
19
  return withClient(client, `/mail/domains/${encodeURIComponent(uid)}`);
@@ -12,12 +12,12 @@
12
12
  * enforces it and a non-trusted caller simply gets a 403 `ApiRequestError` back.
13
13
  */
14
14
  import { withClient } from "../util/api.js";
15
- import { buildQuery } from "../util/apiQuery.js";
15
+ import { buildQuery, sortQuery } from "../util/apiQuery.js";
16
16
  /** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
17
17
  * account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
18
18
  * own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
19
19
  export function listAuditLogEntries(params = {}, client) {
20
- return withClient(client, `/escrow/audit-log?${buildQuery(params)}`);
20
+ return withClient(client, `/escrow/audit-log?${buildQuery(params, sortQuery({ sequence: "DESC" }))}`);
21
21
  }
22
22
  export function getAuditLogEntry(uid, client) {
23
23
  return withClient(client, `/escrow/audit-log/${encodeURIComponent(uid)}`);
@@ -11,12 +11,12 @@
11
11
  * `apps/admin/escrow-scopes/*`, never by the holder-facing `apps/escrow` area.
12
12
  */
13
13
  import { withClient } from "../util/api.js";
14
- import { buildQuery } from "../util/apiQuery.js";
14
+ import { buildQuery, sortQuery } from "../util/apiQuery.js";
15
15
  /** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
16
16
  * account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
17
17
  * own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
18
18
  export function listEscrowScopes(params = {}, client) {
19
- return withClient(client, `/escrow/scopes?${buildQuery(params)}`);
19
+ return withClient(client, `/escrow/scopes?${buildQuery(params, sortQuery({ uid: "ASC" }))}`);
20
20
  }
21
21
  /**
22
22
  * Who a typed principal - a mailbox address, an auth-server username or e-mail alias, or a user uid - is, for the
@@ -32,6 +32,17 @@ export interface PluginManifest {
32
32
  mailboxScopedData?: boolean;
33
33
  }
34
34
  export type PluginSettingValue = string | number | boolean;
35
+ /** What `@rapidmx/restapi` answers in place of the saved value of a setting whose key names a secret (secret, password, credential, token, api key), so a
36
+ * stored API key or shared secret never reaches the console. Sent back as it was given, it means "left alone": the saved value stays. */
37
+ export interface PluginSecretSetting {
38
+ secret: true;
39
+ }
40
+ /** A saved setting as a response shows it. */
41
+ export type PluginSavedSetting = PluginSettingValue | PluginSecretSetting;
42
+ /** Whether `value` is the placeholder a saved secret setting is shown as. */
43
+ export declare function isSecretSetting(value: unknown): value is PluginSecretSetting;
44
+ /** Whether a setting's key names a secret - mirrors `@rapidmx/restapi`'s `SECRET_SETTING_KEY`, which decides what it withholds. */
45
+ export declare function isSecretSettingKey(key: string): boolean;
35
46
  /** Mirrors `@rapidmx/restapi`'s `PluginConfiguredSetting`: what the deployment's own configuration (the command line, the
36
47
  * environment, the server's defaults) says about one setting. It applies until a value is saved, which wins over it. */
37
48
  export interface PluginConfiguredSetting {
@@ -48,7 +59,7 @@ export interface Plugin {
48
59
  packageVersion: string;
49
60
  integrity?: string;
50
61
  enabled: boolean;
51
- settings: Record<string, PluginSettingValue>;
62
+ settings: Record<string, PluginSavedSetting>;
52
63
  /** For each setting the deployment's configuration provides a value for, what it says. Absent from a server that
53
64
  * predates it, which reads as nothing being configured. */
54
65
  configured?: Record<string, PluginConfiguredSetting>;
@@ -214,7 +225,7 @@ export interface UpdatePluginInput {
214
225
  packageVersion?: string;
215
226
  enabled?: boolean;
216
227
  /** A `null` value clears a setting back to the plugin's own default. */
217
- settings?: Record<string, PluginSettingValue | null>;
228
+ settings?: Record<string, PluginSavedSetting | null>;
218
229
  /** What a previewed version change or enable was confirmed to also install and enable. */
219
230
  expectedPlan?: PluginExpectedPlan;
220
231
  }
@@ -9,6 +9,14 @@
9
9
  * `getPluginStatus()`.
10
10
  */
11
11
  import { withClient } from "../util/api.js";
12
+ /** Whether `value` is the placeholder a saved secret setting is shown as. */
13
+ export function isSecretSetting(value) {
14
+ return typeof value === "object" && value !== null && value.secret === true;
15
+ }
16
+ /** Whether a setting's key names a secret - mirrors `@rapidmx/restapi`'s `SECRET_SETTING_KEY`, which decides what it withholds. */
17
+ export function isSecretSettingKey(key) {
18
+ return /secret|password|credential|token|api_?key/i.test(key);
19
+ }
12
20
  /** The `expectedPlan` to send for a change previewed as `plan`. */
13
21
  export function expectedPlanOf(plan) {
14
22
  return {
@@ -20,6 +20,9 @@ export interface RetentionPolicy {
20
20
  * minimum) — mirrored here only so client-side validation can give the same message before a round trip;
21
21
  * the server enforces this regardless. */
22
22
  export declare const MIN_AUDIT_LOG_RETENTION_DAYS = 2190;
23
+ /** The server-enforced floor for `messageRetentionDays` (30 days) - `MIN_MESSAGE_RETENTION_DAYS` in `@rapidmx/restapi`, mirrored here for the same reason as
24
+ * `MIN_AUDIT_LOG_RETENTION_DAYS`. A shorter period is refused with a 400. */
25
+ export declare const MIN_MESSAGE_RETENTION_DAYS = 30;
23
26
  /** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
24
27
  * account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
25
28
  * own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
@@ -14,6 +14,9 @@ import { withClient } from "../util/api.js";
14
14
  * minimum) — mirrored here only so client-side validation can give the same message before a round trip;
15
15
  * the server enforces this regardless. */
16
16
  export const MIN_AUDIT_LOG_RETENTION_DAYS = 2190;
17
+ /** The server-enforced floor for `messageRetentionDays` (30 days) - `MIN_MESSAGE_RETENTION_DAYS` in `@rapidmx/restapi`, mirrored here for the same reason as
18
+ * `MIN_AUDIT_LOG_RETENTION_DAYS`. A shorter period is refused with a 400. */
19
+ export const MIN_MESSAGE_RETENTION_DAYS = 30;
17
20
  /** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
18
21
  * account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
19
22
  * own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
@@ -9,12 +9,12 @@
9
9
  * natural address of its own.
10
10
  */
11
11
  import { withClient } from "../util/api.js";
12
- import { buildQuery } from "../util/apiQuery.js";
12
+ import { buildQuery, sortQuery } from "../util/apiQuery.js";
13
13
  /** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
14
14
  * account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
15
15
  * own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
16
16
  export function listTransportRules(params = {}, client) {
17
- return withClient(client, `/mail/transport-rules?${buildQuery(params)}`);
17
+ return withClient(client, `/mail/transport-rules?${buildQuery(params, sortQuery({ sequence: "ASC", uid: "ASC" }))}`);
18
18
  }
19
19
  export function getTransportRule(uid, client) {
20
20
  return withClient(client, `/mail/transport-rules/${encodeURIComponent(uid)}`);
@@ -112,7 +112,7 @@ export declare function getAppearance(client?: ApiClient): Promise<AppearancePre
112
112
  export declare function saveAppearance(update: AppearanceUpdate, client?: ApiClient): Promise<AppearancePreferences>;
113
113
  /** Stores `file` (already checked with `validateBackgroundFile()`) as the background: its raw bytes, with its own content type - which
114
114
  * bypasses `apiFetch()`, as it always sends JSON. Answers with the updated preferences. */
115
- export declare function uploadAppearanceBackground(file: Blob): Promise<AppearancePreferences>;
115
+ export declare function uploadAppearanceBackground(file: Blob, client?: ApiClient): Promise<AppearancePreferences>;
116
116
  /** Removes the stored background image. Answers with the updated preferences when the server sends them, else `undefined`. */
117
117
  export declare function deleteAppearanceBackground(client?: ApiClient): Promise<AppearancePreferences | undefined>;
118
118
  /** The URL of the stored background image `version` - what CSS `url()` and `<img src>` use. Immutable per version. */
@@ -19,7 +19,7 @@
19
19
  * Everything that comes from outside - the response, a push event, the page's server-rendered props, `localStorage` - goes through
20
20
  * `normalizeAppearance()`, which keeps only well-formed values (so a stale or hostile value can never reach a CSS declaration).
21
21
  */
22
- import { ApiRequestError, apiUrl, withClient, withCsrfHeader } from "../util/api.js";
22
+ import { ApiRequestError, apiUrl, withClient, withClientRaw } from "../util/api.js";
23
23
  export const APPEARANCE_MODES = ["system", "light", "dark"];
24
24
  export const BACKGROUND_KINDS = ["none", "color", "image"];
25
25
  export const BACKGROUND_FITS = ["cover", "contain", "tile"];
@@ -212,19 +212,8 @@ export async function saveAppearance(update, client) {
212
212
  }
213
213
  /** Stores `file` (already checked with `validateBackgroundFile()`) as the background: its raw bytes, with its own content type - which
214
214
  * bypasses `apiFetch()`, as it always sends JSON. Answers with the updated preferences. */
215
- export async function uploadAppearanceBackground(file) {
216
- const res = await fetch(apiUrl(`${PATH}/background`), {
217
- method: "POST",
218
- credentials: "include",
219
- headers: withCsrfHeader({ "Content-Type": file.type || "application/octet-stream" }),
220
- body: file,
221
- });
222
- const contentType = res.headers.get("content-type") ?? "";
223
- const body = contentType.includes("application/json") ? await res.json().catch(() => undefined) : undefined;
224
- if (!res.ok) {
225
- const message = (body && (body.message || body.error)) || res.statusText || "Upload failed.";
226
- throw new ApiRequestError(message, res.status, body?.code);
227
- }
215
+ export async function uploadAppearanceBackground(file, client) {
216
+ const body = await withClientRaw(client, `${PATH}/background`, "POST", file, file.type || "application/octet-stream");
228
217
  const stored = normalizeAppearance(body);
229
218
  if (!stored) {
230
219
  throw new ApiRequestError("The server did not return the saved appearance.", 502);