@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,3 +1,5 @@
1
+ import { pageRoutePath } from "#dms-core/app/utils/pageRoutes";
2
+ import { onBeforeMount } from "vue";
1
3
  import { NotificationEvents } from "./types/events";
2
4
  import { unreadToneWith } from "../../build/components/pages/settings/notification/notificationDisplay";
3
5
 
@@ -30,13 +32,22 @@ export interface UserNotification {
30
32
  * bell, the Notifications entry): the most important unread notification's,
31
33
  * which the server weighs as the inbox draws it.
32
34
  */
33
- interface FeedCounts {
35
+ export interface FeedCounts {
34
36
  all: number;
35
37
  unread: number;
36
38
  unreadTone?: NotificationTone;
37
39
  }
38
40
 
39
- const API_BASE = "/settings/user/notifications";
41
+ /**
42
+ * What a write on the feed answers (marking read or unread, deleting), and
43
+ * what its realtime event carries: the whole feed's counts after it.
44
+ */
45
+ interface FeedWriteResult {
46
+ counts?: FeedCounts;
47
+ }
48
+
49
+ const API_BASE = pageRoutePath("/settings/user/notifications");
50
+ const BELL_COUNTS_DATA_KEY = "dms-notification-bell-counts";
40
51
  const NOTIFICATION_COMPONENT_ID = "global-notifications";
41
52
  const NOTIFICATIONS_PAGE_SIZE = 20;
42
53
 
@@ -130,16 +141,35 @@ export const useNotifications = () => {
130
141
  unreadTone.value = counts.unread > 0 ? counts.unreadTone : undefined;
131
142
  };
132
143
 
144
+ /** Reads the whole feed's unread count and tone, the bell's badge. */
145
+ const readBellCounts = (signal?: AbortSignal) =>
146
+ $authFetch<FeedCounts>(`${API_BASE}/counts`, { signal });
147
+
148
+ /** Takes counts as the bell's: from now on, they are known. */
149
+ const applyBellCounts = (counts: FeedCounts | undefined) => {
150
+ if (counts) applyFeedCounts(counts);
151
+ areCountsLoaded.value = true;
152
+ };
153
+
133
154
  /** Loads the whole feed's unread count and tone, the bell's badge. */
134
155
  const fetchBellCounts = async () => {
135
156
  try {
136
- const counts = await $authFetch<FeedCounts>(`${API_BASE}/counts`);
137
- if (counts) applyFeedCounts(counts);
157
+ applyBellCounts(await readBellCounts());
138
158
  } finally {
139
159
  areCountsLoaded.value = true;
140
160
  }
141
161
  };
142
162
 
163
+ /**
164
+ * Takes the counts a write answered or its realtime event carried. A
165
+ * write's own event brings the same counts again, which changes nothing;
166
+ * without counts (a server before they were sent) they are read.
167
+ */
168
+ const takeWriteCounts = async (counts: FeedCounts | undefined) => {
169
+ if (counts) applyBellCounts(counts);
170
+ else await fetchBellCounts();
171
+ };
172
+
143
173
  const fetchUnreadPreview = async () => {
144
174
  const data = await $authFetch<UserNotification[]>(
145
175
  `${API_BASE}/unread-preview`,
@@ -168,30 +198,33 @@ export const useNotifications = () => {
168
198
  };
169
199
 
170
200
  const markAsRead = async (notificationId: string) => {
171
- await $authFetch(`${API_BASE}/mark-read/${notificationId}`, {
172
- method: "PUT",
173
- });
201
+ const { counts } = await $authFetch<FeedWriteResult>(
202
+ `${API_BASE}/mark-read/${notificationId}`,
203
+ { method: "PUT" },
204
+ );
174
205
  setReadState([notificationId], true);
175
206
  unreadPreview.value = unreadPreview.value.filter((n) => !n.isRead);
176
- await fetchBellCounts();
207
+ await takeWriteCounts(counts);
177
208
  emitRead([notificationId]);
178
209
  };
179
210
 
180
211
  const markAsUnread = async (notificationId: string) => {
181
- await $authFetch(`${API_BASE}/mark-unread/${notificationId}`, {
182
- method: "PUT",
183
- });
212
+ const { counts } = await $authFetch<FeedWriteResult>(
213
+ `${API_BASE}/mark-unread/${notificationId}`,
214
+ { method: "PUT" },
215
+ );
184
216
  setReadState([notificationId], false);
185
- await fetchBellCounts();
217
+ await takeWriteCounts(counts);
186
218
  emitCount();
187
219
  };
188
220
 
189
221
  const deleteNotification = async (notificationId: string) => {
190
- await $authFetch(`${API_BASE}/delete/${notificationId}`, {
191
- method: "DELETE",
192
- });
222
+ const { counts } = await $authFetch<FeedWriteResult>(
223
+ `${API_BASE}/delete/${notificationId}`,
224
+ { method: "DELETE" },
225
+ );
193
226
  removeFromLists([notificationId]);
194
- await fetchBellCounts();
227
+ await takeWriteCounts(counts);
195
228
 
196
229
  sendComponentEvent(
197
230
  NotificationEvents.NOTIFICATION_DELETED,
@@ -200,24 +233,26 @@ export const useNotifications = () => {
200
233
  );
201
234
  };
202
235
 
203
- const handleRemoteRead = async (ids: string[]) => {
236
+ // The realtime events of the user's feed, this tab's own writes included:
237
+ // each carries the feed's counts after it.
238
+ const handleRemoteRead = async (ids: string[], counts?: FeedCounts) => {
204
239
  if (ids.length === 0) return;
205
240
  setReadState(ids, true);
206
241
  unreadPreview.value = unreadPreview.value.filter((n) => !n.isRead);
207
- await fetchBellCounts();
242
+ await takeWriteCounts(counts);
208
243
  emitRead(ids);
209
244
  };
210
245
 
211
- const handleRemoteUnread = async (ids: string[]) => {
246
+ const handleRemoteUnread = async (ids: string[], counts?: FeedCounts) => {
212
247
  if (ids.length === 0) return;
213
248
  setReadState(ids, false);
214
- await fetchBellCounts();
249
+ await takeWriteCounts(counts);
215
250
  emitCount();
216
251
  };
217
252
 
218
- const handleRemoteAllRead = async () => {
253
+ const handleRemoteAllRead = async (counts?: FeedCounts) => {
219
254
  setAllRead();
220
- await fetchBellCounts();
255
+ await takeWriteCounts(counts);
221
256
  emitCount();
222
257
  };
223
258
 
@@ -251,6 +286,8 @@ export const useNotifications = () => {
251
286
  notifications,
252
287
  hasMore,
253
288
  fetchBellCounts,
289
+ readBellCounts,
290
+ applyBellCounts,
254
291
  fetchUnreadPreview,
255
292
  fetchNotifications,
256
293
  markAsRead,
@@ -262,3 +299,47 @@ export const useNotifications = () => {
262
299
  handleRemoteAllRead,
263
300
  };
264
301
  };
302
+
303
+ /**
304
+ * The header bell's counts (the unread total and its tone) for the chrome
305
+ * drawing the bell, called in its setup: read with the page for a signed-in
306
+ * user, so a server render shows the badge, and the bell's state starts from
307
+ * the same counts in the browser before anything renders them. The browser
308
+ * neither asks again nor sees them move as it hydrates (which the inbox would
309
+ * take for a new notification and list again). The bell then keeps them
310
+ * current (realtime, its own refreshes).
311
+ */
312
+ export function useRenderedBellCounts(): void {
313
+ const notifications = useNotifications();
314
+ if (
315
+ !useDmsUserSession().loggedIn.value ||
316
+ notifications.areCountsLoaded.value
317
+ )
318
+ return;
319
+ const read = useApiData<FeedCounts>(
320
+ BELL_COUNTS_DATA_KEY,
321
+ async ({ signal }) => {
322
+ const counts = await notifications.readBellCounts(signal);
323
+ notifications.applyBellCounts(counts);
324
+ return counts;
325
+ },
326
+ );
327
+ // A client navigation into the dashboard mounts the bell before this read
328
+ // answers: the bell waits for it instead of reading the counts again.
329
+ if (!import.meta.env.SSR)
330
+ renderedBellCountsRead = Promise.resolve(read).then(
331
+ () => undefined,
332
+ () => undefined,
333
+ );
334
+ onBeforeMount(() => {
335
+ if (read.data.value) notifications.applyBellCounts(read.data.value);
336
+ });
337
+ }
338
+
339
+ /** The browser's last `useRenderedBellCounts` read, settled or not. */
340
+ let renderedBellCountsRead: Promise<void> | undefined;
341
+
342
+ /** Waits for the counts `useRenderedBellCounts` reads, if it reads any. */
343
+ export function settledRenderedBellCounts(): Promise<void> | undefined {
344
+ return renderedBellCountsRead;
345
+ }
@@ -0,0 +1,109 @@
1
+ import type {
2
+ StoreActionBody,
3
+ StoreListing,
4
+ StoreListingEntry,
5
+ StoreOperationKind,
6
+ } from "../../types/module-store";
7
+ import { isAwaitingFrontend } from "../../build/utils/module-store-catalog";
8
+
9
+ export const ENDPOINT_STORE_LISTING = "/dms/store/listing";
10
+ const ENDPOINT_STORE_ACTION = "/dms/store";
11
+ /** How often the listing is read while an operation or the frontend runs. */
12
+ const FOLLOW_INTERVAL_MS = 1000;
13
+
14
+ /**
15
+ * The module store of the Modules page: the listing, the operations, and the
16
+ * follow-up that reads the listing again while something moves. An operation
17
+ * the owner started here reloads the page once the frontend serves its
18
+ * result, so a new module's pages appear without a manual refresh.
19
+ */
20
+ export const useModuleStore = () => {
21
+ // Taken once, in setup: the follow-up reads the listing from a timer,
22
+ // outside any component context.
23
+ const { $authFetch } = useAuthFetch();
24
+ const listing = ref<StoreListing | undefined>(undefined);
25
+ const loadingError = ref<unknown>(null);
26
+ /** Modules an operation from this page changed, until the frontend follows. */
27
+ const awaitingReload = ref(new Set<string>());
28
+ let followTimer: ReturnType<typeof setTimeout> | undefined;
29
+
30
+ const isFollowing = computed(() => {
31
+ const modules = listing.value?.modules ?? [];
32
+ return (
33
+ listing.value?.busy === true ||
34
+ modules.some((entry) => isAwaitingFrontend(entry))
35
+ );
36
+ });
37
+
38
+ async function load(): Promise<void> {
39
+ try {
40
+ listing.value = await $authFetch<StoreListing>(ENDPOINT_STORE_LISTING);
41
+ loadingError.value = null;
42
+ } catch (error) {
43
+ loadingError.value = error;
44
+ }
45
+ reloadWhenServed();
46
+ scheduleFollow();
47
+ }
48
+
49
+ /**
50
+ * Reloads the page once every module an operation from here changed is
51
+ * served by the frontend (or has no interface at all). A frontend that
52
+ * never reported stays put: the page says what to run instead.
53
+ */
54
+ function reloadWhenServed(): void {
55
+ if (import.meta.env.SSR || awaitingReload.value.size === 0) return;
56
+ const changed = (listing.value?.modules ?? []).filter((entry) =>
57
+ awaitingReload.value.has(entry.id),
58
+ );
59
+ const isSettled = changed.every(
60
+ (entry) =>
61
+ entry.state !== "busy" &&
62
+ (entry.frontend === "synced" || entry.frontend === "none"),
63
+ );
64
+ const needsReload = changed.some((entry) => entry.frontend === "synced");
65
+ if (!isSettled) return;
66
+ awaitingReload.value = new Set();
67
+ if (needsReload) window.location.reload();
68
+ }
69
+
70
+ function scheduleFollow(): void {
71
+ if (import.meta.env.SSR) return;
72
+ if (followTimer) clearTimeout(followTimer);
73
+ followTimer = undefined;
74
+ if (!isFollowing.value) return;
75
+ followTimer = setTimeout(() => void load(), FOLLOW_INTERVAL_MS);
76
+ }
77
+
78
+ /**
79
+ * Starts `kind` on `entry`, with `body` when it carries more than the id;
80
+ * rejects with the server's answer when refused.
81
+ */
82
+ async function run(
83
+ kind: StoreOperationKind,
84
+ entry: StoreListingEntry,
85
+ body: StoreActionBody = { id: entry.id },
86
+ ): Promise<void> {
87
+ listing.value = await $authFetch<StoreListing>(
88
+ `${ENDPOINT_STORE_ACTION}/${kind}`,
89
+ { method: "POST", body },
90
+ );
91
+ awaitingReload.value = new Set([...awaitingReload.value, entry.id]);
92
+ scheduleFollow();
93
+ }
94
+
95
+ function stop(): void {
96
+ if (followTimer) clearTimeout(followTimer);
97
+ followTimer = undefined;
98
+ }
99
+
100
+ return {
101
+ listing,
102
+ loadingError,
103
+ isFollowing,
104
+ awaitingReload,
105
+ load,
106
+ run,
107
+ stop,
108
+ };
109
+ };
@@ -10,15 +10,7 @@ export interface ModulesListingError {
10
10
  status?: number;
11
11
  }
12
12
 
13
- const STATE_KEY_MODULES = "dms-modulesListing";
14
- const STATE_KEY_LOADING = "dms-modulesListingLoading";
15
- const STATE_KEY_ERROR = "dms-modulesListingError";
16
- const STATE_KEY_FETCHED_AT = "dms-modulesListingFetchedAt";
17
- // A listing younger than this is fresh enough to show without refetching:
18
- // it also covers the hydration right after a server render.
19
- const REVALIDATE_AFTER_MS = 30_000;
20
13
  export const ENDPOINT_MODULES_LISTING = "/dms/modules-listing";
21
- const IN_FLIGHT_MODULES_LISTING_KEY = "__dmsInFlightModulesListing";
22
14
 
23
15
  function readStatus(error: unknown): number | undefined {
24
16
  const details = error as
@@ -33,106 +25,37 @@ function toListingError(error: unknown): ModulesListingError {
33
25
  return { kind: "failed", status };
34
26
  }
35
27
 
28
+ /**
29
+ * The modules catalog of the Modules page, read for platform owners only (a
30
+ * member's request would be refused): with the page, so a server render
31
+ * shows it and hands it to the browser, which does not ask again.
32
+ */
36
33
  export const useModulesListing = () => {
37
- const modules = useDmsState<ModuleCatalogEntry[] | undefined>(
38
- STATE_KEY_MODULES,
39
- () => undefined,
34
+ const isOwner = useIsOwner();
35
+ const listing = useApiData<ModuleCatalogEntry[]>(() =>
36
+ isOwner.value ? ENDPOINT_MODULES_LISTING : null,
40
37
  );
41
- const isLoading = useDmsState<boolean>(STATE_KEY_LOADING, () => false);
42
- const loadingError = useDmsState<ModulesListingError | null>(
43
- STATE_KEY_ERROR,
44
- () => null,
45
- );
46
- const fetchedAt = useDmsState<number>(STATE_KEY_FETCHED_AT, () => 0);
47
-
48
- // Held on the DMS app, like the in-flight fetches of useSiteLayout: every
49
- // caller writes the same listing state, so they share one fetch. Not module
50
- // scope, which the server shares across the requests it renders at once: a
51
- // second render would wait on the first one's fetch, whose answer lands in
52
- // the first request's state, and render an empty listing.
53
- const dmsApp = useDmsApp() as unknown as Record<PropertyKey, unknown>;
54
- const inFlightLoadingPromise = {
55
- get value(): Promise<void> | null {
56
- return (dmsApp[IN_FLIGHT_MODULES_LISTING_KEY] as Promise<void>) ?? null;
57
- },
58
- set value(promise: Promise<void> | null) {
59
- dmsApp[IN_FLIGHT_MODULES_LISTING_KEY] = promise;
60
- },
61
- };
62
-
63
- /**
64
- * Fetches the catalog. With `keepOnFailure`, a failed request leaves the
65
- * modules already on screen in place (a background revalidation must not
66
- * blank the page); otherwise the failure replaces them with the error.
67
- */
68
- function fetchModulesListing(keepOnFailure = false): Promise<void> {
69
- const { $authFetch } = useAuthFetch();
70
- isLoading.value = true;
71
- if (!keepOnFailure) loadingError.value = null;
72
38
 
73
- const promise = $authFetch<ModuleCatalogEntry[]>(ENDPOINT_MODULES_LISTING)
74
- .then((result) => {
75
- modules.value = result;
76
- loadingError.value = null;
77
- fetchedAt.value = Date.now();
78
- })
79
- .catch((error: unknown) => {
80
- const listingError = toListingError(error);
81
- if (keepOnFailure && listingError.kind === "failed") return;
82
- loadingError.value = listingError;
83
- modules.value = undefined;
84
- })
85
- .finally(() => {
86
- isLoading.value = false;
87
- inFlightLoadingPromise.value = null;
88
- });
89
-
90
- inFlightLoadingPromise.value = promise;
91
- return promise;
92
- }
93
-
94
- async function loadModulesListing(): Promise<void> {
95
- if (inFlightLoadingPromise.value) {
96
- await inFlightLoadingPromise.value;
97
- return;
98
- }
99
- if (modules.value !== undefined) {
100
- return;
101
- }
102
- await fetchModulesListing();
103
- }
104
-
105
- /**
106
- * Loads the catalog again on request (Refresh, Try again): the current list
107
- * stays on screen while loading, and a failure is shown, not swallowed.
108
- */
109
- async function refresh(): Promise<void> {
110
- if (inFlightLoadingPromise.value) {
111
- await inFlightLoadingPromise.value;
112
- }
113
- await fetchModulesListing();
114
- }
115
-
116
- /**
117
- * Reloads statuses and readouts while keeping the current list on screen,
118
- * unless the listing is only a few seconds old: readouts are live figures,
119
- * so a later revisit should not show stale ones.
120
- */
121
- async function revalidate(): Promise<void> {
122
- if (inFlightLoadingPromise.value) {
123
- await inFlightLoadingPromise.value;
124
- return;
125
- }
126
- if (Date.now() - fetchedAt.value < REVALIDATE_AFTER_MS) return;
127
- await fetchModulesListing(true);
128
- }
39
+ const loadingError = computed<ModulesListingError | null>(() =>
40
+ listing.error.value ? toListingError(listing.error.value) : null,
41
+ );
42
+ // A failed read replaces the modules with the error.
43
+ const modules = computed<ModuleCatalogEntry[] | undefined>(() =>
44
+ loadingError.value ? undefined : (listing.data.value ?? undefined),
45
+ );
129
46
 
130
47
  return {
131
48
  modules,
132
- isLoading,
49
+ isLoading: listing.pending,
133
50
  loadingError,
134
- loadModulesListing,
135
- refresh,
136
- revalidate,
51
+ /** Waits for the catalog to be read. */
52
+ loadModulesListing: async (): Promise<void> => {
53
+ await listing;
54
+ },
55
+ /**
56
+ * Reads the catalog again (Refresh, Try again): the current list stays
57
+ * on screen while loading, and a failure is shown, not swallowed.
58
+ */
59
+ refresh: (): Promise<void> => listing.refresh(),
137
60
  };
138
61
  };
@@ -57,13 +57,3 @@ export function preloadPageLayoutComponents(
57
57
 
58
58
  loadPageLayoutComponents(layout).catch(() => {});
59
59
  }
60
-
61
- /**
62
- * Same as preloadPageLayoutComponents, for speculative loading (e.g. link
63
- * hover) where the user may never navigate to the page.
64
- */
65
- export function prefetchPageLayoutComponents(
66
- layout: PageLayout | null | undefined,
67
- ): void {
68
- preloadPageLayoutComponents(layout);
69
- }