@semiont/react-ui 0.4.21 → 0.5.0

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 (82) hide show
  1. package/dist/{PdfAnnotationCanvas.client-6ZGFEN2N.mjs → PdfAnnotationCanvas.client-5QESNO5H.mjs} +10 -9
  2. package/dist/PdfAnnotationCanvas.client-5QESNO5H.mjs.map +1 -0
  3. package/dist/{chunk-KEDFYI6N.mjs → chunk-4NOUO3W6.mjs} +2 -2
  4. package/dist/{chunk-KEDFYI6N.mjs.map → chunk-4NOUO3W6.mjs.map} +1 -1
  5. package/dist/index.d.mts +54 -79
  6. package/dist/index.mjs +255 -254
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/test-utils.d.mts +3 -1
  9. package/dist/test-utils.mjs +14 -8
  10. package/dist/test-utils.mjs.map +1 -1
  11. package/package.json +2 -1
  12. package/src/components/AnnotateReferencesProgressWidget.tsx +1 -1
  13. package/src/components/CodeMirrorRenderer.tsx +5 -3
  14. package/src/components/LiveRegion.tsx +1 -2
  15. package/src/components/StatusDisplay.tsx +1 -1
  16. package/src/components/__tests__/StatusDisplay.test.tsx +3 -1
  17. package/src/components/annotation/AnnotateToolbar.tsx +5 -7
  18. package/src/components/annotation-popups/JsonLdView.tsx +1 -2
  19. package/src/components/annotation-popups/__tests__/JsonLdView.test.tsx +1 -1
  20. package/src/components/image-annotation/AnnotationOverlay.tsx +12 -14
  21. package/src/components/image-annotation/SvgDrawingCanvas.tsx +8 -13
  22. package/src/components/modals/ConfigureGenerationStep.tsx +1 -2
  23. package/src/components/modals/ReferenceWizardModal.tsx +1 -1
  24. package/src/components/modals/ResourceSearchModal.tsx +3 -3
  25. package/src/components/modals/SearchModal.tsx +2 -1
  26. package/src/components/modals/SearchResultsStep.tsx +1 -3
  27. package/src/components/modals/__tests__/SearchModal.accessibility.test.tsx +6 -2
  28. package/src/components/modals/__tests__/SearchModal.basic.test.tsx +6 -2
  29. package/src/components/modals/__tests__/SearchModal.keyboard.test.tsx +6 -2
  30. package/src/components/modals/__tests__/SearchModal.visual.test.tsx +6 -2
  31. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +11 -12
  32. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +1 -1
  33. package/src/components/resource/AnnotateView.tsx +2 -5
  34. package/src/components/resource/BrowseView.tsx +6 -4
  35. package/src/components/resource/ResourceViewer.tsx +7 -11
  36. package/src/components/resource/__tests__/BrowseView.test.tsx +4 -4
  37. package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +1 -0
  38. package/src/components/resource/__tests__/event-formatting.test.ts +6 -2
  39. package/src/components/resource/event-formatting.ts +2 -3
  40. package/src/components/resource/panels/AssessmentEntry.tsx +3 -5
  41. package/src/components/resource/panels/AssessmentPanel.tsx +5 -5
  42. package/src/components/resource/panels/AssistSection.tsx +6 -10
  43. package/src/components/resource/panels/CollaborationPanel.tsx +1 -1
  44. package/src/components/resource/panels/CommentEntry.tsx +3 -5
  45. package/src/components/resource/panels/CommentsPanel.tsx +5 -5
  46. package/src/components/resource/panels/HighlightEntry.tsx +3 -5
  47. package/src/components/resource/panels/HighlightPanel.tsx +3 -3
  48. package/src/components/resource/panels/ReferenceEntry.tsx +7 -9
  49. package/src/components/resource/panels/ReferencesPanel.tsx +8 -11
  50. package/src/components/resource/panels/ResourceInfoPanel.tsx +5 -5
  51. package/src/components/resource/panels/StatisticsPanel.tsx +2 -3
  52. package/src/components/resource/panels/TagEntry.tsx +3 -5
  53. package/src/components/resource/panels/TaggingPanel.tsx +8 -11
  54. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +1 -1
  55. package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +4 -4
  56. package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +4 -5
  57. package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +4 -4
  58. package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +4 -5
  59. package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +4 -4
  60. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +1 -1
  61. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +4 -4
  62. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +21 -14
  63. package/src/components/resource/panels/__tests__/StatisticsPanel.test.tsx +3 -3
  64. package/src/components/resource/panels/__tests__/TagEntry.test.tsx +4 -4
  65. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +4 -5
  66. package/src/components/settings/SettingsPanel.tsx +1 -1
  67. package/src/components/settings/__tests__/SettingsPanel.test.tsx +3 -3
  68. package/src/features/admin-devops/components/AdminDevOpsPage.tsx +1 -2
  69. package/src/features/admin-exchange/components/AdminExchangePage.tsx +1 -1
  70. package/src/features/admin-exchange/components/ImportCard.tsx +1 -2
  71. package/src/features/admin-security/components/AdminSecurityPage.tsx +1 -2
  72. package/src/features/admin-users/components/AdminUsersPage.tsx +1 -1
  73. package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -2
  74. package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -2
  75. package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -1
  76. package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +1 -1
  77. package/src/features/resource-compose/components/ResourceComposePage.tsx +3 -2
  78. package/src/features/resource-discovery/components/ResourceCard.tsx +1 -2
  79. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +3 -4
  80. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +4 -5
  81. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +12 -11
  82. package/dist/PdfAnnotationCanvas.client-6ZGFEN2N.mjs.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@semiont/react-ui",
3
- "version": "0.4.21",
3
+ "version": "0.5.0",
4
4
  "description": "React components and hooks for Semiont",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.mts",
@@ -104,6 +104,7 @@
104
104
  "dependencies": {
105
105
  "@semiont/api-client": "*",
106
106
  "@semiont/core": "*",
107
+ "@semiont/sdk": "*",
107
108
  "react-error-boundary": "^4.1.2"
108
109
  }
109
110
  }
@@ -24,7 +24,7 @@ export function AnnotateReferencesProgressWidget({ progress, annotationType = 'r
24
24
 
25
25
  const handleCancel = () => {
26
26
  // Emit event for job cancellation
27
- session?.client.emit('job:cancel-requested', { jobType: 'annotation' });
27
+ session?.client.job.cancelRequest('annotation');
28
28
  };
29
29
 
30
30
  if (!progress) return null;
@@ -6,7 +6,9 @@ import { EditorState, RangeSetBuilder, StateField, StateEffect, Compartment } fr
6
6
  import { markdown } from '@codemirror/lang-markdown';
7
7
  import { ReferenceResolutionWidget, showWidgetPreview, hideWidgetPreview } from '../lib/codemirror-widgets';
8
8
  import { scrollAnnotationIntoView } from '../lib/scroll-utils';
9
- import { isReference, createHoverHandlers, type SemiontSession } from '@semiont/api-client';
9
+ import { annotationId as toAnnotationId } from '@semiont/core';
10
+ import { isReference } from '@semiont/core';
11
+ import { createHoverHandlers, type SemiontSession } from '@semiont/sdk';
10
12
  import {
11
13
  convertSegmentPositions,
12
14
  computeAnnotationDecorations,
@@ -301,7 +303,7 @@ export function CodeMirrorRenderer({
301
303
  const container = view.dom;
302
304
 
303
305
  const { handleMouseEnter, handleMouseLeave, cleanup: cleanupHover } = createHoverHandlers(
304
- (annotationId) => sessionRef.current?.client.emit('beckon:hover', { annotationId }),
306
+ (id) => sessionRef.current?.client.beckon.hover(id),
305
307
  hoverDelayMs
306
308
  );
307
309
 
@@ -309,7 +311,7 @@ export function CodeMirrorRenderer({
309
311
  const target = e.target as HTMLElement;
310
312
  const annotationElement = target.closest('[data-annotation-id]');
311
313
  const annotationId = annotationElement?.getAttribute('data-annotation-id');
312
- if (annotationId) handleMouseEnter(annotationId);
314
+ if (annotationId) handleMouseEnter(toAnnotationId(annotationId));
313
315
  };
314
316
 
315
317
  const handleMouseOut = (e: MouseEvent) => {
@@ -1,10 +1,9 @@
1
1
  'use client';
2
2
 
3
3
  import React, { useState, createContext, useContext, useCallback } from 'react';
4
- import type { components } from '@semiont/core';
5
4
  import type { Annotator } from '../lib/annotation-registry';
6
5
 
7
- type Annotation = components['schemas']['Annotation'];
6
+ import type { Annotation } from '@semiont/core';
8
7
 
9
8
  interface LiveRegionContextType {
10
9
  announce: (message: string, priority?: 'polite' | 'assertive') => void;
@@ -30,7 +30,7 @@ export function StatusDisplay({
30
30
  if (!semiont) { setLoading(false); return; }
31
31
 
32
32
  const fetchStatus = () => {
33
- semiont.getStatus()
33
+ semiont.admin.status()
34
34
  .then((result) => {
35
35
  setData(result as StatusData);
36
36
  setError(null);
@@ -8,7 +8,9 @@ import { StatusDisplay } from '../StatusDisplay';
8
8
 
9
9
  let mockGetStatus: ReturnType<typeof vi.fn>;
10
10
  const stableMockClient = {
11
- get getStatus() { return mockGetStatus; },
11
+ admin: {
12
+ get status() { return mockGetStatus; },
13
+ },
12
14
  };
13
15
  const stableMockSession = { client: stableMockClient };
14
16
  const stableActiveSession$ = new BehaviorSubject<any>(stableMockSession);
@@ -189,11 +189,9 @@ export function AnnotateToolbar({
189
189
  const handleSelectionClick = (motivation: SelectionMotivation | null) => {
190
190
  // If null is clicked, always deselect. Otherwise toggle.
191
191
  if (motivation === null) {
192
- session?.client.emit('mark:selection-changed', { motivation: null });
192
+ session?.client.mark.changeSelection(null);
193
193
  } else {
194
- session?.client.emit('mark:selection-changed', {
195
- motivation: selectedMotivation === motivation ? null : motivation
196
- });
194
+ session?.client.mark.changeSelection(selectedMotivation === motivation ? null : motivation);
197
195
  }
198
196
  // Close dropdown after selection
199
197
  setSelectionPinned(false);
@@ -201,21 +199,21 @@ export function AnnotateToolbar({
201
199
  };
202
200
 
203
201
  const handleClickClick = (action: ClickAction) => {
204
- session?.client.emit('mark:click-changed', { action });
202
+ session?.client.mark.changeClick(action);
205
203
  // Close dropdown after selection
206
204
  setClickPinned(false);
207
205
  setClickHovered(false);
208
206
  };
209
207
 
210
208
  const handleShapeClick = (shape: ShapeType) => {
211
- session?.client.emit('mark:shape-changed', { shape });
209
+ session?.client.mark.changeShape(shape);
212
210
  // Close dropdown after selection
213
211
  setShapePinned(false);
214
212
  setShapeHovered(false);
215
213
  };
216
214
 
217
215
  const handleModeToggle = () => {
218
- session?.client.emit('mark:mode-toggled', undefined);
216
+ session?.client.mark.toggleMode();
219
217
  setModePinned(false);
220
218
  setModeHovered(false);
221
219
  };
@@ -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', () => {
@@ -1,13 +1,11 @@
1
1
  'use client';
2
2
 
3
3
  import { useMemo } from 'react';
4
- import type { components } from '@semiont/core';
5
- import { createHoverHandlers, getSvgSelector, isHighlight, isReference, isAssessment, isComment, isTag, isBodyResolved, isResolvedReference } from '@semiont/api-client';
6
- import { parseSvgSelector } from '@semiont/api-client';
7
- import type { SemiontSession } from '@semiont/api-client';
8
-
9
- type Annotation = components['schemas']['Annotation'];
10
-
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';
11
9
  interface AnnotationOverlayProps {
12
10
  annotations: Annotation[];
13
11
  imageWidth: number;
@@ -80,7 +78,7 @@ export function AnnotationOverlay({
80
78
  const scaleY = displayHeight / imageHeight;
81
79
 
82
80
  const { handleMouseEnter, handleMouseLeave } = useMemo(
83
- () => createHoverHandlers((annotationId) => session?.client.emit('beckon:hover', { annotationId }), hoverDelayMs),
81
+ () => createHoverHandlers((id) => session?.client.beckon.hover(id), hoverDelayMs),
84
82
  [session, hoverDelayMs]
85
83
  );
86
84
 
@@ -130,7 +128,7 @@ export function AnnotationOverlay({
130
128
  className="semiont-annotation-overlay__shape"
131
129
  data-hovered={isHovered ? 'true' : 'false'}
132
130
  data-selected={isSelected ? 'true' : 'false'}
133
- onClick={() => session?.client.emit('browse:click', { annotationId: annotation.id, motivation: annotation.motivation })}
131
+ onClick={() => session?.client.browse.click(annotation.id, annotation.motivation)}
134
132
  onMouseEnter={() => handleMouseEnter(annotation.id)}
135
133
  onMouseLeave={handleMouseLeave}
136
134
  />
@@ -143,7 +141,7 @@ export function AnnotationOverlay({
143
141
  style={{ userSelect: 'none' }}
144
142
  onClick={(e) => {
145
143
  e.stopPropagation();
146
- session?.client.emit('browse:click', { annotationId: annotation.id, motivation: annotation.motivation });
144
+ session?.client.browse.click(annotation.id, annotation.motivation);
147
145
  }}
148
146
  onMouseEnter={() => handleMouseEnter(annotation.id)}
149
147
  onMouseLeave={handleMouseLeave}
@@ -177,7 +175,7 @@ export function AnnotationOverlay({
177
175
  className="semiont-annotation-overlay__shape"
178
176
  data-hovered={isHovered ? 'true' : 'false'}
179
177
  data-selected={isSelected ? 'true' : 'false'}
180
- onClick={() => session?.client.emit('browse:click', { annotationId: annotation.id, motivation: annotation.motivation })}
178
+ onClick={() => session?.client.browse.click(annotation.id, annotation.motivation)}
181
179
  onMouseEnter={() => handleMouseEnter(annotation.id)}
182
180
  onMouseLeave={handleMouseLeave}
183
181
  />
@@ -190,7 +188,7 @@ export function AnnotationOverlay({
190
188
  style={{ userSelect: 'none' }}
191
189
  onClick={(e) => {
192
190
  e.stopPropagation();
193
- session?.client.emit('browse:click', { annotationId: annotation.id, motivation: annotation.motivation });
191
+ session?.client.browse.click(annotation.id, annotation.motivation);
194
192
  }}
195
193
  onMouseEnter={() => handleMouseEnter(annotation.id)}
196
194
  onMouseLeave={handleMouseLeave}
@@ -237,7 +235,7 @@ export function AnnotationOverlay({
237
235
  className="semiont-annotation-overlay__shape"
238
236
  data-hovered={isHovered ? 'true' : 'false'}
239
237
  data-selected={isSelected ? 'true' : 'false'}
240
- onClick={() => session?.client.emit('browse:click', { annotationId: annotation.id, motivation: annotation.motivation })}
238
+ onClick={() => session?.client.browse.click(annotation.id, annotation.motivation)}
241
239
  onMouseEnter={() => handleMouseEnter(annotation.id)}
242
240
  onMouseLeave={handleMouseLeave}
243
241
  />
@@ -250,7 +248,7 @@ export function AnnotationOverlay({
250
248
  style={{ userSelect: 'none' }}
251
249
  onClick={(e) => {
252
250
  e.stopPropagation();
253
- session?.client.emit('browse:click', { annotationId: annotation.id, motivation: annotation.motivation });
251
+ session?.client.browse.click(annotation.id, annotation.motivation);
254
252
  }}
255
253
  onMouseEnter={() => handleMouseEnter(annotation.id)}
256
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 { SemiontSession } from '@semiont/api-client';
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
  /**
@@ -212,7 +210,7 @@ export function SvgDrawingCanvas({
212
210
  });
213
211
 
214
212
  if (clickedAnnotation) {
215
- session?.client.emit('browse:click', { 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);
@@ -275,13 +273,10 @@ export function SvgDrawingCanvas({
275
273
 
276
274
  // Emit annotation:requested event with SvgSelector
277
275
  if (session && selectedMotivation) {
278
- session.client.emit('mark:requested', {
279
- selector: {
280
- type: 'SvgSelector',
281
- value: nativeSvg
282
- },
283
- motivation: selectedMotivation
284
- });
276
+ session.client.mark.request(
277
+ { type: 'SvgSelector', value: nativeSvg },
278
+ selectedMotivation,
279
+ );
285
280
  }
286
281
 
287
282
  // Reset drawing state
@@ -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;
@@ -143,7 +143,7 @@ export function ReferenceWizardModal({
143
143
  if (!annotationId || !context || !resourceId) return;
144
144
  setIsSearching(true);
145
145
  const contextWithHint = userHint ? { ...context, userHint } : context;
146
- session?.client.emit('match:search-requested', {
146
+ session?.client.match.requestSearch({
147
147
  correlationId: crypto.randomUUID(),
148
148
  resourceId,
149
149
  referenceId: annotationId,
@@ -3,13 +3,13 @@
3
3
  import { useState, useEffect, useRef } from 'react';
4
4
  import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react';
5
5
  import { map } from 'rxjs/operators';
6
- import type { components } from '@semiont/core';
7
- import { getResourceId, getPrimaryRepresentation, createSearchPipeline } from '@semiont/api-client';
6
+ import { getResourceId, getPrimaryRepresentation } from '@semiont/core';
7
+ import { createSearchPipeline } from '@semiont/sdk';
8
8
  import { useSemiont } from '../../session/SemiontProvider';
9
9
  import { useObservable } from '../../hooks/useObservable';
10
10
  import { useSearchAnnouncements } from '../../hooks/useSearchAnnouncements';
11
11
 
12
- type ResourceDescriptor = components['schemas']['ResourceDescriptor'];
12
+ import type { ResourceDescriptor } from '@semiont/core';
13
13
 
14
14
  type SearchResult = {
15
15
  id: string;
@@ -3,7 +3,8 @@
3
3
  import React, { useState, useEffect, useRef } from 'react';
4
4
  import { Dialog, DialogPanel, Transition, TransitionChild } from '@headlessui/react';
5
5
  import { map } from 'rxjs/operators';
6
- import { getResourceId, createSearchPipeline } from '@semiont/api-client';
6
+ import { getResourceId } from '@semiont/core';
7
+ import { createSearchPipeline } from '@semiont/sdk';
7
8
  import { useSearchAnnouncements } from '../../hooks/useSearchAnnouncements';
8
9
  import { useSemiont } from '../../session/SemiontProvider';
9
10
  import { useObservable } from '../../hooks/useObservable';
@@ -1,11 +1,9 @@
1
1
  'use client';
2
2
 
3
- import type { components, GatheredContext } from '@semiont/core';
3
+ import type { GatheredContext, ResourceDescriptor } from '@semiont/core';
4
4
  import { ContextSummary } from './ContextSummary';
5
5
  import type { ContextSummaryTranslations } from './ContextSummary';
6
6
 
7
- type ResourceDescriptor = components['schemas']['ResourceDescriptor'];
8
-
9
7
  export type ScoredResult = ResourceDescriptor & {
10
8
  score?: number;
11
9
  matchReason?: string;
@@ -15,9 +15,13 @@ vi.mock('../../../hooks/useSearchAnnouncements', () => ({
15
15
  }));
16
16
 
17
17
  // Mock getResourceId
18
- vi.mock('@semiont/api-client', () => ({
18
+ vi.mock('@semiont/core', async (importOriginal) => {
19
+ const actual = await importOriginal<typeof import('@semiont/core')>();
20
+ return {
21
+ ...actual,
19
22
  getResourceId: vi.fn((resource: any) => resource?.id)
20
- }));
23
+ };
24
+ });
21
25
 
22
26
  describe.skip('SearchModal Component - Accessibility', () => {
23
27
  // TODO: All SearchModal tests skipped due to HeadlessUI Dialog + jsdom memory issues
@@ -16,9 +16,13 @@ vi.mock('../../../hooks/useSearchAnnouncements', () => ({
16
16
  }));
17
17
 
18
18
  // Mock getResourceId
19
- vi.mock('@semiont/api-client', () => ({
19
+ vi.mock('@semiont/core', async (importOriginal) => {
20
+ const actual = await importOriginal<typeof import('@semiont/core')>();
21
+ return {
22
+ ...actual,
20
23
  getResourceId: vi.fn((resource: any) => resource?.id)
21
- }));
24
+ };
25
+ });
22
26
 
23
27
  describe('SearchModal Component - Basic Rendering', () => {
24
28
  const defaultProps = {
@@ -15,9 +15,13 @@ vi.mock('../../../hooks/useSearchAnnouncements', () => ({
15
15
  }));
16
16
 
17
17
  // Mock getResourceId
18
- vi.mock('@semiont/api-client', () => ({
18
+ vi.mock('@semiont/core', async (importOriginal) => {
19
+ const actual = await importOriginal<typeof import('@semiont/core')>();
20
+ return {
21
+ ...actual,
19
22
  getResourceId: vi.fn((resource: any) => resource?.id)
20
- }));
23
+ };
24
+ });
21
25
 
22
26
  describe.skip('SearchModal Component - Keyboard Navigation', () => {
23
27
  // TODO: All SearchModal tests skipped due to HeadlessUI Dialog + jsdom memory issues
@@ -15,9 +15,13 @@ vi.mock('../../../hooks/useSearchAnnouncements', () => ({
15
15
  }));
16
16
 
17
17
  // Mock getResourceId
18
- vi.mock('@semiont/api-client', () => ({
18
+ vi.mock('@semiont/core', async (importOriginal) => {
19
+ const actual = await importOriginal<typeof import('@semiont/core')>();
20
+ return {
21
+ ...actual,
19
22
  getResourceId: vi.fn((resource: any) => resource?.id)
20
- }));
23
+ };
24
+ });
21
25
 
22
26
  describe.skip('SearchModal Component - Visual States', () => {
23
27
  // TODO: All SearchModal tests skipped due to HeadlessUI Dialog + jsdom memory issues
@@ -1,8 +1,9 @@
1
1
  'use client';
2
2
 
3
3
  import React, { useRef, useState, useCallback, useEffect, useMemo } from 'react';
4
- import type { components } from '@semiont/core';
5
- import { createHoverHandlers, getTargetSelector, type SemiontSession } from '@semiont/api-client';
4
+ import type { Annotation } from '@semiont/core';
5
+ import { getTargetSelector } from '@semiont/core';
6
+ import { createHoverHandlers, type SemiontSession } from '@semiont/sdk';
6
7
  import type { SelectionMotivation } from '../annotation/AnnotateToolbar';
7
8
  import {
8
9
  canvasToPdfCoordinates,
@@ -19,8 +20,6 @@ import {
19
20
  } from '../../lib/browser-pdfjs';
20
21
  import './PdfAnnotationCanvas.css';
21
22
 
22
- type Annotation = components['schemas']['Annotation'];
23
-
24
23
  export type DrawingMode = 'rectangle' | 'circle' | 'polygon' | null;
25
24
 
26
25
  /**
@@ -278,7 +277,7 @@ export function PdfAnnotationCanvas({
278
277
  });
279
278
 
280
279
  if (clickedAnnotation) {
281
- session?.client.emit('browse:click', { annotationId: clickedAnnotation.id, motivation: clickedAnnotation.motivation });
280
+ session?.client.browse.click(clickedAnnotation.id, clickedAnnotation.motivation);
282
281
  setIsDrawing(false);
283
282
  setSelection(null);
284
283
  return;
@@ -317,14 +316,14 @@ export function PdfAnnotationCanvas({
317
316
 
318
317
  // Emit annotation:requested event with FragmentSelector
319
318
  if (selectedMotivation) {
320
- session.client.emit('mark:requested', {
321
- selector: {
319
+ session.client.mark.request(
320
+ {
322
321
  type: 'FragmentSelector',
323
322
  conformsTo: 'http://tools.ietf.org/rfc/rfc3778',
324
- value: fragmentSelector
323
+ value: fragmentSelector,
325
324
  },
326
- motivation: selectedMotivation
327
- });
325
+ selectedMotivation,
326
+ );
328
327
  }
329
328
 
330
329
  // Keep drawing state active to show preview until annotation is persisted
@@ -355,7 +354,7 @@ export function PdfAnnotationCanvas({
355
354
 
356
355
  // Hover handlers with currentHover guard and dwell delay
357
356
  const { handleMouseEnter, handleMouseLeave } = useMemo(
358
- () => createHoverHandlers((annotationId) => session?.client.emit('beckon:hover', { annotationId }), hoverDelayMs),
357
+ () => createHoverHandlers((id) => session?.client.beckon.hover(id), hoverDelayMs),
359
358
  [session, hoverDelayMs]
360
359
  );
361
360
 
@@ -455,7 +454,7 @@ export function PdfAnnotationCanvas({
455
454
  cursor: 'pointer',
456
455
  opacity: isSelected ? 1 : isHovered ? 0.9 : 0.7
457
456
  }}
458
- onClick={() => session?.client.emit('browse:click', { annotationId: ann.id, motivation: ann.motivation })}
457
+ onClick={() => session?.client.browse.click(ann.id, ann.motivation)}
459
458
  onMouseEnter={() => handleMouseEnter(ann.id)}
460
459
  onMouseLeave={handleMouseLeave}
461
460
  />
@@ -14,7 +14,7 @@ import { PdfAnnotationCanvas } from '../PdfAnnotationCanvas';
14
14
  import { resourceId } from '@semiont/core';
15
15
  import type { components } from '@semiont/core';
16
16
 
17
- type Annotation = components['schemas']['Annotation'];
17
+ import type { Annotation } from '@semiont/core';
18
18
 
19
19
  // Mock browser-pdfjs module
20
20
  vi.mock('../../../lib/browser-pdfjs', () => ({
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useRef, useEffect, useCallback, lazy, Suspense } from 'react';
4
- import { getMimeCategory, isPdfMimeType } from '@semiont/api-client';
4
+ import { getMimeCategory, isPdfMimeType } from '@semiont/core';
5
5
  import { ANNOTATORS } from '../../lib/annotation-registry';
6
6
  import { segmentTextWithAnnotations } from '../../lib/text-segmentation';
7
7
  import { buildTextSelectors, fallbackTextPosition } from '../../lib/text-selection-handler';
@@ -176,10 +176,7 @@ export function AnnotateView({
176
176
  const selectors = buildTextSelectors(content, text, start, end);
177
177
  if (!selectors) return;
178
178
 
179
- session?.client.emit('mark:requested', {
180
- selector: selectors,
181
- motivation: selectedMotivation
182
- });
179
+ session?.client.mark.request(selectors, selectedMotivation);
183
180
 
184
181
  // Clear selection after creating annotation
185
182
  selection.removeAllRanges();
@@ -3,7 +3,9 @@
3
3
  import { useEffect, useRef, useCallback, useMemo, memo, lazy, Suspense } from 'react';
4
4
  import ReactMarkdown from 'react-markdown';
5
5
  import remarkGfm from 'remark-gfm';
6
- import { getMimeCategory, isPdfMimeType, createHoverHandlers } from '@semiont/api-client';
6
+ import { annotationId as toAnnotationId } from '@semiont/core';
7
+ import { getMimeCategory, isPdfMimeType } from '@semiont/core';
8
+ import { createHoverHandlers } from '@semiont/sdk';
7
9
  import { ANNOTATORS } from '../../lib/annotation-registry';
8
10
  import { scrollAnnotationIntoView } from '../../lib/scroll-utils';
9
11
  import { ImageViewer } from '../viewers';
@@ -135,13 +137,13 @@ export const BrowseView = memo(function BrowseView({
135
137
  if (annotationId && annotationType === 'reference') {
136
138
  const annotation = allAnnotations.find(a => a.id === annotationId);
137
139
  if (annotation) {
138
- session.client.emit('browse:click', { annotationId, motivation: annotation.motivation });
140
+ session.client.browse.click(annotation.id, annotation.motivation);
139
141
  }
140
142
  }
141
143
  };
142
144
 
143
145
  const { handleMouseEnter, handleMouseLeave, cleanup: cleanupHover } = createHoverHandlers(
144
- (annotationId) => session.client.emit('beckon:hover', { annotationId }),
146
+ (id) => session.client.beckon.hover(id),
145
147
  hoverDelayMs
146
148
  );
147
149
 
@@ -150,7 +152,7 @@ export const BrowseView = memo(function BrowseView({
150
152
  const target = e.target as HTMLElement;
151
153
  const annotationElement = target.closest('[data-annotation-id]');
152
154
  const annotationId = annotationElement?.getAttribute('data-annotation-id');
153
- if (annotationId) handleMouseEnter(annotationId);
155
+ if (annotationId) handleMouseEnter(toAnnotationId(annotationId));
154
156
  };
155
157
 
156
158
  // Single mouseout handler for the container - fires once on exit
@@ -6,9 +6,8 @@ import { AnnotateView, type SelectionMotivation, type ClickAction, type ShapeTyp
6
6
  import { BrowseView } from './BrowseView';
7
7
  import { PopupContainer } from '../annotation-popups/SharedPopupElements';
8
8
  import { JsonLdView } from '../annotation-popups/JsonLdView';
9
- import type { components } from '@semiont/core';
10
- import { resourceId as toResourceId, annotationId as toAnnotationId } from '@semiont/core';
11
- import { getExactText, getTargetSelector, isHighlight, isAssessment, isReference, isComment, isTag, getBodySource } from '@semiont/api-client';
9
+ import type { Annotation, AnnotationId, ResourceDescriptor as SemiontResource, components } from '@semiont/core';
10
+ import { getExactText, getTargetSelector, isHighlight, isAssessment, isReference, isComment, isTag, getBodySource } from '@semiont/core';
12
11
  import { useEventSubscriptions } from '../../contexts/useEventSubscription';
13
12
  import { useSemiont } from '../../session/SemiontProvider';
14
13
  import { useObservable } from '../../hooks/useObservable';
@@ -17,9 +16,6 @@ import { ANNOTATORS } from '../../lib/annotation-registry';
17
16
  import type { AnnotationsCollection } from '../../types/annotation-props';
18
17
  import { getSelectorType, getSelectedShapeForSelectorType, saveSelectedShapeForSelectorType } from '../../lib/media-shapes';
19
18
 
20
- type Annotation = components['schemas']['Annotation'];
21
- type SemiontResource = components['schemas']['ResourceDescriptor'];
22
-
23
19
  /**
24
20
  * ResourceViewer - Display and interact with resource content and annotations
25
21
  *
@@ -84,7 +80,7 @@ export function ResourceViewer({
84
80
  if (!resource['@id']) {
85
81
  throw new Error('Resource has no @id');
86
82
  }
87
- const rUri = toResourceId(resource['@id']);
83
+ const rUri = resource['@id'];
88
84
 
89
85
  // Helper to get MIME type from resource
90
86
  const getMimeType = (): string => {
@@ -241,10 +237,10 @@ export function ResourceViewer({
241
237
  };
242
238
  };
243
239
 
244
- // Handle deleting annotations - emit event instead of direct call
245
- const handleDeleteAnnotation = useCallback((id: string) => {
246
- session?.client.emit('mark:delete', { annotationId: toAnnotationId(id) });
247
- }, [session]);
240
+ // Handle deleting annotations
241
+ const handleDeleteAnnotation = useCallback((id: AnnotationId) => {
242
+ session?.client.mark.delete(rUri, id);
243
+ }, [session, rUri]);
248
244
 
249
245
  // Handle annotation clicks - memoized
250
246
  const handleAnnotationClick = useCallback((annotation: Annotation, event?: React.MouseEvent) => {
@@ -5,7 +5,7 @@ import { BrowseView } from '../BrowseView';
5
5
  import type { components, EventBus } from '@semiont/core';
6
6
  import { createTestSemiontWrapper } from '../../../test-utils';
7
7
 
8
- type Annotation = components['schemas']['Annotation'];
8
+ import type { Annotation } from '@semiont/core';
9
9
 
10
10
  // Mock ResourceAnnotationsContext - keep this simple
11
11
  let mockNewAnnotationIds = new Set<string>();
@@ -15,9 +15,9 @@ vi.mock('../../../contexts/ResourceAnnotationsContext', () => ({
15
15
  })),
16
16
  }));
17
17
 
18
- // Mock @semiont/api-client utilities
19
- vi.mock('@semiont/api-client', async () => {
20
- const actual = await vi.importActual('@semiont/api-client');
18
+ // Mock @semiont/core utilities (these helpers and `resourceId` all live in core).
19
+ vi.mock('@semiont/core', async () => {
20
+ const actual = await vi.importActual('@semiont/core');
21
21
  return {
22
22
  ...actual,
23
23
  getMimeCategory: vi.fn((mimeType: string) => {
@@ -39,6 +39,7 @@ const stubClient = {
39
39
  };
40
40
  const stubSession = {
41
41
  client: stubClient,
42
+ subscribe: vi.fn(() => () => {}),
42
43
  };
43
44
  const stubActiveSession$ = new BehaviorSubject<any>(stubSession);
44
45
  const stubBrowser = {