@antelopejs/dms 0.6.1 → 0.7.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 (472) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/db/models/notification-feed-filter.d.ts +0 -1
  3. package/dist/db/models/notification-feed-filter.js +0 -4
  4. package/dist/db/models/notification-feed-filter.js.map +1 -1
  5. package/dist/db/models/signInAttempts.model.d.ts +18 -0
  6. package/dist/db/models/signInAttempts.model.js +83 -12
  7. package/dist/db/models/signInAttempts.model.js.map +1 -1
  8. package/dist/db/models/userEmailChanges.model.d.ts +8 -1
  9. package/dist/db/models/userEmailChanges.model.js +27 -2
  10. package/dist/db/models/userEmailChanges.model.js.map +1 -1
  11. package/dist/db/models/userNotificationPreferences.model.d.ts +0 -2
  12. package/dist/db/models/userNotificationPreferences.model.js +0 -13
  13. package/dist/db/models/userNotificationPreferences.model.js.map +1 -1
  14. package/dist/db/models/userNotifications.model.d.ts +5 -2
  15. package/dist/db/models/userNotifications.model.js +19 -2
  16. package/dist/db/models/userNotifications.model.js.map +1 -1
  17. package/dist/db/tables/signInAttempts.table.d.ts +2 -1
  18. package/dist/db/tables/signInAttempts.table.js +4 -0
  19. package/dist/db/tables/signInAttempts.table.js.map +1 -1
  20. package/dist/db/tables/userEmailChanges.table.d.ts +2 -1
  21. package/dist/db/tables/userEmailChanges.table.js +6 -1
  22. package/dist/db/tables/userEmailChanges.table.js.map +1 -1
  23. package/dist/db/tables/userNotificationPreferences.table.d.ts +0 -1
  24. package/dist/db/tables/userNotificationPreferences.table.js +0 -4
  25. package/dist/db/tables/userNotificationPreferences.table.js.map +1 -1
  26. package/dist/implementations/dms/layout-banners.d.ts +4 -1
  27. package/dist/implementations/dms/layout-banners.js +33 -18
  28. package/dist/implementations/dms/layout-banners.js.map +1 -1
  29. package/dist/implementations/dms/nav-badges.d.ts +6 -4
  30. package/dist/implementations/dms/nav-badges.js +6 -5
  31. package/dist/implementations/dms/nav-badges.js.map +1 -1
  32. package/dist/implementations/dms/page.d.ts +1 -0
  33. package/dist/implementations/dms/page.js +46 -8
  34. package/dist/implementations/dms/page.js.map +1 -1
  35. package/dist/implementations/dms/permission-preview.d.ts +1 -1
  36. package/dist/implementations/dms/permission-preview.js +9 -4
  37. package/dist/implementations/dms/permission-preview.js.map +1 -1
  38. package/dist/implementations/dms-auth/index.d.ts +3 -2
  39. package/dist/implementations/dms-auth/index.js +39 -35
  40. package/dist/implementations/dms-auth/index.js.map +1 -1
  41. package/dist/implementations/dms-auth/token-session.d.ts +3 -0
  42. package/dist/implementations/dms-auth/token-session.js +22 -0
  43. package/dist/implementations/dms-auth/token-session.js.map +1 -0
  44. package/dist/implementations/dms-notifications/index.d.ts +0 -1
  45. package/dist/implementations/dms-notifications/index.js +0 -7
  46. package/dist/implementations/dms-notifications/index.js.map +1 -1
  47. package/dist/pages/settings/settings-index.js +3 -2
  48. package/dist/pages/settings/settings-index.js.map +1 -1
  49. package/dist/pages/settings/users/account-activity-feed.d.ts +1 -1
  50. package/dist/pages/settings/users/account-activity-feed.js +1 -1
  51. package/dist/pages/settings/users/account-activity-feed.js.map +1 -1
  52. package/dist/pages/settings/users/account-activity.d.ts +1 -1
  53. package/dist/pages/settings/users/account-activity.js +1 -1
  54. package/dist/pages/settings/users/account-activity.js.map +1 -1
  55. package/dist/pages/settings/users/account-credentials.d.ts +3 -0
  56. package/dist/pages/settings/users/account-credentials.js +2 -0
  57. package/dist/pages/settings/users/account-credentials.js.map +1 -1
  58. package/dist/pages/settings/users/current-password.d.ts +1 -1
  59. package/dist/pages/settings/users/current-password.js +23 -2
  60. package/dist/pages/settings/users/current-password.js.map +1 -1
  61. package/dist/pages/settings/users/email-change.d.ts +1 -1
  62. package/dist/pages/settings/users/email-change.js +33 -26
  63. package/dist/pages/settings/users/email-change.js.map +1 -1
  64. package/dist/pages/settings/users/invite-resend.js +0 -1
  65. package/dist/pages/settings/users/invite-resend.js.map +1 -1
  66. package/dist/pages/settings/users/invites.d.ts +1 -2
  67. package/dist/pages/settings/users/invites.js +21 -19
  68. package/dist/pages/settings/users/invites.js.map +1 -1
  69. package/dist/pages/settings/users/member-invite-batch.js +0 -1
  70. package/dist/pages/settings/users/member-invite-batch.js.map +1 -1
  71. package/dist/pages/settings/users/member-invite-form.js +10 -22
  72. package/dist/pages/settings/users/member-invite-form.js.map +1 -1
  73. package/dist/pages/settings/users/member-role-options.d.ts +6 -4
  74. package/dist/pages/settings/users/member-role-options.js +15 -4
  75. package/dist/pages/settings/users/member-role-options.js.map +1 -1
  76. package/dist/pages/settings/users/members.d.ts +2 -3
  77. package/dist/pages/settings/users/members.js +7 -12
  78. package/dist/pages/settings/users/members.js.map +1 -1
  79. package/dist/pages/settings/users/notifications.d.ts +1 -8
  80. package/dist/pages/settings/users/notifications.js +6 -38
  81. package/dist/pages/settings/users/notifications.js.map +1 -1
  82. package/dist/pages/settings/users/permission-tree-nodes.d.ts +1 -1
  83. package/dist/pages/settings/users/permission-tree-nodes.js +23 -9
  84. package/dist/pages/settings/users/permission-tree-nodes.js.map +1 -1
  85. package/dist/pages/settings/users/permission-warnings.d.ts +7 -0
  86. package/dist/pages/settings/users/permission-warnings.js +42 -0
  87. package/dist/pages/settings/users/permission-warnings.js.map +1 -0
  88. package/dist/pages/settings/users/profile-helpers.d.ts +1 -3
  89. package/dist/pages/settings/users/profile-helpers.js +0 -16
  90. package/dist/pages/settings/users/profile-helpers.js.map +1 -1
  91. package/dist/pages/settings/users/role-editor-store.js +6 -3
  92. package/dist/pages/settings/users/role-editor-store.js.map +1 -1
  93. package/dist/pages/settings/users/role-editor.d.ts +2 -1
  94. package/dist/pages/settings/users/role-editor.js +8 -4
  95. package/dist/pages/settings/users/role-editor.js.map +1 -1
  96. package/dist/pages/settings/users/role-preview.js +2 -1
  97. package/dist/pages/settings/users/role-preview.js.map +1 -1
  98. package/dist/pages/settings/users/roles.js +5 -1
  99. package/dist/pages/settings/users/roles.js.map +1 -1
  100. package/dist/pages/settings/users/security-attention.d.ts +4 -0
  101. package/dist/pages/settings/users/security-attention.js +23 -7
  102. package/dist/pages/settings/users/security-attention.js.map +1 -1
  103. package/dist/pages/settings/users/security.d.ts +2 -1
  104. package/dist/pages/settings/users/security.js +13 -2
  105. package/dist/pages/settings/users/security.js.map +1 -1
  106. package/dist/pages/settings/users/two-factor-operations.d.ts +2 -1
  107. package/dist/pages/settings/users/two-factor-operations.js +28 -29
  108. package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
  109. package/dist/routes/auth/forgot-password.js +3 -0
  110. package/dist/routes/auth/forgot-password.js.map +1 -1
  111. package/dist/routes/auth/index.d.ts +1 -0
  112. package/dist/routes/auth/index.js +1 -0
  113. package/dist/routes/auth/index.js.map +1 -1
  114. package/dist/routes/auth/invite.js +28 -4
  115. package/dist/routes/auth/invite.js.map +1 -1
  116. package/dist/routes/auth/login-throttle.d.ts +2 -0
  117. package/dist/routes/auth/login-throttle.js +5 -4
  118. package/dist/routes/auth/login-throttle.js.map +1 -1
  119. package/dist/routes/auth/request-2fa-email.js +6 -18
  120. package/dist/routes/auth/request-2fa-email.js.map +1 -1
  121. package/dist/routes/auth/reset-code.d.ts +3 -0
  122. package/dist/routes/auth/reset-code.js +37 -0
  123. package/dist/routes/auth/reset-code.js.map +1 -0
  124. package/dist/routes/auth/reset-password.d.ts +2 -2
  125. package/dist/routes/auth/reset-password.js +5 -10
  126. package/dist/routes/auth/reset-password.js.map +1 -1
  127. package/dist/routes/auth/signup.js +10 -32
  128. package/dist/routes/auth/signup.js.map +1 -1
  129. package/dist/routes/auth/two-factor-throttle.d.ts +5 -0
  130. package/dist/routes/auth/two-factor-throttle.js +81 -0
  131. package/dist/routes/auth/two-factor-throttle.js.map +1 -0
  132. package/dist/routes/auth/validate-forgot-password-token.js +2 -8
  133. package/dist/routes/auth/validate-forgot-password-token.js.map +1 -1
  134. package/dist/routes/auth/validate-invite-token.d.ts +1 -0
  135. package/dist/routes/auth/validate-invite-token.js +11 -0
  136. package/dist/routes/auth/validate-invite-token.js.map +1 -0
  137. package/dist/routes/auth/verify-2fa.js +10 -13
  138. package/dist/routes/auth/verify-2fa.js.map +1 -1
  139. package/dist/routes/auth.d.ts +1 -0
  140. package/dist/routes/auth.js +11 -1
  141. package/dist/routes/auth.js.map +1 -1
  142. package/dist/test/helpers/auth.d.ts +0 -1
  143. package/dist/test/helpers/auth.js +0 -1
  144. package/dist/test/helpers/auth.js.map +1 -1
  145. package/dist/test/helpers/emails.d.ts +10 -0
  146. package/dist/test/helpers/emails.js +36 -0
  147. package/dist/test/helpers/emails.js.map +1 -0
  148. package/dist/test/helpers/fixtures.d.ts +0 -1
  149. package/dist/test/helpers/fixtures.js +1 -2
  150. package/dist/test/helpers/fixtures.js.map +1 -1
  151. package/dist/test/helpers/page-access.d.ts +1 -0
  152. package/dist/test/helpers/page-access.js +6 -1
  153. package/dist/test/helpers/page-access.js.map +1 -1
  154. package/dist/test/integration/auth/current-password.test.d.ts +1 -0
  155. package/dist/test/integration/auth/current-password.test.js +65 -0
  156. package/dist/test/integration/auth/current-password.test.js.map +1 -0
  157. package/dist/test/integration/auth/password-reset.test.d.ts +1 -0
  158. package/dist/test/integration/auth/password-reset.test.js +126 -0
  159. package/dist/test/integration/auth/password-reset.test.js.map +1 -0
  160. package/dist/test/integration/auth/session-revocation.test.d.ts +1 -0
  161. package/dist/test/integration/auth/session-revocation.test.js +45 -0
  162. package/dist/test/integration/auth/session-revocation.test.js.map +1 -0
  163. package/dist/test/integration/auth/two-factor-codes.test.d.ts +1 -0
  164. package/dist/test/integration/auth/two-factor-codes.test.js +335 -0
  165. package/dist/test/integration/auth/two-factor-codes.test.js.map +1 -0
  166. package/dist/test/integration/auth/two-factor-password.test.js +5 -1
  167. package/dist/test/integration/auth/two-factor-password.test.js.map +1 -1
  168. package/dist/test/integration/auth/two-factor-token.test.d.ts +1 -0
  169. package/dist/test/integration/auth/two-factor-token.test.js +92 -0
  170. package/dist/test/integration/auth/two-factor-token.test.js.map +1 -0
  171. package/dist/test/integration/email-change.test.js +36 -0
  172. package/dist/test/integration/email-change.test.js.map +1 -1
  173. package/dist/test/integration/invite-extension-edit.test.js +0 -1
  174. package/dist/test/integration/invite-extension-edit.test.js.map +1 -1
  175. package/dist/test/integration/invite-link-status.test.d.ts +1 -0
  176. package/dist/test/integration/invite-link-status.test.js +110 -0
  177. package/dist/test/integration/invite-link-status.test.js.map +1 -0
  178. package/dist/test/integration/invite-resolution.test.js +0 -3
  179. package/dist/test/integration/invite-resolution.test.js.map +1 -1
  180. package/dist/test/integration/member-settings-access.test.js +17 -0
  181. package/dist/test/integration/member-settings-access.test.js.map +1 -1
  182. package/dist/test/integration/notification-unread-badge.test.d.ts +1 -0
  183. package/dist/test/integration/notification-unread-badge.test.js +92 -0
  184. package/dist/test/integration/notification-unread-badge.test.js.map +1 -0
  185. package/dist/test/integration/permission-warnings.test.d.ts +1 -0
  186. package/dist/test/integration/permission-warnings.test.js +71 -0
  187. package/dist/test/integration/permission-warnings.test.js.map +1 -0
  188. package/dist/test/integration/removed-member-notifications.test.d.ts +1 -0
  189. package/dist/test/integration/removed-member-notifications.test.js +80 -0
  190. package/dist/test/integration/removed-member-notifications.test.js.map +1 -0
  191. package/dist/test/integration/role-permission-ancestors.test.d.ts +1 -0
  192. package/dist/test/integration/role-permission-ancestors.test.js +146 -0
  193. package/dist/test/integration/role-permission-ancestors.test.js.map +1 -0
  194. package/dist/test/integration/tenant-lifecycle.test.js +0 -4
  195. package/dist/test/integration/tenant-lifecycle.test.js.map +1 -1
  196. package/dist/test/unit/auth-session-revocation.test.d.ts +1 -0
  197. package/dist/test/unit/auth-session-revocation.test.js +105 -0
  198. package/dist/test/unit/auth-session-revocation.test.js.map +1 -0
  199. package/dist/test/unit/db/email-normalization.test.js +0 -1
  200. package/dist/test/unit/db/email-normalization.test.js.map +1 -1
  201. package/dist/test/unit/db/notification-feed-filter.test.js +0 -16
  202. package/dist/test/unit/db/notification-feed-filter.test.js.map +1 -1
  203. package/dist/test/unit/db/notification-inbox.test.js +63 -13
  204. package/dist/test/unit/db/notification-inbox.test.js.map +1 -1
  205. package/dist/test/unit/db/notification-preferences.test.js +0 -17
  206. package/dist/test/unit/db/notification-preferences.test.js.map +1 -1
  207. package/dist/test/unit/implementations/dms/empty-menu-group.test.d.ts +1 -0
  208. package/dist/test/unit/implementations/dms/empty-menu-group.test.js +133 -0
  209. package/dist/test/unit/implementations/dms/empty-menu-group.test.js.map +1 -0
  210. package/dist/test/unit/implementations/dms/layout-banners.test.js +78 -1
  211. package/dist/test/unit/implementations/dms/layout-banners.test.js.map +1 -1
  212. package/dist/test/unit/implementations/dms/menu-hidden-blocks.test.d.ts +1 -0
  213. package/dist/test/unit/implementations/dms/menu-hidden-blocks.test.js +149 -0
  214. package/dist/test/unit/implementations/dms/menu-hidden-blocks.test.js.map +1 -0
  215. package/dist/test/unit/implementations/dms/nav-badges.test.js +55 -4
  216. package/dist/test/unit/implementations/dms/nav-badges.test.js.map +1 -1
  217. package/dist/test/unit/implementations/dms/page-all-components-hidden.test.d.ts +1 -0
  218. package/dist/test/unit/implementations/dms/page-all-components-hidden.test.js +118 -0
  219. package/dist/test/unit/implementations/dms/page-all-components-hidden.test.js.map +1 -0
  220. package/dist/test/unit/implementations/dms/permission-preview-aggregate.test.js +16 -0
  221. package/dist/test/unit/implementations/dms/permission-preview-aggregate.test.js.map +1 -1
  222. package/dist/test/unit/implementations/dms/permission-preview-cost.test.js +5 -3
  223. package/dist/test/unit/implementations/dms/permission-preview-cost.test.js.map +1 -1
  224. package/dist/test/unit/implementations/dms/permission-preview-menu.test.js +29 -3
  225. package/dist/test/unit/implementations/dms/permission-preview-menu.test.js.map +1 -1
  226. package/dist/test/unit/implementations/dms/quick-actions-button.test.js +4 -2
  227. package/dist/test/unit/implementations/dms/quick-actions-button.test.js.map +1 -1
  228. package/dist/test/unit/implementations/dms/user-permissions-gate.test.js +2 -1
  229. package/dist/test/unit/implementations/dms/user-permissions-gate.test.js.map +1 -1
  230. package/dist/test/unit/implementations/dms-base/table-view-shared-controller.test.js +2 -1
  231. package/dist/test/unit/implementations/dms-base/table-view-shared-controller.test.js.map +1 -1
  232. package/dist/test/unit/interfaces/dms/component-children-permissions.test.js +7 -2
  233. package/dist/test/unit/interfaces/dms/component-children-permissions.test.js.map +1 -1
  234. package/dist/test/unit/interfaces/dms/form-watch-permissions.test.d.ts +1 -0
  235. package/dist/test/unit/interfaces/dms/form-watch-permissions.test.js +133 -0
  236. package/dist/test/unit/interfaces/dms/form-watch-permissions.test.js.map +1 -0
  237. package/dist/test/unit/interfaces/dms/has-permission-ancestors.test.d.ts +1 -0
  238. package/dist/test/unit/interfaces/dms/has-permission-ancestors.test.js +149 -0
  239. package/dist/test/unit/interfaces/dms/has-permission-ancestors.test.js.map +1 -0
  240. package/dist/test/unit/interfaces/dms/invite-extension.test.js +13 -0
  241. package/dist/test/unit/interfaces/dms/invite-extension.test.js.map +1 -1
  242. package/dist/test/unit/interfaces/dms/member-access.test.js +27 -1
  243. package/dist/test/unit/interfaces/dms/member-access.test.js.map +1 -1
  244. package/dist/test/unit/interfaces/dms/nav-badge-tones.test.d.ts +1 -0
  245. package/dist/test/unit/interfaces/dms/nav-badge-tones.test.js +31 -0
  246. package/dist/test/unit/interfaces/dms/nav-badge-tones.test.js.map +1 -0
  247. package/dist/test/unit/interfaces/dms/page-extension.test.js +4 -1
  248. package/dist/test/unit/interfaces/dms/page-extension.test.js.map +1 -1
  249. package/dist/test/unit/interfaces/dms/permission-requirements.test.d.ts +1 -0
  250. package/dist/test/unit/interfaces/dms/permission-requirements.test.js +144 -0
  251. package/dist/test/unit/interfaces/dms/permission-requirements.test.js.map +1 -0
  252. package/dist/test/unit/interfaces/dms-base/column-display.test.js +70 -0
  253. package/dist/test/unit/interfaces/dms-base/column-display.test.js.map +1 -1
  254. package/dist/test/unit/interfaces/dms-base/display-blocks-catalog.test.js +38 -0
  255. package/dist/test/unit/interfaces/dms-base/display-blocks-catalog.test.js.map +1 -1
  256. package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js +6 -2
  257. package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js.map +1 -1
  258. package/dist/test/unit/interfaces/dms-base/lists-and-settings-catalog.test.js +1 -1
  259. package/dist/test/unit/interfaces/dms-base/lists-and-settings-catalog.test.js.map +1 -1
  260. package/dist/test/unit/interfaces/dms-base/table-view-export-permission.test.js +2 -1
  261. package/dist/test/unit/interfaces/dms-base/table-view-export-permission.test.js.map +1 -1
  262. package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js +2 -1
  263. package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js.map +1 -1
  264. package/dist/test/unit/interfaces/dms-base/table-view-shared-controller-permissions.test.js +2 -1
  265. package/dist/test/unit/interfaces/dms-base/table-view-shared-controller-permissions.test.js.map +1 -1
  266. package/dist/test/unit/pages/settings/account-activity-feed.test.js +2 -2
  267. package/dist/test/unit/pages/settings/account-activity-feed.test.js.map +1 -1
  268. package/dist/test/unit/pages/settings/account-activity.test.js +11 -2
  269. package/dist/test/unit/pages/settings/account-activity.test.js.map +1 -1
  270. package/dist/test/unit/pages/settings/invite-email-delivery.test.js +0 -1
  271. package/dist/test/unit/pages/settings/invite-email-delivery.test.js.map +1 -1
  272. package/dist/test/unit/pages/settings/invite-email-validation-option.test.d.ts +1 -0
  273. package/dist/test/unit/pages/settings/invite-email-validation-option.test.js +33 -0
  274. package/dist/test/unit/pages/settings/invite-email-validation-option.test.js.map +1 -0
  275. package/dist/test/unit/pages/settings/member-lists-page-size.test.d.ts +1 -0
  276. package/dist/test/unit/pages/settings/member-lists-page-size.test.js +22 -0
  277. package/dist/test/unit/pages/settings/member-lists-page-size.test.js.map +1 -0
  278. package/dist/test/unit/pages/settings/member-table-widths.test.js +2 -2
  279. package/dist/test/unit/pages/settings/member-table-widths.test.js.map +1 -1
  280. package/dist/test/unit/pages/settings/members-page.test.js +7 -10
  281. package/dist/test/unit/pages/settings/members-page.test.js.map +1 -1
  282. package/dist/test/unit/pages/settings/notifications-access-gate.test.js +3 -10
  283. package/dist/test/unit/pages/settings/notifications-access-gate.test.js.map +1 -1
  284. package/dist/test/unit/pages/settings/permission-tree-nodes.test.js +57 -0
  285. package/dist/test/unit/pages/settings/permission-tree-nodes.test.js.map +1 -1
  286. package/dist/test/unit/pages/settings/permission-warnings.test.d.ts +1 -0
  287. package/dist/test/unit/pages/settings/permission-warnings.test.js +49 -0
  288. package/dist/test/unit/pages/settings/permission-warnings.test.js.map +1 -0
  289. package/dist/test/unit/pages/settings/role-editor.test.js +16 -0
  290. package/dist/test/unit/pages/settings/role-editor.test.js.map +1 -1
  291. package/dist/test/unit/pages/settings/role-preview.test.js +7 -0
  292. package/dist/test/unit/pages/settings/role-preview.test.js.map +1 -1
  293. package/dist/test/unit/pages/settings/security-attention.test.d.ts +1 -0
  294. package/dist/test/unit/pages/settings/security-attention.test.js +74 -0
  295. package/dist/test/unit/pages/settings/security-attention.test.js.map +1 -0
  296. package/dist/test/unit/sanitize-user.test.d.ts +1 -0
  297. package/dist/test/unit/sanitize-user.test.js +21 -0
  298. package/dist/test/unit/sanitize-user.test.js.map +1 -0
  299. package/dist/test/unit/signup-email-validation.test.d.ts +1 -0
  300. package/dist/test/unit/signup-email-validation.test.js +79 -0
  301. package/dist/test/unit/signup-email-validation.test.js.map +1 -0
  302. package/dist/test/unit/validation/member-invite.schema.test.js +14 -52
  303. package/dist/test/unit/validation/member-invite.schema.test.js.map +1 -1
  304. package/dist/utils/account-notifications.d.ts +5 -0
  305. package/dist/utils/account-notifications.js +8 -0
  306. package/dist/utils/account-notifications.js.map +1 -1
  307. package/dist/utils/notification-emitter.d.ts +1 -1
  308. package/dist/utils/notification-emitter.js +1 -1
  309. package/dist/utils/notification-emitter.js.map +1 -1
  310. package/dist/utils/notification-tones.d.ts +2 -1
  311. package/dist/utils/notification-tones.js +5 -1
  312. package/dist/utils/notification-tones.js.map +1 -1
  313. package/dist/utils/one-time-code.d.ts +3 -0
  314. package/dist/utils/one-time-code.js +21 -0
  315. package/dist/utils/one-time-code.js.map +1 -0
  316. package/dist/utils/sign-in-monitor.d.ts +1 -0
  317. package/dist/utils/sign-in-monitor.js +3 -2
  318. package/dist/utils/sign-in-monitor.js.map +1 -1
  319. package/dist/utils/two-factor-codes.d.ts +10 -0
  320. package/dist/utils/two-factor-codes.js +76 -0
  321. package/dist/utils/two-factor-codes.js.map +1 -0
  322. package/dist/validation/auth.schema.d.ts +17 -0
  323. package/dist/validation/auth.schema.js +17 -6
  324. package/dist/validation/auth.schema.js.map +1 -1
  325. package/dist/validation/issue-message.d.ts +1 -0
  326. package/dist/validation/issue-message.js +9 -0
  327. package/dist/validation/issue-message.js.map +1 -0
  328. package/dist/validation/member-invite.schema.d.ts +1 -25
  329. package/dist/validation/member-invite.schema.js +2 -23
  330. package/dist/validation/member-invite.schema.js.map +1 -1
  331. package/frontend-vue/dms.frontend.ts +2 -0
  332. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/forgot-validation.vue +3 -0
  333. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +105 -5
  334. package/frontend-vue/layers/dms-auth/i18n/locales/auth-en-GB.json +20 -4
  335. package/frontend-vue/layers/dms-auth/i18n/locales/auth-fr-FR.json +20 -4
  336. package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +25 -0
  337. package/frontend-vue/layers/dms-core/app/composables/useFieldErrors.ts +3 -2
  338. package/frontend-vue/layers/dms-core/app/types/composed-text.ts +64 -0
  339. package/frontend-vue/layers/dms-core/app/types/menu.ts +15 -0
  340. package/frontend-vue/layers/dms-core/app/utils/composedText.ts +227 -0
  341. package/frontend-vue/layers/dms-core/app/utils/formatter.ts +49 -0
  342. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +4 -1
  343. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +4 -1
  344. package/frontend-vue/layers/dms-layout/app/assets/css/main.css +20 -0
  345. package/frontend-vue/layers/dms-layout/app/build/components/layout/CommandPaletteAssistantAnswer.vue +75 -0
  346. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +17 -3
  347. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardFrame.vue +22 -0
  348. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +24 -10
  349. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidePanel.vue +139 -0
  350. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +4 -2
  351. package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +90 -0
  352. package/frontend-vue/layers/dms-layout/app/build/components/layout/SidePanelHost.vue +17 -0
  353. package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +25 -43
  354. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/members/MemberRolePicker.vue +24 -2
  355. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationMatrixCategory.vue +70 -25
  356. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationPreferencesForm.vue +25 -10
  357. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/notificationDisplay.ts +41 -5
  358. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfilePreferencesSummary.vue +2 -1
  359. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfileSecuritySummary.vue +20 -4
  360. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RoleEditor.vue +21 -0
  361. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionArea.vue +16 -2
  362. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionBranch.vue +1 -0
  363. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionLeaf.vue +13 -1
  364. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionTree.vue +9 -0
  365. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionWarning.vue +46 -0
  366. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +108 -29
  367. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-types.ts +2 -0
  368. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityDialogFrame.vue +6 -6
  369. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityEmail.vue +7 -1
  370. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecuritySessions.vue +5 -4
  371. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityStatus.vue +4 -3
  372. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityTwoFactor.vue +54 -18
  373. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/shell/SettingsNav.vue +13 -8
  374. package/frontend-vue/layers/dms-layout/app/build/composables/command-palette/assistantGroups.ts +143 -0
  375. package/frontend-vue/layers/dms-layout/app/build/composables/command-palette/useCommandPaletteAssistantMode.ts +249 -0
  376. package/frontend-vue/layers/dms-layout/app/build/composables/general/useGithubStarPrompt.ts +178 -0
  377. package/frontend-vue/layers/dms-layout/app/build/composables/layout/sidePanelState.ts +191 -0
  378. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useHeaderActionControls.ts +51 -0
  379. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +113 -0
  380. package/frontend-vue/layers/dms-layout/app/build/composables/navigation/useCategoryNavCards.ts +17 -10
  381. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +45 -0
  382. package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfilePreferences.ts +8 -1
  383. package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityOverview.ts +50 -3
  384. package/frontend-vue/layers/dms-layout/app/build/composables/settings/useSettingsNavigation.ts +3 -0
  385. package/frontend-vue/layers/dms-layout/app/build/utils/categoryPages.ts +1 -2
  386. package/frontend-vue/layers/dms-layout/app/build/utils/github-repository.ts +5 -0
  387. package/frontend-vue/layers/dms-layout/app/components/layout/DashboardSearch.vue +109 -7
  388. package/frontend-vue/layers/dms-layout/app/components/navigation/NavigationMenu.vue +19 -9
  389. package/frontend-vue/layers/dms-layout/app/composables/notification/useNotifications.ts +22 -40
  390. package/frontend-vue/layers/dms-layout/app/composables/useAppSidePanels.ts +96 -0
  391. package/frontend-vue/layers/dms-layout/app/composables/useCommandPaletteAssistant.ts +129 -0
  392. package/frontend-vue/layers/dms-layout/app/composables/useHeaderActions.ts +33 -11
  393. package/frontend-vue/layers/dms-layout/app/composables/useSidePanel.ts +41 -0
  394. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +15 -0
  395. package/frontend-vue/layers/dms-layout/app/plugins/notification-stream.client.ts +1 -7
  396. package/frontend-vue/layers/dms-layout/app/plugins/side-panel-host.ts +13 -0
  397. package/frontend-vue/layers/dms-layout/app/types/page.ts +11 -1
  398. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +43 -13
  399. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +43 -13
  400. package/frontend-vue/layers/dms-ui/app/build/components/form/FormLoadError.vue +45 -0
  401. package/frontend-vue/layers/dms-ui/app/build/components/modal/ModalEscHint.vue +28 -0
  402. package/frontend-vue/layers/dms-ui/app/build/components/table-view/IdentityCell.vue +36 -7
  403. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/cellHelpers.ts +57 -0
  404. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +19 -5
  405. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +22 -2
  406. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +118 -0
  407. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +5 -3
  408. package/frontend-vue/layers/dms-ui/app/build/composables/form/floatingSaveBars.ts +38 -0
  409. package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +66 -0
  410. package/frontend-vue/layers/dms-ui/app/build/composables/navigation/useNavBadges.ts +50 -9
  411. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +7 -1
  412. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/chrome.ts +2 -2
  413. package/frontend-vue/layers/dms-ui/app/build/types/tree.ts +14 -10
  414. package/frontend-vue/layers/dms-ui/app/build/utils/navBadge.ts +12 -0
  415. package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +4 -35
  416. package/frontend-vue/layers/dms-ui/app/build/utils/tone.ts +22 -0
  417. package/frontend-vue/layers/dms-ui/app/components/activity-feed/ActivityFeed.vue +117 -82
  418. package/frontend-vue/layers/dms-ui/app/components/blocks/BannerBlock.vue +8 -5
  419. package/frontend-vue/layers/dms-ui/app/components/blocks/KeyValueListBlock.vue +38 -18
  420. package/frontend-vue/layers/dms-ui/app/components/blocks/NavCardGridBlock.vue +12 -1
  421. package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +26 -23
  422. package/frontend-vue/layers/dms-ui/app/components/card/NavCard.vue +16 -1
  423. package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +7 -8
  424. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +28 -6
  425. package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +5 -1
  426. package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +4 -0
  427. package/frontend-vue/layers/dms-ui/app/components/status-pill/StatusPill.vue +7 -2
  428. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +7 -2
  429. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +48 -34
  430. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +13 -3
  431. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +16 -14
  432. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +16 -14
  433. package/frontend-vue/package.json +2 -2
  434. package/frontend-vue/pnpm-lock.yaml +5 -5
  435. package/frontend-vue/tests/activity-feed-fill-height.test.ts +107 -0
  436. package/frontend-vue/tests/category-nav-cards.test.ts +21 -13
  437. package/frontend-vue/tests/command-palette-assistant.test.ts +344 -0
  438. package/frontend-vue/tests/composed-text-blocks.test.ts +205 -0
  439. package/frontend-vue/tests/composed-text.test.ts +265 -0
  440. package/frontend-vue/tests/fixtures/dashboard-search-stub.ts +91 -0
  441. package/frontend-vue/tests/forgot-validation-resend.test.ts +234 -0
  442. package/frontend-vue/tests/form-display-options.test.ts +64 -0
  443. package/frontend-vue/tests/form-load-error.test.ts +160 -0
  444. package/frontend-vue/tests/form-saved-before-success.test.ts +110 -0
  445. package/frontend-vue/tests/github-star-prompt.test.ts +328 -0
  446. package/frontend-vue/tests/header-actions.test.ts +191 -0
  447. package/frontend-vue/tests/invites-edit-modal.test.ts +157 -0
  448. package/frontend-vue/tests/layout-banners.test.ts +56 -0
  449. package/frontend-vue/tests/member-role-picker.test.ts +26 -0
  450. package/frontend-vue/tests/menu-hidden-page.test.ts +93 -0
  451. package/frontend-vue/tests/nav-badge-tones.test.ts +274 -0
  452. package/frontend-vue/tests/nav-card-badge.test.ts +69 -0
  453. package/frontend-vue/tests/nav-card-grid-empty-category.test.ts +163 -0
  454. package/frontend-vue/tests/notification-popover-errors.test.ts +71 -37
  455. package/frontend-vue/tests/notification-preferences-groups.test.ts +268 -0
  456. package/frontend-vue/tests/notification-tone-contract.test.ts +70 -0
  457. package/frontend-vue/tests/page-fill-height.test.ts +9 -1
  458. package/frontend-vue/tests/page-no-visible-blocks.test.ts +202 -0
  459. package/frontend-vue/tests/profile-preferences-summary.test.ts +26 -2
  460. package/frontend-vue/tests/profile-security-summary.test.ts +121 -0
  461. package/frontend-vue/tests/role-permission-tree.test.ts +102 -8
  462. package/frontend-vue/tests/role-permissions.test.ts +178 -56
  463. package/frontend-vue/tests/side-panels.test.ts +470 -0
  464. package/frontend-vue/tests/signup-invitation-link.test.ts +71 -2
  465. package/frontend-vue/tests/table-actions-column-width.test.ts +51 -1
  466. package/frontend-vue/tests/table-cell-sublines.test.ts +466 -0
  467. package/frontend-vue/tests/table-metric-cells.test.ts +4 -0
  468. package/frontend-vue/tests/table-view-chrome.test.ts +2 -2
  469. package/frontend-vue/tests/vue-inventory.test.ts +1 -1
  470. package/package.json +2 -2
  471. package/skills/dms/REFERENCE.md +1 -1
  472. package/skills/dms/SKILL.md +7 -0
@@ -0,0 +1,268 @@
1
+ // @vitest-environment jsdom
2
+ import {
3
+ computed,
4
+ createApp,
5
+ defineComponent,
6
+ h,
7
+ nextTick,
8
+ onMounted,
9
+ ref,
10
+ type App,
11
+ type Component,
12
+ } from "vue";
13
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
14
+ import NotificationPreferencesForm from "../layers/dms-layout/app/build/components/pages/settings/notification/NotificationPreferencesForm.vue";
15
+ import { NOTIFICATION_GROUP_STORAGE_PREFIX } from "../layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen";
16
+
17
+ const preferences = vi.hoisted(() => ({
18
+ toggleCategory: vi.fn(),
19
+ toggleSubject: vi.fn(),
20
+ }));
21
+
22
+ vi.mock(
23
+ "../layers/dms-layout/app/build/composables/notification/useNotificationPreferences",
24
+ async () => {
25
+ const vue = await import("vue");
26
+ const system = { id: "system", labelKey: "System", icon: "i-ph-gear" };
27
+ const security = {
28
+ id: "security",
29
+ labelKey: "Security",
30
+ icon: "i-ph-shield",
31
+ };
32
+ const subjects = {
33
+ system: [
34
+ { id: "general", category: system, labelKey: "General" },
35
+ { id: "account", category: system, labelKey: "Account" },
36
+ ],
37
+ security: [
38
+ { id: "alerts", category: security, labelKey: "Security alerts" },
39
+ ],
40
+ } as Record<string, { id: string; labelKey: string }[]>;
41
+ return {
42
+ useNotificationPreferences: () => ({
43
+ ...preferences,
44
+ categories: vue.ref([system, security]),
45
+ subjectsOf: (id: string) => subjects[id] ?? [],
46
+ failedChanges: vue.ref(undefined),
47
+ saveState: vue.ref("idle"),
48
+ isLoading: vue.ref(false),
49
+ loadFailed: vue.ref(false),
50
+ isEnabled: () => true,
51
+ rowState: () => "idle",
52
+ toggleableSubjectsOf: (category: { id: string }) =>
53
+ subjects[category.id] ?? [],
54
+ retry: vi.fn(),
55
+ load: vi.fn(),
56
+ }),
57
+ };
58
+ },
59
+ );
60
+
61
+ vi.mock(
62
+ "../layers/dms-layout/app/composables/layout/useInstantSaveHeader",
63
+ () => ({
64
+ useInstantSaveHeader: vi.fn(),
65
+ }),
66
+ );
67
+
68
+ vi.mock("#dms-ui/app/build/components/skeleton/RowSkeleton.vue", async () => {
69
+ const vue = await import("vue");
70
+ return { default: vue.defineComponent({ render: () => vue.h("span") }) };
71
+ });
72
+
73
+ let app: App | undefined;
74
+
75
+ function mount(component: Component) {
76
+ app = createApp(defineComponent({ setup: () => () => h(component) }));
77
+ const passThrough = defineComponent({
78
+ setup:
79
+ (_props, { slots }) =>
80
+ () =>
81
+ h("div", [slots.default?.(), slots.footer?.()]),
82
+ });
83
+ for (const [name, stub] of Object.entries({
84
+ DmsSection: passThrough,
85
+ DmsEmptyState: passThrough,
86
+ DmsIconWell: defineComponent({ render: () => h("span") }),
87
+ UBadge: defineComponent({ render: () => h("span") }),
88
+ UIcon: defineComponent({ render: () => h("span") }),
89
+ // Attributes (aria-*) fall through to the native button.
90
+ UButton: defineComponent({
91
+ props: { label: String, icon: String },
92
+ emits: ["click"],
93
+ setup:
94
+ (props, { emit }) =>
95
+ () =>
96
+ h(
97
+ "button",
98
+ {
99
+ type: "button",
100
+ "data-icon": props.icon,
101
+ onClick: () => emit("click"),
102
+ },
103
+ props.label,
104
+ ),
105
+ }),
106
+ USwitch: defineComponent({
107
+ props: { modelValue: Boolean, disabled: Boolean },
108
+ emits: ["update:modelValue"],
109
+ setup:
110
+ (props, { emit }) =>
111
+ () =>
112
+ h("button", {
113
+ type: "button",
114
+ role: "switch",
115
+ "aria-checked": String(props.modelValue),
116
+ onClick: () => emit("update:modelValue", !props.modelValue),
117
+ }),
118
+ }),
119
+ // Shows its content while open, as Reka's collapsible does.
120
+ UCollapsible: defineComponent({
121
+ props: { open: Boolean },
122
+ setup:
123
+ (props, { slots }) =>
124
+ () =>
125
+ h("div", props.open ? slots.content?.() : []),
126
+ }),
127
+ })) {
128
+ app.component(name, stub);
129
+ }
130
+ const container = document.createElement("div");
131
+ document.body.append(container);
132
+ app.mount(container);
133
+ return container;
134
+ }
135
+
136
+ const chevronOf = (container: HTMLElement, categoryId: string) =>
137
+ container.querySelector<HTMLButtonElement>(
138
+ `button[aria-controls="notification-category-subjects-${categoryId}"]`,
139
+ )!;
140
+
141
+ const subjectNames = (container: HTMLElement) =>
142
+ [...container.querySelectorAll('[role="rowgroup"] [role="rowheader"]')].map(
143
+ (cell) => cell.textContent?.trim(),
144
+ );
145
+
146
+ const rowOf = (container: HTMLElement, categoryId: string) =>
147
+ chevronOf(container, categoryId).closest<HTMLElement>('[role="row"]')!;
148
+
149
+ beforeEach(() => {
150
+ localStorage.clear();
151
+ vi.stubGlobal("computed", computed);
152
+ vi.stubGlobal("ref", ref);
153
+ vi.stubGlobal("onMounted", onMounted);
154
+ vi.stubGlobal("useI18n", () => ({
155
+ t: (key: string, params?: Record<string, unknown>) =>
156
+ params?.name ? `${key}:${String(params.name)}` : key,
157
+ }));
158
+ });
159
+
160
+ afterEach(() => {
161
+ app?.unmount();
162
+ app = undefined;
163
+ document.body.innerHTML = "";
164
+ vi.unstubAllGlobals();
165
+ vi.clearAllMocks();
166
+ });
167
+
168
+ describe("notification preferences groups", () => {
169
+ it("starts every group folded, with its count on the group row", () => {
170
+ const container = mount(NotificationPreferencesForm as Component);
171
+
172
+ expect(subjectNames(container)).toEqual([]);
173
+ const chevron = chevronOf(container, "system");
174
+ expect(chevron.getAttribute("aria-expanded")).toBe("false");
175
+ expect(chevron.getAttribute("aria-label")).toBe(
176
+ "page.settings.notifications.category_expand:System",
177
+ );
178
+ expect(chevron.dataset.icon).toBe("i-ph-caret-right");
179
+ expect(rowOf(container, "system").textContent).toContain(
180
+ "page.settings.notifications.category_count",
181
+ );
182
+ });
183
+
184
+ it("unfolds and folds a group's subjects from its chevron", async () => {
185
+ const container = mount(NotificationPreferencesForm as Component);
186
+ const chevron = chevronOf(container, "system");
187
+
188
+ chevron.click();
189
+ await nextTick();
190
+ expect(chevron.getAttribute("aria-expanded")).toBe("true");
191
+ expect(chevron.dataset.icon).toBe("i-ph-caret-down");
192
+ expect(chevron.getAttribute("aria-label")).toBe(
193
+ "page.settings.notifications.category_collapse:System",
194
+ );
195
+ expect(subjectNames(container)).toEqual(["General", "Account"]);
196
+ expect(
197
+ container.querySelector("#notification-category-subjects-system"),
198
+ ).not.toBeNull();
199
+
200
+ chevron.click();
201
+ await nextTick();
202
+ expect(subjectNames(container)).toEqual([]);
203
+ });
204
+
205
+ it("unfolds a group from its title area too", async () => {
206
+ const container = mount(NotificationPreferencesForm as Component);
207
+
208
+ rowOf(container, "security")
209
+ .querySelector<HTMLElement>("[data-category-title]")!
210
+ .click();
211
+ await nextTick();
212
+ expect(subjectNames(container)).toEqual(["Security alerts"]);
213
+ });
214
+
215
+ it("keeps the master switch working on a folded group without unfolding it", async () => {
216
+ const container = mount(NotificationPreferencesForm as Component);
217
+
218
+ rowOf(container, "system")
219
+ .querySelector<HTMLButtonElement>('[role="switch"]')!
220
+ .click();
221
+ await nextTick();
222
+
223
+ expect(preferences.toggleCategory).toHaveBeenCalledWith(
224
+ expect.objectContaining({ id: "system" }),
225
+ false,
226
+ );
227
+ expect(chevronOf(container, "system").getAttribute("aria-expanded")).toBe(
228
+ "false",
229
+ );
230
+ expect(subjectNames(container)).toEqual([]);
231
+ });
232
+
233
+ it("remembers the open groups in the browser", async () => {
234
+ const first = mount(NotificationPreferencesForm as Component);
235
+ chevronOf(first, "security").click();
236
+ await nextTick();
237
+ expect(
238
+ localStorage.getItem(`${NOTIFICATION_GROUP_STORAGE_PREFIX}security`),
239
+ ).toBe("open");
240
+ app?.unmount();
241
+ document.body.innerHTML = "";
242
+
243
+ const second = mount(NotificationPreferencesForm as Component);
244
+ expect(chevronOf(second, "security").getAttribute("aria-expanded")).toBe(
245
+ "true",
246
+ );
247
+ expect(chevronOf(second, "system").getAttribute("aria-expanded")).toBe(
248
+ "false",
249
+ );
250
+ expect(subjectNames(second)).toEqual(["Security alerts"]);
251
+ });
252
+
253
+ it("folds every group when the browser storage is unavailable", async () => {
254
+ vi.spyOn(Storage.prototype, "getItem").mockImplementation(() => {
255
+ throw new Error("blocked");
256
+ });
257
+ vi.spyOn(Storage.prototype, "setItem").mockImplementation(() => {
258
+ throw new Error("blocked");
259
+ });
260
+ const container = mount(NotificationPreferencesForm as Component);
261
+ expect(subjectNames(container)).toEqual([]);
262
+
263
+ chevronOf(container, "system").click();
264
+ await nextTick();
265
+ expect(subjectNames(container)).toEqual(["General", "Account"]);
266
+ vi.restoreAllMocks();
267
+ });
268
+ });
@@ -0,0 +1,70 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { resolveNotificationTone as resolveOnServer } from "../../src/utils/notification-tones";
3
+ import {
4
+ resolveNotificationTone as resolveInInbox,
5
+ unreadToneWith,
6
+ } from "../layers/dms-layout/app/build/components/pages/settings/notification/notificationDisplay";
7
+ import type {
8
+ NotificationTone,
9
+ UserNotification,
10
+ } from "../layers/dms-layout/app/composables/notification/useNotifications";
11
+
12
+ // The server weighs the unread badge (the bell, the Notifications entry) with
13
+ // the tone the inbox draws each notification in: the two never disagree.
14
+
15
+ const STORED_TONES: Array<NotificationTone | null> = [
16
+ null,
17
+ "neutral",
18
+ "primary",
19
+ "success",
20
+ "warning",
21
+ "error",
22
+ ];
23
+
24
+ const row = (
25
+ tone: NotificationTone | null,
26
+ isRead: boolean,
27
+ ): UserNotification => ({
28
+ _id: "n1",
29
+ userId: "u1",
30
+ icon: "i-ph-bell",
31
+ title: "",
32
+ description: "",
33
+ params: null,
34
+ linkTo: null,
35
+ isRead,
36
+ categoryId: "system",
37
+ tone,
38
+ createdAt: "2026-10-09T10:00:00.000Z",
39
+ updatedAt: "2026-10-09T10:00:00.000Z",
40
+ });
41
+
42
+ it("resolves every stored tone, read or not, as the server does", () => {
43
+ for (const tone of STORED_TONES) {
44
+ for (const isRead of [false, true]) {
45
+ expect(resolveInInbox(row(tone, isRead)), `${tone}/${isRead}`).toBe(
46
+ resolveOnServer(tone, isRead),
47
+ );
48
+ }
49
+ }
50
+ });
51
+
52
+ it("draws an untoned unread notification in primary", () => {
53
+ expect(resolveInInbox(row(null, false))).toBe("primary");
54
+ });
55
+
56
+ describe("the unread badge's tone when a notification arrives", () => {
57
+ it("takes the stronger of the current tone and the new notification's", () => {
58
+ expect(unreadToneWith("warning", row("error", false))).toBe("error");
59
+ expect(unreadToneWith("error", row("success", false))).toBe("error");
60
+ expect(unreadToneWith("success", row(null, false))).toBe("primary");
61
+ });
62
+
63
+ it("takes the new notification's tone when nothing was unread", () => {
64
+ expect(unreadToneWith(undefined, row("warning", false))).toBe("warning");
65
+ });
66
+
67
+ it("keeps the current tone for a notification that arrives read", () => {
68
+ expect(unreadToneWith("success", row("error", true))).toBe("success");
69
+ });
70
+ });
@@ -44,6 +44,10 @@ vi.mock(
44
44
  "../layers/dms-layout/app/build/components/layout/RolePreviewBar.vue",
45
45
  () => ({ default: () => null }),
46
46
  );
47
+ vi.mock(
48
+ "../layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue",
49
+ () => ({ default: () => null }),
50
+ );
47
51
  vi.mock(
48
52
  "../layers/dms-layout/app/build/composables/dev-reload/useDevReloadHolder",
49
53
  async () => {
@@ -91,7 +95,7 @@ const FLOW_PAGE_MARKUP =
91
95
  '<div class="">' +
92
96
  '<section data-component="explorer" page-id="tools.explorer" layout-path="explorer"></section></div></div><!----><!--]--><!--]--><!--]--></div>' +
93
97
  PAGE_SKELETON_MARKUP +
94
- "<!----><!--]--><!--]--><!--]--></div></div><!--[--><!----><!--]--></div></div>";
98
+ "<!----><!--]--><!--]--><!--]--></div></div><!--[--><!----><!--]--></div><!----></div>";
95
99
 
96
100
  function withComponents(...ids: string[]): PageLayoutFixture {
97
101
  return {
@@ -154,6 +158,8 @@ function installRuntime(): void {
154
158
  showError: vi.fn(),
155
159
  validateRequiredQueryParams: vi.fn(),
156
160
  useDefinedFunctions: () => ({ getFunction: () => undefined }),
161
+ useDmsCookie: <T>(_name: string, options: { default?: () => T } = {}) =>
162
+ ref(options.default?.()),
157
163
  useDmsPageLoading: () => pageLoading,
158
164
  useDmsRoute: () => route,
159
165
  useDmsState: <T>(_key: string, init?: () => T) => ref(init?.()),
@@ -185,7 +191,9 @@ function installRuntime(): void {
185
191
  params: {},
186
192
  }),
187
193
  }),
194
+ useI18n: () => ({ t: (key: string) => key }),
188
195
  useTranslation: () => ({ processI18n: (value: string) => value }),
196
+ useUserSession: () => ({ loggedIn: ref(true) }),
189
197
  };
190
198
  Object.entries(runtime).forEach(([name, value]) =>
191
199
  vi.stubGlobal(name, value),
@@ -0,0 +1,202 @@
1
+ // @vitest-environment jsdom
2
+ import {
3
+ computed,
4
+ createSSRApp,
5
+ h,
6
+ inject,
7
+ onMounted,
8
+ onUnmounted,
9
+ provide,
10
+ ref,
11
+ toRef,
12
+ watch,
13
+ type FunctionalComponent,
14
+ } from "vue";
15
+ import { renderToString } from "vue/server-renderer";
16
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
17
+ import CatchAllPage from "../layers/dms-layout/app/pages/[...slug].vue";
18
+ import {
19
+ PAGE_FILL_HEIGHT_CLASSES,
20
+ providePageFillHeight,
21
+ usePageFillHeight,
22
+ } from "../layers/dms-layout/app/composables/layout/usePageFillHeight";
23
+
24
+ vi.mock(
25
+ "../layers/dms-layout/app/build/composables/dev-reload/useDevReloadHolder",
26
+ async () => {
27
+ const { ref } = await import("vue");
28
+ return { useDevReloading: () => ref(false) };
29
+ },
30
+ );
31
+
32
+ interface PageLayoutFixture {
33
+ components: Record<string, { componentName: string }>;
34
+ allComponentsHidden?: boolean;
35
+ }
36
+
37
+ const EMPTY_TITLE_KEY = "page.no_visible_blocks.title";
38
+ const EMPTY_DESCRIPTION_KEY = "page.no_visible_blocks.description";
39
+
40
+ const pageLayout = ref<PageLayoutFixture | null>(null);
41
+ const siteLayoutTree = ref<Record<string, unknown> | undefined>(undefined);
42
+ const navigateDms = vi.fn();
43
+
44
+ const EmptyState: FunctionalComponent<{
45
+ title: string;
46
+ description: string;
47
+ variant: string;
48
+ }> = (props) =>
49
+ h("aside", { "data-variant": props.variant }, [
50
+ h("p", props.title),
51
+ h("p", props.description),
52
+ ]);
53
+ EmptyState.props = ["title", "description", "variant"];
54
+
55
+ const PageComponent: FunctionalComponent<{ componentId: string }> = (props) =>
56
+ h("section", { "data-component": props.componentId });
57
+ PageComponent.props = ["componentId"];
58
+
59
+ function installRuntime(): void {
60
+ const metadata = { fullId: "examples.overview", hasAccess: true };
61
+ const runtime: Record<string, unknown> = {
62
+ computed,
63
+ inject,
64
+ onMounted,
65
+ onUnmounted,
66
+ provide,
67
+ ref,
68
+ toRef,
69
+ watch,
70
+ HTTP_FORBIDDEN: 403,
71
+ HTTP_INTERNAL_SERVER_ERROR: 500,
72
+ HTTP_NOT_FOUND: 404,
73
+ PAGE_FILL_HEIGHT_CLASSES,
74
+ providePageFillHeight,
75
+ usePageFillHeight,
76
+ createError: vi.fn(),
77
+ defineDmsPageMeta: vi.fn(),
78
+ firstAccessiblePagePath: vi.fn(),
79
+ navigateDms,
80
+ preloadPageLayoutComponents: vi.fn(),
81
+ providePageRealtime: vi.fn(),
82
+ resolveDmsComponent: () => "div",
83
+ runCustomValidation: vi.fn(),
84
+ showError: vi.fn(),
85
+ validateRequiredQueryParams: vi.fn(),
86
+ useDefinedFunctions: () => ({ getFunction: () => undefined }),
87
+ useDmsRoute: () => ({ path: "/examples/overview", query: {} }),
88
+ useHomepage: () => "/",
89
+ useI18n: () => ({ t: (key: string) => key }),
90
+ usePageLayout: async () => ({ pageLayout }),
91
+ usePageRealtime: () => ({}),
92
+ usePermissions: () => ({
93
+ permissions: ref([]),
94
+ refreshIfStale: async () => {},
95
+ }),
96
+ useSessionRecovery: () => ({
97
+ loggedIn: ref(true),
98
+ reconcileSession: async () => {},
99
+ redirectToAuth: async () => {},
100
+ }),
101
+ useSiteLayout: () => ({
102
+ siteLayout: ref({ pages: {} }),
103
+ siteLayoutTree,
104
+ loadingError: ref(null),
105
+ loadSiteLayout: async () => {},
106
+ refreshIfStale: async () => {},
107
+ findMatchingRoute: () => ({ metadata, params: {} }),
108
+ }),
109
+ };
110
+ Object.entries(runtime).forEach(([name, value]) =>
111
+ vi.stubGlobal(name, value),
112
+ );
113
+ }
114
+
115
+ async function renderPage(): Promise<HTMLElement> {
116
+ const app = createSSRApp(() => h(CatchAllPage));
117
+ app.component("DmsEmptyState", EmptyState);
118
+ app.component("DmsRecursiveComponent", PageComponent);
119
+ const host = document.createElement("div");
120
+ host.innerHTML = await renderToString(app);
121
+ return host;
122
+ }
123
+
124
+ beforeEach(() => {
125
+ siteLayoutTree.value = undefined;
126
+ navigateDms.mockClear();
127
+ installRuntime();
128
+ });
129
+
130
+ /** The menu tree with the page, and a page nested under it. */
131
+ function treeWithNestedPage(nestedHasAccess: boolean) {
132
+ const nested = {
133
+ id: "invites",
134
+ fullId: "examples.overview.invites",
135
+ fullSlug: "/examples/overview/invites",
136
+ layoutUrl: "/examples/overview/invites/pagelayout",
137
+ hasAccess: nestedHasAccess,
138
+ children: {},
139
+ childrenOrders: [],
140
+ };
141
+ const overview = {
142
+ id: "overview",
143
+ fullId: "examples.overview",
144
+ fullSlug: "/examples/overview",
145
+ layoutUrl: "/examples/overview/pagelayout",
146
+ hasAccess: false,
147
+ children: { invites: nested },
148
+ childrenOrders: ["invites"],
149
+ };
150
+ const examples = {
151
+ id: "examples",
152
+ fullId: "examples",
153
+ children: { overview },
154
+ childrenOrders: ["overview"],
155
+ };
156
+ return { children: { examples }, childrenOrders: ["examples"] };
157
+ }
158
+
159
+ afterEach(() => {
160
+ vi.unstubAllGlobals();
161
+ });
162
+
163
+ describe("a page served without any of its blocks", () => {
164
+ it("says there is nothing to show and who to ask", async () => {
165
+ pageLayout.value = { components: {}, allComponentsHidden: true };
166
+ const host = await renderPage();
167
+ const empty = host.querySelector("aside");
168
+ expect(empty?.getAttribute("data-variant")).toBe("no-access");
169
+ expect(empty?.textContent).toContain(EMPTY_TITLE_KEY);
170
+ expect(empty?.textContent).toContain(EMPTY_DESCRIPTION_KEY);
171
+ expect(host.querySelector(".dms-page-stack")).toBeNull();
172
+ });
173
+
174
+ it("stays put, even with a page nested under it the viewer can open", async () => {
175
+ pageLayout.value = { components: {}, allComponentsHidden: true };
176
+ siteLayoutTree.value = treeWithNestedPage(true);
177
+ const host = await renderPage();
178
+ expect(navigateDms).not.toHaveBeenCalled();
179
+ expect(host.querySelector("aside")).not.toBeNull();
180
+ });
181
+
182
+ it("draws the blocks it was served instead", async () => {
183
+ pageLayout.value = {
184
+ components: { stats: { componentName: "StatGroup" } },
185
+ };
186
+ const host = await renderPage();
187
+ expect(host.querySelector("aside")).toBeNull();
188
+ expect(host.querySelector("[data-component]")).not.toBeNull();
189
+ });
190
+
191
+ it("leaves a page that declares no block as it is", async () => {
192
+ pageLayout.value = { components: {} };
193
+ const host = await renderPage();
194
+ expect(host.querySelector("aside")).toBeNull();
195
+ });
196
+
197
+ it("stays quiet while the layout is still loading", async () => {
198
+ pageLayout.value = null;
199
+ const host = await renderPage();
200
+ expect(host.querySelector("aside")).toBeNull();
201
+ });
202
+ });
@@ -214,6 +214,7 @@ describe("access gating", () => {
214
214
  // The section itself: its composable with every data source stubbed.
215
215
  const navPages = ref<{ fullId: string; to: string }[]>([]);
216
216
  const unreadCount = ref(0);
217
+ const unreadTone = ref<string | undefined>(undefined);
217
218
  const bellPending = ref(true);
218
219
  const preferencesLoading = ref(true);
219
220
  const colorMode = ref<"light" | "dark" | "system">("dark");
@@ -278,6 +279,7 @@ vi.mock(
278
279
  () => ({
279
280
  useNotifications: () => ({
280
281
  unreadCount,
282
+ unreadTone,
281
283
  areCountsLoaded: {
282
284
  get value() {
283
285
  return !bellPending.value;
@@ -396,6 +398,7 @@ beforeEach(() => {
396
398
  { fullId: "settings.user.appearance", to: "/settings/user/appearance" },
397
399
  ];
398
400
  unreadCount.value = 0;
401
+ unreadTone.value = undefined;
399
402
  bellPending.value = true;
400
403
  preferencesLoading.value = true;
401
404
  colorMode.value = "dark";
@@ -445,6 +448,7 @@ describe("Preferences & access section", () => {
445
448
  expect(skeletonRows()).toBe(2);
446
449
 
447
450
  unreadCount.value = 3;
451
+ unreadTone.value = "error";
448
452
  bellPending.value = false;
449
453
  resolveAccess({
450
454
  roles: ["Admin"],
@@ -465,12 +469,32 @@ describe("Preferences & access section", () => {
465
469
  "Dark theme · normal density · increased contrast on",
466
470
  ),
467
471
  ]);
468
- // Unread notifications turn the bell amber.
472
+ // The bell takes the unread badge's tone, as the server weighs it.
469
473
  expect(
470
474
  host
471
475
  .querySelector("[data-icon='i-ph-bell-ringing']")
472
476
  ?.getAttribute("data-tone"),
473
- ).toBe("warning");
477
+ ).toBe("error");
478
+ });
479
+
480
+ it("colours the notifications row with the unread badge's tone, quiet without unread", async () => {
481
+ preferencesLoading.value = false;
482
+ bellPending.value = false;
483
+ unreadCount.value = 2;
484
+ unreadTone.value = "warning";
485
+ await mountSection();
486
+ const bellTone = () =>
487
+ host.querySelector("[data-icon^='i-ph-bell']")?.getAttribute("data-tone");
488
+ expect(bellTone()).toBe("warning");
489
+
490
+ unreadTone.value = "neutral";
491
+ await flush();
492
+ expect(bellTone()).toBe("muted");
493
+
494
+ unreadCount.value = 0;
495
+ unreadTone.value = undefined;
496
+ await flush();
497
+ expect(bellTone()).toBe("muted");
474
498
  });
475
499
 
476
500
  it("hides the rows of pages the user cannot open and keeps Your access informative", async () => {