@semiont/react-ui 0.4.20 → 0.4.21

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 (108) hide show
  1. package/README.md +8 -5
  2. package/dist/{PdfAnnotationCanvas.client-CHDCGQBR.mjs → PdfAnnotationCanvas.client-6ZGFEN2N.mjs} +9 -13
  3. package/dist/PdfAnnotationCanvas.client-6ZGFEN2N.mjs.map +1 -0
  4. package/dist/TranslationManager-9Xj3MIWQ.d.mts +16 -0
  5. package/dist/chunk-KEDFYI6N.mjs +7788 -0
  6. package/dist/chunk-KEDFYI6N.mjs.map +1 -0
  7. package/dist/index.d.mts +171 -1140
  8. package/dist/index.mjs +3263 -13644
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/test-utils.d.mts +46 -21
  11. package/dist/test-utils.mjs +2499 -87
  12. package/dist/test-utils.mjs.map +1 -1
  13. package/package.json +1 -2
  14. package/src/components/AnnotateReferencesProgressWidget.tsx +21 -28
  15. package/src/components/CodeMirrorRenderer.tsx +9 -11
  16. package/src/components/StatusDisplay.tsx +42 -16
  17. package/src/components/Toolbar.tsx +4 -4
  18. package/src/components/__tests__/AnnotateReferencesProgressWidget.test.tsx +34 -20
  19. package/src/components/__tests__/StatusDisplay.test.tsx +47 -64
  20. package/src/components/__tests__/Toolbar.test.tsx +4 -4
  21. package/src/components/annotation/AnnotateToolbar.tsx +8 -7
  22. package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +31 -77
  23. package/src/components/annotation-popups/__tests__/JsonLdView.test.tsx +0 -1
  24. package/src/components/image-annotation/AnnotationOverlay.tsx +12 -13
  25. package/src/components/image-annotation/SvgDrawingCanvas.tsx +7 -7
  26. package/src/components/modals/PermissionDeniedModal.tsx +11 -11
  27. package/src/components/modals/ReferenceWizardModal.tsx +14 -18
  28. package/src/components/modals/ResourceSearchModal.tsx +10 -6
  29. package/src/components/modals/SearchModal.tsx +10 -6
  30. package/src/components/modals/SessionExpiredModal.tsx +11 -11
  31. package/src/components/modals/__tests__/PermissionDeniedModal.test.tsx +7 -7
  32. package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +10 -8
  33. package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +10 -7
  34. package/src/components/modals/__tests__/SessionExpiredModal.test.tsx +5 -5
  35. package/src/components/navigation/CollapsibleResourceNavigation.tsx +10 -10
  36. package/src/components/navigation/ObservableLink.tsx +6 -6
  37. package/src/components/navigation/SimpleNavigation.tsx +4 -4
  38. package/src/components/navigation/__tests__/ObservableLink.test.tsx +4 -4
  39. package/src/components/navigation/__tests__/SimpleNavigation.test.tsx +4 -4
  40. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +9 -11
  41. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +0 -1
  42. package/src/components/resource/AnnotateView.tsx +7 -6
  43. package/src/components/resource/AnnotationHistory.tsx +9 -12
  44. package/src/components/resource/BrowseView.tsx +8 -7
  45. package/src/components/resource/ResourceViewer.tsx +17 -25
  46. package/src/components/resource/__tests__/AnnotationHistory.test.tsx +54 -192
  47. package/src/components/resource/__tests__/BrowseView.test.tsx +34 -83
  48. package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +40 -31
  49. package/src/components/resource/panels/AssessmentEntry.tsx +5 -4
  50. package/src/components/resource/panels/AssessmentPanel.tsx +19 -15
  51. package/src/components/resource/panels/AssistSection.tsx +11 -13
  52. package/src/components/resource/panels/CollaborationPanel.tsx +29 -7
  53. package/src/components/resource/panels/CommentEntry.tsx +5 -4
  54. package/src/components/resource/panels/CommentsPanel.tsx +9 -11
  55. package/src/components/resource/panels/HighlightEntry.tsx +5 -4
  56. package/src/components/resource/panels/HighlightPanel.tsx +10 -11
  57. package/src/components/resource/panels/ReferenceEntry.tsx +8 -8
  58. package/src/components/resource/panels/ReferencesPanel.tsx +13 -12
  59. package/src/components/resource/panels/ResourceInfoPanel.tsx +7 -6
  60. package/src/components/resource/panels/TagEntry.tsx +5 -4
  61. package/src/components/resource/panels/TaggingPanel.tsx +10 -16
  62. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +3 -2
  63. package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +18 -52
  64. package/src/components/resource/panels/__tests__/CollaborationPanel.test.tsx +51 -20
  65. package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +18 -56
  66. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +0 -1
  67. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +4 -5
  68. package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +153 -0
  69. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +51 -106
  70. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +15 -47
  71. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +15 -47
  72. package/src/components/settings/SettingsPanel.tsx +8 -8
  73. package/src/components/settings/__tests__/SettingsPanel.test.tsx +12 -12
  74. package/src/features/admin-devops/components/AdminDevOpsPage.tsx +1 -1
  75. package/src/features/admin-exchange/components/AdminExchangePage.tsx +1 -1
  76. package/src/features/admin-exchange/components/ImportCard.tsx +2 -6
  77. package/src/features/admin-security/components/AdminSecurityPage.tsx +1 -1
  78. package/src/features/admin-users/components/AdminUsersPage.tsx +1 -1
  79. package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -1
  80. package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -1
  81. package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -1
  82. package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +1 -1
  83. package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +5 -3
  84. package/src/features/resource-compose/components/ResourceComposePage.tsx +5 -22
  85. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +4 -3
  86. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +1 -1
  87. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +38 -45
  88. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +123 -192
  89. package/dist/KnowledgeBaseSessionContext-BNNunwzO.d.mts +0 -175
  90. package/dist/PdfAnnotationCanvas.client-CHDCGQBR.mjs.map +0 -1
  91. package/dist/chunk-OZICDVH7.mjs +0 -62
  92. package/dist/chunk-OZICDVH7.mjs.map +0 -1
  93. package/dist/chunk-R4CCMFJH.mjs +0 -877
  94. package/dist/chunk-R4CCMFJH.mjs.map +0 -1
  95. package/dist/chunk-VN5NY4SN.mjs +0 -200
  96. package/dist/chunk-VN5NY4SN.mjs.map +0 -1
  97. package/src/components/modals/ProposeEntitiesModal.tsx +0 -179
  98. package/src/components/modals/__tests__/ProposeEntitiesModal.test.tsx +0 -129
  99. package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +0 -323
  100. package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +0 -245
  101. package/src/features/resource-viewer/__tests__/AnnotationProgressDismissal.test.tsx +0 -303
  102. package/src/features/resource-viewer/__tests__/BindFlowIntegration.test.tsx +0 -150
  103. package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +0 -243
  104. package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +0 -383
  105. package/src/features/resource-viewer/__tests__/ResourceMutations.test.tsx +0 -299
  106. package/src/features/resource-viewer/__tests__/ToastNotifications.test.tsx +0 -186
  107. package/src/features/resource-viewer/__tests__/YieldFlowIntegration.test.tsx +0 -429
  108. package/src/features/resource-viewer/__tests__/annotation-progress-flow.test.tsx +0 -348
@@ -5,8 +5,8 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
5
5
  import userEvent from '@testing-library/user-event';
6
6
  import '@testing-library/jest-dom';
7
7
  import { TaggingPanel } from '../TaggingPanel';
8
- import { EventBusProvider, useEventBus } from '../../../../contexts/EventBusContext';
9
- import type { components } from '@semiont/core';
8
+ import type { components, EventBus } from '@semiont/core';
9
+ import { createTestSemiontWrapper } from '../../../../test-utils';
10
10
 
11
11
  type Annotation = components['schemas']['Annotation'];
12
12
 
@@ -18,59 +18,27 @@ interface TrackedEvent {
18
18
 
19
19
  function createEventTracker() {
20
20
  const events: TrackedEvent[] = [];
21
-
22
- function EventTrackingWrapper({ children }: { children: React.ReactNode }) {
23
- const eventBus = useEventBus();
24
-
25
- React.useEffect(() => {
26
- const handlers: Array<() => void> = [];
27
-
28
- const trackEvent = (eventName: string) => (payload: any) => {
29
- events.push({ event: eventName, payload });
30
- };
31
-
32
- const panelEvents = ['mark:submit', 'mark:assist-request'] as const;
33
-
34
- panelEvents.forEach(eventName => {
35
- const handler = trackEvent(eventName);
36
- const subscription = eventBus.get(eventName).subscribe(handler);
37
- handlers.push(subscription);
38
- });
39
-
40
- return () => {
41
- handlers.forEach(sub => sub.unsubscribe());
42
- };
43
- }, [eventBus]);
44
-
45
- return <>{children}</>;
46
- }
47
-
48
21
  return {
49
- EventTrackingWrapper,
50
22
  events,
51
- clear: () => {
52
- events.length = 0;
23
+ clear: () => { events.length = 0; },
24
+ _attach(eventBus: EventBus) {
25
+ const panelEvents = ['mark:submit', 'mark:assist-request'] as const;
26
+ panelEvents.forEach((eventName) => {
27
+ eventBus.get(eventName).subscribe((payload: any) => {
28
+ events.push({ event: eventName, payload });
29
+ });
30
+ });
53
31
  },
54
32
  };
55
33
  }
56
34
 
57
- // Helper to render with EventBusProvider
58
35
  const renderWithEventBus = (component: React.ReactElement, tracker?: ReturnType<typeof createEventTracker>) => {
59
- if (tracker) {
60
- return render(
61
- <EventBusProvider>
62
- <tracker.EventTrackingWrapper>
63
- {component}
64
- </tracker.EventTrackingWrapper>
65
- </EventBusProvider>
66
- );
67
- }
68
-
69
- return render(
70
- <EventBusProvider>
71
- {component}
72
- </EventBusProvider>
36
+ const { SemiontWrapper, eventBus } = createTestSemiontWrapper();
37
+ if (tracker) tracker._attach(eventBus);
38
+ const Wrapper = ({ children }: { children: React.ReactNode }) => (
39
+ <SemiontWrapper>{children}</SemiontWrapper>
73
40
  );
41
+ return render(component, { wrapper: Wrapper });
74
42
  };
75
43
 
76
44
  // Mock TranslationContext
@@ -4,7 +4,7 @@ import React, { useEffect } from 'react';
4
4
  import { LOCALES } from '@semiont/api-client';
5
5
  import { useTranslations } from '../../contexts/TranslationContext';
6
6
  import { useLanguageChangeAnnouncements } from '../LiveRegion';
7
- import { useEventBus } from '../../contexts/EventBusContext';
7
+ import { useSemiont } from '../../session/SemiontProvider';
8
8
  import './SettingsPanel.css';
9
9
 
10
10
  interface SettingsPanelProps {
@@ -31,7 +31,7 @@ export function SettingsPanel({
31
31
  hoverDelayMs
32
32
  }: SettingsPanelProps) {
33
33
  const t = useTranslations('Settings');
34
- const eventBus = useEventBus();
34
+ const semiont = useSemiont();
35
35
  const { announceLanguageChanging, announceLanguageChanged } = useLanguageChangeAnnouncements();
36
36
 
37
37
  // Track previous locale to detect changes
@@ -41,7 +41,7 @@ export function SettingsPanel({
41
41
  const handleLocaleChange = (newLocale: string) => {
42
42
  const localeName = LOCALES.find(l => l.code === newLocale)?.nativeName || newLocale;
43
43
  announceLanguageChanging(localeName);
44
- eventBus.get('settings:locale-changed').next({ locale: newLocale });
44
+ semiont.emit('settings:locale-changed', { locale: newLocale });
45
45
  };
46
46
 
47
47
  // Announce when language has successfully changed
@@ -70,7 +70,7 @@ export function SettingsPanel({
70
70
  type="button"
71
71
  role="switch"
72
72
  aria-checked={showLineNumbers}
73
- onClick={() => eventBus.get('settings:line-numbers-toggled').next(undefined)}
73
+ onClick={() => semiont.emit('settings:line-numbers-toggled', undefined)}
74
74
  className={`semiont-toggle ${
75
75
  showLineNumbers ? 'semiont-toggle--active' : ''
76
76
  }`}
@@ -94,7 +94,7 @@ export function SettingsPanel({
94
94
  </label>
95
95
  <div className="semiont-settings-panel__button-group">
96
96
  <button
97
- onClick={() => eventBus.get('settings:theme-changed').next({ theme: 'light' })}
97
+ onClick={() => semiont.emit('settings:theme-changed', { theme: 'light' })}
98
98
  className={`semiont-panel-button ${
99
99
  theme === 'light' ? 'semiont-panel-button-active' : ''
100
100
  }`}
@@ -103,7 +103,7 @@ export function SettingsPanel({
103
103
  ☀️ {t('themeLight')}
104
104
  </button>
105
105
  <button
106
- onClick={() => eventBus.get('settings:theme-changed').next({ theme: 'dark' })}
106
+ onClick={() => semiont.emit('settings:theme-changed', { theme: 'dark' })}
107
107
  className={`semiont-panel-button ${
108
108
  theme === 'dark' ? 'semiont-panel-button-active' : ''
109
109
  }`}
@@ -112,7 +112,7 @@ export function SettingsPanel({
112
112
  🌙 {t('themeDark')}
113
113
  </button>
114
114
  <button
115
- onClick={() => eventBus.get('settings:theme-changed').next({ theme: 'system' })}
115
+ onClick={() => semiont.emit('settings:theme-changed', { theme: 'system' })}
116
116
  className={`semiont-panel-button ${
117
117
  theme === 'system' ? 'semiont-panel-button-active' : ''
118
118
  }`}
@@ -170,7 +170,7 @@ export function SettingsPanel({
170
170
  max="500"
171
171
  step="50"
172
172
  value={hoverDelayMs}
173
- onChange={(e) => eventBus.get('settings:hover-delay-changed').next({ hoverDelayMs: Number(e.target.value) })}
173
+ onChange={(e) => semiont.emit('settings:hover-delay-changed', { hoverDelayMs: Number(e.target.value) })}
174
174
  className="semiont-slider"
175
175
  aria-describedby="hover-delay-description"
176
176
  />
@@ -63,12 +63,12 @@ describe('SettingsPanel', () => {
63
63
 
64
64
  it('emits settings:line-numbers-toggled on toggle click', () => {
65
65
  const handler = vi.fn();
66
- const { eventBus } = renderWithProviders(
66
+ const { shellBus } = renderWithProviders(
67
67
  <SettingsPanel {...defaultProps} />,
68
- { returnEventBus: true }
68
+ { returnShellBus: true }
69
69
  );
70
70
 
71
- const sub = eventBus!.get('settings:line-numbers-toggled').subscribe(handler);
71
+ const sub = shellBus!.get('settings:line-numbers-toggled').subscribe(handler);
72
72
  fireEvent.click(screen.getByRole('switch'));
73
73
  expect(handler).toHaveBeenCalled();
74
74
  sub.unsubscribe();
@@ -94,12 +94,12 @@ describe('SettingsPanel', () => {
94
94
 
95
95
  it('emits settings:theme-changed on theme button click', () => {
96
96
  const handler = vi.fn();
97
- const { eventBus } = renderWithProviders(
97
+ const { shellBus } = renderWithProviders(
98
98
  <SettingsPanel {...defaultProps} />,
99
- { returnEventBus: true }
99
+ { returnShellBus: true }
100
100
  );
101
101
 
102
- const sub = eventBus!.get('settings:theme-changed').subscribe(handler);
102
+ const sub = shellBus!.get('settings:theme-changed').subscribe(handler);
103
103
  fireEvent.click(screen.getByText(/Settings.themeDark/));
104
104
  expect(handler).toHaveBeenCalledWith({ theme: 'dark' });
105
105
  sub.unsubscribe();
@@ -125,12 +125,12 @@ describe('SettingsPanel', () => {
125
125
 
126
126
  it('emits settings:locale-changed on language change', () => {
127
127
  const handler = vi.fn();
128
- const { eventBus } = renderWithProviders(
128
+ const { shellBus } = renderWithProviders(
129
129
  <SettingsPanel {...defaultProps} />,
130
- { returnEventBus: true }
130
+ { returnShellBus: true }
131
131
  );
132
132
 
133
- const sub = eventBus!.get('settings:locale-changed').subscribe(handler);
133
+ const sub = shellBus!.get('settings:locale-changed').subscribe(handler);
134
134
  fireEvent.change(screen.getByLabelText('Settings.language'), {
135
135
  target: { value: 'fr' },
136
136
  });
@@ -173,12 +173,12 @@ describe('SettingsPanel', () => {
173
173
 
174
174
  it('emits settings:hover-delay-changed on slider change', () => {
175
175
  const handler = vi.fn();
176
- const { eventBus } = renderWithProviders(
176
+ const { shellBus } = renderWithProviders(
177
177
  <SettingsPanel {...defaultProps} />,
178
- { returnEventBus: true }
178
+ { returnShellBus: true }
179
179
  );
180
180
 
181
- const sub = eventBus!.get('settings:hover-delay-changed').subscribe(handler);
181
+ const sub = shellBus!.get('settings:hover-delay-changed').subscribe(handler);
182
182
  fireEvent.change(screen.getByLabelText('Settings.hoverDelay'), {
183
183
  target: { value: '300' },
184
184
  });
@@ -9,7 +9,7 @@ import React from 'react';
9
9
  import {
10
10
  CommandLineIcon
11
11
  } from '@heroicons/react/24/outline';
12
- import { COMMON_PANELS, type ToolbarPanelType } from '../../../hooks/usePanelBrowse';
12
+ import { COMMON_PANELS, type ToolbarPanelType } from '@semiont/api-client';
13
13
 
14
14
  export interface DevOpsFeature {
15
15
  title: string;
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import React from 'react';
8
- import { COMMON_PANELS, type ToolbarPanelType } from '../../../hooks/usePanelBrowse';
8
+ import { COMMON_PANELS, type ToolbarPanelType } from '@semiont/api-client';
9
9
  import { ExportCard, type ExportCardTranslations } from './ExportCard';
10
10
  import { ImportCard, type ImportCardProps, type ImportCardTranslations } from './ImportCard';
11
11
  import { ImportProgress, type ImportProgressTranslations } from './ImportProgress';
@@ -5,13 +5,9 @@
5
5
  */
6
6
 
7
7
  import React, { useRef, useState, useCallback } from 'react';
8
+ import type { ImportPreview } from '@semiont/api-client';
8
9
 
9
- export interface ImportPreview {
10
- format: string;
11
- version: number;
12
- sourceUrl: string;
13
- stats: Record<string, number>;
14
- }
10
+ export type { ImportPreview };
15
11
 
16
12
  export interface ImportCardTranslations {
17
13
  title: string;
@@ -12,7 +12,7 @@ import {
12
12
  CheckCircleIcon,
13
13
  InformationCircleIcon
14
14
  } from '@heroicons/react/24/outline';
15
- import { COMMON_PANELS, type ToolbarPanelType } from '../../../hooks/usePanelBrowse';
15
+ import { COMMON_PANELS, type ToolbarPanelType } from '@semiont/api-client';
16
16
 
17
17
  export interface OAuthProvider {
18
18
  name: string;
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import React, { useState } from 'react';
9
- import { COMMON_PANELS, type ToolbarPanelType } from '../../../hooks/usePanelBrowse';
9
+ import { COMMON_PANELS, type ToolbarPanelType } from '@semiont/api-client';
10
10
  import {
11
11
  PlusIcon,
12
12
  MagnifyingGlassIcon,
@@ -11,7 +11,7 @@ import {
11
11
  PlusIcon,
12
12
  ExclamationCircleIcon
13
13
  } from '@heroicons/react/24/outline';
14
- import { COMMON_PANELS, type ToolbarPanelType } from '../../../hooks/usePanelBrowse';
14
+ import { COMMON_PANELS, type ToolbarPanelType } from '@semiont/api-client';
15
15
 
16
16
  export interface EntityTagsPageProps {
17
17
  // Data props
@@ -7,7 +7,7 @@
7
7
 
8
8
  import React from 'react';
9
9
  import { ClockIcon } from '@heroicons/react/24/outline';
10
- import { COMMON_PANELS, type ToolbarPanelType } from '../../../hooks/usePanelBrowse';
10
+ import { COMMON_PANELS, type ToolbarPanelType } from '@semiont/api-client';
11
11
 
12
12
  export interface RecentDocumentsPageProps {
13
13
  // Data props
@@ -11,7 +11,7 @@ import {
11
11
  ScaleIcon,
12
12
  LightBulbIcon
13
13
  } from '@heroicons/react/24/outline';
14
- import { COMMON_PANELS, type ToolbarPanelType } from '../../../hooks/usePanelBrowse';
14
+ import { COMMON_PANELS, type ToolbarPanelType } from '@semiont/api-client';
15
15
  import type { TagSchema } from '@semiont/react-ui';
16
16
 
17
17
  export interface TagSchemasPageProps {
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import React from 'react';
9
- import { COMMON_PANELS, type ToolbarPanelType } from '../../../hooks/usePanelBrowse';
9
+ import { COMMON_PANELS, type ToolbarPanelType } from '@semiont/api-client';
10
10
  import { ExportCard, type ExportCardTranslations } from '../../admin-exchange/components/ExportCard';
11
11
  import { ImportCard, type ImportCardProps, type ImportCardTranslations } from '../../admin-exchange/components/ImportCard';
12
12
  import { ImportProgress, type ImportProgressTranslations } from '../../admin-exchange/components/ImportProgress';
@@ -9,7 +9,7 @@ import { describe, it, expect, vi } from 'vitest';
9
9
  import { render, screen, fireEvent, waitFor } from '@testing-library/react';
10
10
  import { ResourceComposePage } from '../components/ResourceComposePage';
11
11
  import type { ResourceComposePageProps, SaveResourceParams } from '../components/ResourceComposePage';
12
- import { EventBusProvider } from '../../../contexts/EventBusContext';
12
+ import { createTestSemiontWrapper } from '../../../test-utils';
13
13
 
14
14
  // Mock CodeMirrorRenderer to avoid CodeMirror dependencies
15
15
  vi.mock('../../../components/CodeMirrorRenderer', () => ({
@@ -72,9 +72,11 @@ const createMockProps = (overrides?: Partial<ResourceComposePageProps>): Resourc
72
72
  ...overrides,
73
73
  });
74
74
 
75
- // Helper to render with EventBusProvider
75
+ // Helper to render with a session-capable tree (provides the event bus the
76
+ // component reaches for via useSemiont/useObservable).
76
77
  const renderWithProviders = (ui: React.ReactElement) => {
77
- return render(<EventBusProvider>{ui}</EventBusProvider>);
78
+ const { SemiontWrapper } = createTestSemiontWrapper();
79
+ return render(<SemiontWrapper>{ui}</SemiontWrapper>);
78
80
  };
79
81
 
80
82
  describe('ResourceComposePage', () => {
@@ -7,34 +7,17 @@
7
7
  */
8
8
 
9
9
  import React, { useState, useEffect } from 'react';
10
- import type { components, GatheredContext } from '@semiont/core';
11
- import { isImageMimeType, isPdfMimeType, LOCALES } from '@semiont/api-client';
12
- import { COMMON_PANELS, type ToolbarPanelType } from '../../../hooks/usePanelBrowse';
10
+ import type { GatheredContext } from '@semiont/core';
11
+ import { isImageMimeType, isPdfMimeType, LOCALES, type CloneData, type ReferenceData } from '@semiont/api-client';
12
+ import { COMMON_PANELS, type ToolbarPanelType } from '@semiont/api-client';
13
13
  import { buttonStyles } from '../../../lib/button-styles';
14
14
  import { CodeMirrorRenderer } from '../../../components/CodeMirrorRenderer';
15
15
  import { useFormAnnouncements } from '../../../components/LiveRegion';
16
16
 
17
- type ResourceDescriptor = components['schemas']['ResourceDescriptor'];
18
-
19
17
  export interface ResourceComposePageProps {
20
- // Mode detection
21
18
  mode: 'new' | 'clone' | 'reference';
22
-
23
- // Clone mode data
24
- cloneData?: {
25
- sourceResource: ResourceDescriptor;
26
- sourceContent: string;
27
- };
28
-
29
- // Reference completion data
30
- referenceData?: {
31
- annotationUri: string;
32
- sourceDocumentId: string;
33
- name: string;
34
- entityTypes: string[];
35
- };
36
-
37
- // Gathered context from wizard (optional, for reference mode)
19
+ cloneData?: CloneData | null;
20
+ referenceData?: ReferenceData | null;
38
21
  gatheredContext?: GatheredContext | null;
39
22
 
40
23
  // Available options
@@ -9,7 +9,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
9
9
  import { render, screen, fireEvent } from '@testing-library/react';
10
10
  import { ResourceDiscoveryPage } from '../components/ResourceDiscoveryPage';
11
11
  import type { ResourceDiscoveryPageProps } from '../components/ResourceDiscoveryPage';
12
- import { EventBusProvider } from '../../../contexts/EventBusContext';
12
+ import { createTestSemiontWrapper } from '../../../test-utils';
13
13
 
14
14
  const createMockResource = (id: string, name: string, entityTypes: string[] = []) => ({
15
15
  '@context': 'https://www.w3.org/ns/anno.jsonld',
@@ -58,9 +58,10 @@ const createMockProps = (overrides?: Partial<ResourceDiscoveryPageProps>): Resou
58
58
  ...overrides,
59
59
  });
60
60
 
61
- // Helper to render with EventBusProvider
61
+ // Helper to render with SemiontProvider (gives components access to session.emit)
62
62
  const renderWithProviders = (ui: React.ReactElement) => {
63
- return render(<EventBusProvider>{ui}</EventBusProvider>);
63
+ const { SemiontWrapper } = createTestSemiontWrapper();
64
+ return render(<SemiontWrapper>{ui}</SemiontWrapper>);
64
65
  };
65
66
 
66
67
  describe('ResourceDiscoveryPage', () => {
@@ -8,7 +8,7 @@
8
8
  import React, { useState, useCallback, useRef } from 'react';
9
9
  import type { components } from '@semiont/core';
10
10
  import { getResourceId } from '@semiont/api-client';
11
- import { COMMON_PANELS, type ToolbarPanelType } from '../../../hooks/usePanelBrowse';
11
+ import { COMMON_PANELS, type ToolbarPanelType } from '@semiont/api-client';
12
12
  import { useRovingTabIndex } from '../../../hooks/useRovingTabIndex';
13
13
  import { Toolbar } from '../../../components/Toolbar';
14
14
  import { ResourceCard } from './ResourceCard';
@@ -10,12 +10,9 @@ import { render, screen, act } from '@testing-library/react';
10
10
  import React from 'react';
11
11
  import { ResourceViewerPage } from '../components/ResourceViewerPage';
12
12
  import type { ResourceViewerPageProps } from '../components/ResourceViewerPage';
13
- // Import directly from context file to bypass mocked barrel export
14
- import { EventBusProvider } from '../../../contexts/EventBusContext';
15
- import { ApiClientProvider } from '../../../contexts/ApiClientContext';
16
- import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
17
13
  import { ToastProvider } from '../../../components/Toast';
18
14
  import { ThemeProvider } from '../../../contexts/ThemeContext';
15
+ import { createTestSemiontWrapper } from '../../../test-utils';
19
16
 
20
17
  // jsdom doesn't implement window.matchMedia — mock it for useTheme
21
18
  Object.defineProperty(window, 'matchMedia', {
@@ -37,32 +34,41 @@ vi.mock('../../../hooks/useResourceContent', () => ({
37
34
  useResourceContent: () => ({ content: 'Test content', loading: false }),
38
35
  }));
39
36
 
40
- vi.mock('../../../lib/api-hooks', () => ({
41
- useResources: () => ({
42
- annotations: { useQuery: () => ({ data: { annotations: [] } }) },
43
- referencedBy: { useQuery: () => ({ data: { referencedBy: [] }, isLoading: false }) },
44
- mediaToken: { useQuery: () => ({ data: { token: 'mock-media-token' }, isLoading: false }) },
45
- update: { useMutation: () => ({ mutateAsync: vi.fn() }) },
46
- generateCloneToken: { useMutation: () => ({ mutateAsync: vi.fn() }) },
47
- }),
48
- useEntityTypes: () => ({
49
- list: { useQuery: () => ({ data: { entityTypes: ['Document', 'Article', 'Book'] } }) },
50
- }),
51
- }));
52
37
 
53
- vi.mock('../../../hooks/useResourceEvents', () => ({
54
- useResourceEvents: () => null,
55
- }));
38
+ // Stub SemiontBrowser whose activeSession$ emits a session carrying a real
39
+ // SemiontApiClient (wired to a dummy baseUrl). The real client surface lets
40
+ // createResourceViewerPageVM run against the full namespace API without us
41
+ // hand-stubbing every method it touches.
42
+ const { stubBrowser } = vi.hoisted(() => {
43
+ const { BehaviorSubject } = require('rxjs');
44
+ const { SemiontApiClient } = require('@semiont/api-client');
45
+ const { baseUrl } = require('@semiont/core');
46
+ const client = new SemiontApiClient({
47
+ baseUrl: baseUrl('http://localhost:4000'),
48
+ });
49
+ const stubActiveSession$ = new BehaviorSubject({ client });
50
+ const stubOpenResources$ = new BehaviorSubject([]);
51
+ const stubBrowser = {
52
+ activeSession$: stubActiveSession$,
53
+ openResources$: stubOpenResources$,
54
+ addOpenResource: vi.fn(),
55
+ removeOpenResource: vi.fn(),
56
+ updateOpenResourceName: vi.fn(),
57
+ reorderOpenResources: vi.fn(),
58
+ emit: vi.fn(),
59
+ on: vi.fn(() => () => {}),
60
+ stream: vi.fn(() => ({ subscribe: () => ({ unsubscribe: () => {} }) })),
61
+ };
62
+ return { stubBrowser };
63
+ });
56
64
 
57
- // Mock dependencies that ResourceViewerPage imports
58
- vi.mock('@tanstack/react-query', async () => {
59
- const actual = await vi.importActual('@tanstack/react-query');
65
+ vi.mock('../../../session/SemiontProvider', async () => {
66
+ const actual = await vi.importActual<typeof import('../../../session/SemiontProvider')>(
67
+ '../../../session/SemiontProvider'
68
+ );
60
69
  return {
61
70
  ...actual,
62
- useQueryClient: () => ({
63
- invalidateQueries: vi.fn(),
64
- setQueryData: vi.fn(),
65
- }),
71
+ useSemiont: () => stubBrowser,
66
72
  };
67
73
  });
68
74
 
@@ -82,12 +88,10 @@ vi.mock('@semiont/react-ui', async () => {
82
88
  createCancelDetectionHandler: () => vi.fn(),
83
89
  useDebouncedCallback: (fn: any) => fn,
84
90
  supportsDetection: () => false,
85
- MakeMeaningEventBusProvider: ({ children }: any) => children,
86
91
  useResourceLoadingAnnouncements: () => ({
87
92
  announceResourceLoading: vi.fn(),
88
93
  announceResourceLoaded: vi.fn(),
89
94
  }),
90
- // Don't mock EventBusProvider, useEventBus - let actual pass through via ...actual
91
95
  useEventSubscriptions: vi.fn(),
92
96
  useResourceAnnotations: () => ({
93
97
  clearNewAnnotationId: vi.fn(),
@@ -99,15 +103,6 @@ useDebouncedCallback: (fn: any) => fn,
99
103
  };
100
104
  });
101
105
 
102
- vi.mock('../../../contexts/OpenResourcesContext', () => ({
103
- useOpenResources: () => ({
104
- openResources: [],
105
- addResource: vi.fn(),
106
- removeResource: vi.fn(),
107
- isResourceOpen: vi.fn().mockReturnValue(false),
108
- }),
109
- }));
110
-
111
106
  vi.mock('../../../contexts/ResourceAnnotationsContext', () => ({
112
107
  useResourceAnnotations: () => ({
113
108
  clearNewAnnotationId: vi.fn(),
@@ -123,6 +118,7 @@ vi.mock('../../../contexts/ResourceAnnotationsContext', () => ({
123
118
  // (the barrel export mock doesn't intercept direct context imports)
124
119
  const mockUseEventSubscriptions = vi.fn();
125
120
  vi.mock('../../../contexts/useEventSubscription', () => ({
121
+ useEventSubscription: vi.fn(),
126
122
  useEventSubscriptions: (...args: unknown[]) => mockUseEventSubscriptions(...args),
127
123
  }));
128
124
 
@@ -154,7 +150,7 @@ const createMockProps = (overrides?: Partial<ResourceViewerPageProps>): Resource
154
150
  Link: ({ children }: any) => <a>{children}</a>,
155
151
  routes: {},
156
152
  refetchDocument: vi.fn().mockResolvedValue(undefined),
157
- streamStatus: 'connected' as const,
153
+ streamStatus: 'open' as const,
158
154
  ToolbarPanels: ({ children, activePanel }: any) =>
159
155
  !activePanel ? null : <div data-testid="toolbar-panels">{children}</div>,
160
156
  ...overrides,
@@ -162,16 +158,13 @@ const createMockProps = (overrides?: Partial<ResourceViewerPageProps>): Resource
162
158
 
163
159
  // Test wrapper to provide all required providers
164
160
  const renderWithProviders = (ui: React.ReactElement) => {
161
+ const { SemiontWrapper } = createTestSemiontWrapper();
165
162
  return render(
166
163
  <ThemeProvider>
167
164
  <ToastProvider>
168
- <AuthTokenProvider token={null}>
169
- <EventBusProvider>
170
- <ApiClientProvider baseUrl="http://localhost:4000">
171
- {ui}
172
- </ApiClientProvider>
173
- </EventBusProvider>
174
- </AuthTokenProvider>
165
+ <SemiontWrapper>
166
+ {ui}
167
+ </SemiontWrapper>
175
168
  </ToastProvider>
176
169
  </ThemeProvider>
177
170
  );