@vertesia/ui 1.5.0-dev.20260910.052912Z → 1.6.0-dev.20260912.110338Z

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 (389) hide show
  1. package/README.md +125 -0
  2. package/lib/boot/branding.d.ts +58 -0
  3. package/lib/boot/branding.d.ts.map +1 -0
  4. package/lib/boot/branding.js +62 -0
  5. package/lib/boot/branding.js.map +1 -0
  6. package/lib/boot/index.d.ts +27 -0
  7. package/lib/boot/index.d.ts.map +1 -0
  8. package/lib/boot/index.js +325 -0
  9. package/lib/boot/index.js.map +1 -0
  10. package/lib/boot/vite.d.ts +63 -0
  11. package/lib/boot/vite.d.ts.map +1 -0
  12. package/lib/boot/vite.js +108 -0
  13. package/lib/boot/vite.js.map +1 -0
  14. package/lib/core/components/Link.js +1 -1
  15. package/lib/core/components/Link.js.map +1 -1
  16. package/lib/core/components/Spinner.js +1 -1
  17. package/lib/core/components/Spinner.js.map +1 -1
  18. package/lib/core/components/TagsInput.js +7 -7
  19. package/lib/core/components/TagsInput.js.map +1 -1
  20. package/lib/core/components/shadcn/badge.js +1 -1
  21. package/lib/core/components/shadcn/badge.js.map +1 -1
  22. package/lib/core/components/shadcn/button.d.ts.map +1 -1
  23. package/lib/core/components/shadcn/button.js +3 -4
  24. package/lib/core/components/shadcn/button.js.map +1 -1
  25. package/lib/core/components/shadcn/filters/comboBox/DateCombobox.js +2 -2
  26. package/lib/core/components/shadcn/filters/comboBox/DateCombobox.js.map +1 -1
  27. package/lib/core/components/shadcn/filters/comboBox/SelectCombobox.js +1 -1
  28. package/lib/core/components/shadcn/filters/comboBox/SelectCombobox.js.map +1 -1
  29. package/lib/core/components/shadcn/filters/comboBox/StringListCombobox.js +1 -1
  30. package/lib/core/components/shadcn/filters/comboBox/StringListCombobox.js.map +1 -1
  31. package/lib/core/components/shadcn/filters/comboBox/TextCombobox.js +1 -1
  32. package/lib/core/components/shadcn/filters/comboBox/TextCombobox.js.map +1 -1
  33. package/lib/core/components/shadcn/filters/filter/dateFilter.js +1 -1
  34. package/lib/core/components/shadcn/filters/filter/dateFilter.js.map +1 -1
  35. package/lib/core/components/shadcn/filters/filter-styles.d.ts +1 -1
  36. package/lib/core/components/shadcn/filters/filter-styles.d.ts.map +1 -1
  37. package/lib/core/components/shadcn/filters/filter-styles.js +4 -4
  38. package/lib/core/components/shadcn/filters/filters.js +1 -1
  39. package/lib/core/components/shadcn/filters/filters.js.map +1 -1
  40. package/lib/core/components/shadcn/label.js +1 -1
  41. package/lib/core/components/shadcn/label.js.map +1 -1
  42. package/lib/core/components/shadcn/overflowTabs.js +2 -2
  43. package/lib/core/components/shadcn/overflowTabs.js.map +1 -1
  44. package/lib/core/components/shadcn/tabs.js +1 -1
  45. package/lib/core/components/shadcn/tabs.js.map +1 -1
  46. package/lib/env/index.d.ts +28 -1
  47. package/lib/env/index.d.ts.map +1 -1
  48. package/lib/env/index.js +51 -3
  49. package/lib/env/index.js.map +1 -1
  50. package/lib/features/activity-doc/ActivityDoc.js +1 -1
  51. package/lib/features/activity-doc/ActivityDoc.js.map +1 -1
  52. package/lib/features/agent/PayloadBuilder.d.ts +4 -0
  53. package/lib/features/agent/PayloadBuilder.d.ts.map +1 -1
  54. package/lib/features/agent/PayloadBuilder.js.map +1 -1
  55. package/lib/features/agent/chat/AgentRunFeedback.d.ts +59 -0
  56. package/lib/features/agent/chat/AgentRunFeedback.d.ts.map +1 -0
  57. package/lib/features/agent/chat/AgentRunFeedback.js +286 -0
  58. package/lib/features/agent/chat/AgentRunFeedback.js.map +1 -0
  59. package/lib/features/agent/chat/AskUserWidget.d.ts +1 -0
  60. package/lib/features/agent/chat/AskUserWidget.d.ts.map +1 -1
  61. package/lib/features/agent/chat/AskUserWidget.js +15 -4
  62. package/lib/features/agent/chat/AskUserWidget.js.map +1 -1
  63. package/lib/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
  64. package/lib/features/agent/chat/ModernAgentConversation.js +3 -2
  65. package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
  66. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts +3 -1
  67. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
  68. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +16 -13
  69. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
  70. package/lib/features/agent/chat/ModernAgentOutput/Header.d.ts.map +1 -1
  71. package/lib/features/agent/chat/ModernAgentOutput/Header.js.map +1 -1
  72. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts +3 -1
  73. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
  74. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js +30 -8
  75. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
  76. package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.d.ts.map +1 -1
  77. package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.js +2 -0
  78. package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.js.map +1 -1
  79. package/lib/features/agent/chat/index.d.ts +1 -0
  80. package/lib/features/agent/chat/index.d.ts.map +1 -1
  81. package/lib/features/agent/chat/index.js +1 -0
  82. package/lib/features/agent/chat/index.js.map +1 -1
  83. package/lib/features/facets/AgentRunnerFacetsNav.d.ts +8 -0
  84. package/lib/features/facets/AgentRunnerFacetsNav.d.ts.map +1 -1
  85. package/lib/features/facets/AgentRunnerFacetsNav.js +12 -0
  86. package/lib/features/facets/AgentRunnerFacetsNav.js.map +1 -1
  87. package/lib/features/layout/GenericPageNavHeader.js +1 -1
  88. package/lib/features/layout/GenericPageNavHeader.js.map +1 -1
  89. package/lib/features/magic-pdf/AnnotatedImageSlider.js +1 -1
  90. package/lib/features/magic-pdf/AnnotatedImageSlider.js.map +1 -1
  91. package/lib/features/magic-pdf/GroundedExtractionView.js +3 -3
  92. package/lib/features/magic-pdf/GroundedExtractionView.js.map +1 -1
  93. package/lib/features/permissions/UserPermissionsProvider.d.ts +15 -1
  94. package/lib/features/permissions/UserPermissionsProvider.d.ts.map +1 -1
  95. package/lib/features/permissions/UserPermissionsProvider.js +20 -12
  96. package/lib/features/permissions/UserPermissionsProvider.js.map +1 -1
  97. package/lib/features/store/objects/DocumentPreviewPanel.js +5 -5
  98. package/lib/features/store/objects/DocumentPreviewPanel.js.map +1 -1
  99. package/lib/features/store/objects/components/DocumentInput.js +1 -1
  100. package/lib/features/store/objects/components/DocumentInput.js.map +1 -1
  101. package/lib/features/store/objects/selection/SelectionActions.js +1 -1
  102. package/lib/features/store/objects/selection/SelectionActions.js.map +1 -1
  103. package/lib/features/store/objects/upload/DocumentUploadModal.js +1 -1
  104. package/lib/features/store/objects/upload/DocumentUploadModal.js.map +1 -1
  105. package/lib/features/store/types/TypeRegistry.d.ts +130 -0
  106. package/lib/features/store/types/TypeRegistry.d.ts.map +1 -1
  107. package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts +130 -0
  108. package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts.map +1 -1
  109. package/lib/features/user/UserInfo.js +1 -1
  110. package/lib/features/user/UserInfo.js.map +1 -1
  111. package/lib/i18n/index.d.ts +5 -4
  112. package/lib/i18n/index.d.ts.map +1 -1
  113. package/lib/i18n/index.js +16 -2
  114. package/lib/i18n/index.js.map +1 -1
  115. package/lib/i18n/locales/ar.json +30 -0
  116. package/lib/i18n/locales/de.json +30 -0
  117. package/lib/i18n/locales/en.json +30 -0
  118. package/lib/i18n/locales/es.json +30 -0
  119. package/lib/i18n/locales/fr.json +30 -0
  120. package/lib/i18n/locales/it.json +30 -0
  121. package/lib/i18n/locales/ja.json +30 -0
  122. package/lib/i18n/locales/ko.json +30 -0
  123. package/lib/i18n/locales/pt.json +30 -0
  124. package/lib/i18n/locales/ru.json +30 -0
  125. package/lib/i18n/locales/tr.json +30 -0
  126. package/lib/i18n/locales/zh-TW.json +30 -0
  127. package/lib/i18n/locales/zh.json +30 -0
  128. package/lib/layout/ErrorPage.js +1 -1
  129. package/lib/layout/ErrorPage.js.map +1 -1
  130. package/lib/layout/Navbar.d.ts.map +1 -1
  131. package/lib/layout/Navbar.js +3 -2
  132. package/lib/layout/Navbar.js.map +1 -1
  133. package/lib/router/RouteComponent.d.ts.map +1 -1
  134. package/lib/router/RouteComponent.js +15 -12
  135. package/lib/router/RouteComponent.js.map +1 -1
  136. package/lib/session/UserSession.d.ts.map +1 -1
  137. package/lib/session/UserSession.js +6 -3
  138. package/lib/session/UserSession.js.map +1 -1
  139. package/lib/session/UserSessionProvider.d.ts +2 -1
  140. package/lib/session/UserSessionProvider.d.ts.map +1 -1
  141. package/lib/session/UserSessionProvider.js +10 -1
  142. package/lib/session/UserSessionProvider.js.map +1 -1
  143. package/lib/session/auth/composable.d.ts.map +1 -1
  144. package/lib/session/auth/composable.js +88 -3
  145. package/lib/session/auth/composable.js.map +1 -1
  146. package/lib/session/auth/domainRouting.d.ts +22 -12
  147. package/lib/session/auth/domainRouting.d.ts.map +1 -1
  148. package/lib/session/auth/domainRouting.js +35 -21
  149. package/lib/session/auth/domainRouting.js.map +1 -1
  150. package/lib/session/auth/useAuthState.d.ts +9 -4
  151. package/lib/session/auth/useAuthState.d.ts.map +1 -1
  152. package/lib/session/auth/useAuthState.js +9 -4
  153. package/lib/session/auth/useAuthState.js.map +1 -1
  154. package/lib/session/index.d.ts.map +1 -1
  155. package/lib/session/index.js +3 -1
  156. package/lib/session/index.js.map +1 -1
  157. package/lib/shell/AuthScreenPreview.d.ts +12 -0
  158. package/lib/shell/AuthScreenPreview.d.ts.map +1 -0
  159. package/lib/shell/AuthScreenPreview.js +140 -0
  160. package/lib/shell/AuthScreenPreview.js.map +1 -0
  161. package/lib/shell/BrandedAuthScreens.d.ts +19 -0
  162. package/lib/shell/BrandedAuthScreens.d.ts.map +1 -0
  163. package/lib/shell/BrandedAuthScreens.js +64 -0
  164. package/lib/shell/BrandedAuthScreens.js.map +1 -0
  165. package/lib/shell/SplashScreen.d.ts +13 -2
  166. package/lib/shell/SplashScreen.d.ts.map +1 -1
  167. package/lib/shell/SplashScreen.js +21 -20
  168. package/lib/shell/SplashScreen.js.map +1 -1
  169. package/lib/shell/VertesiaShell.d.ts +15 -4
  170. package/lib/shell/VertesiaShell.d.ts.map +1 -1
  171. package/lib/shell/VertesiaShell.js +8 -3
  172. package/lib/shell/VertesiaShell.js.map +1 -1
  173. package/lib/shell/apps/StandaloneApp.js +5 -1
  174. package/lib/shell/apps/StandaloneApp.js.map +1 -1
  175. package/lib/shell/index.d.ts +6 -0
  176. package/lib/shell/index.d.ts.map +1 -1
  177. package/lib/shell/index.js +5 -0
  178. package/lib/shell/index.js.map +1 -1
  179. package/lib/shell/login/SignInFlow.d.ts +68 -0
  180. package/lib/shell/login/SignInFlow.d.ts.map +1 -0
  181. package/lib/shell/login/SignInFlow.js +108 -0
  182. package/lib/shell/login/SignInFlow.js.map +1 -0
  183. package/lib/shell/login/SignInPageShell.d.ts +13 -0
  184. package/lib/shell/login/SignInPageShell.d.ts.map +1 -0
  185. package/lib/shell/login/SignInPageShell.js +14 -0
  186. package/lib/shell/login/SignInPageShell.js.map +1 -0
  187. package/lib/shell/login/SignInPrimitives.d.ts +3 -3
  188. package/lib/shell/login/SignInPrimitives.d.ts.map +1 -1
  189. package/lib/shell/login/SignInPrimitives.js +13 -11
  190. package/lib/shell/login/SignInPrimitives.js.map +1 -1
  191. package/lib/shell/login/SignInRestrictedEnvStep.d.ts +5 -0
  192. package/lib/shell/login/SignInRestrictedEnvStep.d.ts.map +1 -1
  193. package/lib/shell/login/SignInRestrictedEnvStep.js +8 -4
  194. package/lib/shell/login/SignInRestrictedEnvStep.js.map +1 -1
  195. package/lib/shell/login/SigninScreen.d.ts +38 -3
  196. package/lib/shell/login/SigninScreen.d.ts.map +1 -1
  197. package/lib/shell/login/SigninScreen.js +33 -86
  198. package/lib/shell/login/SigninScreen.js.map +1 -1
  199. package/lib/shell/login/SignupForm.d.ts +3 -1
  200. package/lib/shell/login/SignupForm.d.ts.map +1 -1
  201. package/lib/shell/login/SignupForm.js +7 -3
  202. package/lib/shell/login/SignupForm.js.map +1 -1
  203. package/lib/shell/login/signInUtils.d.ts +11 -0
  204. package/lib/shell/login/signInUtils.d.ts.map +1 -1
  205. package/lib/shell/login/signInUtils.js +29 -5
  206. package/lib/shell/login/signInUtils.js.map +1 -1
  207. package/lib/vertesia-ui-boot.js +237 -0
  208. package/lib/vertesia-ui-boot.js.map +1 -0
  209. package/lib/vertesia-ui-core.js +46 -46
  210. package/lib/vertesia-ui-core.js.map +1 -1
  211. package/lib/vertesia-ui-env.js +1 -1
  212. package/lib/vertesia-ui-env.js.map +1 -1
  213. package/lib/vertesia-ui-features.js +222 -208
  214. package/lib/vertesia-ui-features.js.map +1 -1
  215. package/lib/vertesia-ui-i18n.B3NFclwl.chunk.js +5 -0
  216. package/lib/vertesia-ui-i18n.B3NFclwl.chunk.js.map +1 -0
  217. package/lib/vertesia-ui-i18n.BWSrNIxc.chunk.js +5 -0
  218. package/lib/vertesia-ui-i18n.BWSrNIxc.chunk.js.map +1 -0
  219. package/lib/vertesia-ui-i18n.BWvWeB1p.chunk.js +5 -0
  220. package/lib/vertesia-ui-i18n.BWvWeB1p.chunk.js.map +1 -0
  221. package/lib/vertesia-ui-i18n.BZCXvTC1.chunk.js +5 -0
  222. package/lib/vertesia-ui-i18n.BZCXvTC1.chunk.js.map +1 -0
  223. package/lib/vertesia-ui-i18n.BbJMvFZW.chunk.js +5 -0
  224. package/lib/vertesia-ui-i18n.BbJMvFZW.chunk.js.map +1 -0
  225. package/lib/vertesia-ui-i18n.CE5ldwpt.chunk.js +5 -0
  226. package/lib/vertesia-ui-i18n.CE5ldwpt.chunk.js.map +1 -0
  227. package/lib/vertesia-ui-i18n.CMC0IShY.chunk.js +5 -0
  228. package/lib/vertesia-ui-i18n.CMC0IShY.chunk.js.map +1 -0
  229. package/lib/vertesia-ui-i18n.CkcRqBKU.chunk.js +5 -0
  230. package/lib/vertesia-ui-i18n.CkcRqBKU.chunk.js.map +1 -0
  231. package/lib/vertesia-ui-i18n.D-Mq1sw_.chunk.js +5 -0
  232. package/lib/vertesia-ui-i18n.D-Mq1sw_.chunk.js.map +1 -0
  233. package/lib/vertesia-ui-i18n.DkK68-A-.chunk.js +5 -0
  234. package/lib/vertesia-ui-i18n.DkK68-A-.chunk.js.map +1 -0
  235. package/lib/vertesia-ui-i18n.XE_dcCg6.chunk.js +5 -0
  236. package/lib/vertesia-ui-i18n.XE_dcCg6.chunk.js.map +1 -0
  237. package/lib/vertesia-ui-i18n.js +2 -2
  238. package/lib/vertesia-ui-i18n.js.map +1 -1
  239. package/lib/vertesia-ui-i18n.qgNqMPvK.chunk.js +5 -0
  240. package/lib/vertesia-ui-i18n.qgNqMPvK.chunk.js.map +1 -0
  241. package/lib/vertesia-ui-layout.js +13 -13
  242. package/lib/vertesia-ui-layout.js.map +1 -1
  243. package/lib/vertesia-ui-rich-text.js +68 -68
  244. package/lib/vertesia-ui-rich-text.js.map +1 -1
  245. package/lib/vertesia-ui-router.js +1 -1
  246. package/lib/vertesia-ui-router.js.map +1 -1
  247. package/lib/vertesia-ui-session.js +1 -1
  248. package/lib/vertesia-ui-session.js.map +1 -1
  249. package/lib/vertesia-ui-shell.js +58 -33
  250. package/lib/vertesia-ui-shell.js.map +1 -1
  251. package/lib/vertesia-ui-widgets.js +94 -94
  252. package/lib/vertesia-ui-widgets.js.map +1 -1
  253. package/lib/widgets/json-view/JSONCode.js +1 -1
  254. package/lib/widgets/json-view/JSONCode.js.map +1 -1
  255. package/lib/widgets/upload/DropZone.d.ts.map +1 -1
  256. package/lib/widgets/upload/DropZone.js +1 -1
  257. package/lib/widgets/upload/DropZone.js.map +1 -1
  258. package/llms.txt +6 -2
  259. package/package.json +44 -35
  260. package/src/__tests__/a11y.test.tsx +25 -0
  261. package/src/__tests__/theme.test.ts +91 -0
  262. package/src/boot/branding.test.ts +72 -0
  263. package/src/boot/branding.ts +108 -0
  264. package/src/boot/index.test.ts +216 -0
  265. package/src/boot/index.ts +356 -0
  266. package/src/boot/vite.test.ts +93 -0
  267. package/src/boot/vite.ts +118 -0
  268. package/src/core/components/Link.tsx +1 -1
  269. package/src/core/components/Spinner.tsx +1 -1
  270. package/src/core/components/TagsInput.tsx +7 -7
  271. package/src/core/components/shadcn/badge.tsx +1 -1
  272. package/src/core/components/shadcn/button.tsx +3 -5
  273. package/src/core/components/shadcn/filters/comboBox/DateCombobox.tsx +2 -2
  274. package/src/core/components/shadcn/filters/comboBox/SelectCombobox.tsx +1 -1
  275. package/src/core/components/shadcn/filters/comboBox/StringListCombobox.tsx +1 -1
  276. package/src/core/components/shadcn/filters/comboBox/TextCombobox.tsx +1 -1
  277. package/src/core/components/shadcn/filters/filter/dateFilter.tsx +1 -1
  278. package/src/core/components/shadcn/filters/filter-styles.ts +4 -4
  279. package/src/core/components/shadcn/filters/filters.tsx +1 -1
  280. package/src/core/components/shadcn/label.tsx +1 -1
  281. package/src/core/components/shadcn/overflowTabs.tsx +2 -2
  282. package/src/core/components/shadcn/tabs.tsx +1 -1
  283. package/src/css/utilities.css +7 -0
  284. package/src/env/index.test.ts +100 -0
  285. package/src/env/index.ts +66 -3
  286. package/src/features/activity-doc/ActivityDoc.tsx +1 -1
  287. package/src/features/agent/PayloadBuilder.tsx +4 -0
  288. package/src/features/agent/chat/AgentRunFeedback.test.tsx +330 -0
  289. package/src/features/agent/chat/AgentRunFeedback.tsx +452 -0
  290. package/src/features/agent/chat/AskUserWidget.test.tsx +24 -2
  291. package/src/features/agent/chat/AskUserWidget.tsx +19 -4
  292. package/src/features/agent/chat/ModernAgentConversation.tsx +46 -42
  293. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.test.tsx +207 -0
  294. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +50 -31
  295. package/src/features/agent/chat/ModernAgentOutput/Header.tsx +4 -0
  296. package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +42 -8
  297. package/src/features/agent/chat/ModernAgentOutput/SummaryConversation.ts +2 -0
  298. package/src/features/agent/chat/ModernAgentOutput/utils.test.ts +14 -0
  299. package/src/features/agent/chat/hooks/useFileProcessing.test.tsx +0 -5
  300. package/src/features/agent/chat/index.ts +6 -0
  301. package/src/features/facets/AgentRunnerFacetsNav.tsx +26 -0
  302. package/src/features/layout/GenericPageNavHeader.test.tsx +36 -0
  303. package/src/features/layout/GenericPageNavHeader.tsx +1 -1
  304. package/src/features/magic-pdf/AnnotatedImageSlider.tsx +1 -1
  305. package/src/features/magic-pdf/GroundedExtractionView.tsx +3 -3
  306. package/src/features/permissions/UserPermissionsProvider.test.tsx +50 -1
  307. package/src/features/permissions/UserPermissionsProvider.tsx +82 -44
  308. package/src/features/store/objects/DocumentPreviewPanel.tsx +10 -10
  309. package/src/features/store/objects/components/DocumentInput.tsx +1 -1
  310. package/src/features/store/objects/selection/SelectionActions.tsx +4 -1
  311. package/src/features/store/objects/upload/DocumentUploadModal.tsx +1 -1
  312. package/src/features/user/UserInfo.tsx +1 -1
  313. package/src/i18n/index.tsx +25 -2
  314. package/src/i18n/locales/ar.json +30 -0
  315. package/src/i18n/locales/de.json +30 -0
  316. package/src/i18n/locales/en.json +30 -0
  317. package/src/i18n/locales/es.json +30 -0
  318. package/src/i18n/locales/fr.json +30 -0
  319. package/src/i18n/locales/it.json +30 -0
  320. package/src/i18n/locales/ja.json +30 -0
  321. package/src/i18n/locales/ko.json +30 -0
  322. package/src/i18n/locales/pt.json +30 -0
  323. package/src/i18n/locales/ru.json +30 -0
  324. package/src/i18n/locales/tr.json +30 -0
  325. package/src/i18n/locales/zh-TW.json +30 -0
  326. package/src/i18n/locales/zh.json +30 -0
  327. package/src/layout/ErrorPage.tsx +1 -1
  328. package/src/layout/Navbar.test.tsx +58 -0
  329. package/src/layout/Navbar.tsx +5 -2
  330. package/src/router/RouteComponent.test.tsx +65 -1
  331. package/src/router/RouteComponent.tsx +18 -15
  332. package/src/session/UserSession.test.ts +65 -0
  333. package/src/session/UserSession.ts +6 -4
  334. package/src/session/UserSessionProvider.strict-mode.test.tsx +44 -0
  335. package/src/session/UserSessionProvider.test.ts +62 -1
  336. package/src/session/UserSessionProvider.tsx +9 -1
  337. package/src/session/auth/composable.renewal.test.ts +170 -0
  338. package/src/session/auth/composable.test.ts +23 -1
  339. package/src/session/auth/composable.ts +101 -2
  340. package/src/session/auth/domainRouting.test.ts +35 -65
  341. package/src/session/auth/domainRouting.ts +51 -34
  342. package/src/session/auth/useAuthState.ts +11 -4
  343. package/src/session/index.ts +3 -1
  344. package/src/shell/AuthScreenPreview.test.tsx +44 -0
  345. package/src/shell/AuthScreenPreview.tsx +228 -0
  346. package/src/shell/BrandedAuthScreens.test.tsx +181 -0
  347. package/src/shell/BrandedAuthScreens.tsx +117 -0
  348. package/src/shell/SplashScreen.test.tsx +37 -0
  349. package/src/shell/SplashScreen.tsx +45 -40
  350. package/src/shell/VertesiaShell.tsx +49 -22
  351. package/src/shell/apps/StandaloneApp.tsx +5 -1
  352. package/src/shell/index.tsx +6 -0
  353. package/src/shell/login/SignInFlow.tsx +213 -0
  354. package/src/shell/login/SignInPageShell.tsx +62 -0
  355. package/src/shell/login/SignInPrimitives.tsx +14 -11
  356. package/src/shell/login/SignInRestrictedEnvStep.tsx +19 -7
  357. package/src/shell/login/SigninScreen.test.tsx +104 -0
  358. package/src/shell/login/SigninScreen.tsx +115 -145
  359. package/src/shell/login/SignupForm.tsx +8 -3
  360. package/src/shell/login/signInUtils.ts +30 -5
  361. package/src/widgets/json-view/JSONCode.test.tsx +2 -2
  362. package/src/widgets/json-view/JSONCode.tsx +1 -1
  363. package/src/widgets/markdown/markdown.css +21 -21
  364. package/src/widgets/upload/DropZone.tsx +2 -4
  365. package/lib/vertesia-ui-i18n.BfN7iOHb.chunk.js +0 -5
  366. package/lib/vertesia-ui-i18n.BfN7iOHb.chunk.js.map +0 -1
  367. package/lib/vertesia-ui-i18n.BjH2zEZw.chunk.js +0 -5
  368. package/lib/vertesia-ui-i18n.BjH2zEZw.chunk.js.map +0 -1
  369. package/lib/vertesia-ui-i18n.BjSwAZXl.chunk.js +0 -5
  370. package/lib/vertesia-ui-i18n.BjSwAZXl.chunk.js.map +0 -1
  371. package/lib/vertesia-ui-i18n.C7PUm5Au.chunk.js +0 -5
  372. package/lib/vertesia-ui-i18n.C7PUm5Au.chunk.js.map +0 -1
  373. package/lib/vertesia-ui-i18n.CF_1mUS5.chunk.js +0 -5
  374. package/lib/vertesia-ui-i18n.CF_1mUS5.chunk.js.map +0 -1
  375. package/lib/vertesia-ui-i18n.ChfUl3_-.chunk.js +0 -5
  376. package/lib/vertesia-ui-i18n.ChfUl3_-.chunk.js.map +0 -1
  377. package/lib/vertesia-ui-i18n.D-Sk-5d1.chunk.js +0 -5
  378. package/lib/vertesia-ui-i18n.D-Sk-5d1.chunk.js.map +0 -1
  379. package/lib/vertesia-ui-i18n.D4rnu1_S.chunk.js +0 -5
  380. package/lib/vertesia-ui-i18n.D4rnu1_S.chunk.js.map +0 -1
  381. package/lib/vertesia-ui-i18n.DDf0-laK.chunk.js +0 -5
  382. package/lib/vertesia-ui-i18n.DDf0-laK.chunk.js.map +0 -1
  383. package/lib/vertesia-ui-i18n.DN79y06J.chunk.js +0 -5
  384. package/lib/vertesia-ui-i18n.DN79y06J.chunk.js.map +0 -1
  385. package/lib/vertesia-ui-i18n.DWLIciYQ.chunk.js +0 -5
  386. package/lib/vertesia-ui-i18n.DWLIciYQ.chunk.js.map +0 -1
  387. package/lib/vertesia-ui-i18n.DvF_YaAJ.chunk.js +0 -5
  388. package/lib/vertesia-ui-i18n.DvF_YaAJ.chunk.js.map +0 -1
  389. package/src/session/auth/authState.test.ts +0 -35
@@ -10,11 +10,86 @@ import {
10
10
  import { Env } from '@vertesia/ui/env';
11
11
  import { jwtDecode } from 'jwt-decode';
12
12
  import { LastSelectedAccountId_KEY, LastSelectedProjectId_KEY } from '../constants';
13
+ import { generateAuthState } from './authState';
14
+ import {
15
+ authReturnUrl,
16
+ buildCentralAuthRedirectUrl,
17
+ centralAuthUrl,
18
+ shouldRedirectToCentralAuth,
19
+ } from './domainRouting';
13
20
  import { getFirebaseAuth, getFirebaseAuthToken } from './firebase';
14
21
 
15
22
  let AUTH_TOKEN_RAW: string | undefined;
16
23
  let AUTH_TOKEN: AuthTokenPayload | undefined;
17
24
 
25
+ /**
26
+ * Whether this tab ever held a token.
27
+ *
28
+ * Separates "the session expired" from "there is no session yet". Only the first is renewed from
29
+ * here; the first load is UserSessionProvider's, and it has the account/project selection that
30
+ * this layer does not.
31
+ */
32
+ let HAD_SESSION = false;
33
+
34
+ /** Session-storage stamp of the last renewal redirect, so the cooldown survives the navigation. */
35
+ const RENEWAL_ATTEMPT_KEY = 'vt.centralAuthRenewalAt';
36
+ const RENEWAL_COOLDOWN_MS = 60_000;
37
+
38
+ /**
39
+ * Renew an expired Central Auth session by bouncing through the broker.
40
+ *
41
+ * A Central Auth session holds exactly one credential -- the STS JWT, in memory -- and STS refuses
42
+ * it once it has expired. There is no Firebase user in this browser to mint a replacement from and
43
+ * no refresh token, so the only way back is the broker, which still has the user's own session and
44
+ * returns a fresh identity token without prompting. That is precisely what a manual reload does
45
+ * today; doing it here turns a page that dead-ends on "Cannot acquire a composable token" into a
46
+ * redirect the user usually does not notice.
47
+ *
48
+ * The cooldown is the important part. If the broker returns a credential STS also rejects, an
49
+ * unguarded redirect bounces the browser between the two forever -- and a redirect loop is a worse
50
+ * failure than the error page it replaced, because it never comes to rest anywhere the user can
51
+ * read. One attempt per minute, recorded in sessionStorage because the module state does not
52
+ * survive the navigation this function starts.
53
+ *
54
+ * Returns whether a redirect was started.
55
+ */
56
+ function renewExpiredCentralAuthSession(): boolean {
57
+ if (!HAD_SESSION || !shouldRedirectToCentralAuth()) {
58
+ return false;
59
+ }
60
+
61
+ let lastAttempt = 0;
62
+ try {
63
+ lastAttempt = Number(sessionStorage.getItem(RENEWAL_ATTEMPT_KEY) ?? '0');
64
+ } catch {
65
+ // sessionStorage unavailable -- treated as "no previous attempt", so the redirect is
66
+ // allowed once and the failure surfaces normally if it does not stick.
67
+ }
68
+ if (Number.isFinite(lastAttempt) && lastAttempt > 0 && Date.now() - lastAttempt < RENEWAL_COOLDOWN_MS) {
69
+ Env.logger.warn('Central Auth session renewal already attempted; surfacing the failure instead', {
70
+ vertesia: { last_attempt_ms_ago: Date.now() - lastAttempt },
71
+ });
72
+ return false;
73
+ }
74
+
75
+ try {
76
+ sessionStorage.setItem(RENEWAL_ATTEMPT_KEY, String(Date.now()));
77
+ } catch {
78
+ // Without the stamp the cooldown cannot be enforced across the navigation. Still worth
79
+ // redirecting once: the alternative is a page the user can only fix by reloading manually.
80
+ }
81
+
82
+ Env.logger.info('Renewing an expired Central Auth session through the broker');
83
+ const url = buildCentralAuthRedirectUrl(
84
+ centralAuthUrl(),
85
+ Env.endpoints.sts ?? 'https://sts.vertesia.io',
86
+ authReturnUrl(),
87
+ generateAuthState(),
88
+ );
89
+ location.replace(url.toString());
90
+ return true;
91
+ }
92
+
18
93
  function clearRejectedPersistedScope(accountId?: string, projectId?: string) {
19
94
  if (!accountId) return;
20
95
 
@@ -62,8 +137,11 @@ function identityFromAcceptedToken(token: string): AuthenticatedIdentity | undef
62
137
  }
63
138
 
64
139
  export function resolveAuthSelection(currentUrl: URL): { accountId?: string; projectId?: string } {
65
- const urlAccount = currentUrl.searchParams.get('a') ?? undefined;
66
- const urlProject = currentUrl.searchParams.get('p') ?? undefined;
140
+ // A URL selection owns the whole pair: never attach an unrelated configured account/project.
141
+ const hasUrlScope = currentUrl.searchParams.has('a') || currentUrl.searchParams.has('p');
142
+ const defaults = hasUrlScope ? undefined : Env.defaultAuthSelection;
143
+ const urlAccount = currentUrl.searchParams.get('a') ?? defaults?.accountId;
144
+ const urlProject = currentUrl.searchParams.get('p') ?? defaults?.projectId;
67
145
  const accountId =
68
146
  urlAccount ??
69
147
  (urlProject === undefined ? (localStorage.getItem(LastSelectedAccountId_KEY) ?? undefined) : undefined);
@@ -434,6 +512,7 @@ export async function getComposableToken(
434
512
  cachedTokenMatchesScope &&
435
513
  AUTH_TOKEN.exp > Date.now() / 1000 + 300
436
514
  ) {
515
+ HAD_SESSION = true;
437
516
  return { rawToken: AUTH_TOKEN_RAW, token: AUTH_TOKEN, error: false };
438
517
  }
439
518
 
@@ -447,6 +526,7 @@ export async function getComposableToken(
447
526
  if (!AUTH_TOKEN.exp) {
448
527
  throw new Error('Invalid composable token');
449
528
  }
529
+ HAD_SESSION = true;
450
530
  return { rawToken: AUTH_TOKEN_RAW, token: AUTH_TOKEN, error: false };
451
531
  }
452
532
 
@@ -458,7 +538,14 @@ export async function getComposableToken(
458
538
  } else if (!devAuthToken) {
459
539
  // Embedded apps can reacquire a fresh credential from their host after their cached token expires.
460
540
  const refreshCredential = (await Env.authTokenProvider?.()) ?? initToken ?? AUTH_TOKEN_RAW;
541
+ // `forceRefresh` has to defeat this shortcut, not just the cache above it. A caller
542
+ // asking for a forced refresh wants claims recomputed -- `refreshAuthToken()` exists so
543
+ // a stale `apps` claim can be re-read after an ACE change, and STS recomputes it on
544
+ // every issuance. Adopting the credential we already hold satisfies the check and
545
+ // returns the very token whose claims were suspect, silently making the call a no-op
546
+ // for exactly the sessions that have no other credential to fall back on.
461
547
  if (
548
+ !forceRefresh &&
462
549
  refreshCredential &&
463
550
  isVertesiaIssuedToken(refreshCredential) &&
464
551
  canUseVertesiaTokenDirectly(refreshCredential, selectedAccount, selectedProject)
@@ -488,6 +575,13 @@ export async function getComposableToken(
488
575
  clearRejectedPersistedScope(selectedAccount, selectedProject);
489
576
  }
490
577
  }
578
+ // An expired Central Auth session presents as a rejected credential: the JWT we sent STS to
579
+ // renew from is the one that timed out. The broker can still vouch for the user. Only this
580
+ // error -- a scope or account failure is about who the user is, and the broker would hand
581
+ // back the same identity and the same refusal.
582
+ if (error instanceof CredentialError) {
583
+ renewExpiredCentralAuthSession();
584
+ }
491
585
  throw error;
492
586
  }
493
587
 
@@ -498,6 +592,10 @@ export async function getComposableToken(
498
592
  project_id: selectedProject,
499
593
  },
500
594
  });
595
+ // Reached once the credential has already been discarded -- typically the call after the
596
+ // one that saw STS reject it. Same remedy, and the cooldown keeps this from redirecting a
597
+ // second time for the same expiry.
598
+ renewExpiredCentralAuthSession();
501
599
  throw new Error('Cannot acquire a composable token');
502
600
  }
503
601
 
@@ -514,6 +612,7 @@ export async function getComposableToken(
514
612
  throw new Error('Invalid composable token');
515
613
  }
516
614
 
615
+ HAD_SESSION = true;
517
616
  return { rawToken: AUTH_TOKEN_RAW, token: AUTH_TOKEN, error: false };
518
617
  }
519
618
 
@@ -1,12 +1,12 @@
1
1
  import { Env } from '@vertesia/ui/env';
2
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { afterEach, describe, expect, it } from 'vitest';
3
3
  import {
4
- centralAuthRedirectUrl,
4
+ centralAuthUrl,
5
5
  clearCentralAuthRoundTripMarker,
6
+ DEFAULT_CENTRAL_AUTH_URL,
6
7
  isCentralAuthRedirectPending,
7
8
  markCentralAuthRoundTripStarted,
8
9
  mountRootUrl,
9
- redirectToCentralAuth,
10
10
  shouldRedirectToCentralAuth,
11
11
  shouldUseFirebaseAuth,
12
12
  } from './domainRouting';
@@ -40,24 +40,6 @@ describe('domainRouting', () => {
40
40
  expect(shouldRedirectToCentralAuth()).toBe(true);
41
41
  });
42
42
 
43
- it('carries the selected account and project through central auth for a gateway-mounted app', () => {
44
- const redirect = centralAuthRedirectUrl({
45
- centralAuthUrl: 'https://internal-auth.vertesia.app/',
46
- stsEndpoint: 'https://sts.example.test',
47
- returnUrl: new URL('https://apps.example.test/tenants/t/apps/a/versions/v/app/?view=grid'),
48
- state: 'state-1',
49
- accountId: 'account-1',
50
- projectId: 'project-1',
51
- });
52
- const returnUrl = new URL(redirect.searchParams.get('redirect_uri') || '');
53
-
54
- expect(redirect.searchParams.get('sts')).toBe('https://sts.example.test');
55
- expect(redirect.searchParams.get('state')).toBe('state-1');
56
- expect(returnUrl.toString()).toBe(
57
- 'https://apps.example.test/tenants/t/apps/a/versions/v/app/?view=grid&p=project-1&a=account-1',
58
- );
59
- });
60
-
61
43
  describe('mountRootUrl', () => {
62
44
  it('returns the deep gateway mount root (dropping any deep route / query / hash)', () => {
63
45
  const mount = 'https://gw.example.com/tenants/05948c_5ed5f4/apps/furniture-catalog/versions/v1/app/';
@@ -80,6 +62,37 @@ describe('domainRouting', () => {
80
62
  });
81
63
  });
82
64
 
65
+ describe('centralAuthUrl', () => {
66
+ function initEnv(auth?: string) {
67
+ Env.init({
68
+ name: 'test',
69
+ version: '0',
70
+ isLocalDev: true,
71
+ isDocker: false,
72
+ type: 'development',
73
+ endpoints: { studio: 'https://studio.test', zeno: 'https://zeno.test', sts: 'https://sts.test', auth },
74
+ });
75
+ }
76
+
77
+ it('falls back to the default broker when no auth endpoint is configured', () => {
78
+ initEnv(undefined);
79
+ expect(centralAuthUrl()).toBe(DEFAULT_CENTRAL_AUTH_URL);
80
+ expect(DEFAULT_CENTRAL_AUTH_URL).toBe('https://internal-auth.vertesia.app/');
81
+ });
82
+
83
+ it('uses the configured auth endpoint when one is set', () => {
84
+ initEnv('https://auth.vertesia.io/');
85
+ expect(centralAuthUrl()).toBe('https://auth.vertesia.io/');
86
+ });
87
+
88
+ // An empty string is what an unset VITE_* override collapses to; it must not become the broker
89
+ // URL, or the app would redirect to its own origin instead of a login page.
90
+ it('treats an empty auth endpoint as unconfigured', () => {
91
+ initEnv('');
92
+ expect(centralAuthUrl()).toBe(DEFAULT_CENTRAL_AUTH_URL);
93
+ });
94
+ });
95
+
83
96
  // The boot-time counterpart of the redirect UserSessionProvider performs after mounting. It must
84
97
  // answer true only when that provider is certain to redirect, because an app's entry module skips
85
98
  // the whole React tree on the strength of it -- and false whenever the answer is not certain, which
@@ -116,7 +129,7 @@ describe('isCentralAuthRedirectPending', () => {
116
129
  expect(isCentralAuthRedirectPending()).toBe(true);
117
130
  });
118
131
 
119
- it('is false when AUTH_MODE is firebase, which signs in without the broker', () => {
132
+ it('is false on a Firebase-allowlisted host, which signs in without the broker', () => {
120
133
  initEnv({});
121
134
  (globalThis as { window?: unknown }).window = { AUTH_MODE: 'firebase', location: { hash: '' } };
122
135
  expect(isCentralAuthRedirectPending()).toBe(false);
@@ -199,46 +212,3 @@ describe('central auth round-trip marker', () => {
199
212
  expect(document.cookie).toBe('vtsauth=; Path=/; Max-Age=0; SameSite=Lax; Secure');
200
213
  });
201
214
  });
202
-
203
- // The shared redirect must preserve release/1.5's gateway selection and write state that
204
- // the provider's hook accepts when the broker returns.
205
- describe('redirectToCentralAuth', () => {
206
- afterEach(() => {
207
- vi.unstubAllGlobals();
208
- sessionStorage.clear();
209
- Env.init();
210
- });
211
-
212
- it('keeps the gateway mount and selection, records state, and marks the round trip before navigating', async () => {
213
- const { verifyAuthState, clearAuthState } = await import('./authState');
214
- const mount = 'https://apps.test/tenants/t/apps/a/versions/v/app/';
215
- const document = { baseURI: mount, cookie: '' };
216
- const replace = vi.fn((target: string) => {
217
- const url = new URL(target);
218
- expect(document.cookie).toBe('vtsauth=1; Path=/; Max-Age=300; SameSite=Lax; Secure');
219
- expect(verifyAuthState(url.searchParams.get('state'))).toBeUndefined();
220
- });
221
- vi.stubGlobal('window', { location: { href: 'https://apps.test/?view=grid#old', protocol: 'https:' } });
222
- vi.stubGlobal('document', document);
223
- vi.stubGlobal('location', { replace });
224
- Env.init({
225
- name: 'test',
226
- version: '0',
227
- isLocalDev: false,
228
- isDocker: false,
229
- type: 'production',
230
- endpoints: { studio: 'https://studio.test', zeno: 'https://zeno.test', sts: 'https://sts.test/?x=1&y=2' },
231
- });
232
-
233
- redirectToCentralAuth({ accountId: 'account-1', projectId: 'project-1' });
234
-
235
- expect(replace).toHaveBeenCalledOnce();
236
- const url = new URL(replace.mock.calls[0][0]);
237
- expect(url.origin).toBe('https://internal-auth.vertesia.app');
238
- expect(url.searchParams.get('sts')).toBe('https://sts.test/?x=1&y=2');
239
- expect(url.searchParams.get('redirect_uri')).toBe(`${mount}?p=project-1&a=account-1`);
240
- expect(verifyAuthState('wrong')).toContain('State mismatched');
241
- clearAuthState();
242
- expect(verifyAuthState(url.searchParams.get('state'))).toContain('State mismatched');
243
- });
244
- });
@@ -7,6 +7,20 @@ declare global {
7
7
  }
8
8
  }
9
9
 
10
+ /**
11
+ * The broker this app sends users to for sign-in and logout.
12
+ *
13
+ * Hard-coded until now, which meant every consumer of this package reached the same deployment no
14
+ * matter what was running. It is read from the environment so a single app can be pointed at a
15
+ * different broker, and it keeps this default so an app that configures nothing is unaffected.
16
+ */
17
+ export const DEFAULT_CENTRAL_AUTH_URL = 'https://internal-auth.vertesia.app/';
18
+
19
+ export function centralAuthUrl(): string {
20
+ const configured = Env.endpoints.auth;
21
+ return configured ? configured : DEFAULT_CENTRAL_AUTH_URL;
22
+ }
23
+
10
24
  export function shouldUseFirebaseAuth(_hostname?: string) {
11
25
  return window.AUTH_MODE === 'firebase';
12
26
  }
@@ -15,35 +29,11 @@ export function shouldRedirectToCentralAuth(_hostname?: string) {
15
29
  return !shouldUseFirebaseAuth();
16
30
  }
17
31
 
18
- interface CentralAuthRedirectOptions {
19
- centralAuthUrl: string;
20
- stsEndpoint: string;
21
- returnUrl: URL;
22
- state: string;
32
+ interface AuthSelection {
23
33
  accountId?: string;
24
34
  projectId?: string;
25
35
  }
26
36
 
27
- /** Build a central-auth round trip without losing the app's requested account/project selection. */
28
- export function centralAuthRedirectUrl({
29
- centralAuthUrl,
30
- stsEndpoint,
31
- returnUrl,
32
- state,
33
- accountId,
34
- projectId,
35
- }: CentralAuthRedirectOptions): URL {
36
- const selectedReturnUrl = new URL(returnUrl);
37
- if (projectId) selectedReturnUrl.searchParams.set('p', projectId);
38
- if (accountId) selectedReturnUrl.searchParams.set('a', accountId);
39
-
40
- const url = new URL(centralAuthUrl);
41
- url.searchParams.set('sts', stsEndpoint);
42
- url.searchParams.set('redirect_uri', selectedReturnUrl.toString());
43
- url.searchParams.set('state', state);
44
- return url;
45
- }
46
-
47
37
  /**
48
38
  * The page URL to return to after a central-auth round-trip (`redirect_uri`).
49
39
  *
@@ -82,6 +72,33 @@ export function mountRootUrl(): URL {
82
72
  return url;
83
73
  }
84
74
 
75
+ /**
76
+ * The broker URL a sign-in or renewal round-trip navigates to.
77
+ *
78
+ * Every parameter goes on through `searchParams`, never by concatenating a query string onto
79
+ * `centralAuth`. The endpoint is configurable, so it may already carry its own query or a
80
+ * fragment -- and appending `?sts=...` to one of those folds the parameter into the existing value
81
+ * or hides it in the fragment, leaving Central Auth with no `sts` at all. `searchParams` also
82
+ * percent-encodes the values, which the interpolated URLs did not.
83
+ */
84
+ export function buildCentralAuthRedirectUrl(
85
+ centralAuth: string,
86
+ stsUrl: string,
87
+ returnUrl: URL,
88
+ state: string,
89
+ selection: AuthSelection = {},
90
+ ): URL {
91
+ const selectedReturnUrl = new URL(returnUrl);
92
+ if (selection.projectId) selectedReturnUrl.searchParams.set('p', selection.projectId);
93
+ if (selection.accountId) selectedReturnUrl.searchParams.set('a', selection.accountId);
94
+
95
+ const url = new URL(centralAuth);
96
+ url.searchParams.set('sts', stsUrl);
97
+ url.searchParams.set('redirect_uri', selectedReturnUrl.toString());
98
+ url.searchParams.set('state', state);
99
+ return url;
100
+ }
101
+
85
102
  /**
86
103
  * A short-lived marker saying "a Central Auth round-trip is in flight for this browser".
87
104
  *
@@ -128,14 +145,14 @@ export function clearCentralAuthRoundTripMarker(): void {
128
145
  * and {@link isCentralAuthRedirectPending}'s counterpart inside `UserSessionProvider` cannot drift
129
146
  * into producing different `redirect_uri` / `state` / `sts` values for the same page load.
130
147
  */
131
- export function redirectToCentralAuth(selection: { accountId?: string; projectId?: string } = {}): void {
132
- const url = centralAuthRedirectUrl({
133
- centralAuthUrl: 'https://internal-auth.vertesia.app/',
134
- stsEndpoint: Env.endpoints.sts ?? 'https://sts.vertesia.io',
135
- returnUrl: authReturnUrl(),
136
- state: generateAuthState(),
137
- ...selection,
138
- });
148
+ export function redirectToCentralAuth(selection: AuthSelection = {}): void {
149
+ const url = buildCentralAuthRedirectUrl(
150
+ centralAuthUrl(),
151
+ Env.endpoints.sts ?? 'https://sts.vertesia.io',
152
+ authReturnUrl(),
153
+ generateAuthState(),
154
+ selection,
155
+ );
139
156
  markCentralAuthRoundTripStarted();
140
157
  location.replace(url.toString());
141
158
  }
@@ -149,7 +166,7 @@ export function redirectToCentralAuth(selection: { accountId?: string; projectId
149
166
  * that provider from redirecting is therefore knowable up front, and this predicate enumerates
150
167
  * them in the same order:
151
168
  *
152
- * - Apps configured with Firebase auth sign in in place rather than at the broker;
169
+ * - Firebase-allowlisted hosts sign in in place rather than at the broker;
153
170
  * - a host app that injects a token through `Env.authTokenProvider` may not need the broker
154
171
  * (and when its token turns out to be empty the provider still redirects — answering `false`
155
172
  * here just lets the normal flow decide, which is the safe direction);
@@ -1,10 +1,17 @@
1
- /** React wrappers for the state record shared with boot-time Central Auth redirects. */
1
+ /**
2
+ * This hook is used to generate and verify state for OAuth2 authorization requests.
3
+ *
4
+ * The implementation lives in ./authState so the token layer can start the same round-trip when it
5
+ * renews an expired Central Auth session, where no component is available to call a hook.
6
+ */
7
+
2
8
  import { useCallback } from 'react';
3
9
  import { clearAuthState, generateAuthState, verifyAuthState } from './authState';
4
10
 
5
11
  export function useAuthState() {
6
- const generateState = useCallback(generateAuthState, []);
7
- const verifyState = useCallback(verifyAuthState, []);
8
- const clearState = useCallback(clearAuthState, []);
12
+ const generateState = useCallback(() => generateAuthState(), []);
13
+ const verifyState = useCallback((returnedState: string | null) => verifyAuthState(returnedState), []);
14
+ const clearState = useCallback(() => clearAuthState(), []);
15
+
9
16
  return { generateState, verifyState, clearState };
10
17
  }
@@ -1,5 +1,7 @@
1
1
  export * from './auth/composable';
2
- // Publish the boot-time auth entry points.
2
+ // Explicit, not `export *`: domainRouting also holds buildCentralAuthRedirectUrl, which
3
+ // UserSessionProvider already re-exports below, and two star exports of one name resolve to
4
+ // nothing. Only the boot-time entry points an app needs are published here.
3
5
  export {
4
6
  clearCentralAuthRoundTripMarker,
5
7
  isCentralAuthRedirectPending,
@@ -0,0 +1,44 @@
1
+ import { cleanup, render, screen } from '@testing-library/react';
2
+ import * as session from '@vertesia/ui/session';
3
+ import { afterEach, describe, expect, it, vi } from 'vitest';
4
+ import { AUTH_PREVIEW_SCREENS, AuthScreenPreview, mountAuthScreenPreview } from './AuthScreenPreview';
5
+ import * as signin from './login/signInUtils';
6
+
7
+ afterEach(() => {
8
+ cleanup();
9
+ vi.restoreAllMocks();
10
+ });
11
+
12
+ describe('shared auth visual previews', () => {
13
+ it.each(AUTH_PREVIEW_SCREENS.filter((name) => name !== 'boot'))(
14
+ 'renders %s without initializing or changing authentication',
15
+ (name) => {
16
+ const firebase = vi.spyOn(session, 'getFirebaseAuth');
17
+ const reset = vi.spyOn(signin, 'resetSignInState');
18
+ const { container } = render(<AuthScreenPreview screen={name} branding={{ name: 'Example' }} />);
19
+ expect(container.querySelector('[inert]')?.textContent?.trim()).not.toBe('');
20
+ expect(screen.getByText(`Auth preview: ${name}`)).toBeTruthy();
21
+ expect(firebase).not.toHaveBeenCalled();
22
+ expect(reset).not.toHaveBeenCalled();
23
+ },
24
+ );
25
+
26
+ it('retains branding and full screen overrides, with links to the shared boot URL', () => {
27
+ render(
28
+ <AuthScreenPreview
29
+ screen="loading"
30
+ branding={{ name: 'Example' }}
31
+ screens={{ Loading: () => <p>Custom loader fixture</p> }}
32
+ />,
33
+ );
34
+ expect(screen.getByText('Custom loader fixture')).toBeTruthy();
35
+ expect(screen.getByText('boot').getAttribute('href')).toContain('__vertesia_boot=slow');
36
+ expect(screen.getByText('pending').getAttribute('href')).toContain('__vertesia_auth=pending');
37
+ });
38
+
39
+ it('keeps the React root empty for boot previews', async () => {
40
+ const container = document.createElement('div');
41
+ await mountAuthScreenPreview(container, 'boot');
42
+ expect(container.childNodes).toHaveLength(0);
43
+ });
44
+ });