@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
@@ -41,11 +41,11 @@ vi.mock('../../../components/Toast', () => ({
41
41
  import { ApiClientProvider } from '../../../contexts/ApiClientContext';
42
42
  import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
43
43
  import { SemiontApiClient } from '@semiont/api-client';
44
- import { resourceUri, accessToken } from '@semiont/core';
44
+ import { resourceId, accessToken, annotationId as toAnnotationId } from '@semiont/core';
45
45
 
46
46
  describe('Annotation Deletion - Feature Integration', () => {
47
47
  let deleteAnnotationSpy: ReturnType<typeof vi.fn>;
48
- const testUri = resourceUri('http://localhost:4000/resources/test-resource');
48
+ const testId = resourceId('test-resource');
49
49
  const testToken = 'test-token-123';
50
50
  const testBaseUrl = 'http://localhost:4000';
51
51
 
@@ -72,7 +72,7 @@ describe('Annotation Deletion - Feature Integration', () => {
72
72
  eventBusInstance = useEventBus();
73
73
  // useMarkFlow is the single registration point for useBindFlow
74
74
  // (handles mark:delete mark:create annotate:detect-request, etc.)
75
- useMarkFlow(testUri);
75
+ useMarkFlow(testId);
76
76
  return null;
77
77
  }
78
78
 
@@ -89,7 +89,7 @@ describe('Annotation Deletion - Feature Integration', () => {
89
89
  return {
90
90
  emitDelete: (annotationId: string) => {
91
91
  act(() => {
92
- eventBusInstance!.get('mark:delete').next({ annotationId });
92
+ eventBusInstance!.get('mark:delete').next({ annotationId: toAnnotationId(annotationId) });
93
93
  });
94
94
  },
95
95
  eventBus: eventBusInstance!,
@@ -109,8 +109,9 @@ describe('Annotation Deletion - Feature Integration', () => {
109
109
  expect(deleteAnnotationSpy).toHaveBeenCalledTimes(1);
110
110
  });
111
111
 
112
- // Verify correct parameters (annotationUri constructed from ID)
112
+ // Verify correct parameters (resourceId, annotationId, opts)
113
113
  expect(deleteAnnotationSpy).toHaveBeenCalledWith(
114
+ 'test-resource',
114
115
  expect.stringContaining(annotationId),
115
116
  expect.objectContaining({
116
117
  auth: accessToken(testToken),
@@ -127,10 +128,10 @@ describe('Annotation Deletion - Feature Integration', () => {
127
128
  expect(deleteAnnotationSpy).toHaveBeenCalled();
128
129
  });
129
130
 
130
- // CRITICAL: Auth token must be present
131
+ // CRITICAL: Auth token must be present (3rd arg is opts)
131
132
  const callArgs = deleteAnnotationSpy.mock.calls[0];
132
- expect(callArgs[1]).toHaveProperty('auth');
133
- expect(callArgs[1].auth).toBe(accessToken(testToken));
133
+ expect(callArgs[2]).toHaveProperty('auth');
134
+ expect(callArgs[2].auth).toBe(accessToken(testToken));
134
135
  });
135
136
 
136
137
  it('should emit mark:deleted event on successful deletion', async () => {
@@ -197,9 +198,9 @@ describe('Annotation Deletion - Feature Integration', () => {
197
198
  expect(deleteAnnotationSpy).toHaveBeenCalledTimes(2);
198
199
  });
199
200
 
200
- // Verify each call had correct annotation ID
201
- expect(deleteAnnotationSpy.mock.calls[0][0]).toContain('annotation-1');
202
- expect(deleteAnnotationSpy.mock.calls[1][0]).toContain('annotation-2');
201
+ // Verify each call had correct annotation ID (2nd arg)
202
+ expect(deleteAnnotationSpy.mock.calls[0][1]).toContain('annotation-1');
203
+ expect(deleteAnnotationSpy.mock.calls[1][1]).toContain('annotation-2');
203
204
  });
204
205
 
205
206
  it('ARCHITECTURE: useBindFlow is called in useMarkFlow (single registration point)', async () => {
@@ -26,7 +26,7 @@ import { EventBusProvider, resetEventBusForTesting, useEventBus } from '../../..
26
26
  import { ApiClientProvider } from '../../../contexts/ApiClientContext';
27
27
  import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
28
28
  import { SSEClient } from '@semiont/api-client';
29
- import { resourceUri } from '@semiont/core';
29
+ import { resourceId } from '@semiont/core';
30
30
 
31
31
  // Mock Toast module to prevent "useToast must be used within a ToastProvider" errors
32
32
  vi.mock('../../../components/Toast', () => ({
@@ -40,7 +40,7 @@ vi.mock('../../../components/Toast', () => ({
40
40
 
41
41
  describe('Detection Progress Dismissal Bug', () => {
42
42
  let mockStream: any;
43
- const rUri = resourceUri('https://example.com/resources/test');
43
+ const rUri = resourceId('test');
44
44
 
45
45
  beforeEach(() => {
46
46
  resetEventBusForTesting();
@@ -1,16 +1,15 @@
1
1
  /**
2
- * Layer 3: Feature Integration Test - Resolution Flow (search modal & body update)
2
+ * Layer 3: Feature Integration Test - Bind Flow (body update)
3
3
  *
4
- * Tests the UNCOVERED half of useBindFlow:
5
- * - bind:link → emits bind:search-requested
6
- * - bind:search-requested → opens search modal with pendingReferenceId
7
- * - onCloseSearchModal → closes modal
4
+ * Tests the write side of useBindFlow:
8
5
  * - bind:update-body → calls updateAnnotationBody API
9
6
  * - bind:update-body → emits bind:body-updated on success
10
7
  * - bind:update-body → emits bind:body-update-failed on error
11
8
  * - auth token passed to updateAnnotationBody
12
9
  *
13
- * The deletion half of useBindFlow is covered by AnnotationDeletionIntegration.test.tsx.
10
+ * The wizard modal (ReferenceWizardModal) handles modal state, context
11
+ * gathering, search configuration, and result display. This test covers
12
+ * only the downstream API calls after the wizard emits bind:update-body.
14
13
  *
15
14
  * Uses real providers (EventBus, ApiClient, AuthToken) with mocked API boundary.
16
15
  */
@@ -23,7 +22,7 @@ import { EventBusProvider, useEventBus, resetEventBusForTesting } from '../../..
23
22
  import { ApiClientProvider } from '../../../contexts/ApiClientContext';
24
23
  import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
25
24
  import { SemiontApiClient } from '@semiont/api-client';
26
- import { resourceUri, accessToken } from '@semiont/core';
25
+ import { resourceId, accessToken, annotationId } from '@semiont/core';
27
26
 
28
27
  // Mock Toast module to prevent "useToast must be used within a ToastProvider" errors
29
28
  vi.mock('../../../components/Toast', () => ({
@@ -35,9 +34,9 @@ vi.mock('../../../components/Toast', () => ({
35
34
  }),
36
35
  }));
37
36
 
38
- describe('Resolution Flow - Search Modal & Body Update Integration', () => {
37
+ describe('Bind Flow - Body Update Integration', () => {
39
38
  let updateAnnotationBodySpy: ReturnType<typeof vi.fn>;
40
- const testUri = resourceUri('http://localhost:4000/resources/test-resource');
39
+ const testId = resourceId('test-resource');
41
40
  const testToken = 'test-resolution-token';
42
41
  const testBaseUrl = 'http://localhost:4000';
43
42
 
@@ -57,11 +56,10 @@ describe('Resolution Flow - Search Modal & Body Update Integration', () => {
57
56
 
58
57
  function renderBindFlow() {
59
58
  let eventBusInstance: ReturnType<typeof useEventBus> | null = null;
60
- let lastState: ReturnType<typeof useBindFlow> | null = null;
61
59
 
62
60
  function TestComponent() {
63
61
  eventBusInstance = useEventBus();
64
- lastState = useBindFlow(testUri);
62
+ useBindFlow(testId);
65
63
  return null;
66
64
  }
67
65
 
@@ -76,109 +74,18 @@ describe('Resolution Flow - Search Modal & Body Update Integration', () => {
76
74
  );
77
75
 
78
76
  return {
79
- getState: () => lastState!,
80
77
  getEventBus: () => eventBusInstance!,
81
78
  };
82
79
  }
83
80
 
84
- // ─── Initial state ──────────────────────────────────────────────────────────
85
-
86
- it('starts with search modal closed and no pending reference', () => {
87
- const { getState } = renderBindFlow();
88
- expect(getState().searchModalOpen).toBe(false);
89
- expect(getState().pendingReferenceId).toBeNull();
90
- });
91
-
92
- // ─── bind:link ─────────────────────────────────────────────────────────
93
-
94
- it('bind:link emits bind:search-requested with referenceId and searchTerm', () => {
95
- const { getEventBus } = renderBindFlow();
96
- const searchRequestedSpy = vi.fn();
97
-
98
- const subscription = getEventBus().get('bind:search-requested').subscribe(searchRequestedSpy);
99
- act(() => { getEventBus().get('bind:link').next({ annotationUri: 'ann-uri-123', searchTerm: 'climate change' }); });
100
- subscription.unsubscribe();
101
-
102
- expect(searchRequestedSpy).toHaveBeenCalledTimes(1);
103
- expect(searchRequestedSpy).toHaveBeenCalledWith({
104
- referenceId: 'ann-uri-123',
105
- searchTerm: 'climate change',
106
- });
107
- });
108
-
109
- // ─── bind:search-requested ────────────────────────────────────────────
110
-
111
- it('bind:search-requested opens the search modal', async () => {
112
- const { getState, getEventBus } = renderBindFlow();
113
-
114
- expect(getState().searchModalOpen).toBe(false);
115
-
116
- act(() => { getEventBus().get('bind:search-requested').next({ referenceId: 'ref-abc', searchTerm: 'oceans' }); });
117
-
118
- await waitFor(() => {
119
- expect(getState().searchModalOpen).toBe(true);
120
- });
121
- });
122
-
123
- it('bind:search-requested sets pendingReferenceId', async () => {
124
- const { getState, getEventBus } = renderBindFlow();
125
-
126
- act(() => { getEventBus().get('bind:search-requested').next({ referenceId: 'ref-xyz', searchTerm: 'forests' }); });
127
-
128
- await waitFor(() => {
129
- expect(getState().pendingReferenceId).toBe('ref-xyz');
130
- });
131
- });
132
-
133
- it('bind:link → bind:search-requested chain opens modal end-to-end', async () => {
134
- const { getState, getEventBus } = renderBindFlow();
135
-
136
- // Simulate the full user journey: user clicks "Link Document" on a reference entry
137
- act(() => { getEventBus().get('bind:link').next({ annotationUri: 'ann-full-chain', searchTerm: 'biodiversity' }); });
138
-
139
- await waitFor(() => {
140
- expect(getState().searchModalOpen).toBe(true);
141
- expect(getState().pendingReferenceId).toBe('ann-full-chain');
142
- });
143
- });
144
-
145
- // ─── onCloseSearchModal ──────────────────────────────────────────────────────
146
-
147
- it('onCloseSearchModal closes the search modal', async () => {
148
- const { getState, getEventBus } = renderBindFlow();
149
-
150
- act(() => { getEventBus().get('bind:search-requested').next({ referenceId: 'ref-close', searchTerm: 'test' }); });
151
-
152
- await waitFor(() => expect(getState().searchModalOpen).toBe(true));
153
-
154
- act(() => { getState().onCloseSearchModal(); });
155
-
156
- await waitFor(() => {
157
- expect(getState().searchModalOpen).toBe(false);
158
- });
159
- });
160
-
161
- it('onCloseSearchModal does not clear pendingReferenceId (preserves for re-open)', async () => {
162
- const { getState, getEventBus } = renderBindFlow();
163
-
164
- act(() => { getEventBus().get('bind:search-requested').next({ referenceId: 'ref-persist', searchTerm: 'test' }); });
165
- await waitFor(() => expect(getState().searchModalOpen).toBe(true));
166
-
167
- act(() => { getState().onCloseSearchModal(); });
168
- await waitFor(() => expect(getState().searchModalOpen).toBe(false));
169
-
170
- // pendingReferenceId remains — modal may reopen
171
- expect(getState().pendingReferenceId).toBe('ref-persist');
172
- });
173
-
174
81
  // ─── bind:update-body ──────────────────────────────────────────────────
175
82
 
176
83
  it('bind:update-body calls updateAnnotationBody API', async () => {
177
84
  const { getEventBus } = renderBindFlow();
178
85
 
179
86
  act(() => { getEventBus().get('bind:update-body').next({
180
- annotationUri: 'http://localhost:4000/resources/test-resource/annotations/ann-body-1',
181
- resourceId: 'linked-resource-id',
87
+ annotationId: annotationId('ann-body-1'),
88
+ resourceId: resourceId('linked-resource-id'),
182
89
  operations: [{ op: 'add', item: { id: 'linked-resource-id' } }],
183
90
  }); });
184
91
 
@@ -191,8 +98,8 @@ describe('Resolution Flow - Search Modal & Body Update Integration', () => {
191
98
  const { getEventBus } = renderBindFlow();
192
99
 
193
100
  act(() => { getEventBus().get('bind:update-body').next({
194
- annotationUri: 'http://localhost:4000/resources/test-resource/annotations/ann-auth',
195
- resourceId: 'resource-id',
101
+ annotationId: annotationId('ann-auth'),
102
+ resourceId: resourceId('resource-id'),
196
103
  operations: [{ op: 'replace', newItem: { id: 'resource-id' } }],
197
104
  }); });
198
105
 
@@ -201,8 +108,8 @@ describe('Resolution Flow - Search Modal & Body Update Integration', () => {
201
108
  });
202
109
 
203
110
  const callArgs = updateAnnotationBodySpy.mock.calls[0];
204
- expect(callArgs[2]).toHaveProperty('auth');
205
- expect(callArgs[2].auth).toBe(accessToken(testToken));
111
+ expect(callArgs[3]).toHaveProperty('auth');
112
+ expect(callArgs[3].auth).toBe(accessToken(testToken));
206
113
  });
207
114
 
208
115
  it('bind:update-body emits bind:body-updated on success', async () => {
@@ -212,8 +119,8 @@ describe('Resolution Flow - Search Modal & Body Update Integration', () => {
212
119
  const subscription = getEventBus().get('bind:body-updated').subscribe(bodyUpdatedSpy);
213
120
 
214
121
  act(() => { getEventBus().get('bind:update-body').next({
215
- annotationUri: 'http://localhost:4000/resources/test-resource/annotations/ann-success',
216
- resourceId: 'resource-id',
122
+ annotationId: annotationId('ann-success'),
123
+ resourceId: resourceId('resource-id'),
217
124
  operations: [{ op: 'add', item: { id: 'resource-id' } }],
218
125
  }); });
219
126
 
@@ -224,7 +131,7 @@ describe('Resolution Flow - Search Modal & Body Update Integration', () => {
224
131
  subscription.unsubscribe();
225
132
 
226
133
  expect(bodyUpdatedSpy).toHaveBeenCalledWith({
227
- annotationUri: 'http://localhost:4000/resources/test-resource/annotations/ann-success',
134
+ annotationId: annotationId('ann-success'),
228
135
  });
229
136
  });
230
137
 
@@ -237,8 +144,8 @@ describe('Resolution Flow - Search Modal & Body Update Integration', () => {
237
144
  const subscription = getEventBus().get('bind:body-update-failed').subscribe(bodyUpdateFailedSpy);
238
145
 
239
146
  act(() => { getEventBus().get('bind:update-body').next({
240
- annotationUri: 'http://localhost:4000/resources/test-resource/annotations/ann-fail',
241
- resourceId: 'resource-id',
147
+ annotationId: annotationId('ann-fail'),
148
+ resourceId: resourceId('resource-id'),
242
149
  operations: [{ op: 'remove', item: { id: 'old-id' } }],
243
150
  }); });
244
151
 
@@ -257,8 +164,8 @@ describe('Resolution Flow - Search Modal & Body Update Integration', () => {
257
164
  const { getEventBus } = renderBindFlow();
258
165
 
259
166
  act(() => { getEventBus().get('bind:update-body').next({
260
- annotationUri: 'http://localhost:4000/resources/test-resource/annotations/ann-dedup',
261
- resourceId: 'resource-id',
167
+ annotationId: annotationId('ann-dedup'),
168
+ resourceId: resourceId('resource-id'),
262
169
  operations: [{ op: 'add', item: { id: 'resource-id' } }],
263
170
  }); });
264
171
 
@@ -52,7 +52,7 @@ describe('REPRODUCING BUG: Detection state not updating', () => {
52
52
  // Component to capture EventBus and hook state
53
53
  function TestComponent() {
54
54
  eventBusInstance = useEventBus();
55
- const state = useMarkFlow('http://localhost:8080/resources/test' as any);
55
+ const state = useMarkFlow('test' as any);
56
56
  currentState = state;
57
57
 
58
58
  console.log('[TEST] useMarkFlow state:', {
@@ -109,7 +109,7 @@ describe('REPRODUCING BUG: Detection state not updating', () => {
109
109
 
110
110
  function TestComponent() {
111
111
  eventBusInstance = useEventBus();
112
- const state = useMarkFlow('http://localhost:8080/resources/test' as any);
112
+ const state = useMarkFlow('test' as any);
113
113
  currentState = state;
114
114
 
115
115
  return (
@@ -162,7 +162,7 @@ describe('REPRODUCING BUG: Detection state not updating', () => {
162
162
 
163
163
  function TestComponent() {
164
164
  eventBusInstance = useEventBus();
165
- const state = useMarkFlow('http://localhost:8080/resources/f45fd44f9cb0b0fe1b7980d3d034bc61' as any);
165
+ const state = useMarkFlow('f45fd44f9cb0b0fe1b7980d3d034bc61' as any);
166
166
 
167
167
  stateSnapshots.push({
168
168
  assistingMotivation: state.assistingMotivation,
@@ -31,7 +31,7 @@ import { ApiClientProvider } from '../../../contexts/ApiClientContext';
31
31
  import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
32
32
  import { SSEClient } from '@semiont/api-client';
33
33
  import type { Motivation } from '@semiont/core';
34
- import { resourceUri } from '@semiont/core';
34
+ import { resourceId } from '@semiont/core';
35
35
  import type { Emitter } from 'mitt';
36
36
 
37
37
  // Mock Toast module to prevent "useToast must be used within a ToastProvider" errors
@@ -77,10 +77,10 @@ describe('Detection Flow - Feature Integration', () => {
77
77
  });
78
78
 
79
79
  it('should call annotateReferences exactly ONCE when detection starts (not twice)', async () => {
80
- const testUri = resourceUri('http://localhost:4000/resources/test-resource');
80
+ const testId = resourceId('test-resource');
81
81
 
82
82
  // Render with real component composition
83
- const { emitDetectionStart } = renderDetectionFlow(testUri);
83
+ const { emitDetectionStart } = renderDetectionFlow(testId);
84
84
 
85
85
  // Trigger detection for linking (uses annotateReferences)
86
86
  act(() => {
@@ -98,7 +98,7 @@ describe('Detection Flow - Feature Integration', () => {
98
98
 
99
99
  // Verify correct parameters (eventBus is passed but we don't need to verify its exact value)
100
100
  expect(annotateReferencesSpy).toHaveBeenCalledWith(
101
- testUri,
101
+ testId,
102
102
  {
103
103
  entityTypes: ['Person', 'Organization'],
104
104
  includeDescriptiveReferences: false,
@@ -108,10 +108,10 @@ describe('Detection Flow - Feature Integration', () => {
108
108
  });
109
109
 
110
110
  it('should propagate SSE progress events to useMarkFlow state', async () => {
111
- const testUri = resourceUri('http://localhost:4000/resources/test-resource');
111
+ const testId = resourceId('test-resource');
112
112
 
113
113
  // Render with state observer
114
- const { emitDetectionStart, getEventBus } = renderDetectionFlow(testUri);
114
+ const { emitDetectionStart, getEventBus } = renderDetectionFlow(testId);
115
115
 
116
116
  // Start detection
117
117
  act(() => {
@@ -145,8 +145,8 @@ describe('Detection Flow - Feature Integration', () => {
145
145
  });
146
146
 
147
147
  it('should handle multiple progress updates correctly', async () => {
148
- const testUri = resourceUri('http://localhost:4000/resources/test-resource');
149
- const { emitDetectionStart, getEventBus } = renderDetectionFlow(testUri);
148
+ const testId = resourceId('test-resource');
149
+ const { emitDetectionStart, getEventBus } = renderDetectionFlow(testId);
150
150
 
151
151
  // Start detection
152
152
  act(() => {
@@ -200,8 +200,8 @@ describe('Detection Flow - Feature Integration', () => {
200
200
  });
201
201
 
202
202
  it('should keep progress visible after detection completes', async () => {
203
- const testUri = resourceUri('http://localhost:4000/resources/test-resource');
204
- const { emitDetectionStart, getEventBus } = renderDetectionFlow(testUri);
203
+ const testId = resourceId('test-resource');
204
+ const { emitDetectionStart, getEventBus } = renderDetectionFlow(testId);
205
205
 
206
206
  // Start detection
207
207
  act(() => {
@@ -237,8 +237,8 @@ describe('Detection Flow - Feature Integration', () => {
237
237
  });
238
238
 
239
239
  it('should clear progress on detection failure', async () => {
240
- const testUri = resourceUri('http://localhost:4000/resources/test-resource');
241
- const { emitDetectionStart, getEventBus } = renderDetectionFlow(testUri);
240
+ const testId = resourceId('test-resource');
241
+ const { emitDetectionStart, getEventBus } = renderDetectionFlow(testId);
242
242
 
243
243
  // Start detection
244
244
  act(() => {
@@ -282,8 +282,8 @@ describe('Detection Flow - Feature Integration', () => {
282
282
  });
283
283
 
284
284
  it('should handle different detection motivations with correct API calls', async () => {
285
- const testUri = resourceUri('http://localhost:4000/resources/test-resource');
286
- const { emitDetectionStart } = renderDetectionFlow(testUri);
285
+ const testId = resourceId('test-resource');
286
+ const { emitDetectionStart } = renderDetectionFlow(testId);
287
287
 
288
288
  // Test highlighting
289
289
  act(() => {
@@ -292,7 +292,7 @@ describe('Detection Flow - Feature Integration', () => {
292
292
 
293
293
  await waitFor(() => {
294
294
  expect(annotateHighlightsSpy).toHaveBeenCalledTimes(1);
295
- expect(annotateHighlightsSpy).toHaveBeenCalledWith(testUri, {
295
+ expect(annotateHighlightsSpy).toHaveBeenCalledWith(testId, {
296
296
  instructions: 'Find important text',
297
297
  }, expect.objectContaining({ auth: undefined }));
298
298
  });
@@ -312,7 +312,7 @@ describe('Detection Flow - Feature Integration', () => {
312
312
 
313
313
  await waitFor(() => {
314
314
  expect(detectCommentsSpy).toHaveBeenCalledTimes(1);
315
- expect(detectCommentsSpy).toHaveBeenCalledWith(testUri, {
315
+ expect(detectCommentsSpy).toHaveBeenCalledWith(testId, {
316
316
  instructions: 'Add helpful comments',
317
317
  tone: 'educational',
318
318
  }, expect.objectContaining({ auth: undefined }));
@@ -320,11 +320,11 @@ describe('Detection Flow - Feature Integration', () => {
320
320
  });
321
321
 
322
322
  it('should only call API once even with multiple event listeners', async () => {
323
- const testUri = resourceUri('http://localhost:4000/resources/test-resource');
323
+ const testId = resourceId('test-resource');
324
324
 
325
325
  // This test specifically catches the duplicate useBindFlow bug
326
326
  // If multiple components call useBindFlow, we'll see multiple API calls
327
- const { emitDetectionStart, getEventBus } = renderDetectionFlow(testUri);
327
+ const { emitDetectionStart, getEventBus } = renderDetectionFlow(testId);
328
328
 
329
329
  // Add an additional event listener (simulating multiple subscribers)
330
330
  const additionalListener = vi.fn();
@@ -354,7 +354,7 @@ describe('Detection Flow - Feature Integration', () => {
354
354
  * Helper: Render useMarkFlow hook with real component composition
355
355
  * Returns methods to interact with the rendered component
356
356
  */
357
- function renderDetectionFlow(testUri: string) {
357
+ function renderDetectionFlow(testId: string) {
358
358
  let eventBusInstance: Emitter<EventMap>;
359
359
 
360
360
  // Component to capture EventBus instance
@@ -365,7 +365,7 @@ function renderDetectionFlow(testUri: string) {
365
365
 
366
366
  // Test harness component that uses the hook
367
367
  function DetectionFlowTestHarness() {
368
- const { progress, assistingMotivation } = useMarkFlow(testUri as any);
368
+ const { progress, assistingMotivation } = useMarkFlow(testId as any);
369
369
  return (
370
370
  <div>
371
371
  <div data-testid="detecting">{assistingMotivation || 'none'}</div>
@@ -26,7 +26,7 @@ import { render, waitFor } from '@testing-library/react';
26
26
  import { act } from 'react';
27
27
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
28
28
  import { SemiontApiClient } from '@semiont/api-client';
29
- import { resourceUri, accessToken } from '@semiont/core';
29
+ import { resourceId, accessToken } from '@semiont/core';
30
30
  import { EventBusProvider, useEventBus, resetEventBusForTesting } from '../../../contexts/EventBusContext';
31
31
  import { useEventSubscriptions } from '../../../contexts/useEventSubscription';
32
32
  import { ApiClientProvider } from '../../../contexts/ApiClientContext';
@@ -36,7 +36,7 @@ import type { EventMap, EventBus } from '@semiont/core';
36
36
 
37
37
  // ─── Constants ────────────────────────────────────────────────────────────────
38
38
 
39
- const TEST_URI = resourceUri('http://localhost:4000/resources/test-resource');
39
+ const TEST_URI = resourceId('test-resource');
40
40
  const TEST_TOKEN = 'test-auth-token-123';
41
41
  const BASE_URL = 'http://localhost:4000';
42
42
  const CLONE_TOKEN = 'generated-clone-token-xyz';
@@ -66,11 +66,11 @@ function ResourceMutationHarness({ onEventBus }: { onEventBus: (eventBus: EventB
66
66
  const generateCloneTokenMutation = resources.generateCloneToken.useMutation();
67
67
 
68
68
  const handleResourceArchive = useCallback(async () => {
69
- await updateMutation.mutateAsync({ rUri: TEST_URI, data: { archived: true } });
69
+ await updateMutation.mutateAsync({ id: TEST_URI, data: { archived: true } });
70
70
  }, [updateMutation]);
71
71
 
72
72
  const handleResourceUnarchive = useCallback(async () => {
73
- await updateMutation.mutateAsync({ rUri: TEST_URI, data: { archived: false } });
73
+ await updateMutation.mutateAsync({ id: TEST_URI, data: { archived: false } });
74
74
  }, [updateMutation]);
75
75
 
76
76
  const handleResourceClone = useCallback(async () => {
@@ -175,7 +175,7 @@ describe('Resource mutations — hooks hoisted to top level', () => {
175
175
  await waitFor(() => {
176
176
  expect(generateCloneTokenSpy).toHaveBeenCalledWith(
177
177
  TEST_URI,
178
- expect.anything()
178
+ expect.anything(),
179
179
  );
180
180
  });
181
181
  });
@@ -241,7 +241,7 @@ describe('Resource mutations — hooks hoisted to top level', () => {
241
241
  expect(updateResourceSpy).toHaveBeenCalledWith(
242
242
  TEST_URI,
243
243
  expect.objectContaining({ archived: true }),
244
- expect.anything()
244
+ expect.anything(),
245
245
  );
246
246
  });
247
247
 
@@ -275,7 +275,7 @@ describe('Resource mutations — hooks hoisted to top level', () => {
275
275
  expect(updateResourceSpy).toHaveBeenCalledWith(
276
276
  TEST_URI,
277
277
  expect.objectContaining({ archived: false }),
278
- expect.anything()
278
+ expect.anything(),
279
279
  );
280
280
  });
281
281
 
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { describe, it, expect, vi, beforeEach } from 'vitest';
9
- import { render, screen } from '@testing-library/react';
9
+ import { render, screen, act } from '@testing-library/react';
10
10
  import React from 'react';
11
11
  import { ResourceViewerPage } from '../components/ResourceViewerPage';
12
12
  import type { ResourceViewerPageProps } from '../components/ResourceViewerPage';
@@ -118,23 +118,22 @@ vi.mock('../../../contexts/ResourceAnnotationsContext', () => ({
118
118
  ResourceAnnotationsProvider: ({ children }: any) => children,
119
119
  }));
120
120
 
121
- vi.mock('@/components/toolbar/ToolbarPanels', () => ({
122
- ToolbarPanels: ({ children }: any) => <div data-testid="toolbar-panels">{children}</div>,
123
- }));
124
-
125
- vi.mock('@/components/modals/SearchResourcesModal', () => ({
126
- SearchResourcesModal: () => <div data-testid="search-modal">Search Modal</div>,
121
+ // Mock useEventSubscription at the direct path used by ResourceViewerPage
122
+ // (the barrel export mock doesn't intercept direct context imports)
123
+ const mockUseEventSubscriptions = vi.fn();
124
+ vi.mock('../../../contexts/useEventSubscription', () => ({
125
+ useEventSubscriptions: (...args: unknown[]) => mockUseEventSubscriptions(...args),
127
126
  }));
128
127
 
129
- vi.mock('@/components/modals/GenerationConfigModal', () => ({
130
- GenerationConfigModal: () => <div data-testid="generation-modal">Generation Modal</div>,
128
+ vi.mock('@/components/toolbar/ToolbarPanels', () => ({
129
+ ToolbarPanels: ({ children }: any) => <div data-testid="toolbar-panels">{children}</div>,
131
130
  }));
132
131
 
133
132
  // Create mock props matching the current ResourceViewerPageProps
134
133
  const createMockProps = (overrides?: Partial<ResourceViewerPageProps>): ResourceViewerPageProps => ({
135
134
  resource: {
136
135
  '@context': 'https://www.w3.org/ns/anno.jsonld',
137
- '@id': 'http://localhost/resources/test-123',
136
+ '@id': 'test-123',
138
137
  '@type': 'schema:DigitalDocument',
139
138
  name: 'Test Resource',
140
139
  description: 'A test resource for unit testing',
@@ -148,7 +147,7 @@ const createMockProps = (overrides?: Partial<ResourceViewerPageProps>): Resource
148
147
  },
149
148
  ],
150
149
  },
151
- rUri: 'http://localhost/resources/test-123' as any,
150
+ rUri: 'test-123' as any,
152
151
  locale: 'en',
153
152
  cacheManager: {},
154
153
  Link: ({ children }: any) => <a>{children}</a>,
@@ -156,8 +155,6 @@ const createMockProps = (overrides?: Partial<ResourceViewerPageProps>): Resource
156
155
  refetchDocument: vi.fn().mockResolvedValue(undefined),
157
156
  ToolbarPanels: ({ children, activePanel }: any) =>
158
157
  !activePanel ? null : <div data-testid="toolbar-panels">{children}</div>,
159
- SearchResourcesModal: () => <div data-testid="search-modal">Search Modal</div>,
160
- GenerationConfigModal: () => <div data-testid="generation-modal">Generation Modal</div>,
161
158
  ...overrides,
162
159
  });
163
160
 
@@ -281,21 +278,47 @@ describe('ResourceViewerPage', () => {
281
278
  // Archived badge only shows in annotate mode, which defaults to false
282
279
  expect(screen.queryByText('📦 Archived')).not.toBeInTheDocument();
283
280
  });
284
- });
285
281
 
286
- describe('Modals', () => {
287
- it('renders search resources modal', () => {
288
- const props = createMockProps();
282
+ it('shows archived badge after mark:mode-toggled event fires', () => {
283
+ localStorage.setItem('annotateMode', 'false');
284
+ localStorage.setItem('activeToolbarPanel', 'annotations');
285
+
286
+ const props = createMockProps({
287
+ resource: {
288
+ ...createMockProps().resource,
289
+ archived: true,
290
+ },
291
+ });
292
+
289
293
  renderWithProviders(<ResourceViewerPage {...props} />);
290
294
 
291
- expect(screen.getByTestId('search-modal')).toBeInTheDocument();
295
+ // Before toggle: annotateMode is false, so archived badge is hidden
296
+ expect(screen.queryByText('📦 Archived')).not.toBeInTheDocument();
297
+
298
+ // Get the handler map that ResourceViewerPage passed to useEventSubscriptions
299
+ const handlerMap = mockUseEventSubscriptions.mock.calls[mockUseEventSubscriptions.mock.calls.length - 1]?.[0] as Record<string, () => void>;
300
+ expect(handlerMap).toBeDefined();
301
+ expect(handlerMap['mark:mode-toggled']).toBeDefined();
302
+
303
+ // Fire the mode toggle — this is what the toolbar emits
304
+ act(() => {
305
+ handlerMap['mark:mode-toggled']();
306
+ });
307
+
308
+ // After toggle: annotateMode is true, so archived badge should appear
309
+ expect(screen.getByText('📦 Archived')).toBeInTheDocument();
310
+
311
+ localStorage.clear();
292
312
  });
313
+ });
293
314
 
294
- it('renders generation config modal', () => {
315
+ describe('Modals', () => {
316
+ it('renders reference wizard modal', () => {
295
317
  const props = createMockProps();
296
318
  renderWithProviders(<ResourceViewerPage {...props} />);
297
319
 
298
- expect(screen.getByTestId('generation-modal')).toBeInTheDocument();
320
+ // Wizard modal is rendered but closed by default
321
+ // It opens when bind:initiate is emitted from ReferenceEntry
299
322
  });
300
323
  });
301
324