@antelopejs/dms 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/implementations/dms/frontend-backend-proxies.d.ts +3 -0
  3. package/dist/implementations/dms/frontend-backend-proxies.js +39 -0
  4. package/dist/implementations/dms/frontend-backend-proxies.js.map +1 -0
  5. package/dist/implementations/dms/frontend-bootstrap.d.ts +1 -0
  6. package/dist/implementations/dms/frontend-bootstrap.js +13 -0
  7. package/dist/implementations/dms/frontend-bootstrap.js.map +1 -1
  8. package/dist/implementations/dms/frontend-modules.d.ts +4 -2
  9. package/dist/implementations/dms/frontend-modules.js +8 -7
  10. package/dist/implementations/dms/frontend-modules.js.map +1 -1
  11. package/dist/implementations/dms/manifest.d.ts +6 -0
  12. package/dist/implementations/dms/manifest.js +13 -0
  13. package/dist/implementations/dms/manifest.js.map +1 -1
  14. package/dist/implementations/dms/menu-categories.d.ts +16 -0
  15. package/dist/implementations/dms/menu-categories.js +30 -0
  16. package/dist/implementations/dms/menu-categories.js.map +1 -0
  17. package/dist/implementations/dms/nav-badges.d.ts +0 -8
  18. package/dist/implementations/dms/nav-badges.js +0 -16
  19. package/dist/implementations/dms/nav-badges.js.map +1 -1
  20. package/dist/implementations/dms/page.d.ts +11 -5
  21. package/dist/implementations/dms/page.js +56 -41
  22. package/dist/implementations/dms/page.js.map +1 -1
  23. package/dist/implementations/dms-html-render/index.js +5 -2
  24. package/dist/implementations/dms-html-render/index.js.map +1 -1
  25. package/dist/implementations/dms-notifications/index.d.ts +4 -3
  26. package/dist/implementations/dms-notifications/index.js +14 -6
  27. package/dist/implementations/dms-notifications/index.js.map +1 -1
  28. package/dist/pages/settings/settings-index.js +3 -2
  29. package/dist/pages/settings/settings-index.js.map +1 -1
  30. package/dist/pages/settings/users/invites.js +2 -2
  31. package/dist/pages/settings/users/invites.js.map +1 -1
  32. package/dist/pages/settings/users/member-change-notifications.js +17 -6
  33. package/dist/pages/settings/users/member-change-notifications.js.map +1 -1
  34. package/dist/pages/settings/users/member-invite-form.d.ts +3 -2
  35. package/dist/pages/settings/users/member-invite-form.js +7 -4
  36. package/dist/pages/settings/users/member-invite-form.js.map +1 -1
  37. package/dist/pages/settings/users/member-roles-field.js +2 -1
  38. package/dist/pages/settings/users/member-roles-field.js.map +1 -1
  39. package/dist/pages/settings/users/members.js +4 -1
  40. package/dist/pages/settings/users/members.js.map +1 -1
  41. package/dist/pages/settings/users/notifications.d.ts +6 -0
  42. package/dist/pages/settings/users/notifications.js +23 -15
  43. package/dist/pages/settings/users/notifications.js.map +1 -1
  44. package/dist/pages/settings/users/profile.js +1 -1
  45. package/dist/pages/settings/users/profile.js.map +1 -1
  46. package/dist/pages/settings/users/region-form.d.ts +1 -1
  47. package/dist/pages/settings/users/region-form.js +2 -1
  48. package/dist/pages/settings/users/region-form.js.map +1 -1
  49. package/dist/pages/settings/users/role-editor-store.d.ts +3 -1
  50. package/dist/pages/settings/users/role-editor-store.js +17 -4
  51. package/dist/pages/settings/users/role-editor-store.js.map +1 -1
  52. package/dist/pages/settings/users/roles.js +1 -0
  53. package/dist/pages/settings/users/roles.js.map +1 -1
  54. package/dist/realtime/registry.d.ts +1 -0
  55. package/dist/realtime/registry.js +5 -0
  56. package/dist/realtime/registry.js.map +1 -1
  57. package/dist/routes/realtime.d.ts +5 -1
  58. package/dist/routes/realtime.js +16 -9
  59. package/dist/routes/realtime.js.map +1 -1
  60. package/dist/test/integration/account-deletion.test.js +2 -2
  61. package/dist/test/integration/account-deletion.test.js.map +1 -1
  62. package/dist/test/integration/auth/auth-key-rotation.test.js +2 -1
  63. package/dist/test/integration/auth/auth-key-rotation.test.js.map +1 -1
  64. package/dist/test/integration/auth/current-password.test.js +2 -1
  65. package/dist/test/integration/auth/current-password.test.js.map +1 -1
  66. package/dist/test/integration/auth/session-revocation.test.js +2 -1
  67. package/dist/test/integration/auth/session-revocation.test.js.map +1 -1
  68. package/dist/test/integration/auth/sign-in-country.test.js +2 -1
  69. package/dist/test/integration/auth/sign-in-country.test.js.map +1 -1
  70. package/dist/test/integration/auth/two-factor-codes.test.js +2 -1
  71. package/dist/test/integration/auth/two-factor-codes.test.js.map +1 -1
  72. package/dist/test/integration/auth/two-factor-password.test.js +3 -2
  73. package/dist/test/integration/auth/two-factor-password.test.js.map +1 -1
  74. package/dist/test/integration/email-change.test.js +2 -1
  75. package/dist/test/integration/email-change.test.js.map +1 -1
  76. package/dist/test/integration/files/component-files.test.js +2 -2
  77. package/dist/test/integration/files/component-files.test.js.map +1 -1
  78. package/dist/test/integration/member-settings-access.test.js +8 -7
  79. package/dist/test/integration/member-settings-access.test.js.map +1 -1
  80. package/dist/test/integration/menu-access-events.test.d.ts +1 -0
  81. package/dist/test/integration/menu-access-events.test.js +148 -0
  82. package/dist/test/integration/menu-access-events.test.js.map +1 -0
  83. package/dist/test/integration/notification-inbox.test.js +4 -1
  84. package/dist/test/integration/notification-inbox.test.js.map +1 -1
  85. package/dist/test/integration/notification-locked-subjects.test.js +3 -2
  86. package/dist/test/integration/notification-locked-subjects.test.js.map +1 -1
  87. package/dist/test/integration/notification-unread-badge.test.js +51 -5
  88. package/dist/test/integration/notification-unread-badge.test.js.map +1 -1
  89. package/dist/test/integration/page-layout-route-guard.test.d.ts +1 -0
  90. package/dist/test/integration/page-layout-route-guard.test.js +56 -0
  91. package/dist/test/integration/page-layout-route-guard.test.js.map +1 -0
  92. package/dist/test/integration/permission-warnings.test.js +3 -2
  93. package/dist/test/integration/permission-warnings.test.js.map +1 -1
  94. package/dist/test/integration/removed-member-notifications.test.js +3 -2
  95. package/dist/test/integration/removed-member-notifications.test.js.map +1 -1
  96. package/dist/test/integration/role-delete-reassign.test.js +2 -1
  97. package/dist/test/integration/role-delete-reassign.test.js.map +1 -1
  98. package/dist/test/integration/role-permission-ancestors.test.js +2 -1
  99. package/dist/test/integration/role-permission-ancestors.test.js.map +1 -1
  100. package/dist/test/unit/frontend-bootstrap.test.js +13 -0
  101. package/dist/test/unit/frontend-bootstrap.test.js.map +1 -1
  102. package/dist/test/unit/implementations/dms/category-slug-registry.test.js +14 -5
  103. package/dist/test/unit/implementations/dms/category-slug-registry.test.js.map +1 -1
  104. package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.d.ts +1 -0
  105. package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.js +66 -0
  106. package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.js.map +1 -0
  107. package/dist/test/unit/implementations/dms/manifest-strip.test.js +26 -0
  108. package/dist/test/unit/implementations/dms/manifest-strip.test.js.map +1 -1
  109. package/dist/test/unit/implementations/dms/nav-badges.test.js +23 -9
  110. package/dist/test/unit/implementations/dms/nav-badges.test.js.map +1 -1
  111. package/dist/test/unit/implementations/dms-html-render/generate-html.test.js +22 -4
  112. package/dist/test/unit/implementations/dms-html-render/generate-html.test.js.map +1 -1
  113. package/dist/test/unit/interfaces/dms/page-layout-query.test.js +12 -0
  114. package/dist/test/unit/interfaces/dms/page-layout-query.test.js.map +1 -1
  115. package/dist/test/unit/interfaces/dms/page-teardown.test.js +1 -1
  116. package/dist/test/unit/interfaces/dms/page-teardown.test.js.map +1 -1
  117. package/dist/test/unit/interfaces/dms-base/table-and-card-catalog.test.js +25 -0
  118. package/dist/test/unit/interfaces/dms-base/table-and-card-catalog.test.js.map +1 -1
  119. package/dist/test/unit/pages/settings/member-roles-field.test.js +1 -1
  120. package/dist/test/unit/pages/settings/member-roles-field.test.js.map +1 -1
  121. package/dist/test/unit/pages/settings/role-delete-confirm.test.js.map +1 -1
  122. package/dist/test/unit/realtime/menu-topics.test.d.ts +1 -0
  123. package/dist/test/unit/realtime/menu-topics.test.js +26 -0
  124. package/dist/test/unit/realtime/menu-topics.test.js.map +1 -0
  125. package/dist/validation/regional-preferences.schema.d.ts +2 -2
  126. package/frontend-vue/deferred-plugins.ts +3 -4
  127. package/frontend-vue/dms.email.ts +5 -1
  128. package/frontend-vue/dms.frontend.ts +15 -5
  129. package/frontend-vue/dms.server.ts +74 -0
  130. package/frontend-vue/layers/dms-auth/app/composables/useLogout.ts +1 -1
  131. package/frontend-vue/layers/dms-auth/app/composables/usePostLoginRedirect.ts +16 -3
  132. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/2fa.vue +3 -1
  133. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/backup.vue +3 -1
  134. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/forgot-validation.vue +1 -1
  135. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/oauth-complete.vue +26 -3
  136. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/recover.vue +1 -1
  137. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +22 -25
  138. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/validate.vue +1 -1
  139. package/frontend-vue/layers/dms-auth/app/middleware/auth.ts +5 -7
  140. package/frontend-vue/layers/dms-auth/app/plugins/permissions-preload.ts +17 -0
  141. package/frontend-vue/layers/dms-auth/server/accounts.ts +135 -0
  142. package/frontend-vue/layers/dms-auth/server/actions.ts +251 -0
  143. package/frontend-vue/layers/dms-auth/server/oauth.ts +260 -0
  144. package/frontend-vue/layers/dms-auth/server/session.ts +158 -0
  145. package/frontend-vue/layers/dms-core/app/build/utils/asyncDataScope.ts +47 -0
  146. package/frontend-vue/layers/dms-core/app/build/utils/permission-preview.ts +6 -2
  147. package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +148 -0
  148. package/frontend-vue/layers/dms-core/app/composables/auth/useAuthFetch.ts +7 -20
  149. package/frontend-vue/layers/dms-core/app/composables/auth/useCurrentUser.ts +2 -2
  150. package/frontend-vue/layers/dms-core/app/composables/auth/useMultiAccount.ts +18 -5
  151. package/frontend-vue/layers/dms-core/app/composables/auth/usePermissions.ts +35 -6
  152. package/frontend-vue/layers/dms-core/app/composables/auth/useSessionRecovery.ts +1 -1
  153. package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +20 -14
  154. package/frontend-vue/layers/dms-core/app/composables/period/periodClock.ts +121 -0
  155. package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +42 -0
  156. package/frontend-vue/layers/dms-core/app/composables/period/periodTimeZone.ts +31 -0
  157. package/frontend-vue/layers/dms-core/app/composables/period/presetResolvers.ts +48 -19
  158. package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +33 -4
  159. package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +78 -25
  160. package/frontend-vue/layers/dms-core/app/composables/realtime/usePageRealtime.ts +3 -0
  161. package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +36 -6
  162. package/frontend-vue/layers/dms-core/app/composables/user/useUserRegionalPreferences.ts +1 -1
  163. package/frontend-vue/layers/dms-core/app/plugins/async-data-scope.ts +26 -0
  164. package/frontend-vue/layers/dms-core/app/utils/pageRoutes.ts +22 -0
  165. package/frontend-vue/layers/dms-core/app/utils/regional.ts +36 -3
  166. package/frontend-vue/layers/dms-core/app/utils/trailingDeduper.ts +10 -1
  167. package/frontend-vue/layers/dms-core/shared/types/app-config.ts +48 -4
  168. package/frontend-vue/layers/dms-layout/app/app.config.ts +42 -23
  169. package/frontend-vue/layers/dms-layout/app/assets/css/main.css +72 -20
  170. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardFrame.vue +9 -0
  171. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +0 -3
  172. package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +9 -3
  173. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/members/MemberRolePicker.vue +13 -12
  174. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +17 -7
  175. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationPreferencesForm.vue +0 -1
  176. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfilePersonalInfo.vue +36 -33
  177. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfileSecuritySummary.vue +1 -2
  178. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-types.ts +5 -4
  179. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecuritySessions.vue +35 -28
  180. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityStatus.vue +0 -1
  181. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityTwoFactor.vue +1 -1
  182. package/frontend-vue/layers/dms-layout/app/build/composables/dev-reload/useDevReloadHolder.ts +7 -6
  183. package/frontend-vue/layers/dms-layout/app/build/composables/layout/sidePanelState.ts +1 -1
  184. package/frontend-vue/layers/dms-layout/app/build/composables/navigation/useVisitNavBadges.ts +26 -0
  185. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationCatalog.ts +25 -23
  186. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationPreferences.ts +44 -23
  187. package/frontend-vue/layers/dms-layout/app/build/composables/security/useSessionHandoff.ts +1 -1
  188. package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/preferencesSummary.ts +1 -1
  189. package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useAccountData.ts +3 -2
  190. package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfileAvatar.ts +1 -18
  191. package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfilePreferences.ts +19 -23
  192. package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityFormat.ts +6 -3
  193. package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityOverview.ts +13 -18
  194. package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +8 -6
  195. package/frontend-vue/layers/dms-layout/app/build/utils/publicErrorMessage.ts +19 -0
  196. package/frontend-vue/layers/dms-layout/app/components/navigation/CollapsedMenuItem.vue +0 -11
  197. package/frontend-vue/layers/dms-layout/app/components/navigation/NavigationMenu.vue +3 -13
  198. package/frontend-vue/layers/dms-layout/app/composables/general/useSystemState.ts +7 -11
  199. package/frontend-vue/layers/dms-layout/app/composables/notification/useNotifications.ts +103 -22
  200. package/frontend-vue/layers/dms-layout/app/composables/page/useModulesListing.ts +25 -102
  201. package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +0 -10
  202. package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +177 -96
  203. package/frontend-vue/layers/dms-layout/app/composables/profile/useUserLanguage.ts +3 -2
  204. package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +6 -28
  205. package/frontend-vue/layers/dms-layout/app/emails/components/EmailLayout.vue +31 -11
  206. package/frontend-vue/layers/dms-layout/app/error.vue +5 -2
  207. package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +1 -1
  208. package/frontend-vue/layers/dms-layout/app/middleware/page-leave-guard.global.ts +2 -2
  209. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +12 -9
  210. package/frontend-vue/layers/dms-layout/app/plugins/dms-menu-sync.client.ts +31 -3
  211. package/frontend-vue/layers/dms-layout/app/plugins/favicon.ts +58 -0
  212. package/frontend-vue/layers/dms-layout/app/plugins/notification-stream.client.ts +15 -7
  213. package/frontend-vue/layers/dms-layout/app/plugins/profile-sync.client.ts +5 -1
  214. package/frontend-vue/layers/dms-layout/app/plugins/regional-preferences.ts +10 -4
  215. package/frontend-vue/layers/dms-layout/app/plugins/time-zone-cookie.client.ts +12 -0
  216. package/frontend-vue/layers/dms-layout/app/types/page.ts +13 -0
  217. package/frontend-vue/layers/dms-layout/server/html-render.ts +58 -0
  218. package/frontend-vue/layers/dms-layout/server/render-token.ts +95 -0
  219. package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/useOnboarding.ts +25 -21
  220. package/frontend-vue/layers/dms-onboarding/app/middleware/onboarding.global.ts +1 -1
  221. package/frontend-vue/layers/dms-onboarding/app/plugins/onboarding-preload.ts +9 -0
  222. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +4 -1
  223. package/frontend-vue/layers/dms-ui/app/build/composables/files/useFileMetadata.ts +162 -0
  224. package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +23 -4
  225. package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageDataRefresh.ts +101 -0
  226. package/frontend-vue/layers/dms-ui/app/build/composables/navigation/useNavBadges.ts +33 -6
  227. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +19 -16
  228. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodQuery.ts +40 -20
  229. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +6 -8
  230. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableListData.ts +42 -14
  231. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +19 -8
  232. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +1 -1
  233. package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +5 -2
  234. package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +9 -9
  235. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +20 -18
  236. package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +2 -1
  237. package/frontend-vue/layers/dms-ui/app/components/form/components/File.vue +26 -132
  238. package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +40 -105
  239. package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +2 -0
  240. package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +4 -4
  241. package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +9 -13
  242. package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +2 -1
  243. package/frontend-vue/layers/dms-ui/app/components/table-view/FilePreview.vue +12 -24
  244. package/frontend-vue/layers/dms-ui/app/components/table-view/ImagePreview.vue +17 -29
  245. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +7 -3
  246. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +20 -8
  247. package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +8 -11
  248. package/frontend-vue/layers/dms-ui/app/composables/chart/useChartFetch.ts +146 -182
  249. package/frontend-vue/layers/dms-ui/app/composables/live/usePageLiveRefresh.ts +8 -2
  250. package/frontend-vue/layers/dms-ui/app/composables/useFileReadUrls.ts +11 -99
  251. package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +32 -3
  252. package/frontend-vue/package.json +2 -2
  253. package/frontend-vue/pnpm-lock.yaml +5 -5
  254. package/frontend-vue/server/private-options.ts +27 -0
  255. package/frontend-vue/tests/async-data-scope.test.ts +121 -0
  256. package/frontend-vue/tests/auth-middleware-error.test.ts +3 -3
  257. package/frontend-vue/tests/banner-block-fetch.test.ts +3 -0
  258. package/frontend-vue/tests/block-fetch-options-forwarding.test.ts +3 -0
  259. package/frontend-vue/tests/block-items-period-realtime.test.ts +3 -0
  260. package/frontend-vue/tests/block-not-found.test.ts +16 -2
  261. package/frontend-vue/tests/block-page-refresh.test.ts +3 -0
  262. package/frontend-vue/tests/block-route-params.test.ts +3 -0
  263. package/frontend-vue/tests/branding-defaults.test.ts +34 -0
  264. package/frontend-vue/tests/builtin-emails.test.ts +23 -2
  265. package/frontend-vue/tests/chart-live-pause.test.ts +4 -1
  266. package/frontend-vue/tests/chart-value-propagation.test.ts +4 -1
  267. package/frontend-vue/tests/composed-text-blocks.test.ts +3 -0
  268. package/frontend-vue/tests/email-layout.test.ts +66 -4
  269. package/frontend-vue/tests/error-page-message.test.ts +25 -2
  270. package/frontend-vue/tests/favicon.test.ts +131 -0
  271. package/frontend-vue/tests/file-fields-ssr.test.ts +145 -0
  272. package/frontend-vue/tests/file-read-urls.test.ts +145 -34
  273. package/frontend-vue/tests/file-unavailable.test.ts +5 -1
  274. package/frontend-vue/tests/fixtures/dataEngine.ts +44 -0
  275. package/frontend-vue/tests/form-load-error.test.ts +55 -1
  276. package/frontend-vue/tests/helpers/api-data-runtime.ts +77 -0
  277. package/frontend-vue/tests/image-hydration-ids.test.ts +21 -16
  278. package/frontend-vue/tests/image-reference-preservation.test.ts +59 -53
  279. package/frontend-vue/tests/input-number-adjust.test.ts +14 -0
  280. package/frontend-vue/tests/key-value-list-copy.test.ts +3 -0
  281. package/frontend-vue/tests/member-role-picker.test.ts +4 -2
  282. package/frontend-vue/tests/menu-sync-reconnect.test.ts +47 -0
  283. package/frontend-vue/tests/metadata-retry.test.ts +5 -1
  284. package/frontend-vue/tests/module-routing.test.ts +2 -5
  285. package/frontend-vue/tests/modules-catalog.test.ts +17 -4
  286. package/frontend-vue/tests/modules-listing-transfer.test.ts +95 -0
  287. package/frontend-vue/tests/multi-account-validate.test.ts +23 -1
  288. package/frontend-vue/tests/nav-badge-tones.test.ts +66 -3
  289. package/frontend-vue/tests/notification-counts-from-writes.test.ts +223 -0
  290. package/frontend-vue/tests/notification-counts-transfer.test.ts +88 -0
  291. package/frontend-vue/tests/notification-inbox-display.test.ts +57 -5
  292. package/frontend-vue/tests/notification-popover-errors.test.ts +21 -1
  293. package/frontend-vue/tests/oauth-complete-handoff.test.ts +209 -0
  294. package/frontend-vue/tests/onboarding-middleware.test.ts +1 -1
  295. package/frontend-vue/tests/onboarding-transfer.test.ts +106 -0
  296. package/frontend-vue/tests/page-data-refresh.test.ts +141 -0
  297. package/frontend-vue/tests/page-fill-height.test.ts +16 -3
  298. package/frontend-vue/tests/page-no-visible-blocks.test.ts +12 -2
  299. package/frontend-vue/tests/page-realtime-remount.test.ts +1 -1
  300. package/frontend-vue/tests/page-record-label.test.ts +0 -7
  301. package/frontend-vue/tests/page-validation-redirect.test.ts +12 -2
  302. package/frontend-vue/tests/period-hydration-now.test.ts +185 -0
  303. package/frontend-vue/tests/period-live-pause.test.ts +3 -0
  304. package/frontend-vue/tests/period-reader-zone.test.ts +137 -0
  305. package/frontend-vue/tests/period-scope-first-fetch.test.ts +17 -1
  306. package/frontend-vue/tests/period-ssr.test.ts +195 -0
  307. package/frontend-vue/tests/permission-preview-partial.test.ts +0 -2
  308. package/frontend-vue/tests/permissions-transfer.test.ts +194 -0
  309. package/frontend-vue/tests/post-login-redirect.test.ts +10 -5
  310. package/frontend-vue/tests/profile-personal-info-ssr.test.ts +150 -0
  311. package/frontend-vue/tests/profile-preferences-summary.test.ts +15 -11
  312. package/frontend-vue/tests/realtime-reconnect.test.ts +22 -3
  313. package/frontend-vue/tests/roles-name-errors.test.ts +1 -1
  314. package/frontend-vue/tests/row-side-panel.test.ts +1 -1
  315. package/frontend-vue/tests/security-sessions-load-error.test.ts +3 -1
  316. package/frontend-vue/tests/server-routes-vs-pages.test.ts +26 -0
  317. package/frontend-vue/tests/server-routes.test.ts +582 -0
  318. package/frontend-vue/tests/session-handoff.test.ts +1 -1
  319. package/frontend-vue/tests/settings-reads-ssr.test.ts +232 -0
  320. package/frontend-vue/tests/side-panels.test.ts +8 -1
  321. package/frontend-vue/tests/sign-in-exit-history.test.ts +219 -0
  322. package/frontend-vue/tests/signup-invitation-link.test.ts +56 -8
  323. package/frontend-vue/tests/site-layout-categories.test.ts +42 -0
  324. package/frontend-vue/tests/site-layout-hydration.test.ts +3 -3
  325. package/frontend-vue/tests/site-layout-rendered.test.ts +103 -0
  326. package/frontend-vue/tests/site-layout-resync.test.ts +87 -0
  327. package/frontend-vue/tests/ssr-guard-preload.test.ts +188 -0
  328. package/frontend-vue/tests/table-cell-tones.test.ts +36 -1
  329. package/frontend-vue/tests/table-data-ssr.test.ts +195 -0
  330. package/frontend-vue/tests/table-list-data-requests.test.ts +154 -0
  331. package/frontend-vue/tests/table-retry-loading.test.ts +37 -0
  332. package/frontend-vue/tests/table-source-navigation.test.ts +0 -1
  333. package/frontend-vue/tests/table-source-period-scope.test.ts +0 -1
  334. package/frontend-vue/tests/table-view-views.test.ts +1 -1
  335. package/frontend-vue/tests/top-list-card-actions.test.ts +3 -0
  336. package/frontend-vue/tests/top-list-card-period-badge.test.ts +79 -0
  337. package/frontend-vue/tests/top-list-card-value-suffix.test.ts +3 -0
  338. package/frontend-vue/tests/trailing-deduper.test.ts +22 -0
  339. package/frontend-vue/tests/use-api-data-update.test.ts +119 -0
  340. package/frontend-vue/tests/use-api-data.test.ts +410 -0
  341. package/frontend-vue/tests/user-language.test.ts +8 -5
  342. package/frontend-vue/tests/widget-label-i18n.test.ts +4 -1
  343. package/frontend-vue/vitest.config.ts +6 -0
  344. package/package.json +2 -2
  345. package/skills/dms/SKILL.md +2 -1
  346. package/skills/dms-module/SKILL.md +1 -1
  347. package/skills/dms-pages/SKILL.md +4 -1
  348. package/frontend-vue/layers/dms-core/app/build/composables/page/usePageRecordLabel.ts +0 -9
  349. package/frontend-vue/layers/dms-layout/app/composables/page/usePrefetch.ts +0 -21
  350. package/frontend-vue/layers/dms-ui/app/build/composables/form/useControlError.ts +0 -10
  351. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useServerRenderedAsyncData.ts +0 -23
  352. package/frontend-vue/tests/control-error-path.test.ts +0 -11
  353. package/frontend-vue/tests/modules-listing-per-request.test.ts +0 -104
  354. package/frontend-vue/tests/page-layout-background-refresh.test.ts +0 -321
@@ -1,4 +1,4 @@
1
- import { type Ref, ref } from "vue";
1
+ import { type Ref, ref, watch } from "vue";
2
2
  import { apiErrorStatus } from "#dms-core/app/composables/useFieldErrors";
3
3
  import { HTTP_FORBIDDEN } from "#dms-core/app/utils/http-status";
4
4
 
@@ -27,9 +27,7 @@ export function missingRecordFailure(
27
27
  address: FormRecordAddress,
28
28
  ): FormLoadFailure | null {
29
29
  const isMissing =
30
- !!address.requiresRecord &&
31
- !!address.fetchUrl &&
32
- !address.resolvedFetchUrl;
30
+ !!address.requiresRecord && !!address.fetchUrl && !address.resolvedFetchUrl;
33
31
  return isMissing ? "missing" : null;
34
32
  }
35
33
 
@@ -56,15 +54,31 @@ export function formLoadFailure(error: unknown): FormLoadFailure | null {
56
54
  return apiErrorStatus(error) === HTTP_FORBIDDEN ? "forbidden" : "error";
57
55
  }
58
56
 
57
+ /** How a form takes the answers of its record's later reads. */
58
+ export interface FormRecordLoadOptions {
59
+ /**
60
+ * Whether the user holds edits a later read must not replace: the read is
61
+ * then left for the next opening (a save reads the record again).
62
+ */
63
+ isEditing?: () => boolean;
64
+ /** Called once a later read was applied: its values are the saved ones. */
65
+ onReloaded?: () => void;
66
+ }
67
+
59
68
  /**
60
69
  * Follow a form's record load: `apply` receives the record once it loaded,
61
70
  * and only then. A failed load is never applied, not even the values a
62
71
  * previous opening left in the cache: a form showing them, or its defaults,
63
72
  * would read as the record and save over it.
73
+ *
74
+ * The record is read again in the background each time the form mounts on a
75
+ * cached one, and when the page refreshes its data: the form opens on the
76
+ * cached values, then takes the new ones, unless the user started editing.
64
77
  */
65
78
  export function trackFormRecordLoad<T>(
66
79
  load: FormLoadSource<T>,
67
80
  apply: (payload: T) => void,
81
+ options: FormRecordLoadOptions = {},
68
82
  ): FormRecordLoad {
69
83
  const failure = ref<FormLoadFailure | null>(null);
70
84
  const retrying = ref(false);
@@ -74,6 +88,11 @@ export function trackFormRecordLoad<T>(
74
88
  if (!failure.value && payload) apply(payload);
75
89
  };
76
90
  settle();
91
+ watch([load.data, load.error], () => {
92
+ if (options.isEditing?.()) return;
93
+ settle();
94
+ if (!failure.value) options.onReloaded?.();
95
+ });
77
96
  return {
78
97
  failure,
79
98
  retrying,
@@ -0,0 +1,101 @@
1
+ import { onScopeDispose, type Ref, watch } from "vue";
2
+ import { refreshDmsPageData } from "#dms/frontend-module";
3
+ import type { RealtimeTopicHandler } from "../../../../../dms-core/app/composables/realtime/useUserRealtime";
4
+ import {
5
+ onPageBlocksRefresh,
6
+ waitForPageBlocksRefresh,
7
+ } from "../../../utils/blockRefresh";
8
+ import { useRefreshTriggers } from "../blocks/useRefreshTriggers";
9
+
10
+ // One frame: the events of one burst (a bulk edit) read the data once.
11
+ const BURST_WINDOW_MS = 16;
12
+
13
+ /** What the page's data refresh listens to. */
14
+ export interface PageDataRefreshSource {
15
+ /** The page's realtime topics, once its session is subscribed to it. */
16
+ pageTopics: Ref<string[]>;
17
+ subscribe(topic: string, handler: RealtimeTopicHandler): () => void;
18
+ }
19
+
20
+ /**
21
+ * Reads again the data the page's components show through `useApiData` (and
22
+ * `useDmsAsyncData`), with `refreshDmsPageData()`: on each
23
+ * `refreshPageBlocks()`, an action of the user, at once; on each event of one
24
+ * of the page's realtime topics and each `refreshLivePageBlocks()`, through
25
+ * the page's live gate, like a built-in block. A read opts out with
26
+ * `refreshOnPageRefresh: false`, as the built-in blocks that refresh on their
27
+ * own triggers do. The page renderer calls it once, from its setup;
28
+ * `refreshPageBlocks()` resolves once the refresh it asked for is done.
29
+ */
30
+ export function usePageDataRefresh(source: PageDataRefreshSource): void {
31
+ const { refresh, cancel } = burstRefresh();
32
+ const gate = useRefreshTriggers(undefined, () => void refresh());
33
+ // The gate's own listener reads at once on the same event: this call joins
34
+ // the same burst, and hands its promise to the caller.
35
+ const stopWaiting = onPageBlocksRefresh((event) =>
36
+ waitForPageBlocksRefresh(event, refresh()),
37
+ );
38
+ // Presence snapshots ride the same topics: they carry no `type`.
39
+ const onEvent: RealtimeTopicHandler = (event) => {
40
+ if ("type" in event) gate.trigger(event);
41
+ };
42
+ let unsubscribes: Array<() => void> = [];
43
+ const unsubscribeAll = () => {
44
+ for (const unsubscribe of unsubscribes) unsubscribe();
45
+ unsubscribes = [];
46
+ };
47
+ watch(
48
+ source.pageTopics,
49
+ (topics) => {
50
+ unsubscribeAll();
51
+ unsubscribes = topics.map((topic) => source.subscribe(topic, onEvent));
52
+ },
53
+ { immediate: true },
54
+ );
55
+ onScopeDispose(() => {
56
+ unsubscribeAll();
57
+ stopWaiting();
58
+ cancel();
59
+ });
60
+ }
61
+
62
+ interface BurstRefresh {
63
+ /** Joins the burst under way, or starts one; resolves once it is read. */
64
+ refresh(): Promise<void>;
65
+ cancel(): void;
66
+ }
67
+
68
+ interface Burst {
69
+ promise: Promise<void>;
70
+ done(): void;
71
+ }
72
+
73
+ function startBurst(): Burst {
74
+ let done!: () => void;
75
+ const promise = new Promise<void>((resolve) => {
76
+ done = resolve;
77
+ });
78
+ return { promise, done };
79
+ }
80
+
81
+ /** `refreshDmsPageData()`, once for the calls of one burst. */
82
+ function burstRefresh(): BurstRefresh {
83
+ let timer: ReturnType<typeof setTimeout> | undefined;
84
+ let burst: Burst | undefined;
85
+ return {
86
+ refresh() {
87
+ clearTimeout(timer);
88
+ const current = (burst ??= startBurst());
89
+ timer = setTimeout(() => {
90
+ burst = undefined;
91
+ refreshDmsPageData().then(current.done, current.done);
92
+ }, BURST_WINDOW_MS);
93
+ return current.promise;
94
+ },
95
+ cancel() {
96
+ clearTimeout(timer);
97
+ burst?.done();
98
+ burst = undefined;
99
+ },
100
+ };
101
+ }
@@ -10,17 +10,25 @@ export interface NavBadge {
10
10
  /** A menu entry as the server serves it, as far as its badge goes. */
11
11
  export interface ServedNavBadge {
12
12
  fullId?: string;
13
+ /** A static badge the page declares. */
13
14
  badge?: string;
14
- /** The tone of a counted badge; neutral when absent. */
15
15
  badgeTone?: Tone;
16
16
  }
17
17
 
18
+ /** A badge a page visit carries (`navBadges`), counted for this visit. */
19
+ export interface CountedNavBadge {
20
+ badge: string;
21
+ /** Neutral when absent. */
22
+ tone?: Tone;
23
+ }
24
+
18
25
  /**
19
- * Fresh counts a page publishes for its navigation entry, by page full id
26
+ * The counts of the navigation entries, by page full id
20
27
  * (`settings.workspace.members` → `{ label: "7" }`, `""` for none), with
21
- * their tone. The server counts a table view tab declared with `navBadge`
22
- * when the menu loads; the table publishes its own counter here once shown,
23
- * which navigation surfaces draw over the server's badge.
28
+ * their tone. Each page visit brings the ones the server counted for it
29
+ * (`navBadges`, a page prop: the site layout a browser keeps for minutes
30
+ * holds none), and a table view tab declared with `navBadge` publishes its
31
+ * own counter once shown; navigation surfaces draw them over a static badge.
24
32
  *
25
33
  * Shared state, so a count published on one page stays shown on the others.
26
34
  */
@@ -29,6 +37,7 @@ export const useNavBadges = () => {
29
37
  "dms-nav-badges",
30
38
  () => ({}),
31
39
  );
40
+ const servedIds = useDmsState<string[]>("dms-served-nav-badges", () => []);
32
41
 
33
42
  /** Sets, or with `undefined` clears, the badge of a page. */
34
43
  function setNavBadge(
@@ -61,5 +70,23 @@ export const useNavBadges = () => {
61
70
  return badge?.label ? badge : undefined;
62
71
  }
63
72
 
64
- return { badges, setNavBadge, navBadgeOf };
73
+ /**
74
+ * Takes the counts a page visit carries: each one replaces the entry's
75
+ * badge, and an entry the previous visit counted that this one does not
76
+ * (its count fell to zero) loses its badge.
77
+ */
78
+ function applyCountedNavBadges(
79
+ counted: Record<string, CountedNavBadge> | undefined,
80
+ ): void {
81
+ if (!counted) return;
82
+ for (const fullId of servedIds.value) {
83
+ if (!(fullId in counted)) setNavBadge(fullId, "");
84
+ }
85
+ for (const [fullId, { badge, tone }] of Object.entries(counted)) {
86
+ setNavBadge(fullId, badge, tone);
87
+ }
88
+ servedIds.value = Object.keys(counted);
89
+ }
90
+
91
+ return { badges, setNavBadge, navBadgeOf, applyCountedNavBadges };
65
92
  };
@@ -14,6 +14,7 @@ import {
14
14
  type PeriodRange,
15
15
  } from "#dms-core/app/composables/period/types";
16
16
  import { regionalDateTimeFormat } from "#dms-core/app/utils/regional";
17
+ import { readerClock } from "#dms-core/app/composables/period/periodClock";
17
18
 
18
19
  /** A day range as the range calendar reads and writes it. */
19
20
  export interface CalendarRange {
@@ -102,20 +103,16 @@ export function isBeyondMaxDays(
102
103
  return Math.abs(date.compare(pickedDay)) >= maxDays;
103
104
  }
104
105
 
106
+ // A period's days are the reader's calendar days (`readerClock`), on the
107
+ // server as in the browser.
105
108
  function isStartOfDay(date: Date): boolean {
106
- return (
107
- date.getHours() === 0 &&
108
- date.getMinutes() === 0 &&
109
- date.getSeconds() === 0 &&
110
- date.getMilliseconds() === 0
111
- );
109
+ const { hours, minutes, seconds, milliseconds } = readerClock().wall(date);
110
+ return hours === 0 && minutes === 0 && seconds === 0 && milliseconds === 0;
112
111
  }
113
112
 
114
113
  function isEndOfDay(date: Date): boolean {
115
- return (
116
- date.getHours() === END_OF_DAY_HOURS &&
117
- date.getMinutes() === END_OF_DAY_MINUTES
118
- );
114
+ const { hours, minutes } = readerClock().wall(date);
115
+ return hours === END_OF_DAY_HOURS && minutes === END_OF_DAY_MINUTES;
119
116
  }
120
117
 
121
118
  // Up to a day and an hour: `last-24h` plus slack for a DST shift.
@@ -207,11 +204,16 @@ const YESTERDAY_OFFSET = -1;
207
204
  const WEEKDAY_FORMAT: FormatOptions = { weekday: "short" };
208
205
 
209
206
  function localDayStart(date: Date): number {
210
- return new Date(
211
- date.getFullYear(),
212
- date.getMonth(),
213
- date.getDate(),
214
- ).getTime();
207
+ const clock = readerClock();
208
+ return clock
209
+ .instant({
210
+ ...clock.wall(date),
211
+ hours: 0,
212
+ minutes: 0,
213
+ seconds: 0,
214
+ milliseconds: 0,
215
+ })
216
+ .getTime();
215
217
  }
216
218
 
217
219
  function daysBefore(date: Date, now: Date): number {
@@ -235,7 +237,8 @@ function relativeDayPrefix(date: Date, now: Date, locale: string): string {
235
237
  if (days < DAYS_PER_WEEK) {
236
238
  return `${formatWith(date, locale, WEEKDAY_FORMAT)} `;
237
239
  }
238
- const sameYear = date.getFullYear() === now.getFullYear();
240
+ const clock = readerClock();
241
+ const sameYear = clock.wall(date).year === clock.wall(now).year;
239
242
  const day: FormatOptions = sameYear
240
243
  ? DAY_FORMAT
241
244
  : { ...DAY_FORMAT, year: "numeric" };
@@ -8,6 +8,10 @@ import {
8
8
  type PeriodRange,
9
9
  } from "#dms-core/app/composables/period/types";
10
10
  import type { PeriodQueryKeys } from "#dms-core/app/composables/period/periodLink";
11
+ import {
12
+ readerClock,
13
+ type WallTime,
14
+ } from "#dms-core/app/composables/period/periodClock";
11
15
 
12
16
  export {
13
17
  type PeriodQueryKeys,
@@ -67,27 +71,41 @@ function readText(query: PeriodQuery, key: string): string | undefined {
67
71
 
68
72
  type DayBoundary = "start" | "end";
69
73
 
70
- function dayBoundary(day: Date, boundary: DayBoundary): Date {
71
- if (boundary === "end") {
72
- day.setHours(
73
- END_OF_DAY_HOURS,
74
- END_OF_DAY_MINUTES,
75
- END_OF_DAY_SECONDS,
76
- END_OF_DAY_MS,
77
- );
78
- }
79
- return day;
74
+ const DAY_BOUNDARY_TIMES: Record<
75
+ DayBoundary,
76
+ Pick<WallTime, "hours" | "minutes" | "seconds" | "milliseconds">
77
+ > = {
78
+ start: { hours: 0, minutes: 0, seconds: 0, milliseconds: 0 },
79
+ end: {
80
+ hours: END_OF_DAY_HOURS,
81
+ minutes: END_OF_DAY_MINUTES,
82
+ seconds: END_OF_DAY_SECONDS,
83
+ milliseconds: END_OF_DAY_MS,
84
+ },
85
+ };
86
+
87
+ // Days are the reader's calendar days (`readerClock`): the browser's zone,
88
+ // on the server as in the browser.
89
+ function dayBoundary(
90
+ wall: Pick<WallTime, "year" | "month" | "day">,
91
+ boundary: DayBoundary,
92
+ ): Date {
93
+ return readerClock().instant({ ...wall, ...DAY_BOUNDARY_TIMES[boundary] });
80
94
  }
81
95
 
82
96
  /** A local calendar day, refused when it does not exist (`2026-02-30`). */
83
97
  function parseIsoDay(match: RegExpExecArray, boundary: DayBoundary) {
84
98
  const [year, month, day] = match.slice(1).map(Number);
85
- const date = new Date(year!, month! - MONTH_OFFSET, day);
99
+ const date = dayBoundary(
100
+ { year: year!, month: month! - MONTH_OFFSET, day: day! },
101
+ boundary,
102
+ );
103
+ const wall = readerClock().wall(date);
86
104
  const exists =
87
- date.getFullYear() === year &&
88
- date.getMonth() === month! - MONTH_OFFSET &&
89
- date.getDate() === day;
90
- return exists ? dayBoundary(date, boundary) : null;
105
+ wall.year === year &&
106
+ wall.month === month! - MONTH_OFFSET &&
107
+ wall.day === day;
108
+ return exists ? date : null;
91
109
  }
92
110
 
93
111
  /**
@@ -112,8 +130,9 @@ function pad(value: number, length = DATE_PART_LENGTH): string {
112
130
  }
113
131
 
114
132
  function isDayBoundary(date: Date, boundary: DayBoundary): boolean {
115
- const day = new Date(date.getFullYear(), date.getMonth(), date.getDate());
116
- return dayBoundary(day, boundary).getTime() === date.getTime();
133
+ return (
134
+ dayBoundary(readerClock().wall(date), boundary).getTime() === date.getTime()
135
+ );
117
136
  }
118
137
 
119
138
  /**
@@ -122,10 +141,11 @@ function isDayBoundary(date: Date, boundary: DayBoundary): boolean {
122
141
  */
123
142
  export function formatPeriodDate(date: Date, boundary: DayBoundary): string {
124
143
  if (!isDayBoundary(date, boundary)) return date.toISOString();
144
+ const { year, month, day } = readerClock().wall(date);
125
145
  return [
126
- pad(date.getFullYear(), YEAR_PART_LENGTH),
127
- pad(date.getMonth() + MONTH_OFFSET),
128
- pad(date.getDate()),
146
+ pad(year, YEAR_PART_LENGTH),
147
+ pad(month + MONTH_OFFSET),
148
+ pad(day),
129
149
  ].join("-");
130
150
  }
131
151
 
@@ -4,7 +4,6 @@ import type {
4
4
  TableViewFooter,
5
5
  TableViewFooterSummaryConfig,
6
6
  } from "../../../composables/table-view/types";
7
- import { useServerRenderedAsyncData } from "./useServerRenderedAsyncData";
8
7
  import type {
9
8
  TableFooterLegendItem,
10
9
  TableFooterTexts,
@@ -35,10 +34,9 @@ const UNSORTED_KEYS = new Set(["sortKey", "sortDirection"]);
35
34
  * route computes over every listed row — a sum read like its column's cells
36
35
  * unless a `format` is given — and the legend of a select column.
37
36
  */
38
- export async function useTableFooter(options: TableFooterOptions) {
37
+ export function useTableFooter(options: TableFooterOptions) {
39
38
  const { footer, columns, location } = options;
40
39
  const summaries = footer?.summary ?? [];
41
- const { $authFetch } = useAuthFetch();
42
40
  const { locale } = useI18n();
43
41
  const { processI18n } = useTranslation();
44
42
  const { renderValue } = useColumnValueRenderer();
@@ -52,17 +50,17 @@ export async function useTableFooter(options: TableFooterOptions) {
52
50
  ids: summaries.map((summary) => summary.id),
53
51
  }));
54
52
 
55
- const { data: values, refresh } = await useServerRenderedAsyncData<
56
- Record<string, number>
57
- >(
53
+ const { data: values, refresh } = useApiData<Record<string, number>>(
58
54
  `${options.dataKey}-summary`,
59
- () =>
55
+ ({ $authFetch, signal }) =>
60
56
  summaries.length === 0
61
57
  ? Promise.resolve({})
62
58
  : $authFetch<Record<string, number>>(`${location}/summary`, {
63
59
  query: summaryQuery.value,
60
+ signal,
64
61
  }),
65
- { watch: [summaryQuery] },
62
+ // Read again with the table's rows, on the table's own triggers.
63
+ { lazy: true, watch: [summaryQuery], refreshOnPageRefresh: false },
66
64
  );
67
65
 
68
66
  const formatSummary = (
@@ -1,6 +1,5 @@
1
- import type { ComputedRef } from "vue";
1
+ import { computed, type ComputedRef } from "vue";
2
2
  import type { TableViewListResponse } from "../../../composables/table-view/types";
3
- import { useServerRenderedAsyncData } from "./useServerRenderedAsyncData";
4
3
  import type { useTableRows } from "./useTableRows";
5
4
  import { buildTableDataKey } from "./utils/tableQuery";
6
5
 
@@ -43,6 +42,14 @@ export interface TableCountsDataOptions<T extends Record<string, unknown>> {
43
42
 
44
43
  const NO_HELD_QUERY = (): Query => ({});
45
44
 
45
+ /**
46
+ * A watch source following `source` by value: a query rebuilt equal (its
47
+ * inputs recomputed to the same values, a period re-resolved to the same
48
+ * bounds) must not read the rows again.
49
+ */
50
+ const byValue = (source: ComputedRef<unknown>): ComputedRef<string> =>
51
+ computed(() => JSON.stringify(source.value));
52
+
46
53
  const EMPTY_LIST_RESULT: TableViewListResponse<never> = {
47
54
  results: [],
48
55
  total: 0,
@@ -51,7 +58,10 @@ const EMPTY_LIST_RESULT: TableViewListResponse<never> = {
51
58
  };
52
59
 
53
60
  /**
54
- * The page of rows a table view shows. The key names the source URL: a
61
+ * The page of rows a table view shows: awaited by a server render, which
62
+ * transfers it, but not by a client navigation, which paints the list chrome
63
+ * and its skeleton at once (`lazy`). Read the loading state from `data`
64
+ * (`null` until the first result). The key names the source URL: a
55
65
  * navigation that remounts the page while the previous one is still on screen
56
66
  * binds to the cached entry of its key, so another URL needs another key to be
57
67
  * requested at all, and the watch covers a page kept mounted.
@@ -69,14 +79,27 @@ export function useTableListData<T extends Record<string, unknown>>(
69
79
  isSelfManaged: isSelfManaged.value,
70
80
  sourceUrl: rows.sourceUrl.value,
71
81
  });
72
- return useServerRenderedAsyncData(
82
+ return useDmsAsyncData(
73
83
  key,
74
84
  // `null` while a source waits on its period scope: the table's skeleton.
75
- (): Promise<TableViewListResponse<T> | null> =>
85
+ ({ signal }): Promise<TableViewListResponse<T> | null> =>
76
86
  isSelfManaged.value
77
87
  ? Promise.resolve(EMPTY_LIST_RESULT)
78
- : rows.list({ ...query.value, ...archiveQuery.value, ...heldQuery() }),
79
- { watch: [query, archiveQuery, isSelfManaged, rows.sourceUrl] },
88
+ : rows.list(
89
+ { ...query.value, ...archiveQuery.value, ...heldQuery() },
90
+ signal,
91
+ ),
92
+ {
93
+ watch: [
94
+ byValue(query),
95
+ byValue(archiveQuery),
96
+ isSelfManaged,
97
+ rows.sourceUrl,
98
+ ],
99
+ lazy: true,
100
+ // The table reads again on its own triggers, through its live hold.
101
+ refreshOnPageRefresh: false,
102
+ },
80
103
  );
81
104
  }
82
105
 
@@ -84,10 +107,10 @@ export function useTableListData<T extends Record<string, unknown>>(
84
107
  * The counters of a table view's tabs and views, keyed by id; `null` until
85
108
  * they arrive. Keyed by source URL like the list (see useTableListData).
86
109
  *
87
- * A source waiting on its period scope answers `null` too, like its list:
88
- * the scope never registers during the server render, so the server and the
89
- * browser (whose key carries the period, and has no payload yet) both draw
90
- * the counters' skeleton, and hydration matches.
110
+ * A source waiting on its period scope answers `null` too, like its list: a
111
+ * server render that cannot resolve the period as the browser will (see
112
+ * `usePeriodScope`) draws the counters' skeleton, as the browser does until
113
+ * its own request answers, and hydration matches.
91
114
  */
92
115
  export function useTableCountsData<T extends Record<string, unknown>>(
93
116
  options: TableCountsDataOptions<T>,
@@ -95,9 +118,9 @@ export function useTableCountsData<T extends Record<string, unknown>>(
95
118
  const { rows, queries, archiveQuery } = options;
96
119
  const heldQuery = options.heldQuery ?? NO_HELD_QUERY;
97
120
  const sourceSuffix = rows.sourceUrl.value ? `-${rows.sourceUrl.value}` : "";
98
- return useServerRenderedAsyncData<Record<string, number> | null>(
121
+ return useDmsAsyncData<Record<string, number> | null>(
99
122
  `table-view-${options.componentId}-${options.pageId}-tab-counts${sourceSuffix}`,
100
- async () => {
123
+ async ({ signal }) => {
101
124
  if (rows.isPeriodPending.value) return null;
102
125
  if (queries.value.length === 0) return {};
103
126
  return await rows.countBatch(
@@ -105,8 +128,13 @@ export function useTableCountsData<T extends Record<string, unknown>>(
105
128
  id,
106
129
  query: { ...query, ...archiveQuery.value, ...heldQuery() },
107
130
  })),
131
+ signal,
108
132
  );
109
133
  },
110
- { watch: [queries, archiveQuery, rows.sourceUrl] },
134
+ {
135
+ watch: [byValue(queries), byValue(archiveQuery), rows.sourceUrl],
136
+ lazy: true,
137
+ refreshOnPageRefresh: false,
138
+ },
111
139
  );
112
140
  }
@@ -185,6 +185,7 @@ export function useTableRows<T extends Record<string, unknown>>(
185
185
  const listFromSource = async (
186
186
  sourceConfig: TableViewSourceConfig,
187
187
  query: Query,
188
+ signal?: AbortSignal,
188
189
  ): Promise<TableViewListResponse<T> | null> => {
189
190
  if (isPeriodPending.value) return null;
190
191
  const url = sourceUrl.value;
@@ -192,6 +193,7 @@ export function useTableRows<T extends Record<string, unknown>>(
192
193
  const { capabilities } = sourceConfig;
193
194
  const response = await api<{ results: T[]; total: number }>(url, {
194
195
  query: sourceRouteQuery(query, capabilities),
196
+ signal,
195
197
  });
196
198
  if (capabilities.paginate) {
197
199
  return {
@@ -214,30 +216,39 @@ export function useTableRows<T extends Record<string, unknown>>(
214
216
  ): TableViewListResponse<T> | null =>
215
217
  page && { ...page, results: pinRows(page.results, options.pinnedRows?.()) };
216
218
 
217
- const list = async (query: Query): Promise<TableViewListResponse<T> | null> =>
219
+ // `signal` aborts the request once its answer is no longer wanted.
220
+ const list = async (
221
+ query: Query,
222
+ signal?: AbortSignal,
223
+ ): Promise<TableViewListResponse<T> | null> =>
218
224
  withPinnedRows(
219
225
  source
220
- ? await listFromSource(source, query)
221
- : await api<TableViewListResponse<T>>(`${location}/list`, { query }),
226
+ ? await listFromSource(source, query, signal)
227
+ : await api<TableViewListResponse<T>>(`${location}/list`, {
228
+ query,
229
+ signal,
230
+ }),
222
231
  );
223
232
 
224
233
  // A source answers its totals one list at a time.
225
234
  const countBatch = async (
226
235
  queries: TableCountQuery[],
236
+ signal?: AbortSignal,
227
237
  ): Promise<Record<string, number>> => {
228
238
  if (!source) {
229
239
  return api<Record<string, number>>(`${location}/count/batch`, {
230
240
  method: "POST",
231
241
  body: { queries },
242
+ signal,
232
243
  });
233
244
  }
234
245
  const totals = await Promise.all(
235
246
  queries.map(async ({ id, query }) => {
236
- const page = await listFromSource(source, {
237
- ...query,
238
- offset: 0,
239
- limit: 1,
240
- });
247
+ const page = await listFromSource(
248
+ source,
249
+ { ...query, offset: 0, limit: 1 },
250
+ signal,
251
+ );
241
252
  return page ? [[id, page.total] as const] : [];
242
253
  }),
243
254
  );
@@ -64,7 +64,7 @@ const VIEW_NAME_FIELD = "label";
64
64
  */
65
65
  export function useTableViews(options: TableViewsOptions) {
66
66
  const { views, defaults, state, preferences, urlScope } = options;
67
- const { user } = useUserSession();
67
+ const { user } = useDmsUserSession();
68
68
  const { t } = useI18n();
69
69
  const { processI18n } = useTranslation();
70
70
  const toast = useToast();
@@ -42,7 +42,8 @@ interface Props extends DefaultComponentProps {
42
42
  }
43
43
 
44
44
  // The comparison series takes palette step 2 (v2 violet), re-resolved on
45
- // theme changes via useThemeRevision so it tracks light/dark mode.
45
+ // theme changes via useThemeRevision so it tracks light/dark mode. Its legend
46
+ // key reads the same token in CSS.
46
47
  const COMPARISON_COLOR_TOKEN = "--dms-chart-2";
47
48
  const themeRevision = useThemeRevision();
48
49
  const comparisonColor = computed(() => {
@@ -236,9 +237,11 @@ const hasMeta = computed(
236
237
  {{ primaryLegendLabel }}
237
238
  </span>
238
239
  <span class="flex items-center gap-1.5">
240
+ <!-- The token itself, not its resolved value: a server render
241
+ cannot read the theme, and must draw what the browser draws. -->
239
242
  <span
240
243
  :class="LEGEND_KEY_CLASS"
241
- :style="{ background: comparisonColor }"
244
+ class="bg-(--dms-chart-2)"
242
245
  aria-hidden="true"
243
246
  />
244
247
  {{ comparisonLegendLabel }}