@antelopejs/dms 0.8.3 → 0.9.1

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 (424) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/data/module-store/catalog.json +71 -0
  3. package/dist/config.d.ts +7 -0
  4. package/dist/config.js +8 -0
  5. package/dist/config.js.map +1 -1
  6. package/dist/implementations/dms/frontend-backend-proxies.d.ts +3 -0
  7. package/dist/implementations/dms/frontend-backend-proxies.js +39 -0
  8. package/dist/implementations/dms/frontend-backend-proxies.js.map +1 -0
  9. package/dist/implementations/dms/frontend-bootstrap.d.ts +1 -0
  10. package/dist/implementations/dms/frontend-bootstrap.js +13 -0
  11. package/dist/implementations/dms/frontend-bootstrap.js.map +1 -1
  12. package/dist/implementations/dms/frontend-modules.d.ts +6 -2
  13. package/dist/implementations/dms/frontend-modules.js +37 -8
  14. package/dist/implementations/dms/frontend-modules.js.map +1 -1
  15. package/dist/implementations/dms/frontend-ready.d.ts +8 -0
  16. package/dist/implementations/dms/frontend-ready.js +34 -0
  17. package/dist/implementations/dms/frontend-ready.js.map +1 -0
  18. package/dist/implementations/dms/manifest.d.ts +7 -0
  19. package/dist/implementations/dms/manifest.js +13 -0
  20. package/dist/implementations/dms/manifest.js.map +1 -1
  21. package/dist/implementations/dms/menu-categories.d.ts +16 -0
  22. package/dist/implementations/dms/menu-categories.js +30 -0
  23. package/dist/implementations/dms/menu-categories.js.map +1 -0
  24. package/dist/implementations/dms/nav-badges.d.ts +0 -8
  25. package/dist/implementations/dms/nav-badges.js +0 -16
  26. package/dist/implementations/dms/nav-badges.js.map +1 -1
  27. package/dist/implementations/dms/page.d.ts +13 -5
  28. package/dist/implementations/dms/page.js +72 -41
  29. package/dist/implementations/dms/page.js.map +1 -1
  30. package/dist/implementations/dms-html-render/index.js +5 -2
  31. package/dist/implementations/dms-html-render/index.js.map +1 -1
  32. package/dist/implementations/dms-notifications/index.d.ts +4 -3
  33. package/dist/implementations/dms-notifications/index.js +14 -6
  34. package/dist/implementations/dms-notifications/index.js.map +1 -1
  35. package/dist/pages/settings/settings-index.js +3 -2
  36. package/dist/pages/settings/settings-index.js.map +1 -1
  37. package/dist/pages/settings/users/invites.js +2 -2
  38. package/dist/pages/settings/users/invites.js.map +1 -1
  39. package/dist/pages/settings/users/member-change-notifications.js +17 -6
  40. package/dist/pages/settings/users/member-change-notifications.js.map +1 -1
  41. package/dist/pages/settings/users/member-invite-form.d.ts +3 -2
  42. package/dist/pages/settings/users/member-invite-form.js +7 -4
  43. package/dist/pages/settings/users/member-invite-form.js.map +1 -1
  44. package/dist/pages/settings/users/member-roles-field.js +2 -1
  45. package/dist/pages/settings/users/member-roles-field.js.map +1 -1
  46. package/dist/pages/settings/users/members.js +4 -1
  47. package/dist/pages/settings/users/members.js.map +1 -1
  48. package/dist/pages/settings/users/notifications.d.ts +6 -0
  49. package/dist/pages/settings/users/notifications.js +23 -15
  50. package/dist/pages/settings/users/notifications.js.map +1 -1
  51. package/dist/pages/settings/users/profile.js +1 -1
  52. package/dist/pages/settings/users/profile.js.map +1 -1
  53. package/dist/pages/settings/users/region-form.d.ts +1 -1
  54. package/dist/pages/settings/users/region-form.js +2 -1
  55. package/dist/pages/settings/users/region-form.js.map +1 -1
  56. package/dist/pages/settings/users/role-editor-store.d.ts +3 -1
  57. package/dist/pages/settings/users/role-editor-store.js +17 -4
  58. package/dist/pages/settings/users/role-editor-store.js.map +1 -1
  59. package/dist/pages/settings/users/roles.js +1 -0
  60. package/dist/pages/settings/users/roles.js.map +1 -1
  61. package/dist/realtime/registry.d.ts +1 -0
  62. package/dist/realtime/registry.js +5 -0
  63. package/dist/realtime/registry.js.map +1 -1
  64. package/dist/routes/index.d.ts +1 -0
  65. package/dist/routes/index.js +1 -0
  66. package/dist/routes/index.js.map +1 -1
  67. package/dist/routes/realtime.d.ts +5 -1
  68. package/dist/routes/realtime.js +16 -9
  69. package/dist/routes/realtime.js.map +1 -1
  70. package/dist/routes/store.d.ts +21 -0
  71. package/dist/routes/store.js +130 -0
  72. package/dist/routes/store.js.map +1 -0
  73. package/dist/store/activity.d.ts +2 -0
  74. package/dist/store/activity.js +12 -0
  75. package/dist/store/activity.js.map +1 -0
  76. package/dist/store/cache.d.ts +8 -0
  77. package/dist/store/cache.js +29 -0
  78. package/dist/store/cache.js.map +1 -0
  79. package/dist/store/catalog.d.ts +4 -0
  80. package/dist/store/catalog.js +69 -0
  81. package/dist/store/catalog.js.map +1 -0
  82. package/dist/store/compatibility.d.ts +3 -0
  83. package/dist/store/compatibility.js +45 -0
  84. package/dist/store/compatibility.js.map +1 -0
  85. package/dist/store/declarations.d.ts +18 -0
  86. package/dist/store/declarations.js +116 -0
  87. package/dist/store/declarations.js.map +1 -0
  88. package/dist/store/frontend-compatibility.d.ts +18 -0
  89. package/dist/store/frontend-compatibility.js +93 -0
  90. package/dist/store/frontend-compatibility.js.map +1 -0
  91. package/dist/store/interfaces.d.ts +6 -0
  92. package/dist/store/interfaces.js +126 -0
  93. package/dist/store/interfaces.js.map +1 -0
  94. package/dist/store/listing.d.ts +2 -0
  95. package/dist/store/listing.js +180 -0
  96. package/dist/store/listing.js.map +1 -0
  97. package/dist/store/operations.d.ts +8 -0
  98. package/dist/store/operations.js +254 -0
  99. package/dist/store/operations.js.map +1 -0
  100. package/dist/store/registry.d.ts +4 -0
  101. package/dist/store/registry.js +61 -0
  102. package/dist/store/registry.js.map +1 -0
  103. package/dist/store/requirements.d.ts +14 -0
  104. package/dist/store/requirements.js +81 -0
  105. package/dist/store/requirements.js.map +1 -0
  106. package/dist/store/runtime.d.ts +11 -0
  107. package/dist/store/runtime.js +78 -0
  108. package/dist/store/runtime.js.map +1 -0
  109. package/dist/store/tarball.d.ts +2 -0
  110. package/dist/store/tarball.js +80 -0
  111. package/dist/store/tarball.js.map +1 -0
  112. package/dist/store/types.d.ts +98 -0
  113. package/dist/store/types.js +3 -0
  114. package/dist/store/types.js.map +1 -0
  115. package/dist/test/integration/account-deletion.test.js +2 -2
  116. package/dist/test/integration/account-deletion.test.js.map +1 -1
  117. package/dist/test/integration/auth/auth-key-rotation.test.js +2 -1
  118. package/dist/test/integration/auth/auth-key-rotation.test.js.map +1 -1
  119. package/dist/test/integration/auth/current-password.test.js +2 -1
  120. package/dist/test/integration/auth/current-password.test.js.map +1 -1
  121. package/dist/test/integration/auth/session-revocation.test.js +2 -1
  122. package/dist/test/integration/auth/session-revocation.test.js.map +1 -1
  123. package/dist/test/integration/auth/sign-in-country.test.js +2 -1
  124. package/dist/test/integration/auth/sign-in-country.test.js.map +1 -1
  125. package/dist/test/integration/auth/two-factor-codes.test.js +2 -1
  126. package/dist/test/integration/auth/two-factor-codes.test.js.map +1 -1
  127. package/dist/test/integration/auth/two-factor-password.test.js +3 -2
  128. package/dist/test/integration/auth/two-factor-password.test.js.map +1 -1
  129. package/dist/test/integration/email-change.test.js +2 -1
  130. package/dist/test/integration/email-change.test.js.map +1 -1
  131. package/dist/test/integration/files/component-files.test.js +2 -2
  132. package/dist/test/integration/files/component-files.test.js.map +1 -1
  133. package/dist/test/integration/member-settings-access.test.js +8 -7
  134. package/dist/test/integration/member-settings-access.test.js.map +1 -1
  135. package/dist/test/integration/menu-access-events.test.d.ts +1 -0
  136. package/dist/test/integration/menu-access-events.test.js +148 -0
  137. package/dist/test/integration/menu-access-events.test.js.map +1 -0
  138. package/dist/test/integration/notification-inbox.test.js +4 -1
  139. package/dist/test/integration/notification-inbox.test.js.map +1 -1
  140. package/dist/test/integration/notification-locked-subjects.test.js +3 -2
  141. package/dist/test/integration/notification-locked-subjects.test.js.map +1 -1
  142. package/dist/test/integration/notification-unread-badge.test.js +51 -5
  143. package/dist/test/integration/notification-unread-badge.test.js.map +1 -1
  144. package/dist/test/integration/page-layout-route-guard.test.d.ts +1 -0
  145. package/dist/test/integration/page-layout-route-guard.test.js +56 -0
  146. package/dist/test/integration/page-layout-route-guard.test.js.map +1 -0
  147. package/dist/test/integration/permission-warnings.test.js +3 -2
  148. package/dist/test/integration/permission-warnings.test.js.map +1 -1
  149. package/dist/test/integration/removed-member-notifications.test.js +3 -2
  150. package/dist/test/integration/removed-member-notifications.test.js.map +1 -1
  151. package/dist/test/integration/role-delete-reassign.test.js +2 -1
  152. package/dist/test/integration/role-delete-reassign.test.js.map +1 -1
  153. package/dist/test/integration/role-permission-ancestors.test.js +2 -1
  154. package/dist/test/integration/role-permission-ancestors.test.js.map +1 -1
  155. package/dist/test/unit/frontend-bootstrap.test.js +13 -0
  156. package/dist/test/unit/frontend-bootstrap.test.js.map +1 -1
  157. package/dist/test/unit/implementations/dms/category-slug-registry.test.js +14 -5
  158. package/dist/test/unit/implementations/dms/category-slug-registry.test.js.map +1 -1
  159. package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.d.ts +1 -0
  160. package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.js +66 -0
  161. package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.js.map +1 -0
  162. package/dist/test/unit/implementations/dms/manifest-strip.test.js +26 -0
  163. package/dist/test/unit/implementations/dms/manifest-strip.test.js.map +1 -1
  164. package/dist/test/unit/implementations/dms/nav-badges.test.js +23 -9
  165. package/dist/test/unit/implementations/dms/nav-badges.test.js.map +1 -1
  166. package/dist/test/unit/implementations/dms-html-render/generate-html.test.js +22 -4
  167. package/dist/test/unit/implementations/dms-html-render/generate-html.test.js.map +1 -1
  168. package/dist/test/unit/interfaces/dms/page-layout-query.test.js +12 -0
  169. package/dist/test/unit/interfaces/dms/page-layout-query.test.js.map +1 -1
  170. package/dist/test/unit/interfaces/dms/page-teardown.test.js +1 -1
  171. package/dist/test/unit/interfaces/dms/page-teardown.test.js.map +1 -1
  172. package/dist/test/unit/pages/settings/member-roles-field.test.js +1 -1
  173. package/dist/test/unit/pages/settings/member-roles-field.test.js.map +1 -1
  174. package/dist/test/unit/pages/settings/role-delete-confirm.test.js.map +1 -1
  175. package/dist/test/unit/realtime/menu-topics.test.d.ts +1 -0
  176. package/dist/test/unit/realtime/menu-topics.test.js +26 -0
  177. package/dist/test/unit/realtime/menu-topics.test.js.map +1 -0
  178. package/dist/test/unit/store/store.test.d.ts +1 -0
  179. package/dist/test/unit/store/store.test.js +127 -0
  180. package/dist/test/unit/store/store.test.js.map +1 -0
  181. package/dist/validation/store.schema.d.ts +39 -0
  182. package/dist/validation/store.schema.js +27 -0
  183. package/dist/validation/store.schema.js.map +1 -0
  184. package/frontend-vue/deferred-plugins.ts +3 -4
  185. package/frontend-vue/dms.email.ts +5 -1
  186. package/frontend-vue/dms.frontend.ts +15 -5
  187. package/frontend-vue/dms.server.ts +74 -0
  188. package/frontend-vue/layers/dms-auth/app/composables/useLogout.ts +1 -1
  189. package/frontend-vue/layers/dms-auth/app/composables/usePostLoginRedirect.ts +16 -3
  190. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/2fa.vue +3 -1
  191. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/backup.vue +3 -1
  192. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/forgot-validation.vue +1 -1
  193. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/oauth-complete.vue +26 -3
  194. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/recover.vue +1 -1
  195. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +22 -25
  196. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/validate.vue +1 -1
  197. package/frontend-vue/layers/dms-auth/app/middleware/auth.ts +5 -7
  198. package/frontend-vue/layers/dms-auth/app/plugins/permissions-preload.ts +17 -0
  199. package/frontend-vue/layers/dms-auth/server/accounts.ts +135 -0
  200. package/frontend-vue/layers/dms-auth/server/actions.ts +251 -0
  201. package/frontend-vue/layers/dms-auth/server/oauth.ts +260 -0
  202. package/frontend-vue/layers/dms-auth/server/session.ts +158 -0
  203. package/frontend-vue/layers/dms-core/app/build/utils/asyncDataScope.ts +47 -0
  204. package/frontend-vue/layers/dms-core/app/build/utils/permission-preview.ts +6 -2
  205. package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +148 -0
  206. package/frontend-vue/layers/dms-core/app/composables/auth/useAuthFetch.ts +7 -20
  207. package/frontend-vue/layers/dms-core/app/composables/auth/useCurrentUser.ts +2 -2
  208. package/frontend-vue/layers/dms-core/app/composables/auth/useMultiAccount.ts +18 -5
  209. package/frontend-vue/layers/dms-core/app/composables/auth/usePermissions.ts +35 -6
  210. package/frontend-vue/layers/dms-core/app/composables/auth/useSessionRecovery.ts +1 -1
  211. package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +20 -14
  212. package/frontend-vue/layers/dms-core/app/composables/period/periodClock.ts +121 -0
  213. package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +42 -0
  214. package/frontend-vue/layers/dms-core/app/composables/period/periodTimeZone.ts +31 -0
  215. package/frontend-vue/layers/dms-core/app/composables/period/presetResolvers.ts +48 -19
  216. package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +33 -4
  217. package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +78 -25
  218. package/frontend-vue/layers/dms-core/app/composables/realtime/usePageRealtime.ts +3 -0
  219. package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +36 -6
  220. package/frontend-vue/layers/dms-core/app/composables/user/useUserRegionalPreferences.ts +1 -1
  221. package/frontend-vue/layers/dms-core/app/plugins/async-data-scope.ts +26 -0
  222. package/frontend-vue/layers/dms-core/app/utils/pageRoutes.ts +22 -0
  223. package/frontend-vue/layers/dms-core/app/utils/regional.ts +36 -3
  224. package/frontend-vue/layers/dms-core/app/utils/trailingDeduper.ts +10 -1
  225. package/frontend-vue/layers/dms-core/shared/types/app-config.ts +48 -4
  226. package/frontend-vue/layers/dms-layout/app/app.config.ts +42 -23
  227. package/frontend-vue/layers/dms-layout/app/assets/css/main.css +72 -20
  228. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardFrame.vue +9 -0
  229. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +0 -3
  230. package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +9 -3
  231. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreDependencyPicker.vue +136 -0
  232. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +98 -0
  233. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +247 -29
  234. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +19 -2
  235. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/members/MemberRolePicker.vue +13 -12
  236. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +17 -7
  237. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationPreferencesForm.vue +0 -1
  238. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfilePersonalInfo.vue +36 -33
  239. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfileSecuritySummary.vue +1 -2
  240. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-types.ts +5 -4
  241. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecuritySessions.vue +35 -28
  242. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityStatus.vue +0 -1
  243. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityTwoFactor.vue +1 -1
  244. package/frontend-vue/layers/dms-layout/app/build/composables/dev-reload/useDevReloadHolder.ts +7 -6
  245. package/frontend-vue/layers/dms-layout/app/build/composables/layout/sidePanelState.ts +1 -1
  246. package/frontend-vue/layers/dms-layout/app/build/composables/navigation/useVisitNavBadges.ts +26 -0
  247. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationCatalog.ts +25 -23
  248. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationPreferences.ts +44 -23
  249. package/frontend-vue/layers/dms-layout/app/build/composables/security/useSessionHandoff.ts +1 -1
  250. package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/preferencesSummary.ts +1 -1
  251. package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useAccountData.ts +3 -2
  252. package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfileAvatar.ts +1 -18
  253. package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfilePreferences.ts +19 -23
  254. package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityFormat.ts +6 -3
  255. package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityOverview.ts +13 -18
  256. package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +186 -120
  257. package/frontend-vue/layers/dms-layout/app/build/utils/module-store-install.ts +146 -0
  258. package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +8 -6
  259. package/frontend-vue/layers/dms-layout/app/build/utils/publicErrorMessage.ts +19 -0
  260. package/frontend-vue/layers/dms-layout/app/components/navigation/CollapsedMenuItem.vue +0 -11
  261. package/frontend-vue/layers/dms-layout/app/components/navigation/NavigationMenu.vue +3 -13
  262. package/frontend-vue/layers/dms-layout/app/composables/general/useSystemState.ts +7 -11
  263. package/frontend-vue/layers/dms-layout/app/composables/notification/useNotifications.ts +103 -22
  264. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +109 -0
  265. package/frontend-vue/layers/dms-layout/app/composables/page/useModulesListing.ts +25 -102
  266. package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +0 -10
  267. package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +177 -96
  268. package/frontend-vue/layers/dms-layout/app/composables/profile/useUserLanguage.ts +3 -2
  269. package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +341 -62
  270. package/frontend-vue/layers/dms-layout/app/emails/components/EmailLayout.vue +31 -11
  271. package/frontend-vue/layers/dms-layout/app/error.vue +5 -2
  272. package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +1 -1
  273. package/frontend-vue/layers/dms-layout/app/middleware/page-leave-guard.global.ts +2 -2
  274. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +12 -9
  275. package/frontend-vue/layers/dms-layout/app/plugins/dms-menu-sync.client.ts +31 -3
  276. package/frontend-vue/layers/dms-layout/app/plugins/favicon.ts +58 -0
  277. package/frontend-vue/layers/dms-layout/app/plugins/notification-stream.client.ts +15 -7
  278. package/frontend-vue/layers/dms-layout/app/plugins/profile-sync.client.ts +5 -1
  279. package/frontend-vue/layers/dms-layout/app/plugins/regional-preferences.ts +10 -4
  280. package/frontend-vue/layers/dms-layout/app/plugins/time-zone-cookie.client.ts +12 -0
  281. package/frontend-vue/layers/dms-layout/app/types/module-store.ts +134 -0
  282. package/frontend-vue/layers/dms-layout/app/types/page.ts +13 -0
  283. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +107 -9
  284. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +107 -9
  285. package/frontend-vue/layers/dms-layout/server/html-render.ts +58 -0
  286. package/frontend-vue/layers/dms-layout/server/render-token.ts +95 -0
  287. package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/useOnboarding.ts +25 -21
  288. package/frontend-vue/layers/dms-onboarding/app/middleware/onboarding.global.ts +1 -1
  289. package/frontend-vue/layers/dms-onboarding/app/plugins/onboarding-preload.ts +9 -0
  290. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +4 -1
  291. package/frontend-vue/layers/dms-ui/app/build/composables/files/useFileMetadata.ts +162 -0
  292. package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +23 -4
  293. package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageDataRefresh.ts +101 -0
  294. package/frontend-vue/layers/dms-ui/app/build/composables/navigation/useNavBadges.ts +33 -6
  295. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +19 -16
  296. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodQuery.ts +40 -20
  297. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +6 -8
  298. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableListData.ts +42 -14
  299. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +19 -8
  300. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +1 -1
  301. package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +5 -2
  302. package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +9 -9
  303. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +20 -18
  304. package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +2 -1
  305. package/frontend-vue/layers/dms-ui/app/components/form/components/File.vue +26 -132
  306. package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +40 -105
  307. package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +2 -0
  308. package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +4 -4
  309. package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +9 -13
  310. package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +2 -1
  311. package/frontend-vue/layers/dms-ui/app/components/table-view/FilePreview.vue +12 -24
  312. package/frontend-vue/layers/dms-ui/app/components/table-view/ImagePreview.vue +17 -29
  313. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +7 -3
  314. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +20 -8
  315. package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +8 -11
  316. package/frontend-vue/layers/dms-ui/app/composables/chart/useChartFetch.ts +146 -182
  317. package/frontend-vue/layers/dms-ui/app/composables/live/usePageLiveRefresh.ts +8 -2
  318. package/frontend-vue/layers/dms-ui/app/composables/useFileReadUrls.ts +11 -99
  319. package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +32 -3
  320. package/frontend-vue/package.json +3 -3
  321. package/frontend-vue/pnpm-lock.yaml +16 -22
  322. package/frontend-vue/server/private-options.ts +27 -0
  323. package/frontend-vue/tests/async-data-scope.test.ts +121 -0
  324. package/frontend-vue/tests/auth-middleware-error.test.ts +3 -3
  325. package/frontend-vue/tests/banner-block-fetch.test.ts +3 -0
  326. package/frontend-vue/tests/block-fetch-options-forwarding.test.ts +3 -0
  327. package/frontend-vue/tests/block-items-period-realtime.test.ts +3 -0
  328. package/frontend-vue/tests/block-not-found.test.ts +16 -2
  329. package/frontend-vue/tests/block-page-refresh.test.ts +3 -0
  330. package/frontend-vue/tests/block-route-params.test.ts +3 -0
  331. package/frontend-vue/tests/branding-defaults.test.ts +34 -0
  332. package/frontend-vue/tests/builtin-emails.test.ts +23 -2
  333. package/frontend-vue/tests/chart-live-pause.test.ts +4 -1
  334. package/frontend-vue/tests/chart-value-propagation.test.ts +4 -1
  335. package/frontend-vue/tests/composed-text-blocks.test.ts +3 -0
  336. package/frontend-vue/tests/email-layout.test.ts +66 -4
  337. package/frontend-vue/tests/error-page-message.test.ts +25 -2
  338. package/frontend-vue/tests/favicon.test.ts +131 -0
  339. package/frontend-vue/tests/file-fields-ssr.test.ts +145 -0
  340. package/frontend-vue/tests/file-read-urls.test.ts +145 -34
  341. package/frontend-vue/tests/file-unavailable.test.ts +5 -1
  342. package/frontend-vue/tests/fixtures/dataEngine.ts +44 -0
  343. package/frontend-vue/tests/form-load-error.test.ts +55 -1
  344. package/frontend-vue/tests/helpers/api-data-runtime.ts +77 -0
  345. package/frontend-vue/tests/image-hydration-ids.test.ts +21 -16
  346. package/frontend-vue/tests/image-reference-preservation.test.ts +59 -53
  347. package/frontend-vue/tests/input-number-adjust.test.ts +14 -0
  348. package/frontend-vue/tests/key-value-list-copy.test.ts +3 -0
  349. package/frontend-vue/tests/member-role-picker.test.ts +4 -2
  350. package/frontend-vue/tests/menu-sync-reconnect.test.ts +47 -0
  351. package/frontend-vue/tests/metadata-retry.test.ts +5 -1
  352. package/frontend-vue/tests/module-routing.test.ts +2 -5
  353. package/frontend-vue/tests/module-store-catalog.test.ts +270 -51
  354. package/frontend-vue/tests/modules-catalog.test.ts +17 -4
  355. package/frontend-vue/tests/modules-listing-transfer.test.ts +95 -0
  356. package/frontend-vue/tests/multi-account-validate.test.ts +23 -1
  357. package/frontend-vue/tests/nav-badge-tones.test.ts +66 -3
  358. package/frontend-vue/tests/notification-counts-from-writes.test.ts +223 -0
  359. package/frontend-vue/tests/notification-counts-transfer.test.ts +88 -0
  360. package/frontend-vue/tests/notification-inbox-display.test.ts +57 -5
  361. package/frontend-vue/tests/notification-popover-errors.test.ts +21 -1
  362. package/frontend-vue/tests/oauth-complete-handoff.test.ts +209 -0
  363. package/frontend-vue/tests/onboarding-middleware.test.ts +1 -1
  364. package/frontend-vue/tests/onboarding-transfer.test.ts +106 -0
  365. package/frontend-vue/tests/page-data-refresh.test.ts +141 -0
  366. package/frontend-vue/tests/page-fill-height.test.ts +16 -3
  367. package/frontend-vue/tests/page-no-visible-blocks.test.ts +12 -2
  368. package/frontend-vue/tests/page-realtime-remount.test.ts +1 -1
  369. package/frontend-vue/tests/page-record-label.test.ts +0 -7
  370. package/frontend-vue/tests/page-validation-redirect.test.ts +12 -2
  371. package/frontend-vue/tests/period-hydration-now.test.ts +185 -0
  372. package/frontend-vue/tests/period-live-pause.test.ts +3 -0
  373. package/frontend-vue/tests/period-reader-zone.test.ts +137 -0
  374. package/frontend-vue/tests/period-scope-first-fetch.test.ts +17 -1
  375. package/frontend-vue/tests/period-ssr.test.ts +195 -0
  376. package/frontend-vue/tests/permission-preview-partial.test.ts +0 -2
  377. package/frontend-vue/tests/permissions-transfer.test.ts +194 -0
  378. package/frontend-vue/tests/post-login-redirect.test.ts +10 -5
  379. package/frontend-vue/tests/profile-personal-info-ssr.test.ts +150 -0
  380. package/frontend-vue/tests/profile-preferences-summary.test.ts +15 -11
  381. package/frontend-vue/tests/realtime-reconnect.test.ts +22 -3
  382. package/frontend-vue/tests/roles-name-errors.test.ts +1 -1
  383. package/frontend-vue/tests/row-side-panel.test.ts +1 -1
  384. package/frontend-vue/tests/security-sessions-load-error.test.ts +3 -1
  385. package/frontend-vue/tests/server-routes-vs-pages.test.ts +26 -0
  386. package/frontend-vue/tests/server-routes.test.ts +582 -0
  387. package/frontend-vue/tests/session-handoff.test.ts +1 -1
  388. package/frontend-vue/tests/settings-reads-ssr.test.ts +232 -0
  389. package/frontend-vue/tests/side-panels.test.ts +8 -1
  390. package/frontend-vue/tests/sign-in-exit-history.test.ts +219 -0
  391. package/frontend-vue/tests/signup-invitation-link.test.ts +56 -8
  392. package/frontend-vue/tests/site-layout-categories.test.ts +42 -0
  393. package/frontend-vue/tests/site-layout-hydration.test.ts +3 -3
  394. package/frontend-vue/tests/site-layout-rendered.test.ts +103 -0
  395. package/frontend-vue/tests/site-layout-resync.test.ts +87 -0
  396. package/frontend-vue/tests/ssr-guard-preload.test.ts +188 -0
  397. package/frontend-vue/tests/table-cell-tones.test.ts +36 -1
  398. package/frontend-vue/tests/table-data-ssr.test.ts +195 -0
  399. package/frontend-vue/tests/table-list-data-requests.test.ts +154 -0
  400. package/frontend-vue/tests/table-retry-loading.test.ts +37 -0
  401. package/frontend-vue/tests/table-source-navigation.test.ts +0 -1
  402. package/frontend-vue/tests/table-source-period-scope.test.ts +0 -1
  403. package/frontend-vue/tests/table-view-views.test.ts +1 -1
  404. package/frontend-vue/tests/top-list-card-actions.test.ts +3 -0
  405. package/frontend-vue/tests/top-list-card-period-badge-ssr.test.ts +212 -0
  406. package/frontend-vue/tests/top-list-card-period-badge.test.ts +79 -0
  407. package/frontend-vue/tests/top-list-card-value-suffix.test.ts +3 -0
  408. package/frontend-vue/tests/trailing-deduper.test.ts +22 -0
  409. package/frontend-vue/tests/use-api-data-update.test.ts +119 -0
  410. package/frontend-vue/tests/use-api-data.test.ts +410 -0
  411. package/frontend-vue/tests/user-language.test.ts +8 -5
  412. package/frontend-vue/tests/widget-label-i18n.test.ts +4 -1
  413. package/frontend-vue/vitest.config.ts +6 -0
  414. package/package.json +2 -2
  415. package/skills/dms/SKILL.md +2 -1
  416. package/skills/dms-module/SKILL.md +1 -1
  417. package/skills/dms-pages/SKILL.md +4 -1
  418. package/frontend-vue/layers/dms-core/app/build/composables/page/usePageRecordLabel.ts +0 -9
  419. package/frontend-vue/layers/dms-layout/app/composables/page/usePrefetch.ts +0 -21
  420. package/frontend-vue/layers/dms-ui/app/build/composables/form/useControlError.ts +0 -10
  421. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useServerRenderedAsyncData.ts +0 -23
  422. package/frontend-vue/tests/control-error-path.test.ts +0 -11
  423. package/frontend-vue/tests/modules-listing-per-request.test.ts +0 -104
  424. package/frontend-vue/tests/page-layout-background-refresh.test.ts +0 -321
@@ -1,23 +1,175 @@
1
1
  <script setup lang="ts">
2
2
  import ModuleTile from "./ModuleTile.vue";
3
+ import ModuleStoreRequirements from "./ModuleStoreRequirements.vue";
3
4
  import {
4
- MODULE_INSTALL_COMMAND,
5
- moduleInstallCommand,
6
5
  type ModuleStoreEntry,
6
+ type StoreAction,
7
+ type StoreActionBlock,
8
+ storeActionBlock,
9
+ storeActions,
10
+ storeTileStatus,
7
11
  } from "../../../utils/module-store-catalog";
12
+ import type {
13
+ StoreOperationKind,
14
+ StoreOperationStep,
15
+ } from "../../../../types/module-store";
8
16
 
9
17
  interface ModuleStoreTileProps {
10
18
  /** The store module the tile presents. */
11
19
  entry: ModuleStoreEntry;
20
+ /** Another module's operation runs: every action waits for it. */
21
+ isLocked?: boolean;
12
22
  }
13
23
 
14
- const props = defineProps<ModuleStoreTileProps>();
24
+ interface ModuleStoreTileEmits {
25
+ action: [kind: StoreOperationKind];
26
+ }
27
+
28
+ const props = withDefaults(defineProps<ModuleStoreTileProps>(), {
29
+ isLocked: false,
30
+ });
31
+ const emit = defineEmits<ModuleStoreTileEmits>();
15
32
  const { t } = useI18n();
16
33
  const { processI18n } = useTranslation();
17
34
 
18
35
  const TOOLTIP_CONTENT = { side: "top", sideOffset: 6 } as const;
36
+ /** The steps an operation shows, in the order they run. */
37
+ const PROGRESS_STEPS: StoreOperationStep[] = [
38
+ "resolve",
39
+ "dependencies",
40
+ "unload",
41
+ "load",
42
+ "save",
43
+ ];
44
+ /**
45
+ * Steps an operation of this kind goes through. The file is written last:
46
+ * it only ever names a module that loaded.
47
+ */
48
+ const KIND_STEPS: Record<StoreOperationKind, StoreOperationStep[]> = {
49
+ install: ["resolve", "dependencies", "load", "save"],
50
+ update: ["resolve", "unload", "load", "save"],
51
+ enable: ["load", "save"],
52
+ disable: ["unload", "save"],
53
+ uninstall: ["unload", "save"],
54
+ apply: ["load"],
55
+ };
56
+
57
+ const status = computed(() => storeTileStatus(props.entry));
58
+ const actions = computed(() => storeActions(props.entry));
59
+ const primary = computed<StoreAction | undefined>(() =>
60
+ actions.value.find((action) => !action.isMenuOnly),
61
+ );
62
+ const secondary = computed(() =>
63
+ actions.value
64
+ .filter((action) => action !== primary.value)
65
+ .map((action) => ({
66
+ label: t(`modules.store.actions.${action.labelKey}`),
67
+ icon: action.icon,
68
+ color: action.kind === "uninstall" ? ("error" as const) : undefined,
69
+ disabled: storeActionBlock(props.entry, action.kind) !== undefined,
70
+ onSelect: () => emit("action", action.kind),
71
+ })),
72
+ );
73
+
74
+ const isInstalledHere = computed(
75
+ () => props.entry.installedVersion !== undefined,
76
+ );
77
+ const LIST_SEPARATOR = ", ";
78
+
79
+ const BLOCK_MESSAGES: Record<StoreActionBlock, () => string> = {
80
+ no_release: () => t("modules.store.no_release"),
81
+ incompatible: () =>
82
+ t("modules.store.incompatible", {
83
+ range: props.entry.requiredInterfaceRange ?? "",
84
+ }),
85
+ frontend: () =>
86
+ t("modules.store.frontend_requirement.needs", {
87
+ range: props.entry.frontendRequirement?.range ?? "",
88
+ running: props.entry.frontendRequirement?.running ?? "",
89
+ }),
90
+ interfaces: () =>
91
+ t("modules.store.requires_first", {
92
+ interfaces: props.entry.missingInterfaces
93
+ .map((missing) => missing.interfacePackage)
94
+ .join(LIST_SEPARATOR),
95
+ }),
96
+ dependents: () =>
97
+ t("modules.store.used_by", {
98
+ modules: props.entry.dependents.join(LIST_SEPARATOR),
99
+ }),
100
+ };
101
+
102
+ /** Why the main action waits, when it does. */
103
+ const blockedReason = computed(() => {
104
+ if (props.isLocked) return t("modules.store.locked");
105
+ if (!primary.value) return undefined;
106
+ const block = storeActionBlock(props.entry, primary.value.kind);
107
+ return block ? BLOCK_MESSAGES[block]() : undefined;
108
+ });
109
+ const isBlocked = computed(() => blockedReason.value !== undefined);
110
+
111
+ const operation = computed(() => props.entry.operation);
112
+ /**
113
+ * Whether the running install loads modules for missing interfaces. Kept
114
+ * for the whole operation: once they run, the listing no longer misses them.
115
+ */
116
+ const isInstallingDependencies = ref(false);
117
+ watch(
118
+ operation,
119
+ (running) => {
120
+ if (!running) {
121
+ isInstallingDependencies.value = false;
122
+ return;
123
+ }
124
+ if (
125
+ running.step === "dependencies" ||
126
+ (running.kind === "install" && props.entry.missingInterfaces.length > 0)
127
+ ) {
128
+ isInstallingDependencies.value = true;
129
+ }
130
+ },
131
+ { immediate: true },
132
+ );
133
+
134
+ const steps = computed(() => {
135
+ const kind = operation.value?.kind;
136
+ if (!kind) return [];
137
+ const current = PROGRESS_STEPS.indexOf(operation.value.step);
138
+ const kindSteps = KIND_STEPS[kind].filter(
139
+ (step) => step !== "dependencies" || isInstallingDependencies.value,
140
+ );
141
+ return kindSteps.map((step) => {
142
+ const index = PROGRESS_STEPS.indexOf(step);
143
+ const state =
144
+ index < current ? "done" : index === current ? "running" : "todo";
145
+ return { step, state };
146
+ });
147
+ });
19
148
 
20
- const command = computed(() => moduleInstallCommand(props.entry));
149
+ const STEP_ICONS: Record<string, string> = {
150
+ done: "i-ph-check-circle-fill",
151
+ running: "i-ph-circle-notch",
152
+ todo: "i-ph-circle",
153
+ };
154
+
155
+ const PLAIN_VERSION = /^\d/;
156
+
157
+ /** A version as the footer shows it: `v1.2.3`, or a range as written. */
158
+ function formatVersion(version: string): string {
159
+ return PLAIN_VERSION.test(version) ? `v${version}` : version;
160
+ }
161
+
162
+ const versionLabel = computed(() => {
163
+ const { installedVersion, latestVersion, operation: running } = props.entry;
164
+ if (running?.targetVersion && installedVersion && running.kind === "update") {
165
+ return `${formatVersion(installedVersion)} → ${formatVersion(running.targetVersion)}`;
166
+ }
167
+ if (props.entry.updateAvailable && installedVersion && latestVersion) {
168
+ return `${formatVersion(installedVersion)} → ${formatVersion(latestVersion)}`;
169
+ }
170
+ const version = installedVersion ?? latestVersion;
171
+ return version ? formatVersion(version) : undefined;
172
+ });
21
173
  </script>
22
174
 
23
175
  <template>
@@ -26,49 +178,115 @@ const command = computed(() => moduleInstallCommand(props.entry));
26
178
  :title="processI18n(props.entry.title)"
27
179
  :description="processI18n(props.entry.description)"
28
180
  :status="{
29
- tone: 'neutral',
30
- dot: 'none',
31
- label: t('modules.store.available_soon'),
181
+ tone: status.tone,
182
+ dot: status.dot,
183
+ label: t(`modules.store.state.${status.labelKey}`),
32
184
  }"
185
+ :is-solid="isInstalledHere"
33
186
  >
34
- <!-- The CLI command that adds the module today, shown muted: the store
35
- will run it for you once installing from here is possible. -->
187
+ <!-- Running operation: its steps, the current one spinning. -->
188
+ <ol
189
+ v-if="steps.length > 0"
190
+ class="flex flex-col gap-1 text-xs"
191
+ :aria-label="t(`modules.store.progress.${operation?.kind}`)"
192
+ >
193
+ <li
194
+ v-for="{ step, state } in steps"
195
+ :key="step"
196
+ class="flex items-center gap-1.5"
197
+ :class="state === 'todo' ? 'text-dimmed' : 'text-toned'"
198
+ >
199
+ <UIcon
200
+ :name="STEP_ICONS[state]"
201
+ class="size-3.5 shrink-0"
202
+ :class="{
203
+ 'animate-spin text-(--ui-primary)': state === 'running',
204
+ 'text-(--ui-success)': state === 'done',
205
+ }"
206
+ />
207
+ {{ t(`modules.store.steps.${step}`) }}
208
+ </li>
209
+ </ol>
210
+
211
+ <p
212
+ v-else-if="props.entry.state === 'error' && props.entry.error"
213
+ class="rounded-[8px] bg-(--ui-error)/8 px-2.5 py-1.5 text-xs text-(--ui-error)"
214
+ :title="props.entry.error"
215
+ >
216
+ <span class="line-clamp-3 break-words">{{ props.entry.error }}</span>
217
+ </p>
218
+
219
+ <p
220
+ v-else-if="props.entry.state === 'pending'"
221
+ class="text-xs text-(--ui-warning)"
222
+ >
223
+ {{ t("modules.store.pending_hint") }}
224
+ </p>
225
+
226
+ <!-- The npm package the module comes from. -->
36
227
  <div
37
- class="text-dimmed flex h-[30px] min-w-0 items-center gap-1.5 rounded-[8px] border border-(--ui-border) bg-(--dms-bg-muted) px-2.5 font-mono text-[11.5px]"
38
- :title="command"
228
+ v-else
229
+ class="text-dimmed flex h-[30px] min-w-0 items-center rounded-[8px] border border-(--ui-border) bg-(--dms-bg-muted) px-2.5 font-mono text-[11.5px]"
230
+ :title="props.entry.package"
39
231
  >
40
- <span class="sr-only">{{ t("modules.store.command_label") }}:</span>
41
- <!-- The package name never truncates: the CLI prefix gives way first. -->
42
- <span class="min-w-0 truncate">
43
- <span aria-hidden="true" class="opacity-70">$</span>
44
- {{ MODULE_INSTALL_COMMAND }}
45
- </span>
46
- <b class="text-toned shrink-0 font-semibold">
47
- {{ props.entry.packageName }}
48
- </b>
232
+ <span class="truncate">{{ props.entry.package }}</span>
49
233
  </div>
50
234
 
235
+ <ModuleStoreRequirements v-if="steps.length === 0" :entry="props.entry" />
236
+
51
237
  <template #footer>
52
238
  <span class="truncate">
53
239
  {{ processI18n(props.entry.catalogCategory) }}
54
240
  </span>
55
- <!-- aria-disabled rather than disabled: the button stays focusable and
56
- hoverable so its "Coming soon" tooltip can explain why. -->
57
- <span class="ms-auto shrink-0 font-sans">
241
+ <span
242
+ v-if="versionLabel"
243
+ class="shrink-0"
244
+ :class="props.entry.updateAvailable ? 'text-(--ui-warning)' : ''"
245
+ >
246
+ {{ versionLabel }}
247
+ </span>
248
+ <span class="ms-auto flex shrink-0 items-center gap-1 font-sans">
58
249
  <UTooltip
59
- :text="t('modules.store.install_soon')"
250
+ v-if="primary"
251
+ :text="blockedReason"
252
+ :disabled="!blockedReason"
60
253
  :content="TOOLTIP_CONTENT"
61
254
  >
255
+ <!-- aria-disabled rather than disabled: the button keeps its
256
+ tooltip, which says why it waits. -->
62
257
  <UButton
258
+ size="xs"
259
+ :color="primary.kind === 'update' ? 'warning' : 'neutral'"
260
+ :variant="primary.kind === 'install' ? 'solid' : 'outline'"
261
+ :icon="primary.icon"
262
+ :label="t(`modules.store.actions.${primary.labelKey}`)"
263
+ :aria-disabled="isBlocked ? 'true' : undefined"
264
+ :class="{ 'cursor-not-allowed opacity-50': isBlocked }"
265
+ @click="!isBlocked && emit('action', primary.kind)"
266
+ />
267
+ </UTooltip>
268
+ <UDropdownMenu
269
+ v-if="secondary.length > 0 && !props.isLocked"
270
+ :items="secondary"
271
+ :content="{ align: 'end' }"
272
+ >
273
+ <UButton
274
+ v-if="primary"
275
+ size="xs"
276
+ color="neutral"
277
+ variant="ghost"
278
+ icon="i-ph-dots-three-vertical"
279
+ :aria-label="t('modules.store.more_actions')"
280
+ />
281
+ <UButton
282
+ v-else
63
283
  size="xs"
64
284
  color="neutral"
65
285
  variant="outline"
66
- icon="i-ph-download-simple"
67
- :label="t('modules.store.install')"
68
- aria-disabled="true"
69
- @click.prevent
286
+ trailing-icon="i-ph-caret-down"
287
+ :label="t('modules.store.actions.manage')"
70
288
  />
71
- </UTooltip>
289
+ </UDropdownMenu>
72
290
  </span>
73
291
  </template>
74
292
  </ModuleTile>
@@ -23,6 +23,11 @@ interface ModuleTileProps {
23
23
  status: ModuleTileStatus;
24
24
  /** Where the tile leads; without it, the tile is a placeholder. */
25
25
  to?: string;
26
+ /**
27
+ * A tile with no link that still stands for a module running here (one
28
+ * the store manages): drawn solid rather than dashed.
29
+ */
30
+ isSolid?: boolean;
26
31
  }
27
32
 
28
33
  interface ModuleTileSlots {
@@ -36,6 +41,7 @@ interface ModuleTileSlots {
36
41
 
37
42
  const props = withDefaults(defineProps<ModuleTileProps>(), {
38
43
  to: undefined,
44
+ isSolid: false,
39
45
  });
40
46
  defineSlots<ModuleTileSlots>();
41
47
 
@@ -58,10 +64,18 @@ const theme = tv({
58
64
  footer: "py-[7px]",
59
65
  },
60
66
  },
67
+ isSolid: {
68
+ true: {
69
+ root: "dms-card border-solid",
70
+ footer: "bg-(--dms-bg-muted) py-[7px]",
71
+ },
72
+ },
61
73
  },
62
74
  });
63
75
 
64
- const ui = computed(() => theme({ isPlaceholder: !props.to }));
76
+ const ui = computed(() =>
77
+ theme({ isPlaceholder: !props.to, isSolid: !props.to && props.isSolid }),
78
+ );
65
79
  </script>
66
80
 
67
81
  <template>
@@ -72,7 +86,10 @@ const ui = computed(() => theme({ isPlaceholder: !props.to }));
72
86
  :class="ui.root()"
73
87
  >
74
88
  <div class="flex items-start justify-between gap-2.5">
75
- <DmsIconWell :icon="props.icon" :tone="props.to ? undefined : 'muted'" />
89
+ <DmsIconWell
90
+ :icon="props.icon"
91
+ :tone="props.to || props.isSolid ? undefined : 'muted'"
92
+ />
76
93
  <DmsStatusPill
77
94
  :tone="props.status.tone"
78
95
  :dot="props.status.dot"
@@ -67,7 +67,6 @@ const props = defineProps<Props>();
67
67
  const model = defineModel<string[] | null | undefined>();
68
68
 
69
69
  const { t } = useI18n();
70
- const { $authFetch } = useAuthFetch();
71
70
  // The wrapping UFormField's error ("pick at least one role") marks the group
72
71
  // invalid, describes it and rings the pills in the error ink.
73
72
  const { ariaAttrs } = useFormField();
@@ -84,17 +83,19 @@ const showsOwnerHint = computed(() =>
84
83
  props.ownerField ? isOwner.value : props.disabled,
85
84
  );
86
85
 
87
- const options = ref<RoleOptionsResponse>({ roles: [], totalPermissions: 0 });
88
- const isFetching = ref(true);
89
- const isLoading = computed(() => isFetching.value || props.loading);
90
-
91
- onMounted(async () => {
92
- try {
93
- options.value = await $authFetch<RoleOptionsResponse>(props.rolesUrl);
94
- } finally {
95
- isFetching.value = false;
96
- }
97
- });
86
+ // Read with the form: a page-mode form renders the pills on the server; a
87
+ // drawer opened again shows the roles at once and reads them again in the
88
+ // background (one may have been added since).
89
+ const roleOptions = useApiData<RoleOptionsResponse>(() => props.rolesUrl);
90
+ const options = computed<RoleOptionsResponse>(
91
+ () => roleOptions.data.value ?? { roles: [], totalPermissions: 0 },
92
+ );
93
+ const isLoading = computed(
94
+ () =>
95
+ props.loading ||
96
+ roleOptions.status.value === "idle" ||
97
+ roleOptions.status.value === "pending",
98
+ );
98
99
 
99
100
  const selectedIds = computed(() => new Set(model.value ?? []));
100
101
  const roleItems = computed<ChipGroupItem[]>(() =>
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { nextTick } from "vue";
2
3
  import { formatRelativeTime } from "#dms-core/app/utils/formatter";
3
4
  import TableEmpty from "#dms-ui/app/build/components/table/Empty.vue";
4
5
  import TablePagination from "#dms-ui/app/build/components/table/Pagination.vue";
@@ -63,12 +64,21 @@ const sourceOf = (
63
64
  return labelKey ? { label: t(labelKey), isModule: false } : undefined;
64
65
  };
65
66
 
66
- // A change of a row is listed again, from the server.
67
+ // A change made here moves the counts too (the write's answer, then its
68
+ // realtime echo): the watcher below leaves it to the one read that follows it.
69
+ let ownChanges = 0;
70
+
71
+ // A change of a row is listed again, from the server, once.
67
72
  const runThenRefresh = async (action: () => Promise<unknown>) => {
73
+ ownChanges++;
68
74
  try {
69
75
  await action();
76
+ // What the action changed reaches the watcher before the guard drops.
77
+ await nextTick();
70
78
  } catch {
71
79
  toast.add({ title: t("dms.form.error_title"), color: "error" });
80
+ } finally {
81
+ ownChanges--;
72
82
  }
73
83
  await props.context.refresh();
74
84
  };
@@ -88,15 +98,15 @@ const toggleRead = (notification: UserNotification) =>
88
98
  );
89
99
 
90
100
  // What the stream changes elsewhere (a notification arriving, read in
91
- // another tab) is listed again here.
101
+ // another tab) is listed again here: once per change of the count or of the
102
+ // newest notification, not each time the bell's lists are rebuilt.
92
103
  watch(
93
- () => [unreadCount.value, notifications.value[0]?._id],
94
- () => void props.context.refresh(),
104
+ [unreadCount, () => notifications.value[0]?._id],
105
+ () => {
106
+ if (ownChanges === 0) void props.context.refresh();
107
+ },
95
108
  );
96
109
 
97
- onMounted(() => {
98
- if (!catalog.isLoaded.value) void catalog.loadCatalog();
99
- });
100
110
  </script>
101
111
 
102
112
  <template>
@@ -65,7 +65,6 @@ const failureMessage = computed(() => {
65
65
  : t("page.settings.notifications.save_failed_on");
66
66
  });
67
67
 
68
- onMounted(load);
69
68
  </script>
70
69
 
71
70
  <template>
@@ -47,9 +47,9 @@ const toast = useToast();
47
47
  const { $authFetch } = useAuthFetch();
48
48
  const { processApiMessage } = useTranslation();
49
49
  const { user, refresh: refreshSession } = useCurrentUser();
50
- const { upload, resolveUrl } = useProfileAvatar(
51
- () => props.avatarField?.component?.options ?? {},
52
- );
50
+ const avatarOptions = () => props.avatarField?.component?.options ?? {};
51
+ const { upload } = useProfileAvatar(avatarOptions);
52
+ const avatarUrls = useFileReadUrls(avatarOptions().storage);
53
53
  // A refused name or picture shows under its field; a failed request is a
54
54
  // toast.
55
55
  const fieldErrors = useFieldErrors({
@@ -57,13 +57,29 @@ const fieldErrors = useFieldErrors({
57
57
  codes: { [INVALID_AVATAR]: "avatar" },
58
58
  });
59
59
 
60
- const saved = ref<ProfileResponse>({
61
- name: user.value?.name,
62
- email: user.value?.email,
63
- });
60
+ // Read with the page: the server renders the name, the address with its
61
+ // verification, and the picture. The form edits a copy of it.
62
+ const profile = await useApiData<ProfileResponse>(() => props.endpoint);
63
+ const isProfileLoaded = computed(
64
+ () => profile.status.value === "success" || profile.status.value === "error",
65
+ );
66
+
67
+ const saved = ref<ProfileResponse>(
68
+ profile.data.value ?? { name: user.value?.name, email: user.value?.email },
69
+ );
64
70
  const name = ref(saved.value.name ?? "");
65
- const avatar = ref<AvatarValue | null>(null);
66
- const avatarUrl = ref<string | null>(null);
71
+ const avatar = ref<AvatarValue | null>(saved.value.avatar ?? null);
72
+ // A picture picked here shows from the file until the profile is saved.
73
+ const pickedAvatarUrl = ref<string | null>(null);
74
+ const avatarUrl = computed(() => {
75
+ if (!avatar.value) return null;
76
+ return pickedAvatarUrl.value ?? avatarUrls.getUrl(avatar.value.key) ?? null;
77
+ });
78
+ watch(
79
+ () => avatar.value?.key,
80
+ (key) => key && void avatarUrls.resolve(key),
81
+ { immediate: true },
82
+ );
67
83
  const isUploading = ref(false);
68
84
  const isSaving = ref(false);
69
85
  const fileInput = useTemplateRef<HTMLInputElement>("fileInput");
@@ -86,14 +102,12 @@ const canSave = computed(() => !!name.value.trim() && !isUploading.value);
86
102
 
87
103
  watch(name, () => fieldErrors.clear("name"));
88
104
 
89
- async function applyProfile(profile: ProfileResponse): Promise<void> {
105
+ function applyProfile(answer: ProfileResponse): void {
90
106
  fieldErrors.clear();
91
- saved.value = profile;
92
- name.value = profile.name ?? "";
93
- avatar.value = profile.avatar ?? null;
94
- avatarUrl.value = profile.avatar
95
- ? await resolveUrl(profile.avatar.key)
96
- : null;
107
+ saved.value = answer;
108
+ name.value = answer.name ?? "";
109
+ avatar.value = answer.avatar ?? null;
110
+ pickedAvatarUrl.value = null;
97
111
  }
98
112
 
99
113
  function pickFile(): void {
@@ -117,7 +131,7 @@ async function onFileChange(event: Event): Promise<void> {
117
131
  return;
118
132
  }
119
133
  avatar.value = { key: result.key! };
120
- avatarUrl.value = URL.createObjectURL(file);
134
+ pickedAvatarUrl.value = URL.createObjectURL(file);
121
135
  } catch {
122
136
  toast.add({
123
137
  title: t("page.settings.profile.avatar_error"),
@@ -131,11 +145,11 @@ async function onFileChange(event: Event): Promise<void> {
131
145
  function removeAvatar(): void {
132
146
  fieldErrors.clear("avatar");
133
147
  avatar.value = null;
134
- avatarUrl.value = null;
148
+ pickedAvatarUrl.value = null;
135
149
  }
136
150
 
137
- async function discard(): Promise<void> {
138
- await applyProfile(saved.value);
151
+ function discard(): void {
152
+ applyProfile(saved.value);
139
153
  }
140
154
 
141
155
  async function save(): Promise<void> {
@@ -146,11 +160,11 @@ async function save(): Promise<void> {
146
160
  if (!canSave.value) return;
147
161
  isSaving.value = true;
148
162
  try {
149
- const profile = await $authFetch<ProfileResponse>(props.endpoint, {
163
+ const answer = await $authFetch<ProfileResponse>(props.endpoint, {
150
164
  method: "POST",
151
165
  body: { name: name.value.trim(), avatar: avatar.value },
152
166
  });
153
- await applyProfile(profile);
167
+ applyProfile(answer);
154
168
  toast.add({ title: t("page.settings.profile.saved"), color: "success" });
155
169
  await refreshSession();
156
170
  } catch (error) {
@@ -162,17 +176,6 @@ async function save(): Promise<void> {
162
176
  }
163
177
  }
164
178
 
165
- // The verification state comes with the profile, fetched after mount: a
166
- // skeleton holds the badge's place until then.
167
- const isProfileLoaded = ref(false);
168
-
169
- onMounted(async () => {
170
- try {
171
- await applyProfile(await $authFetch<ProfileResponse>(props.endpoint));
172
- } finally {
173
- isProfileLoaded.value = true;
174
- }
175
- });
176
179
  </script>
177
180
 
178
181
  <template>
@@ -7,7 +7,7 @@ import { iconWellToneOf } from "#dms-ui/app/build/utils/tone";
7
7
  import ProfileSummaryRow from "./ProfileSummaryRow.vue";
8
8
 
9
9
  const { t, locale } = useI18n();
10
- const { overview, attention, attentionTone, isUnavailable, refresh } =
10
+ const { overview, attention, attentionTone, isUnavailable } =
11
11
  useSecurityOverview();
12
12
 
13
13
  const needsAttention = computed(() => attention.value.length > 0);
@@ -52,7 +52,6 @@ const placeholder = computed(
52
52
  )}`,
53
53
  );
54
54
 
55
- onMounted(refresh);
56
55
  </script>
57
56
 
58
57
  <template>
@@ -1,4 +1,5 @@
1
- /** A permission of the roles editor tree (`GET /settings/workspace/roles/editor-tree`). */
1
+ import { pageRoutePath } from "#dms-core/app/utils/pageRoutes";
2
+ /** A permission of the roles editor tree (`GET /dms/pages/settings/workspace/roles/editor-tree`). */
2
3
  export interface RolePermissionNode {
3
4
  id: string;
4
5
  label: string;
@@ -17,7 +18,7 @@ export interface RoleMemberPreview {
17
18
  name: string;
18
19
  }
19
20
 
20
- /** One role of the roles list (`GET /settings/workspace/roles/overview`). */
21
+ /** One role of the roles list (`GET /dms/pages/settings/workspace/roles/overview`). */
21
22
  export interface RoleSummary {
22
23
  _id: string;
23
24
  name: string;
@@ -42,7 +43,7 @@ export interface RoleEditorCapabilities {
42
43
  canDelete: boolean;
43
44
  }
44
45
 
45
- /** Payload of `GET /settings/workspace/roles/overview`. */
46
+ /** Payload of `GET /dms/pages/settings/workspace/roles/overview`. */
46
47
  export interface RolesOverview {
47
48
  roles: RoleSummary[];
48
49
  owners: RoleOwnersSummary;
@@ -78,7 +79,7 @@ export const OWNER_ENTRY_ID = "__owner__";
78
79
  export const NEW_ROLE_ENTRY_ID = "__new__";
79
80
 
80
81
  /** Base path of the roles page API. */
81
- export const ROLES_API_PATH = "/settings/workspace/roles";
82
+ export const ROLES_API_PATH = pageRoutePath("/settings/workspace/roles");
82
83
 
83
84
  /** The roles' table data routes, which delete a role. */
84
85
  export const ROLES_TABLE_API_PATH = "/api/tables/roles";