@semiont/react-ui 0.4.6 → 0.4.9

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 (190) hide show
  1. package/dist/{PdfAnnotationCanvas.client-LF6DDTCV.mjs → PdfAnnotationCanvas.client-CW6SKH2U.mjs} +7 -25
  2. package/dist/PdfAnnotationCanvas.client-CW6SKH2U.mjs.map +1 -0
  3. package/dist/{EventBusContext-DUIMowqQ.d.mts → TranslationManager-CudgH3gw.d.mts} +1 -74
  4. package/dist/{ar-3URRW77J.mjs → ar-R4CRNXEF.mjs} +3 -2
  5. package/dist/ar-R4CRNXEF.mjs.map +1 -0
  6. package/dist/{bn-DCQD3XZ5.mjs → bn-CZKGRHTA.mjs} +3 -2
  7. package/dist/bn-CZKGRHTA.mjs.map +1 -0
  8. package/dist/{chunk-XMCUHQ2Y.mjs → chunk-BQJWOK4C.mjs} +15 -34
  9. package/dist/chunk-BQJWOK4C.mjs.map +1 -0
  10. package/dist/{chunk-5JZFKRLW.mjs → chunk-HNZOXH4L.mjs} +33 -35
  11. package/dist/chunk-HNZOXH4L.mjs.map +1 -0
  12. package/dist/{chunk-PWIVZQ4X.mjs → chunk-HVMAGUFA.mjs} +3 -2
  13. package/dist/chunk-HVMAGUFA.mjs.map +1 -0
  14. package/dist/{chunk-4RMWYJUJ.mjs → chunk-OL5UST25.mjs} +31 -31
  15. package/dist/{cs-23KOZUFE.mjs → cs-4WIB2IHH.mjs} +3 -2
  16. package/dist/cs-4WIB2IHH.mjs.map +1 -0
  17. package/dist/{da-OIQ66A42.mjs → da-JWYEUYPX.mjs} +3 -2
  18. package/dist/da-JWYEUYPX.mjs.map +1 -0
  19. package/dist/{de-FCCLKE2X.mjs → de-GWUQZGER.mjs} +3 -2
  20. package/dist/de-GWUQZGER.mjs.map +1 -0
  21. package/dist/{el-3ADITCGI.mjs → el-DM2GT7P5.mjs} +3 -2
  22. package/dist/el-DM2GT7P5.mjs.map +1 -0
  23. package/dist/{en-LNW2A3RA.mjs → en-IUV4ZXKH.mjs} +2 -2
  24. package/dist/{es-POQEEYIW.mjs → es-6LVQIM3D.mjs} +3 -2
  25. package/dist/es-6LVQIM3D.mjs.map +1 -0
  26. package/dist/{fa-RQPXVELG.mjs → fa-IRUJY3QI.mjs} +3 -2
  27. package/dist/fa-IRUJY3QI.mjs.map +1 -0
  28. package/dist/{fi-UXOVOUGT.mjs → fi-53FBOEVT.mjs} +3 -2
  29. package/dist/fi-53FBOEVT.mjs.map +1 -0
  30. package/dist/{fr-6W2T3R7G.mjs → fr-Q5KY7QL6.mjs} +3 -2
  31. package/dist/fr-Q5KY7QL6.mjs.map +1 -0
  32. package/dist/{he-65UHPZIU.mjs → he-HJNKULBY.mjs} +3 -2
  33. package/dist/he-HJNKULBY.mjs.map +1 -0
  34. package/dist/{hi-SGJIVPTN.mjs → hi-UYZ4X6CR.mjs} +3 -2
  35. package/dist/hi-UYZ4X6CR.mjs.map +1 -0
  36. package/dist/{id-EYJJQCS2.mjs → id-UAQMH6U2.mjs} +3 -2
  37. package/dist/id-UAQMH6U2.mjs.map +1 -0
  38. package/dist/index.css +48 -0
  39. package/dist/index.css.map +1 -1
  40. package/dist/index.d.mts +176 -125
  41. package/dist/index.mjs +556 -781
  42. package/dist/index.mjs.map +1 -1
  43. package/dist/{it-IZGQEDO7.mjs → it-C7QEBNFA.mjs} +3 -2
  44. package/dist/it-C7QEBNFA.mjs.map +1 -0
  45. package/dist/{ja-SR272JSY.mjs → ja-THS6AOSJ.mjs} +3 -2
  46. package/dist/ja-THS6AOSJ.mjs.map +1 -0
  47. package/dist/{ko-YWTXVVXE.mjs → ko-XKK3TWQG.mjs} +3 -2
  48. package/dist/ko-XKK3TWQG.mjs.map +1 -0
  49. package/dist/{ms-3K2XSJGM.mjs → ms-GSK7LIF7.mjs} +3 -2
  50. package/dist/ms-GSK7LIF7.mjs.map +1 -0
  51. package/dist/{nl-YIGP4SLE.mjs → nl-KUBWITGY.mjs} +3 -2
  52. package/dist/nl-KUBWITGY.mjs.map +1 -0
  53. package/dist/{no-IFYIL3ND.mjs → no-ECWZUHT6.mjs} +3 -2
  54. package/dist/no-ECWZUHT6.mjs.map +1 -0
  55. package/dist/{pl-6MWSASJR.mjs → pl-PLVWSZWS.mjs} +3 -2
  56. package/dist/pl-PLVWSZWS.mjs.map +1 -0
  57. package/dist/{pt-NZNN6WUN.mjs → pt-AL74ZTKB.mjs} +3 -2
  58. package/dist/pt-AL74ZTKB.mjs.map +1 -0
  59. package/dist/{ro-NF3SMUJS.mjs → ro-WTPHLHGS.mjs} +3 -2
  60. package/dist/ro-WTPHLHGS.mjs.map +1 -0
  61. package/dist/{sv-ZHM7GSTD.mjs → sv-QCLI7SG4.mjs} +3 -2
  62. package/dist/sv-QCLI7SG4.mjs.map +1 -0
  63. package/dist/test-utils.d.mts +1 -4
  64. package/dist/test-utils.mjs +4 -6
  65. package/dist/test-utils.mjs.map +1 -1
  66. package/dist/{th-LX4NO5BJ.mjs → th-WCKVZU6U.mjs} +3 -2
  67. package/dist/th-WCKVZU6U.mjs.map +1 -0
  68. package/dist/{tr-DZ4GDSRR.mjs → tr-2CAFS2XS.mjs} +3 -2
  69. package/dist/tr-2CAFS2XS.mjs.map +1 -0
  70. package/dist/{uk-KC5KVVBY.mjs → uk-TDE4JLCY.mjs} +3 -2
  71. package/dist/uk-TDE4JLCY.mjs.map +1 -0
  72. package/dist/{vi-KNCR3OXZ.mjs → vi-KKXZ4PCX.mjs} +3 -2
  73. package/dist/vi-KKXZ4PCX.mjs.map +1 -0
  74. package/dist/{zh-M2HV2A27.mjs → zh-VH4XN5PV.mjs} +3 -2
  75. package/dist/zh-VH4XN5PV.mjs.map +1 -0
  76. package/package.json +1 -1
  77. package/src/components/Toolbar.tsx +15 -0
  78. package/src/components/__tests__/AnnotateReferencesProgressWidget.test.tsx +2 -6
  79. package/src/components/__tests__/Toolbar.test.tsx +2 -6
  80. package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +1 -2
  81. package/src/components/image-annotation/SvgDrawingCanvas.tsx +4 -7
  82. package/src/components/modals/ConfigureGenerationStep.tsx +54 -60
  83. package/src/components/modals/ReferenceWizardModal.tsx +3 -3
  84. package/src/components/navigation/__tests__/ObservableLink.test.tsx +2 -6
  85. package/src/components/navigation/__tests__/SimpleNavigation.test.tsx +2 -6
  86. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +3 -9
  87. package/src/components/resource/AnnotateView.tsx +4 -5
  88. package/src/components/resource/AnnotationHistory.tsx +2 -5
  89. package/src/components/resource/BrowseView.tsx +2 -3
  90. package/src/components/resource/HistoryEvent.tsx +3 -3
  91. package/src/components/resource/__tests__/BrowseView.test.tsx +1 -2
  92. package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +8 -8
  93. package/src/components/resource/event-formatting.ts +22 -19
  94. package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +1 -2
  95. package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +1 -2
  96. package/src/components/resource/panels/__tests__/AssistSection.test.tsx +0 -2
  97. package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +1 -2
  98. package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +1 -2
  99. package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +1 -2
  100. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +0 -2
  101. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +1 -2
  102. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +1 -2
  103. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +1 -2
  104. package/src/components/resource/panels/__tests__/TagEntry.test.tsx +1 -2
  105. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +1 -2
  106. package/src/components/settings/__tests__/SettingsPanel.test.tsx +1 -2
  107. package/src/components/viewers/ImageViewer.tsx +2 -8
  108. package/src/components/viewers/__tests__/ImageViewer.test.tsx +3 -16
  109. package/src/features/auth/__tests__/SignInForm.a11y.test.tsx +8 -0
  110. package/src/features/auth/auth.css +62 -0
  111. package/src/features/auth/components/SignInForm.tsx +139 -29
  112. package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +2 -6
  113. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +1 -2
  114. package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +1 -2
  115. package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +1 -2
  116. package/src/features/resource-viewer/__tests__/AnnotationProgressDismissal.test.tsx +1 -2
  117. package/src/features/resource-viewer/__tests__/BindFlowIntegration.test.tsx +1 -2
  118. package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +1 -2
  119. package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +1 -2
  120. package/src/features/resource-viewer/__tests__/ResourceMutations.test.tsx +9 -10
  121. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +9 -6
  122. package/src/features/resource-viewer/__tests__/ToastNotifications.test.tsx +2 -12
  123. package/src/features/resource-viewer/__tests__/YieldFlowIntegration.test.tsx +1 -2
  124. package/src/features/resource-viewer/__tests__/annotation-progress-flow.test.tsx +16 -6
  125. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +45 -75
  126. package/src/styles/core/forms.css +32 -0
  127. package/src/styles/core/sliders.css +5 -0
  128. package/translations/ar.json +3 -2
  129. package/translations/bn.json +3 -2
  130. package/translations/cs.json +3 -2
  131. package/translations/da.json +3 -2
  132. package/translations/de.json +3 -2
  133. package/translations/el.json +3 -2
  134. package/translations/en.json +4 -3
  135. package/translations/es.json +3 -2
  136. package/translations/fa.json +3 -2
  137. package/translations/fi.json +3 -2
  138. package/translations/fr.json +3 -2
  139. package/translations/he.json +3 -2
  140. package/translations/hi.json +3 -2
  141. package/translations/id.json +3 -2
  142. package/translations/it.json +3 -2
  143. package/translations/ja.json +3 -2
  144. package/translations/ko.json +3 -2
  145. package/translations/ms.json +3 -2
  146. package/translations/nl.json +3 -2
  147. package/translations/no.json +3 -2
  148. package/translations/pl.json +3 -2
  149. package/translations/pt.json +3 -2
  150. package/translations/ro.json +3 -2
  151. package/translations/sv.json +3 -2
  152. package/translations/th.json +3 -2
  153. package/translations/tr.json +3 -2
  154. package/translations/uk.json +3 -2
  155. package/translations/vi.json +3 -2
  156. package/translations/zh.json +3 -2
  157. package/dist/PdfAnnotationCanvas.client-LF6DDTCV.mjs.map +0 -1
  158. package/dist/ar-3URRW77J.mjs.map +0 -1
  159. package/dist/bn-DCQD3XZ5.mjs.map +0 -1
  160. package/dist/chunk-5JZFKRLW.mjs.map +0 -1
  161. package/dist/chunk-PWIVZQ4X.mjs.map +0 -1
  162. package/dist/chunk-XMCUHQ2Y.mjs.map +0 -1
  163. package/dist/cs-23KOZUFE.mjs.map +0 -1
  164. package/dist/da-OIQ66A42.mjs.map +0 -1
  165. package/dist/de-FCCLKE2X.mjs.map +0 -1
  166. package/dist/el-3ADITCGI.mjs.map +0 -1
  167. package/dist/es-POQEEYIW.mjs.map +0 -1
  168. package/dist/fa-RQPXVELG.mjs.map +0 -1
  169. package/dist/fi-UXOVOUGT.mjs.map +0 -1
  170. package/dist/fr-6W2T3R7G.mjs.map +0 -1
  171. package/dist/he-65UHPZIU.mjs.map +0 -1
  172. package/dist/hi-SGJIVPTN.mjs.map +0 -1
  173. package/dist/id-EYJJQCS2.mjs.map +0 -1
  174. package/dist/it-IZGQEDO7.mjs.map +0 -1
  175. package/dist/ja-SR272JSY.mjs.map +0 -1
  176. package/dist/ko-YWTXVVXE.mjs.map +0 -1
  177. package/dist/ms-3K2XSJGM.mjs.map +0 -1
  178. package/dist/nl-YIGP4SLE.mjs.map +0 -1
  179. package/dist/no-IFYIL3ND.mjs.map +0 -1
  180. package/dist/pl-6MWSASJR.mjs.map +0 -1
  181. package/dist/pt-NZNN6WUN.mjs.map +0 -1
  182. package/dist/ro-NF3SMUJS.mjs.map +0 -1
  183. package/dist/sv-ZHM7GSTD.mjs.map +0 -1
  184. package/dist/th-LX4NO5BJ.mjs.map +0 -1
  185. package/dist/tr-DZ4GDSRR.mjs.map +0 -1
  186. package/dist/uk-KC5KVVBY.mjs.map +0 -1
  187. package/dist/vi-KNCR3OXZ.mjs.map +0 -1
  188. package/dist/zh-M2HV2A27.mjs.map +0 -1
  189. /package/dist/{chunk-4RMWYJUJ.mjs.map → chunk-OL5UST25.mjs.map} +0 -0
  190. /package/dist/{en-LNW2A3RA.mjs.map → en-IUV4ZXKH.mjs.map} +0 -0
@@ -22,7 +22,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
22
22
  import { render, screen, waitFor } from '@testing-library/react';
23
23
  import { act } from 'react';
24
24
  import { useMarkFlow } from '../../../hooks/useMarkFlow';
25
- import { EventBusProvider, resetEventBusForTesting, useEventBus } from '../../../contexts/EventBusContext';
25
+ import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
26
26
  import { ApiClientProvider } from '../../../contexts/ApiClientContext';
27
27
  import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
28
28
  import { SSEClient } from '@semiont/api-client';
@@ -43,7 +43,6 @@ describe('Detection Progress Dismissal Bug', () => {
43
43
  const rUri = resourceId('test');
44
44
 
45
45
  beforeEach(() => {
46
- resetEventBusForTesting();
47
46
  vi.clearAllMocks();
48
47
 
49
48
  mockStream = {
@@ -18,7 +18,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
18
18
  import { render, waitFor } from '@testing-library/react';
19
19
  import { act } from 'react';
20
20
  import { useBindFlow } from '../../../hooks/useBindFlow';
21
- import { EventBusProvider, useEventBus, resetEventBusForTesting } from '../../../contexts/EventBusContext';
21
+ import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
22
22
  import { ApiClientProvider } from '../../../contexts/ApiClientContext';
23
23
  import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
24
24
  import { SSEClient } from '@semiont/api-client';
@@ -42,7 +42,6 @@ describe('Bind Flow - Body Update Integration', () => {
42
42
 
43
43
  beforeEach(() => {
44
44
  vi.clearAllMocks();
45
- resetEventBusForTesting();
46
45
 
47
46
  bindAnnotationSpy = vi.fn().mockImplementation((_rId: any, annId: any, _req: any, opts: any) => {
48
47
  queueMicrotask(() => opts.eventBus.get('bind:finished').next({ annotationId: annId }));
@@ -14,7 +14,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
14
14
  import { render, screen, waitFor } from '@testing-library/react';
15
15
  import { act } from 'react';
16
16
  import { useMarkFlow } from '../../../hooks/useMarkFlow';
17
- import { EventBusProvider, useEventBus, resetEventBusForTesting } from '../../../contexts/EventBusContext';
17
+ import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
18
18
  import { ApiClientProvider } from '../../../contexts/ApiClientContext';
19
19
  import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
20
20
  import { SSEClient } from '@semiont/api-client';
@@ -31,7 +31,6 @@ vi.mock('../../../components/Toast', () => ({
31
31
 
32
32
  describe('REPRODUCING BUG: Detection state not updating', () => {
33
33
  beforeEach(() => {
34
- resetEventBusForTesting();
35
34
  vi.clearAllMocks();
36
35
 
37
36
  // Minimal mock - SSE streams not needed for this test
@@ -26,7 +26,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
26
26
  import { render, screen, waitFor } from '@testing-library/react';
27
27
  import { act } from 'react';
28
28
  import { useMarkFlow } from '../../../hooks/useMarkFlow';
29
- import { EventBusProvider, useEventBus, resetEventBusForTesting } from '../../../contexts/EventBusContext';
29
+ import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
30
30
  import { ApiClientProvider } from '../../../contexts/ApiClientContext';
31
31
  import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
32
32
  import { SSEClient } from '@semiont/api-client';
@@ -60,7 +60,6 @@ describe('Detection Flow - Feature Integration', () => {
60
60
 
61
61
  beforeEach(() => {
62
62
  vi.clearAllMocks();
63
- resetEventBusForTesting();
64
63
 
65
64
  // Create fresh mock stream for each test
66
65
  mockStream = createMockSSEStream();
@@ -27,7 +27,7 @@ import { act } from 'react';
27
27
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
28
28
  import { SemiontApiClient } from '@semiont/api-client';
29
29
  import { resourceId, accessToken } from '@semiont/core';
30
- import { EventBusProvider, useEventBus, resetEventBusForTesting } from '../../../contexts/EventBusContext';
30
+ import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
31
31
  import { useEventSubscriptions } from '../../../contexts/useEventSubscription';
32
32
  import { ApiClientProvider } from '../../../contexts/ApiClientContext';
33
33
  import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
@@ -101,15 +101,15 @@ function renderHarness() {
101
101
  });
102
102
 
103
103
  render(
104
- <AuthTokenProvider token={TEST_TOKEN}>
105
- <ApiClientProvider baseUrl={BASE_URL}>
106
- <QueryClientProvider client={queryClient}>
107
- <EventBusProvider>
104
+ <EventBusProvider>
105
+ <AuthTokenProvider token={TEST_TOKEN}>
106
+ <ApiClientProvider baseUrl={BASE_URL}>
107
+ <QueryClientProvider client={queryClient}>
108
108
  <ResourceMutationHarness onEventBus={(eventBus) => { capturedEventBus = eventBus; }} />
109
- </EventBusProvider>
110
- </QueryClientProvider>
111
- </ApiClientProvider>
112
- </AuthTokenProvider>
109
+ </QueryClientProvider>
110
+ </ApiClientProvider>
111
+ </AuthTokenProvider>
112
+ </EventBusProvider>
113
113
  );
114
114
 
115
115
  const emit = <K extends keyof EventMap>(event: K, payload: EventMap[K]) => {
@@ -128,7 +128,6 @@ describe('Resource mutations — hooks hoisted to top level', () => {
128
128
 
129
129
  beforeEach(() => {
130
130
  vi.clearAllMocks();
131
- resetEventBusForTesting();
132
131
 
133
132
  generateCloneTokenSpy = vi
134
133
  .spyOn(SemiontApiClient.prototype, 'generateCloneToken')
@@ -11,7 +11,7 @@ import React from 'react';
11
11
  import { ResourceViewerPage } from '../components/ResourceViewerPage';
12
12
  import type { ResourceViewerPageProps } from '../components/ResourceViewerPage';
13
13
  // Import directly from context file to bypass mocked barrel export
14
- import { EventBusProvider, resetEventBusForTesting } from '../../../contexts/EventBusContext';
14
+ import { EventBusProvider } from '../../../contexts/EventBusContext';
15
15
  import { ApiClientProvider } from '../../../contexts/ApiClientContext';
16
16
  import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
17
17
  import { ToastProvider } from '../../../components/Toast';
@@ -41,6 +41,7 @@ vi.mock('../../../lib/api-hooks', () => ({
41
41
  useResources: () => ({
42
42
  annotations: { useQuery: () => ({ data: { annotations: [] } }) },
43
43
  referencedBy: { useQuery: () => ({ data: { referencedBy: [] }, isLoading: false }) },
44
+ mediaToken: { useQuery: () => ({ data: { token: 'mock-media-token' }, isLoading: false }) },
44
45
  update: { useMutation: () => ({ mutateAsync: vi.fn() }) },
45
46
  generateCloneToken: { useMutation: () => ({ mutateAsync: vi.fn() }) },
46
47
  }),
@@ -86,7 +87,7 @@ useDebouncedCallback: (fn: any) => fn,
86
87
  announceResourceLoading: vi.fn(),
87
88
  announceResourceLoaded: vi.fn(),
88
89
  }),
89
- // Don't mock EventBusProvider, useEventBus, resetEventBusForTesting - let actual pass through via ...actual
90
+ // Don't mock EventBusProvider, useEventBus - let actual pass through via ...actual
90
91
  useEventSubscriptions: vi.fn(),
91
92
  useResourceAnnotations: () => ({
92
93
  clearNewAnnotationId: vi.fn(),
@@ -153,6 +154,7 @@ const createMockProps = (overrides?: Partial<ResourceViewerPageProps>): Resource
153
154
  Link: ({ children }: any) => <a>{children}</a>,
154
155
  routes: {},
155
156
  refetchDocument: vi.fn().mockResolvedValue(undefined),
157
+ streamStatus: 'connected' as const,
156
158
  ToolbarPanels: ({ children, activePanel }: any) =>
157
159
  !activePanel ? null : <div data-testid="toolbar-panels">{children}</div>,
158
160
  ...overrides,
@@ -164,9 +166,11 @@ const renderWithProviders = (ui: React.ReactElement) => {
164
166
  <ThemeProvider>
165
167
  <ToastProvider>
166
168
  <AuthTokenProvider token={null}>
167
- <ApiClientProvider baseUrl="http://localhost:4000">
168
- <EventBusProvider>{ui}</EventBusProvider>
169
- </ApiClientProvider>
169
+ <EventBusProvider>
170
+ <ApiClientProvider baseUrl="http://localhost:4000">
171
+ {ui}
172
+ </ApiClientProvider>
173
+ </EventBusProvider>
170
174
  </AuthTokenProvider>
171
175
  </ToastProvider>
172
176
  </ThemeProvider>
@@ -175,7 +179,6 @@ const renderWithProviders = (ui: React.ReactElement) => {
175
179
 
176
180
  describe('ResourceViewerPage', () => {
177
181
  beforeEach(() => {
178
- resetEventBusForTesting();
179
182
  });
180
183
 
181
184
  describe('Basic Rendering', () => {
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { describe, it, expect, vi, beforeEach } from 'vitest';
18
18
  import { render, waitFor, act } from '@testing-library/react';
19
- import { EventBusProvider, resetEventBusForTesting, useEventBus } from '../../../contexts/EventBusContext';
19
+ import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
20
20
  import { ApiClientProvider } from '../../../contexts/ApiClientContext';
21
21
  import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
22
22
  import { resourceId } from '@semiont/core';
@@ -41,7 +41,6 @@ describe('Toast Notifications - Verifies Toast Integration', () => {
41
41
  const rUri = resourceId('test');
42
42
 
43
43
  beforeEach(() => {
44
- resetEventBusForTesting();
45
44
  mockShowSuccess.mockClear();
46
45
  mockShowError.mockClear();
47
46
  });
@@ -115,17 +114,8 @@ describe('Toast Notifications - Verifies Toast Integration', () => {
115
114
  // Emit detection failed event
116
115
  act(() => {
117
116
  eventBusInstance.get('mark:assist-failed').next({
118
- type: 'job.failed' as const,
119
117
  resourceId: 'test' as any,
120
- userId: 'user' as any,
121
- id: 'evt-1' as any,
122
- timestamp: new Date().toISOString(),
123
- version: 1,
124
- payload: {
125
- jobId: 'job-1' as any,
126
- jobType: 'detection',
127
- error: 'AI service unavailable',
128
- },
118
+ message: 'AI service unavailable',
129
119
  });
130
120
  });
131
121
 
@@ -22,7 +22,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
22
22
  import { render, screen, waitFor } from '@testing-library/react';
23
23
  import { act } from 'react';
24
24
  import { useYieldFlow } from '../../../hooks/useYieldFlow';
25
- import { EventBusProvider, useEventBus, resetEventBusForTesting } from '../../../contexts/EventBusContext';
25
+ import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
26
26
  import { ApiClientProvider } from '../../../contexts/ApiClientContext';
27
27
  import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
28
28
  import { useBindFlow } from '../../../hooks/useBindFlow';
@@ -58,7 +58,6 @@ describe('Generation Flow - Feature Integration', () => {
58
58
 
59
59
  beforeEach(() => {
60
60
  vi.clearAllMocks();
61
- resetEventBusForTesting();
62
61
 
63
62
  // Create fresh mock stream for each test
64
63
  mockStream = createMockGenerationStream();
@@ -28,7 +28,7 @@ import userEvent from '@testing-library/user-event';
28
28
  import { act } from 'react';
29
29
  import { HighlightPanel } from '../../../components/resource/panels/HighlightPanel';
30
30
  import { useMarkFlow } from '../../../hooks/useMarkFlow';
31
- import { EventBusProvider, resetEventBusForTesting } from '../../../contexts/EventBusContext';
31
+ import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
32
32
  import { ApiClientProvider } from '../../../contexts/ApiClientContext';
33
33
  import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
34
34
  import { SSEClient } from '@semiont/api-client';
@@ -71,7 +71,11 @@ vi.mock('../../../contexts/TranslationContext', () => ({
71
71
 
72
72
  // Create a mock SSE stream that we can control
73
73
  class MockSSEStream {
74
- constructor(private eventBus: any) {}
74
+ private eventBus: any = null;
75
+
76
+ setEventBus(eventBus: any) {
77
+ this.eventBus = eventBus;
78
+ }
75
79
 
76
80
  close() {
77
81
  // Mock close method
@@ -133,10 +137,18 @@ describe('Detection Progress Flow Integration (Layer 3)', () => {
133
137
  let mockStream: MockSSEStream;
134
138
  const rUri = 'https://example.com/resources/test-resource-1';
135
139
 
140
+ // Helper component that captures the EventBus and wires it into mockStream
141
+ function EventBusCapturer() {
142
+ const eventBus = useEventBus();
143
+ mockStream.setEventBus(eventBus);
144
+ return null;
145
+ }
146
+
136
147
  // Helper to render test harness with composition
137
148
  const renderDetectionFlow = () => {
138
149
  return render(
139
150
  <EventBusProvider>
151
+ <EventBusCapturer />
140
152
  <AuthTokenProvider token={null}>
141
153
  <ApiClientProvider baseUrl="http://localhost:4000">
142
154
  <DetectionFlowTestHarness
@@ -150,12 +162,10 @@ describe('Detection Progress Flow Integration (Layer 3)', () => {
150
162
  };
151
163
 
152
164
  beforeEach(() => {
153
- // Reset event bus for test isolation
154
- const eventBus = resetEventBusForTesting();
155
165
  vi.clearAllMocks();
156
166
 
157
- // Reset mocks - create stream with eventBus
158
- mockStream = new MockSSEStream(eventBus);
167
+ // Create fresh stream for each test
168
+ mockStream = new MockSSEStream();
159
169
 
160
170
  // Spy on SSEClient prototype methods to inject mock stream
161
171
  vi.spyOn(SSEClient.prototype, 'markHighlights').mockReturnValue(mockStream as any);
@@ -9,7 +9,7 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react';
9
9
  import { useQueryClient } from '@tanstack/react-query';
10
10
  import type { components, ResourceId, ResourceEvent, GatheredContext } from '@semiont/core';
11
11
  import { annotationId } from '@semiont/core';
12
- import { getLanguage, getPrimaryRepresentation, getPrimaryMediaType } from '@semiont/api-client';
12
+ import { getLanguage, getPrimaryRepresentation, getPrimaryMediaType, getMimeCategory } from '@semiont/api-client';
13
13
  import { ANNOTATORS } from '@semiont/react-ui';
14
14
  import { ErrorBoundary } from '@semiont/react-ui';
15
15
  import { AnnotationHistory } from '@semiont/react-ui';
@@ -20,15 +20,16 @@ import { JsonLdPanel } from '@semiont/react-ui';
20
20
  import { Toolbar } from '@semiont/react-ui';
21
21
  import { useResourceLoadingAnnouncements } from '@semiont/react-ui';
22
22
  import { ResourceViewer } from '@semiont/react-ui';
23
+ import { useObservable } from '@semiont/react-ui';
23
24
  import { QUERY_KEYS } from '../../../lib/query-keys';
24
25
  import { useResources, useEntityTypes } from '../../../lib/api-hooks';
25
26
  import { useResourceContent } from '../../../hooks/useResourceContent';
27
+ import { useMediaToken } from '../../../hooks/useMediaToken';
26
28
  import { useToast } from '../../../components/Toast';
27
29
  import { useTheme } from '../../../contexts/ThemeContext';
28
30
  import { useLineNumbers } from '../../../hooks/useLineNumbers';
29
31
  import { useHoverDelay } from '../../../hooks/useHoverDelay';
30
32
  import { useResourceEvents } from '../../../hooks/useResourceEvents';
31
- import { useDebouncedCallback } from '../../../hooks/useDebounce';
32
33
  import { useOpenResources } from '../../../contexts/OpenResourcesContext';
33
34
  // Import EventBus hooks directly from context to avoid mocking issues in tests
34
35
  import { useEventBus } from '../../../contexts/EventBusContext';
@@ -38,6 +39,7 @@ import { useApiClient } from '../../../contexts/ApiClientContext';
38
39
  import { useBindFlow } from '../../../hooks/useBindFlow';
39
40
  import { useMarkFlow } from '../../../hooks/useMarkFlow';
40
41
  import { useBeckonFlow } from '../../../hooks/useBeckonFlow';
42
+ import type { StreamStatus } from '../../../hooks/useResourceEvents';
41
43
  import { usePanelBrowse } from '../../../hooks/usePanelBrowse';
42
44
  import { useYieldFlow } from '../../../hooks/useYieldFlow';
43
45
  import { useContextGatherFlow } from '../../../hooks/useContextGatherFlow';
@@ -83,6 +85,11 @@ export interface ResourceViewerPageProps {
83
85
  * Callback to refetch document from parent
84
86
  */
85
87
  refetchDocument: () => Promise<unknown>;
88
+
89
+ /**
90
+ * SSE attention stream connection status for the active workspace
91
+ */
92
+ streamStatus: StreamStatus;
86
93
  }
87
94
 
88
95
  /**
@@ -120,6 +127,7 @@ export function ResourceViewerPage({
120
127
  routes,
121
128
  ToolbarPanels,
122
129
  refetchDocument,
130
+ streamStatus,
123
131
  }: ResourceViewerPageProps) {
124
132
  // Translations
125
133
  const tw = useTranslations('ReferenceWizard');
@@ -127,7 +135,7 @@ export function ResourceViewerPage({
127
135
  // Get unified event bus for subscribing to UI events
128
136
  const eventBus = useEventBus();
129
137
  const client = useApiClient();
130
- const queryClient = useQueryClient();
138
+ const queryClient = useQueryClient(); // retained for non-store queries (events log)
131
139
 
132
140
  // UI state hooks
133
141
  const { showError, showSuccess } = useToast();
@@ -141,10 +149,23 @@ export function ResourceViewerPage({
141
149
  const resources = useResources();
142
150
  const entityTypesAPI = useEntityTypes();
143
151
 
144
- // Load all data
145
- const { content, loading: contentLoading } = useResourceContent(rUri, resource);
152
+ // Determine MIME category to choose content path
153
+ const resourceMediaType = getPrimaryMediaType(resource) || 'text/plain';
154
+ const isBinary = getMimeCategory(resourceMediaType) === 'image';
155
+
156
+ // Text path: fetch and decode representation (disabled for binary — mediaToken path handles those)
157
+ const { content: textContent, loading: textLoading } = useResourceContent(rUri, resource, !isBinary);
146
158
 
147
- const { data: annotationsData } = resources.annotations.useQuery(rUri);
159
+ // Binary path: fetch short-lived media token, construct URL
160
+ const { token: mediaToken, loading: mediaTokenLoading } = useMediaToken(rUri);
161
+ const binaryContent = (isBinary && mediaToken && client)
162
+ ? `${client.baseUrl}/api/resources/${rUri}?token=${mediaToken}`
163
+ : '';
164
+
165
+ const content = isBinary ? binaryContent : textContent;
166
+ const contentLoading = isBinary ? mediaTokenLoading : textLoading;
167
+
168
+ const annotationsData = useObservable(client.stores.annotations.listForResource(rUri));
148
169
  const annotations = useMemo(
149
170
  () => annotationsData?.annotations || [],
150
171
  [annotationsData?.annotations]
@@ -165,7 +186,7 @@ export function ResourceViewerPage({
165
186
  generationProgress,
166
187
  onGenerateDocument,
167
188
  } = useYieldFlow(locale, rUri, clearNewAnnotationId);
168
- const { gatherContext, gatherLoading, gatherError } = useContextGatherFlow(eventBus, { client, resourceId: rUri });
189
+ const { gatherContext, gatherLoading, gatherError } = useContextGatherFlow({ resourceId: rUri });
169
190
 
170
191
  // Wizard state — driven by bind:initiate from ReferenceEntry
171
192
  const [wizardOpen, setWizardOpen] = useState(false);
@@ -183,7 +204,7 @@ export function ResourceViewerPage({
183
204
  setWizardOpen(true);
184
205
 
185
206
  // Trigger context gathering
186
- eventBus.get('gather:requested').next({ annotationId: event.annotationId, resourceId: event.resourceId });
207
+ eventBus.get('gather:requested').next({ correlationId: crypto.randomUUID(), annotationId: event.annotationId, resourceId: event.resourceId });
187
208
  });
188
209
  return () => subscription.unsubscribe();
189
210
  }, [eventBus]);
@@ -241,15 +262,6 @@ export function ResourceViewerPage({
241
262
  });
242
263
  }, []); // eventBus is stable singleton
243
264
 
244
- // Debounced invalidation for real-time events
245
- const debouncedInvalidateAnnotations = useDebouncedCallback(
246
- () => {
247
- queryClient.invalidateQueries({ queryKey: QUERY_KEYS.resources.annotations(rUri) });
248
- queryClient.invalidateQueries({ queryKey: QUERY_KEYS.resources.events(rUri) });
249
- },
250
- 500
251
- );
252
-
253
265
  // Add resource to open tabs when it loads
254
266
  useEffect(() => {
255
267
  if (resource && rUri) {
@@ -262,56 +274,22 @@ export function ResourceViewerPage({
262
274
  }, [resource, rUri, addResource]);
263
275
 
264
276
  // Real-time document events (SSE)
277
+ // Annotation updates are handled by AnnotationStore reacting to EventBus events.
278
+ // Callbacks here only handle non-annotation side effects.
265
279
  useResourceEvents({
266
280
  rUri,
267
281
  autoConnect: true,
268
282
 
269
- // Annotation events - use debounced invalidation to batch rapid updates
270
283
  onAnnotationAdded: useCallback((_event: any) => {
271
- debouncedInvalidateAnnotations();
272
- }, [debouncedInvalidateAnnotations]),
284
+ // Store handles annotation refresh; events log needs explicit invalidation
285
+ queryClient.invalidateQueries({ queryKey: QUERY_KEYS.resources.events(rUri) });
286
+ }, [queryClient, rUri]),
273
287
 
274
288
  onAnnotationRemoved: useCallback((_event: any) => {
275
- debouncedInvalidateAnnotations();
276
- }, [debouncedInvalidateAnnotations]),
277
-
278
- onAnnotationBodyUpdated: useCallback((event: any) => {
279
- // Optimistically update annotations cache with body operations
280
- queryClient.setQueryData(QUERY_KEYS.resources.annotations(rUri), (old: any) => {
281
- if (!old) return old;
282
- return {
283
- ...old,
284
- annotations: old.annotations.map((annotation: any) => {
285
- if (annotation.id === event.payload.annotationId) {
286
- let bodyArray = Array.isArray(annotation.body) ? [...annotation.body] : [];
287
-
288
- for (const op of event.payload.operations || []) {
289
- if (op.op === 'add') {
290
- bodyArray.push(op.item);
291
- } else if (op.op === 'remove') {
292
- bodyArray = bodyArray.filter((item: any) =>
293
- JSON.stringify(item) !== JSON.stringify(op.item)
294
- );
295
- } else if (op.op === 'replace') {
296
- const index = bodyArray.findIndex((item: any) =>
297
- JSON.stringify(item) === JSON.stringify(op.oldItem)
298
- );
299
- if (index !== -1) {
300
- bodyArray[index] = op.newItem;
301
- }
302
- }
303
- }
304
-
305
- return {
306
- ...annotation,
307
- body: bodyArray,
308
- };
309
- }
310
- return annotation;
311
- }),
312
- };
313
- });
289
+ queryClient.invalidateQueries({ queryKey: QUERY_KEYS.resources.events(rUri) });
290
+ }, [queryClient, rUri]),
314
291
 
292
+ onAnnotationBodyUpdated: useCallback((_event: any) => {
315
293
  queryClient.invalidateQueries({ queryKey: QUERY_KEYS.resources.events(rUri) });
316
294
  }, [queryClient, rUri]),
317
295
 
@@ -319,25 +297,21 @@ export function ResourceViewerPage({
319
297
  onDocumentArchived: useCallback((_event: any) => {
320
298
  refetchDocument();
321
299
  showSuccess('This document has been archived');
322
- debouncedInvalidateAnnotations();
323
- }, [refetchDocument, showSuccess, debouncedInvalidateAnnotations]),
300
+ }, [refetchDocument, showSuccess]),
324
301
 
325
302
  onDocumentUnarchived: useCallback((_event: any) => {
326
303
  refetchDocument();
327
304
  showSuccess('This document has been unarchived');
328
- debouncedInvalidateAnnotations();
329
- }, [refetchDocument, showSuccess, debouncedInvalidateAnnotations]),
305
+ }, [refetchDocument, showSuccess]),
330
306
 
331
307
  // Entity tag events
332
308
  onEntityTagAdded: useCallback((_event: any) => {
333
309
  refetchDocument();
334
- debouncedInvalidateAnnotations();
335
- }, [refetchDocument, debouncedInvalidateAnnotations]),
310
+ }, [refetchDocument]),
336
311
 
337
312
  onEntityTagRemoved: useCallback((_event: any) => {
338
313
  refetchDocument();
339
- debouncedInvalidateAnnotations();
340
- }, [refetchDocument, debouncedInvalidateAnnotations]),
314
+ }, [refetchDocument]),
341
315
 
342
316
  onError: useCallback((error: any) => {
343
317
  console.error('[RealTime] Event stream error:', error);
@@ -386,8 +360,7 @@ export function ResourceViewerPage({
386
360
 
387
361
  const handleAnnotationAdded = useCallback((event: Extract<ResourceEvent, { type: 'annotation.added' }>) => {
388
362
  triggerSparkleAnimation(event.payload.annotation.id);
389
- debouncedInvalidateAnnotations();
390
- }, [triggerSparkleAnimation, debouncedInvalidateAnnotations]);
363
+ }, [triggerSparkleAnimation]);
391
364
 
392
365
  const handleAnnotationCreateFailed = useCallback(() => showError('Failed to create annotation'), [showError]);
393
366
  const handleAnnotationDeleteFailed = useCallback(() => showError('Failed to delete annotation'), [showError]);
@@ -399,13 +372,11 @@ export function ResourceViewerPage({
399
372
  const handleSettingsThemeChanged = useCallback(({ theme }: { theme: any }) => setTheme(theme), [setTheme]);
400
373
 
401
374
  const handleDetectionComplete = useCallback(() => {
402
- // Toast notification is handled by useMarkFlow
403
- queryClient.invalidateQueries({ queryKey: QUERY_KEYS.resources.annotations(rUri) });
375
+ // Toast notification is handled by useMarkFlow; store handles annotation refresh
404
376
  queryClient.invalidateQueries({ queryKey: QUERY_KEYS.resources.events(rUri) });
405
377
  }, [queryClient, rUri]);
406
378
  const handleDetectionFailed = useCallback(() => {
407
- // Error notification is handled by useMarkFlow
408
- queryClient.invalidateQueries({ queryKey: QUERY_KEYS.resources.annotations(rUri) });
379
+ // Error notification is handled by useMarkFlow; store handles annotation refresh
409
380
  queryClient.invalidateQueries({ queryKey: QUERY_KEYS.resources.events(rUri) });
410
381
  }, [queryClient, rUri]);
411
382
  const handleGenerationComplete = useCallback(() => {
@@ -441,7 +412,6 @@ export function ResourceViewerPage({
441
412
  'yield:clone': handleResourceClone,
442
413
  'beckon:sparkle': handleAnnotationSparkle,
443
414
  'mark:added': handleAnnotationAdded,
444
- 'mark:removed': debouncedInvalidateAnnotations,
445
415
  'mark:create-failed': handleAnnotationCreateFailed,
446
416
  'mark:delete-failed': handleAnnotationDeleteFailed,
447
417
  'mark:body-updated': handleAnnotateBodyUpdated,
@@ -656,7 +626,7 @@ export function ResourceViewerPage({
656
626
  {/* Collaboration Panel */}
657
627
  {activePanel === 'collaboration' && (
658
628
  <CollaborationPanel
659
- isConnected={false}
629
+ isConnected={streamStatus === 'connected'}
660
630
  eventCount={0}
661
631
  />
662
632
  )}
@@ -252,3 +252,35 @@
252
252
  /* Note: Basic form controls (.semiont-textarea, .semiont-select, .semiont-checkbox)
253
253
  are now defined in /styles/core/ as they are fundamental UI elements.
254
254
  See: core/textareas.css, core/selects.css, core/checkboxes.css */
255
+
256
+ /* Scrollable form body — keeps form within viewport height */
257
+ .semiont-form--scrollable {
258
+ overflow-y: auto;
259
+ max-height: 70vh;
260
+ padding-right: 0.25rem;
261
+ }
262
+
263
+ /* Inline row — lays out multiple fields side by side */
264
+ .semiont-form__inline-row {
265
+ display: flex;
266
+ flex-direction: row;
267
+ gap: 0.75rem;
268
+ align-items: flex-start;
269
+ }
270
+
271
+ /* Inline field variant — participates in an inline row */
272
+ .semiont-form__field--inline {
273
+ flex-shrink: 0;
274
+ }
275
+
276
+ /* Grow modifier — field takes remaining space */
277
+ .semiont-form__field--grow {
278
+ flex: 1;
279
+ min-width: 0;
280
+ }
281
+
282
+ /* Narrow modifier — fixed compact width for short numeric inputs */
283
+ .semiont-form__field--narrow {
284
+ width: 5.5rem;
285
+ flex-shrink: 0;
286
+ }
@@ -216,6 +216,11 @@
216
216
  color: var(--semiont-text-secondary);
217
217
  }
218
218
 
219
+ /* Small labels variant — reduced font size for compact inline contexts */
220
+ .semiont-slider__labels--small {
221
+ font-size: 0.6875rem;
222
+ }
223
+
219
224
  .semiont-slider__value {
220
225
  text-align: center;
221
226
  font-size: var(--semiont-text-sm);
@@ -5,7 +5,8 @@
5
5
  "resourceInfo": "معلومات المورد",
6
6
  "collaboration": "التعاون",
7
7
  "userAccount": "حساب المستخدم",
8
- "settings": "الإعدادات"
8
+ "settings": "الإعدادات",
9
+ "knowledgeBase": "قاعدة المعرفة"
9
10
  },
10
11
  "ResourceInfoPanel": {
11
12
  "title": "معلومات المورد",
@@ -373,4 +374,4 @@
373
374
  "searching": "جاري البحث...",
374
375
  "search": "بحث"
375
376
  }
376
- }
377
+ }
@@ -5,7 +5,8 @@
5
5
  "resourceInfo": "সম্পদের তথ্য",
6
6
  "collaboration": "সহযোগিতা",
7
7
  "userAccount": "ব্যবহারকারীর অ্যাকাউন্ট",
8
- "settings": "সেটিংস"
8
+ "settings": "সেটিংস",
9
+ "knowledgeBase": "জ্ঞান ভান্ডার"
9
10
  },
10
11
  "ResourceInfoPanel": {
11
12
  "title": "সম্পদের তথ্য",
@@ -373,4 +374,4 @@
373
374
  "searching": "অনুসন্ধান করা হচ্ছে...",
374
375
  "search": "অনুসন্ধান"
375
376
  }
376
- }
377
+ }
@@ -5,7 +5,8 @@
5
5
  "resourceInfo": "Informace o zdroji",
6
6
  "collaboration": "Spolupráce",
7
7
  "userAccount": "Uživatelský účet",
8
- "settings": "Nastavení"
8
+ "settings": "Nastavení",
9
+ "knowledgeBase": "Znalostní báze"
9
10
  },
10
11
  "ResourceInfoPanel": {
11
12
  "title": "Informace o zdroji",
@@ -373,4 +374,4 @@
373
374
  "searching": "Vyhledávání...",
374
375
  "search": "Hledat"
375
376
  }
376
- }
377
+ }
@@ -5,7 +5,8 @@
5
5
  "resourceInfo": "Ressourceinfo",
6
6
  "collaboration": "Samarbejde",
7
7
  "userAccount": "Brugerkonto",
8
- "settings": "Indstillinger"
8
+ "settings": "Indstillinger",
9
+ "knowledgeBase": "Vidensbase"
9
10
  },
10
11
  "ResourceInfoPanel": {
11
12
  "title": "Ressourceinfo",
@@ -373,4 +374,4 @@
373
374
  "searching": "Søger...",
374
375
  "search": "Søg"
375
376
  }
376
- }
377
+ }