@semiont/react-ui 0.2.45 → 0.3.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 (220) hide show
  1. package/dist/{PdfAnnotationCanvas.client-COQREPXU.mjs → PdfAnnotationCanvas.client-PVTVPDBQ.mjs} +3 -4
  2. package/dist/PdfAnnotationCanvas.client-PVTVPDBQ.mjs.map +1 -0
  3. package/dist/{ar-7SUXNE34.mjs → ar-APUOG2AP.mjs} +46 -6
  4. package/dist/ar-APUOG2AP.mjs.map +1 -0
  5. package/dist/{bn-XOET3DOI.mjs → bn-EFK2LJGK.mjs} +46 -6
  6. package/dist/bn-EFK2LJGK.mjs.map +1 -0
  7. package/dist/{chunk-JH7BXE2P.mjs → chunk-7DW2P4UE.mjs} +45 -5
  8. package/dist/chunk-7DW2P4UE.mjs.map +1 -0
  9. package/dist/{chunk-Q2KV6Y2J.mjs → chunk-7GEYABC6.mjs} +32 -32
  10. package/dist/{chunk-3JTO27MH.mjs → chunk-D4GAAQMM.mjs} +2 -9
  11. package/dist/{cs-X63DXX7L.mjs → cs-A26MEEQE.mjs} +46 -6
  12. package/dist/cs-A26MEEQE.mjs.map +1 -0
  13. package/dist/{da-OWTCV57A.mjs → da-U3L2FHSZ.mjs} +46 -6
  14. package/dist/da-U3L2FHSZ.mjs.map +1 -0
  15. package/dist/{de-77BMFDVF.mjs → de-Y5BHEBT7.mjs} +46 -6
  16. package/dist/de-Y5BHEBT7.mjs.map +1 -0
  17. package/dist/dist-YLEIY3JJ.mjs +547 -0
  18. package/dist/dist-YLEIY3JJ.mjs.map +1 -0
  19. package/dist/{el-FIBNLH2V.mjs → el-HU7LAWQY.mjs} +46 -6
  20. package/dist/el-HU7LAWQY.mjs.map +1 -0
  21. package/dist/{en-XWEPVTB4.mjs → en-HAKDCFKL.mjs} +5 -3
  22. package/dist/{es-726NTS53.mjs → es-4BN64QH5.mjs} +46 -6
  23. package/dist/es-4BN64QH5.mjs.map +1 -0
  24. package/dist/{fa-3N4CIWE6.mjs → fa-6ELTBARU.mjs} +46 -6
  25. package/dist/fa-6ELTBARU.mjs.map +1 -0
  26. package/dist/{fi-JOM3M7Z4.mjs → fi-DJ4WGIFW.mjs} +46 -6
  27. package/dist/fi-DJ4WGIFW.mjs.map +1 -0
  28. package/dist/{fr-56QSXS7E.mjs → fr-23XM6H6H.mjs} +46 -6
  29. package/dist/fr-23XM6H6H.mjs.map +1 -0
  30. package/dist/{he-SNAXPJEK.mjs → he-JSWJC2XU.mjs} +46 -6
  31. package/dist/he-JSWJC2XU.mjs.map +1 -0
  32. package/dist/{hi-CRBRD5TB.mjs → hi-BENHG3OJ.mjs} +46 -6
  33. package/dist/hi-BENHG3OJ.mjs.map +1 -0
  34. package/dist/{id-BRCVLICF.mjs → id-4HHQJQNF.mjs} +46 -6
  35. package/dist/id-4HHQJQNF.mjs.map +1 -0
  36. package/dist/index.css +108 -12
  37. package/dist/index.css.map +1 -1
  38. package/dist/index.d.mts +399 -147
  39. package/dist/index.mjs +3573 -2226
  40. package/dist/index.mjs.map +1 -1
  41. package/dist/{it-M2Z27BNB.mjs → it-U6I5PDKU.mjs} +46 -6
  42. package/dist/it-U6I5PDKU.mjs.map +1 -0
  43. package/dist/{ja-TZUKW7HD.mjs → ja-K3YBDWDP.mjs} +46 -6
  44. package/dist/ja-K3YBDWDP.mjs.map +1 -0
  45. package/dist/{ko-NKBGGOL6.mjs → ko-KC2HXRXG.mjs} +46 -6
  46. package/dist/ko-KC2HXRXG.mjs.map +1 -0
  47. package/dist/{magic-string.es-7FJ3LUGB.mjs → magic-string.es-K77I4ZQN.mjs} +2 -2
  48. package/dist/{ms-XFXPN6RX.mjs → ms-KY5QGBNN.mjs} +46 -6
  49. package/dist/ms-KY5QGBNN.mjs.map +1 -0
  50. package/dist/{nl-MVYXAS5C.mjs → nl-6PZFLGY2.mjs} +46 -6
  51. package/dist/nl-6PZFLGY2.mjs.map +1 -0
  52. package/dist/{no-XOLO4JPV.mjs → no-5QR7PLVJ.mjs} +46 -6
  53. package/dist/no-5QR7PLVJ.mjs.map +1 -0
  54. package/dist/{pl-TRWLMMC4.mjs → pl-4GV2NQXE.mjs} +46 -6
  55. package/dist/pl-4GV2NQXE.mjs.map +1 -0
  56. package/dist/{pt-M3TE24UI.mjs → pt-F3F5QD2P.mjs} +46 -6
  57. package/dist/pt-F3F5QD2P.mjs.map +1 -0
  58. package/dist/{ro-QBFG2T64.mjs → ro-TFYL2IQB.mjs} +46 -6
  59. package/dist/ro-TFYL2IQB.mjs.map +1 -0
  60. package/dist/{sv-IUECBXWX.mjs → sv-PRVF2QLR.mjs} +46 -6
  61. package/dist/sv-PRVF2QLR.mjs.map +1 -0
  62. package/dist/test-utils.mjs +16994 -22140
  63. package/dist/test-utils.mjs.map +1 -1
  64. package/dist/{th-US7KIN5Q.mjs → th-SUQOQFUZ.mjs} +46 -6
  65. package/dist/th-SUQOQFUZ.mjs.map +1 -0
  66. package/dist/{tr-DWJ2FFUK.mjs → tr-AYUJZOFJ.mjs} +46 -6
  67. package/dist/tr-AYUJZOFJ.mjs.map +1 -0
  68. package/dist/{uk-M4ZE4DPZ.mjs → uk-YY5WGLBM.mjs} +46 -6
  69. package/dist/uk-YY5WGLBM.mjs.map +1 -0
  70. package/dist/{vi-FERZNPSH.mjs → vi-6RO77ITD.mjs} +46 -6
  71. package/dist/{vi-FERZNPSH.mjs.map → vi-6RO77ITD.mjs.map} +1 -1
  72. package/dist/{zh-3J2I3WYK.mjs → zh-L6GA65H6.mjs} +46 -6
  73. package/dist/zh-L6GA65H6.mjs.map +1 -0
  74. package/package.json +18 -14
  75. package/src/components/Button/Button.tsx +23 -25
  76. package/src/components/annotation/AnnotateToolbar.tsx +1 -1
  77. package/src/components/annotation-popups/SharedPopupElements.tsx +5 -7
  78. package/src/components/image-annotation/SvgDrawingCanvas.tsx +3 -4
  79. package/src/components/modals/ConfigureGenerationStep.tsx +190 -0
  80. package/src/components/modals/ConfigureSearchStep.tsx +105 -0
  81. package/src/components/modals/ContextSummary.tsx +183 -0
  82. package/src/components/modals/GatherContextStep.tsx +89 -0
  83. package/src/components/modals/KeyboardShortcutsHelpModal.tsx +4 -6
  84. package/src/components/modals/ProposeEntitiesModal.tsx +4 -6
  85. package/src/components/modals/ReferenceWizardModal.tsx +326 -0
  86. package/src/components/modals/ResourceSearchModal.tsx +4 -6
  87. package/src/components/modals/SearchModal.css +43 -0
  88. package/src/components/modals/SearchModal.tsx +4 -6
  89. package/src/components/modals/SearchResultsStep.tsx +126 -0
  90. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +3 -4
  91. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +36 -14
  92. package/src/components/resource/AnnotateView.tsx +4 -4
  93. package/src/components/resource/AnnotationHistory.tsx +2 -2
  94. package/src/components/resource/BrowseView.tsx +4 -4
  95. package/src/components/resource/ResourceViewer.tsx +5 -8
  96. package/src/components/resource/__tests__/AnnotationHistory.test.tsx +16 -16
  97. package/src/components/resource/__tests__/BrowseView.test.tsx +2 -2
  98. package/src/components/resource/__tests__/HistoryEvent.test.tsx +1 -1
  99. package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +1 -1
  100. package/src/components/resource/panels/AssessmentEntry.tsx +9 -11
  101. package/src/components/resource/panels/AssessmentPanel.tsx +1 -1
  102. package/src/components/resource/panels/CommentEntry.tsx +10 -12
  103. package/src/components/resource/panels/CommentsPanel.tsx +1 -1
  104. package/src/components/resource/panels/HighlightEntry.tsx +9 -11
  105. package/src/components/resource/panels/HighlightPanel.tsx +2 -2
  106. package/src/components/resource/panels/ReferenceEntry.tsx +57 -104
  107. package/src/components/resource/panels/ReferencesPanel.css +85 -13
  108. package/src/components/resource/panels/ReferencesPanel.tsx +2 -3
  109. package/src/components/resource/panels/TagEntry.tsx +9 -11
  110. package/src/components/resource/panels/TaggingPanel.tsx +1 -1
  111. package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +4 -4
  112. package/src/components/resource/panels/__tests__/AssistSection.test.tsx +7 -7
  113. package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +3 -3
  114. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +2 -2
  115. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +64 -101
  116. package/src/components/resource/panels/__tests__/StatisticsPanel.test.tsx +1 -1
  117. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +7 -7
  118. package/src/components/viewers/ImageViewer.tsx +3 -6
  119. package/src/components/viewers/__tests__/ImageViewer.test.tsx +3 -3
  120. package/src/features/admin-devops/__tests__/AdminDevOpsPage.test.tsx +5 -5
  121. package/src/features/admin-exchange/__tests__/AdminExchangePage.test.tsx +141 -0
  122. package/src/features/admin-exchange/__tests__/ExportCard.test.tsx +41 -0
  123. package/src/features/admin-exchange/__tests__/ImportCard.test.tsx +148 -0
  124. package/src/features/admin-exchange/__tests__/ImportProgress.test.tsx +106 -0
  125. package/src/features/admin-exchange/components/AdminExchangePage.tsx +120 -0
  126. package/src/features/admin-exchange/components/ExportCard.tsx +35 -0
  127. package/src/features/admin-exchange/components/ImportCard.tsx +188 -0
  128. package/src/features/admin-exchange/components/ImportProgress.tsx +86 -0
  129. package/src/features/admin-security/__tests__/AdminSecurityPage.test.tsx +3 -3
  130. package/src/features/moderate-entity-tags/__tests__/EntityTagsPage.test.tsx +2 -2
  131. package/src/features/moderate-recent/__tests__/RecentDocumentsPage.test.tsx +4 -4
  132. package/src/features/moderate-tag-schemas/__tests__/TagSchemasPage.test.tsx +3 -3
  133. package/src/features/moderation-linked-data/__tests__/LinkedDataPage.test.tsx +117 -0
  134. package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +121 -0
  135. package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +5 -5
  136. package/src/features/resource-compose/components/ResourceComposePage.tsx +56 -1
  137. package/src/features/resource-discovery/__tests__/ResourceCard.test.tsx +1 -1
  138. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +2 -2
  139. package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +14 -14
  140. package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +12 -11
  141. package/src/features/resource-viewer/__tests__/AnnotationProgressDismissal.test.tsx +2 -2
  142. package/src/features/resource-viewer/__tests__/BindFlowIntegration.test.tsx +22 -115
  143. package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +3 -3
  144. package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +20 -20
  145. package/src/features/resource-viewer/__tests__/ResourceMutations.test.tsx +7 -7
  146. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +43 -20
  147. package/src/features/resource-viewer/__tests__/ToastNotifications.test.tsx +2 -2
  148. package/src/features/resource-viewer/__tests__/YieldFlowIntegration.test.tsx +45 -82
  149. package/src/features/resource-viewer/__tests__/annotation-progress-flow.test.tsx +4 -4
  150. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +151 -74
  151. package/src/integrations/tailwind-plugin.js +3 -3
  152. package/src/styles/core/buttons.css +31 -0
  153. package/src/styles/features/exchange.css +404 -0
  154. package/src/styles/index.css +1 -0
  155. package/translations/ar.json +42 -4
  156. package/translations/bn.json +42 -4
  157. package/translations/cs.json +42 -4
  158. package/translations/da.json +128 -90
  159. package/translations/de.json +122 -84
  160. package/translations/el.json +42 -4
  161. package/translations/en.json +42 -4
  162. package/translations/es.json +42 -4
  163. package/translations/fa.json +42 -4
  164. package/translations/fi.json +68 -30
  165. package/translations/fr.json +42 -4
  166. package/translations/he.json +42 -4
  167. package/translations/hi.json +42 -4
  168. package/translations/id.json +43 -5
  169. package/translations/it.json +62 -24
  170. package/translations/ja.json +43 -5
  171. package/translations/ko.json +42 -4
  172. package/translations/ms.json +43 -5
  173. package/translations/nl.json +41 -3
  174. package/translations/no.json +104 -66
  175. package/translations/pl.json +42 -4
  176. package/translations/pt.json +43 -5
  177. package/translations/ro.json +42 -4
  178. package/translations/sv.json +42 -4
  179. package/translations/th.json +42 -4
  180. package/translations/tr.json +42 -4
  181. package/translations/uk.json +42 -4
  182. package/translations/vi.json +42 -4
  183. package/translations/zh.json +42 -4
  184. package/dist/PdfAnnotationCanvas.client-COQREPXU.mjs.map +0 -1
  185. package/dist/ar-7SUXNE34.mjs.map +0 -1
  186. package/dist/bn-XOET3DOI.mjs.map +0 -1
  187. package/dist/chunk-JH7BXE2P.mjs.map +0 -1
  188. package/dist/cs-X63DXX7L.mjs.map +0 -1
  189. package/dist/da-OWTCV57A.mjs.map +0 -1
  190. package/dist/de-77BMFDVF.mjs.map +0 -1
  191. package/dist/el-FIBNLH2V.mjs.map +0 -1
  192. package/dist/es-726NTS53.mjs.map +0 -1
  193. package/dist/fa-3N4CIWE6.mjs.map +0 -1
  194. package/dist/fi-JOM3M7Z4.mjs.map +0 -1
  195. package/dist/fr-56QSXS7E.mjs.map +0 -1
  196. package/dist/he-SNAXPJEK.mjs.map +0 -1
  197. package/dist/hi-CRBRD5TB.mjs.map +0 -1
  198. package/dist/id-BRCVLICF.mjs.map +0 -1
  199. package/dist/it-M2Z27BNB.mjs.map +0 -1
  200. package/dist/ja-TZUKW7HD.mjs.map +0 -1
  201. package/dist/ko-NKBGGOL6.mjs.map +0 -1
  202. package/dist/ms-XFXPN6RX.mjs.map +0 -1
  203. package/dist/nl-MVYXAS5C.mjs.map +0 -1
  204. package/dist/no-XOLO4JPV.mjs.map +0 -1
  205. package/dist/pl-TRWLMMC4.mjs.map +0 -1
  206. package/dist/pt-M3TE24UI.mjs.map +0 -1
  207. package/dist/ro-QBFG2T64.mjs.map +0 -1
  208. package/dist/sv-IUECBXWX.mjs.map +0 -1
  209. package/dist/th-US7KIN5Q.mjs.map +0 -1
  210. package/dist/tr-DWJ2FFUK.mjs.map +0 -1
  211. package/dist/uk-M4ZE4DPZ.mjs.map +0 -1
  212. package/dist/zh-3J2I3WYK.mjs.map +0 -1
  213. package/src/examples/ButtonUsageExample.tsx +0 -242
  214. package/src/examples/button-css-modules.module.css +0 -164
  215. package/src/examples/button-styled-components.tsx +0 -215
  216. package/src/examples/button-tailwind.css +0 -51
  217. /package/dist/{chunk-Q2KV6Y2J.mjs.map → chunk-7GEYABC6.mjs.map} +0 -0
  218. /package/dist/{chunk-3JTO27MH.mjs.map → chunk-D4GAAQMM.mjs.map} +0 -0
  219. /package/dist/{en-XWEPVTB4.mjs.map → en-HAKDCFKL.mjs.map} +0 -0
  220. /package/dist/{magic-string.es-7FJ3LUGB.mjs.map → magic-string.es-K77I4ZQN.mjs.map} +0 -0
@@ -11,7 +11,7 @@ import { describe, test, expect, vi, beforeEach } from 'vitest';
11
11
  import { render, screen, waitFor, fireEvent } from '@testing-library/react';
12
12
  import userEvent from '@testing-library/user-event';
13
13
  import { PdfAnnotationCanvas } from '../PdfAnnotationCanvas';
14
- import { resourceUri } from '@semiont/core';
14
+ import { resourceId } from '@semiont/core';
15
15
  import type { components } from '@semiont/core';
16
16
 
17
17
  type Annotation = components['schemas']['Annotation'];
@@ -40,16 +40,24 @@ vi.mock('../../../lib/browser-pdfjs', () => ({
40
40
  }));
41
41
 
42
42
  describe('PdfAnnotationCanvas', () => {
43
- const mockResourceUri = resourceUri('http://example.com/resources/123');
43
+ const mockResourceId = resourceId('123');
44
44
 
45
45
  beforeEach(() => {
46
46
  vi.clearAllMocks();
47
+
48
+ // jsdom doesn't fire image onLoad or support clientWidth/clientHeight.
49
+ // Mock requestAnimationFrame to run callbacks synchronously and
50
+ // provide dimensions on the image element so the SVG overlay renders.
51
+ vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => {
52
+ cb(0);
53
+ return 0;
54
+ });
47
55
  });
48
56
 
49
57
  test('renders loading state initially', () => {
50
58
  render(
51
59
  <PdfAnnotationCanvas
52
- resourceUri={mockResourceUri}
60
+ resourceUri={mockResourceId}
53
61
  drawingMode={null}
54
62
  />
55
63
  );
@@ -60,7 +68,7 @@ describe('PdfAnnotationCanvas', () => {
60
68
  test('renders page navigation controls after loading', async () => {
61
69
  render(
62
70
  <PdfAnnotationCanvas
63
- resourceUri={mockResourceUri}
71
+ resourceUri={mockResourceId}
64
72
  drawingMode={null}
65
73
  />
66
74
  );
@@ -76,7 +84,7 @@ describe('PdfAnnotationCanvas', () => {
76
84
  test('previous button is disabled on first page', async () => {
77
85
  render(
78
86
  <PdfAnnotationCanvas
79
- resourceUri={mockResourceUri}
87
+ resourceUri={mockResourceId}
80
88
  drawingMode={null}
81
89
  />
82
90
  );
@@ -92,7 +100,7 @@ describe('PdfAnnotationCanvas', () => {
92
100
  test('next button is disabled on last page', async () => {
93
101
  render(
94
102
  <PdfAnnotationCanvas
95
- resourceUri={mockResourceUri}
103
+ resourceUri={mockResourceId}
96
104
  drawingMode={null}
97
105
  />
98
106
  );
@@ -124,7 +132,7 @@ describe('PdfAnnotationCanvas', () => {
124
132
  id: 'ann-1',
125
133
  body: [],
126
134
  target: {
127
- source: mockResourceUri,
135
+ source: mockResourceId,
128
136
  selector: {
129
137
  type: 'FragmentSelector',
130
138
  value: 'page=1&viewrect=100,200,150,100',
@@ -138,7 +146,7 @@ describe('PdfAnnotationCanvas', () => {
138
146
 
139
147
  render(
140
148
  <PdfAnnotationCanvas
141
- resourceUri={mockResourceUri}
149
+ resourceUri={mockResourceId}
142
150
  existingAnnotations={mockAnnotations}
143
151
  drawingMode={null}
144
152
  />
@@ -148,12 +156,26 @@ describe('PdfAnnotationCanvas', () => {
148
156
  expect(screen.getByText(/page 1 of 3/i)).toBeInTheDocument();
149
157
  });
150
158
 
151
- // Annotation should be rendered in SVG
152
- const svg = document.querySelector('.semiont-pdf-annotation-canvas__svg');
153
- expect(svg).toBeInTheDocument();
159
+ // jsdom doesn't fire image onLoad or provide clientWidth/clientHeight.
160
+ // Wait for the image element to appear, then simulate load with dimensions.
161
+ await waitFor(() => {
162
+ const img = document.querySelector('.semiont-pdf-annotation-canvas__image') as HTMLImageElement;
163
+ expect(img).toBeInTheDocument();
164
+ });
165
+
166
+ const img = document.querySelector('.semiont-pdf-annotation-canvas__image') as HTMLImageElement;
167
+ Object.defineProperty(img, 'clientWidth', { value: 612, configurable: true });
168
+ Object.defineProperty(img, 'clientHeight', { value: 792, configurable: true });
169
+ fireEvent.load(img);
170
+
171
+ await waitFor(() => {
172
+ // Annotation should be rendered in SVG
173
+ const svg = document.querySelector('.semiont-pdf-annotation-canvas__svg');
174
+ expect(svg).toBeInTheDocument();
154
175
 
155
- const rects = svg?.querySelectorAll('rect');
156
- expect(rects?.length).toBeGreaterThan(0);
176
+ const rects = svg?.querySelectorAll('rect');
177
+ expect(rects?.length).toBeGreaterThan(0);
178
+ });
157
179
  });
158
180
 
159
181
  test('emits annotate:requested via eventBus when drawing with sufficient drag', async () => {
@@ -167,7 +189,7 @@ describe('PdfAnnotationCanvas', () => {
167
189
 
168
190
  render(
169
191
  <PdfAnnotationCanvas
170
- resourceUri={mockResourceUri}
192
+ resourceUri={mockResourceId}
171
193
  drawingMode="rectangle"
172
194
  selectedMotivation="highlighting"
173
195
  eventBus={mockEventBus as any}
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useRef, useEffect, useCallback, lazy, Suspense } from 'react';
4
- import { resourceUri as toResourceUri } from '@semiont/core';
4
+ import { resourceId as toResourceId } from '@semiont/core';
5
5
  import { getMimeCategory, isPdfMimeType } from '@semiont/api-client';
6
6
  import { ANNOTATORS } from '../../lib/annotation-registry';
7
7
  import { segmentTextWithAnnotations } from '../../lib/text-segmentation';
@@ -247,7 +247,7 @@ export function AnnotateView({
247
247
  {resourceUri && (
248
248
  <Suspense fallback={<div className="semiont-annotate-view__loading">Loading PDF viewer...</div>}>
249
249
  <PdfAnnotationCanvas
250
- resourceUri={toResourceUri(resourceUri)}
250
+ resourceUri={toResourceId(resourceUri)}
251
251
  existingAnnotations={allAnnotations}
252
252
  drawingMode={selectedMotivation ? selectedShape : null}
253
253
  selectedMotivation={selectedMotivation}
@@ -277,7 +277,7 @@ export function AnnotateView({
277
277
  <div className="semiont-annotate-view__content">
278
278
  {resourceUri && (
279
279
  <SvgDrawingCanvas
280
- resourceUri={toResourceUri(resourceUri)}
280
+ resourceUri={toResourceId(resourceUri)}
281
281
  existingAnnotations={allAnnotations}
282
282
  drawingMode={selectedMotivation ? selectedShape : null}
283
283
  selectedMotivation={selectedMotivation}
@@ -300,7 +300,7 @@ export function AnnotateView({
300
300
  </p>
301
301
  {resourceUri && (
302
302
  <a
303
- href={resourceUri}
303
+ href={`/api/resources/${resourceUri}`}
304
304
  download
305
305
  className="semiont-button semiont-button--primary"
306
306
  >
@@ -4,13 +4,13 @@ import React, { useEffect, useRef } from 'react';
4
4
  import { useTranslations } from '../../contexts/TranslationContext';
5
5
  import type { RouteBuilder, LinkComponentProps } from '../../contexts/RoutingContext';
6
6
  import { useResources } from '../../lib/api-hooks';
7
- import type { ResourceUri } from '@semiont/core';
7
+ import type { ResourceId } from '@semiont/core';
8
8
  import type { StoredEvent } from '@semiont/core';
9
9
  import { getAnnotationUriFromEvent } from '@semiont/core';
10
10
  import { HistoryEvent } from './HistoryEvent';
11
11
 
12
12
  interface Props {
13
- rUri: ResourceUri;
13
+ rUri: ResourceId;
14
14
  hoveredAnnotationId?: string | null;
15
15
  onEventHover?: (annotationId: string | null) => void;
16
16
  onEventClick?: (annotationId: string | null) => void;
@@ -3,7 +3,7 @@
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 { resourceUri as toResourceUri } from '@semiont/core';
6
+ import { resourceId as toResourceId } from '@semiont/core';
7
7
  import { getMimeCategory, isPdfMimeType } from '@semiont/api-client';
8
8
  import { ANNOTATORS } from '../../lib/annotation-registry';
9
9
  import { createHoverHandlers } from '../../hooks/useBeckonFlow';
@@ -240,7 +240,7 @@ export const BrowseView = memo(function BrowseView({
240
240
  <div ref={containerRef} className="semiont-browse-view__content">
241
241
  <Suspense fallback={<div className="semiont-browse-view__loading">Loading PDF viewer...</div>}>
242
242
  <PdfAnnotationCanvas
243
- resourceUri={toResourceUri(resourceUri)}
243
+ resourceUri={toResourceId(resourceUri)}
244
244
  existingAnnotations={allAnnotations}
245
245
  drawingMode={null}
246
246
  selectedMotivation={null}
@@ -264,7 +264,7 @@ export const BrowseView = memo(function BrowseView({
264
264
  />
265
265
  <div ref={containerRef} className="semiont-browse-view__content">
266
266
  <ImageViewer
267
- resourceUri={resourceUri as any}
267
+ resourceUri={toResourceId(resourceUri)}
268
268
  mimeType={mimeType}
269
269
  alt="Resource content"
270
270
  />
@@ -280,7 +280,7 @@ export const BrowseView = memo(function BrowseView({
280
280
  Preview not available for {mimeType}
281
281
  </p>
282
282
  <a
283
- href={resourceUri}
283
+ href={`/api/resources/${resourceUri}`}
284
284
  download
285
285
  className="semiont-button semiont-button--primary"
286
286
  >
@@ -7,7 +7,7 @@ import { BrowseView } from './BrowseView';
7
7
  import { PopupContainer } from '../annotation-popups/SharedPopupElements';
8
8
  import { JsonLdView } from '../annotation-popups/JsonLdView';
9
9
  import type { components } from '@semiont/core';
10
- import { resourceUri } from '@semiont/core';
10
+ import { resourceId as toResourceId, annotationId as toAnnotationId } from '@semiont/core';
11
11
  import { getExactText, getTargetSelector, isHighlight, isAssessment, isReference, isComment, isTag, getBodySource } from '@semiont/api-client';
12
12
  import { useEventBus } from '../../contexts/EventBusContext';
13
13
  import { useEventSubscriptions } from '../../contexts/useEventSubscription';
@@ -84,7 +84,7 @@ export function ResourceViewer({
84
84
  if (!resource['@id']) {
85
85
  throw new Error('Resource has no @id');
86
86
  }
87
- const rUri = resourceUri(resource['@id']);
87
+ const rUri = toResourceId(resource['@id']);
88
88
 
89
89
  // Helper to get MIME type from resource
90
90
  const getMimeType = (): string => {
@@ -251,7 +251,7 @@ export function ResourceViewer({
251
251
 
252
252
  // Handle deleting annotations - emit event instead of direct call
253
253
  const handleDeleteAnnotation = useCallback((id: string) => {
254
- eventBus.get('mark:delete').next({ annotationId: id });
254
+ eventBus.get('mark:delete').next({ annotationId: toAnnotationId(id) });
255
255
  }, []); // eventBus is stable
256
256
 
257
257
  // Handle annotation clicks - memoized
@@ -279,11 +279,8 @@ export function ResourceViewer({
279
279
  if (selectedClick === 'follow' && isReference(annotation)) {
280
280
  const bodySource = getBodySource(annotation.body);
281
281
  if (bodySource) {
282
- // Navigate to the linked resource - emits 'browse:external-navigate' event
283
- const resourceId = bodySource.split('/resources/')[1];
284
- if (resourceId) {
285
- navigate(`/know/resource/${resourceId}`, { resourceId });
286
- }
282
+ // bodySource is already a bare resource ID
283
+ navigate(`/know/resource/${bodySource}`, { resourceId: bodySource });
287
284
  }
288
285
  return;
289
286
  }
@@ -4,7 +4,7 @@ import { screen } from '@testing-library/react';
4
4
  import '@testing-library/jest-dom';
5
5
  import { AnnotationHistory } from '../AnnotationHistory';
6
6
  import { renderWithProviders } from '../../../test-utils';
7
- import type { StoredEvent, ResourceUri } from '@semiont/core';
7
+ import type { StoredEvent, ResourceId } from '@semiont/core';
8
8
 
9
9
  // Mock @semiont/core - must use importOriginal to preserve EventBus etc.
10
10
  vi.mock('@semiont/core', async (importOriginal) => {
@@ -52,7 +52,7 @@ vi.mock('../HistoryEvent', () => ({
52
52
  import { getAnnotationUriFromEvent } from '@semiont/core';
53
53
  const mockGetAnnotationUri = getAnnotationUriFromEvent as ReturnType<typeof vi.fn>;
54
54
 
55
- const testRUri = 'http://localhost/resources/res-1' as ResourceUri;
55
+ const testRId = 'res-1' as ResourceId;
56
56
 
57
57
  function makeStoredEvent(id: string, type: string, seq: number, overrides: Record<string, any> = {}): StoredEvent {
58
58
  return {
@@ -60,7 +60,7 @@ function makeStoredEvent(id: string, type: string, seq: number, overrides: Recor
60
60
  id,
61
61
  type,
62
62
  timestamp: '2026-03-06T12:00:00Z',
63
- resourceId: 'http://localhost/resources/res-1',
63
+ resourceId: 'res-1',
64
64
  userId: 'user-1',
65
65
  version: 1,
66
66
  payload: {},
@@ -90,7 +90,7 @@ describe('AnnotationHistory', () => {
90
90
 
91
91
  renderWithProviders(
92
92
  <AnnotationHistory
93
- rUri={testRUri}
93
+ rUri={testRId}
94
94
  Link={MockLink}
95
95
  routes={mockRoutes}
96
96
  />
@@ -105,7 +105,7 @@ describe('AnnotationHistory', () => {
105
105
 
106
106
  const { container } = renderWithProviders(
107
107
  <AnnotationHistory
108
- rUri={testRUri}
108
+ rUri={testRId}
109
109
  Link={MockLink}
110
110
  routes={mockRoutes}
111
111
  />
@@ -119,7 +119,7 @@ describe('AnnotationHistory', () => {
119
119
 
120
120
  const { container } = renderWithProviders(
121
121
  <AnnotationHistory
122
- rUri={testRUri}
122
+ rUri={testRId}
123
123
  Link={MockLink}
124
124
  routes={mockRoutes}
125
125
  />
@@ -138,7 +138,7 @@ describe('AnnotationHistory', () => {
138
138
 
139
139
  renderWithProviders(
140
140
  <AnnotationHistory
141
- rUri={testRUri}
141
+ rUri={testRId}
142
142
  Link={MockLink}
143
143
  routes={mockRoutes}
144
144
  />
@@ -169,7 +169,7 @@ describe('AnnotationHistory', () => {
169
169
 
170
170
  renderWithProviders(
171
171
  <AnnotationHistory
172
- rUri={testRUri}
172
+ rUri={testRId}
173
173
  Link={MockLink}
174
174
  routes={mockRoutes}
175
175
  />
@@ -194,7 +194,7 @@ describe('AnnotationHistory', () => {
194
194
 
195
195
  renderWithProviders(
196
196
  <AnnotationHistory
197
- rUri={testRUri}
197
+ rUri={testRId}
198
198
  hoveredAnnotationId={annotationUri}
199
199
  Link={MockLink}
200
200
  routes={mockRoutes}
@@ -215,7 +215,7 @@ describe('AnnotationHistory', () => {
215
215
 
216
216
  renderWithProviders(
217
217
  <AnnotationHistory
218
- rUri={testRUri}
218
+ rUri={testRId}
219
219
  hoveredAnnotationId="http://localhost/annotations/ann-1"
220
220
  Link={MockLink}
221
221
  routes={mockRoutes}
@@ -234,7 +234,7 @@ describe('AnnotationHistory', () => {
234
234
 
235
235
  renderWithProviders(
236
236
  <AnnotationHistory
237
- rUri={testRUri}
237
+ rUri={testRId}
238
238
  Link={MockLink}
239
239
  routes={mockRoutes}
240
240
  />
@@ -255,7 +255,7 @@ describe('AnnotationHistory', () => {
255
255
 
256
256
  renderWithProviders(
257
257
  <AnnotationHistory
258
- rUri={testRUri}
258
+ rUri={testRId}
259
259
  onEventClick={onEventClick}
260
260
  onEventHover={onEventHover}
261
261
  Link={MockLink}
@@ -276,7 +276,7 @@ describe('AnnotationHistory', () => {
276
276
 
277
277
  renderWithProviders(
278
278
  <AnnotationHistory
279
- rUri={testRUri}
279
+ rUri={testRId}
280
280
  Link={MockLink}
281
281
  routes={mockRoutes}
282
282
  />
@@ -298,7 +298,7 @@ describe('AnnotationHistory', () => {
298
298
 
299
299
  renderWithProviders(
300
300
  <AnnotationHistory
301
- rUri={testRUri}
301
+ rUri={testRId}
302
302
  Link={MockLink}
303
303
  routes={mockRoutes}
304
304
  />
@@ -318,7 +318,7 @@ describe('AnnotationHistory', () => {
318
318
 
319
319
  renderWithProviders(
320
320
  <AnnotationHistory
321
- rUri={testRUri}
321
+ rUri={testRId}
322
322
  Link={MockLink}
323
323
  routes={mockRoutes}
324
324
  />
@@ -336,7 +336,7 @@ describe('AnnotationHistory', () => {
336
336
 
337
337
  const { container } = renderWithProviders(
338
338
  <AnnotationHistory
339
- rUri={testRUri}
339
+ rUri={testRId}
340
340
  Link={MockLink}
341
341
  routes={mockRoutes}
342
342
  />
@@ -27,7 +27,7 @@ vi.mock('@semiont/api-client', async () => {
27
27
  return 'unsupported';
28
28
  }),
29
29
  isPdfMimeType: vi.fn((mimeType: string) => mimeType === 'application/pdf'),
30
- resourceUri: vi.fn((uri: string) => uri),
30
+ resourceId: vi.fn((id: string) => id),
31
31
  getExactText: vi.fn(() => 'exact text'),
32
32
  getTextPositionSelector: vi.fn(() => ({ start: 0, end: 10 })),
33
33
  getTargetSelector: vi.fn(() => ({ type: 'TextPositionSelector', start: 0, end: 10 })),
@@ -200,7 +200,7 @@ describe('BrowseView Component', () => {
200
200
  const defaultProps = {
201
201
  content: '# Test Content\n\nThis is test markdown content.',
202
202
  mimeType: 'text/markdown',
203
- resourceUri: 'http://localhost:8080/resources/test-resource',
203
+ resourceUri: 'test-resource',
204
204
  annotations: {
205
205
  highlights: [],
206
206
  references: [],
@@ -55,7 +55,7 @@ function makeStoredEvent(overrides: Partial<StoredEvent['event']> = {}): StoredE
55
55
  id: 'evt-1',
56
56
  type: 'resource.created',
57
57
  timestamp: '2026-03-06T12:00:00Z',
58
- resourceId: 'http://localhost/resources/res-1',
58
+ resourceId: 'res-1',
59
59
  userId: 'user-1',
60
60
  version: 1,
61
61
  payload: { name: 'Test', format: 'text/plain', contentChecksum: 'abc', creationMethod: 'upload' },
@@ -31,7 +31,7 @@ vi.mock('../../../hooks/useObservableBrowse', () => ({
31
31
 
32
32
  const mockResource: SemiontResource & { content: string } = {
33
33
  '@context': 'https://www.w3.org/ns/activitystreams',
34
- '@id': 'http://example.com/resources/test-123',
34
+ '@id': 'test-123',
35
35
  name: 'Test Document',
36
36
  created: '2024-01-01T00:00:00Z',
37
37
  entityTypes: [],
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { forwardRef } from 'react';
3
+ import type { Ref } from 'react';
4
4
  import type { components } from '@semiont/core';
5
5
  import { getAnnotationExactText } from '@semiont/api-client';
6
6
  import { useEventBus } from '../../../contexts/EventBusContext';
@@ -20,6 +20,7 @@ interface AssessmentEntryProps {
20
20
  assessment: Annotation;
21
21
  isFocused: boolean;
22
22
  isHovered?: boolean;
23
+ ref?: Ref<HTMLDivElement>;
23
24
  }
24
25
 
25
26
  function formatRelativeTime(isoString: string): string {
@@ -67,15 +68,12 @@ function getAssessmentText(annotation: Annotation): string | null {
67
68
  return null;
68
69
  }
69
70
 
70
- export const AssessmentEntry = forwardRef<HTMLDivElement, AssessmentEntryProps>(
71
- function AssessmentEntry(
72
- {
73
- assessment,
74
- isFocused,
75
- isHovered = false,
76
- },
77
- ref
78
- ) {
71
+ export function AssessmentEntry({
72
+ assessment,
73
+ isFocused,
74
+ isHovered = false,
75
+ ref,
76
+ }: AssessmentEntryProps) {
79
77
  const eventBus = useEventBus();
80
78
  const hoverProps = useHoverEmitter(assessment.id);
81
79
 
@@ -113,4 +111,4 @@ export const AssessmentEntry = forwardRef<HTMLDivElement, AssessmentEntryProps>(
113
111
  </div>
114
112
  </div>
115
113
  );
116
- });
114
+ }
@@ -141,7 +141,7 @@ export function AssessmentPanel({
141
141
  ? [{ type: 'TextualBody' as const, value: newAssessmentText, purpose: 'assessing' as const }]
142
142
  : [];
143
143
 
144
- eventBus.get('mark:create').next({
144
+ eventBus.get('mark:submit').next({
145
145
  motivation: 'assessing',
146
146
  selector: pendingAnnotation.selector,
147
147
  body,
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useState, useEffect, useRef, forwardRef, useImperativeHandle } from 'react';
3
+ import { useState, useEffect, useRef, useImperativeHandle, type Ref } from 'react';
4
4
  import { useTranslations } from '../../../contexts/TranslationContext';
5
5
  import type { components } from '@semiont/core';
6
6
  import { getAnnotationExactText, getCommentText } from '@semiont/api-client';
@@ -14,6 +14,7 @@ interface CommentEntryProps {
14
14
  isFocused: boolean;
15
15
  isHovered?: boolean;
16
16
  annotateMode?: boolean;
17
+ ref?: Ref<HTMLDivElement>;
17
18
  }
18
19
 
19
20
  function formatRelativeTime(isoString: string): string {
@@ -33,16 +34,13 @@ function formatRelativeTime(isoString: string): string {
33
34
  return date.toLocaleDateString();
34
35
  }
35
36
 
36
- export const CommentEntry = forwardRef<HTMLDivElement, CommentEntryProps>(
37
- function CommentEntry(
38
- {
39
- comment,
40
- isFocused,
41
- isHovered = false,
42
- annotateMode = true,
43
- },
44
- ref
45
- ) {
37
+ export function CommentEntry({
38
+ comment,
39
+ isFocused,
40
+ isHovered = false,
41
+ annotateMode = true,
42
+ ref,
43
+ }: CommentEntryProps) {
46
44
  const t = useTranslations('CommentsPanel');
47
45
  const eventBus = useEventBus();
48
46
  const hoverProps = useHoverEmitter(comment.id);
@@ -156,4 +154,4 @@ export const CommentEntry = forwardRef<HTMLDivElement, CommentEntryProps>(
156
154
  )}
157
155
  </div>
158
156
  );
159
- });
157
+ }
@@ -167,7 +167,7 @@ export function CommentsPanel({
167
167
 
168
168
  const handleSaveNewComment = () => {
169
169
  if (newCommentText.trim() && pendingAnnotation) {
170
- eventBus.get('mark:create').next({
170
+ eventBus.get('mark:submit').next({
171
171
  motivation: 'commenting',
172
172
  selector: pendingAnnotation.selector,
173
173
  body: [{ type: 'TextualBody', value: newCommentText, purpose: 'commenting' }],
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { forwardRef } from 'react';
3
+ import type { Ref } from 'react';
4
4
  import type { components } from '@semiont/core';
5
5
  import { getAnnotationExactText } from '@semiont/api-client';
6
6
  import { useEventBus } from '../../../contexts/EventBusContext';
@@ -12,6 +12,7 @@ interface HighlightEntryProps {
12
12
  highlight: Annotation;
13
13
  isFocused: boolean;
14
14
  isHovered?: boolean;
15
+ ref?: Ref<HTMLDivElement>;
15
16
  }
16
17
 
17
18
  function formatRelativeTime(isoString: string): string {
@@ -31,15 +32,12 @@ function formatRelativeTime(isoString: string): string {
31
32
  return date.toLocaleDateString();
32
33
  }
33
34
 
34
- export const HighlightEntry = forwardRef<HTMLDivElement, HighlightEntryProps>(
35
- function HighlightEntry(
36
- {
37
- highlight,
38
- isFocused,
39
- isHovered = false,
40
- },
41
- ref
42
- ) {
35
+ export function HighlightEntry({
36
+ highlight,
37
+ isFocused,
38
+ isHovered = false,
39
+ ref,
40
+ }: HighlightEntryProps) {
43
41
  const eventBus = useEventBus();
44
42
  const hoverProps = useHoverEmitter(highlight.id);
45
43
 
@@ -69,4 +67,4 @@ export const HighlightEntry = forwardRef<HTMLDivElement, HighlightEntryProps>(
69
67
  </div>
70
68
  </div>
71
69
  );
72
- });
70
+ }
@@ -129,10 +129,10 @@ export function HighlightPanel({
129
129
  });
130
130
 
131
131
  // Highlights auto-create: when pendingAnnotation arrives with highlighting motivation,
132
- // immediately emit mark:create event
132
+ // immediately emit mark:submit event
133
133
  useEffect(() => {
134
134
  if (pendingAnnotation && pendingAnnotation.motivation === 'highlighting') {
135
- eventBus.get('mark:create').next({
135
+ eventBus.get('mark:submit').next({
136
136
  motivation: 'highlighting',
137
137
  selector: pendingAnnotation.selector,
138
138
  body: [],