@antelopejs/dms 0.8.3 → 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 (351) hide show
  1. package/CHANGELOG.md +20 -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/pages/settings/member-roles-field.test.js +1 -1
  118. package/dist/test/unit/pages/settings/member-roles-field.test.js.map +1 -1
  119. package/dist/test/unit/pages/settings/role-delete-confirm.test.js.map +1 -1
  120. package/dist/test/unit/realtime/menu-topics.test.d.ts +1 -0
  121. package/dist/test/unit/realtime/menu-topics.test.js +26 -0
  122. package/dist/test/unit/realtime/menu-topics.test.js.map +1 -0
  123. package/frontend-vue/deferred-plugins.ts +3 -4
  124. package/frontend-vue/dms.email.ts +5 -1
  125. package/frontend-vue/dms.frontend.ts +15 -5
  126. package/frontend-vue/dms.server.ts +74 -0
  127. package/frontend-vue/layers/dms-auth/app/composables/useLogout.ts +1 -1
  128. package/frontend-vue/layers/dms-auth/app/composables/usePostLoginRedirect.ts +16 -3
  129. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/2fa.vue +3 -1
  130. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/backup.vue +3 -1
  131. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/forgot-validation.vue +1 -1
  132. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/oauth-complete.vue +26 -3
  133. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/recover.vue +1 -1
  134. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +22 -25
  135. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/validate.vue +1 -1
  136. package/frontend-vue/layers/dms-auth/app/middleware/auth.ts +5 -7
  137. package/frontend-vue/layers/dms-auth/app/plugins/permissions-preload.ts +17 -0
  138. package/frontend-vue/layers/dms-auth/server/accounts.ts +135 -0
  139. package/frontend-vue/layers/dms-auth/server/actions.ts +251 -0
  140. package/frontend-vue/layers/dms-auth/server/oauth.ts +260 -0
  141. package/frontend-vue/layers/dms-auth/server/session.ts +158 -0
  142. package/frontend-vue/layers/dms-core/app/build/utils/asyncDataScope.ts +47 -0
  143. package/frontend-vue/layers/dms-core/app/build/utils/permission-preview.ts +6 -2
  144. package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +148 -0
  145. package/frontend-vue/layers/dms-core/app/composables/auth/useAuthFetch.ts +7 -20
  146. package/frontend-vue/layers/dms-core/app/composables/auth/useCurrentUser.ts +2 -2
  147. package/frontend-vue/layers/dms-core/app/composables/auth/useMultiAccount.ts +18 -5
  148. package/frontend-vue/layers/dms-core/app/composables/auth/usePermissions.ts +35 -6
  149. package/frontend-vue/layers/dms-core/app/composables/auth/useSessionRecovery.ts +1 -1
  150. package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +20 -14
  151. package/frontend-vue/layers/dms-core/app/composables/period/periodClock.ts +121 -0
  152. package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +42 -0
  153. package/frontend-vue/layers/dms-core/app/composables/period/periodTimeZone.ts +31 -0
  154. package/frontend-vue/layers/dms-core/app/composables/period/presetResolvers.ts +48 -19
  155. package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +33 -4
  156. package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +78 -25
  157. package/frontend-vue/layers/dms-core/app/composables/realtime/usePageRealtime.ts +3 -0
  158. package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +36 -6
  159. package/frontend-vue/layers/dms-core/app/composables/user/useUserRegionalPreferences.ts +1 -1
  160. package/frontend-vue/layers/dms-core/app/plugins/async-data-scope.ts +26 -0
  161. package/frontend-vue/layers/dms-core/app/utils/pageRoutes.ts +22 -0
  162. package/frontend-vue/layers/dms-core/app/utils/regional.ts +36 -3
  163. package/frontend-vue/layers/dms-core/app/utils/trailingDeduper.ts +10 -1
  164. package/frontend-vue/layers/dms-core/shared/types/app-config.ts +48 -4
  165. package/frontend-vue/layers/dms-layout/app/app.config.ts +42 -23
  166. package/frontend-vue/layers/dms-layout/app/assets/css/main.css +72 -20
  167. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardFrame.vue +9 -0
  168. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +0 -3
  169. package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +9 -3
  170. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/members/MemberRolePicker.vue +13 -12
  171. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +17 -7
  172. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationPreferencesForm.vue +0 -1
  173. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfilePersonalInfo.vue +36 -33
  174. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfileSecuritySummary.vue +1 -2
  175. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-types.ts +5 -4
  176. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecuritySessions.vue +35 -28
  177. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityStatus.vue +0 -1
  178. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityTwoFactor.vue +1 -1
  179. package/frontend-vue/layers/dms-layout/app/build/composables/dev-reload/useDevReloadHolder.ts +7 -6
  180. package/frontend-vue/layers/dms-layout/app/build/composables/layout/sidePanelState.ts +1 -1
  181. package/frontend-vue/layers/dms-layout/app/build/composables/navigation/useVisitNavBadges.ts +26 -0
  182. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationCatalog.ts +25 -23
  183. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationPreferences.ts +44 -23
  184. package/frontend-vue/layers/dms-layout/app/build/composables/security/useSessionHandoff.ts +1 -1
  185. package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/preferencesSummary.ts +1 -1
  186. package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useAccountData.ts +3 -2
  187. package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfileAvatar.ts +1 -18
  188. package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfilePreferences.ts +19 -23
  189. package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityFormat.ts +6 -3
  190. package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityOverview.ts +13 -18
  191. package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +8 -6
  192. package/frontend-vue/layers/dms-layout/app/build/utils/publicErrorMessage.ts +19 -0
  193. package/frontend-vue/layers/dms-layout/app/components/navigation/CollapsedMenuItem.vue +0 -11
  194. package/frontend-vue/layers/dms-layout/app/components/navigation/NavigationMenu.vue +3 -13
  195. package/frontend-vue/layers/dms-layout/app/composables/general/useSystemState.ts +7 -11
  196. package/frontend-vue/layers/dms-layout/app/composables/notification/useNotifications.ts +103 -22
  197. package/frontend-vue/layers/dms-layout/app/composables/page/useModulesListing.ts +25 -102
  198. package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +0 -10
  199. package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +177 -96
  200. package/frontend-vue/layers/dms-layout/app/composables/profile/useUserLanguage.ts +3 -2
  201. package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +6 -28
  202. package/frontend-vue/layers/dms-layout/app/emails/components/EmailLayout.vue +31 -11
  203. package/frontend-vue/layers/dms-layout/app/error.vue +5 -2
  204. package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +1 -1
  205. package/frontend-vue/layers/dms-layout/app/middleware/page-leave-guard.global.ts +2 -2
  206. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +12 -9
  207. package/frontend-vue/layers/dms-layout/app/plugins/dms-menu-sync.client.ts +31 -3
  208. package/frontend-vue/layers/dms-layout/app/plugins/favicon.ts +58 -0
  209. package/frontend-vue/layers/dms-layout/app/plugins/notification-stream.client.ts +15 -7
  210. package/frontend-vue/layers/dms-layout/app/plugins/profile-sync.client.ts +5 -1
  211. package/frontend-vue/layers/dms-layout/app/plugins/regional-preferences.ts +10 -4
  212. package/frontend-vue/layers/dms-layout/app/plugins/time-zone-cookie.client.ts +12 -0
  213. package/frontend-vue/layers/dms-layout/app/types/page.ts +13 -0
  214. package/frontend-vue/layers/dms-layout/server/html-render.ts +58 -0
  215. package/frontend-vue/layers/dms-layout/server/render-token.ts +95 -0
  216. package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/useOnboarding.ts +25 -21
  217. package/frontend-vue/layers/dms-onboarding/app/middleware/onboarding.global.ts +1 -1
  218. package/frontend-vue/layers/dms-onboarding/app/plugins/onboarding-preload.ts +9 -0
  219. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +4 -1
  220. package/frontend-vue/layers/dms-ui/app/build/composables/files/useFileMetadata.ts +162 -0
  221. package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +23 -4
  222. package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageDataRefresh.ts +101 -0
  223. package/frontend-vue/layers/dms-ui/app/build/composables/navigation/useNavBadges.ts +33 -6
  224. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +19 -16
  225. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodQuery.ts +40 -20
  226. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +6 -8
  227. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableListData.ts +42 -14
  228. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +19 -8
  229. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +1 -1
  230. package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +5 -2
  231. package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +9 -9
  232. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +20 -18
  233. package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +2 -1
  234. package/frontend-vue/layers/dms-ui/app/components/form/components/File.vue +26 -132
  235. package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +40 -105
  236. package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +2 -0
  237. package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +4 -4
  238. package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +9 -13
  239. package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +2 -1
  240. package/frontend-vue/layers/dms-ui/app/components/table-view/FilePreview.vue +12 -24
  241. package/frontend-vue/layers/dms-ui/app/components/table-view/ImagePreview.vue +17 -29
  242. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +7 -3
  243. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +20 -8
  244. package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +8 -11
  245. package/frontend-vue/layers/dms-ui/app/composables/chart/useChartFetch.ts +146 -182
  246. package/frontend-vue/layers/dms-ui/app/composables/live/usePageLiveRefresh.ts +8 -2
  247. package/frontend-vue/layers/dms-ui/app/composables/useFileReadUrls.ts +11 -99
  248. package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +32 -3
  249. package/frontend-vue/package.json +2 -2
  250. package/frontend-vue/pnpm-lock.yaml +5 -5
  251. package/frontend-vue/server/private-options.ts +27 -0
  252. package/frontend-vue/tests/async-data-scope.test.ts +121 -0
  253. package/frontend-vue/tests/auth-middleware-error.test.ts +3 -3
  254. package/frontend-vue/tests/banner-block-fetch.test.ts +3 -0
  255. package/frontend-vue/tests/block-fetch-options-forwarding.test.ts +3 -0
  256. package/frontend-vue/tests/block-items-period-realtime.test.ts +3 -0
  257. package/frontend-vue/tests/block-not-found.test.ts +16 -2
  258. package/frontend-vue/tests/block-page-refresh.test.ts +3 -0
  259. package/frontend-vue/tests/block-route-params.test.ts +3 -0
  260. package/frontend-vue/tests/branding-defaults.test.ts +34 -0
  261. package/frontend-vue/tests/builtin-emails.test.ts +23 -2
  262. package/frontend-vue/tests/chart-live-pause.test.ts +4 -1
  263. package/frontend-vue/tests/chart-value-propagation.test.ts +4 -1
  264. package/frontend-vue/tests/composed-text-blocks.test.ts +3 -0
  265. package/frontend-vue/tests/email-layout.test.ts +66 -4
  266. package/frontend-vue/tests/error-page-message.test.ts +25 -2
  267. package/frontend-vue/tests/favicon.test.ts +131 -0
  268. package/frontend-vue/tests/file-fields-ssr.test.ts +145 -0
  269. package/frontend-vue/tests/file-read-urls.test.ts +145 -34
  270. package/frontend-vue/tests/file-unavailable.test.ts +5 -1
  271. package/frontend-vue/tests/fixtures/dataEngine.ts +44 -0
  272. package/frontend-vue/tests/form-load-error.test.ts +55 -1
  273. package/frontend-vue/tests/helpers/api-data-runtime.ts +77 -0
  274. package/frontend-vue/tests/image-hydration-ids.test.ts +21 -16
  275. package/frontend-vue/tests/image-reference-preservation.test.ts +59 -53
  276. package/frontend-vue/tests/input-number-adjust.test.ts +14 -0
  277. package/frontend-vue/tests/key-value-list-copy.test.ts +3 -0
  278. package/frontend-vue/tests/member-role-picker.test.ts +4 -2
  279. package/frontend-vue/tests/menu-sync-reconnect.test.ts +47 -0
  280. package/frontend-vue/tests/metadata-retry.test.ts +5 -1
  281. package/frontend-vue/tests/module-routing.test.ts +2 -5
  282. package/frontend-vue/tests/modules-catalog.test.ts +17 -4
  283. package/frontend-vue/tests/modules-listing-transfer.test.ts +95 -0
  284. package/frontend-vue/tests/multi-account-validate.test.ts +23 -1
  285. package/frontend-vue/tests/nav-badge-tones.test.ts +66 -3
  286. package/frontend-vue/tests/notification-counts-from-writes.test.ts +223 -0
  287. package/frontend-vue/tests/notification-counts-transfer.test.ts +88 -0
  288. package/frontend-vue/tests/notification-inbox-display.test.ts +57 -5
  289. package/frontend-vue/tests/notification-popover-errors.test.ts +21 -1
  290. package/frontend-vue/tests/oauth-complete-handoff.test.ts +209 -0
  291. package/frontend-vue/tests/onboarding-middleware.test.ts +1 -1
  292. package/frontend-vue/tests/onboarding-transfer.test.ts +106 -0
  293. package/frontend-vue/tests/page-data-refresh.test.ts +141 -0
  294. package/frontend-vue/tests/page-fill-height.test.ts +16 -3
  295. package/frontend-vue/tests/page-no-visible-blocks.test.ts +12 -2
  296. package/frontend-vue/tests/page-realtime-remount.test.ts +1 -1
  297. package/frontend-vue/tests/page-record-label.test.ts +0 -7
  298. package/frontend-vue/tests/page-validation-redirect.test.ts +12 -2
  299. package/frontend-vue/tests/period-hydration-now.test.ts +185 -0
  300. package/frontend-vue/tests/period-live-pause.test.ts +3 -0
  301. package/frontend-vue/tests/period-reader-zone.test.ts +137 -0
  302. package/frontend-vue/tests/period-scope-first-fetch.test.ts +17 -1
  303. package/frontend-vue/tests/period-ssr.test.ts +195 -0
  304. package/frontend-vue/tests/permission-preview-partial.test.ts +0 -2
  305. package/frontend-vue/tests/permissions-transfer.test.ts +194 -0
  306. package/frontend-vue/tests/post-login-redirect.test.ts +10 -5
  307. package/frontend-vue/tests/profile-personal-info-ssr.test.ts +150 -0
  308. package/frontend-vue/tests/profile-preferences-summary.test.ts +15 -11
  309. package/frontend-vue/tests/realtime-reconnect.test.ts +22 -3
  310. package/frontend-vue/tests/roles-name-errors.test.ts +1 -1
  311. package/frontend-vue/tests/row-side-panel.test.ts +1 -1
  312. package/frontend-vue/tests/security-sessions-load-error.test.ts +3 -1
  313. package/frontend-vue/tests/server-routes-vs-pages.test.ts +26 -0
  314. package/frontend-vue/tests/server-routes.test.ts +582 -0
  315. package/frontend-vue/tests/session-handoff.test.ts +1 -1
  316. package/frontend-vue/tests/settings-reads-ssr.test.ts +232 -0
  317. package/frontend-vue/tests/side-panels.test.ts +8 -1
  318. package/frontend-vue/tests/sign-in-exit-history.test.ts +219 -0
  319. package/frontend-vue/tests/signup-invitation-link.test.ts +56 -8
  320. package/frontend-vue/tests/site-layout-categories.test.ts +42 -0
  321. package/frontend-vue/tests/site-layout-hydration.test.ts +3 -3
  322. package/frontend-vue/tests/site-layout-rendered.test.ts +103 -0
  323. package/frontend-vue/tests/site-layout-resync.test.ts +87 -0
  324. package/frontend-vue/tests/ssr-guard-preload.test.ts +188 -0
  325. package/frontend-vue/tests/table-cell-tones.test.ts +36 -1
  326. package/frontend-vue/tests/table-data-ssr.test.ts +195 -0
  327. package/frontend-vue/tests/table-list-data-requests.test.ts +154 -0
  328. package/frontend-vue/tests/table-retry-loading.test.ts +37 -0
  329. package/frontend-vue/tests/table-source-navigation.test.ts +0 -1
  330. package/frontend-vue/tests/table-source-period-scope.test.ts +0 -1
  331. package/frontend-vue/tests/table-view-views.test.ts +1 -1
  332. package/frontend-vue/tests/top-list-card-actions.test.ts +3 -0
  333. package/frontend-vue/tests/top-list-card-period-badge.test.ts +79 -0
  334. package/frontend-vue/tests/top-list-card-value-suffix.test.ts +3 -0
  335. package/frontend-vue/tests/trailing-deduper.test.ts +22 -0
  336. package/frontend-vue/tests/use-api-data-update.test.ts +119 -0
  337. package/frontend-vue/tests/use-api-data.test.ts +410 -0
  338. package/frontend-vue/tests/user-language.test.ts +8 -5
  339. package/frontend-vue/tests/widget-label-i18n.test.ts +4 -1
  340. package/frontend-vue/vitest.config.ts +6 -0
  341. package/package.json +2 -2
  342. package/skills/dms/SKILL.md +2 -1
  343. package/skills/dms-module/SKILL.md +1 -1
  344. package/skills/dms-pages/SKILL.md +4 -1
  345. package/frontend-vue/layers/dms-core/app/build/composables/page/usePageRecordLabel.ts +0 -9
  346. package/frontend-vue/layers/dms-layout/app/composables/page/usePrefetch.ts +0 -21
  347. package/frontend-vue/layers/dms-ui/app/build/composables/form/useControlError.ts +0 -10
  348. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useServerRenderedAsyncData.ts +0 -23
  349. package/frontend-vue/tests/control-error-path.test.ts +0 -11
  350. package/frontend-vue/tests/modules-listing-per-request.test.ts +0 -104
  351. package/frontend-vue/tests/page-layout-background-refresh.test.ts +0 -321
@@ -1,4 +1,5 @@
1
- import { computed, ref, type ComputedRef } from "vue";
1
+ import { computed, markRaw, ref, type ComputedRef, type Ref } from "vue";
2
+ import { resolvesPeriodsAsBrowser } from "./periodTimeZone";
2
3
  import type { PeriodState } from "./types";
3
4
 
4
5
  interface PeriodScopeRegistration {
@@ -6,65 +7,117 @@ interface PeriodScopeRegistration {
6
7
  refCount: number;
7
8
  }
8
9
 
9
- const periodScopeRegistry = new Map<string, PeriodScopeRegistration>();
10
- const registryVersion = ref(0);
10
+ interface PeriodScopeRegistry {
11
+ scopes: Map<string, PeriodScopeRegistration>;
12
+ version: Ref<number>;
13
+ }
14
+
15
+ const PERIOD_SCOPES_STATE_KEY = "dms-period-scopes";
11
16
 
12
- function bumpRegistry(): void {
13
- registryVersion.value += 1;
17
+ function createRegistry(): PeriodScopeRegistry {
18
+ return markRaw({ scopes: new Map(), version: ref(0) });
14
19
  }
15
20
 
16
- const noopUnregister = (): void => undefined;
21
+ // A browser tab runs one application, whose scopes every caller shares; a
22
+ // server renders many requests at once, each with its own scopes.
23
+ const browserRegistry = createRegistry();
24
+
25
+ function usePeriodScopeRegistry(): PeriodScopeRegistry {
26
+ if (typeof window !== "undefined") return browserRegistry;
27
+ return useDmsState(PERIOD_SCOPES_STATE_KEY, createRegistry).value;
28
+ }
17
29
 
18
30
  export function registerPeriodScope(
19
31
  id: string,
20
32
  state: ComputedRef<PeriodState>,
21
33
  ): () => void {
22
- if (typeof window === "undefined") return noopUnregister;
23
- const existing = periodScopeRegistry.get(id);
34
+ const registry = usePeriodScopeRegistry();
35
+ const existing = registry.scopes.get(id);
24
36
  if (existing) {
25
37
  existing.state = state;
26
38
  existing.refCount += 1;
27
39
  } else {
28
- periodScopeRegistry.set(id, { state, refCount: 1 });
40
+ registry.scopes.set(id, { state, refCount: 1 });
29
41
  }
30
- bumpRegistry();
42
+ registry.version.value += 1;
31
43
  return () => {
32
- const entry = periodScopeRegistry.get(id);
44
+ const entry = registry.scopes.get(id);
33
45
  if (!entry) return;
34
46
  entry.refCount -= 1;
35
- if (entry.refCount <= 0) periodScopeRegistry.delete(id);
36
- bumpRegistry();
47
+ if (entry.refCount <= 0) registry.scopes.delete(id);
48
+ registry.version.value += 1;
37
49
  };
38
50
  }
39
51
 
40
52
  const warnedMissingScopes = new Set<string>();
41
53
  const MISSING_SCOPE_WARN_DELAY_MS = 250;
42
54
 
43
- function deferMissingScopeWarn(id: string): void {
55
+ function deferMissingScopeWarn(
56
+ registry: PeriodScopeRegistry,
57
+ id: string,
58
+ ): void {
44
59
  if (warnedMissingScopes.has(id)) return;
45
60
  warnedMissingScopes.add(id);
46
61
  if (typeof window === "undefined") return;
47
62
  setTimeout(() => {
48
- if (periodScopeRegistry.has(id)) return;
63
+ if (registry.scopes.has(id)) return;
49
64
  console.warn(
50
65
  `[dms] periodScope "${id}" is not registered. The consumer will run without period filtering. Add a PeriodSelector with id="${id}" to bind it.`,
51
66
  );
52
67
  }, MISSING_SCOPE_WARN_DELAY_MS);
53
68
  }
54
69
 
70
+ function readPeriodScope(
71
+ registry: PeriodScopeRegistry,
72
+ id: string,
73
+ ): PeriodState | null {
74
+ void registry.version.value;
75
+ const entry = registry.scopes.get(id);
76
+ if (!entry) {
77
+ deferMissingScopeWarn(registry, id);
78
+ return null;
79
+ }
80
+ return entry.state.value;
81
+ }
82
+
83
+ /**
84
+ * The state of a period scope, `null` while no selector registers it.
85
+ *
86
+ * A server render reads it once, as the caller sets up: a selector that sets
87
+ * up later (further down the page) leaves the caller to the browser, whose
88
+ * first render must match the HTML. It also answers `null` for a period the
89
+ * browser could resolve to other bounds: before the browser's cookie names
90
+ * its time zone. A relative preset resolves from the instant the server
91
+ * hands the browser (`periodRenderNow`), so both read the same bounds.
92
+ */
55
93
  export function usePeriodScope(
56
94
  id: string | undefined,
57
95
  ): ComputedRef<PeriodState | null> {
58
- return computed(() => {
59
- if (!id) return null;
60
- void registryVersion.value;
61
- const entry = periodScopeRegistry.get(id);
62
- if (!entry) {
63
- deferMissingScopeWarn(id);
64
- return null;
65
- }
66
- return entry.state.value;
67
- });
96
+ if (!id) return computed(() => null);
97
+ const registry = usePeriodScopeRegistry();
98
+ if (typeof window !== "undefined")
99
+ return computed(() => readPeriodScope(registry, id));
100
+ const state = readPeriodScope(registry, id);
101
+ const resolvesAsBrowser = state !== null && resolvesPeriodsAsBrowser();
102
+ return computed(() => (resolvesAsBrowser ? state : null));
103
+ }
104
+
105
+ /**
106
+ * The preset of a period scope, `null` while no selector registers it: what
107
+ * a card's period badge names. A server render reads it once, as the caller
108
+ * sets up, like `usePeriodScope`, but whatever bounds the browser would
109
+ * resolve the period to: a preset is the same name on both sides, so the
110
+ * server prints the badge the browser hydrates.
111
+ */
112
+ export function usePeriodScopePreset(
113
+ id: string | undefined,
114
+ ): ComputedRef<PeriodState["preset"] | null> {
115
+ if (!id) return computed(() => null);
116
+ const registry = usePeriodScopeRegistry();
117
+ if (typeof window !== "undefined")
118
+ return computed(() => readPeriodScope(registry, id)?.preset ?? null);
119
+ const preset = readPeriodScope(registry, id)?.preset ?? null;
120
+ return computed(() => preset);
68
121
  }
69
122
 
70
123
  /**
@@ -10,6 +10,8 @@ export interface PageRealtimeApi {
10
10
  subscribe(topic: string, handler: RealtimeTopicHandler): () => void;
11
11
  sessionId: Ref<string | undefined>;
12
12
  pageId: Ref<string | undefined>;
13
+ /** The page's realtime topics, once the session is subscribed to it. */
14
+ pageTopics: Ref<string[]>;
13
15
  }
14
16
 
15
17
  interface PageClaim {
@@ -70,6 +72,7 @@ export function usePageRealtime(
70
72
  subscribe: user.subscribe,
71
73
  sessionId: user.sessionId,
72
74
  pageId: resolvedPageId,
75
+ pageTopics: user.pageTopics,
73
76
  };
74
77
  }
75
78
 
@@ -51,12 +51,18 @@ export interface UserRealtimeApi {
51
51
  * announced in the greeting.
52
52
  */
53
53
  menuTopics: Ref<string[]>;
54
+ /**
55
+ * The realtime topics of the page this session is on, as its subscription
56
+ * answered them: an event on one means the page's data moved.
57
+ */
58
+ pageTopics: Ref<string[]>;
54
59
  }
55
60
 
56
61
  interface UserRealtimeState {
57
62
  sessionId: Ref<string | undefined>;
58
63
  pageId: Ref<string | undefined>;
59
64
  menuTopics: Ref<string[]>;
65
+ pageTopics: Ref<string[]>;
60
66
  appliedPageId: Ref<string | null>;
61
67
  handlers: Map<string, Set<RealtimeTopicHandler>>;
62
68
  snapshots: Map<string, RealtimeSnapshotEvent>;
@@ -230,22 +236,42 @@ const connectWithBackoff = (
230
236
  };
231
237
  };
232
238
 
239
+ interface PageSubscriptionAnswer {
240
+ topics?: unknown;
241
+ }
242
+
243
+ /** The topics a page subscription answered, none when it answered none. */
244
+ const readPageTopics = async (response: Response): Promise<string[]> => {
245
+ let answer: PageSubscriptionAnswer | null;
246
+ try {
247
+ answer = (await response.json()) as PageSubscriptionAnswer | null;
248
+ } catch {
249
+ return [];
250
+ }
251
+ const topics = answer?.topics;
252
+ return Array.isArray(topics)
253
+ ? topics.filter((topic): topic is string => typeof topic === "string")
254
+ : [];
255
+ };
256
+
257
+ /** Subscribes the session to `pageId`, or to no page; returns its topics. */
233
258
  const callPageEndpoint = async (
234
259
  state: UserRealtimeState,
235
260
  pageId: string | null,
236
261
  baseUrl: string,
237
- ): Promise<void> => {
262
+ ): Promise<string[]> => {
238
263
  const sessionId = state.sessionId.value;
239
- if (!sessionId) return;
264
+ if (!sessionId) return [];
240
265
  const headers: Record<string, string> = { [SESSION_HEADER]: sessionId };
241
266
  const url = pageId
242
267
  ? `${baseUrl}${REALTIME_SUBSCRIBE_PATH_PREFIX}${pageId}`
243
268
  : `${baseUrl}${REALTIME_UNSUBSCRIBE_PATH}`;
244
- await fetch(url, {
269
+ const response = await fetch(url, {
245
270
  method: "POST",
246
271
  headers,
247
272
  credentials: "include",
248
273
  });
274
+ return pageId && response.ok ? readPageTopics(response) : [];
249
275
  };
250
276
 
251
277
  const applyDesiredPage = async (
@@ -256,11 +282,12 @@ const applyDesiredPage = async (
256
282
  const sessionId = state.sessionId.value;
257
283
  if (state.appliedPageId.value === desired) return;
258
284
  if (!sessionId) return;
259
- await callPageEndpoint(state, desired, baseUrl);
285
+ const topics = await callPageEndpoint(state, desired, baseUrl);
260
286
  // A reconnect during the call replaced the session: the page is applied to
261
287
  // the dead one, and the new one still needs it.
262
288
  if (state.sessionId.value !== sessionId) return;
263
289
  state.appliedPageId.value = desired;
290
+ state.pageTopics.value = topics;
264
291
  };
265
292
 
266
293
  /**
@@ -288,6 +315,7 @@ const reconcilePage = (
288
315
  const resetSession = (state: UserRealtimeState): void => {
289
316
  state.sessionId.value = undefined;
290
317
  state.appliedPageId.value = null;
318
+ state.pageTopics.value = [];
291
319
  state.snapshots.clear();
292
320
  };
293
321
 
@@ -299,10 +327,12 @@ const buildApi = (): UserRealtimeApi => {
299
327
  const menuTopics = useDmsState<string[]>(MENU_TOPICS_STATE_KEY, () => []);
300
328
  const appliedPageId = ref<string | null>(null);
301
329
  const pageId = ref<string | undefined>(undefined);
330
+ const pageTopics = ref<string[]>([]);
302
331
  const state: UserRealtimeState = {
303
332
  sessionId,
304
333
  pageId,
305
334
  menuTopics,
335
+ pageTopics,
306
336
  appliedPageId,
307
337
  handlers: new Map(),
308
338
  snapshots: new Map(),
@@ -310,7 +340,7 @@ const buildApi = (): UserRealtimeApi => {
310
340
  };
311
341
 
312
342
  const config = useDmsRuntimeConfig();
313
- const { loggedIn } = useUserSession();
343
+ const { loggedIn } = useDmsUserSession();
314
344
  const { refreshSession, redirectToAuth } = useSessionRecovery();
315
345
  const baseUrl = config.public.dms.baseURL as string;
316
346
 
@@ -366,7 +396,7 @@ const buildApi = (): UserRealtimeApi => {
366
396
  await reconcilePage(state, baseUrl);
367
397
  };
368
398
 
369
- return { subscribe, setPage, sessionId, pageId, menuTopics };
399
+ return { subscribe, setPage, sessionId, pageId, menuTopics, pageTopics };
370
400
  };
371
401
 
372
402
  export function useUserRealtime(): UserRealtimeApi {
@@ -27,7 +27,7 @@ function localeTimeFormat(locale: string): TimeFormatPreference {
27
27
  * the values themselves, such as a calendar's `weekStartsOn`.
28
28
  */
29
29
  export function useUserRegionalPreferences() {
30
- const { user } = useUserSession<RegionalPreferenceFields>();
30
+ const { user } = useDmsUserSession<RegionalPreferenceFields>();
31
31
  const { locale } = useI18n();
32
32
 
33
33
  const preferences = computed<RegionalPreferences>(() =>
@@ -0,0 +1,26 @@
1
+ import { watch } from "vue";
2
+ import {
3
+ defineDmsPlugin,
4
+ setDmsAsyncDataScope,
5
+ useDmsUserSession,
6
+ } from "#dms/frontend-module";
7
+ import {
8
+ asyncDataScopeOf,
9
+ type ScopedSession,
10
+ } from "../build/utils/asyncDataScope";
11
+
12
+ /**
13
+ * Keeps every `useApiData` / `useDmsAsyncData` key in the scope of the
14
+ * session's account and workspace, on both sides before the page renders:
15
+ * the browser finds the data the server transferred under the same scope,
16
+ * and a switch of account or workspace drops the data of the previous one
17
+ * instead of showing it under the new session.
18
+ */
19
+ export default defineDmsPlugin(() => {
20
+ const { session } = useDmsUserSession<unknown, ScopedSession>();
21
+ const scope = () => asyncDataScopeOf(session.value);
22
+ setDmsAsyncDataScope(scope());
23
+ // One app per request on a server: its session never changes there.
24
+ if (typeof window === "undefined") return;
25
+ watch(scope, (next) => setDmsAsyncDataScope(next));
26
+ });
@@ -0,0 +1,22 @@
1
+ // Mirror of PAGE_ROUTE_PREFIX and pageRoutePath from
2
+ // @antelopejs/interface-dms/page.
3
+
4
+ /**
5
+ * Where the backend serves the routes a page controller declares, and its
6
+ * layout: under the DMS's own prefix, which the frontend server proxies. The
7
+ * page's own path is the page, never a backend route.
8
+ */
9
+ export const PAGE_ROUTE_PREFIX = "/dms/pages";
10
+
11
+ /**
12
+ * The backend path of a page's route, as the browser calls it:
13
+ * `pageRoutePath("/settings/user/security", "/sessions")` is
14
+ * `/dms/pages/settings/user/security/sessions`.
15
+ *
16
+ * @param pageSlug The page's path, such as `/settings/user/security`
17
+ * @param route The route below the page; none for the page's own route
18
+ */
19
+ export function pageRoutePath(pageSlug: string, route = ""): string {
20
+ const path = `${PAGE_ROUTE_PREFIX}/${pageSlug}/${route}`.replace(/\/+/g, "/");
21
+ return path.endsWith("/") ? path.slice(0, -1) : path;
22
+ }
@@ -37,6 +37,7 @@ export interface RegionalOptionsSettings {
37
37
  }
38
38
 
39
39
  type PreferencesSource = () => RegionalPreferences;
40
+ type ReaderTimeZoneSource = () => string | null | undefined;
40
41
 
41
42
  const WEEK_STARTS: readonly number[] = [0, 1, 6];
42
43
  const TIME_FORMATS: readonly string[] = ["h23", "h12"];
@@ -73,6 +74,7 @@ const NUMERIC_DATE: Intl.DateTimeFormatOptions = {
73
74
 
74
75
  const knownTimeZones = new Map<string, boolean>();
75
76
  let preferencesSource: PreferencesSource | undefined;
77
+ let readerTimeZoneSource: ReaderTimeZoneSource | undefined;
76
78
 
77
79
  /** Whether the runtime knows `timeZone` (an unknown one would throw in Intl). */
78
80
  export function isKnownTimeZone(timeZone: string): boolean {
@@ -125,6 +127,35 @@ export function setRegionalPreferencesSource(source: PreferencesSource): void {
125
127
  preferencesSource = source;
126
128
  }
127
129
 
130
+ /**
131
+ * Tells a server render where to read the zone of the browser it renders for
132
+ * (its `dms-time-zone` cookie). Called on every read, so it resolves the
133
+ * request being rendered rather than a value captured once.
134
+ */
135
+ export function setReaderTimeZoneSource(source: ReaderTimeZoneSource): void {
136
+ readerTimeZoneSource = source;
137
+ }
138
+
139
+ /**
140
+ * The time zone of the browser the page is for, when it is not the zone the
141
+ * code runs in: `undefined` in the browser, where it is; on a server, the zone
142
+ * the browser's cookie names, if this runtime knows it. Dates computed or
143
+ * written for the browser use it, so the server renders what the browser
144
+ * renders.
145
+ */
146
+ export function readerTimeZone(): string | undefined {
147
+ if (typeof window !== "undefined") return undefined;
148
+ let zone: string | null | undefined;
149
+ try {
150
+ zone = readerTimeZoneSource?.();
151
+ } catch {
152
+ return undefined;
153
+ }
154
+ return zone && zone !== browserTimeZone() && isKnownTimeZone(zone)
155
+ ? zone
156
+ : undefined;
157
+ }
158
+
128
159
  /** The current user's preferences; none outside an application. */
129
160
  export function currentRegionalPreferences(): RegionalPreferences {
130
161
  try {
@@ -195,8 +226,10 @@ export function withRegionalOptions(
195
226
  let result = preferences.dateFormat
196
227
  ? DATE_FORMAT_REWRITES[preferences.dateFormat](options)
197
228
  : { ...options };
198
- if (preferences.timeZone && !settings.keepLocalZone && !result.timeZone) {
199
- result = { ...result, timeZone: preferences.timeZone };
229
+ const timeZone =
230
+ (!settings.keepLocalZone && preferences.timeZone) || readerTimeZone();
231
+ if (timeZone && !result.timeZone) {
232
+ result = { ...result, timeZone };
200
233
  }
201
234
  const statesClock =
202
235
  result.hour12 !== undefined || result.hourCycle !== undefined;
@@ -238,7 +271,7 @@ export function regionalDayNumber(
238
271
  preferences: RegionalPreferences = currentRegionalPreferences(),
239
272
  ): number {
240
273
  const parts = new Intl.DateTimeFormat("en-US", {
241
- timeZone: preferences.timeZone,
274
+ timeZone: preferences.timeZone ?? readerTimeZone(),
242
275
  year: "numeric",
243
276
  month: "numeric",
244
277
  day: "numeric",
@@ -1,5 +1,11 @@
1
1
  export interface TrailingDeduper {
2
2
  run(task: () => Promise<void>): Promise<void>;
3
+ /**
4
+ * Joins the run in flight, or starts one: for a caller that needs a result
5
+ * rather than a fresh one, such as a first load, which a run started
6
+ * before it answers as well as its own would.
7
+ */
8
+ join(task: () => Promise<void>): Promise<void>;
3
9
  }
4
10
 
5
11
  /**
@@ -38,5 +44,8 @@ export function createTrailingDeduper(): TrailingDeduper {
38
44
  return trailing;
39
45
  };
40
46
 
41
- return { run };
47
+ const join = (task: () => Promise<void>): Promise<void> =>
48
+ inFlight ?? run(task);
49
+
50
+ return { run, join };
42
51
  }
@@ -12,6 +12,51 @@ export interface DmsLogoSources {
12
12
  dark: string;
13
13
  }
14
14
 
15
+ /** The logos of an instance, one per place it is drawn. */
16
+ export interface DmsBrandingLogo {
17
+ /** The expanded sidebar and the auth pages. */
18
+ default: DmsLogoSources;
19
+ /** The icon-only sidebar. */
20
+ collapsed: DmsLogoSources;
21
+ /**
22
+ * The header of the emails, in place of `default.light`. A PNG or a JPEG:
23
+ * Gmail and Outlook refuse SVG, and an email leaves out a logo they cannot
24
+ * load. `dark` goes to the mail clients that follow the reader's dark mode.
25
+ */
26
+ email?: DmsLogoSources;
27
+ /**
28
+ * The browser tab icon. `light` and `dark` follow the operating system's
29
+ * color scheme, not the dashboard's color mode: the browser draws the tab
30
+ * outside the page.
31
+ */
32
+ favicon?: DmsLogoSources;
33
+ }
34
+
35
+ /**
36
+ * The Tailwind palette each Nuxt UI color role resolves to (`ui.colors`): a
37
+ * stock palette name, or one whose `--color-<name>-*` shades a stylesheet
38
+ * declares.
39
+ */
40
+ export interface DmsUiColors {
41
+ primary: string;
42
+ secondary: string;
43
+ neutral: string;
44
+ success: string;
45
+ warning: string;
46
+ error: string;
47
+ info: string;
48
+ }
49
+
50
+ /**
51
+ * The DMS's own logos and palettes, kept whatever a layer sets in their place,
52
+ * so a tool can preview a return to the DMS look (the builder's theme editor).
53
+ * A layer overrides `branding.logo` and `ui.colors`, never these.
54
+ */
55
+ export interface DmsBrandingDefaults {
56
+ logo: DmsBrandingLogo;
57
+ colors: DmsUiColors;
58
+ }
59
+
15
60
  /**
16
61
  * What the footer of the public and auth pages shows besides its links
17
62
  * (`registerFooterLink`).
@@ -33,11 +78,10 @@ export interface DmsBrandingConfig {
33
78
  * Absent when an instance ships no logo of its own; the layer's app config
34
79
  * supplies the AntelopeJS one by default.
35
80
  */
36
- logo?: {
37
- default: DmsLogoSources;
38
- collapsed: DmsLogoSources;
39
- };
81
+ logo?: DmsBrandingLogo;
40
82
  footer?: DmsFooterConfig;
83
+ /** Supplied by the DMS layout layer's app config. */
84
+ defaults?: DmsBrandingDefaults;
41
85
  }
42
86
 
43
87
  export interface DmsAppConfig {
@@ -1,3 +1,7 @@
1
+ import type {
2
+ DmsBrandingLogo,
3
+ DmsUiColors,
4
+ } from "#dms-core/shared/types/app-config";
1
5
  import { EYEBROW_CLASS } from "#dms-ui/app/build/utils/eyebrow";
2
6
  import { actionsTheme } from "./theme/actions";
3
7
  import { dataTheme } from "./theme/data";
@@ -8,21 +12,44 @@ import {
8
12
  } from "./theme/navigation";
9
13
  import { surfacesTheme } from "./theme/surfaces";
10
14
 
15
+ const ANTELOPE_ICON = "/images/antelope-logo/icon.svg";
16
+
17
+ // The palettes and logos are built afresh for each place that holds them: a
18
+ // merged app config keeps the very objects no layer overrides, so a live edit
19
+ // of `ui.colors` or `branding.logo` would otherwise rewrite
20
+ // `branding.defaults` too.
21
+
22
+ function dmsColors(): DmsUiColors {
23
+ return {
24
+ primary: "dms",
25
+ // The AI accent of the v2 design.
26
+ secondary: "violet",
27
+ neutral: "neutral",
28
+ // Design semantic hexes map exactly onto these Tailwind palettes:
29
+ // success #10b981 (emerald), warning #f59e0b (amber),
30
+ // error #ef4444 (red), info #06b6d4 (cyan).
31
+ success: "emerald",
32
+ warning: "amber",
33
+ error: "red",
34
+ info: "cyan",
35
+ };
36
+ }
37
+
38
+ // No email logo: the AntelopeJS logos are SVG, which mail clients refuse.
39
+ function dmsLogo(): DmsBrandingLogo {
40
+ return {
41
+ default: {
42
+ light: "/images/antelope-logo/light.svg",
43
+ dark: "/images/antelope-logo/dark.svg",
44
+ },
45
+ collapsed: { light: ANTELOPE_ICON, dark: ANTELOPE_ICON },
46
+ favicon: { light: ANTELOPE_ICON, dark: ANTELOPE_ICON },
47
+ };
48
+ }
49
+
11
50
  export default {
12
51
  ui: {
13
- colors: {
14
- primary: "dms",
15
- // The AI accent of the v2 design.
16
- secondary: "violet",
17
- neutral: "neutral",
18
- // Design semantic hexes map exactly onto these Tailwind palettes:
19
- // success #10b981 (emerald), warning #f59e0b (amber),
20
- // error #ef4444 (red), info #06b6d4 (cyan).
21
- success: "emerald",
22
- warning: "amber",
23
- error: "red",
24
- info: "cyan",
25
- },
52
+ colors: dmsColors(),
26
53
  icons: {
27
54
  arrowDown: "i-ph-arrow-down-light",
28
55
  arrowLeft: "i-ph-arrow-left-light",
@@ -184,16 +211,8 @@ export default {
184
211
  },
185
212
  },
186
213
  branding: {
187
- logo: {
188
- default: {
189
- light: "/images/antelope-logo/light.svg",
190
- dark: "/images/antelope-logo/dark.svg",
191
- },
192
- collapsed: {
193
- light: "/images/antelope-logo/icon.svg",
194
- dark: "/images/antelope-logo/icon.svg",
195
- },
196
- },
214
+ logo: dmsLogo(),
215
+ defaults: { logo: dmsLogo(), colors: dmsColors() },
197
216
  footer: {
198
217
  copyright: "Antelope",
199
218
  githubStar: true,