@semiont/react-ui 0.5.5 → 0.5.7

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 (218) hide show
  1. package/README.md +59 -55
  2. package/dist/{PdfAnnotationCanvas.client-CN3C3S55.js → PdfAnnotationCanvas.client-NIMALXNZ.js} +7 -27
  3. package/dist/PdfAnnotationCanvas.client-NIMALXNZ.js.map +1 -0
  4. package/dist/{ar-U2EXWUMQ.js → ar-SONK6MON.js} +3 -7
  5. package/dist/ar-SONK6MON.js.map +1 -0
  6. package/dist/{bn-DRJGV772.js → bn-ZKPRITNG.js} +3 -7
  7. package/dist/bn-ZKPRITNG.js.map +1 -0
  8. package/dist/{chunk-3Q3TUKWP.js → chunk-Y2EEAOMZ.js} +29 -29
  9. package/dist/{cs-PTWDM23V.js → cs-LPXQ7NHQ.js} +3 -7
  10. package/dist/cs-LPXQ7NHQ.js.map +1 -0
  11. package/dist/{da-KSNIKYSS.js → da-6TKY7MCY.js} +6 -10
  12. package/dist/da-6TKY7MCY.js.map +1 -0
  13. package/dist/{de-F2XBEWFY.js → de-C3GNII74.js} +3 -7
  14. package/dist/de-C3GNII74.js.map +1 -0
  15. package/dist/{el-DLD2GWAP.js → el-UBCXQDJ7.js} +3 -7
  16. package/dist/el-UBCXQDJ7.js.map +1 -0
  17. package/dist/{es-WLPYWGB5.js → es-BQ23TRI7.js} +11 -15
  18. package/dist/es-BQ23TRI7.js.map +1 -0
  19. package/dist/{fa-BAXHSDZG.js → fa-AFTBZB77.js} +3 -7
  20. package/dist/fa-AFTBZB77.js.map +1 -0
  21. package/dist/{fi-FCHSYVOT.js → fi-WOYNLZC2.js} +3 -7
  22. package/dist/fi-WOYNLZC2.js.map +1 -0
  23. package/dist/{fr-3UERBSL6.js → fr-NDSMIFJM.js} +3 -7
  24. package/dist/fr-NDSMIFJM.js.map +1 -0
  25. package/dist/{he-F6F3FV2K.js → he-VJXVRDOY.js} +3 -7
  26. package/dist/he-VJXVRDOY.js.map +1 -0
  27. package/dist/{hi-4BK6IK7Q.js → hi-BF6PHIE2.js} +3 -7
  28. package/dist/hi-BF6PHIE2.js.map +1 -0
  29. package/dist/{id-7ECCWP3J.js → id-GXG5QCZY.js} +3 -7
  30. package/dist/id-GXG5QCZY.js.map +1 -0
  31. package/dist/index.css +103 -0
  32. package/dist/index.css.map +1 -1
  33. package/dist/index.d.ts +271 -120
  34. package/dist/index.js +877 -698
  35. package/dist/index.js.map +1 -1
  36. package/dist/{it-234Z6XK6.js → it-XKHHCBAF.js} +3 -7
  37. package/dist/it-XKHHCBAF.js.map +1 -0
  38. package/dist/{ja-PJWQI4OQ.js → ja-TX7VM4XD.js} +3 -7
  39. package/dist/ja-TX7VM4XD.js.map +1 -0
  40. package/dist/{ko-APUEW2RS.js → ko-DNC7EQ7J.js} +3 -7
  41. package/dist/ko-DNC7EQ7J.js.map +1 -0
  42. package/dist/{ms-PJBZWZWD.js → ms-POZGBKPH.js} +3 -7
  43. package/dist/ms-POZGBKPH.js.map +1 -0
  44. package/dist/{nl-L4C3ZBCU.js → nl-IRMTKI7Z.js} +4 -11
  45. package/dist/nl-IRMTKI7Z.js.map +1 -0
  46. package/dist/{no-QE5N5KNG.js → no-ZUDJA4S6.js} +20 -24
  47. package/dist/no-ZUDJA4S6.js.map +1 -0
  48. package/dist/{pl-5Q2D23PD.js → pl-2NGAXL5U.js} +3 -7
  49. package/dist/pl-2NGAXL5U.js.map +1 -0
  50. package/dist/{pt-AIGUOIOC.js → pt-ABMCXZUM.js} +118 -122
  51. package/dist/pt-ABMCXZUM.js.map +1 -0
  52. package/dist/{ro-T56CSHTY.js → ro-VOJP6O5X.js} +3 -7
  53. package/dist/ro-VOJP6O5X.js.map +1 -0
  54. package/dist/{sv-L4TJQ2UH.js → sv-4HVFIIE5.js} +43 -47
  55. package/dist/sv-4HVFIIE5.js.map +1 -0
  56. package/dist/test-utils.js +2 -2
  57. package/dist/test-utils.js.map +1 -1
  58. package/dist/{th-6O7Y6O2Q.js → th-IFPZP3HQ.js} +3 -7
  59. package/dist/th-IFPZP3HQ.js.map +1 -0
  60. package/dist/{tr-D4CQCSNO.js → tr-2GYEAMJ4.js} +3 -7
  61. package/dist/tr-2GYEAMJ4.js.map +1 -0
  62. package/dist/{uk-2HMQG6ND.js → uk-XCJBVLLD.js} +3 -7
  63. package/dist/uk-XCJBVLLD.js.map +1 -0
  64. package/dist/{vi-XVJ4RUEJ.js → vi-4FR7CB2F.js} +3 -7
  65. package/dist/vi-4FR7CB2F.js.map +1 -0
  66. package/dist/{zh-K2KDPGHK.js → zh-NSKFOINB.js} +3 -7
  67. package/dist/zh-NSKFOINB.js.map +1 -0
  68. package/package.json +17 -13
  69. package/src/components/Button/__tests__/Button.test.tsx +0 -2
  70. package/src/components/CodeMirrorRenderer.tsx +2 -0
  71. package/src/components/ErrorBoundary.tsx +0 -9
  72. package/src/components/ProtectedErrorBoundary.css +119 -0
  73. package/src/components/ProtectedErrorBoundary.tsx +24 -15
  74. package/src/components/__tests__/AnnotateReferencesProgressWidget.test.tsx +0 -1
  75. package/src/components/__tests__/ErrorBoundary.test.tsx +20 -13
  76. package/src/components/__tests__/LiveRegion.hooks.test.tsx +1 -1
  77. package/src/components/__tests__/ProtectedErrorBoundary.test.tsx +2 -1
  78. package/src/components/__tests__/ResizeHandle.test.tsx +0 -1
  79. package/src/components/__tests__/SessionExpiryBanner.test.tsx +0 -1
  80. package/src/components/__tests__/StatusDisplay.test.tsx +0 -1
  81. package/src/components/__tests__/Toast.test.tsx +2 -3
  82. package/src/components/__tests__/Toolbar.test.tsx +0 -1
  83. package/src/components/annotation/annotations.css +14 -0
  84. package/src/components/annotation-popups/__tests__/JsonLdView.test.tsx +3 -5
  85. package/src/components/annotation-popups/__tests__/SharedPopupElements.test.tsx +0 -1
  86. package/src/components/branding/__tests__/SemiontBranding.test.tsx +1 -2
  87. package/src/components/layout/__tests__/LeftSidebar.test.tsx +5 -6
  88. package/src/components/layout/__tests__/PageLayout.test.tsx +1 -3
  89. package/src/components/layout/__tests__/SkipLinks.a11y.test.tsx +8 -8
  90. package/src/components/layout/__tests__/UnifiedHeader.test.tsx +12 -1
  91. package/src/components/modals/__tests__/KeyboardShortcutsHelpModal.test.tsx +0 -1
  92. package/src/components/modals/__tests__/PermissionDeniedModal.test.tsx +3 -4
  93. package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +1 -2
  94. package/src/components/modals/__tests__/SearchModal.basic.test.tsx +1 -1
  95. package/src/components/modals/__tests__/SearchModal.keyboard.test.tsx +0 -5
  96. package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +1 -2
  97. package/src/components/modals/__tests__/SearchModal.visual.test.tsx +2 -2
  98. package/src/components/modals/__tests__/SessionExpiredModal.test.tsx +0 -1
  99. package/src/components/navigation/NavigationMenu.tsx +1 -1
  100. package/src/components/navigation/__tests__/Footer.a11y.test.tsx +4 -0
  101. package/src/components/navigation/__tests__/Footer.test.tsx +3 -6
  102. package/src/components/navigation/__tests__/NavigationMenu.a11y.test.tsx +1 -1
  103. package/src/components/navigation/__tests__/NavigationMenu.test.tsx +7 -9
  104. package/src/components/navigation/__tests__/ObservableLink.test.tsx +0 -1
  105. package/src/components/navigation/__tests__/SimpleNavigation.test.tsx +1 -2
  106. package/src/components/navigation/__tests__/SortableResourceTab.test.tsx +0 -1
  107. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +6 -4
  108. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +10 -19
  109. package/src/components/resource/AnnotateView.tsx +35 -37
  110. package/src/components/resource/BrowseView.tsx +31 -31
  111. package/src/components/resource/__tests__/AnnotationHistory.test.tsx +0 -1
  112. package/src/components/resource/__tests__/BrowseView.test.tsx +12 -14
  113. package/src/components/resource/__tests__/HistoryEvent.test.tsx +0 -5
  114. package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +4 -6
  115. package/src/components/resource/__tests__/event-formatting.test.ts +1 -1
  116. package/src/components/resource/panels/CollaborationPanel.tsx +1 -1
  117. package/src/components/resource/panels/JsonLdPanel.tsx +33 -16
  118. package/src/components/resource/panels/ReferencesPanel.tsx +1 -1
  119. package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +4 -5
  120. package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +8 -7
  121. package/src/components/resource/panels/__tests__/AssistSection.test.tsx +14 -10
  122. package/src/components/resource/panels/__tests__/CollaborationPanel.test.tsx +0 -1
  123. package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +31 -18
  124. package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +7 -6
  125. package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +5 -6
  126. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +19 -13
  127. package/src/components/resource/panels/__tests__/JsonLdPanel.test.tsx +95 -426
  128. package/src/components/resource/panels/__tests__/PanelHeader.test.tsx +0 -1
  129. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +5 -5
  130. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +40 -7
  131. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +4 -4
  132. package/src/components/resource/panels/__tests__/StatisticsPanel.test.tsx +30 -32
  133. package/src/components/resource/panels/__tests__/TagEntry.test.tsx +6 -6
  134. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +7 -6
  135. package/src/components/settings/__tests__/SettingsPanel.test.tsx +0 -1
  136. package/src/components/viewers/__tests__/ImageViewer.test.tsx +0 -1
  137. package/src/features/admin-exchange/__tests__/AdminExchangePage.test.tsx +7 -10
  138. package/src/features/admin-exchange/__tests__/ImportProgress.test.tsx +38 -27
  139. package/src/features/admin-exchange/components/ImportProgress.tsx +28 -34
  140. package/src/features/auth/__tests__/SignInForm.a11y.test.tsx +2 -0
  141. package/src/features/auth/__tests__/SignUpForm.a11y.test.tsx +11 -12
  142. package/src/features/auth/__tests__/SignUpForm.test.tsx +3 -3
  143. package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -0
  144. package/src/features/moderation-linked-data/__tests__/LinkedDataPage.test.tsx +11 -9
  145. package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +2 -1
  146. package/src/features/resource-compose/components/ResourceComposePage.tsx +36 -9
  147. package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -8
  148. package/src/features/resource-discovery/__tests__/ResourceCard.test.tsx +0 -1
  149. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +33 -35
  150. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +12 -11
  151. package/src/features/resource-discovery/state/__tests__/discover-state-unit.test.ts +204 -11
  152. package/src/features/resource-discovery/state/discover-state-unit.ts +70 -11
  153. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +2 -2
  154. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +10 -7
  155. package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +37 -1
  156. package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +14 -7
  157. package/src/integrations/__tests__/css-modules-helper.test.tsx +2 -3
  158. package/src/integrations/__tests__/styled-components-theme.test.ts +1 -3
  159. package/src/styles/features/exchange.css +0 -30
  160. package/src/styles/index.css +1 -0
  161. package/translations/ar.json +1 -3
  162. package/translations/bn.json +1 -3
  163. package/translations/cs.json +1 -3
  164. package/translations/da.json +4 -6
  165. package/translations/de.json +1 -3
  166. package/translations/el.json +1 -3
  167. package/translations/es.json +9 -11
  168. package/translations/fa.json +1 -3
  169. package/translations/fi.json +1 -3
  170. package/translations/fr.json +1 -3
  171. package/translations/he.json +1 -3
  172. package/translations/hi.json +1 -3
  173. package/translations/id.json +1 -3
  174. package/translations/it.json +1 -3
  175. package/translations/ja.json +1 -3
  176. package/translations/ko.json +1 -3
  177. package/translations/ms.json +1 -3
  178. package/translations/nl.json +2 -7
  179. package/translations/no.json +18 -20
  180. package/translations/pl.json +1 -3
  181. package/translations/pt.json +116 -118
  182. package/translations/ro.json +1 -3
  183. package/translations/sv.json +41 -43
  184. package/translations/th.json +1 -3
  185. package/translations/tr.json +1 -3
  186. package/translations/uk.json +1 -3
  187. package/translations/vi.json +1 -3
  188. package/translations/zh.json +1 -3
  189. package/dist/PdfAnnotationCanvas.client-CN3C3S55.js.map +0 -1
  190. package/dist/ar-U2EXWUMQ.js.map +0 -1
  191. package/dist/bn-DRJGV772.js.map +0 -1
  192. package/dist/cs-PTWDM23V.js.map +0 -1
  193. package/dist/da-KSNIKYSS.js.map +0 -1
  194. package/dist/de-F2XBEWFY.js.map +0 -1
  195. package/dist/el-DLD2GWAP.js.map +0 -1
  196. package/dist/es-WLPYWGB5.js.map +0 -1
  197. package/dist/fa-BAXHSDZG.js.map +0 -1
  198. package/dist/fi-FCHSYVOT.js.map +0 -1
  199. package/dist/fr-3UERBSL6.js.map +0 -1
  200. package/dist/he-F6F3FV2K.js.map +0 -1
  201. package/dist/hi-4BK6IK7Q.js.map +0 -1
  202. package/dist/id-7ECCWP3J.js.map +0 -1
  203. package/dist/it-234Z6XK6.js.map +0 -1
  204. package/dist/ja-PJWQI4OQ.js.map +0 -1
  205. package/dist/ko-APUEW2RS.js.map +0 -1
  206. package/dist/ms-PJBZWZWD.js.map +0 -1
  207. package/dist/nl-L4C3ZBCU.js.map +0 -1
  208. package/dist/no-QE5N5KNG.js.map +0 -1
  209. package/dist/pl-5Q2D23PD.js.map +0 -1
  210. package/dist/pt-AIGUOIOC.js.map +0 -1
  211. package/dist/ro-T56CSHTY.js.map +0 -1
  212. package/dist/sv-L4TJQ2UH.js.map +0 -1
  213. package/dist/th-6O7Y6O2Q.js.map +0 -1
  214. package/dist/tr-D4CQCSNO.js.map +0 -1
  215. package/dist/uk-2HMQG6ND.js.map +0 -1
  216. package/dist/vi-XVJ4RUEJ.js.map +0 -1
  217. package/dist/zh-K2KDPGHK.js.map +0 -1
  218. /package/dist/{chunk-3Q3TUKWP.js.map → chunk-Y2EEAOMZ.js.map} +0 -0
@@ -4,7 +4,7 @@ import { useEffect, useRef, useCallback, useMemo, memo, lazy, Suspense } from 'r
4
4
  import ReactMarkdown from 'react-markdown';
5
5
  import remarkGfm from 'remark-gfm';
6
6
  import { annotationId as toAnnotationId } from '@semiont/core';
7
- import { getMimeCategory, isPdfMimeType } from '@semiont/core';
7
+ import { capabilitiesOf } from '@semiont/core';
8
8
  import { createHoverHandlers } from '@semiont/sdk';
9
9
  import { ANNOTATORS } from '../../lib/annotation-registry';
10
10
  import { scrollAnnotationIntoView } from '../../lib/scroll-utils';
@@ -83,7 +83,7 @@ export const BrowseView = memo(function BrowseView({
83
83
  const session = useObservable(useSemiont().activeSession$);
84
84
  const containerRef = useRef<HTMLDivElement>(null);
85
85
 
86
- const category = getMimeCategory(mimeType);
86
+ const render = capabilitiesOf(mimeType)?.render ?? 'none';
87
87
 
88
88
  const { highlights, references, assessments, comments, tags } = annotations;
89
89
 
@@ -207,8 +207,9 @@ export const BrowseView = memo(function BrowseView({
207
207
  'beckon:focus': handleAnnotationFocus,
208
208
  });
209
209
 
210
- // Route to appropriate viewer based on MIME type category
211
- switch (category) {
210
+ // Route to the viewer for this media type's render mode. The switch is
211
+ // exhaustive over RenderMode, so every path returns.
212
+ switch (render) {
212
213
  case 'text':
213
214
  return (
214
215
  <div className="semiont-browse-view" data-mime-type="text">
@@ -226,34 +227,31 @@ export const BrowseView = memo(function BrowseView({
226
227
  </div>
227
228
  );
228
229
 
229
- case 'image':
230
- // Check if it's actually a PDF (categorized as 'image' for spatial annotations)
231
- if (isPdfMimeType(mimeType)) {
232
- return (
233
- <div className="semiont-browse-view" data-mime-type="pdf">
234
- <AnnotateToolbar
235
- selectedMotivation={null}
236
- selectedClick={selectedClick}
237
- showSelectionGroup={false}
238
- showDeleteButton={false}
239
- annotateMode={annotateMode}
240
- annotators={ANNOTATORS}
241
- />
242
- <div ref={containerRef} className="semiont-browse-view__content">
243
- <Suspense fallback={<div className="semiont-browse-view__loading">Loading PDF viewer...</div>}>
244
- <PdfAnnotationCanvas
245
- pdfUrl={content}
246
- existingAnnotations={allAnnotations}
247
- drawingMode={null}
248
- selectedMotivation={null}
249
- />
250
- </Suspense>
251
- </div>
230
+ case 'pdf':
231
+ return (
232
+ <div className="semiont-browse-view" data-mime-type="pdf">
233
+ <AnnotateToolbar
234
+ selectedMotivation={null}
235
+ selectedClick={selectedClick}
236
+ showSelectionGroup={false}
237
+ showDeleteButton={false}
238
+ annotateMode={annotateMode}
239
+ annotators={ANNOTATORS}
240
+ />
241
+ <div ref={containerRef} className="semiont-browse-view__content">
242
+ <Suspense fallback={<div className="semiont-browse-view__loading">Loading PDF viewer...</div>}>
243
+ <PdfAnnotationCanvas
244
+ pdfUrl={content}
245
+ existingAnnotations={allAnnotations}
246
+ drawingMode={null}
247
+ selectedMotivation={null}
248
+ />
249
+ </Suspense>
252
250
  </div>
253
- );
254
- }
251
+ </div>
252
+ );
255
253
 
256
- // Regular image
254
+ case 'image':
257
255
  return (
258
256
  <div className="semiont-browse-view" data-mime-type="image">
259
257
  <AnnotateToolbar
@@ -274,7 +272,9 @@ export const BrowseView = memo(function BrowseView({
274
272
  </div>
275
273
  );
276
274
 
277
- case 'unsupported':
275
+ case 'none':
276
+ // Catalogued type with no preview (render: 'none') or an imported
277
+ // foreign type the registry doesn't know — same UI: metadata + download.
278
278
  return (
279
279
  <div ref={containerRef} className="semiont-browse-view semiont-browse-view--unsupported" data-mime-type="unsupported">
280
280
  <div className="semiont-browse-view__empty">
@@ -78,7 +78,6 @@ function makeStoredEvent(id: string, type: string, seq: number, overrides: Recor
78
78
  ...overrides,
79
79
  metadata: {
80
80
  sequenceNumber: seq,
81
- streamPosition: 0,
82
81
  },
83
82
  };
84
83
  }
@@ -2,10 +2,10 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
2
  import React from 'react';
3
3
  import { render, screen, fireEvent, waitFor } from '@testing-library/react';
4
4
  import { BrowseView } from '../BrowseView';
5
- import type { components, EventBus } from '@semiont/core';
5
+ import type { EventBus } from '@semiont/core';
6
6
  import { createTestSemiontWrapper } from '../../../test-utils';
7
7
 
8
- import type { Annotation } from '@semiont/core';
8
+ import type { Annotation, AnnotationId } from '@semiont/core';
9
9
 
10
10
  // Mock ResourceAnnotationsContext - keep this simple
11
11
  let mockNewAnnotationIds = new Set<string>();
@@ -15,18 +15,14 @@ vi.mock('../../../contexts/ResourceAnnotationsContext', () => ({
15
15
  })),
16
16
  }));
17
17
 
18
- // Mock @semiont/core utilities (these helpers and `resourceId` all live in core).
18
+ // Mock @semiont/core utilities. The media-type registry (`capabilitiesOf`)
19
+ // is NOT mocked — BrowseView dispatches on the real registry's render mode,
20
+ // so the tested types (text/markdown, image/png, application/octet-stream)
21
+ // resolve through the real source of truth.
19
22
  vi.mock('@semiont/core', async () => {
20
23
  const actual = await vi.importActual('@semiont/core');
21
24
  return {
22
25
  ...actual,
23
- getMimeCategory: vi.fn((mimeType: string) => {
24
- if (mimeType.startsWith('text/')) return 'text';
25
- if (mimeType.startsWith('image/')) return 'image';
26
- if (mimeType === 'application/pdf') return 'image';
27
- return 'unsupported';
28
- }),
29
- isPdfMimeType: vi.fn((mimeType: string) => mimeType === 'application/pdf'),
30
26
  resourceId: vi.fn((id: string) => id),
31
27
  getExactText: vi.fn(() => 'exact text'),
32
28
  getTextPositionSelector: vi.fn(() => ({ start: 0, end: 10 })),
@@ -151,12 +147,12 @@ const renderWithEventTracking = (
151
147
  };
152
148
 
153
149
  // Test data fixtures
154
- const createMockAnnotation = (motivation: string, id: string): Annotation => ({
150
+ const createMockAnnotation = (motivation: Annotation['motivation'], id: string): Annotation => ({
155
151
  '@context': 'http://www.w3.org/ns/anno.jsonld',
156
- id,
152
+ id: id as AnnotationId,
157
153
  type: 'Annotation',
158
154
  motivation,
159
- creator: { name: 'user@example.com' },
155
+ creator: { '@type': 'Person', name: 'user@example.com' },
160
156
  created: '2024-01-01T10:00:00Z',
161
157
  target: {
162
158
  source: 'resource-1',
@@ -197,7 +193,9 @@ describe('BrowseView Component', () => {
197
193
  // Mock querySelector and querySelectorAll
198
194
  if (typeof document !== 'undefined') {
199
195
  document.querySelector = vi.fn();
200
- document.querySelectorAll = vi.fn(() => []);
196
+ // Return a real (empty) NodeList so the mock matches the DOM signature.
197
+ const emptyNodeList = document.createDocumentFragment().querySelectorAll('*');
198
+ document.querySelectorAll = vi.fn(() => emptyNodeList);
201
199
  }
202
200
  });
203
201
 
@@ -1,10 +1,8 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import React from 'react';
3
2
  import { screen, fireEvent, act } from '@testing-library/react';
4
3
  import '@testing-library/jest-dom';
5
4
  import { HistoryEvent } from '../HistoryEvent';
6
5
  import { renderWithProviders } from '../../../test-utils';
7
- import type { StoredEvent } from '@semiont/core';
8
6
 
9
7
  // Mock @semiont/core - must use importOriginal to preserve EventBus etc.
10
8
  vi.mock('@semiont/core', async (importOriginal) => {
@@ -37,8 +35,6 @@ vi.mock('../event-formatting', () => ({
37
35
  import { getAnnotationUriFromEvent } from '@semiont/core';
38
36
  import {
39
37
  formatEventType,
40
- getEventEmoji,
41
- formatRelativeTime,
42
38
  getEventDisplayContent,
43
39
  getEventEntityTypes,
44
40
  getResourceCreationDetails,
@@ -62,7 +58,6 @@ function makeStoredEvent(overrides: Record<string, any> = {}): any {
62
58
  ...overrides,
63
59
  metadata: {
64
60
  sequenceNumber: 1,
65
- streamPosition: 0,
66
61
  },
67
62
  };
68
63
  }
@@ -11,15 +11,13 @@
11
11
  */
12
12
 
13
13
  import { describe, it, expect, vi, beforeEach } from 'vitest';
14
- import { render, screen, fireEvent, waitFor } from '@testing-library/react';
14
+ import { render, screen } from '@testing-library/react';
15
15
  import { BehaviorSubject } from 'rxjs';
16
16
  import { ResourceViewer } from '../ResourceViewer';
17
17
  import { createTestSemiontWrapper } from '../../../test-utils';
18
18
  import { TranslationProvider } from '../../../contexts/TranslationContext';
19
19
  import { ResourceAnnotationsProvider } from '../../../contexts/ResourceAnnotationsContext';
20
- import type { components } from '@semiont/core';
21
-
22
- type SemiontResource = components['schemas']['ResourceDescriptor'];
20
+ import type { ResourceDescriptor as SemiontResource, ResourceId } from '@semiont/core';
23
21
 
24
22
  // Mock dependencies
25
23
  vi.mock('../../../hooks/useObservableBrowse', () => ({
@@ -61,7 +59,7 @@ vi.mock('../../../session/SemiontProvider', async () => {
61
59
 
62
60
  const mockResource: SemiontResource & { content: string } = {
63
61
  '@context': 'https://www.w3.org/ns/activitystreams',
64
- '@id': 'test-123',
62
+ '@id': 'test-123' as ResourceId,
65
63
  name: 'Test Document',
66
64
  created: '2024-01-01T00:00:00Z',
67
65
  entityTypes: [],
@@ -84,7 +82,7 @@ const mockAnnotations = {
84
82
  };
85
83
 
86
84
  const mockTranslationManager = {
87
- t: (namespace: string, key: string, params?: Record<string, any>) => {
85
+ t: (namespace: string, key: string) => {
88
86
  return `${namespace}.${key}`;
89
87
  },
90
88
  };
@@ -8,7 +8,7 @@ import {
8
8
  getResourceCreationDetails,
9
9
  } from '../event-formatting';
10
10
 
11
- // Mock api-client functions
11
+ // Mock http-transport functions
12
12
  vi.mock('@semiont/core', async (importOriginal) => {
13
13
  const actual = await importOriginal<typeof import('@semiont/core')>();
14
14
  return {
@@ -7,7 +7,7 @@ import './CollaborationPanel.css';
7
7
  interface Props {
8
8
  /**
9
9
  * Connection state from `client.actor.state$`. See
10
- * `packages/api-client/src/state/domain/actor-state-unit.ts`.
10
+ * `packages/http-transport/src/state/domain/actor-state-unit.ts`.
11
11
  *
12
12
  * UI mapping:
13
13
  * `open` | `reconnecting` | `initial` | `connecting`
@@ -8,42 +8,46 @@ 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';
11
+ import { useResourceGraph } from '../../../hooks/useResourceGraph';
12
+ import type { ResourceId } from '@semiont/core';
12
13
  import './JsonLdPanel.css';
13
14
 
14
- type SemiontResource = components['schemas']['ResourceDescriptor'];
15
-
16
15
  interface Props {
17
- resource: SemiontResource;
16
+ resourceId: ResourceId;
18
17
  }
19
18
 
20
- export function JsonLdPanel({ resource: semiontResource }: Props) {
19
+ /**
20
+ * Dereferences the resource's LD face (`GET /resources/:id/jsonld` via
21
+ * `browse.resourceGraph`) and pretty-prints the full graph — descriptor +
22
+ * annotations + inbound entity references — read-only. This is exactly what
23
+ * an external linked-data client gets when dereferencing the resource's
24
+ * `describedby` URI, so the panel doubles as a living end-to-end test of the
25
+ * LD face. See `.plans/SIMPLER-JSON-LD.md` §5.
26
+ */
27
+ export function JsonLdPanel({ resourceId }: Props) {
21
28
  const editorRef = useRef<HTMLDivElement>(null);
22
29
  const viewRef = useRef<EditorView | null>(null);
23
30
  const { showLineNumbers } = useLineNumbers();
31
+ const { graph, loading, error } = useResourceGraph(resourceId);
32
+
33
+ const documentText = graph ? JSON.stringify(graph, null, 2) : '';
24
34
 
25
- // Initialize CodeMirror
35
+ // Initialize CodeMirror once the graph has loaded.
26
36
  useEffect(() => {
27
- if (!editorRef.current) return;
37
+ if (!editorRef.current || !documentText) return;
28
38
 
29
- // Check if dark mode is active
30
39
  const isDarkMode = document.documentElement?.classList.contains('dark') ?? false;
31
40
 
32
- // Convert resource to JSON-LD format
33
- const jsonLdContent = JSON.stringify(semiontResource, null, 2);
34
-
35
41
  const extensions = [
36
42
  json(),
37
43
  EditorView.editable.of(false),
38
44
  EditorState.readOnly.of(true),
39
45
  ];
40
46
 
41
- // Add line numbers if enabled
42
47
  if (showLineNumbers) {
43
48
  extensions.push(lineNumbers());
44
49
  }
45
50
 
46
- // Add theme based on dark/light mode
47
51
  if (isDarkMode) {
48
52
  extensions.push(oneDark);
49
53
  } else {
@@ -52,7 +56,7 @@ export function JsonLdPanel({ resource: semiontResource }: Props) {
52
56
  }
53
57
 
54
58
  const state = EditorState.create({
55
- doc: jsonLdContent,
59
+ doc: documentText,
56
60
  extensions,
57
61
  });
58
62
 
@@ -67,11 +71,12 @@ export function JsonLdPanel({ resource: semiontResource }: Props) {
67
71
  view.destroy();
68
72
  viewRef.current = null;
69
73
  };
70
- }, [semiontResource, showLineNumbers]);
74
+ }, [documentText, showLineNumbers]);
71
75
 
72
76
  const handleCopyToClipboard = async () => {
77
+ if (!documentText) return;
73
78
  try {
74
- await navigator.clipboard.writeText(JSON.stringify(semiontResource, null, 2));
79
+ await navigator.clipboard.writeText(documentText);
75
80
  } catch (err) {
76
81
  console.error('Failed to copy JSON-LD:', err);
77
82
  }
@@ -88,11 +93,23 @@ export function JsonLdPanel({ resource: semiontResource }: Props) {
88
93
  onClick={handleCopyToClipboard}
89
94
  className="semiont-button semiont-button--icon"
90
95
  title="Copy to clipboard"
96
+ disabled={!graph}
91
97
  >
92
98
  📋 Copy
93
99
  </button>
94
100
  </div>
95
101
 
102
+ {loading && (
103
+ <p className="semiont-jsonld-panel__status" role="status">
104
+ Loading JSON-LD…
105
+ </p>
106
+ )}
107
+ {error && !loading && (
108
+ <p className="semiont-jsonld-panel__status semiont-jsonld-panel__status--error" role="alert">
109
+ Failed to load JSON-LD.
110
+ </p>
111
+ )}
112
+
96
113
  {/* JSON-LD content rendered with CodeMirror */}
97
114
  <div
98
115
  ref={editorRef}
@@ -236,7 +236,7 @@ export function ReferencesPanel({
236
236
  }
237
237
 
238
238
  // When annotation is complete and we haven't saved yet, save the log
239
- if (!isAssisting && !hasSavedLogRef.current && progress?.completedEntityTypes) {
239
+ if (!hasSavedLogRef.current && progress?.completedEntityTypes) {
240
240
  hasSavedLogRef.current = true;
241
241
  setLastDetectionLog(progress.completedEntityTypes);
242
242
  setSelectedEntityTypes([]);
@@ -1,14 +1,12 @@
1
1
  import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import React from 'react';
3
2
  import { screen } from '@testing-library/react';
4
3
  import '@testing-library/jest-dom';
5
4
  import { renderWithProviders } from '../../../../test-utils';
6
5
  import userEvent from '@testing-library/user-event';
7
- import type { components } from '@semiont/core';
8
6
 
9
- import type { Annotation } from '@semiont/core';
7
+ import type { Annotation, AnnotationId } from '@semiont/core';
10
8
 
11
- // Mock @semiont/api-client
9
+ // Mock @semiont/http-transport
12
10
  vi.mock('@semiont/core', async () => {
13
11
  const actual = await vi.importActual('@semiont/core');
14
12
  return {
@@ -25,10 +23,11 @@ const mockGetAnnotationExactText = getAnnotationExactText as MockedFunction<type
25
23
 
26
24
  const createMockAssessment = (overrides?: Partial<Annotation>): Annotation => ({
27
25
  '@context': 'http://www.w3.org/ns/anno.jsonld',
28
- id: 'assessment-1',
26
+ id: 'assessment-1' as AnnotationId,
29
27
  type: 'Annotation',
30
28
  motivation: 'assessing',
31
29
  creator: {
30
+ '@type': 'Person',
32
31
  name: 'reviewer@example.com',
33
32
  },
34
33
  created: '2024-06-15T12:00:00Z',
@@ -1,14 +1,14 @@
1
1
  import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
2
  import type { MockedFunction } from 'vitest';
3
3
  import React from 'react';
4
- import { render, screen, fireEvent, waitFor } from '@testing-library/react';
4
+ import { render, screen, waitFor } from '@testing-library/react';
5
5
  import userEvent from '@testing-library/user-event';
6
6
  import '@testing-library/jest-dom';
7
7
  import { AssessmentPanel } from '../AssessmentPanel';
8
- import type { components, EventBus } from '@semiont/core';
8
+ import type { EventBus } from '@semiont/core';
9
9
  import { createTestSemiontWrapper } from '../../../../test-utils';
10
10
 
11
- import type { Annotation } from '@semiont/core';
11
+ import type { Annotation, AnnotationId } from '@semiont/core';
12
12
 
13
13
  // Composition-based event tracker
14
14
  interface TrackedEvent {
@@ -57,7 +57,7 @@ vi.mock('../../../../contexts/TranslationContext', () => ({
57
57
  TranslationProvider: ({ children }: { children: React.ReactNode }) => children,
58
58
  }));
59
59
 
60
- // Mock @semiont/api-client utilities
60
+ // Mock @semiont/http-transport utilities
61
61
  vi.mock('@semiont/core', async () => {
62
62
  const actual = await vi.importActual('@semiont/core');
63
63
  return {
@@ -69,7 +69,7 @@ vi.mock('@semiont/core', async () => {
69
69
 
70
70
  // Mock AssessmentEntry component to simplify testing
71
71
  vi.mock('../AssessmentEntry', () => ({
72
- AssessmentEntry: ({ assessment, onAssessmentRef }: any) => (
72
+ AssessmentEntry: ({ assessment }: any) => (
73
73
  <div data-testid={`assessment-${assessment.id}`}>
74
74
  <div>{assessment.id}</div>
75
75
  </div>
@@ -78,7 +78,7 @@ vi.mock('../AssessmentEntry', () => ({
78
78
 
79
79
  // Mock AssistSection component — just render a simplified version.
80
80
  vi.mock('../AssistSection', () => ({
81
- AssistSection: ({ annotationType, isAssisting }: any) => (
81
+ AssistSection: ({ isAssisting }: any) => (
82
82
  <div data-testid="detect-section">
83
83
  <button>Start Detection</button>
84
84
  {isAssisting && <div>Detecting...</div>}
@@ -93,10 +93,11 @@ const mockGetTargetSelector = getTargetSelector as MockedFunction<typeof getTarg
93
93
  // Test data fixtures
94
94
  const createMockAssessment = (id: string, start: number, end: number): Annotation => ({
95
95
  '@context': 'http://www.w3.org/ns/anno.jsonld',
96
- id,
96
+ id: id as AnnotationId,
97
97
  type: 'Annotation',
98
98
  motivation: 'assessing',
99
99
  creator: {
100
+ '@type': 'Person',
100
101
  name: `user${id}@example.com`,
101
102
  },
102
103
  created: `2024-01-0${id.slice(-1)}T10:00:00Z`,
@@ -17,7 +17,6 @@ import { screen } from '@testing-library/react';
17
17
  import { renderWithProviders } from '../../../../test-utils';
18
18
  import userEvent from '@testing-library/user-event';
19
19
  import { AssistSection } from '../AssistSection';
20
- import type { EventBus } from "@semiont/core"
21
20
 
22
21
  // Mock translations
23
22
  const mockT = vi.fn((key: string) => {
@@ -68,7 +67,7 @@ describe('AssistSection', () => {
68
67
  annotationType="highlight"
69
68
  isAssisting={true}
70
69
  progress={{
71
- status: 'analyzing',
70
+ stage: 'analyzing',
72
71
  percentage: 30,
73
72
  message: 'Analyzing text for highlights...',
74
73
  }}
@@ -84,7 +83,8 @@ describe('AssistSection', () => {
84
83
  annotationType="highlight"
85
84
  isAssisting={true}
86
85
  progress={{
87
- status: 'analyzing',
86
+ stage: 'analyzing',
87
+ percentage: 0,
88
88
  message: 'Processing...',
89
89
  }}
90
90
  />
@@ -102,7 +102,8 @@ describe('AssistSection', () => {
102
102
  annotationType="highlight"
103
103
  isAssisting={true}
104
104
  progress={{
105
- status: 'analyzing',
105
+ stage: 'analyzing',
106
+ percentage: 0,
106
107
  message: 'Analyzing...',
107
108
  requestParams: [
108
109
  { label: 'Instructions', value: 'Find important points' },
@@ -125,7 +126,8 @@ describe('AssistSection', () => {
125
126
  annotationType="highlight"
126
127
  isAssisting={true}
127
128
  progress={{
128
- status: 'analyzing',
129
+ stage: 'analyzing',
130
+ percentage: 0,
129
131
  message: 'Analyzing...',
130
132
  }}
131
133
  />
@@ -170,7 +172,7 @@ describe('AssistSection', () => {
170
172
  annotationType="highlight"
171
173
  isAssisting={false}
172
174
  progress={{
173
- status: 'complete',
175
+ stage: 'complete',
174
176
  percentage: 100,
175
177
  message: 'Complete! Created 14 highlights',
176
178
  }}
@@ -433,7 +435,8 @@ describe('AssistSection', () => {
433
435
  annotationType="highlight"
434
436
  isAssisting={true}
435
437
  progress={{
436
- status: 'analyzing',
438
+ stage: 'analyzing',
439
+ percentage: 0,
437
440
  message: '',
438
441
  }}
439
442
  />
@@ -450,9 +453,9 @@ describe('AssistSection', () => {
450
453
  annotationType="highlight"
451
454
  isAssisting={true}
452
455
  progress={{
453
- status: 'analyzing',
456
+ stage: 'analyzing',
457
+ percentage: 0,
454
458
  message: 'Processing...',
455
- // no percentage
456
459
  }}
457
460
  />
458
461
  );
@@ -466,7 +469,8 @@ describe('AssistSection', () => {
466
469
  annotationType="highlight"
467
470
  isAssisting={true}
468
471
  progress={{
469
- status: 'analyzing',
472
+ stage: 'analyzing',
473
+ percentage: 0,
470
474
  message: 'Processing...',
471
475
  requestParams: [],
472
476
  }}
@@ -1,5 +1,4 @@
1
1
  import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import React from 'react';
3
2
  import { render, screen } from '@testing-library/react';
4
3
  import '@testing-library/jest-dom';
5
4
  import { CollaborationPanel } from '../CollaborationPanel';
@@ -5,10 +5,8 @@ import { renderWithProviders } from '../../../../test-utils';
5
5
  import userEvent from '@testing-library/user-event';
6
6
  import '@testing-library/jest-dom';
7
7
  import { CommentEntry } from '../CommentEntry';
8
- import type { components } from '@semiont/core';
9
- import type { EventBus } from "@semiont/core"
10
8
 
11
- import type { Annotation } from '@semiont/core';
9
+ import type { Annotation, AnnotationId } from '@semiont/core';
12
10
 
13
11
  // Mock TranslationContext
14
12
  vi.mock('../../../../contexts/TranslationContext', () => ({
@@ -23,7 +21,7 @@ vi.mock('../../../../contexts/TranslationContext', () => ({
23
21
  TranslationProvider: ({ children }: { children: React.ReactNode }) => children,
24
22
  }));
25
23
 
26
- // Mock @semiont/api-client utilities
24
+ // Mock @semiont/http-transport utilities
27
25
  vi.mock('@semiont/core', async () => {
28
26
  const actual = await vi.importActual('@semiont/core');
29
27
  return {
@@ -42,10 +40,11 @@ const mockGetAnnotationExactText = getAnnotationExactText as MockedFunction<type
42
40
  // Test data fixtures
43
41
  const createMockComment = (overrides?: Partial<Annotation>): Annotation => ({
44
42
  '@context': 'http://www.w3.org/ns/anno.jsonld',
45
- id: 'comment-1',
43
+ id: 'comment-1' as AnnotationId,
46
44
  type: 'Annotation',
47
45
  motivation: 'commenting',
48
46
  creator: {
47
+ '@type': 'Person',
49
48
  name: 'user@example.com',
50
49
  },
51
50
  created: '2024-01-01T10:00:00Z',
@@ -98,9 +97,6 @@ describe('CommentEntry Component', () => {
98
97
  const defaultProps = {
99
98
  comment: mockCommentStates.standard,
100
99
  isFocused: false,
101
- onClick: vi.fn(),
102
- onCommentRef: vi.fn(),
103
- onCommentHover: vi.fn(),
104
100
  };
105
101
 
106
102
  beforeEach(() => {
@@ -396,19 +392,26 @@ describe('CommentEntry Component', () => {
396
392
  });
397
393
 
398
394
  it('should stop event propagation when clicking textarea in edit mode', async () => {
399
- const onClick = vi.fn();
400
- renderWithProviders(<CommentEntry {...defaultProps} onClick={onClick} />);
395
+ const clickHandler = vi.fn();
396
+ const { eventBus } = renderWithProviders(
397
+ <CommentEntry {...defaultProps} />,
398
+ { returnEventBus: true }
399
+ );
400
+
401
+ const subscription = eventBus!.get('browse:click').subscribe(clickHandler);
401
402
 
402
403
  await userEvent.click(screen.getByText('Edit'));
403
404
 
404
- // Clear the onClick calls that happened during entering edit mode
405
- onClick.mockClear();
405
+ // Clear any browse:click emissions that happened during entering edit mode
406
+ clickHandler.mockClear();
406
407
 
407
408
  const textarea = screen.getByRole('textbox');
408
409
  await userEvent.click(textarea);
409
410
 
410
- // onClick should not be called for textarea click due to stopPropagation
411
- expect(onClick).not.toHaveBeenCalled();
411
+ // browse:click should not be emitted for textarea click due to stopPropagation
412
+ expect(clickHandler).not.toHaveBeenCalled();
413
+
414
+ subscription.unsubscribe();
412
415
  });
413
416
 
414
417
  it('should update text in textarea', async () => {
@@ -484,14 +487,24 @@ describe('CommentEntry Component', () => {
484
487
  expect(screen.getByText('This is a test comment')).toBeInTheDocument();
485
488
  });
486
489
 
487
- it('should not call onUpdate when cancel is clicked', async () => {
488
- const onUpdate = vi.fn();
489
- renderWithProviders(<CommentEntry {...defaultProps} onUpdate={onUpdate} />);
490
+ it('should not emit any update when cancel is clicked', async () => {
491
+ const clickHandler = vi.fn();
492
+ const { eventBus } = renderWithProviders(
493
+ <CommentEntry {...defaultProps} />,
494
+ { returnEventBus: true }
495
+ );
496
+
497
+ const subscription = eventBus!.get('browse:click').subscribe(clickHandler);
490
498
 
491
499
  await userEvent.click(screen.getByText('Edit'));
500
+ clickHandler.mockClear();
501
+
492
502
  await userEvent.click(screen.getByText('Cancel'));
493
503
 
494
- expect(onUpdate).not.toHaveBeenCalled();
504
+ // Cancel discards the edit without emitting any event
505
+ expect(clickHandler).not.toHaveBeenCalled();
506
+
507
+ subscription.unsubscribe();
495
508
  });
496
509
  });
497
510