@vertesia/ui 1.6.0-dev.20260912.110338Z → 1.6.0-dev.20260918.063824Z

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 (89) hide show
  1. package/lib/env/index.d.ts +10 -4
  2. package/lib/env/index.d.ts.map +1 -1
  3. package/lib/env/index.js +8 -0
  4. package/lib/env/index.js.map +1 -1
  5. package/lib/features/agent/PayloadBuilder.d.ts.map +1 -1
  6. package/lib/features/agent/PayloadBuilder.js +6 -1
  7. package/lib/features/agent/PayloadBuilder.js.map +1 -1
  8. package/lib/features/agent/chat/AgentRunFeedback.d.ts +7 -0
  9. package/lib/features/agent/chat/AgentRunFeedback.d.ts.map +1 -1
  10. package/lib/features/agent/chat/AgentRunFeedback.js +13 -1
  11. package/lib/features/agent/chat/AgentRunFeedback.js.map +1 -1
  12. package/lib/features/agent/chat/ModernAgentConversation.js +4 -3
  13. package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
  14. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +2 -2
  15. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
  16. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js +2 -2
  17. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
  18. package/lib/features/agent/chat/hooks/useAgentPlans.d.ts +1 -1
  19. package/lib/features/agent/chat/hooks/useAgentPlans.d.ts.map +1 -1
  20. package/lib/features/agent/chat/hooks/useAgentPlans.js +5 -5
  21. package/lib/features/agent/chat/hooks/useAgentPlans.js.map +1 -1
  22. package/lib/features/store/objects/search/DocumentSearchContext.d.ts.map +1 -1
  23. package/lib/features/store/objects/search/DocumentSearchContext.js +6 -1
  24. package/lib/features/store/objects/search/DocumentSearchContext.js.map +1 -1
  25. package/lib/features/user/UserInfo.d.ts +2 -0
  26. package/lib/features/user/UserInfo.d.ts.map +1 -1
  27. package/lib/session/UserSessionProvider.js +3 -3
  28. package/lib/session/UserSessionProvider.js.map +1 -1
  29. package/lib/session/auth/domainRouting.d.ts +3 -4
  30. package/lib/session/auth/domainRouting.d.ts.map +1 -1
  31. package/lib/session/auth/domainRouting.js +3 -4
  32. package/lib/session/auth/domainRouting.js.map +1 -1
  33. package/lib/session/auth/firebase.d.ts.map +1 -1
  34. package/lib/session/auth/firebase.js +10 -0
  35. package/lib/session/auth/firebase.js.map +1 -1
  36. package/lib/shell/login/SignInAuthPending.js +1 -1
  37. package/lib/shell/login/SignInAuthPending.js.map +1 -1
  38. package/lib/shell/login/SignInEmailStep.d.ts.map +1 -1
  39. package/lib/shell/login/SignInEmailStep.js +2 -2
  40. package/lib/shell/login/SignInEmailStep.js.map +1 -1
  41. package/lib/shell/login/SignInIcons.d.ts +1 -1
  42. package/lib/shell/login/SignInIcons.d.ts.map +1 -1
  43. package/lib/shell/login/signInUtils.d.ts +1 -1
  44. package/lib/shell/login/signInUtils.d.ts.map +1 -1
  45. package/lib/shell/login/signInUtils.js +5 -6
  46. package/lib/shell/login/signInUtils.js.map +1 -1
  47. package/lib/vertesia-ui-core.js +83 -41
  48. package/lib/vertesia-ui-core.js.map +1 -1
  49. package/lib/vertesia-ui-env.js +1 -1
  50. package/lib/vertesia-ui-env.js.map +1 -1
  51. package/lib/vertesia-ui-features.js +336 -317
  52. package/lib/vertesia-ui-features.js.map +1 -1
  53. package/lib/vertesia-ui-layout.js +43 -19
  54. package/lib/vertesia-ui-layout.js.map +1 -1
  55. package/lib/vertesia-ui-rich-text.js +123 -74
  56. package/lib/vertesia-ui-rich-text.js.map +1 -1
  57. package/lib/vertesia-ui-router.js.map +1 -1
  58. package/lib/vertesia-ui-session.js +1 -1
  59. package/lib/vertesia-ui-session.js.map +1 -1
  60. package/lib/vertesia-ui-shell.js +57 -28
  61. package/lib/vertesia-ui-shell.js.map +1 -1
  62. package/lib/vertesia-ui-widgets.js +153 -97
  63. package/lib/vertesia-ui-widgets.js.map +1 -1
  64. package/lib/widgets/xml-viewer/hooks/useCollapsible.d.ts +1 -1
  65. package/package.json +20 -20
  66. package/src/env/index.test.ts +48 -0
  67. package/src/env/index.ts +15 -2
  68. package/src/features/agent/PayloadBuilder.tsx +6 -1
  69. package/src/features/agent/chat/AgentRunFeedback.test.tsx +18 -1
  70. package/src/features/agent/chat/AgentRunFeedback.tsx +13 -0
  71. package/src/features/agent/chat/ModernAgentConversation.test.tsx +1 -1
  72. package/src/features/agent/chat/ModernAgentConversation.tsx +6 -5
  73. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +2 -2
  74. package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +2 -2
  75. package/src/features/agent/chat/hooks/useAgentPlans.test.ts +52 -0
  76. package/src/features/agent/chat/hooks/useAgentPlans.ts +5 -5
  77. package/src/features/store/objects/search/DocumentSearchContext.ts +8 -3
  78. package/src/router/path.test.ts +5 -5
  79. package/src/session/UserSessionProvider.tsx +3 -3
  80. package/src/session/auth/auth-flow.md +21 -9
  81. package/src/session/auth/domainRouting.test.ts +1 -1
  82. package/src/session/auth/domainRouting.ts +3 -4
  83. package/src/session/auth/firebase.test.ts +25 -0
  84. package/src/session/auth/firebase.ts +10 -0
  85. package/src/shell/login/SignInAuthPending.tsx +3 -3
  86. package/src/shell/login/SignInEmailStep.test.tsx +32 -0
  87. package/src/shell/login/SignInEmailStep.tsx +13 -9
  88. package/src/shell/login/signInUtils.ts +12 -4
  89. package/src/widgets/markdown/CollaborativeMarkdownRenderer.test.tsx +11 -3
@@ -10,11 +10,10 @@ declare global {
10
10
  /**
11
11
  * The broker this app sends users to for sign-in and logout.
12
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.
13
+ * A gateway or app can select a regional broker through Env.endpoints.auth.
14
+ * Unconfigured apps use the first-party central broker.
16
15
  */
17
- export const DEFAULT_CENTRAL_AUTH_URL = 'https://internal-auth.vertesia.app/';
16
+ export const DEFAULT_CENTRAL_AUTH_URL = 'https://auth.vertesia.io/';
18
17
 
19
18
  export function centralAuthUrl(): string {
20
19
  const configured = Env.endpoints.auth;
@@ -0,0 +1,25 @@
1
+ import { Env } from '@vertesia/ui/env';
2
+ import { afterEach, describe, expect, it, vi } from 'vitest';
3
+ import { getFirebaseAuth, setFirebaseTenant } from './firebase';
4
+
5
+ vi.mock('firebase/app', () => ({ initializeApp: vi.fn(() => ({})) }));
6
+ vi.mock('firebase/auth', () => ({ getAuth: vi.fn(() => ({ tenantId: null })) }));
7
+
8
+ describe('configured Firebase tenant', () => {
9
+ afterEach(() => vi.restoreAllMocks());
10
+
11
+ it('initializes redirect handling with the tenant and skips email discovery', async () => {
12
+ vi.spyOn(Env, 'firebase', 'get').mockReturnValue({
13
+ apiKey: 'key',
14
+ authDomain: 'app.example.com',
15
+ projectId: 'project',
16
+ tenantId: 'fixed-tenant',
17
+ });
18
+ const fetchSpy = vi.spyOn(globalThis, 'fetch');
19
+ expect(getFirebaseAuth().tenantId).toBe('fixed-tenant');
20
+ const tenant = await setFirebaseTenant('someone@another-company.com');
21
+ expect(tenant?.firebaseTenantId).toBe('fixed-tenant');
22
+ expect(tenant?.provider).toBe('oidc');
23
+ expect(fetchSpy).not.toHaveBeenCalled();
24
+ });
25
+ });
@@ -37,11 +37,21 @@ export function getFirebaseAnalytics(): Analytics {
37
37
  export function getFirebaseAuth(): Auth {
38
38
  if (!_firebaseAuth) {
39
39
  _firebaseAuth = getAuth(getFirebaseApp());
40
+ _firebaseAuth.tenantId = Env.firebase?.tenantId ?? null;
40
41
  }
41
42
  return _firebaseAuth;
42
43
  }
43
44
 
44
45
  export async function setFirebaseTenant(tenantEmail?: string) {
46
+ if (Env.firebase?.tenantId) {
47
+ getFirebaseAuth().tenantId = Env.firebase.tenantId;
48
+ return {
49
+ firebaseTenantId: Env.firebase.tenantId,
50
+ provider: Env.firebase.providerType ?? 'oidc',
51
+ name: '',
52
+ label: '',
53
+ };
54
+ }
45
55
  if (!tenantEmail) {
46
56
  console.log('No tenant name or email specified, skipping tenant setup');
47
57
  return;
@@ -28,10 +28,10 @@ export default function SignInAuthPending({ provider, onCancel }: SignInAuthPend
28
28
  </div>
29
29
 
30
30
  <div className="w-full flex flex-col gap-2">
31
- <SignInStepButton variant="loading">
31
+ <div className="flex justify-center">
32
32
  <Spinner />
33
- <span>{t('auth.pending.authenticating')}</span>
34
- </SignInStepButton>
33
+ </div>
34
+ <span>{t('auth.pending.authenticating')}</span>
35
35
  {onCancel && (
36
36
  <SignInStepButton variant="ghost" onClick={onCancel}>
37
37
  {t('auth.pending.cancel')}
@@ -0,0 +1,32 @@
1
+ import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
2
+ import { afterEach, expect, it, vi } from 'vitest';
3
+ import SignInEmailStep from './SignInEmailStep';
4
+
5
+ const resolveTenant = vi.hoisted(() => vi.fn());
6
+ vi.mock('@vertesia/ui/session', () => ({ setFirebaseTenant: resolveTenant }));
7
+ vi.mock('@vertesia/ui/i18n', () => ({ useUITranslation: () => ({ t: (key: string) => key }) }));
8
+ afterEach(() => {
9
+ cleanup();
10
+ vi.clearAllMocks();
11
+ });
12
+
13
+ it('keeps feedback visible and prevents duplicate requests while tenant discovery is pending', async () => {
14
+ let finish!: () => void;
15
+ resolveTenant.mockReturnValue(
16
+ new Promise<void>((resolve) => {
17
+ finish = resolve;
18
+ }),
19
+ );
20
+ const onProceed = vi.fn();
21
+ render(<SignInEmailStep initialEmail="person@example.com" onProceed={onProceed} />);
22
+ const button = screen.getByRole('button', { name: 'auth.continue' });
23
+ fireEvent.click(button);
24
+ expect(screen.queryByRole('button', { name: 'auth.continue' })).toBeNull();
25
+ expect(screen.getByRole('textbox')).toBeTruthy();
26
+ expect(screen.getByRole('status')).toBeTruthy();
27
+ expect(button.contains(screen.getByRole('status'))).toBe(false);
28
+ fireEvent.submit(screen.getByRole('textbox').closest('form')!);
29
+ expect(resolveTenant).toHaveBeenCalledTimes(1);
30
+ finish();
31
+ await waitFor(() => expect(onProceed).toHaveBeenCalledWith('person@example.com', undefined));
32
+ });
@@ -67,16 +67,20 @@ export default function SignInEmailStep({ initialEmail, onProceed }: SignInEmail
67
67
  error={submitError ? t('auth.email.invalidError') : undefined}
68
68
  />
69
69
 
70
- <SignInStepButton type="submit" disabled={loading}>
71
- {loading ? (
70
+ {loading ? (
71
+ <div
72
+ role="status"
73
+ aria-label={t('auth.pending.authenticating')}
74
+ className="h-[42px] flex items-center justify-center"
75
+ >
72
76
  <Spinner />
73
- ) : (
74
- <>
75
- <span>{t('auth.continue')}</span>
76
- <ArrowRight className="!size-3.5" />
77
- </>
78
- )}
79
- </SignInStepButton>
77
+ </div>
78
+ ) : (
79
+ <SignInStepButton type="submit">
80
+ <span>{t('auth.continue')}</span>
81
+ <ArrowRight className="!size-3.5" />
82
+ </SignInStepButton>
83
+ )}
80
84
  </form>
81
85
  </SignInStepLayout>
82
86
  );
@@ -1,3 +1,4 @@
1
+ import { Env } from '@vertesia/ui/env';
1
2
  import { getFirebaseAuth, setFirebaseTenant } from '@vertesia/ui/session';
2
3
  import {
3
4
  type AuthProvider,
@@ -197,7 +198,7 @@ export async function startSignIn(
197
198
  } else {
198
199
  // No tenant — clear any stale tenant routing from a prior attempt.
199
200
  localStorage.removeItem('tenantName');
200
- if (auth.tenantId) auth.tenantId = null;
201
+ auth.tenantId = Env.firebase?.tenantId ?? null;
201
202
  }
202
203
 
203
204
  writePendingSignin({ email, provider: effectiveIdp, tenantName });
@@ -205,12 +206,19 @@ export async function startSignIn(
205
206
  return { ok: true };
206
207
  }
207
208
 
208
- /** Starts a provider sign-in directly, skipping email/tenant resolution and clearing any tenant routing. */
209
+ /** Starts sign-in without email discovery, preserving an explicitly configured tenant. */
209
210
  export function startSignInWithoutTenant(provider: ProviderId, redirectTo?: string): void {
210
211
  const auth = getFirebaseAuth();
211
212
  localStorage.removeItem('tenantName');
212
- if (auth.tenantId) auth.tenantId = null;
213
- void signInWithRedirect(auth, buildFirebaseProvider(provider, undefined, redirectTo));
213
+ auth.tenantId = Env.firebase?.tenantId ?? null;
214
+ void signInWithRedirect(
215
+ auth,
216
+ buildFirebaseProvider(
217
+ Env.firebase?.tenantId ? ((Env.firebase.providerType as ProviderId) ?? 'oidc') : provider,
218
+ undefined,
219
+ redirectTo,
220
+ ),
221
+ );
214
222
  }
215
223
 
216
224
  export function providerLabel(id: ProviderId): string {
@@ -412,11 +412,19 @@ describe('collaborative Markdown actions', () => {
412
412
  await user.click(screen.getByRole('button', { name: 'Insert component after this block' }));
413
413
  await user.click(await screen.findByRole('menuitem', { name: 'Paragraph' }));
414
414
 
415
- const editor = await screen.findByRole('textbox');
415
+ // The lazy editor first renders a textarea with the same template text. Wait for
416
+ // the actual rich-text editor before selecting and pasting into its content.
417
+ const editor = await waitFor(() => {
418
+ const textbox = screen.getByRole('textbox');
419
+ expect(textbox.getAttribute('contenteditable')).toBe('true');
420
+ expect(textbox.textContent).toBe('Paragraph');
421
+ return textbox;
422
+ });
416
423
  await user.click(editor);
424
+ await user.keyboard('{Control>}a{/Control}');
417
425
  // Paste in one operation — char-by-char typing races ProseMirror in jsdom and drops chars.
418
426
  await user.paste('Inserted paragraph.');
419
- await waitFor(() => expect(editor.textContent ?? '').toContain('Inserted paragraph.'));
427
+ await waitFor(() => expect(editor.textContent).toBe('Inserted paragraph.'));
420
428
  fireEvent.click(screen.getByRole('button', { name: 'Send' }));
421
429
 
422
430
  await waitFor(() => expect(onAction).toHaveBeenCalledTimes(1));
@@ -425,7 +433,7 @@ describe('collaborative Markdown actions', () => {
425
433
  action: 'edit',
426
434
  user_change: {
427
435
  before: 'Original paragraph.',
428
- after: expect.stringMatching(/^Original paragraph\.\n\n.*Inserted paragraph\.$/),
436
+ after: 'Original paragraph.\n\nInserted paragraph.',
429
437
  },
430
438
  }),
431
439
  );