@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
@@ -5,11 +5,30 @@ import {
5
5
  moduleCategoryOptions,
6
6
  } from "../layers/dms-layout/app/build/utils/modules-catalog";
7
7
  import {
8
- MODULE_STORE_CATALOG,
9
- availableStoreModules,
10
- moduleInstallCommand,
11
8
  type ModuleStoreEntry,
9
+ filterByScope,
10
+ isAwaitingFrontend,
11
+ needsDependencyChoice,
12
+ sortStoreModules,
13
+ storeActionBlock,
14
+ storeActions,
15
+ storeTileStatus,
16
+ toModuleStoreEntry,
12
17
  } from "../layers/dms-layout/app/build/utils/module-store-catalog";
18
+ import {
19
+ OTHER_PACKAGE_CHOICE,
20
+ areDependencyDraftsValid,
21
+ buildInstallBody,
22
+ createDependencyDrafts,
23
+ isNpmPackageName,
24
+ parseDependencyConfig,
25
+ validateDependencyDraft,
26
+ } from "../layers/dms-layout/app/build/utils/module-store-install";
27
+ import type {
28
+ MissingInterface,
29
+ StoreListingEntry,
30
+ } from "../layers/dms-layout/app/types/module-store";
31
+ import catalogDocument from "../../data/module-store/catalog.json";
13
32
  import en from "../layers/dms-layout/i18n/locales/layout-en-GB.json";
14
33
  import fr from "../layers/dms-layout/i18n/locales/layout-fr-FR.json";
15
34
 
@@ -33,16 +52,33 @@ const resolve = (value: string): string => {
33
52
  return typeof text === "string" ? text : value;
34
53
  };
35
54
 
36
- function storeEntry(
55
+ function listingEntry(
37
56
  id: string,
38
- overrides: Partial<ModuleStoreEntry> = {},
39
- ): ModuleStoreEntry {
57
+ overrides: Partial<StoreListingEntry> = {},
58
+ ): StoreListingEntry {
40
59
  return {
41
60
  id,
42
- packageName: `@antelopejs/dms-${id}`,
61
+ package: `@antelopejs/dms-${id}`,
62
+ icon: "i-ph-cube",
63
+ category: "data",
43
64
  title: id[0].toUpperCase() + id.slice(1),
44
65
  description: `${id} description`,
45
- icon: "i-ph-cube",
66
+ state: "available",
67
+ updateAvailable: false,
68
+ compatible: true,
69
+ frontend: "none",
70
+ missingInterfaces: [],
71
+ dependents: [],
72
+ ...overrides,
73
+ };
74
+ }
75
+
76
+ function storeEntry(
77
+ id: string,
78
+ overrides: Partial<ModuleStoreEntry> = {},
79
+ ): ModuleStoreEntry {
80
+ return {
81
+ ...toModuleStoreEntry(listingEntry(id)),
46
82
  catalogCategory: "Data",
47
83
  ...overrides,
48
84
  };
@@ -53,32 +89,95 @@ const catalog = [
53
89
  catalogCategory: "Content",
54
90
  description: "Asset library",
55
91
  }),
56
- storeEntry("database"),
92
+ storeEntry("database", { state: "active", installedVersion: "0.1.2" }),
57
93
  storeEntry("lang", { title: "Translations", catalogCategory: "Content" }),
58
- storeEntry("ai", { catalogCategory: "AI" }),
94
+ storeEntry("ai", {
95
+ catalogCategory: "AI",
96
+ state: "disabled",
97
+ installedVersion: "0.2.1",
98
+ }),
59
99
  ];
60
100
 
101
+ const actionKinds = (entry: StoreListingEntry) =>
102
+ storeActions(entry).map((action) => action.kind);
103
+
61
104
  describe("module store catalog", () => {
62
- it("hides the modules already installed", () => {
63
- const available = availableStoreModules(
64
- catalog,
65
- new Set(["database", "demo"]),
66
- resolve,
67
- );
68
- expect(available.map((entry) => entry.id)).toEqual(["ai", "media", "lang"]);
105
+ it("sorts the modules by their shown name", () => {
106
+ expect(
107
+ sortStoreModules(catalog, resolve).map((entry) => entry.title),
108
+ ).toEqual(["Ai", "Database", "Media", "Translations"]);
69
109
  });
70
110
 
71
- it("lists every module, by name, when none is installed", () => {
111
+ it("splits installed modules, paused ones included, from available ones", () => {
72
112
  expect(
73
- availableStoreModules(catalog, new Set(), resolve).map(
74
- (entry) => entry.title,
113
+ filterByScope(catalog, "installed").map((entry) => entry.id),
114
+ ).toEqual(["database", "ai"]);
115
+ expect(
116
+ filterByScope(catalog, "available").map((entry) => entry.id),
117
+ ).toEqual(["media", "lang"]);
118
+ expect(filterByScope(catalog, "all")).toHaveLength(4);
119
+ });
120
+
121
+ it("counts a failed install as available, a failed update as installed", () => {
122
+ const failedInstall = listingEntry("api", { state: "error" });
123
+ const failedUpdate = listingEntry("api", {
124
+ state: "error",
125
+ installedVersion: "0.3.0",
126
+ });
127
+ expect(filterByScope([failedInstall], "available")).toHaveLength(1);
128
+ expect(filterByScope([failedUpdate], "installed")).toHaveLength(1);
129
+ expect(actionKinds(failedInstall)).toEqual(["install"]);
130
+ expect(actionKinds(failedUpdate)).toEqual(["apply", "uninstall"]);
131
+ });
132
+
133
+ it("offers each state its actions, the main one first", () => {
134
+ expect(actionKinds(listingEntry("a"))).toEqual(["install"]);
135
+ expect(actionKinds(listingEntry("a", { state: "active" }))).toEqual([
136
+ "disable",
137
+ "uninstall",
138
+ ]);
139
+ expect(
140
+ actionKinds(
141
+ listingEntry("a", { state: "active", updateAvailable: true }),
75
142
  ),
76
- ).toEqual(["Ai", "Database", "Media", "Translations"]);
143
+ ).toEqual(["update", "disable", "uninstall"]);
144
+ expect(actionKinds(listingEntry("a", { state: "disabled" }))).toEqual([
145
+ "enable",
146
+ "uninstall",
147
+ ]);
148
+ expect(actionKinds(listingEntry("a", { state: "busy" }))).toEqual([]);
77
149
  });
78
150
 
79
- it("is empty once every module is installed", () => {
80
- const installed = new Set(catalog.map((entry) => entry.id));
81
- expect(availableStoreModules(catalog, installed, resolve)).toEqual([]);
151
+ it("asks for confirmation before uninstalling only", () => {
152
+ const confirmed = storeActions(listingEntry("a", { state: "disabled" }))
153
+ .filter((action) => action.confirm)
154
+ .map((action) => action.kind);
155
+ expect(confirmed).toEqual(["uninstall"]);
156
+ });
157
+
158
+ it("flags an update on an active module's pill", () => {
159
+ expect(
160
+ storeTileStatus(
161
+ listingEntry("a", { state: "active", updateAvailable: true }),
162
+ ).labelKey,
163
+ ).toBe("update_available");
164
+ expect(storeTileStatus(listingEntry("a", { state: "busy" })).dot).toBe(
165
+ "live",
166
+ );
167
+ });
168
+
169
+ it("waits on the frontend only once the operation is over", () => {
170
+ expect(isAwaitingFrontend(listingEntry("a", { frontend: "pending" }))).toBe(
171
+ true,
172
+ );
173
+ expect(
174
+ isAwaitingFrontend(
175
+ listingEntry("a", { state: "busy", frontend: "pending" }),
176
+ ),
177
+ ).toBe(false);
178
+ expect(isAwaitingFrontend(listingEntry("a", { frontend: "unknown" }))).toBe(
179
+ false,
180
+ );
82
181
  });
83
182
 
84
183
  it("searches the name, description, category and id", () => {
@@ -95,23 +194,6 @@ describe("module store catalog", () => {
95
194
  expect(search("payments")).toEqual([]);
96
195
  });
97
196
 
98
- it("filters by category, combined with the search", () => {
99
- expect(
100
- filterModules(catalog, {
101
- query: "",
102
- category: "Content",
103
- resolve,
104
- }).map((entry) => entry.id),
105
- ).toEqual(["media", "lang"]);
106
- expect(
107
- filterModules(catalog, {
108
- query: "lang",
109
- category: "Data",
110
- resolve,
111
- }),
112
- ).toEqual([]);
113
- });
114
-
115
197
  it("offers the categories present, with their counts", () => {
116
198
  expect(moduleCategoryOptions(catalog, resolve)).toEqual([
117
199
  { value: "AI", label: "AI", count: 1 },
@@ -119,31 +201,168 @@ describe("module store catalog", () => {
119
201
  { value: "Data", label: "Data", count: 1 },
120
202
  ]);
121
203
  });
204
+ });
205
+
206
+ const REDIS: MissingInterface = {
207
+ interfacePackage: "@antelopejs/interface-redis",
208
+ candidates: [
209
+ { name: "redis", package: "@antelopejs/redis" },
210
+ { name: "valkey", package: "@antelopejs/valkey" },
211
+ ],
212
+ };
213
+ const QUEUE: MissingInterface = {
214
+ interfacePackage: "@antelopejs/interface-queue",
215
+ candidates: [],
216
+ };
217
+
218
+ const FRONTEND_REQUIREMENT = {
219
+ range: ">=0.7.0",
220
+ running: "0.6.0",
221
+ target: "0.7.2",
222
+ command: "pnpm add @antelopejs/dms-frontend@0.7.2",
223
+ breaks: [],
224
+ };
225
+
226
+ describe("module store blocked actions", () => {
227
+ const released = (overrides: Partial<StoreListingEntry> = {}) =>
228
+ listingEntry("cache", { latestVersion: "1.0.0", ...overrides });
229
+
230
+ it("lets a releasable module install and update", () => {
231
+ expect(storeActionBlock(released(), "install")).toBeUndefined();
232
+ expect(storeActionBlock(released(), "update")).toBeUndefined();
233
+ });
122
234
 
123
- it("shows the AntelopeJS CLI command that adds the package", () => {
124
- expect(moduleInstallCommand(storeEntry("media"))).toBe(
125
- "ajs project modules add @antelopejs/dms-media",
235
+ it("blocks a release without a version, on another interface or dms-frontend", () => {
236
+ expect(storeActionBlock(listingEntry("cache"), "install")).toBe(
237
+ "no_release",
238
+ );
239
+ expect(storeActionBlock(released({ compatible: false }), "update")).toBe(
240
+ "incompatible",
126
241
  );
242
+ expect(
243
+ storeActionBlock(
244
+ released({ frontendRequirement: FRONTEND_REQUIREMENT }),
245
+ "install",
246
+ ),
247
+ ).toBe("frontend");
248
+ });
249
+
250
+ it("asks for the missing interfaces on install, refuses them on update", () => {
251
+ const missing = released({ missingInterfaces: [REDIS] });
252
+ expect(storeActionBlock(missing, "install")).toBeUndefined();
253
+ expect(needsDependencyChoice(missing, "install")).toBe(true);
254
+ expect(storeActionBlock(missing, "update")).toBe("interfaces");
255
+ expect(needsDependencyChoice(missing, "update")).toBe(false);
256
+ expect(needsDependencyChoice(released(), "install")).toBe(false);
257
+ });
258
+
259
+ it("keeps a module others need from being paused or removed", () => {
260
+ const needed = released({ state: "active", dependents: ["@acme/jobs"] });
261
+ expect(storeActionBlock(needed, "disable")).toBe("dependents");
262
+ expect(storeActionBlock(needed, "uninstall")).toBe("dependents");
263
+ expect(storeActionBlock(needed, "enable")).toBeUndefined();
264
+ expect(
265
+ storeActionBlock(released({ state: "active" }), "uninstall"),
266
+ ).toBeUndefined();
127
267
  });
128
268
  });
129
269
 
130
- describe("official module list", () => {
270
+ describe("module store install dialog", () => {
271
+ it("accepts npm package names only", () => {
272
+ expect(isNpmPackageName("redis")).toBe(true);
273
+ expect(isNpmPackageName("@antelopejs/redis")).toBe(true);
274
+ expect(isNpmPackageName("Redis")).toBe(false);
275
+ expect(isNpmPackageName("git+https://github.com/a/b.git")).toBe(false);
276
+ expect(isNpmPackageName("./local/module")).toBe(false);
277
+ expect(isNpmPackageName("@scope")).toBe(false);
278
+ expect(isNpmPackageName("a".repeat(215))).toBe(false);
279
+ });
280
+
281
+ it("reads an empty config as none and refuses anything but an object", () => {
282
+ expect(parseDependencyConfig(" ")).toEqual({ isValid: true });
283
+ expect(parseDependencyConfig('{ "url": "redis://cache" }')).toEqual({
284
+ isValid: true,
285
+ config: { url: "redis://cache" },
286
+ });
287
+ expect(parseDependencyConfig("[]").isValid).toBe(false);
288
+ expect(parseDependencyConfig("null").isValid).toBe(false);
289
+ expect(parseDependencyConfig("{ url: }").isValid).toBe(false);
290
+ });
291
+
292
+ it("preselects the first candidate, or the other package without one", () => {
293
+ expect(
294
+ createDependencyDrafts([REDIS, QUEUE]).map((draft) => draft.choice),
295
+ ).toEqual(["@antelopejs/redis", OTHER_PACKAGE_CHOICE]);
296
+ });
297
+
298
+ it("keeps what an earlier attempt picked for the same interface", () => {
299
+ const [earlier] = createDependencyDrafts([REDIS]);
300
+ const edited = { ...earlier!, choice: "@antelopejs/valkey", config: "{}" };
301
+ expect(createDependencyDrafts([REDIS, QUEUE], [edited])[0]).toEqual(edited);
302
+ });
303
+
304
+ it("flags a missing or invalid package and a config that is not an object", () => {
305
+ const [draft] = createDependencyDrafts([QUEUE]);
306
+ expect(validateDependencyDraft(draft!)).toEqual({
307
+ package: "package_required",
308
+ });
309
+ expect(
310
+ validateDependencyDraft({
311
+ ...draft!,
312
+ otherPackage: "github:acme/queue",
313
+ config: "[1]",
314
+ }),
315
+ ).toEqual({ package: "package_invalid", config: "config_invalid" });
316
+ expect(
317
+ areDependencyDraftsValid([{ ...draft!, otherPackage: " @acme/queue " }]),
318
+ ).toBe(true);
319
+ });
320
+
321
+ it("builds the install body, leaving out an empty config", () => {
322
+ const [redis, queue] = createDependencyDrafts([REDIS, QUEUE]);
323
+ expect(
324
+ buildInstallBody("cache", [
325
+ redis!,
326
+ { ...queue!, otherPackage: "@acme/queue", config: '{"size":2}' },
327
+ ]),
328
+ ).toEqual({
329
+ id: "cache",
330
+ dependencies: [
331
+ {
332
+ interfacePackage: "@antelopejs/interface-redis",
333
+ package: "@antelopejs/redis",
334
+ },
335
+ {
336
+ interfacePackage: "@antelopejs/interface-queue",
337
+ package: "@acme/queue",
338
+ config: { size: 2 },
339
+ },
340
+ ],
341
+ });
342
+ expect(buildInstallBody("cache", [])).toEqual({ id: "cache" });
343
+ });
344
+ });
345
+
346
+ describe("bundled module catalog", () => {
347
+ const entries = catalogDocument.modules.map((entry) =>
348
+ toModuleStoreEntry(listingEntry(entry.id, { category: entry.category })),
349
+ );
350
+
131
351
  it("has unique ids and packages", () => {
132
- const ids = MODULE_STORE_CATALOG.map((entry) => entry.id);
133
- const packages = MODULE_STORE_CATALOG.map((entry) => entry.packageName);
352
+ const ids = catalogDocument.modules.map((entry) => entry.id);
353
+ const packages = catalogDocument.modules.map((entry) => entry.package);
134
354
  expect(new Set(ids).size).toBe(ids.length);
135
355
  expect(new Set(packages).size).toBe(packages.length);
136
356
  });
137
357
 
138
358
  it("translates every name, description and category in both locales", () => {
139
359
  for (const messages of [en, fr]) {
140
- for (const entry of MODULE_STORE_CATALOG) {
360
+ for (const entry of entries) {
141
361
  for (const key of [
142
- entry.title,
143
- entry.description,
362
+ `$modules.store.catalog.${entry.id}.title`,
363
+ `$modules.store.catalog.${entry.id}.description`,
144
364
  entry.catalogCategory,
145
365
  ]) {
146
- expect(key.startsWith("$")).toBe(true);
147
366
  expect(typeof lookup(messages, key.slice(1)), key).toBe("string");
148
367
  }
149
368
  }
@@ -117,7 +117,7 @@ describe("findModulePageMatch", () => {
117
117
  "/modules/saas/billing/invoices": page("/modules/saas/billing/invoices", {
118
118
  displayName: "Invoices",
119
119
  module: "saas",
120
- category: { displayName: "Billing" },
120
+ categoryId: "saas.billing",
121
121
  }),
122
122
  "/modules/saas/invoice/:id": page("/modules/saas/invoice/:id", {
123
123
  displayName: "Invoice detail",
@@ -130,9 +130,22 @@ describe("findModulePageMatch", () => {
130
130
  }),
131
131
  };
132
132
 
133
+ // Every category once, by full id: a page names its own.
134
+ const layout = {
135
+ pages,
136
+ categories: {
137
+ "saas.billing": {
138
+ id: "billing",
139
+ fullId: "saas.billing",
140
+ fullSlug: "/modules/saas/billing",
141
+ displayName: "Billing",
142
+ },
143
+ },
144
+ };
145
+
133
146
  it("finds an openable page of an installed module and its section", () => {
134
147
  expect(
135
- findModulePageMatch(pages, "invoice", new Set(["saas"]), resolve),
148
+ findModulePageMatch(layout, "invoice", new Set(["saas"]), resolve),
136
149
  ).toEqual({
137
150
  moduleId: "saas",
138
151
  path: "/modules/saas/billing/invoices",
@@ -143,10 +156,10 @@ describe("findModulePageMatch", () => {
143
156
 
144
157
  it("ignores hidden pages, parameterised pages and other modules", () => {
145
158
  expect(
146
- findModulePageMatch(pages, "detail", new Set(["saas"]), resolve),
159
+ findModulePageMatch(layout, "detail", new Set(["saas"]), resolve),
147
160
  ).toBeNull();
148
161
  expect(
149
- findModulePageMatch(pages, "invoices", new Set(["hidden"]), resolve),
162
+ findModulePageMatch(layout, "invoices", new Set(["hidden"]), resolve),
150
163
  ).toBeNull();
151
164
  });
152
165
  });
@@ -0,0 +1,95 @@
1
+ // @vitest-environment jsdom
2
+ /**
3
+ * The modules catalog through the runtime's real async data: a server render
4
+ * reads it for a platform owner and hands it to the browser, which does not
5
+ * ask again (its live readouts would no longer match the markup); a member
6
+ * reads nothing; two renders in flight at once each render their own.
7
+ */
8
+ import { computed, defineComponent, h, nextTick, ref, type App } from "vue";
9
+ import { afterEach, beforeEach, expect, it, vi } from "vitest";
10
+ import { hydrate, serverRender, stubApiData } from "./helpers/api-data-runtime";
11
+ import { useModulesListing } from "../layers/dms-layout/app/composables/page/useModulesListing";
12
+
13
+ const ordersModule = { id: "orders", title: "Orders", hasAccess: true };
14
+ const billingModule = { id: "billing", title: "Billing", hasAccess: true };
15
+
16
+ const authFetch = vi.fn();
17
+ const isOwner = ref(true);
18
+ const mounted: App[] = [];
19
+
20
+ const Catalog = defineComponent({
21
+ async setup() {
22
+ const listing = useModulesListing();
23
+ await listing.loadModulesListing();
24
+ return () =>
25
+ h(
26
+ "p",
27
+ listing.loadingError.value?.kind ??
28
+ (listing.modules.value ?? []).map((entry) => entry.id).join(","),
29
+ );
30
+ },
31
+ });
32
+ const Page = defineComponent({
33
+ setup: () => () => h("main", [h(Catalog)]),
34
+ });
35
+
36
+ beforeEach(() => {
37
+ isOwner.value = true;
38
+ vi.stubGlobal("computed", computed);
39
+ vi.stubGlobal("HTTP_FORBIDDEN", 403);
40
+ vi.stubGlobal("useIsOwner", () => computed(() => isOwner.value));
41
+ stubApiData(authFetch);
42
+ });
43
+
44
+ afterEach(() => {
45
+ mounted.splice(0).forEach((app) => app.unmount());
46
+ vi.unstubAllGlobals();
47
+ vi.resetAllMocks();
48
+ });
49
+
50
+ it("hands the listing the server rendered to the browser, which does not ask again", async () => {
51
+ authFetch.mockResolvedValue([ordersModule]);
52
+ const { html, payload } = await serverRender(Page);
53
+ expect(html).toBe("<main><p>orders</p></main>");
54
+ expect(authFetch).toHaveBeenCalledTimes(1);
55
+
56
+ authFetch.mockClear();
57
+ const { app, container } = hydrate(Page, html, payload);
58
+ mounted.push(app);
59
+ await nextTick();
60
+ await new Promise((done) => setTimeout(done, 0));
61
+ expect(authFetch).not.toHaveBeenCalled();
62
+ expect(container.innerHTML).toBe(html);
63
+ });
64
+
65
+ it("reads nothing for a member", async () => {
66
+ isOwner.value = false;
67
+ const { html } = await serverRender(Page);
68
+ expect(html).toBe("<main><p></p></main>");
69
+ expect(authFetch).not.toHaveBeenCalled();
70
+ });
71
+
72
+ it("shows a refused listing as forbidden", async () => {
73
+ authFetch.mockRejectedValue(
74
+ Object.assign(new Error("Forbidden"), { statusCode: 403 }),
75
+ );
76
+ const { html } = await serverRender(Page);
77
+ expect(html).toBe("<main><p>forbidden</p></main>");
78
+ });
79
+
80
+ it("renders each request's own listing when two renders are in flight at once", async () => {
81
+ let answerFirst!: (value: unknown) => void;
82
+ authFetch
83
+ .mockReturnValueOnce(
84
+ new Promise((resolve) => {
85
+ answerFirst = resolve;
86
+ }),
87
+ )
88
+ .mockResolvedValueOnce([billingModule]);
89
+ const first = serverRender(Page);
90
+ const second = serverRender(Page);
91
+ await new Promise((done) => setTimeout(done, 0));
92
+ answerFirst([ordersModule]);
93
+ expect((await first).html).toBe("<main><p>orders</p></main>");
94
+ expect((await second).html).toBe("<main><p>billing</p></main>");
95
+ });
@@ -1,5 +1,8 @@
1
1
  import { afterEach, describe, expect, it, vi } from "vitest";
2
- import { validateAccount } from "../layers/dms-core/app/composables/auth/useMultiAccount";
2
+ import {
3
+ refreshSessionSurfaces,
4
+ validateAccount,
5
+ } from "../layers/dms-core/app/composables/auth/useMultiAccount";
3
6
  import type { StoredAccount } from "../layers/dms-core/app/types/auth";
4
7
 
5
8
  function makeAccount(overrides: Partial<StoredAccount> = {}): StoredAccount {
@@ -97,3 +100,22 @@ describe("validateAccount", () => {
97
100
  expect(account).not.toHaveProperty("refreshToken");
98
101
  });
99
102
  });
103
+
104
+ describe("refreshSessionSurfaces", () => {
105
+ afterEach(() => vi.unstubAllGlobals());
106
+
107
+ it("reads the session again, then reloads the site layout through Inertia", async () => {
108
+ const calls: string[] = [];
109
+ vi.stubGlobal("useSiteLayout", () => ({
110
+ resync: vi.fn(async () => calls.push("inertia reload")),
111
+ refresh: vi.fn(async () => calls.push("site layout fetch")),
112
+ }));
113
+ vi.stubGlobal("usePermissions", () => ({
114
+ clear: () => calls.push("permissions cleared"),
115
+ }));
116
+
117
+ await refreshSessionSurfaces(async () => calls.push("session"));
118
+
119
+ expect(calls).toEqual(["session", "permissions cleared", "inertia reload"]);
120
+ });
121
+ });
@@ -3,6 +3,7 @@ import {
3
3
  computed,
4
4
  createApp,
5
5
  defineComponent,
6
+ effectScope,
6
7
  h,
7
8
  onMounted,
8
9
  ref,
@@ -51,8 +52,6 @@ beforeEach(() => {
51
52
  vi.stubGlobal("useTranslation", () => ({
52
53
  processI18n: (value: string) => value,
53
54
  }));
54
- vi.stubGlobal("usePrefetch", () => ({ prefetchPageLayout: vi.fn() }));
55
- vi.stubGlobal("stripQueryAndHash", (path: string) => path);
56
55
  vi.stubGlobal("MENU_STATUS_TEXT_CLASSES", MENU_STATUS_TEXT_CLASSES);
57
56
  host = document.createElement("div");
58
57
  document.body.append(host);
@@ -86,6 +85,43 @@ describe("useNavBadges", () => {
86
85
  });
87
86
  });
88
87
 
88
+ describe("the counts a page visit carries", () => {
89
+ it("draws each one, over a static badge", () => {
90
+ const { navBadgeOf, applyCountedNavBadges } = useNavBadges();
91
+ applyCountedNavBadges({
92
+ "settings.user.notifications": { badge: "4", tone: "error" },
93
+ });
94
+ expect(
95
+ navBadgeOf({ fullId: "settings.user.notifications", badge: "new" }),
96
+ ).toEqual({ label: "4", tone: "error" });
97
+ });
98
+
99
+ it("drops the badge of an entry the next visit no longer counts", () => {
100
+ const { navBadgeOf, applyCountedNavBadges } = useNavBadges();
101
+ applyCountedNavBadges({
102
+ "settings.user.notifications": { badge: "4" },
103
+ "settings.workspace.invites": { badge: "2" },
104
+ });
105
+ applyCountedNavBadges({ "settings.workspace.invites": { badge: "1" } });
106
+
107
+ expect(
108
+ navBadgeOf({ fullId: "settings.user.notifications" }),
109
+ ).toBeUndefined();
110
+ expect(navBadgeOf({ fullId: "settings.workspace.invites" })).toEqual({
111
+ label: "1",
112
+ });
113
+ });
114
+
115
+ it("leaves the badges alone when a visit carries none, such as a frontend page", () => {
116
+ const { navBadgeOf, applyCountedNavBadges } = useNavBadges();
117
+ applyCountedNavBadges({ "settings.user.notifications": { badge: "4" } });
118
+ applyCountedNavBadges(undefined);
119
+ expect(navBadgeOf({ fullId: "settings.user.notifications" })).toEqual({
120
+ label: "4",
121
+ });
122
+ });
123
+ });
124
+
89
125
  describe("NavigationMenu badges", () => {
90
126
  // Renders each entry's badge the way UNavigationMenu hands it over.
91
127
  const MenuStub = defineComponent({
@@ -102,6 +138,7 @@ describe("NavigationMenu badges", () => {
102
138
  "data-badge": item.badge?.label,
103
139
  "data-color": item.badge?.color,
104
140
  "data-variant": item.badge?.variant,
141
+ "data-prefetch": String(item.prefetch),
105
142
  });
106
143
  return () => h("nav", props.items.flat().map(renderItem));
107
144
  },
@@ -150,6 +187,19 @@ describe("NavigationMenu badges", () => {
150
187
  expect(badgeOf("settings.user.notifications").color).toBe("primary");
151
188
  });
152
189
 
190
+ it("lets Inertia prefetch each linked page on hover, with no fetch of its own", () => {
191
+ const fetch = vi.fn();
192
+ vi.stubGlobal("fetch", fetch);
193
+ vi.stubGlobal("$fetch", fetch);
194
+ mount();
195
+ for (const item of ITEMS)
196
+ expect(badgeOf(item.fullId as string).prefetch).toBe("true");
197
+ host
198
+ .querySelector("nav")!
199
+ .dispatchEvent(new MouseEvent("mouseover", { bubbles: true }));
200
+ expect(fetch).not.toHaveBeenCalled();
201
+ });
202
+
153
203
  it("follows the live count and tone, and hides the badge at zero", async () => {
154
204
  mount();
155
205
  const { setNavBadge } = useNavBadges();
@@ -259,13 +309,26 @@ describe("Security page badge", () => {
259
309
  const { useSecurityOverview } = await import(
260
310
  "../layers/dms-layout/app/build/composables/settings/security/useSecurityOverview"
261
311
  );
312
+ const { engine, browserRuntime } = await import(
313
+ "./helpers/api-data-runtime"
314
+ );
315
+ const { useApiData } = await import(
316
+ "../layers/dms-core/app/composables/auth/useApiData"
317
+ );
318
+ vi.stubGlobal("useDmsAsyncData", engine.useDmsAsyncData);
319
+ vi.stubGlobal("useDmsFetch", engine.useDmsFetch);
320
+ vi.stubGlobal("useApiData", useApiData);
262
321
  vi.stubGlobal("useAuthFetch", () => ({
263
322
  $authFetch: vi.fn(async () => ({
264
323
  attention: ["backup_codes_low", "backup_codes_unsaved"],
265
324
  attentionTone: "error",
266
325
  })),
267
326
  }));
268
- await useSecurityOverview().refresh();
327
+ browserRuntime();
328
+ const scope = effectScope();
329
+ const overview = scope.run(() => useSecurityOverview())!;
330
+ await overview.refresh();
331
+ scope.stop();
269
332
  expect(liveBadges()?.value["settings.user.security"]).toEqual({
270
333
  label: "2",
271
334
  tone: "error",