@rapidmx/web-client 0.4.0 → 0.6.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 (320) hide show
  1. package/README.md +88 -0
  2. package/apps/admin/audit-log/index.tsx +35 -8
  3. package/apps/admin/data-requests/index.tsx +480 -485
  4. package/apps/admin/distribution-lists/[uid].tsx +19 -1
  5. package/apps/admin/escrow-scopes/[uid].tsx +348 -237
  6. package/apps/admin/escrow-scopes/new/index.tsx +8 -2
  7. package/apps/admin/index.tsx +19 -1
  8. package/apps/admin/ingest-queue/index.tsx +44 -5
  9. package/apps/admin/mailboxes/[uid].tsx +211 -184
  10. package/apps/admin/quarantine/index.tsx +46 -14
  11. package/apps/admin/transport-rules/[uid].tsx +30 -1
  12. package/apps/admin/transport-rules/_transportRuleConfig.tsx +38 -2
  13. package/apps/admin/transport-rules/index.tsx +24 -6
  14. package/apps/admin/transport-rules/new/index.tsx +30 -1
  15. package/apps/escrow/_layout.tsx +2 -3
  16. package/apps/escrow/audit-log/index.tsx +196 -168
  17. package/apps/escrow/matters/[uid].tsx +621 -521
  18. package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +22 -1
  19. package/apps/shared/components/admin/layout/AdminShell.tsx +28 -9
  20. package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +152 -0
  21. package/apps/shared/components/admin/mailboxes/ResourceSettingsCard.tsx +3 -3
  22. package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +55 -11
  23. package/apps/shared/components/admin/settings/BrandingForm.tsx +15 -4
  24. package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +18 -7
  25. package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +10 -2
  26. package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +6 -0
  27. package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +45 -12
  28. package/apps/shared/components/admin/settings/PluginsManager.tsx +1235 -595
  29. package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +174 -98
  30. package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +49 -13
  31. package/apps/shared/components/admin/setup/SetupWizard.tsx +150 -34
  32. package/apps/shared/components/admin/signOut.ts +79 -0
  33. package/apps/shared/components/admin/usePagedList.tsx +129 -0
  34. package/apps/shared/components/calendar/EventModal.tsx +717 -549
  35. package/apps/shared/components/calendar/MonthView.tsx +173 -139
  36. package/apps/shared/components/calendar/RecurrenceEditor.tsx +222 -180
  37. package/apps/shared/components/calendar/SplitDayView.tsx +143 -137
  38. package/apps/shared/components/calendar/TimeGridView.tsx +244 -198
  39. package/apps/shared/components/calendar/allDay.ts +124 -0
  40. package/apps/shared/components/calendar/layout/CalendarShell.tsx +194 -192
  41. package/apps/shared/components/contacts/ContactDetailPane.tsx +100 -21
  42. package/apps/shared/components/contacts/ContactForm.tsx +377 -348
  43. package/apps/shared/components/contacts/ContactsSidebar.tsx +6 -2
  44. package/apps/shared/components/contacts/KeyChangeReview.tsx +192 -0
  45. package/apps/shared/components/contacts/contactKeys.ts +78 -0
  46. package/apps/shared/components/contacts/layout/ContactsShell.tsx +199 -197
  47. package/apps/shared/components/escrow/layout/EscrowShell.tsx +148 -134
  48. package/apps/shared/components/layout/AppShell.tsx +123 -33
  49. package/apps/shared/components/layout/BrandingChrome.tsx +48 -10
  50. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +389 -298
  51. package/apps/shared/components/layout/MailboxProvisioning.tsx +19 -3
  52. package/apps/shared/components/layout/RecoveryCodeUnlock.tsx +350 -0
  53. package/apps/shared/components/layout/UnlockPromptProvider.tsx +257 -128
  54. package/apps/shared/components/mail/ConversationThreadPane.tsx +216 -181
  55. package/apps/shared/components/mail/MessageDetailPane.tsx +1318 -630
  56. package/apps/shared/components/mail/compose/ComposeContext.tsx +20 -15
  57. package/apps/shared/components/mail/compose/ComposeWindow.tsx +854 -132
  58. package/apps/shared/components/mail/compose/composeFlushRegistry.ts +60 -0
  59. package/apps/shared/components/mail/layout/MailShell.tsx +420 -408
  60. package/apps/shared/components/mail/pinnedSigners.ts +124 -0
  61. package/apps/shared/components/mail/verificationSeals.ts +125 -0
  62. package/apps/shared/components/mail/writableMailboxes.ts +101 -0
  63. package/apps/shared/components/rules/RuleBuilder.tsx +311 -276
  64. package/apps/shared/components/settings/layout/SettingsShell.tsx +23 -9
  65. package/apps/shared/components/tasks/layout/TasksShell.tsx +201 -199
  66. package/apps/shared/mail/listAllPages.ts +39 -0
  67. package/apps/shared/plugins/pluginNav.ts +73 -0
  68. package/apps/shared/search/LocalIndexLifecycle.tsx +64 -55
  69. package/apps/shared/search/localIndexBuilder.ts +481 -179
  70. package/apps/shared/search/localIndexRpcClient.ts +231 -29
  71. package/apps/shared/search/localIndexSchema.ts +15 -6
  72. package/apps/shared/search/localIndexStorage.ts +98 -0
  73. package/apps/shared/search/localIndexVFS.ts +32 -1
  74. package/apps/shared/search/localIndexWorker.ts +648 -186
  75. package/apps/www/calendar/index.tsx +438 -417
  76. package/apps/www/contacts/[uid].tsx +21 -2
  77. package/apps/www/contacts/index.tsx +116 -12
  78. package/apps/www/index.tsx +1314 -1041
  79. package/apps/www/messages/[uid].tsx +18 -6
  80. package/apps/www/settings/auto-reply/index.tsx +134 -129
  81. package/apps/www/settings/encryption/index.tsx +739 -216
  82. package/apps/www/settings/filters/[uid].tsx +171 -155
  83. package/apps/www/settings/filters/new/index.tsx +8 -1
  84. package/apps/www/settings/focused-inbox/index.tsx +150 -148
  85. package/apps/www/settings/privacy/index.tsx +494 -436
  86. package/apps/www/settings/read-receipts/index.tsx +148 -125
  87. package/apps/www/settings/sharing/index.tsx +17 -14
  88. package/apps/www/tasks/index.tsx +638 -579
  89. package/dist/apps/admin/_404.d.ts +2 -0
  90. package/dist/apps/admin/_500.d.ts +2 -0
  91. package/dist/apps/admin/_layout.d.ts +8 -0
  92. package/dist/apps/admin/audit-log/index.d.ts +11 -0
  93. package/dist/apps/admin/audit-log/index.js +38 -8
  94. package/dist/apps/admin/branding/index.d.ts +3 -0
  95. package/dist/apps/admin/data-requests/index.d.ts +3 -0
  96. package/dist/apps/admin/data-requests/index.js +52 -72
  97. package/dist/apps/admin/distribution-lists/[uid].d.ts +7 -0
  98. package/dist/apps/admin/distribution-lists/[uid].js +1 -1
  99. package/dist/apps/admin/distribution-lists/index.d.ts +3 -0
  100. package/dist/apps/admin/distribution-lists/new/index.d.ts +3 -0
  101. package/dist/apps/admin/domains/[uid].d.ts +7 -0
  102. package/dist/apps/admin/domains/index.d.ts +3 -0
  103. package/dist/apps/admin/domains/new/index.d.ts +3 -0
  104. package/dist/apps/admin/encryption-policy/index.d.ts +3 -0
  105. package/dist/apps/admin/escrow-scopes/[uid].d.ts +21 -0
  106. package/dist/apps/admin/escrow-scopes/[uid].js +91 -12
  107. package/dist/apps/admin/escrow-scopes/index.d.ts +3 -0
  108. package/dist/apps/admin/escrow-scopes/new/index.d.ts +3 -0
  109. package/dist/apps/admin/escrow-scopes/new/index.js +8 -3
  110. package/dist/apps/admin/index.d.ts +3 -0
  111. package/dist/apps/admin/index.js +4 -1
  112. package/dist/apps/admin/ingest-queue/index.d.ts +4 -0
  113. package/dist/apps/admin/ingest-queue/index.js +24 -6
  114. package/dist/apps/admin/mailbox-policy/index.d.ts +3 -0
  115. package/dist/apps/admin/mailboxes/[uid].d.ts +7 -0
  116. package/dist/apps/admin/mailboxes/[uid].js +20 -4
  117. package/dist/apps/admin/mailboxes/new/index.d.ts +3 -0
  118. package/dist/apps/admin/plugins/index.d.ts +3 -0
  119. package/dist/apps/admin/quarantine/index.d.ts +6 -0
  120. package/dist/apps/admin/quarantine/index.js +20 -11
  121. package/dist/apps/admin/retention-policy/index.d.ts +3 -0
  122. package/dist/apps/admin/setup/index.d.ts +3 -0
  123. package/dist/apps/admin/transport-rules/[uid].d.ts +7 -0
  124. package/dist/apps/admin/transport-rules/[uid].js +18 -2
  125. package/dist/apps/admin/transport-rules/_transportRuleConfig.d.ts +21 -0
  126. package/dist/apps/admin/transport-rules/_transportRuleConfig.js +19 -0
  127. package/dist/apps/admin/transport-rules/index.d.ts +3 -0
  128. package/dist/apps/admin/transport-rules/index.js +22 -6
  129. package/dist/apps/admin/transport-rules/new/index.d.ts +3 -0
  130. package/dist/apps/admin/transport-rules/new/index.js +18 -2
  131. package/dist/apps/escrow/_layout.d.ts +10 -0
  132. package/dist/apps/escrow/_layout.js +3 -3
  133. package/dist/apps/escrow/audit-log/index.d.ts +9 -0
  134. package/dist/apps/escrow/audit-log/index.js +26 -1
  135. package/dist/apps/escrow/index.d.ts +3 -0
  136. package/dist/apps/escrow/matters/[uid].d.ts +7 -0
  137. package/dist/apps/escrow/matters/[uid].js +77 -42
  138. package/dist/apps/escrow/matters/new/index.d.ts +3 -0
  139. package/dist/apps/shared/components/admin/distributionLists/MemberListCard.d.ts +17 -0
  140. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.d.ts +32 -0
  141. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +11 -2
  142. package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +32 -0
  143. package/dist/apps/shared/components/admin/layout/AdminShell.js +15 -6
  144. package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.d.ts +12 -0
  145. package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.js +72 -0
  146. package/dist/apps/shared/components/admin/mailboxes/MailboxTable.d.ts +6 -0
  147. package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.d.ts +19 -0
  148. package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.js +3 -3
  149. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.d.ts +12 -0
  150. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +22 -4
  151. package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +9 -0
  152. package/dist/apps/shared/components/admin/settings/BrandingForm.js +15 -4
  153. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.d.ts +12 -0
  154. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +13 -7
  155. package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.d.ts +12 -0
  156. package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +7 -2
  157. package/dist/apps/shared/components/admin/settings/LoadedSettingsForm.d.ts +9 -0
  158. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.d.ts +19 -0
  159. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +6 -0
  160. package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.d.ts +10 -0
  161. package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +34 -12
  162. package/dist/apps/shared/components/admin/settings/PluginsManager.d.ts +4 -0
  163. package/dist/apps/shared/components/admin/settings/PluginsManager.js +390 -78
  164. package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.d.ts +9 -0
  165. package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +40 -11
  166. package/dist/apps/shared/components/admin/setup/EscrowSetupStep.d.ts +14 -0
  167. package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +29 -13
  168. package/dist/apps/shared/components/admin/setup/SetupWizard.d.ts +36 -0
  169. package/dist/apps/shared/components/admin/setup/SetupWizard.js +87 -21
  170. package/dist/apps/shared/components/admin/signOut.d.ts +23 -0
  171. package/dist/apps/shared/components/admin/signOut.js +75 -0
  172. package/dist/apps/shared/components/admin/usePagedList.d.ts +39 -0
  173. package/dist/apps/shared/components/admin/usePagedList.js +99 -0
  174. package/dist/apps/shared/components/calendar/CalendarListSidebar.d.ts +26 -0
  175. package/dist/apps/shared/components/calendar/EventModal.d.ts +46 -0
  176. package/dist/apps/shared/components/calendar/EventModal.js +142 -21
  177. package/dist/apps/shared/components/calendar/MonthView.d.ts +15 -0
  178. package/dist/apps/shared/components/calendar/MonthView.js +12 -5
  179. package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +20 -0
  180. package/dist/apps/shared/components/calendar/RecurrenceEditor.js +40 -5
  181. package/dist/apps/shared/components/calendar/ResourcePicker.d.ts +19 -0
  182. package/dist/apps/shared/components/calendar/SplitDayView.d.ts +28 -0
  183. package/dist/apps/shared/components/calendar/SplitDayView.js +10 -4
  184. package/dist/apps/shared/components/calendar/TimeGridView.d.ts +17 -0
  185. package/dist/apps/shared/components/calendar/TimeGridView.js +23 -7
  186. package/dist/apps/shared/components/calendar/allDay.d.ts +51 -0
  187. package/dist/apps/shared/components/calendar/allDay.js +111 -0
  188. package/dist/apps/shared/components/calendar/layout/CalendarShell.d.ts +40 -0
  189. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +3 -3
  190. package/dist/apps/shared/components/contacts/ContactDetailPane.d.ts +23 -0
  191. package/dist/apps/shared/components/contacts/ContactDetailPane.js +43 -5
  192. package/dist/apps/shared/components/contacts/ContactForm.d.ts +20 -0
  193. package/dist/apps/shared/components/contacts/ContactForm.js +32 -10
  194. package/dist/apps/shared/components/contacts/ContactsSidebar.d.ts +40 -0
  195. package/dist/apps/shared/components/contacts/ContactsSidebar.js +2 -2
  196. package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +25 -0
  197. package/dist/apps/shared/components/contacts/KeyChangeReview.d.ts +40 -0
  198. package/dist/apps/shared/components/contacts/KeyChangeReview.js +63 -0
  199. package/dist/apps/shared/components/contacts/contactKeys.d.ts +29 -0
  200. package/dist/apps/shared/components/contacts/contactKeys.js +64 -0
  201. package/dist/apps/shared/components/contacts/layout/ContactsShell.d.ts +21 -0
  202. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +2 -2
  203. package/dist/apps/shared/components/escrow/layout/EscrowShell.d.ts +25 -0
  204. package/dist/apps/shared/components/escrow/layout/EscrowShell.js +24 -5
  205. package/dist/apps/shared/components/forms/StringListField.d.ts +22 -0
  206. package/dist/apps/shared/components/layout/AppShell.d.ts +49 -0
  207. package/dist/apps/shared/components/layout/AppShell.js +97 -26
  208. package/dist/apps/shared/components/layout/BrandingChrome.d.ts +18 -0
  209. package/dist/apps/shared/components/layout/BrandingChrome.js +45 -10
  210. package/dist/apps/shared/components/layout/KeyEnrollmentGate.d.ts +51 -0
  211. package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +49 -12
  212. package/dist/apps/shared/components/layout/MailboxProvisioning.d.ts +13 -0
  213. package/dist/apps/shared/components/layout/MailboxProvisioning.js +10 -2
  214. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.d.ts +66 -0
  215. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.js +174 -0
  216. package/dist/apps/shared/components/layout/UnlockPromptProvider.d.ts +45 -0
  217. package/dist/apps/shared/components/layout/UnlockPromptProvider.js +73 -14
  218. package/dist/apps/shared/components/layout/UserMenu.d.ts +23 -0
  219. package/dist/apps/shared/components/mail/ConversationList.d.ts +11 -0
  220. package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +23 -0
  221. package/dist/apps/shared/components/mail/ConversationThreadPane.js +50 -12
  222. package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +93 -0
  223. package/dist/apps/shared/components/mail/MessageDetailPane.js +441 -36
  224. package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +58 -0
  225. package/dist/apps/shared/components/mail/compose/ComposeContext.js +9 -6
  226. package/dist/apps/shared/components/mail/compose/ComposeToolbar.d.ts +15 -0
  227. package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +37 -0
  228. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +699 -128
  229. package/dist/apps/shared/components/mail/compose/EmojiPicker.d.ts +14 -0
  230. package/dist/apps/shared/components/mail/compose/GifPicker.d.ts +13 -0
  231. package/dist/apps/shared/components/mail/compose/RichTextEditor.d.ts +34 -0
  232. package/dist/apps/shared/components/mail/compose/ScheduleSendPicker.d.ts +16 -0
  233. package/dist/apps/shared/components/mail/compose/composeFlushRegistry.d.ts +27 -0
  234. package/dist/apps/shared/components/mail/compose/composeFlushRegistry.js +50 -0
  235. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +57 -0
  236. package/dist/apps/shared/components/mail/layout/MailShell.js +13 -4
  237. package/dist/apps/shared/components/mail/pinnedSigners.d.ts +33 -0
  238. package/dist/apps/shared/components/mail/pinnedSigners.js +98 -0
  239. package/dist/apps/shared/components/mail/verificationSeals.d.ts +22 -0
  240. package/dist/apps/shared/components/mail/verificationSeals.js +112 -0
  241. package/dist/apps/shared/components/mail/writableMailboxes.d.ts +15 -0
  242. package/dist/apps/shared/components/mail/writableMailboxes.js +87 -0
  243. package/dist/apps/shared/components/rules/RuleBuilder.d.ts +70 -0
  244. package/dist/apps/shared/components/rules/RuleBuilder.js +34 -6
  245. package/dist/apps/shared/components/settings/layout/SettingsShell.d.ts +41 -0
  246. package/dist/apps/shared/components/settings/layout/SettingsShell.js +19 -9
  247. package/dist/apps/shared/components/tasks/TasksSidebar.d.ts +41 -0
  248. package/dist/apps/shared/components/tasks/TasksToolbar.d.ts +14 -0
  249. package/dist/apps/shared/components/tasks/layout/TasksShell.d.ts +24 -0
  250. package/dist/apps/shared/components/tasks/layout/TasksShell.js +2 -2
  251. package/dist/apps/shared/mail/findWellKnownFolderUid.d.ts +5 -0
  252. package/dist/apps/shared/mail/listAllPages.d.ts +17 -0
  253. package/dist/apps/shared/mail/listAllPages.js +25 -0
  254. package/dist/apps/shared/plugins/pluginNav.d.ts +39 -0
  255. package/dist/apps/shared/plugins/pluginNav.js +33 -0
  256. package/dist/apps/shared/search/LocalIndexLifecycle.d.ts +13 -0
  257. package/dist/apps/shared/search/LocalIndexLifecycle.js +58 -54
  258. package/dist/apps/shared/search/localIndexBlockCipher.d.ts +36 -0
  259. package/dist/apps/shared/search/localIndexBuilder.d.ts +85 -0
  260. package/dist/apps/shared/search/localIndexBuilder.js +292 -37
  261. package/dist/apps/shared/search/localIndexKey.d.ts +4 -0
  262. package/dist/apps/shared/search/localIndexRpcClient.d.ts +71 -0
  263. package/dist/apps/shared/search/localIndexRpcClient.js +191 -26
  264. package/dist/apps/shared/search/localIndexSchema.d.ts +111 -0
  265. package/dist/apps/shared/search/localIndexSchema.js +12 -6
  266. package/dist/apps/shared/search/localIndexSizePreference.d.ts +27 -0
  267. package/dist/apps/shared/search/localIndexStorage.d.ts +28 -0
  268. package/dist/apps/shared/search/localIndexStorage.js +86 -0
  269. package/dist/apps/shared/search/localIndexVFS.d.ts +101 -0
  270. package/dist/apps/shared/search/localIndexVFS.js +68 -40
  271. package/dist/apps/shared/search/localIndexWorker.d.ts +145 -0
  272. package/dist/apps/shared/search/localIndexWorker.js +519 -173
  273. package/dist/apps/shared/search/searchTier2.d.ts +30 -0
  274. package/dist/apps/www/_404.d.ts +2 -0
  275. package/dist/apps/www/_500.d.ts +2 -0
  276. package/dist/apps/www/_layout.d.ts +14 -0
  277. package/dist/apps/www/calendar/index.d.ts +3 -0
  278. package/dist/apps/www/calendar/index.js +28 -10
  279. package/dist/apps/www/contacts/[uid].d.ts +13 -0
  280. package/dist/apps/www/contacts/[uid].js +13 -2
  281. package/dist/apps/www/contacts/index.d.ts +3 -0
  282. package/dist/apps/www/contacts/index.js +80 -10
  283. package/dist/apps/www/index.d.ts +3 -0
  284. package/dist/apps/www/index.js +339 -108
  285. package/dist/apps/www/messages/[uid].d.ts +11 -0
  286. package/dist/apps/www/messages/[uid].js +18 -6
  287. package/dist/apps/www/settings/auto-reply/index.d.ts +4 -0
  288. package/dist/apps/www/settings/auto-reply/index.js +9 -4
  289. package/dist/apps/www/settings/encryption/index.d.ts +7 -0
  290. package/dist/apps/www/settings/encryption/index.js +514 -97
  291. package/dist/apps/www/settings/filters/[uid].d.ts +8 -0
  292. package/dist/apps/www/settings/filters/[uid].js +22 -7
  293. package/dist/apps/www/settings/filters/_mailFilterRuleConfig.d.ts +11 -0
  294. package/dist/apps/www/settings/filters/index.d.ts +4 -0
  295. package/dist/apps/www/settings/filters/new/index.d.ts +4 -0
  296. package/dist/apps/www/settings/filters/new/index.js +8 -2
  297. package/dist/apps/www/settings/focused-inbox/index.d.ts +4 -0
  298. package/dist/apps/www/settings/focused-inbox/index.js +3 -1
  299. package/dist/apps/www/settings/labels/index.d.ts +4 -0
  300. package/dist/apps/www/settings/privacy/index.d.ts +4 -0
  301. package/dist/apps/www/settings/privacy/index.js +57 -22
  302. package/dist/apps/www/settings/read-receipts/index.d.ts +4 -0
  303. package/dist/apps/www/settings/read-receipts/index.js +10 -3
  304. package/dist/apps/www/settings/sharing/index.d.ts +4 -0
  305. package/dist/apps/www/settings/sharing/index.js +7 -11
  306. package/dist/apps/www/settings/signatures/[uid].d.ts +8 -0
  307. package/dist/apps/www/settings/signatures/index.d.ts +4 -0
  308. package/dist/apps/www/settings/signatures/new/index.d.ts +4 -0
  309. package/dist/apps/www/settings/signatures/signatureDefaults.d.ts +10 -0
  310. package/dist/apps/www/tasks/index.d.ts +3 -0
  311. package/dist/apps/www/tasks/index.js +50 -10
  312. package/package.json +3 -3
  313. package/apps/shared/components/booking/AvailabilityEditor.tsx +0 -118
  314. package/apps/www/settings/booking-types/[uid].tsx +0 -300
  315. package/apps/www/settings/booking-types/index.tsx +0 -98
  316. package/apps/www/settings/booking-types/new/index.tsx +0 -212
  317. package/dist/apps/shared/components/booking/AvailabilityEditor.js +0 -45
  318. package/dist/apps/www/settings/booking-types/[uid].js +0 -129
  319. package/dist/apps/www/settings/booking-types/index.js +0 -29
  320. package/dist/apps/www/settings/booking-types/new/index.js +0 -86
@@ -1,15 +1,26 @@
1
+ import { removeLocalIndexDirectories, removeLocalIndexDirectory } from "./localIndexStorage.js";
1
2
  let worker;
2
3
  let nextRequestId = 1;
3
4
  const pending = new Map();
4
- /** Every mailboxUid `initLocalIndex()` has opened this session, not yet `destroyLocalIndex()`-ed - lets
5
- * `destroyAllLocalIndexes()` (called from `AppShell.tsx`'s sign-out, which has no mailboxUid of its own
6
- * to pass - see that call site's own comment) destroy every open index without needing one threaded
7
- * through. Mirrors `keySession.ts`'s own module-level `sessions` map in spirit - session-scoped,
8
- * intentionally never persisted. */
5
+ /** Every mailboxUid `initLocalIndex()` has opened this session, not yet `destroyLocalIndex()`-ed. Gates
6
+ * the in-session mutation helpers (`removeLocalEntity()`/`moveLocalEntity()`) so a delete/move in a
7
+ * mailbox this tab never indexed doesn't spin up the Worker just to no-op. */
9
8
  const initializedMailboxes = new Set();
9
+ let generationCounter = 0;
10
+ /** A fresh generation - see this module's doc comment. */
11
+ export function nextLocalIndexGeneration() {
12
+ generationCounter += 1;
13
+ return generationCounter;
14
+ }
15
+ /** Set once this tab (or another one) signed out - no index may be opened again in this page load. */
16
+ let signedOut = false;
17
+ export const SIGN_OUT_CHANNEL = "rapidmx-localsearch";
18
+ let channel;
10
19
  function getWorker() {
11
20
  if (!worker) {
12
- worker = new Worker(new URL("./localIndexWorker.ts", import.meta.url), { type: "module" });
21
+ // Named by its compiled `.js` file, like every other relative import: tsc copies the literal into
22
+ // `dist` unchanged, where only the `.js` file exists, and Vite maps it back to the `.ts` source.
23
+ worker = new Worker(new URL("./localIndexWorker.js", import.meta.url), { type: "module" });
13
24
  worker.addEventListener("message", (event) => {
14
25
  const entry = pending.get(event.data.id);
15
26
  if (!entry) {
@@ -23,25 +34,131 @@ function getWorker() {
23
34
  entry.reject(new Error(event.data.error));
24
35
  }
25
36
  });
37
+ // Only a tab with a Worker has connections to close when another tab signs out.
38
+ listenForSignOut();
26
39
  }
27
40
  return worker;
28
41
  }
42
+ function getChannel() {
43
+ if (!channel && typeof BroadcastChannel !== "undefined") {
44
+ channel = new BroadcastChannel(SIGN_OUT_CHANNEL);
45
+ }
46
+ return channel;
47
+ }
48
+ function listenForSignOut() {
49
+ getChannel()?.addEventListener("message", (event) => {
50
+ if (event.data?.type === "sign-out") {
51
+ void closeEverythingInThisTab();
52
+ }
53
+ });
54
+ }
55
+ /** Another tab signed out: stop every build here and close this tab's connections (which also removes their
56
+ * directories, now that nothing holds them open). Never rejects. */
57
+ async function closeEverythingInThisTab() {
58
+ signedOut = true;
59
+ initializedMailboxes.clear();
60
+ await call("destroyAll", { generation: nextLocalIndexGeneration() }).catch(() => undefined);
61
+ }
29
62
  function call(method, params) {
30
63
  const id = nextRequestId++;
31
64
  return new Promise((resolve, reject) => {
32
65
  pending.set(id, { resolve: resolve, reject });
33
- getWorker().postMessage({ id, method, params });
66
+ try {
67
+ getWorker().postMessage({ id, method, params });
68
+ }
69
+ catch (err) {
70
+ pending.delete(id);
71
+ reject(err instanceof Error ? err : new Error(String(err)));
72
+ }
34
73
  });
35
74
  }
75
+ export const PENDING_DELETIONS_KEY = "rapidmx-localsearch-pending-deletions";
76
+ /** Stands for "every index on this device" in the pending-deletions list - recorded before a sign-out starts,
77
+ * so a navigation that kills it midway still gets finished on the next load. */
78
+ const ALL_INDEXES = "*";
79
+ function readPendingDeletions() {
80
+ try {
81
+ const parsed = JSON.parse(localStorage.getItem(PENDING_DELETIONS_KEY) ?? "[]");
82
+ return Array.isArray(parsed) ? parsed.filter((uid) => typeof uid === "string") : [];
83
+ }
84
+ catch {
85
+ return [];
86
+ }
87
+ }
88
+ function writePendingDeletions(uids) {
89
+ try {
90
+ const list = [...new Set(uids)];
91
+ if (list.length === 0) {
92
+ localStorage.removeItem(PENDING_DELETIONS_KEY);
93
+ }
94
+ else {
95
+ localStorage.setItem(PENDING_DELETIONS_KEY, JSON.stringify(list));
96
+ }
97
+ }
98
+ catch {
99
+ // Storage blocked - nothing to retry from, the same as before this existed.
100
+ }
101
+ }
102
+ function updatePendingDeletions(update) {
103
+ const current = new Set(readPendingDeletions());
104
+ update(current);
105
+ writePendingDeletions(current);
106
+ }
107
+ let pendingRetry;
108
+ /** Retries every deletion an earlier page load couldn't finish. Runs once per page load (memoized), before
109
+ * this tab's first `init` - so it never races an index this tab itself opens. Never rejects. */
110
+ export function retryPendingLocalIndexDeletions() {
111
+ pendingRetry ?? (pendingRetry = (async () => {
112
+ const uids = readPendingDeletions();
113
+ if (uids.length === 0) {
114
+ return;
115
+ }
116
+ const failed = [];
117
+ if (uids.includes(ALL_INDEXES)) {
118
+ const result = await removeLocalIndexDirectories().catch(() => ({ failed: [ALL_INDEXES] }));
119
+ failed.push(...result.failed);
120
+ }
121
+ else {
122
+ for (const uid of uids) {
123
+ await removeLocalIndexDirectory(uid).catch(() => failed.push(uid));
124
+ }
125
+ }
126
+ writePendingDeletions(failed);
127
+ })());
128
+ return pendingRetry;
129
+ }
36
130
  export async function initLocalIndex(params) {
131
+ await retryPendingLocalIndexDeletions();
132
+ if (signedOut) {
133
+ throw new Error("Signed out - the local search index is unavailable in this tab.");
134
+ }
37
135
  await call("init", params);
38
136
  initializedMailboxes.add(params.mailboxUid);
39
137
  }
40
- export function indexLocalEntities(mailboxUid, entities) {
41
- return call("indexEntities", { mailboxUid, entities });
138
+ export function indexLocalEntities(mailboxUid, entities, generation) {
139
+ return call("indexEntities", { mailboxUid, entities, generation });
140
+ }
141
+ /** Drops one message from this session's local index (a delete). A no-op for a mailbox not indexed in
142
+ * this tab - a later build pass prunes it instead. Never rejects: best-effort housekeeping. */
143
+ export async function removeLocalEntity(mailboxUid, entityUid) {
144
+ if (!initializedMailboxes.has(mailboxUid)) {
145
+ return;
146
+ }
147
+ await call("removeEntity", { mailboxUid, entityUid }).catch(() => undefined);
148
+ }
149
+ /** Re-points one indexed message at its new folder (archive, cancel-scheduled-send) so `folder:`-scoped
150
+ * local searches stay correct. Same no-op/never-rejects rules as `removeLocalEntity()`. */
151
+ export async function moveLocalEntity(mailboxUid, entityUid, folderUid) {
152
+ if (!initializedMailboxes.has(mailboxUid)) {
153
+ return;
154
+ }
155
+ await call("moveEntity", { mailboxUid, entityUid, folderUid }).catch(() => undefined);
156
+ }
157
+ export function getIndexedVersions(mailboxUid, entityUids) {
158
+ return call("indexedVersions", { mailboxUid, entityUids });
42
159
  }
43
- export function removeLocalEntity(mailboxUid, entityUid) {
44
- return call("removeEntity", { mailboxUid, entityUid });
160
+ export function pruneLocalEntities(mailboxUid, keepEntityUids, since, options = {}) {
161
+ return call("pruneEntities", { mailboxUid, keepEntityUids, since, ...options });
45
162
  }
46
163
  export function searchLocal(mailboxUid, parsed, limit, offset = 0) {
47
164
  return call("search", { mailboxUid, parsed, limit, offset });
@@ -49,30 +166,78 @@ export function searchLocal(mailboxUid, parsed, limit, offset = 0) {
49
166
  export function getLocalCoverage(mailboxUid) {
50
167
  return call("coverage", mailboxUid);
51
168
  }
52
- export function setLocalIndexWindow(mailboxUid, timeFloorMonths, byteBudgetBytes) {
53
- return call("setWindow", { mailboxUid, timeFloorMonths, byteBudgetBytes });
169
+ export function setLocalIndexWindow(mailboxUid, timeFloorMonths, byteBudgetBytes, generation) {
170
+ return call("setWindow", { mailboxUid, timeFloorMonths, byteBudgetBytes, generation });
54
171
  }
55
- export function setLocalIndexBuilding(mailboxUid, building) {
56
- return call("setBuilding", { mailboxUid, building });
172
+ export function setLocalIndexBuilding(mailboxUid, building, completion = { complete: false }) {
173
+ return call("setBuilding", { mailboxUid, building, ...completion });
57
174
  }
58
175
  /** Destroys one mailbox's local index (both the SQLite connection and its on-disk OPFS storage) - spec
59
- * §11 "MUST be destroyed on the same events that destroy private keys." Never throws: called from
60
- * lifecycle hooks (idle timeout, logout, the manual "destroy keys now" button) where a destroy failure
61
- * shouldn't block the key-destruction it's piggybacking on. */
176
+ * §11 "MUST be destroyed on the same events that destroy private keys." Never rejects (it's called from
177
+ * lifecycle hooks where a failure mustn't block key destruction) but resolves `false` - and logs why -
178
+ * when the index could not be removed, e.g. because another tab still has it open; the deletion is then
179
+ * retried on the next page load. */
62
180
  export async function destroyLocalIndex(mailboxUid) {
181
+ updatePendingDeletions((current) => current.add(mailboxUid));
63
182
  try {
64
- await call("destroy", mailboxUid);
183
+ await call("destroy", { mailboxUid, generation: nextLocalIndexGeneration() });
184
+ updatePendingDeletions((current) => current.delete(mailboxUid));
185
+ return true;
65
186
  }
66
- catch {
67
- // Best-effort - see this function's own doc comment.
187
+ catch (err) {
188
+ // `call()` only ever rejects with an Error.
189
+ console.warn(`Could not destroy the local search index for mailbox ${mailboxUid}: ${err.message}`);
190
+ return false;
68
191
  }
69
192
  finally {
70
193
  initializedMailboxes.delete(mailboxUid);
71
194
  }
72
195
  }
73
- /** Destroys every mailbox's local index this session has opened - for a caller (`AppShell.tsx`'s
74
- * sign-out) that has no specific mailboxUid of its own, the same "clear everything" shape
75
- * `destroyUnlockedKeys()` itself offers when called with no argument. */
76
- export async function destroyAllLocalIndexes() {
77
- await Promise.all([...initializedMailboxes].map((mailboxUid) => destroyLocalIndex(mailboxUid)));
196
+ /** How long sign-out waits for local index destruction before navigating anyway. */
197
+ export const DESTROY_ALL_TIMEOUT_MS = 3000;
198
+ /**
199
+ * Destroys **every** local index on this device - not just the mailboxes this page load opened (sign-out
200
+ * from Calendar, say, never opened any), by enumerating OPFS for the index directory prefix - and tells every
201
+ * other tab to close its own connections. Closes this tab's own open connections through the Worker first
202
+ * (only if one was ever spawned - there's nothing to close otherwise, and booting one just to delete files
203
+ * would be waste), then removes the directories. Anything left behind is retried on the next load. Resolves
204
+ * `true` when everything was removed within `timeoutMs`; never rejects.
205
+ */
206
+ export async function destroyAllLocalIndexes(timeoutMs = DESTROY_ALL_TIMEOUT_MS) {
207
+ signedOut = true;
208
+ writePendingDeletions([ALL_INDEXES]);
209
+ try {
210
+ getChannel()?.postMessage({ type: "sign-out" });
211
+ }
212
+ catch {
213
+ // A closed/unsupported channel only loses the cross-tab notification.
214
+ }
215
+ const work = (async () => {
216
+ let ok = true;
217
+ if (worker) {
218
+ const { failed } = await call("destroyAll", { generation: nextLocalIndexGeneration() }).catch(() => ({
219
+ failed: ["(worker)"],
220
+ }));
221
+ ok = failed.length === 0;
222
+ }
223
+ initializedMailboxes.clear();
224
+ const { failed } = await removeLocalIndexDirectories();
225
+ writePendingDeletions(failed);
226
+ return ok && failed.length === 0;
227
+ })().catch(() => false);
228
+ let timer;
229
+ const timeout = new Promise((resolve) => {
230
+ timer = setTimeout(() => resolve(false), timeoutMs);
231
+ });
232
+ const result = await Promise.race([work, timeout]);
233
+ clearTimeout(timer);
234
+ if (!result) {
235
+ console.warn("Could not destroy every local search index before signing out.");
236
+ }
237
+ return result;
238
+ }
239
+ /** Removes local indexes for mailboxes outside `accessibleMailboxUids` - e.g. left behind by a different
240
+ * user who signed in on this device without signing out. Never rejects. */
241
+ export async function pruneInaccessibleLocalIndexes(accessibleMailboxUids) {
242
+ await removeLocalIndexDirectories(new Set(accessibleMailboxUids)).catch(() => undefined);
78
243
  }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * The Tier 2 local index's SQLite schema (`specs/search.md` §13). One database per mailbox (see
3
+ * `localIndexWorker.ts`), holding both the FTS5 index and the metadata cache in the same store - the
4
+ * spec's own rationale for choosing SQLite over an in-memory JS index in the first place ("the index
5
+ * database also holds the decrypted metadata cache... so there is one local store rather than two").
6
+ *
7
+ * Bumping `SCHEMA_VERSION` is how a schema change invalidates every existing local index (§11
8
+ * "Invalidation... on schema version change") - `localIndexWorker.ts` compares it against `meta`'s
9
+ * stored value on open and discards+rebuilds on a mismatch, the same path a corruption/GCM-auth-failure
10
+ * takes.
11
+ */
12
+ /** Bump whenever `CREATE_SCHEMA_SQL` changes in a way existing on-disk databases can't be reconciled
13
+ * with in place. A mismatch deletes and recreates the whole database file (not just its rows), so
14
+ * creation-time-only settings like `auto_vacuum` also apply to upgraded indexes.
15
+ *
16
+ * 2 - added `entities.entity_version` (incremental rebuild skip) and `auto_vacuum=INCREMENTAL`. */
17
+ export declare const SCHEMA_VERSION = 2;
18
+ /**
19
+ * `entities` is the real row store (metadata + the plaintext content fields), `entities_fts` is an FTS5
20
+ * *external content* table over it (`content='entities'`) - the standard SQLite pattern for keeping one
21
+ * copy of the text instead of duplicating it into the FTS5 shadow tables, kept in sync via the three
22
+ * triggers below (SQLite's own documented pattern for external-content FTS5 tables; there is no
23
+ * "ON CONFLICT UPDATE re-index" primitive, so update is modeled as delete-then-reinsert into the FTS
24
+ * index specifically, not into `entities` itself).
25
+ *
26
+ * Column order in `entities_fts` (`subject, participants, body, attachment_text`) is load-bearing: every
27
+ * `bm25(entities_fts, 3.0, 2.0, 1.0, 1.0)` call elsewhere in this module family assumes that exact
28
+ * positional order, matching `searchScoring.ts`'s `SEARCH_FIELD_WEIGHTS` (`subject: 3, participants: 2,
29
+ * body: 1, attachmentText: 1`) so Tier 2's local ranking agrees with the Tier 1/Tier 3 re-scoring the
30
+ * spec requires for one consistent ordering across tiers (§7).
31
+ */
32
+ export declare const CREATE_SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS meta (\n key TEXT PRIMARY KEY,\n value TEXT\n);\n\nCREATE TABLE IF NOT EXISTS entities (\n rowid INTEGER PRIMARY KEY,\n entity_type TEXT NOT NULL,\n entity_uid TEXT NOT NULL UNIQUE,\n mailbox_uid TEXT NOT NULL,\n folder_uid TEXT,\n date_for_sort TEXT NOT NULL,\n participants TEXT,\n flags TEXT,\n has_attachments INTEGER NOT NULL DEFAULT 0,\n subject TEXT,\n body TEXT,\n attachment_text TEXT,\n byte_size INTEGER NOT NULL DEFAULT 0,\n entity_version TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_entities_date ON entities(date_for_sort);\nCREATE INDEX IF NOT EXISTS idx_entities_mailbox ON entities(mailbox_uid);\n\nCREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(\n subject, participants, body, attachment_text,\n content='entities', content_rowid='rowid', tokenize='unicode61'\n);\n\nCREATE TRIGGER IF NOT EXISTS entities_ai AFTER INSERT ON entities BEGIN\n INSERT INTO entities_fts(rowid, subject, participants, body, attachment_text)\n VALUES (new.rowid, new.subject, new.participants, new.body, new.attachment_text);\nEND;\n\nCREATE TRIGGER IF NOT EXISTS entities_ad AFTER DELETE ON entities BEGIN\n INSERT INTO entities_fts(entities_fts, rowid, subject, participants, body, attachment_text)\n VALUES ('delete', old.rowid, old.subject, old.participants, old.body, old.attachment_text);\nEND;\n\nCREATE TRIGGER IF NOT EXISTS entities_au AFTER UPDATE ON entities BEGIN\n INSERT INTO entities_fts(entities_fts, rowid, subject, participants, body, attachment_text)\n VALUES ('delete', old.rowid, old.subject, old.participants, old.body, old.attachment_text);\n INSERT INTO entities_fts(rowid, subject, participants, body, attachment_text)\n VALUES (new.rowid, new.subject, new.participants, new.body, new.attachment_text);\nEND;\n";
33
+ /** The exact `bm25()` weight arguments every ranked query against `entities_fts` MUST pass, in column
34
+ * order - see this module's own doc comment on why the order is load-bearing. Centralized here so a
35
+ * future column reorder can't silently desync a query building its own literal weight list. */
36
+ export declare const BM25_WEIGHTS_SQL = "3.0, 2.0, 1.0, 1.0";
37
+ /** One message's decrypted content, ready to index - `localIndexBuilder.ts`'s own output shape, built
38
+ * from `Message` + the recovered `MessageSecurityResult` fields the same way `searchTier3.ts` already
39
+ * derives them for its own per-candidate matching. */
40
+ export interface LocalIndexEntity {
41
+ entityType: "message";
42
+ entityUid: string;
43
+ mailboxUid: string;
44
+ folderUid?: string;
45
+ /** ISO 8601 - compares correctly as plain text since every value here is UTC. */
46
+ dateForSort: string;
47
+ participants: string;
48
+ /** Comma-delimited, leading/trailing commas included (`,read,flagged,`) - simplest possible substring
49
+ * match (`flags LIKE '%,read,%'`) without needing SQLite's JSON1 extension compiled in. */
50
+ flags: string;
51
+ hasAttachments: boolean;
52
+ subject?: string;
53
+ body?: string;
54
+ attachmentText?: string;
55
+ /** Rough on-disk cost of this entity's own content, in bytes - what `localIndexBuilder.ts`'s
56
+ * byte-budget accounting (spec §11) sums against the configured budget. */
57
+ byteSize: number;
58
+ /** Opaque change marker for the source entity (the builder uses the message's `version` plus its
59
+ * folder) - lets a rebuild skip re-fetching/decrypting anything already indexed unchanged. */
60
+ entityVersion?: string;
61
+ }
62
+ /** `entity_uid` upsert - `ON CONFLICT` (SQLite's UPSERT syntax) rather than a separate delete-then-insert,
63
+ * so re-indexing an already-present message (a flag changed, a folder move) updates it in place and the
64
+ * `entities_au` trigger keeps `entities_fts` in sync automatically. */
65
+ export declare const UPSERT_ENTITY_SQL = "\nINSERT INTO entities (entity_type, entity_uid, mailbox_uid, folder_uid, date_for_sort, participants, flags, has_attachments, subject, body, attachment_text, byte_size, entity_version)\nVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\nON CONFLICT(entity_uid) DO UPDATE SET\n folder_uid = excluded.folder_uid, date_for_sort = excluded.date_for_sort, participants = excluded.participants,\n flags = excluded.flags, has_attachments = excluded.has_attachments, subject = excluded.subject,\n body = excluded.body, attachment_text = excluded.attachment_text, byte_size = excluded.byte_size,\n entity_version = excluded.entity_version\n";
66
+ /** Bind values for `UPSERT_ENTITY_SQL`, in column order - kept alongside it so the two can never drift
67
+ * out of sync with each other. */
68
+ export declare function entityBindValues(entity: LocalIndexEntity): (string | number)[];
69
+ /** A parsed query's structured (non-free-text) fields - the subset of `ParsedSearchQuery`
70
+ * (`react-shared`'s `queryGrammar.ts`) this module's predicate builder reads. Typed locally rather than
71
+ * importing `ParsedSearchQuery` itself so this Worker-bundled module has no dependency on `@rapidmx/
72
+ * react-shared` beyond what it actually uses - `localIndexBuilder.ts`/`searchTier2.ts` (main-thread side)
73
+ * pass the real `ParsedSearchQuery` in, which structurally satisfies this. */
74
+ export interface LocalSearchPredicateFields {
75
+ from?: string;
76
+ to?: string;
77
+ cc?: string;
78
+ subject?: string;
79
+ hasAttachment?: boolean;
80
+ before?: Date;
81
+ after?: Date;
82
+ folderUid?: string;
83
+ flags?: string[];
84
+ }
85
+ /**
86
+ * Builds the SQL `WHERE` predicate (and its bind params) for every *structured* operator this local
87
+ * index can actually evaluate. **Known simplification**: unlike Tier 1's server-side `SearchDocument`
88
+ * (which splits `from`/`to`/`cc` into distinct fields - confirmed already implemented server-side), this
89
+ * local schema keeps only the combined `participants` field (see `CREATE_SCHEMA_SQL`'s own doc comment) -
90
+ * `from:`/`to:`/`cc:` are therefore evaluated here as a substring match against that combined field
91
+ * rather than a precise per-role match. Reasonable for a bounded, best-effort recent-window cache
92
+ * (Tier 1 already serves the precise version for anything it indexes), but a real gap if Tier 2 is later
93
+ * extended to distinguish them - flagged here rather than left silently approximate.
94
+ */
95
+ export declare function buildSearchPredicates(parsed: LocalSearchPredicateFields, mailboxUid: string): {
96
+ where: string;
97
+ params: (string | number)[];
98
+ };
99
+ /**
100
+ * Builds the FTS5 `MATCH` expression for a parsed query's free-text and `subject:` portions, or
101
+ * `undefined` when there's nothing to match on text at all (a pure operator/structured-filter query -
102
+ * `buildSearchPredicates()`'s `WHERE` clause alone already narrows that case correctly, no `MATCH`
103
+ * needed). `parsed.text` is passed through close to verbatim (quoted phrases, `OR`, `-` negation - FTS5's
104
+ * own query syntax supports the same shape `queryGrammar.ts`'s own doc comment says every provider's
105
+ * free-text engine is expected to), wrapped only enough to combine it with a `subject:`-scoped clause
106
+ * when both are present.
107
+ */
108
+ export declare function buildMatchExpression(parsed: {
109
+ text: string;
110
+ subject?: string;
111
+ }): string | undefined;
@@ -14,8 +14,11 @@
14
14
  * takes.
15
15
  */
16
16
  /** Bump whenever `CREATE_SCHEMA_SQL` changes in a way existing on-disk databases can't be reconciled
17
- * with in place. */
18
- export const SCHEMA_VERSION = 1;
17
+ * with in place. A mismatch deletes and recreates the whole database file (not just its rows), so
18
+ * creation-time-only settings like `auto_vacuum` also apply to upgraded indexes.
19
+ *
20
+ * 2 - added `entities.entity_version` (incremental rebuild skip) and `auto_vacuum=INCREMENTAL`. */
21
+ export const SCHEMA_VERSION = 2;
19
22
  /**
20
23
  * `entities` is the real row store (metadata + the plaintext content fields), `entities_fts` is an FTS5
21
24
  * *external content* table over it (`content='entities'`) - the standard SQLite pattern for keeping one
@@ -49,7 +52,8 @@ CREATE TABLE IF NOT EXISTS entities (
49
52
  subject TEXT,
50
53
  body TEXT,
51
54
  attachment_text TEXT,
52
- byte_size INTEGER NOT NULL DEFAULT 0
55
+ byte_size INTEGER NOT NULL DEFAULT 0,
56
+ entity_version TEXT
53
57
  );
54
58
  CREATE INDEX IF NOT EXISTS idx_entities_date ON entities(date_for_sort);
55
59
  CREATE INDEX IF NOT EXISTS idx_entities_mailbox ON entities(mailbox_uid);
@@ -84,12 +88,13 @@ export const BM25_WEIGHTS_SQL = "3.0, 2.0, 1.0, 1.0";
84
88
  * so re-indexing an already-present message (a flag changed, a folder move) updates it in place and the
85
89
  * `entities_au` trigger keeps `entities_fts` in sync automatically. */
86
90
  export const UPSERT_ENTITY_SQL = `
87
- INSERT INTO entities (entity_type, entity_uid, mailbox_uid, folder_uid, date_for_sort, participants, flags, has_attachments, subject, body, attachment_text, byte_size)
88
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
91
+ INSERT INTO entities (entity_type, entity_uid, mailbox_uid, folder_uid, date_for_sort, participants, flags, has_attachments, subject, body, attachment_text, byte_size, entity_version)
92
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
89
93
  ON CONFLICT(entity_uid) DO UPDATE SET
90
94
  folder_uid = excluded.folder_uid, date_for_sort = excluded.date_for_sort, participants = excluded.participants,
91
95
  flags = excluded.flags, has_attachments = excluded.has_attachments, subject = excluded.subject,
92
- body = excluded.body, attachment_text = excluded.attachment_text, byte_size = excluded.byte_size
96
+ body = excluded.body, attachment_text = excluded.attachment_text, byte_size = excluded.byte_size,
97
+ entity_version = excluded.entity_version
93
98
  `;
94
99
  /** Bind values for `UPSERT_ENTITY_SQL`, in column order - kept alongside it so the two can never drift
95
100
  * out of sync with each other. */
@@ -107,6 +112,7 @@ export function entityBindValues(entity) {
107
112
  entity.body ?? null,
108
113
  entity.attachmentText ?? null,
109
114
  entity.byteSize,
115
+ entity.entityVersion ?? null,
110
116
  ];
111
117
  }
112
118
  /**
@@ -0,0 +1,27 @@
1
+ /** §11's Window Sizing table, Web row. */
2
+ export declare const WEB_DEFAULT_BYTE_BUDGET_BYTES: number;
3
+ /** §11's Window Sizing table, Native desktop row - still a real, user-adjustable ceiling (not
4
+ * `UNBOUNDED`), just a roomier default given Electron's storage is disk-limited rather than
5
+ * browser-quota-limited. */
6
+ export declare const ELECTRON_DEFAULT_BYTE_BUDGET_BYTES: number;
7
+ export interface LocalIndexSizeOption {
8
+ bytes: number;
9
+ label: string;
10
+ }
11
+ /** Selectable presets for the Settings UI. `0` means "unlimited" - `applyEviction()`
12
+ * (`localIndexWorker.ts`) already treats a falsy byte budget as unconfigured/unenforced. */
13
+ export declare const LOCAL_INDEX_SIZE_OPTIONS: LocalIndexSizeOption[];
14
+ /** This device's default byte budget before any explicit preference is saved - `500 MB` in a browser
15
+ * tab, `1 GB` in the Electron shell. */
16
+ export declare function getDefaultLocalIndexByteBudget(): number;
17
+ /**
18
+ * Reads this device's configured local-index byte budget. Falls back to
19
+ * `getDefaultLocalIndexByteBudget()` for a never-configured device, a corrupted/non-numeric stored
20
+ * value, or a `localStorage` access that throws (private-browsing/storage-blocked contexts) - never
21
+ * throws itself, matching `getIdleTimeoutMinutes()`'s identical fallback posture.
22
+ */
23
+ export declare function getLocalIndexByteBudget(): number;
24
+ /** Persists this device's local-index byte-budget preference. A `localStorage` write failure is
25
+ * swallowed, not thrown - the setting just doesn't survive a reload in that case, same fallback-to-default
26
+ * behavior `getLocalIndexByteBudget()` already has for a storage-blocked context. */
27
+ export declare function setLocalIndexByteBudget(bytes: number): void;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The Tier 2 local index's on-disk naming and bulk-removal helpers, shared by the Worker
3
+ * (`localIndexWorker.ts`) and the main thread (`localIndexRpcClient.ts`). Deliberately free of any
4
+ * wa-sqlite import so the main thread can use it without pulling the WASM build into its own bundle.
5
+ *
6
+ * Every mailbox's index lives in its own top-level OPFS directory named `rapidmx-localsearch-<mailboxUid>`
7
+ * (`AccessHandlePoolVFS` creates it from the VFS name). Enumerating that prefix is how sign-out destroys
8
+ * *every* index on this device - including ones for mailboxes this page load never opened, which the
9
+ * RPC client's own session-scoped bookkeeping can't know about - and how a sign-in prunes indexes left
10
+ * behind for mailboxes the current user can no longer access.
11
+ */
12
+ export declare const LOCAL_INDEX_POOL_PREFIX = "rapidmx-localsearch-";
13
+ /** The OPFS directory name (and `EncryptingVFS` name) a mailbox's index lives under. */
14
+ export declare function poolNameFor(mailboxUid: string): string;
15
+ /** Inverse of `poolNameFor()`; `undefined` for any OPFS entry that isn't a local index directory. */
16
+ export declare function mailboxUidFromPoolName(name: string): string | undefined;
17
+ /** Removes one mailbox's index directory. Resolves quietly when it doesn't exist; rejects for any other
18
+ * failure (most commonly `NoModificationAllowedError` - another tab still holds its access handles). */
19
+ export declare function removeLocalIndexDirectory(mailboxUid: string): Promise<void>;
20
+ export interface LocalIndexRemovalResult {
21
+ removed: string[];
22
+ /** Mailbox uids whose directory could not be removed (e.g. still open in another tab). */
23
+ failed: string[];
24
+ }
25
+ /** Every mailbox uid that has a local index directory on this origin. */
26
+ export declare function listLocalIndexMailboxUids(): Promise<string[]>;
27
+ /** Removes every local index directory on this origin except those whose mailbox uid is in `keep`. */
28
+ export declare function removeLocalIndexDirectories(keep?: ReadonlySet<string>): Promise<LocalIndexRemovalResult>;
@@ -0,0 +1,86 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ /**
6
+ * The Tier 2 local index's on-disk naming and bulk-removal helpers, shared by the Worker
7
+ * (`localIndexWorker.ts`) and the main thread (`localIndexRpcClient.ts`). Deliberately free of any
8
+ * wa-sqlite import so the main thread can use it without pulling the WASM build into its own bundle.
9
+ *
10
+ * Every mailbox's index lives in its own top-level OPFS directory named `rapidmx-localsearch-<mailboxUid>`
11
+ * (`AccessHandlePoolVFS` creates it from the VFS name). Enumerating that prefix is how sign-out destroys
12
+ * *every* index on this device - including ones for mailboxes this page load never opened, which the
13
+ * RPC client's own session-scoped bookkeeping can't know about - and how a sign-in prunes indexes left
14
+ * behind for mailboxes the current user can no longer access.
15
+ */
16
+ export const LOCAL_INDEX_POOL_PREFIX = "rapidmx-localsearch-";
17
+ /** The OPFS directory name (and `EncryptingVFS` name) a mailbox's index lives under. */
18
+ export function poolNameFor(mailboxUid) {
19
+ return `${LOCAL_INDEX_POOL_PREFIX}${mailboxUid}`;
20
+ }
21
+ /** Inverse of `poolNameFor()`; `undefined` for any OPFS entry that isn't a local index directory. */
22
+ export function mailboxUidFromPoolName(name) {
23
+ return name.startsWith(LOCAL_INDEX_POOL_PREFIX) && name.length > LOCAL_INDEX_POOL_PREFIX.length
24
+ ? name.slice(LOCAL_INDEX_POOL_PREFIX.length)
25
+ : undefined;
26
+ }
27
+ /** `navigator.storage.getDirectory()`, or `undefined` where OPFS isn't available at all (older browsers,
28
+ * jsdom/node) - there is nothing on disk to remove in that case. */
29
+ async function getOpfsRoot() {
30
+ const storage = typeof navigator === "undefined" ? undefined : navigator.storage;
31
+ if (!storage?.getDirectory) {
32
+ return undefined;
33
+ }
34
+ return storage.getDirectory();
35
+ }
36
+ function isNotFound(err) {
37
+ return err?.name === "NotFoundError";
38
+ }
39
+ /** Removes one mailbox's index directory. Resolves quietly when it doesn't exist; rejects for any other
40
+ * failure (most commonly `NoModificationAllowedError` - another tab still holds its access handles). */
41
+ export async function removeLocalIndexDirectory(mailboxUid) {
42
+ const root = await getOpfsRoot();
43
+ if (!root) {
44
+ return;
45
+ }
46
+ try {
47
+ await root.removeEntry(poolNameFor(mailboxUid), { recursive: true });
48
+ }
49
+ catch (err) {
50
+ if (!isNotFound(err)) {
51
+ throw err;
52
+ }
53
+ }
54
+ }
55
+ /** Every mailbox uid that has a local index directory on this origin. */
56
+ export async function listLocalIndexMailboxUids() {
57
+ const root = await getOpfsRoot();
58
+ if (!root) {
59
+ return [];
60
+ }
61
+ const mailboxUids = [];
62
+ // `keys()` is part of the OPFS spec and every browser that has `getDirectory()` at all, but isn't in
63
+ // this TypeScript version's DOM lib yet.
64
+ for await (const name of root.keys()) {
65
+ const mailboxUid = mailboxUidFromPoolName(name);
66
+ if (mailboxUid) {
67
+ mailboxUids.push(mailboxUid);
68
+ }
69
+ }
70
+ return mailboxUids;
71
+ }
72
+ /** Removes every local index directory on this origin except those whose mailbox uid is in `keep`. */
73
+ export async function removeLocalIndexDirectories(keep = new Set()) {
74
+ const result = { removed: [], failed: [] };
75
+ const mailboxUids = (await listLocalIndexMailboxUids()).filter((mailboxUid) => !keep.has(mailboxUid));
76
+ for (const mailboxUid of mailboxUids) {
77
+ try {
78
+ await removeLocalIndexDirectory(mailboxUid);
79
+ result.removed.push(mailboxUid);
80
+ }
81
+ catch {
82
+ result.failed.push(mailboxUid);
83
+ }
84
+ }
85
+ return result;
86
+ }