@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
@@ -294,7 +294,7 @@ describe('AdminSecurityPage', () => {
294
294
 
295
295
  expect(ToolbarPanels).toHaveBeenCalledWith(
296
296
  expect.objectContaining({ theme: 'dark' }),
297
- expect.anything()
297
+ undefined,
298
298
  );
299
299
  });
300
300
 
@@ -305,7 +305,7 @@ describe('AdminSecurityPage', () => {
305
305
 
306
306
  expect(Toolbar).toHaveBeenCalledWith(
307
307
  expect.objectContaining({ activePanel: 'settings' }),
308
- expect.anything()
308
+ undefined,
309
309
  );
310
310
  });
311
311
 
@@ -316,7 +316,7 @@ describe('AdminSecurityPage', () => {
316
316
 
317
317
  expect(Toolbar).toHaveBeenCalledWith(
318
318
  expect.objectContaining({ context: 'simple' }),
319
- expect.anything()
319
+ undefined,
320
320
  );
321
321
  });
322
322
  });
@@ -278,7 +278,7 @@ describe('EntityTagsPage', () => {
278
278
 
279
279
  expect(ToolbarPanels).toHaveBeenCalledWith(
280
280
  expect.objectContaining({ theme: 'dark' }),
281
- expect.anything()
281
+ undefined,
282
282
  );
283
283
  });
284
284
 
@@ -289,7 +289,7 @@ describe('EntityTagsPage', () => {
289
289
 
290
290
  expect(Toolbar).toHaveBeenCalledWith(
291
291
  expect.objectContaining({ activePanel: 'settings' }),
292
- expect.anything()
292
+ undefined,
293
293
  );
294
294
  });
295
295
  });
@@ -190,7 +190,7 @@ describe('RecentDocumentsPage', () => {
190
190
 
191
191
  expect(ToolbarPanels).toHaveBeenCalledWith(
192
192
  expect.objectContaining({ theme: 'dark' }),
193
- expect.anything()
193
+ undefined,
194
194
  );
195
195
  });
196
196
 
@@ -201,7 +201,7 @@ describe('RecentDocumentsPage', () => {
201
201
 
202
202
  expect(Toolbar).toHaveBeenCalledWith(
203
203
  expect.objectContaining({ activePanel: 'settings' }),
204
- expect.anything()
204
+ undefined,
205
205
  );
206
206
  });
207
207
 
@@ -212,7 +212,7 @@ describe('RecentDocumentsPage', () => {
212
212
 
213
213
  expect(ToolbarPanels).toHaveBeenCalledWith(
214
214
  expect.objectContaining({ showLineNumbers: true }),
215
- expect.anything()
215
+ undefined,
216
216
  );
217
217
  });
218
218
 
@@ -224,7 +224,7 @@ describe('RecentDocumentsPage', () => {
224
224
 
225
225
  expect(Toolbar).toHaveBeenCalledWith(
226
226
  expect.objectContaining({ context: 'simple' }),
227
- expect.anything()
227
+ undefined,
228
228
  );
229
229
  });
230
230
  });
@@ -261,7 +261,7 @@ describe('TagSchemasPage', () => {
261
261
 
262
262
  expect(ToolbarPanels).toHaveBeenCalledWith(
263
263
  expect.objectContaining({ theme: 'dark' }),
264
- expect.anything()
264
+ undefined,
265
265
  );
266
266
  });
267
267
 
@@ -272,7 +272,7 @@ describe('TagSchemasPage', () => {
272
272
 
273
273
  expect(Toolbar).toHaveBeenCalledWith(
274
274
  expect.objectContaining({ activePanel: 'settings' }),
275
- expect.anything()
275
+ undefined,
276
276
  );
277
277
  });
278
278
 
@@ -283,7 +283,7 @@ describe('TagSchemasPage', () => {
283
283
 
284
284
  expect(ToolbarPanels).toHaveBeenCalledWith(
285
285
  expect.objectContaining({ showLineNumbers: true }),
286
- expect.anything()
286
+ undefined,
287
287
  );
288
288
  });
289
289
 
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Tests for LinkedDataPage component
3
+ */
4
+
5
+ import { describe, it, expect, vi } from 'vitest';
6
+ import { render, screen } from '@testing-library/react';
7
+ import { LinkedDataPage } from '../components/LinkedDataPage';
8
+ import type { LinkedDataPageProps } from '../components/LinkedDataPage';
9
+
10
+ const createProps = (overrides?: Partial<LinkedDataPageProps>): LinkedDataPageProps => ({
11
+ onExport: vi.fn(),
12
+ isExporting: false,
13
+ onFileSelected: vi.fn(),
14
+ onImport: vi.fn(),
15
+ onCancelImport: vi.fn(),
16
+ selectedFile: null,
17
+ preview: null,
18
+ isImporting: false,
19
+ importPhase: null,
20
+ theme: 'light',
21
+ showLineNumbers: false,
22
+ activePanel: null,
23
+ translations: {
24
+ title: 'Export & Import',
25
+ subtitle: 'Exchange knowledge as JSON-LD Linked Data',
26
+ export: {
27
+ title: 'Export',
28
+ description: 'Export your knowledge base as JSON-LD',
29
+ exportButton: 'Export as JSON-LD',
30
+ exporting: 'Exporting…',
31
+ },
32
+ import: {
33
+ title: 'Import',
34
+ description: 'Import from JSON-LD',
35
+ dropzoneLabel: 'Drop a file here',
36
+ dropzoneActive: 'Drop to upload',
37
+ detectedFormat: 'Format',
38
+ statsPreview: 'Preview',
39
+ importButton: 'Import',
40
+ importing: 'Importing…',
41
+ importConfirmTitle: 'Confirm',
42
+ importConfirmMessage: 'This will create new resources.',
43
+ confirmImport: 'Proceed',
44
+ cancelImport: 'Cancel',
45
+ },
46
+ progress: {
47
+ phaseStarted: 'Starting…',
48
+ phaseEntityTypes: 'Entity types…',
49
+ phaseResources: 'Resources…',
50
+ phaseAnnotations: 'Annotations…',
51
+ phaseComplete: 'Complete',
52
+ phaseError: 'Failed',
53
+ hashChainValid: 'Verified',
54
+ hashChainInvalid: 'Verification failed',
55
+ streams: 'Resources',
56
+ events: 'Annotations',
57
+ blobs: 'Entity types',
58
+ },
59
+ },
60
+ ToolbarPanels: () => <div data-testid="toolbar-panels" />,
61
+ Toolbar: () => <div data-testid="toolbar" />,
62
+ ...overrides,
63
+ });
64
+
65
+ describe('LinkedDataPage', () => {
66
+ it('renders page title and subtitle', () => {
67
+ render(<LinkedDataPage {...createProps()} />);
68
+ expect(screen.getByText('Export & Import')).toBeInTheDocument();
69
+ expect(screen.getByText('Exchange knowledge as JSON-LD Linked Data')).toBeInTheDocument();
70
+ });
71
+
72
+ it('renders ExportCard', () => {
73
+ render(<LinkedDataPage {...createProps()} />);
74
+ expect(screen.getByRole('heading', { name: 'Export' })).toBeInTheDocument();
75
+ expect(screen.getByText('Export as JSON-LD')).toBeInTheDocument();
76
+ });
77
+
78
+ it('renders ImportCard', () => {
79
+ render(<LinkedDataPage {...createProps()} />);
80
+ expect(screen.getByRole('heading', { name: 'Import' })).toBeInTheDocument();
81
+ expect(screen.getByText('Import from JSON-LD')).toBeInTheDocument();
82
+ });
83
+
84
+ it('renders toolbar components', () => {
85
+ render(<LinkedDataPage {...createProps()} />);
86
+ expect(screen.getByTestId('toolbar-panels')).toBeInTheDocument();
87
+ expect(screen.getByTestId('toolbar')).toBeInTheDocument();
88
+ });
89
+
90
+ it('does not render ImportProgress when importPhase is null', () => {
91
+ const { container } = render(<LinkedDataPage {...createProps()} />);
92
+ expect(container.querySelector('.semiont-exchange__progress')).not.toBeInTheDocument();
93
+ });
94
+
95
+ it('renders ImportProgress when importPhase is set', () => {
96
+ render(<LinkedDataPage {...createProps({ importPhase: 'started' })} />);
97
+ expect(screen.getByText('Starting…')).toBeInTheDocument();
98
+ });
99
+
100
+ it('renders ImportProgress with result on completion', () => {
101
+ render(<LinkedDataPage {...createProps({
102
+ importPhase: 'complete',
103
+ importResult: { resourcesCreated: 5, annotationsCreated: 12, entityTypesAdded: 3 },
104
+ })} />);
105
+ expect(screen.getByText('Complete')).toBeInTheDocument();
106
+ });
107
+
108
+ it('applies panel-open class when common panel is active', () => {
109
+ const { container } = render(<LinkedDataPage {...createProps({ activePanel: 'user' })} />);
110
+ expect(container.querySelector('.semiont-page--panel-open')).toBeInTheDocument();
111
+ });
112
+
113
+ it('does not apply panel-open class when no panel is active', () => {
114
+ const { container } = render(<LinkedDataPage {...createProps({ activePanel: null })} />);
115
+ expect(container.querySelector('.semiont-page--panel-open')).not.toBeInTheDocument();
116
+ });
117
+ });
@@ -0,0 +1,121 @@
1
+ /**
2
+ * LinkedDataPage — JSON-LD Export/Import moderation page
3
+ *
4
+ * Pure React component. All state and handlers passed as props.
5
+ * Reuses ExportCard, ImportCard, and ImportProgress from admin-exchange.
6
+ */
7
+
8
+ import React from 'react';
9
+ import { COMMON_PANELS, type ToolbarPanelType } from '../../../hooks/usePanelBrowse';
10
+ import { ExportCard, type ExportCardTranslations } from '../../admin-exchange/components/ExportCard';
11
+ import { ImportCard, type ImportCardProps, type ImportCardTranslations } from '../../admin-exchange/components/ImportCard';
12
+ import { ImportProgress, type ImportProgressTranslations } from '../../admin-exchange/components/ImportProgress';
13
+
14
+ export interface LinkedDataPageTranslations {
15
+ title: string;
16
+ subtitle: string;
17
+ export: ExportCardTranslations;
18
+ import: ImportCardTranslations;
19
+ progress: ImportProgressTranslations;
20
+ }
21
+
22
+ export interface LinkedDataPageProps {
23
+ // Export
24
+ onExport: () => void;
25
+ isExporting: boolean;
26
+
27
+ // Import
28
+ onFileSelected: (file: File) => void;
29
+ onImport: () => void;
30
+ onCancelImport: () => void;
31
+ selectedFile: File | null;
32
+ preview: ImportCardProps['preview'];
33
+ isImporting: boolean;
34
+
35
+ // Progress
36
+ importPhase: string | null;
37
+ importMessage?: string | undefined;
38
+ importResult?: Record<string, unknown> | undefined;
39
+
40
+ // UI state
41
+ theme: 'light' | 'dark' | 'system';
42
+ showLineNumbers: boolean;
43
+ activePanel: string | null;
44
+
45
+ // Translations
46
+ translations: LinkedDataPageTranslations;
47
+
48
+ // Component dependencies
49
+ ToolbarPanels: React.ComponentType<any>;
50
+ Toolbar: React.ComponentType<any>;
51
+ }
52
+
53
+ export function LinkedDataPage({
54
+ onExport,
55
+ isExporting,
56
+ onFileSelected,
57
+ onImport,
58
+ onCancelImport,
59
+ selectedFile,
60
+ preview,
61
+ isImporting,
62
+ importPhase,
63
+ importMessage,
64
+ importResult,
65
+ theme,
66
+ showLineNumbers,
67
+ activePanel,
68
+ translations: t,
69
+ ToolbarPanels,
70
+ Toolbar,
71
+ }: LinkedDataPageProps) {
72
+ return (
73
+ <div className={`semiont-page${activePanel && COMMON_PANELS.includes(activePanel as ToolbarPanelType) ? ' semiont-page--panel-open' : ''}`}>
74
+ <div className="semiont-page__content">
75
+ <div className="semiont-page__header">
76
+ <h1 className="semiont-page__title">{t.title}</h1>
77
+ <p className="semiont-page__subtitle">{t.subtitle}</p>
78
+ </div>
79
+
80
+ <div className="semiont-exchange__cards">
81
+ <ExportCard
82
+ onExport={onExport}
83
+ isExporting={isExporting}
84
+ translations={t.export}
85
+ />
86
+
87
+ <ImportCard
88
+ onFileSelected={onFileSelected}
89
+ onImport={onImport}
90
+ onCancel={onCancelImport}
91
+ selectedFile={selectedFile}
92
+ preview={preview}
93
+ isImporting={isImporting}
94
+ translations={t.import}
95
+ />
96
+ </div>
97
+
98
+ {importPhase && (
99
+ <ImportProgress
100
+ phase={importPhase}
101
+ message={importMessage}
102
+ result={importResult}
103
+ translations={t.progress}
104
+ />
105
+ )}
106
+ </div>
107
+
108
+ <div className="semiont-page__sidebar">
109
+ <ToolbarPanels
110
+ activePanel={activePanel}
111
+ theme={theme}
112
+ showLineNumbers={showLineNumbers}
113
+ />
114
+ <Toolbar
115
+ context="simple"
116
+ activePanel={activePanel}
117
+ />
118
+ </div>
119
+ </div>
120
+ );
121
+ }
@@ -137,7 +137,7 @@ describe('ResourceComposePage', () => {
137
137
  mode: 'clone',
138
138
  cloneData: {
139
139
  sourceResource: {
140
- '@id': 'http://localhost/resources/1',
140
+ '@id': '1',
141
141
  name: 'Original Resource',
142
142
  } as any,
143
143
  sourceContent: 'Original content',
@@ -154,7 +154,7 @@ describe('ResourceComposePage', () => {
154
154
  mode: 'clone',
155
155
  cloneData: {
156
156
  sourceResource: {
157
- '@id': 'http://localhost/resources/1',
157
+ '@id': '1',
158
158
  name: 'Original Resource',
159
159
  } as any,
160
160
  sourceContent: 'Original content',
@@ -174,7 +174,7 @@ describe('ResourceComposePage', () => {
174
174
  mode: 'clone',
175
175
  cloneData: {
176
176
  sourceResource: {
177
- '@id': 'http://localhost/resources/1',
177
+ '@id': '1',
178
178
  name: 'Original Resource',
179
179
  } as any,
180
180
  sourceContent: 'Original content',
@@ -190,7 +190,7 @@ describe('ResourceComposePage', () => {
190
190
  mode: 'clone',
191
191
  cloneData: {
192
192
  sourceResource: {
193
- '@id': 'http://localhost/resources/1',
193
+ '@id': '1',
194
194
  name: 'Original Resource',
195
195
  } as any,
196
196
  sourceContent: 'Original content',
@@ -448,7 +448,7 @@ describe('ResourceComposePage', () => {
448
448
  expect.objectContaining({
449
449
  theme: 'dark',
450
450
  }),
451
- expect.anything()
451
+ undefined,
452
452
  );
453
453
  });
454
454
  });
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import React, { useState, useEffect } from 'react';
10
- import type { components } from '@semiont/core';
10
+ import type { components, GatheredContext } from '@semiont/core';
11
11
  import { isImageMimeType, isPdfMimeType, LOCALES } from '@semiont/api-client';
12
12
  import { COMMON_PANELS, type ToolbarPanelType } from '../../../hooks/usePanelBrowse';
13
13
  import { buttonStyles } from '../../../lib/button-styles';
@@ -34,6 +34,9 @@ export interface ResourceComposePageProps {
34
34
  entityTypes: string[];
35
35
  };
36
36
 
37
+ // Gathered context from wizard (optional, for reference mode)
38
+ gatheredContext?: GatheredContext | null;
39
+
37
40
  // Available options
38
41
  availableEntityTypes: string[];
39
42
 
@@ -108,6 +111,7 @@ export function ResourceComposePage({
108
111
  mode,
109
112
  cloneData,
110
113
  referenceData,
114
+ gatheredContext,
111
115
  availableEntityTypes,
112
116
  initialLocale,
113
117
  theme,
@@ -277,6 +281,57 @@ export function ResourceComposePage({
277
281
  )}
278
282
  </div>
279
283
 
284
+ {/* Gathered Context Panel (from wizard) */}
285
+ {gatheredContext && (
286
+ <div className="semiont-form__field" style={{
287
+ marginBottom: '1.5rem',
288
+ padding: '1rem',
289
+ backgroundColor: 'var(--semiont-bg-secondary)',
290
+ borderRadius: 'var(--semiont-radius-md)',
291
+ border: '1px solid var(--semiont-border-primary)',
292
+ }}>
293
+ <h3 className="semiont-form__label" style={{ marginBottom: '0.75rem' }}>Gathered Context</h3>
294
+ {gatheredContext.sourceContext && (
295
+ <div style={{
296
+ padding: '0.75rem',
297
+ backgroundColor: 'var(--semiont-bg-primary)',
298
+ borderRadius: 'var(--semiont-radius-md)',
299
+ border: '1px solid var(--semiont-border-primary)',
300
+ maxHeight: '150px',
301
+ overflowY: 'auto',
302
+ marginBottom: '0.5rem',
303
+ }}>
304
+ <div style={{ fontSize: 'var(--semiont-text-sm)', fontFamily: 'monospace', whiteSpace: 'pre-wrap', color: 'var(--semiont-text-secondary)' }}>
305
+ {gatheredContext.sourceContext.before && <span>{gatheredContext.sourceContext.before}</span>}
306
+ <span style={{
307
+ backgroundColor: 'var(--semiont-color-primary-100)',
308
+ padding: '0 0.25rem',
309
+ fontWeight: 600,
310
+ color: 'var(--semiont-color-primary-900)',
311
+ }}>
312
+ {gatheredContext.sourceContext.selected}
313
+ </span>
314
+ {gatheredContext.sourceContext.after && <span>{gatheredContext.sourceContext.after}</span>}
315
+ </div>
316
+ </div>
317
+ )}
318
+ {gatheredContext.graphContext && gatheredContext.graphContext.connections && gatheredContext.graphContext.connections.length > 0 && (
319
+ <div style={{
320
+ padding: '0.5rem 0',
321
+ fontSize: 'var(--semiont-text-sm)',
322
+ color: 'var(--semiont-text-secondary)',
323
+ }}>
324
+ <span style={{ fontSize: 'var(--semiont-text-xs)', fontWeight: 500, marginRight: '0.5rem' }}>Connections:</span>
325
+ {gatheredContext.graphContext.connections.map(conn => (
326
+ <span key={conn.resourceId} className="semiont-chip" style={{ fontSize: 'var(--semiont-text-xs)', padding: '0.125rem 0.5rem', marginRight: '0.25rem' }}>
327
+ {conn.resourceName}
328
+ </span>
329
+ ))}
330
+ </div>
331
+ )}
332
+ </div>
333
+ )}
334
+
280
335
  {/* Create Form */}
281
336
  <div className="semiont-form">
282
337
  <form onSubmit={handleSaveResource} className="semiont-form__fields">
@@ -11,7 +11,7 @@ import type { ResourceCardProps } from '../components/ResourceCard';
11
11
 
12
12
  const createMockResource = (overrides?: any) => ({
13
13
  '@context': 'https://www.w3.org/ns/anno.jsonld',
14
- '@id': 'http://localhost/resources/test-123',
14
+ '@id': 'test-123',
15
15
  '@type': 'schema:DigitalDocument',
16
16
  name: 'Test Resource',
17
17
  description: 'A test resource',
@@ -13,7 +13,7 @@ import { EventBusProvider, resetEventBusForTesting } from '../../../contexts/Eve
13
13
 
14
14
  const createMockResource = (id: string, name: string, entityTypes: string[] = []) => ({
15
15
  '@context': 'https://www.w3.org/ns/anno.jsonld',
16
- '@id': `http://localhost/resources/${id}`,
16
+ '@id': id,
17
17
  '@type': 'schema:DigitalDocument',
18
18
  name,
19
19
  description: `Description for ${name}`,
@@ -352,7 +352,7 @@ describe('ResourceDiscoveryPage', () => {
352
352
  expect.objectContaining({
353
353
  theme: 'dark',
354
354
  }),
355
- expect.anything()
355
+ undefined,
356
356
  );
357
357
  });
358
358
  });
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Regression test: pendingAnnotation cleared after mark:createsucceeds
2
+ * Regression test: pendingAnnotation cleared after mark:submit succeeds
3
3
  *
4
4
  * Bug: handleAnnotationCreate in useMarkFlow called the API and emitted
5
5
  * mark:created, but never called setPendingAnnotation(null). The pending
@@ -25,7 +25,7 @@ import { EventBusProvider, useEventBus, resetEventBusForTesting } from '../../..
25
25
  import { ApiClientProvider } from '../../../contexts/ApiClientContext';
26
26
  import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
27
27
  import { SemiontApiClient } from '@semiont/api-client';
28
- import { resourceUri } from '@semiont/core';
28
+ import { resourceId } from '@semiont/core';
29
29
  import type { Emitter } from 'mitt';
30
30
  import type { EventMap } from '@semiont/core';
31
31
 
@@ -40,10 +40,10 @@ vi.mock('../../../components/Toast', () => ({
40
40
  }));
41
41
  import type { Motivation, Selector } from '@semiont/core';
42
42
 
43
- const TEST_URI = resourceUri('http://localhost:4000/resources/test-resource');
43
+ const TEST_URI = resourceId('test-resource');
44
44
 
45
45
  const MOCK_ANNOTATION = {
46
- id: 'http://localhost:4000/annotations/new-1',
46
+ id: 'new-1',
47
47
  type: 'Annotation',
48
48
  motivation: 'linking' as Motivation,
49
49
  target: { source: TEST_URI },
@@ -110,7 +110,7 @@ describe('Annotation creation clears pendingAnnotation', () => {
110
110
  resetEventBusForTesting();
111
111
  createAnnotationSpy = vi
112
112
  .spyOn(SemiontApiClient.prototype, 'createAnnotation')
113
- .mockResolvedValue({ annotation: MOCK_ANNOTATION } as any);
113
+ .mockResolvedValue({ annotationId: MOCK_ANNOTATION.id } as any);
114
114
  });
115
115
 
116
116
  afterEach(() => {
@@ -129,9 +129,9 @@ describe('Annotation creation clears pendingAnnotation', () => {
129
129
  expect(screen.getByTestId('pending-motivation')).toHaveTextContent('linking');
130
130
  });
131
131
 
132
- // Emit mark:create(what ReferencesPanel does when user clicks "Create Reference")
132
+ // Emit mark:submit(what ReferencesPanel does when user clicks "Create Reference")
133
133
  await act(async () => {
134
- emit('mark:create', {
134
+ emit('mark:submit', {
135
135
  motivation: 'linking',
136
136
  selector: TEXT_SELECTOR,
137
137
  body: [{ type: 'TextualBody', value: 'Person', purpose: 'tagging' }],
@@ -158,7 +158,7 @@ describe('Annotation creation clears pendingAnnotation', () => {
158
158
  });
159
159
 
160
160
  await act(async () => {
161
- emit('mark:create', {
161
+ emit('mark:submit', {
162
162
  motivation: 'assessing',
163
163
  selector: SVG_SELECTOR,
164
164
  body: [{ type: 'TextualBody', value: 'Looks good', purpose: 'assessing' }],
@@ -183,7 +183,7 @@ describe('Annotation creation clears pendingAnnotation', () => {
183
183
 
184
184
  // Empty body is valid for assessments
185
185
  await act(async () => {
186
- emit('mark:create', {
186
+ emit('mark:submit', {
187
187
  motivation: 'assessing',
188
188
  selector: SVG_SELECTOR,
189
189
  body: [],
@@ -207,7 +207,7 @@ describe('Annotation creation clears pendingAnnotation', () => {
207
207
  });
208
208
 
209
209
  await act(async () => {
210
- emit('mark:create', {
210
+ emit('mark:submit', {
211
211
  motivation: 'commenting',
212
212
  selector: TEXT_SELECTOR,
213
213
  body: [{ type: 'TextualBody', value: 'Great point', purpose: 'commenting' }],
@@ -231,7 +231,7 @@ describe('Annotation creation clears pendingAnnotation', () => {
231
231
  });
232
232
 
233
233
  await act(async () => {
234
- emit('mark:create', {
234
+ emit('mark:submit', {
235
235
  motivation: 'tagging',
236
236
  selector: SVG_SELECTOR,
237
237
  body: [{ type: 'TextualBody', value: 'concept:trust', purpose: 'tagging' }],
@@ -256,7 +256,7 @@ describe('Annotation creation clears pendingAnnotation', () => {
256
256
  });
257
257
 
258
258
  await act(async () => {
259
- emit('mark:create', {
259
+ emit('mark:submit', {
260
260
  motivation: 'linking',
261
261
  selector: TEXT_SELECTOR,
262
262
  body: [],
@@ -265,7 +265,7 @@ describe('Annotation creation clears pendingAnnotation', () => {
265
265
 
266
266
  await waitFor(() => {
267
267
  expect(createdListener).toHaveBeenCalledTimes(1);
268
- expect(createdListener).toHaveBeenCalledWith({ annotation: MOCK_ANNOTATION });
268
+ expect(createdListener).toHaveBeenCalledWith({ annotationId: 'new-1' });
269
269
  });
270
270
 
271
271
  subscription.unsubscribe();
@@ -285,7 +285,7 @@ describe('Annotation creation clears pendingAnnotation', () => {
285
285
  });
286
286
 
287
287
  await act(async () => {
288
- emit('mark:create', {
288
+ emit('mark:submit', {
289
289
  motivation: 'linking',
290
290
  selector: TEXT_SELECTOR,
291
291
  body: [],