@vertesia/ui 1.5.0-dev.20260714.072725Z → 1.5.0-dev.20260722.120446Z

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 (393) hide show
  1. package/lib/core/components/SidePanel.js +1 -1
  2. package/lib/core/components/SidePanel.js.map +1 -1
  3. package/lib/core/components/shadcn/button.d.ts +10 -3
  4. package/lib/core/components/shadcn/button.d.ts.map +1 -1
  5. package/lib/core/components/shadcn/button.js +41 -9
  6. package/lib/core/components/shadcn/button.js.map +1 -1
  7. package/lib/core/components/shadcn/modal/dialog.js +1 -1
  8. package/lib/core/components/shadcn/modal/dialog.js.map +1 -1
  9. package/lib/core/components/shadcn/theme/ThemeSwitcher.js +1 -1
  10. package/lib/core/components/shadcn/theme/ThemeSwitcher.js.map +1 -1
  11. package/lib/env/index.d.ts +19 -0
  12. package/lib/env/index.d.ts.map +1 -1
  13. package/lib/env/index.js +6 -0
  14. package/lib/env/index.js.map +1 -1
  15. package/lib/features/agent/chat/AgentChatPlaybackControls.d.ts.map +1 -1
  16. package/lib/features/agent/chat/AgentChatPlaybackControls.js +2 -2
  17. package/lib/features/agent/chat/AgentChatPlaybackControls.js.map +1 -1
  18. package/lib/features/agent/chat/AgentRequestInputOverlay.d.ts.map +1 -1
  19. package/lib/features/agent/chat/AgentRequestInputOverlay.js +2 -2
  20. package/lib/features/agent/chat/AgentRequestInputOverlay.js.map +1 -1
  21. package/lib/features/agent/chat/ArtifactsTab.d.ts.map +1 -1
  22. package/lib/features/agent/chat/ArtifactsTab.js +68 -11
  23. package/lib/features/agent/chat/ArtifactsTab.js.map +1 -1
  24. package/lib/features/agent/chat/ImageLightbox.d.ts.map +1 -1
  25. package/lib/features/agent/chat/ImageLightbox.js +1 -1
  26. package/lib/features/agent/chat/ImageLightbox.js.map +1 -1
  27. package/lib/features/agent/chat/ModernAgentConversation.js +52 -49
  28. package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
  29. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
  30. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +5 -5
  31. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
  32. package/lib/features/agent/chat/ModernAgentOutput/Header.d.ts.map +1 -1
  33. package/lib/features/agent/chat/ModernAgentOutput/Header.js +4 -2
  34. package/lib/features/agent/chat/ModernAgentOutput/Header.js.map +1 -1
  35. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts.map +1 -1
  36. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js +2 -2
  37. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js.map +1 -1
  38. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
  39. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js +2 -1
  40. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
  41. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.d.ts +6 -0
  42. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.d.ts.map +1 -1
  43. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.js +15 -1
  44. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.js.map +1 -1
  45. package/lib/features/agent/chat/hooks/useArtifacts.d.ts +8 -1
  46. package/lib/features/agent/chat/hooks/useArtifacts.d.ts.map +1 -1
  47. package/lib/features/agent/chat/hooks/useArtifacts.js +28 -10
  48. package/lib/features/agent/chat/hooks/useArtifacts.js.map +1 -1
  49. package/lib/features/document-viewer/UniversalDocumentViewer.d.ts +73 -0
  50. package/lib/features/document-viewer/UniversalDocumentViewer.d.ts.map +1 -0
  51. package/lib/features/document-viewer/UniversalDocumentViewer.js +415 -0
  52. package/lib/features/document-viewer/UniversalDocumentViewer.js.map +1 -0
  53. package/lib/features/document-viewer/index.d.ts +2 -0
  54. package/lib/features/document-viewer/index.d.ts.map +1 -0
  55. package/lib/features/document-viewer/index.js +2 -0
  56. package/lib/features/document-viewer/index.js.map +1 -0
  57. package/lib/features/environment/SelectEnvironment.d.ts +28 -0
  58. package/lib/features/environment/SelectEnvironment.d.ts.map +1 -0
  59. package/lib/features/environment/SelectEnvironment.js +97 -0
  60. package/lib/features/environment/SelectEnvironment.js.map +1 -0
  61. package/lib/features/environment/index.d.ts +2 -0
  62. package/lib/features/environment/index.d.ts.map +1 -0
  63. package/lib/features/environment/index.js +2 -0
  64. package/lib/features/environment/index.js.map +1 -0
  65. package/lib/features/index.d.ts +3 -0
  66. package/lib/features/index.d.ts.map +1 -1
  67. package/lib/features/index.js +3 -0
  68. package/lib/features/index.js.map +1 -1
  69. package/lib/features/magic-pdf/AnnotatedImageSlider.js +12 -12
  70. package/lib/features/magic-pdf/AnnotatedImageSlider.js.map +1 -1
  71. package/lib/features/magic-pdf/DownloadPopover.d.ts.map +1 -1
  72. package/lib/features/magic-pdf/DownloadPopover.js +3 -17
  73. package/lib/features/magic-pdf/DownloadPopover.js.map +1 -1
  74. package/lib/features/magic-pdf/ExtractedContentView.js +2 -6
  75. package/lib/features/magic-pdf/ExtractedContentView.js.map +1 -1
  76. package/lib/features/magic-pdf/GroundedExtractionAssistantPanel.d.ts +25 -0
  77. package/lib/features/magic-pdf/GroundedExtractionAssistantPanel.d.ts.map +1 -0
  78. package/lib/features/magic-pdf/GroundedExtractionAssistantPanel.js +42 -0
  79. package/lib/features/magic-pdf/GroundedExtractionAssistantPanel.js.map +1 -0
  80. package/lib/features/magic-pdf/GroundedExtractionView.d.ts +27 -0
  81. package/lib/features/magic-pdf/GroundedExtractionView.d.ts.map +1 -0
  82. package/lib/features/magic-pdf/GroundedExtractionView.js +377 -0
  83. package/lib/features/magic-pdf/GroundedExtractionView.js.map +1 -0
  84. package/lib/features/magic-pdf/MagicPdfProvider.d.ts +0 -2
  85. package/lib/features/magic-pdf/MagicPdfProvider.d.ts.map +1 -1
  86. package/lib/features/magic-pdf/MagicPdfProvider.js +15 -42
  87. package/lib/features/magic-pdf/MagicPdfProvider.js.map +1 -1
  88. package/lib/features/magic-pdf/MagicPdfView.d.ts.map +1 -1
  89. package/lib/features/magic-pdf/MagicPdfView.js +2 -19
  90. package/lib/features/magic-pdf/MagicPdfView.js.map +1 -1
  91. package/lib/features/magic-pdf/index.d.ts +1 -0
  92. package/lib/features/magic-pdf/index.d.ts.map +1 -1
  93. package/lib/features/magic-pdf/index.js +1 -0
  94. package/lib/features/magic-pdf/index.js.map +1 -1
  95. package/lib/features/magic-pdf/types.d.ts +1 -1
  96. package/lib/features/magic-pdf/types.d.ts.map +1 -1
  97. package/lib/features/oauth/McpConnectionsButton.d.ts.map +1 -1
  98. package/lib/features/oauth/McpConnectionsButton.js +2 -2
  99. package/lib/features/oauth/McpConnectionsButton.js.map +1 -1
  100. package/lib/features/oauth/McpConnectionsDialog.d.ts.map +1 -1
  101. package/lib/features/oauth/McpConnectionsDialog.js +1 -1
  102. package/lib/features/oauth/McpConnectionsDialog.js.map +1 -1
  103. package/lib/features/oauth/RemoteMcpConnectionButton.d.ts.map +1 -1
  104. package/lib/features/oauth/RemoteMcpConnectionButton.js +1 -1
  105. package/lib/features/oauth/RemoteMcpConnectionButton.js.map +1 -1
  106. package/lib/features/pdf-viewer/PdfPageSlider.js +2 -2
  107. package/lib/features/pdf-viewer/PdfPageSlider.js.map +1 -1
  108. package/lib/features/pdf-viewer/SimplePdfViewer.d.ts.map +1 -1
  109. package/lib/features/pdf-viewer/SimplePdfViewer.js +3 -3
  110. package/lib/features/pdf-viewer/SimplePdfViewer.js.map +1 -1
  111. package/lib/features/permissions/UserPermissionsProvider.d.ts +2 -3
  112. package/lib/features/permissions/UserPermissionsProvider.d.ts.map +1 -1
  113. package/lib/features/permissions/UserPermissionsProvider.js +31 -38
  114. package/lib/features/permissions/UserPermissionsProvider.js.map +1 -1
  115. package/lib/features/store/objects/DocumentQuickFilter.d.ts.map +1 -1
  116. package/lib/features/store/objects/DocumentQuickFilter.js +5 -5
  117. package/lib/features/store/objects/DocumentQuickFilter.js.map +1 -1
  118. package/lib/features/store/objects/DocumentSearchResults.d.ts.map +1 -1
  119. package/lib/features/store/objects/DocumentSearchResults.js +2 -2
  120. package/lib/features/store/objects/DocumentSearchResults.js.map +1 -1
  121. package/lib/features/store/objects/components/ContentDispositionButton.d.ts.map +1 -1
  122. package/lib/features/store/objects/components/ContentDispositionButton.js +1 -1
  123. package/lib/features/store/objects/components/ContentDispositionButton.js.map +1 -1
  124. package/lib/features/store/objects/components/ContentOverview.d.ts.map +1 -1
  125. package/lib/features/store/objects/components/ContentOverview.js +148 -31
  126. package/lib/features/store/objects/components/ContentOverview.js.map +1 -1
  127. package/lib/features/store/objects/components/DocumentIcon.js +1 -1
  128. package/lib/features/store/objects/components/VectorSearchWidget.js +1 -1
  129. package/lib/features/store/objects/components/VectorSearchWidget.js.map +1 -1
  130. package/lib/features/store/objects/components/useContentPanelHooks.d.ts +1 -2
  131. package/lib/features/store/objects/components/useContentPanelHooks.d.ts.map +1 -1
  132. package/lib/features/store/objects/components/useContentPanelHooks.js +4 -8
  133. package/lib/features/store/objects/components/useContentPanelHooks.js.map +1 -1
  134. package/lib/features/store/objects/layout/renderers.d.ts.map +1 -1
  135. package/lib/features/store/objects/layout/renderers.js +2 -2
  136. package/lib/features/store/objects/layout/renderers.js.map +1 -1
  137. package/lib/features/store/objects/selection/SelectionActions.js +1 -1
  138. package/lib/features/store/objects/selection/SelectionActions.js.map +1 -1
  139. package/lib/features/store/types/ContentObjectTypesTable.d.ts.map +1 -1
  140. package/lib/features/store/types/ContentObjectTypesTable.js +4 -4
  141. package/lib/features/store/types/ContentObjectTypesTable.js.map +1 -1
  142. package/lib/features/store/types/IntakePolicyEditor.d.ts +15 -0
  143. package/lib/features/store/types/IntakePolicyEditor.d.ts.map +1 -0
  144. package/lib/features/store/types/IntakePolicyEditor.js +455 -0
  145. package/lib/features/store/types/IntakePolicyEditor.js.map +1 -0
  146. package/lib/features/store/types/IntakePolicyForm.d.ts +11 -0
  147. package/lib/features/store/types/IntakePolicyForm.d.ts.map +1 -0
  148. package/lib/features/store/types/IntakePolicyForm.js +91 -0
  149. package/lib/features/store/types/IntakePolicyForm.js.map +1 -0
  150. package/lib/features/store/types/SelectContentTypeModal.js +1 -1
  151. package/lib/features/store/types/SelectContentTypeModal.js.map +1 -1
  152. package/lib/features/store/types/index.d.ts +2 -0
  153. package/lib/features/store/types/index.d.ts.map +1 -1
  154. package/lib/features/store/types/index.js +2 -0
  155. package/lib/features/store/types/index.js.map +1 -1
  156. package/lib/features/store/types/intake-policy-editor.logic.d.ts +7 -0
  157. package/lib/features/store/types/intake-policy-editor.logic.d.ts.map +1 -0
  158. package/lib/features/store/types/intake-policy-editor.logic.js +60 -0
  159. package/lib/features/store/types/intake-policy-editor.logic.js.map +1 -0
  160. package/lib/features/store/types/typeCatalogCache.d.ts +5 -0
  161. package/lib/features/store/types/typeCatalogCache.d.ts.map +1 -0
  162. package/lib/features/store/types/typeCatalogCache.js +40 -0
  163. package/lib/features/store/types/typeCatalogCache.js.map +1 -0
  164. package/lib/features/user/UserInfo.js +7 -3
  165. package/lib/features/user/UserInfo.js.map +1 -1
  166. package/lib/features/views/ViewExperience.d.ts +20 -0
  167. package/lib/features/views/ViewExperience.d.ts.map +1 -0
  168. package/lib/features/views/ViewExperience.js +188 -0
  169. package/lib/features/views/ViewExperience.js.map +1 -0
  170. package/lib/features/views/ViewNavigation.d.ts +3 -0
  171. package/lib/features/views/ViewNavigation.d.ts.map +1 -0
  172. package/lib/features/views/ViewNavigation.js +36 -0
  173. package/lib/features/views/ViewNavigation.js.map +1 -0
  174. package/lib/features/views/ViewResults.d.ts +9 -0
  175. package/lib/features/views/ViewResults.d.ts.map +1 -0
  176. package/lib/features/views/ViewResults.js +229 -0
  177. package/lib/features/views/ViewResults.js.map +1 -0
  178. package/lib/features/views/ViewSearch.d.ts +3 -0
  179. package/lib/features/views/ViewSearch.d.ts.map +1 -0
  180. package/lib/features/views/ViewSearch.js +74 -0
  181. package/lib/features/views/ViewSearch.js.map +1 -0
  182. package/lib/features/views/index.d.ts +7 -0
  183. package/lib/features/views/index.d.ts.map +1 -0
  184. package/lib/features/views/index.js +7 -0
  185. package/lib/features/views/index.js.map +1 -0
  186. package/lib/features/views/types.d.ts +39 -0
  187. package/lib/features/views/types.d.ts.map +1 -0
  188. package/lib/features/views/types.js +2 -0
  189. package/lib/features/views/types.js.map +1 -0
  190. package/lib/features/views/viewState.d.ts +14 -0
  191. package/lib/features/views/viewState.d.ts.map +1 -0
  192. package/lib/features/views/viewState.js +117 -0
  193. package/lib/features/views/viewState.js.map +1 -0
  194. package/lib/i18n/locales/ar.json +18 -0
  195. package/lib/i18n/locales/de.json +14 -0
  196. package/lib/i18n/locales/en.json +246 -32
  197. package/lib/i18n/locales/es.json +15 -0
  198. package/lib/i18n/locales/fr.json +15 -0
  199. package/lib/i18n/locales/it.json +15 -0
  200. package/lib/i18n/locales/ja.json +13 -0
  201. package/lib/i18n/locales/ko.json +13 -0
  202. package/lib/i18n/locales/pt.json +15 -0
  203. package/lib/i18n/locales/ru.json +16 -0
  204. package/lib/i18n/locales/tr.json +14 -0
  205. package/lib/i18n/locales/zh-TW.json +13 -0
  206. package/lib/i18n/locales/zh.json +13 -0
  207. package/lib/router/HistoryNavigator.d.ts.map +1 -1
  208. package/lib/router/HistoryNavigator.js +27 -14
  209. package/lib/router/HistoryNavigator.js.map +1 -1
  210. package/lib/router/Nav.d.ts.map +1 -1
  211. package/lib/router/Nav.js +21 -6
  212. package/lib/router/Nav.js.map +1 -1
  213. package/lib/router/Router.d.ts.map +1 -1
  214. package/lib/router/Router.js +19 -4
  215. package/lib/router/Router.js.map +1 -1
  216. package/lib/router/path.d.ts +15 -0
  217. package/lib/router/path.d.ts.map +1 -1
  218. package/lib/router/path.js +43 -0
  219. package/lib/router/path.js.map +1 -1
  220. package/lib/session/UserSession.d.ts +6 -0
  221. package/lib/session/UserSession.d.ts.map +1 -1
  222. package/lib/session/UserSession.js +36 -12
  223. package/lib/session/UserSession.js.map +1 -1
  224. package/lib/session/UserSessionProvider.d.ts.map +1 -1
  225. package/lib/session/UserSessionProvider.js +111 -49
  226. package/lib/session/UserSessionProvider.js.map +1 -1
  227. package/lib/session/auth/composable.d.ts.map +1 -1
  228. package/lib/session/auth/composable.js +52 -3
  229. package/lib/session/auth/composable.js.map +1 -1
  230. package/lib/session/auth/domainRouting.d.ts +25 -0
  231. package/lib/session/auth/domainRouting.d.ts.map +1 -1
  232. package/lib/session/auth/domainRouting.js +36 -0
  233. package/lib/session/auth/domainRouting.js.map +1 -1
  234. package/lib/shell/login/SignInRestrictedEnvStep.d.ts +1 -1
  235. package/lib/shell/login/SignInRestrictedEnvStep.js +1 -1
  236. package/lib/shell/login/SigninScreen.js +9 -1
  237. package/lib/shell/login/SigninScreen.js.map +1 -1
  238. package/lib/shell/login/productionUrl.d.ts +0 -14
  239. package/lib/shell/login/productionUrl.d.ts.map +1 -1
  240. package/lib/shell/login/productionUrl.js +14 -5
  241. package/lib/shell/login/productionUrl.js.map +1 -1
  242. package/lib/vertesia-ui-core.js +4 -4
  243. package/lib/vertesia-ui-core.js.map +1 -1
  244. package/lib/vertesia-ui-env.js +1 -1
  245. package/lib/vertesia-ui-env.js.map +1 -1
  246. package/lib/vertesia-ui-features.js +35 -28
  247. package/lib/vertesia-ui-features.js.map +1 -1
  248. package/lib/vertesia-ui-i18n.js +15 -15
  249. package/lib/vertesia-ui-i18n.js.map +1 -1
  250. package/lib/vertesia-ui-router.js +1 -1
  251. package/lib/vertesia-ui-router.js.map +1 -1
  252. package/lib/vertesia-ui-session.js +1 -1
  253. package/lib/vertesia-ui-session.js.map +1 -1
  254. package/lib/vertesia-ui-shell.js +1 -1
  255. package/lib/vertesia-ui-shell.js.map +1 -1
  256. package/lib/vertesia-ui-widgets.js +7 -7
  257. package/lib/vertesia-ui-widgets.js.map +1 -1
  258. package/lib/widgets/form/Form.js +1 -1
  259. package/lib/widgets/form/Form.js.map +1 -1
  260. package/lib/widgets/json-view/JSONSwitcher.js +1 -1
  261. package/lib/widgets/json-view/JSONSwitcher.js.map +1 -1
  262. package/lib/widgets/json-view/JSONView.js +6 -6
  263. package/lib/widgets/json-view/JSONView.js.map +1 -1
  264. package/lib/widgets/monacoEditor/MonacoEditor.d.ts.map +1 -1
  265. package/lib/widgets/monacoEditor/MonacoEditor.js +29 -0
  266. package/lib/widgets/monacoEditor/MonacoEditor.js.map +1 -1
  267. package/lib/widgets/schema-editor/ManagedSchema.d.ts +8 -0
  268. package/lib/widgets/schema-editor/ManagedSchema.d.ts.map +1 -1
  269. package/lib/widgets/schema-editor/ManagedSchema.js +16 -0
  270. package/lib/widgets/schema-editor/ManagedSchema.js.map +1 -1
  271. package/lib/widgets/schema-editor/editor/EditableSchemaProperty.d.ts +2 -0
  272. package/lib/widgets/schema-editor/editor/EditableSchemaProperty.d.ts.map +1 -1
  273. package/lib/widgets/schema-editor/editor/EditableSchemaProperty.js +1 -0
  274. package/lib/widgets/schema-editor/editor/EditableSchemaProperty.js.map +1 -1
  275. package/lib/widgets/schema-editor/editor/PropertyViewer.d.ts.map +1 -1
  276. package/lib/widgets/schema-editor/editor/PropertyViewer.js +4 -1
  277. package/lib/widgets/schema-editor/editor/PropertyViewer.js.map +1 -1
  278. package/lib/widgets/schema-editor/editor/SchemaEditor.d.ts.map +1 -1
  279. package/lib/widgets/schema-editor/editor/SchemaEditor.js +22 -11
  280. package/lib/widgets/schema-editor/editor/SchemaEditor.js.map +1 -1
  281. package/llms.txt +70 -70
  282. package/package.json +33 -32
  283. package/src/__tests__/a11y.test.tsx +73 -0
  284. package/src/core/components/SidePanel.tsx +1 -1
  285. package/src/core/components/shadcn/button.tsx +67 -12
  286. package/src/core/components/shadcn/modal/dialog.tsx +1 -1
  287. package/src/core/components/shadcn/theme/ThemeSwitcher.tsx +1 -1
  288. package/src/env/index.ts +24 -0
  289. package/src/features/agent/chat/AgentChatPlaybackControls.tsx +0 -5
  290. package/src/features/agent/chat/AgentRequestInputOverlay.test.tsx +31 -0
  291. package/src/features/agent/chat/AgentRequestInputOverlay.tsx +6 -2
  292. package/src/features/agent/chat/ArtifactsTab.tsx +165 -21
  293. package/src/features/agent/chat/ImageLightbox.tsx +0 -1
  294. package/src/features/agent/chat/ModernAgentConversation.test.tsx +48 -0
  295. package/src/features/agent/chat/ModernAgentConversation.tsx +53 -51
  296. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.test.tsx +6 -1
  297. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +21 -15
  298. package/src/features/agent/chat/ModernAgentOutput/Header.tsx +3 -3
  299. package/src/features/agent/chat/ModernAgentOutput/MessageInput.tsx +0 -2
  300. package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +4 -1
  301. package/src/features/agent/chat/ModernAgentOutput/requestInputMessages.test.ts +80 -0
  302. package/src/features/agent/chat/ModernAgentOutput/requestInputMessages.ts +21 -1
  303. package/src/features/agent/chat/hooks/useArtifacts.ts +44 -11
  304. package/src/features/document-viewer/UniversalDocumentViewer.test.tsx +123 -0
  305. package/src/features/document-viewer/UniversalDocumentViewer.tsx +746 -0
  306. package/src/features/document-viewer/index.ts +1 -0
  307. package/src/features/environment/SelectEnvironment.tsx +186 -0
  308. package/src/features/environment/index.ts +1 -0
  309. package/src/features/index.ts +3 -0
  310. package/src/features/magic-pdf/AnnotatedImageSlider.tsx +62 -66
  311. package/src/features/magic-pdf/DownloadPopover.tsx +5 -48
  312. package/src/features/magic-pdf/ExtractedContentView.tsx +2 -14
  313. package/src/features/magic-pdf/GroundedExtractionAssistantPanel.tsx +86 -0
  314. package/src/features/magic-pdf/GroundedExtractionView.test.tsx +65 -0
  315. package/src/features/magic-pdf/GroundedExtractionView.tsx +1055 -0
  316. package/src/features/magic-pdf/MagicPdfProvider.tsx +17 -47
  317. package/src/features/magic-pdf/MagicPdfView.tsx +3 -51
  318. package/src/features/magic-pdf/index.ts +1 -0
  319. package/src/features/magic-pdf/types.ts +1 -1
  320. package/src/features/oauth/McpConnectionsButton.tsx +0 -2
  321. package/src/features/oauth/McpConnectionsDialog.tsx +0 -1
  322. package/src/features/oauth/RemoteMcpConnectionButton.tsx +0 -1
  323. package/src/features/pdf-viewer/PdfPageSlider.tsx +5 -5
  324. package/src/features/pdf-viewer/SimplePdfViewer.tsx +11 -12
  325. package/src/features/permissions/UserPermissionsProvider.tsx +36 -39
  326. package/src/features/store/objects/DocumentQuickFilter.tsx +13 -15
  327. package/src/features/store/objects/DocumentSearchResults.tsx +3 -6
  328. package/src/features/store/objects/components/ContentDispositionButton.tsx +5 -1
  329. package/src/features/store/objects/components/ContentOverview.tsx +240 -67
  330. package/src/features/store/objects/components/DocumentIcon.tsx +1 -1
  331. package/src/features/store/objects/components/VectorSearchWidget.tsx +2 -2
  332. package/src/features/store/objects/components/useContentPanelHooks.ts +3 -15
  333. package/src/features/store/objects/layout/renderers.tsx +1 -2
  334. package/src/features/store/objects/selection/SelectionActions.tsx +1 -1
  335. package/src/features/store/types/ContentObjectTypesTable.tsx +12 -14
  336. package/src/features/store/types/IntakePolicyEditor.tsx +679 -0
  337. package/src/features/store/types/IntakePolicyForm.tsx +838 -0
  338. package/src/features/store/types/SelectContentTypeModal.tsx +2 -2
  339. package/src/features/store/types/index.ts +2 -0
  340. package/src/features/store/types/intake-policy-editor.logic.test.ts +109 -0
  341. package/src/features/store/types/intake-policy-editor.logic.ts +87 -0
  342. package/src/features/store/types/typeCatalogCache.ts +45 -0
  343. package/src/features/user/UserInfo.tsx +7 -7
  344. package/src/features/views/ViewExperience.test.tsx +273 -0
  345. package/src/features/views/ViewExperience.tsx +408 -0
  346. package/src/features/views/ViewNavigation.test.tsx +86 -0
  347. package/src/features/views/ViewNavigation.tsx +187 -0
  348. package/src/features/views/ViewResults.test.ts +82 -0
  349. package/src/features/views/ViewResults.tsx +609 -0
  350. package/src/features/views/ViewSearch.test.tsx +147 -0
  351. package/src/features/views/ViewSearch.tsx +205 -0
  352. package/src/features/views/index.ts +6 -0
  353. package/src/features/views/types.ts +57 -0
  354. package/src/features/views/viewState.test.ts +111 -0
  355. package/src/features/views/viewState.ts +132 -0
  356. package/src/i18n/locales/ar.json +18 -0
  357. package/src/i18n/locales/de.json +14 -0
  358. package/src/i18n/locales/en.json +965 -751
  359. package/src/i18n/locales/es.json +15 -0
  360. package/src/i18n/locales/fr.json +15 -0
  361. package/src/i18n/locales/it.json +15 -0
  362. package/src/i18n/locales/ja.json +13 -0
  363. package/src/i18n/locales/ko.json +13 -0
  364. package/src/i18n/locales/pt.json +15 -0
  365. package/src/i18n/locales/ru.json +16 -0
  366. package/src/i18n/locales/tr.json +14 -0
  367. package/src/i18n/locales/zh-TW.json +13 -0
  368. package/src/i18n/locales/zh.json +13 -0
  369. package/src/router/HistoryNavigator.test.ts +48 -0
  370. package/src/router/HistoryNavigator.ts +28 -14
  371. package/src/router/Nav.tsx +24 -8
  372. package/src/router/Router.tsx +18 -4
  373. package/src/router/path.test.ts +87 -0
  374. package/src/router/path.ts +38 -0
  375. package/src/session/UserSession.test.ts +39 -0
  376. package/src/session/UserSession.ts +37 -12
  377. package/src/session/UserSessionProvider.tsx +138 -62
  378. package/src/session/auth/composable.test.ts +111 -0
  379. package/src/session/auth/composable.ts +58 -3
  380. package/src/session/auth/domainRouting.test.ts +28 -1
  381. package/src/session/auth/domainRouting.ts +38 -0
  382. package/src/shell/login/SignInRestrictedEnvStep.tsx +1 -1
  383. package/src/shell/login/SigninScreen.tsx +9 -1
  384. package/src/shell/login/productionUrl.test.ts +8 -5
  385. package/src/shell/login/productionUrl.ts +15 -5
  386. package/src/widgets/form/Form.tsx +1 -1
  387. package/src/widgets/json-view/JSONSwitcher.tsx +2 -2
  388. package/src/widgets/json-view/JSONView.tsx +7 -7
  389. package/src/widgets/monacoEditor/MonacoEditor.tsx +29 -0
  390. package/src/widgets/schema-editor/ManagedSchema.ts +22 -0
  391. package/src/widgets/schema-editor/editor/EditableSchemaProperty.tsx +3 -0
  392. package/src/widgets/schema-editor/editor/PropertyViewer.tsx +14 -3
  393. package/src/widgets/schema-editor/editor/SchemaEditor.tsx +80 -29
@@ -0,0 +1,87 @@
1
+ import { afterEach, describe, expect, it } from 'vitest';
2
+ import { getMountBasename, joinPath, stripMountBasename, withMountBasename } from './path';
3
+
4
+ // Simulate a served `<base href>` (or its absence) by stubbing document.querySelector + document.URL.
5
+ function setBaseHref(href: string | null, docUrl = 'https://gw.example.com/x') {
6
+ (globalThis as { document?: unknown }).document = {
7
+ URL: docUrl,
8
+ querySelector: (sel: string) =>
9
+ sel === 'base[href]' && href ? ({ href: new URL(href, docUrl).href } as unknown) : null,
10
+ };
11
+ }
12
+
13
+ const MOUNT = '/tenants/05948c_5ed5f4/apps/furniture-catalog/versions/20260620T064257113Z/app';
14
+
15
+ describe('mount basename helpers', () => {
16
+ afterEach(() => {
17
+ delete (globalThis as { document?: unknown }).document;
18
+ });
19
+
20
+ describe('getMountBasename', () => {
21
+ it('returns the base path (no trailing slash) when a deep <base href> is present', () => {
22
+ setBaseHref(`${MOUNT}/`);
23
+ expect(getMountBasename()).toBe(MOUNT);
24
+ });
25
+ it('returns "" for the Studio UI (no <base> element)', () => {
26
+ setBaseHref(null);
27
+ expect(getMountBasename()).toBe('');
28
+ });
29
+ it('returns "" for a root <base href="/">', () => {
30
+ setBaseHref('/');
31
+ expect(getMountBasename()).toBe('');
32
+ });
33
+ it('returns "" when there is no document (SSR)', () => {
34
+ delete (globalThis as { document?: unknown }).document;
35
+ expect(getMountBasename()).toBe('');
36
+ });
37
+ });
38
+
39
+ describe('withMountBasename (navigate side)', () => {
40
+ it('prepends the mount to an absolute app route', () => {
41
+ setBaseHref(`${MOUNT}/`);
42
+ expect(withMountBasename('/assistant')).toBe(`${MOUNT}/assistant`);
43
+ expect(withMountBasename('/')).toBe(`${MOUNT}/`);
44
+ });
45
+ it('does not double-prepend a path already under the mount', () => {
46
+ setBaseHref(`${MOUNT}/`);
47
+ expect(withMountBasename(`${MOUNT}/items/5`)).toBe(`${MOUNT}/items/5`);
48
+ expect(withMountBasename(MOUNT)).toBe(MOUNT);
49
+ });
50
+ it('leaves relative paths untouched', () => {
51
+ setBaseHref(`${MOUNT}/`);
52
+ expect(withMountBasename('assistant')).toBe('assistant');
53
+ });
54
+ it('is a no-op for the Studio UI (no mount)', () => {
55
+ setBaseHref(null);
56
+ expect(withMountBasename('/assistant')).toBe('/assistant');
57
+ expect(withMountBasename('/')).toBe('/');
58
+ });
59
+ });
60
+
61
+ describe('stripMountBasename (match side)', () => {
62
+ it('strips the mount so route patterns match app-relative', () => {
63
+ setBaseHref(`${MOUNT}/`);
64
+ expect(stripMountBasename(`${MOUNT}/assistant`)).toBe('/assistant');
65
+ expect(stripMountBasename(MOUNT)).toBe('/');
66
+ expect(stripMountBasename(`${MOUNT}/`)).toBe('/');
67
+ });
68
+ it('leaves a path not under the mount untouched', () => {
69
+ setBaseHref(`${MOUNT}/`);
70
+ expect(stripMountBasename('/somewhere-else')).toBe('/somewhere-else');
71
+ });
72
+ it('is a no-op for the Studio UI (no mount)', () => {
73
+ setBaseHref(null);
74
+ expect(stripMountBasename('/agents/123')).toBe('/agents/123');
75
+ expect(stripMountBasename('/')).toBe('/');
76
+ });
77
+ });
78
+
79
+ it('round-trips: strip(with(route)) === route under a mount', () => {
80
+ setBaseHref(`${MOUNT}/`);
81
+ for (const route of ['/', '/assistant', '/items/5']) {
82
+ expect(stripMountBasename(withMountBasename(route))).toBe(route === '/' ? '/' : route);
83
+ }
84
+ // sanity: joinPath stays available/correct
85
+ expect(joinPath(MOUNT, '/assistant')).toBe(`${MOUNT}/assistant`);
86
+ });
87
+ });
@@ -19,6 +19,44 @@ export function joinPath(path1: string, path2: string) {
19
19
  return path2;
20
20
  }
21
21
 
22
+ /**
23
+ * The mount base path from an explicit `<base href>` element, or `''` when there is none.
24
+ *
25
+ * Apps served under a deep gateway mount carry `<base href="/tenants/.../app/">`, injected at serve
26
+ * time. The router otherwise resolves absolute routes against the origin, which collapses the URL
27
+ * off the mount (and breaks in-app links). This returns the base pathname without a trailing slash
28
+ * so {@link withMountBasename} can keep navigation under the mount and {@link stripMountBasename}
29
+ * can match routes app-relative. The Studio UI has no `<base>` element, so this returns `''` and
30
+ * every caller becomes a no-op there.
31
+ */
32
+ export function getMountBasename(): string {
33
+ if (typeof document === 'undefined') return '';
34
+ const base = document.querySelector('base[href]') as HTMLBaseElement | null;
35
+ if (!base) return '';
36
+ try {
37
+ return new URL(base.href, document.URL).pathname.replace(/\/+$/, '');
38
+ } catch {
39
+ return '';
40
+ }
41
+ }
42
+
43
+ /** Prepend the mount basename to an absolute app path (no-op when no mount, path is relative, or already prefixed). */
44
+ export function withMountBasename(path: string): string {
45
+ const base = getMountBasename();
46
+ if (!base || !path.startsWith('/')) return path;
47
+ if (path === base || path.startsWith(`${base}/`)) return path;
48
+ return joinPath(base, path);
49
+ }
50
+
51
+ /** Strip the mount basename from a browser path so route patterns match app-relative (no-op when no mount). */
52
+ export function stripMountBasename(path: string): string {
53
+ const base = getMountBasename();
54
+ if (!base) return path;
55
+ if (path === base) return '/';
56
+ if (path.startsWith(`${base}/`)) return path.slice(base.length) || '/';
57
+ return path;
58
+ }
59
+
22
60
  export function getPathSegments(path: string) {
23
61
  if (path === '') {
24
62
  return [];
@@ -0,0 +1,39 @@
1
+ import type { VertesiaClient } from '@vertesia/client';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import { UserSession } from './UserSession';
4
+
5
+ function createSession(onboardingProgress: () => Promise<Record<string, boolean>>) {
6
+ const client = {
7
+ account: { onboardingProgress },
8
+ } as unknown as VertesiaClient;
9
+ const session = new UserSession(client);
10
+ session.setSession = vi.fn();
11
+ return session;
12
+ }
13
+
14
+ describe('UserSession.fetchOnboardingStatus', () => {
15
+ it('does not replace the session when an incomplete status is unchanged', async () => {
16
+ const session = createSession(async () => ({ project_created: false }));
17
+ session.onboardingComplete = false;
18
+
19
+ await expect(session.fetchOnboardingStatus()).resolves.toBe(false);
20
+ expect(session.setSession).not.toHaveBeenCalled();
21
+ });
22
+
23
+ it('updates the session and reports when onboarding becomes complete', async () => {
24
+ const session = createSession(async () => ({ project_created: true }));
25
+ session.onboardingComplete = false;
26
+
27
+ await expect(session.fetchOnboardingStatus()).resolves.toBe(true);
28
+ expect(session.setSession).toHaveBeenCalledOnce();
29
+ expect(vi.mocked(session.setSession!).mock.calls[0]?.[0]?.onboardingComplete).toBe(true);
30
+ });
31
+
32
+ it('publishes the initial incomplete status without reporting completion', async () => {
33
+ const session = createSession(async () => ({ project_created: false }));
34
+
35
+ await expect(session.fetchOnboardingStatus()).resolves.toBe(false);
36
+ expect(session.setSession).toHaveBeenCalledOnce();
37
+ expect(vi.mocked(session.setSession!).mock.calls[0]?.[0]?.onboardingComplete).toBe(false);
38
+ });
39
+ });
@@ -5,7 +5,7 @@ import { jwtDecode } from 'jwt-decode';
5
5
  import { createContext, useContext } from 'react';
6
6
 
7
7
  import { getComposableToken } from './auth/composable';
8
- import { shouldRedirectToCentralAuth } from './auth/domainRouting';
8
+ import { authReturnUrl, mountRootUrl, shouldRedirectToCentralAuth } from './auth/domainRouting';
9
9
  import { getFirebaseAuth } from './auth/firebase';
10
10
 
11
11
  import { LastSelectedAccountId_KEY, LastSelectedProjectId_KEY } from './constants';
@@ -84,6 +84,22 @@ class UserSession {
84
84
  });
85
85
  }
86
86
 
87
+ /**
88
+ * Force a fresh Vertesia JWT from STS, bypassing the in-memory cache.
89
+ * Use this when the current token's claims (e.g. `apps`) are suspected
90
+ * stale — STS recomputes the `apps` claim from ACEs on every issuance.
91
+ */
92
+ async refreshAuthToken(): Promise<AuthTokenPayload> {
93
+ const res = await getComposableToken(undefined, undefined, undefined, true);
94
+ const token = res?.rawToken;
95
+ if (!token) {
96
+ throw new Error('No token available');
97
+ }
98
+ this.authToken = jwtDecode(token) as unknown as AuthTokenPayload;
99
+ this.setSession?.(this.clone());
100
+ return this.authToken;
101
+ }
102
+
87
103
  signOut() {
88
104
  //compatibility
89
105
  this.logout();
@@ -136,8 +152,7 @@ class UserSession {
136
152
  this.client.withAuthCallback(undefined);
137
153
 
138
154
  const logoutUrl = new URL(CENTRAL_AUTH_REDIRECT);
139
- const currentUrl = new URL(window.location.href);
140
- currentUrl.hash = '';
155
+ const currentUrl = authReturnUrl();
141
156
  logoutUrl.pathname = '/logout';
142
157
  logoutUrl.searchParams.set('redirect_uri', currentUrl.toString());
143
158
  location.replace(logoutUrl.toString());
@@ -153,12 +168,13 @@ class UserSession {
153
168
  this.authToken = undefined;
154
169
  this.setSession = undefined;
155
170
  this.client.withAuthCallback(undefined);
156
- // Navigate to root to avoid React rendering errors when
171
+ // Navigate to the app root to avoid React rendering errors when
157
172
  // unmounting deeply nested route components during logout.
158
- // Only navigate if user was actually logged in to avoid
159
- // infinite reload loop on fresh/incognito sessions.
173
+ // Use the mount root (not bare '/') so a gateway-mounted app stays
174
+ // on its mount. Only navigate if user was actually logged in to
175
+ // avoid an infinite reload loop on fresh/incognito sessions.
160
176
  if (wasLoggedIn) {
161
- location.replace('/');
177
+ location.replace(mountRootUrl().toString());
162
178
  }
163
179
  }
164
180
  }
@@ -173,7 +189,9 @@ class UserSession {
173
189
  }
174
190
  }
175
191
 
176
- window.location.replace(`/?a=${targetAccountId}`);
192
+ const url = mountRootUrl();
193
+ url.searchParams.set('a', targetAccountId);
194
+ window.location.replace(url.toString());
177
195
  }
178
196
 
179
197
  async switchProject(targetProjectId: string) {
@@ -181,7 +199,12 @@ class UserSession {
181
199
  localStorage.setItem(`${LastSelectedProjectId_KEY}-${this.account.id}`, targetProjectId);
182
200
  }
183
201
 
184
- window.location.replace(`/?a=${this.account?.id}&p=${targetProjectId}`);
202
+ const url = mountRootUrl();
203
+ if (this.account?.id) {
204
+ url.searchParams.set('a', this.account.id);
205
+ }
206
+ url.searchParams.set('p', targetProjectId);
207
+ window.location.replace(url.toString());
185
208
  }
186
209
 
187
210
  async fetchAccounts() {
@@ -237,13 +260,15 @@ class UserSession {
237
260
  const onboarding = await this.client.account.onboardingProgress();
238
261
  this.onboardingComplete = Object.values(onboarding).every((value) => value === true);
239
262
  if (previousStatus !== this.onboardingComplete) {
240
- return true;
263
+ this.setSession?.(this.clone());
241
264
  }
242
- this.setSession?.(this.clone());
265
+ return previousStatus === false && this.onboardingComplete;
243
266
  } catch (error) {
244
267
  console.error('Error fetching onboarding status:', error);
245
268
  this.onboardingComplete = false;
246
- this.setSession?.(this.clone());
269
+ if (previousStatus !== this.onboardingComplete) {
270
+ this.setSession?.(this.clone());
271
+ }
247
272
  }
248
273
  return false;
249
274
  }
@@ -2,13 +2,19 @@ import { Env } from '@vertesia/ui/env';
2
2
  import { onAuthStateChanged } from 'firebase/auth';
3
3
  import { type ReactNode, useEffect, useRef, useState } from 'react';
4
4
  import { getComposableToken, RestrictedEnvironmentError, STSError, UserNotFoundError } from './auth/composable';
5
- import { shouldRedirectToCentralAuth } from './auth/domainRouting';
5
+ import { authReturnUrl, shouldRedirectToCentralAuth } from './auth/domainRouting';
6
6
  import { getFirebaseAuth } from './auth/firebase';
7
7
  import { useAuthState } from './auth/useAuthState';
8
8
  import { LastSelectedAccountId_KEY, LastSelectedProjectId_KEY, UserSession, UserSessionContext } from './UserSession';
9
9
 
10
10
  const CENTRAL_AUTH_REDIRECT = 'https://internal-auth.vertesia.app/';
11
11
 
12
+ function clearAuthHash() {
13
+ const url = new URL(window.location.href);
14
+ url.hash = '';
15
+ window.history.replaceState(window.history.state, '', `${url.pathname}${url.search}`);
16
+ }
17
+
12
18
  interface UserSessionProviderProps {
13
19
  children: ReactNode | ReactNode[];
14
20
  loadOnboardingStatus?: boolean;
@@ -24,8 +30,7 @@ export function UserSessionProvider({ children, loadOnboardingStatus = true }: U
24
30
 
25
31
  const redirectToCentralAuth = (projectId?: string, accountId?: string) => {
26
32
  const url = new URL(`${CENTRAL_AUTH_REDIRECT}?sts=${Env.endpoints.sts ?? 'https://sts.vertesia.io'}`);
27
- const currentUrl = new URL(window.location.href);
28
- currentUrl.hash = '';
33
+ const currentUrl = authReturnUrl();
29
34
  if (projectId) currentUrl.searchParams.set('p', projectId);
30
35
  if (accountId) currentUrl.searchParams.set('a', accountId);
31
36
  url.searchParams.set('redirect_uri', currentUrl.toString());
@@ -59,6 +64,28 @@ export function UserSessionProvider({ children, loadOnboardingStatus = true }: U
59
64
  },
60
65
  });
61
66
 
67
+ if (Env.isLocalDev && Env.devAuthToken) {
68
+ session.setSession = setSession;
69
+ getComposableToken(selectedAccount, selectedProject, Env.devAuthToken)
70
+ .then((res) => {
71
+ session.login(res.rawToken).then(() => setSession(session.clone()));
72
+ })
73
+ .catch((err) => {
74
+ console.error('Failed to initialize dev auth token', err);
75
+ Env.logger.error('Failed to initialize dev auth token', {
76
+ vertesia: {
77
+ account_id: selectedAccount,
78
+ project_id: selectedProject,
79
+ error: err,
80
+ },
81
+ });
82
+ session.isLoading = false;
83
+ session.authError = err instanceof Error ? err : new Error(String(err));
84
+ setSession(session.clone());
85
+ });
86
+ return;
87
+ }
88
+
62
89
  if (token && state) {
63
90
  session.setSession = setSession;
64
91
  const validationError = verifyState(state);
@@ -77,8 +104,7 @@ export function UserSessionProvider({ children, loadOnboardingStatus = true }: U
77
104
  .then((res) => {
78
105
  session.login(res.rawToken, { loadOnboardingStatus }).then(() => {
79
106
  setSession(session.clone());
80
- //cleanup the hash
81
- window.location.hash = '';
107
+ clearAuthHash();
82
108
  });
83
109
  })
84
110
  .catch((err) => {
@@ -117,7 +143,7 @@ export function UserSessionProvider({ children, loadOnboardingStatus = true }: U
117
143
  );
118
144
  session.logout();
119
145
  setSession(session.clone());
120
- window.location.hash = '';
146
+ clearAuthHash();
121
147
  return;
122
148
  }
123
149
 
@@ -130,7 +156,14 @@ export function UserSessionProvider({ children, loadOnboardingStatus = true }: U
130
156
  redirectToCentralAuth();
131
157
  });
132
158
  return;
133
- } else {
159
+ }
160
+
161
+ let cancelled = false;
162
+ let unsubscribe: (() => void) | undefined;
163
+
164
+ const startFirebaseOrCentralAuth = () => {
165
+ if (cancelled) return;
166
+
134
167
  // If the current host is not in the Firebase allowlist, central auth owns sign-in.
135
168
  if (!session.isLoggedIn()) {
136
169
  console.log('Auth: not logged in & no token/state');
@@ -154,71 +187,114 @@ export function UserSessionProvider({ children, loadOnboardingStatus = true }: U
154
187
  });
155
188
  redirectToCentralAuth();
156
189
  return; // Don't register onAuthStateChanged listener when redirecting
157
- } else {
158
- console.log('Auth: host is in Firebase auth allowlist');
159
- Env.logger.info('Host is in Firebase auth allowlist', {
160
- vertesia: {
161
- account_id: selectedAccount,
162
- project_id: selectedProject,
163
- },
164
- });
165
190
  }
166
- }
167
- }
168
191
 
169
- return onAuthStateChanged(getFirebaseAuth(), async (firebaseUser) => {
170
- if (firebaseUser) {
171
- console.log('Auth: successful login with firebase');
172
- Env.logger.info('Successful login with firebase', {
192
+ console.log('Auth: host is in Firebase auth allowlist');
193
+ Env.logger.info('Host is in Firebase auth allowlist', {
173
194
  vertesia: {
174
195
  account_id: selectedAccount,
175
196
  project_id: selectedProject,
176
197
  },
177
198
  });
178
- session.setSession = setSession;
179
- await getComposableToken(
180
- selectedAccount,
181
- selectedProject,
182
- undefined,
183
- false,
184
- shouldRedirectToCentralAuth(),
185
- )
186
- .then((res) => {
187
- session.login(res.rawToken, { loadOnboardingStatus }).then(() => setSession(session.clone()));
188
- })
189
- .catch((err) => {
190
- console.error('Failed to fetch user token from studio', err);
191
- Env.logger.error('Failed to fetch user token from studio', {
192
- vertesia: {
193
- account_id: selectedAccount,
194
- project_id: selectedProject,
195
- error: err,
196
- },
199
+ }
200
+
201
+ unsubscribe = onAuthStateChanged(getFirebaseAuth(), async (firebaseUser) => {
202
+ if (firebaseUser) {
203
+ console.log('Auth: successful login with firebase');
204
+ Env.logger.info('Successful login with firebase', {
205
+ vertesia: {
206
+ account_id: selectedAccount,
207
+ project_id: selectedProject,
208
+ },
209
+ });
210
+ session.setSession = setSession;
211
+ await getComposableToken(
212
+ selectedAccount,
213
+ selectedProject,
214
+ undefined,
215
+ false,
216
+ shouldRedirectToCentralAuth(),
217
+ )
218
+ .then((res) => {
219
+ session
220
+ .login(res.rawToken, { loadOnboardingStatus })
221
+ .then(() => setSession(session.clone()));
222
+ })
223
+ .catch((err) => {
224
+ console.error('Failed to fetch user token from studio', err);
225
+ Env.logger.error('Failed to fetch user token from studio', {
226
+ vertesia: {
227
+ account_id: selectedAccount,
228
+ project_id: selectedProject,
229
+ error: err,
230
+ },
231
+ });
232
+ // Keep the Firebase session for UserNotFoundError (signup flow) and
233
+ // RestrictedEnvironmentError (identity is valid; the environment is gated).
234
+ // Logging out would retrigger onAuthStateChanged and clear the authError.
235
+ if (!(err instanceof UserNotFoundError) && !(err instanceof RestrictedEnvironmentError)) {
236
+ session.logout();
237
+ }
238
+ session.isLoading = false;
239
+ session.authError = err;
240
+ setSession(session.clone());
197
241
  });
198
- // Keep the Firebase session for UserNotFoundError (signup flow) and
199
- // RestrictedEnvironmentError (identity is valid; the environment is gated).
200
- // Logging out would retrigger onAuthStateChanged and clear the authError.
201
- if (!(err instanceof UserNotFoundError) && !(err instanceof RestrictedEnvironmentError)) {
202
- session.logout();
203
- }
204
- session.isLoading = false;
205
- session.authError = err;
206
- setSession(session.clone());
242
+ } else {
243
+ // anonymous user
244
+ console.log('Auth: using anonymous user');
245
+ Env.logger.info('Using anonymous user', {
246
+ vertesia: {
247
+ account_id: selectedAccount,
248
+ project_id: selectedProject,
249
+ },
207
250
  });
208
- } else {
209
- // anonymous user
210
- console.log('Auth: using anonymous user');
211
- Env.logger.info('Using anonymous user', {
212
- vertesia: {
213
- account_id: selectedAccount,
214
- project_id: selectedProject,
215
- },
251
+ session.client.withAuthCallback(undefined);
252
+ session.logout();
253
+ setSession(session.clone());
254
+ }
255
+ });
256
+ };
257
+
258
+ if (Env.authTokenProvider) {
259
+ session.setSession = setSession;
260
+ void Env.authTokenProvider()
261
+ .then(async (injectedToken) => {
262
+ if (!injectedToken) {
263
+ startFirebaseOrCentralAuth();
264
+ return;
265
+ }
266
+ const res = await getComposableToken(
267
+ selectedAccount,
268
+ selectedProject,
269
+ injectedToken,
270
+ false,
271
+ shouldRedirectToCentralAuth(),
272
+ );
273
+ await session.login(res.rawToken, { loadOnboardingStatus });
274
+ if (!cancelled) setSession(session.clone());
275
+ })
276
+ .catch((err: unknown) => {
277
+ console.warn('Auth: failed to initialize injected auth token', err);
278
+ Env.logger.warn('Failed to initialize injected auth token', {
279
+ vertesia: {
280
+ account_id: selectedAccount,
281
+ project_id: selectedProject,
282
+ error: err,
283
+ },
284
+ });
285
+ startFirebaseOrCentralAuth();
216
286
  });
217
- session.client.withAuthCallback(undefined);
218
- session.logout();
219
- setSession(session.clone());
220
- }
221
- });
287
+ return () => {
288
+ cancelled = true;
289
+ unsubscribe?.();
290
+ };
291
+ }
292
+
293
+ startFirebaseOrCentralAuth();
294
+ return () => {
295
+ cancelled = true;
296
+ unsubscribe?.();
297
+ };
222
298
  };
223
299
 
224
300
  useEffect(() => {
@@ -0,0 +1,111 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ function makeJwt(payload: Record<string, unknown>) {
4
+ const encode = (value: unknown) => Buffer.from(JSON.stringify(value)).toString('base64url');
5
+ return `${encode({ alg: 'ES256', typ: 'JWT' })}.${encode(payload)}.signature`;
6
+ }
7
+
8
+ async function importComposableAuth() {
9
+ vi.resetModules();
10
+ const [{ Env }, composableAuth] = await Promise.all([import('@vertesia/ui/env'), import('./composable')]);
11
+ Env.init({
12
+ name: 'test',
13
+ version: '0.0.0',
14
+ isLocalDev: false,
15
+ isDocker: false,
16
+ type: 'test',
17
+ endpoints: {
18
+ studio: 'https://studio-server-dev-feat-appgen.api.dev1.vertesia.io',
19
+ zeno: 'https://zeno-server-dev-feat-appgen.api.dev1.vertesia.io',
20
+ sts: 'https://sts.dev1.vertesia.io',
21
+ },
22
+ });
23
+ return composableAuth;
24
+ }
25
+
26
+ describe('getComposableToken', () => {
27
+ beforeEach(() => {
28
+ localStorage.clear();
29
+ });
30
+
31
+ afterEach(() => {
32
+ vi.unstubAllGlobals();
33
+ });
34
+
35
+ it('uses an authorization-bearing STS-issued Vertesia token directly instead of exchanging it', async () => {
36
+ const token = makeJwt({
37
+ iss: 'https://sts.dev1.vertesia.io',
38
+ exp: Math.floor(Date.now() / 1000) + 3600,
39
+ account: { id: 'account-id', name: 'Account' },
40
+ account_roles: [],
41
+ project: { id: 'project-id', name: 'Project', account: 'account-id' },
42
+ project_roles: ['developer'],
43
+ });
44
+ const fetchMock = vi.fn();
45
+ vi.stubGlobal('fetch', fetchMock);
46
+
47
+ const { getComposableToken } = await importComposableAuth();
48
+ const result = await getComposableToken('account-id', 'project-id', token, false, true);
49
+
50
+ expect(result.rawToken).toBe(token);
51
+ expect(result.token.iss).toBe('https://sts.dev1.vertesia.io');
52
+ expect(fetchMock).not.toHaveBeenCalled();
53
+ });
54
+
55
+ it('exchanges a bare STS-issued Vertesia token so roles can be hydrated', async () => {
56
+ const sourceToken = makeJwt({
57
+ iss: 'https://sts.dev1.vertesia.io',
58
+ exp: Math.floor(Date.now() / 1000) + 3600,
59
+ account: { id: 'account-id', name: 'Account' },
60
+ project: { id: 'project-id', name: 'Project', account: 'account-id' },
61
+ });
62
+ const exchangedToken = makeJwt({
63
+ iss: 'https://sts.dev1.vertesia.io',
64
+ exp: Math.floor(Date.now() / 1000) + 3600,
65
+ account: { id: 'account-id', name: 'Account' },
66
+ account_roles: [],
67
+ project: { id: 'project-id', name: 'Project', account: 'account-id' },
68
+ project_roles: ['admin'],
69
+ });
70
+ const fetchMock = vi.fn().mockResolvedValue({
71
+ ok: true,
72
+ status: 200,
73
+ json: () => Promise.resolve({ token: exchangedToken }),
74
+ });
75
+ vi.stubGlobal('fetch', fetchMock);
76
+
77
+ const { getComposableToken } = await importComposableAuth();
78
+ const result = await getComposableToken('account-id', 'project-id', sourceToken, false, true);
79
+
80
+ expect(result.rawToken).toBe(exchangedToken);
81
+ expect(fetchMock).toHaveBeenCalledOnce();
82
+ expect(fetchMock.mock.calls[0]?.[0]?.toString()).toBe('https://sts.dev1.vertesia.io/token/issue');
83
+ });
84
+
85
+ it('still exchanges a non-STS source token', async () => {
86
+ const sourceToken = makeJwt({
87
+ iss: 'https://securetoken.google.com/example',
88
+ exp: Math.floor(Date.now() / 1000) + 3600,
89
+ email: 'user@example.com',
90
+ });
91
+ const exchangedToken = makeJwt({
92
+ iss: 'https://sts.dev1.vertesia.io',
93
+ exp: Math.floor(Date.now() / 1000) + 3600,
94
+ account: { id: 'account-id', name: 'Account' },
95
+ project: { id: 'project-id', name: 'Project', account: 'account-id' },
96
+ });
97
+ const fetchMock = vi.fn().mockResolvedValue({
98
+ ok: true,
99
+ status: 200,
100
+ json: () => Promise.resolve({ token: exchangedToken }),
101
+ });
102
+ vi.stubGlobal('fetch', fetchMock);
103
+
104
+ const { getComposableToken } = await importComposableAuth();
105
+ const result = await getComposableToken('account-id', 'project-id', sourceToken, false, true);
106
+
107
+ expect(result.rawToken).toBe(exchangedToken);
108
+ expect(fetchMock).toHaveBeenCalledOnce();
109
+ expect(fetchMock.mock.calls[0]?.[0]?.toString()).toBe('https://sts.dev1.vertesia.io/token/issue');
110
+ });
111
+ });