@semiont/react-ui 0.4.20 → 0.4.22

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 (125) hide show
  1. package/README.md +8 -5
  2. package/dist/{PdfAnnotationCanvas.client-CHDCGQBR.mjs → PdfAnnotationCanvas.client-5QESNO5H.mjs} +13 -16
  3. package/dist/PdfAnnotationCanvas.client-5QESNO5H.mjs.map +1 -0
  4. package/dist/TranslationManager-9Xj3MIWQ.d.mts +16 -0
  5. package/dist/chunk-4NOUO3W6.mjs +7788 -0
  6. package/dist/chunk-4NOUO3W6.mjs.map +1 -0
  7. package/dist/index.d.mts +212 -1206
  8. package/dist/index.mjs +3332 -13712
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/test-utils.d.mts +48 -21
  11. package/dist/test-utils.mjs +2505 -87
  12. package/dist/test-utils.mjs.map +1 -1
  13. package/package.json +2 -2
  14. package/src/components/AnnotateReferencesProgressWidget.tsx +21 -28
  15. package/src/components/CodeMirrorRenderer.tsx +12 -12
  16. package/src/components/LiveRegion.tsx +1 -2
  17. package/src/components/StatusDisplay.tsx +42 -16
  18. package/src/components/Toolbar.tsx +4 -4
  19. package/src/components/__tests__/AnnotateReferencesProgressWidget.test.tsx +34 -20
  20. package/src/components/__tests__/StatusDisplay.test.tsx +50 -65
  21. package/src/components/__tests__/Toolbar.test.tsx +4 -4
  22. package/src/components/annotation/AnnotateToolbar.tsx +8 -9
  23. package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +31 -77
  24. package/src/components/annotation-popups/JsonLdView.tsx +1 -2
  25. package/src/components/annotation-popups/__tests__/JsonLdView.test.tsx +1 -2
  26. package/src/components/image-annotation/AnnotationOverlay.tsx +15 -18
  27. package/src/components/image-annotation/SvgDrawingCanvas.tsx +12 -17
  28. package/src/components/modals/ConfigureGenerationStep.tsx +1 -2
  29. package/src/components/modals/PermissionDeniedModal.tsx +11 -11
  30. package/src/components/modals/ReferenceWizardModal.tsx +14 -18
  31. package/src/components/modals/ResourceSearchModal.tsx +12 -8
  32. package/src/components/modals/SearchModal.tsx +11 -6
  33. package/src/components/modals/SearchResultsStep.tsx +1 -3
  34. package/src/components/modals/SessionExpiredModal.tsx +11 -11
  35. package/src/components/modals/__tests__/PermissionDeniedModal.test.tsx +7 -7
  36. package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +10 -8
  37. package/src/components/modals/__tests__/SearchModal.accessibility.test.tsx +6 -2
  38. package/src/components/modals/__tests__/SearchModal.basic.test.tsx +6 -2
  39. package/src/components/modals/__tests__/SearchModal.keyboard.test.tsx +6 -2
  40. package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +10 -7
  41. package/src/components/modals/__tests__/SearchModal.visual.test.tsx +6 -2
  42. package/src/components/modals/__tests__/SessionExpiredModal.test.tsx +5 -5
  43. package/src/components/navigation/CollapsibleResourceNavigation.tsx +10 -10
  44. package/src/components/navigation/ObservableLink.tsx +6 -6
  45. package/src/components/navigation/SimpleNavigation.tsx +4 -4
  46. package/src/components/navigation/__tests__/ObservableLink.test.tsx +4 -4
  47. package/src/components/navigation/__tests__/SimpleNavigation.test.tsx +4 -4
  48. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +15 -18
  49. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +1 -2
  50. package/src/components/resource/AnnotateView.tsx +8 -10
  51. package/src/components/resource/AnnotationHistory.tsx +9 -12
  52. package/src/components/resource/BrowseView.tsx +11 -8
  53. package/src/components/resource/ResourceViewer.tsx +22 -34
  54. package/src/components/resource/__tests__/AnnotationHistory.test.tsx +54 -192
  55. package/src/components/resource/__tests__/BrowseView.test.tsx +38 -87
  56. package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +41 -31
  57. package/src/components/resource/__tests__/event-formatting.test.ts +6 -2
  58. package/src/components/resource/event-formatting.ts +2 -3
  59. package/src/components/resource/panels/AssessmentEntry.tsx +7 -8
  60. package/src/components/resource/panels/AssessmentPanel.tsx +21 -17
  61. package/src/components/resource/panels/AssistSection.tsx +15 -21
  62. package/src/components/resource/panels/CollaborationPanel.tsx +29 -7
  63. package/src/components/resource/panels/CommentEntry.tsx +7 -8
  64. package/src/components/resource/panels/CommentsPanel.tsx +11 -13
  65. package/src/components/resource/panels/HighlightEntry.tsx +7 -8
  66. package/src/components/resource/panels/HighlightPanel.tsx +12 -13
  67. package/src/components/resource/panels/ReferenceEntry.tsx +13 -15
  68. package/src/components/resource/panels/ReferencesPanel.tsx +17 -19
  69. package/src/components/resource/panels/ResourceInfoPanel.tsx +8 -7
  70. package/src/components/resource/panels/StatisticsPanel.tsx +2 -3
  71. package/src/components/resource/panels/TagEntry.tsx +7 -8
  72. package/src/components/resource/panels/TaggingPanel.tsx +14 -23
  73. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +4 -3
  74. package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +4 -4
  75. package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +22 -57
  76. package/src/components/resource/panels/__tests__/CollaborationPanel.test.tsx +51 -20
  77. package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +4 -4
  78. package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +22 -61
  79. package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +4 -4
  80. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +1 -2
  81. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +7 -8
  82. package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +153 -0
  83. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +51 -106
  84. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +28 -53
  85. package/src/components/resource/panels/__tests__/StatisticsPanel.test.tsx +3 -3
  86. package/src/components/resource/panels/__tests__/TagEntry.test.tsx +4 -4
  87. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +19 -52
  88. package/src/components/settings/SettingsPanel.tsx +9 -9
  89. package/src/components/settings/__tests__/SettingsPanel.test.tsx +15 -15
  90. package/src/features/admin-devops/components/AdminDevOpsPage.tsx +1 -2
  91. package/src/features/admin-exchange/components/AdminExchangePage.tsx +1 -1
  92. package/src/features/admin-exchange/components/ImportCard.tsx +2 -7
  93. package/src/features/admin-security/components/AdminSecurityPage.tsx +1 -2
  94. package/src/features/admin-users/components/AdminUsersPage.tsx +1 -1
  95. package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -2
  96. package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -2
  97. package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -1
  98. package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +1 -1
  99. package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +5 -3
  100. package/src/features/resource-compose/components/ResourceComposePage.tsx +6 -22
  101. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +4 -3
  102. package/src/features/resource-discovery/components/ResourceCard.tsx +1 -2
  103. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +3 -4
  104. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +37 -45
  105. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +129 -197
  106. package/dist/KnowledgeBaseSessionContext-BNNunwzO.d.mts +0 -175
  107. package/dist/PdfAnnotationCanvas.client-CHDCGQBR.mjs.map +0 -1
  108. package/dist/chunk-OZICDVH7.mjs +0 -62
  109. package/dist/chunk-OZICDVH7.mjs.map +0 -1
  110. package/dist/chunk-R4CCMFJH.mjs +0 -877
  111. package/dist/chunk-R4CCMFJH.mjs.map +0 -1
  112. package/dist/chunk-VN5NY4SN.mjs +0 -200
  113. package/dist/chunk-VN5NY4SN.mjs.map +0 -1
  114. package/src/components/modals/ProposeEntitiesModal.tsx +0 -179
  115. package/src/components/modals/__tests__/ProposeEntitiesModal.test.tsx +0 -129
  116. package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +0 -323
  117. package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +0 -245
  118. package/src/features/resource-viewer/__tests__/AnnotationProgressDismissal.test.tsx +0 -303
  119. package/src/features/resource-viewer/__tests__/BindFlowIntegration.test.tsx +0 -150
  120. package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +0 -243
  121. package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +0 -383
  122. package/src/features/resource-viewer/__tests__/ResourceMutations.test.tsx +0 -299
  123. package/src/features/resource-viewer/__tests__/ToastNotifications.test.tsx +0 -186
  124. package/src/features/resource-viewer/__tests__/YieldFlowIntegration.test.tsx +0 -429
  125. package/src/features/resource-viewer/__tests__/annotation-progress-flow.test.tsx +0 -348
@@ -1,42 +1,37 @@
1
1
  import { describe, it, expect, beforeEach, vi } from 'vitest';
2
2
  import React from 'react';
3
- import { screen } from '@testing-library/react';
3
+ import { screen, waitFor } from '@testing-library/react';
4
+ import { BehaviorSubject } from 'rxjs';
4
5
  import '@testing-library/jest-dom';
5
6
  import { renderWithProviders } from '../../test-utils';
6
-
7
- // Mock api-hooks
8
- vi.mock('../../lib/api-hooks', () => ({
9
- useHealth: vi.fn(),
10
- }));
11
-
12
- import { useHealth } from '../../lib/api-hooks';
13
- import type { MockedFunction } from 'vitest';
14
7
  import { StatusDisplay } from '../StatusDisplay';
15
8
 
16
- const mockUseHealth = useHealth as MockedFunction<typeof useHealth>;
17
-
18
- function createMockHealth(queryResult: { data?: unknown; isLoading?: boolean; error?: unknown }) {
9
+ let mockGetStatus: ReturnType<typeof vi.fn>;
10
+ const stableMockClient = {
11
+ admin: {
12
+ get status() { return mockGetStatus; },
13
+ },
14
+ };
15
+ const stableMockSession = { client: stableMockClient };
16
+ const stableActiveSession$ = new BehaviorSubject<any>(stableMockSession);
17
+ const stableMockBrowser = { activeSession$: stableActiveSession$ };
18
+
19
+ vi.mock('../../session/SemiontProvider', async (importOriginal) => {
20
+ const actual = await importOriginal<typeof import('../../session/SemiontProvider')>();
19
21
  return {
20
- check: {
21
- useQuery: vi.fn(),
22
- },
23
- status: {
24
- useQuery: vi.fn().mockReturnValue({
25
- data: queryResult.data ?? undefined,
26
- isLoading: queryResult.isLoading ?? false,
27
- error: queryResult.error ?? undefined,
28
- }),
29
- },
22
+ ...actual,
23
+ useSemiont: () => stableMockBrowser,
30
24
  };
31
- }
25
+ });
32
26
 
33
27
  describe('StatusDisplay', () => {
34
28
  beforeEach(() => {
35
29
  vi.clearAllMocks();
30
+ mockGetStatus = vi.fn();
36
31
  });
37
32
 
38
33
  it('should show "Authentication required" when not fully authenticated', () => {
39
- mockUseHealth.mockReturnValue(createMockHealth({ data: undefined, isLoading: false }) as ReturnType<typeof useHealth>);
34
+ mockGetStatus.mockResolvedValue({ status: 'healthy', version: '1.0.0' });
40
35
 
41
36
  renderWithProviders(
42
37
  <StatusDisplay isFullyAuthenticated={false} isAuthenticated={false} />
@@ -45,55 +40,45 @@ describe('StatusDisplay', () => {
45
40
  expect(screen.getByText(/Authentication required/)).toBeInTheDocument();
46
41
  });
47
42
 
48
- it('should show "Connecting..." when loading', () => {
49
- mockUseHealth.mockReturnValue(createMockHealth({ isLoading: true }) as ReturnType<typeof useHealth>);
50
-
51
- renderWithProviders(
52
- <StatusDisplay isFullyAuthenticated={true} isAuthenticated={true} hasValidBackendToken={true} />
53
- );
54
-
55
- expect(screen.getByText(/Connecting\.\.\./)).toBeInTheDocument();
56
- });
57
-
58
- it('should show status when data is available', () => {
59
- mockUseHealth.mockReturnValue(createMockHealth({
60
- data: { status: 'healthy', version: '1.2.3' },
61
- }) as ReturnType<typeof useHealth>);
43
+ it('should show status when data is available', async () => {
44
+ mockGetStatus.mockResolvedValue({ status: 'healthy', version: '1.2.3' });
62
45
 
63
46
  renderWithProviders(
64
47
  <StatusDisplay isFullyAuthenticated={true} isAuthenticated={true} hasValidBackendToken={true} />
65
48
  );
66
49
 
67
- expect(screen.getByText(/healthy/)).toBeInTheDocument();
68
- expect(screen.getByText(/v1\.2\.3/)).toBeInTheDocument();
50
+ await waitFor(() => {
51
+ expect(screen.getByText(/healthy/)).toBeInTheDocument();
52
+ expect(screen.getByText(/v1\.2\.3/)).toBeInTheDocument();
53
+ });
69
54
  });
70
55
 
71
- it('should show "Connection failed" on error', () => {
72
- mockUseHealth.mockReturnValue(createMockHealth({
73
- error: new Error('Network error'),
74
- }) as ReturnType<typeof useHealth>);
56
+ it('should show "Connection failed" on error', async () => {
57
+ mockGetStatus.mockRejectedValue(new Error('Network error'));
75
58
 
76
59
  renderWithProviders(
77
60
  <StatusDisplay isFullyAuthenticated={true} isAuthenticated={true} hasValidBackendToken={true} />
78
61
  );
79
62
 
80
- expect(screen.getByText(/Connection failed/)).toBeInTheDocument();
63
+ await waitFor(() => {
64
+ expect(screen.getByText(/Connection failed/)).toBeInTheDocument();
65
+ });
81
66
  });
82
67
 
83
- it('should show re-login message for 401 errors', () => {
84
- mockUseHealth.mockReturnValue(createMockHealth({
85
- error: new Error('401 Unauthorized'),
86
- }) as ReturnType<typeof useHealth>);
68
+ it('should show re-login message for 401 errors', async () => {
69
+ mockGetStatus.mockRejectedValue(new Error('401 Unauthorized'));
87
70
 
88
71
  renderWithProviders(
89
72
  <StatusDisplay isFullyAuthenticated={true} isAuthenticated={true} hasValidBackendToken={true} />
90
73
  );
91
74
 
92
- expect(screen.getByText(/sign out and sign in again/i)).toBeInTheDocument();
75
+ await waitFor(() => {
76
+ expect(screen.getByText(/sign out and sign in again/i)).toBeInTheDocument();
77
+ });
93
78
  });
94
79
 
95
80
  it('should show warning for authenticated users missing backend token', () => {
96
- mockUseHealth.mockReturnValue(createMockHealth({ data: undefined }) as ReturnType<typeof useHealth>);
81
+ mockGetStatus.mockResolvedValue(undefined);
97
82
 
98
83
  renderWithProviders(
99
84
  <StatusDisplay isFullyAuthenticated={false} isAuthenticated={true} hasValidBackendToken={false} />
@@ -103,7 +88,7 @@ describe('StatusDisplay', () => {
103
88
  });
104
89
 
105
90
  it('should have role="status"', () => {
106
- mockUseHealth.mockReturnValue(createMockHealth({ data: undefined }) as ReturnType<typeof useHealth>);
91
+ mockGetStatus.mockResolvedValue(undefined);
107
92
 
108
93
  renderWithProviders(
109
94
  <StatusDisplay isFullyAuthenticated={false} isAuthenticated={false} />
@@ -113,7 +98,7 @@ describe('StatusDisplay', () => {
113
98
  });
114
99
 
115
100
  it('should have aria-live="polite"', () => {
116
- mockUseHealth.mockReturnValue(createMockHealth({ data: undefined }) as ReturnType<typeof useHealth>);
101
+ mockGetStatus.mockResolvedValue(undefined);
117
102
 
118
103
  renderWithProviders(
119
104
  <StatusDisplay isFullyAuthenticated={false} isAuthenticated={false} />
@@ -124,7 +109,7 @@ describe('StatusDisplay', () => {
124
109
  });
125
110
 
126
111
  it('should show sign-in hint when not authenticated', () => {
127
- mockUseHealth.mockReturnValue(createMockHealth({ data: undefined }) as ReturnType<typeof useHealth>);
112
+ mockGetStatus.mockResolvedValue(undefined);
128
113
 
129
114
  renderWithProviders(
130
115
  <StatusDisplay isFullyAuthenticated={false} isAuthenticated={false} />
@@ -133,28 +118,28 @@ describe('StatusDisplay', () => {
133
118
  expect(screen.getByText('Sign in to view backend status')).toBeInTheDocument();
134
119
  });
135
120
 
136
- it('should show error hint when there is an error', () => {
137
- mockUseHealth.mockReturnValue(createMockHealth({
138
- error: new Error('Connection refused'),
139
- }) as ReturnType<typeof useHealth>);
121
+ it('should show error hint when there is an error', async () => {
122
+ mockGetStatus.mockRejectedValue(new Error('Connection refused'));
140
123
 
141
124
  renderWithProviders(
142
125
  <StatusDisplay isFullyAuthenticated={true} isAuthenticated={true} hasValidBackendToken={true} />
143
126
  );
144
127
 
145
- expect(screen.getByText(/Check that the backend server is running/)).toBeInTheDocument();
128
+ await waitFor(() => {
129
+ expect(screen.getByText(/Check that the backend server is running/)).toBeInTheDocument();
130
+ });
146
131
  });
147
132
 
148
- it('should set data-status attribute based on state', () => {
149
- mockUseHealth.mockReturnValue(createMockHealth({
150
- data: { status: 'healthy', version: '1.0.0' },
151
- }) as ReturnType<typeof useHealth>);
133
+ it('should set data-status attribute based on state', async () => {
134
+ mockGetStatus.mockResolvedValue({ status: 'healthy', version: '1.0.0' });
152
135
 
153
136
  renderWithProviders(
154
137
  <StatusDisplay isFullyAuthenticated={true} isAuthenticated={true} hasValidBackendToken={true} />
155
138
  );
156
139
 
157
- const status = screen.getByRole('status');
158
- expect(status).toHaveAttribute('data-status', 'success');
140
+ await waitFor(() => {
141
+ const status = screen.getByRole('status');
142
+ expect(status).toHaveAttribute('data-status', 'success');
143
+ });
159
144
  });
160
145
  });
@@ -66,15 +66,15 @@ describe('Toolbar', () => {
66
66
  });
67
67
 
68
68
  describe('event emission', () => {
69
- it('emits browse:panel-toggle with panel name on click', () => {
69
+ it('emits panel:toggle with panel name on click', () => {
70
70
  const handler = vi.fn();
71
71
 
72
- const { eventBus } = renderWithProviders(
72
+ const { shellBus } = renderWithProviders(
73
73
  <Toolbar context="document" activePanel={null} />,
74
- { returnEventBus: true }
74
+ { returnShellBus: true }
75
75
  );
76
76
 
77
- const subscription = eventBus!.get('browse:panel-toggle').subscribe(handler);
77
+ const subscription = shellBus!.get('panel:toggle').subscribe(handler);
78
78
 
79
79
  fireEvent.click(screen.getByLabelText('Toolbar.resourceInfo'));
80
80
  expect(handler).toHaveBeenCalledWith({ panel: 'info' });
@@ -2,7 +2,8 @@
2
2
 
3
3
  import React, { useState, useRef, useEffect } from 'react';
4
4
  import { useTranslations } from '../../contexts/TranslationContext';
5
- import { useEventBus } from '../../contexts/EventBusContext';
5
+ import { useSemiont } from '../../session/SemiontProvider';
6
+ import { useObservable } from '../../hooks/useObservable';
6
7
  import { getSupportedShapes } from '../../lib/media-shapes';
7
8
  import type { Annotator } from '../../lib/annotation-registry';
8
9
  import './annotations.css';
@@ -119,7 +120,7 @@ export function AnnotateToolbar({
119
120
  annotators
120
121
  }: AnnotateToolbarProps) {
121
122
  const t = useTranslations('AnnotateToolbar');
122
- const eventBus = useEventBus();
123
+ const session = useObservable(useSemiont().activeSession$);
123
124
 
124
125
  // Helper to get emoji from annotators by motivation (with fallback for safety)
125
126
  const getMotivationEmoji = (motivation: SelectionMotivation): string => {
@@ -188,11 +189,9 @@ export function AnnotateToolbar({
188
189
  const handleSelectionClick = (motivation: SelectionMotivation | null) => {
189
190
  // If null is clicked, always deselect. Otherwise toggle.
190
191
  if (motivation === null) {
191
- eventBus.get('mark:selection-changed').next({ motivation: null });
192
+ session?.client.mark.changeSelection(null);
192
193
  } else {
193
- eventBus.get('mark:selection-changed').next({
194
- motivation: selectedMotivation === motivation ? null : motivation
195
- });
194
+ session?.client.mark.changeSelection(selectedMotivation === motivation ? null : motivation);
196
195
  }
197
196
  // Close dropdown after selection
198
197
  setSelectionPinned(false);
@@ -200,21 +199,21 @@ export function AnnotateToolbar({
200
199
  };
201
200
 
202
201
  const handleClickClick = (action: ClickAction) => {
203
- eventBus.get('mark:click-changed').next({ action });
202
+ session?.client.mark.changeClick(action);
204
203
  // Close dropdown after selection
205
204
  setClickPinned(false);
206
205
  setClickHovered(false);
207
206
  };
208
207
 
209
208
  const handleShapeClick = (shape: ShapeType) => {
210
- eventBus.get('mark:shape-changed').next({ shape });
209
+ session?.client.mark.changeShape(shape);
211
210
  // Close dropdown after selection
212
211
  setShapePinned(false);
213
212
  setShapeHovered(false);
214
213
  };
215
214
 
216
215
  const handleModeToggle = () => {
217
- eventBus.get('mark:mode-toggled').next(undefined);
216
+ session?.client.mark.toggleMode();
218
217
  setModePinned(false);
219
218
  setModeHovered(false);
220
219
  };
@@ -3,9 +3,10 @@ import { render, screen, fireEvent, waitFor, within } from '@testing-library/rea
3
3
  import { vi, beforeEach, describe, it, expect } from 'vitest';
4
4
  import { AnnotateToolbar, type SelectionMotivation, type ClickAction } from '../AnnotateToolbar';
5
5
  import { ANNOTATORS } from '../../../lib/annotation-registry';
6
- import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
7
6
  import { TranslationProvider } from '../../../contexts/TranslationContext';
7
+ import { createTestSemiontWrapper } from '../../../test-utils';
8
8
  import type { TranslationManager } from '../../../types/TranslationManager';
9
+ import type { EventBus } from '@semiont/core';
9
10
 
10
11
  // Mock translations
11
12
  const messages: Record<string, Record<string, string>> = {
@@ -43,68 +44,37 @@ interface TrackedEvent {
43
44
 
44
45
  function createEventTracker() {
45
46
  const events: TrackedEvent[] = [];
46
-
47
- function EventTrackingWrapper({ children }: { children: React.ReactNode }) {
48
- const eventBus = useEventBus();
49
-
50
- React.useEffect(() => {
51
- const handlers: Array<() => void> = [];
52
-
53
- // Track toolbar-related events
54
- const trackEvent = (eventName: string) => (payload: any) => {
55
- events.push({ event: eventName, payload });
56
- };
57
-
58
- const toolbarEvents = [
47
+ return {
48
+ events,
49
+ clear: () => { events.length = 0; },
50
+ _attach(eventBus: EventBus) {
51
+ const trackedEvents = [
59
52
  'mark:mode-toggled',
60
53
  'mark:click-changed',
61
54
  'mark:selection-changed',
62
55
  'mark:shape-changed',
63
56
  ] as const;
64
-
65
- toolbarEvents.forEach(eventName => {
66
- const handler = trackEvent(eventName);
67
- const subscription = eventBus.get(eventName).subscribe(handler);
68
- handlers.push(subscription);
57
+ trackedEvents.forEach((eventName) => {
58
+ eventBus.get(eventName).subscribe((payload: any) => {
59
+ events.push({ event: eventName, payload });
60
+ });
69
61
  });
70
-
71
- return () => {
72
- handlers.forEach(sub => sub.unsubscribe());
73
- };
74
- }, [eventBus]);
75
-
76
- return <>{children}</>;
77
- }
78
-
79
- return {
80
- EventTrackingWrapper,
81
- events,
82
- clear: () => {
83
- events.length = 0;
84
62
  },
85
63
  };
86
64
  }
87
65
 
88
66
  const renderWithIntl = (component: React.ReactElement, tracker?: ReturnType<typeof createEventTracker>) => {
89
- if (tracker) {
90
- return render(
91
- <EventBusProvider>
92
- <TranslationProvider translationManager={translationManager}>
93
- <tracker.EventTrackingWrapper>
94
- {component}
95
- </tracker.EventTrackingWrapper>
96
- </TranslationProvider>
97
- </EventBusProvider>
98
- );
99
- }
100
-
101
- return render(
102
- <EventBusProvider>
67
+ const { SemiontWrapper, eventBus } = createTestSemiontWrapper();
68
+ if (tracker) tracker._attach(eventBus);
69
+ const Wrapper = ({ children }: { children: React.ReactNode }) => (
70
+ <SemiontWrapper>
103
71
  <TranslationProvider translationManager={translationManager}>
104
- {component}
72
+ {children}
105
73
  </TranslationProvider>
106
- </EventBusProvider>
74
+ </SemiontWrapper>
107
75
  );
76
+ const result = render(component, { wrapper: Wrapper });
77
+ return { ...result, eventBus };
108
78
  };
109
79
 
110
80
  describe('AnnotateToolbar', () => {
@@ -187,14 +157,10 @@ describe('AnnotateToolbar', () => {
187
157
  expect(screen.getByText('Browse')).toBeInTheDocument();
188
158
 
189
159
  rerender(
190
- <EventBusProvider>
191
- <TranslationProvider translationManager={translationManager}>
192
- <AnnotateToolbar
193
- {...defaultProps}
194
- annotateMode={true}
195
- />
196
- </TranslationProvider>
197
- </EventBusProvider>
160
+ <AnnotateToolbar
161
+ {...defaultProps}
162
+ annotateMode={true}
163
+ />
198
164
  );
199
165
  expect(screen.getByText('Annotate')).toBeInTheDocument();
200
166
  });
@@ -292,16 +258,10 @@ describe('AnnotateToolbar', () => {
292
258
 
293
259
  // Simulate mode change by rerendering with new mode
294
260
  rerender(
295
- <EventBusProvider>
296
- <TranslationProvider translationManager={translationManager}>
297
- <tracker.EventTrackingWrapper>
298
- <AnnotateToolbar
299
- {...defaultProps}
300
- annotateMode={true}
301
- />
302
- </tracker.EventTrackingWrapper>
303
- </TranslationProvider>
304
- </EventBusProvider>
261
+ <AnnotateToolbar
262
+ {...defaultProps}
263
+ annotateMode={true}
264
+ />
305
265
  );
306
266
 
307
267
  // After mode change, the collapsed content should show "Annotate"
@@ -399,16 +359,10 @@ describe('AnnotateToolbar', () => {
399
359
 
400
360
  // Simulate selection
401
361
  rerender(
402
- <EventBusProvider>
403
- <TranslationProvider translationManager={translationManager}>
404
- <tracker.EventTrackingWrapper>
405
- <AnnotateToolbar
406
- {...defaultProps}
407
- selectedMotivation="highlighting"
408
- />
409
- </tracker.EventTrackingWrapper>
410
- </TranslationProvider>
411
- </EventBusProvider>
362
+ <AnnotateToolbar
363
+ {...defaultProps}
364
+ selectedMotivation="highlighting"
365
+ />
412
366
  );
413
367
 
414
368
  // Click again to deselect
@@ -8,9 +8,8 @@ import { oneDark } from '@codemirror/theme-one-dark';
8
8
  import { syntaxHighlighting } from '@codemirror/language';
9
9
  import { jsonLightTheme, jsonLightHighlightStyle } from '../../lib/codemirror-json-theme';
10
10
  import { useLineNumbers } from '../../hooks/useLineNumbers';
11
- import type { components } from '@semiont/core';
12
11
 
13
- type Annotation = components['schemas']['Annotation'];
12
+ import type { Annotation } from '@semiont/core';
14
13
 
15
14
  interface JsonLdViewProps {
16
15
  annotation: Annotation;
@@ -5,7 +5,7 @@ import userEvent from '@testing-library/user-event';
5
5
  import '@testing-library/jest-dom';
6
6
  import type { components } from '@semiont/core';
7
7
 
8
- type Annotation = components['schemas']['Annotation'];
8
+ import type { Annotation } from '@semiont/core';
9
9
 
10
10
  // Mock CodeMirror modules
11
11
  vi.mock('@codemirror/view', () => {
@@ -70,7 +70,6 @@ const createMockAnnotation = (overrides?: Partial<Annotation>): Annotation => ({
70
70
  end: 10,
71
71
  },
72
72
  },
73
- body: [],
74
73
  ...overrides,
75
74
  });
76
75
 
@@ -1,21 +1,18 @@
1
1
  'use client';
2
2
 
3
3
  import { useMemo } from 'react';
4
- import type { components } from '@semiont/core';
5
- import { createHoverHandlers } from '../../hooks/useBeckonFlow';
6
- import { getSvgSelector, isHighlight, isReference, isAssessment, isComment, isTag, isBodyResolved, isResolvedReference } from '@semiont/api-client';
7
- import { parseSvgSelector } from '@semiont/api-client';
8
- import type { EventBus } from "@semiont/core"
9
-
10
- type Annotation = components['schemas']['Annotation'];
11
-
4
+ import type { Annotation } from '@semiont/core';
5
+ import { getSvgSelector, isHighlight, isReference, isAssessment, isComment, isTag, isBodyResolved, isResolvedReference } from '@semiont/core';
6
+ import { createHoverHandlers } from '@semiont/sdk';
7
+ import { parseSvgSelector } from '@semiont/core';
8
+ import type { SemiontSession } from '@semiont/sdk';
12
9
  interface AnnotationOverlayProps {
13
10
  annotations: Annotation[];
14
11
  imageWidth: number;
15
12
  imageHeight: number;
16
13
  displayWidth: number;
17
14
  displayHeight: number;
18
- eventBus?: EventBus;
15
+ session?: SemiontSession | null | undefined;
19
16
  hoveredAnnotationId?: string | null;
20
17
  selectedAnnotationId?: string | null;
21
18
  hoverDelayMs: number;
@@ -72,7 +69,7 @@ export function AnnotationOverlay({
72
69
  imageHeight,
73
70
  displayWidth,
74
71
  displayHeight,
75
- eventBus,
72
+ session,
76
73
  hoveredAnnotationId,
77
74
  selectedAnnotationId,
78
75
  hoverDelayMs
@@ -81,8 +78,8 @@ export function AnnotationOverlay({
81
78
  const scaleY = displayHeight / imageHeight;
82
79
 
83
80
  const { handleMouseEnter, handleMouseLeave } = useMemo(
84
- () => createHoverHandlers((annotationId) => eventBus?.get('beckon:hover').next({ annotationId }), hoverDelayMs),
85
- [eventBus, hoverDelayMs]
81
+ () => createHoverHandlers((id) => session?.client.beckon.hover(id), hoverDelayMs),
82
+ [session, hoverDelayMs]
86
83
  );
87
84
 
88
85
  return (
@@ -131,7 +128,7 @@ export function AnnotationOverlay({
131
128
  className="semiont-annotation-overlay__shape"
132
129
  data-hovered={isHovered ? 'true' : 'false'}
133
130
  data-selected={isSelected ? 'true' : 'false'}
134
- onClick={() => eventBus?.get('browse:click').next({ annotationId: annotation.id, motivation: annotation.motivation })}
131
+ onClick={() => session?.client.browse.click(annotation.id, annotation.motivation)}
135
132
  onMouseEnter={() => handleMouseEnter(annotation.id)}
136
133
  onMouseLeave={handleMouseLeave}
137
134
  />
@@ -144,7 +141,7 @@ export function AnnotationOverlay({
144
141
  style={{ userSelect: 'none' }}
145
142
  onClick={(e) => {
146
143
  e.stopPropagation();
147
- eventBus?.get('browse:click').next({ annotationId: annotation.id, motivation: annotation.motivation });
144
+ session?.client.browse.click(annotation.id, annotation.motivation);
148
145
  }}
149
146
  onMouseEnter={() => handleMouseEnter(annotation.id)}
150
147
  onMouseLeave={handleMouseLeave}
@@ -178,7 +175,7 @@ export function AnnotationOverlay({
178
175
  className="semiont-annotation-overlay__shape"
179
176
  data-hovered={isHovered ? 'true' : 'false'}
180
177
  data-selected={isSelected ? 'true' : 'false'}
181
- onClick={() => eventBus?.get('browse:click').next({ annotationId: annotation.id, motivation: annotation.motivation })}
178
+ onClick={() => session?.client.browse.click(annotation.id, annotation.motivation)}
182
179
  onMouseEnter={() => handleMouseEnter(annotation.id)}
183
180
  onMouseLeave={handleMouseLeave}
184
181
  />
@@ -191,7 +188,7 @@ export function AnnotationOverlay({
191
188
  style={{ userSelect: 'none' }}
192
189
  onClick={(e) => {
193
190
  e.stopPropagation();
194
- eventBus?.get('browse:click').next({ annotationId: annotation.id, motivation: annotation.motivation });
191
+ session?.client.browse.click(annotation.id, annotation.motivation);
195
192
  }}
196
193
  onMouseEnter={() => handleMouseEnter(annotation.id)}
197
194
  onMouseLeave={handleMouseLeave}
@@ -238,7 +235,7 @@ export function AnnotationOverlay({
238
235
  className="semiont-annotation-overlay__shape"
239
236
  data-hovered={isHovered ? 'true' : 'false'}
240
237
  data-selected={isSelected ? 'true' : 'false'}
241
- onClick={() => eventBus?.get('browse:click').next({ annotationId: annotation.id, motivation: annotation.motivation })}
238
+ onClick={() => session?.client.browse.click(annotation.id, annotation.motivation)}
242
239
  onMouseEnter={() => handleMouseEnter(annotation.id)}
243
240
  onMouseLeave={handleMouseLeave}
244
241
  />
@@ -251,7 +248,7 @@ export function AnnotationOverlay({
251
248
  style={{ userSelect: 'none' }}
252
249
  onClick={(e) => {
253
250
  e.stopPropagation();
254
- eventBus?.get('browse:click').next({ annotationId: annotation.id, motivation: annotation.motivation });
251
+ session?.client.browse.click(annotation.id, annotation.motivation);
255
252
  }}
256
253
  onMouseEnter={() => handleMouseEnter(annotation.id)}
257
254
  onMouseLeave={handleMouseLeave}
@@ -1,15 +1,13 @@
1
1
  'use client';
2
2
 
3
3
  import React, { useRef, useState, useEffect, useCallback } from 'react';
4
- import type { components } from '@semiont/core';
5
- import { createRectangleSvg, createCircleSvg, createPolygonSvg, scaleSvgToNative, parseSvgSelector, Point } from '@semiont/api-client';
4
+ import type { Annotation } from '@semiont/core';
5
+ import { createRectangleSvg, createCircleSvg, createPolygonSvg, scaleSvgToNative, parseSvgSelector, Point } from '@semiont/core';
6
6
  import { AnnotationOverlay } from './AnnotationOverlay';
7
7
  import type { SelectionMotivation } from '../annotation/AnnotateToolbar';
8
- import type { EventBus } from "@semiont/core"
8
+ import type { SemiontSession } from '@semiont/sdk';
9
9
  import { useHoverDelay } from '../../hooks/useHoverDelay';
10
10
 
11
- type Annotation = components['schemas']['Annotation'];
12
-
13
11
  export type DrawingMode = 'rectangle' | 'polygon' | 'circle' | 'freeform' | null;
14
12
 
15
13
  /**
@@ -40,7 +38,7 @@ interface SvgDrawingCanvasProps {
40
38
  existingAnnotations?: Annotation[];
41
39
  drawingMode: DrawingMode;
42
40
  selectedMotivation?: SelectionMotivation | null;
43
- eventBus?: EventBus;
41
+ session?: SemiontSession | null | undefined;
44
42
  hoveredAnnotationId?: string | null;
45
43
  selectedAnnotationId?: string | null;
46
44
  hoverDelayMs?: number;
@@ -57,7 +55,7 @@ export function SvgDrawingCanvas({
57
55
  existingAnnotations = [],
58
56
  drawingMode,
59
57
  selectedMotivation,
60
- eventBus,
58
+ session,
61
59
  hoveredAnnotationId,
62
60
  selectedAnnotationId
63
61
  }: SvgDrawingCanvasProps) {
@@ -212,7 +210,7 @@ export function SvgDrawingCanvas({
212
210
  });
213
211
 
214
212
  if (clickedAnnotation) {
215
- eventBus?.get('browse:click').next({ annotationId: clickedAnnotation.id, motivation: clickedAnnotation.motivation });
213
+ session?.client.browse.click(clickedAnnotation.id, clickedAnnotation.motivation);
216
214
  setIsDrawing(false);
217
215
  setStartPoint(null);
218
216
  setCurrentPoint(null);
@@ -274,14 +272,11 @@ export function SvgDrawingCanvas({
274
272
  );
275
273
 
276
274
  // Emit annotation:requested event with SvgSelector
277
- if (eventBus && selectedMotivation) {
278
- eventBus.get('mark:requested').next({
279
- selector: {
280
- type: 'SvgSelector',
281
- value: nativeSvg
282
- },
283
- motivation: selectedMotivation
284
- });
275
+ if (session && selectedMotivation) {
276
+ session.client.mark.request(
277
+ { type: 'SvgSelector', value: nativeSvg },
278
+ selectedMotivation,
279
+ );
285
280
  }
286
281
 
287
282
  // Reset drawing state
@@ -338,7 +333,7 @@ export function SvgDrawingCanvas({
338
333
  displayWidth={displayDimensions.width}
339
334
  displayHeight={displayDimensions.height}
340
335
  hoverDelayMs={hoverDelayMs}
341
- {...(eventBus && { eventBus })}
336
+ {...(session && { session })}
342
337
  {...(hoveredAnnotationId !== undefined && { hoveredAnnotationId })}
343
338
  {...(selectedAnnotationId !== undefined && { selectedAnnotationId })}
344
339
  />
@@ -2,8 +2,7 @@
2
2
 
3
3
  import React, { useState } from 'react';
4
4
  import type { GatheredContext } from '@semiont/core';
5
- import { LOCALES } from '@semiont/api-client';
6
-
5
+ import { LOCALES } from '@semiont/core';
7
6
  export interface GenerationConfig {
8
7
  title: string;
9
8
  storagePath: string;