@semiont/react-ui 0.2.33-build.79 → 0.2.33-build.80

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 (213) hide show
  1. package/dist/EventBusContext-7GvDyO0d.d.mts +414 -0
  2. package/dist/{PdfAnnotationCanvas.client-ADC4FFSE.mjs → PdfAnnotationCanvas.client-RAJRPQLU.mjs} +42 -27
  3. package/dist/PdfAnnotationCanvas.client-RAJRPQLU.mjs.map +1 -0
  4. package/dist/{ar-EMHEHPCJ.mjs → ar-4ZEORRW2.mjs} +7 -4
  5. package/dist/ar-4ZEORRW2.mjs.map +1 -0
  6. package/dist/{bn-OVCI4F6X.mjs → bn-SEDE5BQJ.mjs} +7 -4
  7. package/dist/bn-SEDE5BQJ.mjs.map +1 -0
  8. package/dist/{chunk-LIHZTECW.mjs → chunk-D7NBW4RV.mjs} +7 -4
  9. package/dist/chunk-D7NBW4RV.mjs.map +1 -0
  10. package/dist/{chunk-JZIO2A3B.mjs → chunk-ZR4ZV2LY.mjs} +206 -146
  11. package/dist/chunk-ZR4ZV2LY.mjs.map +1 -0
  12. package/dist/{cs-FAN66Q2F.mjs → cs-7W4WF5WD.mjs} +7 -4
  13. package/dist/cs-7W4WF5WD.mjs.map +1 -0
  14. package/dist/{da-YBBIHI2O.mjs → da-75XGBCBK.mjs} +7 -4
  15. package/dist/da-75XGBCBK.mjs.map +1 -0
  16. package/dist/{de-MAYU33LB.mjs → de-ODJVFLHM.mjs} +7 -4
  17. package/dist/de-ODJVFLHM.mjs.map +1 -0
  18. package/dist/{el-MKGSWN4O.mjs → el-C4PM4WB3.mjs} +7 -4
  19. package/dist/el-C4PM4WB3.mjs.map +1 -0
  20. package/dist/{en-DDLIXJCU.mjs → en-KJCJQ4OO.mjs} +2 -2
  21. package/dist/{es-52LHUWJD.mjs → es-WD33R7QL.mjs} +7 -4
  22. package/dist/es-WD33R7QL.mjs.map +1 -0
  23. package/dist/{fa-FJICRANB.mjs → fa-2BP6V56P.mjs} +7 -4
  24. package/dist/fa-2BP6V56P.mjs.map +1 -0
  25. package/dist/{fi-O455XFCR.mjs → fi-USRRW24J.mjs} +7 -4
  26. package/dist/fi-USRRW24J.mjs.map +1 -0
  27. package/dist/{fr-TXIXHOOE.mjs → fr-EC5S6WVF.mjs} +7 -4
  28. package/dist/fr-EC5S6WVF.mjs.map +1 -0
  29. package/dist/{he-JBSOX5IN.mjs → he-7TBVIKAA.mjs} +7 -4
  30. package/dist/he-7TBVIKAA.mjs.map +1 -0
  31. package/dist/{hi-KGHI3XVT.mjs → hi-FO4VIZLA.mjs} +7 -4
  32. package/dist/hi-FO4VIZLA.mjs.map +1 -0
  33. package/dist/{id-5OCPPZLO.mjs → id-7U7GGVWY.mjs} +7 -4
  34. package/dist/id-7U7GGVWY.mjs.map +1 -0
  35. package/dist/index.css +123 -85
  36. package/dist/index.css.map +1 -1
  37. package/dist/index.d.mts +645 -471
  38. package/dist/index.mjs +3461 -3025
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/{it-PNBBZSM2.mjs → it-Y4OPL6I2.mjs} +7 -4
  41. package/dist/it-Y4OPL6I2.mjs.map +1 -0
  42. package/dist/{ja-LDD7R3TJ.mjs → ja-PK7SQL55.mjs} +7 -4
  43. package/dist/ja-PK7SQL55.mjs.map +1 -0
  44. package/dist/{ko-F47ZDEY3.mjs → ko-L25PXMYD.mjs} +7 -4
  45. package/dist/ko-L25PXMYD.mjs.map +1 -0
  46. package/dist/{ms-Z7LMXJWL.mjs → ms-STH777QM.mjs} +7 -4
  47. package/dist/ms-STH777QM.mjs.map +1 -0
  48. package/dist/{nl-6SJFBPJ3.mjs → nl-Y7LECDDR.mjs} +7 -4
  49. package/dist/nl-Y7LECDDR.mjs.map +1 -0
  50. package/dist/{no-YXPBPSGF.mjs → no-KEKCEWU6.mjs} +7 -4
  51. package/dist/no-KEKCEWU6.mjs.map +1 -0
  52. package/dist/{pl-P4AZ2QME.mjs → pl-7A7OC75O.mjs} +7 -4
  53. package/dist/pl-7A7OC75O.mjs.map +1 -0
  54. package/dist/{pt-LHWUS6U6.mjs → pt-35HTM7RA.mjs} +7 -4
  55. package/dist/pt-35HTM7RA.mjs.map +1 -0
  56. package/dist/{ro-EA5J2ZON.mjs → ro-VAWL5KQA.mjs} +7 -4
  57. package/dist/ro-VAWL5KQA.mjs.map +1 -0
  58. package/dist/{sv-DATBS3UQ.mjs → sv-7ZK5EQEB.mjs} +7 -4
  59. package/dist/sv-7ZK5EQEB.mjs.map +1 -0
  60. package/dist/test-utils.d.mts +18 -8
  61. package/dist/test-utils.mjs +36 -14
  62. package/dist/test-utils.mjs.map +1 -1
  63. package/dist/{th-WTFJRWPT.mjs → th-UDWZ4X34.mjs} +7 -4
  64. package/dist/th-UDWZ4X34.mjs.map +1 -0
  65. package/dist/{tr-IKO3RXOX.mjs → tr-4WMPK3UX.mjs} +7 -4
  66. package/dist/tr-4WMPK3UX.mjs.map +1 -0
  67. package/dist/{uk-CF6CTTRK.mjs → uk-SSLASQYJ.mjs} +7 -4
  68. package/dist/uk-SSLASQYJ.mjs.map +1 -0
  69. package/dist/{vi-AJLTXPZQ.mjs → vi-IF42Z5PU.mjs} +7 -4
  70. package/dist/vi-IF42Z5PU.mjs.map +1 -0
  71. package/dist/{zh-U3ORHHYH.mjs → zh-HRQTNTAI.mjs} +7 -4
  72. package/dist/zh-HRQTNTAI.mjs.map +1 -0
  73. package/package.json +3 -1
  74. package/src/components/CodeMirrorRenderer.tsx +66 -93
  75. package/src/components/DetectionProgressWidget.tsx +16 -5
  76. package/src/components/LiveRegion.tsx +18 -18
  77. package/src/components/ResizeHandle.tsx +10 -4
  78. package/src/components/SessionTimer.tsx +2 -2
  79. package/src/components/Toolbar.tsx +18 -9
  80. package/src/components/__tests__/SessionTimer.test.tsx +9 -9
  81. package/src/components/annotation/AnnotateToolbar.tsx +17 -15
  82. package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +165 -63
  83. package/src/components/annotation/annotation-entries.css +10 -0
  84. package/src/components/annotation-popups/JsonLdView.tsx +8 -2
  85. package/src/components/image-annotation/AnnotationOverlay.tsx +42 -22
  86. package/src/components/image-annotation/SvgDrawingCanvas.tsx +27 -30
  87. package/src/components/layout/__tests__/LeftSidebar.test.tsx +12 -33
  88. package/src/components/layout/__tests__/PageLayout.test.tsx +37 -32
  89. package/src/components/layout/__tests__/UnifiedHeader.test.tsx +21 -40
  90. package/src/components/modals/ResourceSearchModal.tsx +2 -2
  91. package/src/components/modals/SearchModal.tsx +1 -1
  92. package/src/components/navigation/CollapsibleResourceNavigation.tsx +14 -9
  93. package/src/components/navigation/NavigationTabs.css +36 -24
  94. package/src/components/navigation/ObservableLink.tsx +91 -0
  95. package/src/components/navigation/SimpleNavigation.tsx +20 -16
  96. package/src/components/navigation/SortableResourceTab.tsx +11 -5
  97. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +51 -26
  98. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +28 -22
  99. package/src/components/resource/AnnotateView.tsx +64 -134
  100. package/src/components/resource/BrowseView.tsx +86 -166
  101. package/src/components/resource/HistoryEvent.tsx +13 -7
  102. package/src/components/resource/ResourceViewer.tsx +122 -264
  103. package/src/components/resource/__tests__/BrowseView.test.tsx +631 -0
  104. package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +231 -0
  105. package/src/components/resource/panels/AssessmentEntry.tsx +25 -33
  106. package/src/components/resource/panels/AssessmentPanel.tsx +106 -28
  107. package/src/components/resource/panels/CommentEntry.tsx +38 -32
  108. package/src/components/resource/panels/CommentsPanel.tsx +121 -28
  109. package/src/components/resource/panels/DetectSection.css +36 -1
  110. package/src/components/resource/panels/DetectSection.tsx +38 -10
  111. package/src/components/resource/panels/HighlightEntry.tsx +25 -33
  112. package/src/components/resource/panels/HighlightPanel.tsx +100 -25
  113. package/src/components/resource/panels/ReferenceEntry.tsx +61 -75
  114. package/src/components/resource/panels/ReferencesPanel.tsx +134 -42
  115. package/src/components/resource/panels/ResourceInfoPanel.tsx +47 -48
  116. package/src/components/resource/panels/TagEntry.tsx +25 -33
  117. package/src/components/resource/panels/TaggingPanel.tsx +119 -30
  118. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +30 -92
  119. package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +129 -110
  120. package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +86 -78
  121. package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +144 -149
  122. package/src/components/resource/panels/__tests__/DetectSection.test.tsx +480 -0
  123. package/src/components/resource/panels/__tests__/HighlightPanel.detectionProgress.test.tsx +362 -0
  124. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +226 -111
  125. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +117 -61
  126. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +128 -106
  127. package/src/components/settings/SettingsPanel.tsx +15 -12
  128. package/src/features/admin-devops/__tests__/AdminDevOpsPage.test.tsx +1 -46
  129. package/src/features/admin-devops/components/AdminDevOpsPage.tsx +0 -9
  130. package/src/features/admin-security/__tests__/AdminSecurityPage.test.tsx +0 -3
  131. package/src/features/admin-security/components/AdminSecurityPage.tsx +0 -9
  132. package/src/features/admin-users/__tests__/AdminUsersPage.test.tsx +0 -3
  133. package/src/features/admin-users/components/AdminUsersPage.tsx +0 -9
  134. package/src/features/moderate-entity-tags/__tests__/EntityTagsPage.test.tsx +0 -3
  135. package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -9
  136. package/src/features/moderate-recent/__tests__/RecentDocumentsPage.test.tsx +0 -32
  137. package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -9
  138. package/src/features/moderate-tag-schemas/__tests__/TagSchemasPage.test.tsx +0 -32
  139. package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -9
  140. package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +51 -54
  141. package/src/features/resource-compose/components/ResourceComposePage.tsx +3 -13
  142. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +39 -45
  143. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +9 -13
  144. package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +231 -0
  145. package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +234 -0
  146. package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +388 -0
  147. package/src/features/resource-viewer/__tests__/DetectionProgressDismissal.test.tsx +318 -0
  148. package/src/features/resource-viewer/__tests__/GenerationFlowIntegration.test.tsx +504 -0
  149. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +135 -88
  150. package/src/features/resource-viewer/__tests__/detection-progress-flow.test.tsx +322 -0
  151. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +308 -528
  152. package/translations/ar.json +6 -3
  153. package/translations/bn.json +6 -3
  154. package/translations/cs.json +6 -3
  155. package/translations/da.json +6 -3
  156. package/translations/de.json +6 -3
  157. package/translations/el.json +6 -3
  158. package/translations/en.json +6 -3
  159. package/translations/es.json +6 -3
  160. package/translations/fa.json +6 -3
  161. package/translations/fi.json +6 -3
  162. package/translations/fr.json +6 -3
  163. package/translations/he.json +6 -3
  164. package/translations/hi.json +6 -3
  165. package/translations/id.json +6 -3
  166. package/translations/it.json +6 -3
  167. package/translations/ja.json +6 -3
  168. package/translations/ko.json +6 -3
  169. package/translations/ms.json +6 -3
  170. package/translations/nl.json +6 -3
  171. package/translations/no.json +6 -3
  172. package/translations/pl.json +6 -3
  173. package/translations/pt.json +6 -3
  174. package/translations/ro.json +6 -3
  175. package/translations/sv.json +6 -3
  176. package/translations/th.json +6 -3
  177. package/translations/tr.json +6 -3
  178. package/translations/uk.json +6 -3
  179. package/translations/vi.json +6 -3
  180. package/translations/zh.json +6 -3
  181. package/dist/PdfAnnotationCanvas.client-ADC4FFSE.mjs.map +0 -1
  182. package/dist/TranslationManager-Co_5fSxl.d.mts +0 -118
  183. package/dist/ar-EMHEHPCJ.mjs.map +0 -1
  184. package/dist/bn-OVCI4F6X.mjs.map +0 -1
  185. package/dist/chunk-JZIO2A3B.mjs.map +0 -1
  186. package/dist/chunk-LIHZTECW.mjs.map +0 -1
  187. package/dist/cs-FAN66Q2F.mjs.map +0 -1
  188. package/dist/da-YBBIHI2O.mjs.map +0 -1
  189. package/dist/de-MAYU33LB.mjs.map +0 -1
  190. package/dist/el-MKGSWN4O.mjs.map +0 -1
  191. package/dist/es-52LHUWJD.mjs.map +0 -1
  192. package/dist/fa-FJICRANB.mjs.map +0 -1
  193. package/dist/fi-O455XFCR.mjs.map +0 -1
  194. package/dist/fr-TXIXHOOE.mjs.map +0 -1
  195. package/dist/he-JBSOX5IN.mjs.map +0 -1
  196. package/dist/hi-KGHI3XVT.mjs.map +0 -1
  197. package/dist/id-5OCPPZLO.mjs.map +0 -1
  198. package/dist/it-PNBBZSM2.mjs.map +0 -1
  199. package/dist/ja-LDD7R3TJ.mjs.map +0 -1
  200. package/dist/ko-F47ZDEY3.mjs.map +0 -1
  201. package/dist/ms-Z7LMXJWL.mjs.map +0 -1
  202. package/dist/nl-6SJFBPJ3.mjs.map +0 -1
  203. package/dist/no-YXPBPSGF.mjs.map +0 -1
  204. package/dist/pl-P4AZ2QME.mjs.map +0 -1
  205. package/dist/pt-LHWUS6U6.mjs.map +0 -1
  206. package/dist/ro-EA5J2ZON.mjs.map +0 -1
  207. package/dist/sv-DATBS3UQ.mjs.map +0 -1
  208. package/dist/th-WTFJRWPT.mjs.map +0 -1
  209. package/dist/tr-IKO3RXOX.mjs.map +0 -1
  210. package/dist/uk-CF6CTTRK.mjs.map +0 -1
  211. package/dist/vi-AJLTXPZQ.mjs.map +0 -1
  212. package/dist/zh-U3ORHHYH.mjs.map +0 -1
  213. /package/dist/{en-DDLIXJCU.mjs.map → en-KJCJQ4OO.mjs.map} +0 -0
@@ -1,16 +1,17 @@
1
1
  /**
2
- * ResourceViewerPage - Pure React component for viewing resources
2
+ * ResourceViewerPage - Self-contained resource viewer component
3
3
  *
4
- * No Next.js dependencies - receives all data and callbacks via props.
5
- * This component handles the UI rendering and state management for the resource viewer.
4
+ * Handles all data loading, event subscriptions, and side effects internally.
5
+ * Only requires minimal props from the framework layer (routing, modals).
6
6
  */
7
7
 
8
- import React, { useState, useEffect, useCallback } from 'react';
9
- import type { components, ResourceUri, GenerationContext, Selector } from '@semiont/api-client';
10
- import { getLanguage, getPrimaryRepresentation, annotationUri, resourceUri, resourceAnnotationUri } from '@semiont/api-client';
11
- import { createCancelDetectionHandler, ANNOTATORS } from '@semiont/react-ui';
8
+ import React, { useState, useEffect, useCallback, useMemo } from 'react';
9
+ import { useQueryClient } from '@tanstack/react-query';
10
+ import type { components, ResourceUri } from '@semiont/api-client';
11
+ import { getLanguage, getPrimaryRepresentation, resourceAnnotationUri, getPrimaryMediaType } from '@semiont/api-client';
12
+ import { uriToAnnotationId } from '@semiont/core';
13
+ import { ANNOTATORS } from '@semiont/react-ui';
12
14
  import { ErrorBoundary } from '@semiont/react-ui';
13
- import { useGenerationProgress } from '@semiont/react-ui';
14
15
  import { AnnotationHistory } from '@semiont/react-ui';
15
16
  import { UnifiedAnnotationsPanel } from '@semiont/react-ui';
16
17
  import { ResourceInfoPanel } from '@semiont/react-ui';
@@ -20,19 +21,26 @@ import { Toolbar } from '@semiont/react-ui';
20
21
  import { useResourceLoadingAnnouncements } from '@semiont/react-ui';
21
22
  import type { GenerationOptions } from '@semiont/react-ui';
22
23
  import { ResourceViewer } from '@semiont/react-ui';
23
- import { MakeMeaningEventBusProvider, useMakeMeaningEvents } from '@semiont/react-ui';
24
+ import { QUERY_KEYS } from '../../../lib/query-keys';
25
+ import { useResources, useEntityTypes } from '../../../lib/api-hooks';
26
+ import { useResourceContent } from '../../../hooks/useResourceContent';
27
+ import { useToast } from '../../../components/Toast';
28
+ import { useTheme } from '../../../hooks/useTheme';
29
+ import { useLineNumbers } from '../../../hooks/useLineNumbers';
30
+ import { useResourceEvents } from '../../../hooks/useResourceEvents';
31
+ import { useDebouncedCallback } from '../../../hooks/useDebounce';
32
+ import { useOpenResources } from '../../../contexts/OpenResourcesContext';
33
+ // Import EventBus hooks directly from context to avoid mocking issues in tests
34
+ import { useEventBus } from '../../../contexts/EventBusContext';
35
+ import { useEventSubscriptions } from '../../../contexts/useEventSubscription';
36
+ import { useResourceAnnotations } from '../../../contexts/ResourceAnnotationsContext';
37
+ import { useDetectionFlow } from '../../../hooks/useDetectionFlow';
38
+ import { usePanelNavigation } from '../../../hooks/usePanelNavigation';
39
+ import { useAnnotationFlow } from '../../../hooks/useAnnotationFlow';
40
+ import { useGenerationFlow } from '../../../hooks/useGenerationFlow';
24
41
 
25
42
  type SemiontResource = components['schemas']['ResourceDescriptor'];
26
43
  type Annotation = components['schemas']['Annotation'];
27
- type Motivation = components['schemas']['Motivation'];
28
-
29
- // Unified pending annotation type - all human-created annotations flow through this
30
- interface PendingAnnotation {
31
- selector: Selector | Selector[];
32
- motivation: Motivation;
33
- }
34
-
35
- import type { DetectionProgress } from '@semiont/react-ui';
36
44
 
37
45
  export interface ResourceViewerPageProps {
38
46
  /**
@@ -45,97 +53,16 @@ export interface ResourceViewerPageProps {
45
53
  */
46
54
  rUri: ResourceUri;
47
55
 
48
- /**
49
- * Document content (already loaded)
50
- */
51
- content: string;
52
-
53
- /**
54
- * Whether content is still loading
55
- */
56
- contentLoading: boolean;
57
-
58
- /**
59
- * All annotations for this resource
60
- */
61
- annotations: Annotation[];
62
-
63
- /**
64
- * Resources that reference this resource
65
- */
66
- referencedBy: any[];
67
-
68
- /**
69
- * Whether referencedBy is loading
70
- */
71
- referencedByLoading: boolean;
72
-
73
- /**
74
- * All available entity types
75
- */
76
- allEntityTypes: string[];
77
-
78
56
  /**
79
57
  * Current locale
80
58
  */
81
59
  locale: string;
82
60
 
83
-
84
- /**
85
- * Theme state
86
- */
87
- theme: any;
88
- onThemeChange: (theme: any) => void;
89
-
90
- /**
91
- * Line numbers state
92
- */
93
- showLineNumbers: boolean;
94
- onLineNumbersToggle: () => void;
95
-
96
- /**
97
- * Active toolbar panel
98
- */
99
- activePanel: any;
100
- onPanelToggle: (panel: any) => void;
101
- setActivePanel: (panel: any) => void;
102
-
103
- /**
104
- * Callbacks for resource actions
105
- */
106
- onArchive: () => Promise<void>;
107
- onUnarchive: () => Promise<void>;
108
- onClone: () => Promise<void>;
109
- onUpdateAnnotationBody: (annotationUri: string, data: any) => Promise<void>;
110
-
111
- /**
112
- * Annotation CRUD callbacks
113
- */
114
- onCreateAnnotation: (
115
- rUri: ResourceUri,
116
- motivation: Motivation,
117
- selector: any,
118
- body: any[]
119
- ) => Promise<void>;
120
- onTriggerSparkleAnimation: (annotationId: string) => void;
121
- onClearNewAnnotationId: (annotationId: string) => void;
122
-
123
- /**
124
- * Toast notifications
125
- */
126
- showSuccess: (message: string) => void;
127
- showError: (message: string) => void;
128
-
129
61
  /**
130
62
  * Cache manager for detection
131
63
  */
132
64
  cacheManager: any;
133
65
 
134
- /**
135
- * API client
136
- */
137
- client: any;
138
-
139
66
  /**
140
67
  * Link component for routing
141
68
  */
@@ -147,216 +74,279 @@ export interface ResourceViewerPageProps {
147
74
  routes: any;
148
75
 
149
76
  /**
150
- * Component dependencies - passed from frontend
77
+ * Component dependencies - passed from framework layer
151
78
  */
152
79
  ToolbarPanels: React.ComponentType<any>;
153
80
  SearchResourcesModal: React.ComponentType<any>;
154
81
  GenerationConfigModal: React.ComponentType<any>;
82
+
83
+ /**
84
+ * Callback to refetch document from parent
85
+ */
86
+ refetchDocument: () => Promise<unknown>;
155
87
  }
156
88
 
157
- // Inner component that has access to event bus
158
- function ResourceViewerPageInner({
89
+ /**
90
+ * ResourceViewerPage - Main component
91
+ *
92
+ * Uses hooks directly (NO containers, NO render props, NO ResourceViewerPageContent wrapper)
93
+ */
94
+ export function ResourceViewerPage({
159
95
  resource,
160
96
  rUri,
161
- content,
162
- contentLoading,
163
- annotations,
164
- referencedBy,
165
- referencedByLoading,
166
- allEntityTypes,
167
97
  locale,
168
- theme,
169
- onThemeChange,
170
- showLineNumbers,
171
- onLineNumbersToggle,
172
- activePanel,
173
- onPanelToggle,
174
- setActivePanel,
175
- onArchive,
176
- onUnarchive,
177
- onClone,
178
- onUpdateAnnotationBody,
179
- onCreateAnnotation,
180
- onTriggerSparkleAnimation,
181
- onClearNewAnnotationId,
182
- showSuccess,
183
- showError,
184
98
  cacheManager,
185
- client,
186
99
  Link,
187
100
  routes,
188
101
  ToolbarPanels,
189
102
  SearchResourcesModal,
190
103
  GenerationConfigModal,
104
+ refetchDocument,
191
105
  }: ResourceViewerPageProps) {
192
106
  // Get unified event bus for subscribing to UI events
193
- const eventBus = useMakeMeaningEvents();
194
- // Resource loading announcements
195
- const {
196
- announceResourceLoading,
197
- announceResourceLoaded
198
- } = useResourceLoadingAnnouncements();
199
-
200
- // Derived state
201
- const documentEntityTypes = resource.entityTypes || [];
202
-
203
- // Get primary representation metadata
204
- const primaryRep = getPrimaryRepresentation(resource);
205
- const primaryMediaType = primaryRep?.mediaType;
206
- const primaryByteSize = primaryRep?.byteSize;
207
-
208
- // Annotate mode state - read-only copy for sidebar panel coordination
209
- // ResourceViewer manages the authoritative state and persists to localStorage
210
- const [annotateMode, _setAnnotateMode] = useState(() => {
211
- if (typeof window !== 'undefined') {
212
- return localStorage.getItem('annotateMode') === 'true';
213
- }
214
- return false;
215
- });
216
-
217
- // Unified annotation state (motivation-agnostic) - used by sidebar panels
218
- const [focusedAnnotationId, _setFocusedAnnotationId] = useState<string | null>(null);
219
- const [hoveredAnnotationId, setHoveredAnnotationId] = useState<string | null>(null);
220
- // scrollToAnnotationId removed - ResourceViewer now manages scroll state internally
221
-
222
- // Unified pending annotation - all human-created annotations flow through this
223
- const [pendingAnnotation, setPendingAnnotation] = useState<PendingAnnotation | null>(null);
224
-
225
- // Search state
226
- const [searchModalOpen, setSearchModalOpen] = useState(false);
227
- const [searchTerm, setSearchTerm] = useState('');
228
- const [pendingReferenceId, setPendingReferenceId] = useState<string | null>(null);
229
-
230
- // Generation config modal state
231
- const [generationModalOpen, setGenerationModalOpen] = useState(false);
232
- const [generationReferenceId, setGenerationReferenceId] = useState<string | null>(null);
233
- const [generationDefaultTitle, setGenerationDefaultTitle] = useState('');
234
-
235
- // Unified detection state (motivation-based)
236
- const [detectingMotivation, setDetectingMotivation] = useState<Motivation | null>(null);
237
- const [motivationDetectionProgress, setMotivationDetectionProgress] = useState<DetectionProgress | null>(null);
238
-
239
- // SSE stream reference for cancellation
240
- const detectionStreamRef = React.useRef<any>(null);
107
+ const eventBus = useEventBus();
108
+ const queryClient = useQueryClient();
109
+
110
+ // UI state hooks
111
+ const { showError, showSuccess } = useToast();
112
+ const { theme, setTheme } = useTheme();
113
+ const { showLineNumbers, toggleLineNumbers } = useLineNumbers();
114
+ const { addResource } = useOpenResources();
115
+ const { triggerSparkleAnimation, clearNewAnnotationId } = useResourceAnnotations();
116
+
117
+ // API hooks
118
+ const resources = useResources();
119
+ const entityTypesAPI = useEntityTypes();
120
+
121
+ // Load all data
122
+ const { content, loading: contentLoading } = useResourceContent(rUri, resource);
123
+
124
+ const { data: annotationsData } = resources.annotations.useQuery(rUri);
125
+ const annotations = useMemo(
126
+ () => annotationsData?.annotations || [],
127
+ [annotationsData?.annotations]
128
+ );
241
129
 
242
- // Handle event hover - trigger sparkle animation
243
- const handleEventHover = useCallback((annotationId: string | null) => {
244
- setHoveredAnnotationId(annotationId);
245
- if (annotationId) {
246
- onTriggerSparkleAnimation(annotationId);
247
- }
248
- }, [onTriggerSparkleAnimation]);
130
+ const { data: referencedByData, isLoading: referencedByLoading } = resources.referencedBy.useQuery(rUri);
131
+ const referencedBy = referencedByData?.referencedBy || [];
249
132
 
250
- // Handle event click - scroll handled internally by ResourceViewer now
251
- const handleEventClick = useCallback((_annotationId: string | null) => {
252
- // ResourceViewer now manages scroll state internally
253
- }, []);
133
+ const { data: entityTypesData } = entityTypesAPI.list.useQuery();
134
+ const allEntityTypes = (entityTypesData as { entityTypes: string[] } | undefined)?.entityTypes || [];
254
135
 
255
- // Use SSE-based document generation progress - provides inline sparkle animation
136
+ // Flow state hooks (NO CONTAINERS)
137
+ const { detectingMotivation, detectionProgress } = useDetectionFlow(rUri);
138
+ const { activePanel, scrollToAnnotationId, panelInitialTab, onScrollCompleted } = usePanelNavigation();
139
+ const { pendingAnnotation, hoveredAnnotationId } = useAnnotationFlow(rUri);
256
140
  const {
257
- progress: generationProgress,
258
- startGeneration,
259
- clearProgress
260
- } = useGenerationProgress({
261
- onComplete: () => {
262
- // Clear progress widget
263
- setTimeout(() => clearProgress(), 1000);
141
+ generationProgress,
142
+ generationModalOpen,
143
+ generationReferenceId,
144
+ generationDefaultTitle,
145
+ searchModalOpen,
146
+ pendingReferenceId,
147
+ onGenerateDocument,
148
+ onCloseGenerationModal,
149
+ onCloseSearchModal,
150
+ } = useGenerationFlow(locale, rUri.split('/').pop() || '', showSuccess, showError, cacheManager, clearNewAnnotationId);
151
+
152
+ // Debounced invalidation for real-time events
153
+ const debouncedInvalidateAnnotations = useDebouncedCallback(
154
+ () => {
155
+ queryClient.invalidateQueries({ queryKey: QUERY_KEYS.documents.annotations(rUri) });
156
+ queryClient.invalidateQueries({ queryKey: QUERY_KEYS.documents.events(rUri) });
264
157
  },
265
- onError: (error) => {
266
- console.error('[Generation] Error:', error);
158
+ 500
159
+ );
160
+
161
+ // Add resource to open tabs when it loads
162
+ useEffect(() => {
163
+ if (resource && rUri) {
164
+ const resourceIdSegment = rUri.split('/').pop() || '';
165
+ const mediaType = getPrimaryMediaType(resource);
166
+ addResource(resourceIdSegment, resource.name, mediaType || undefined);
167
+ if (typeof localStorage !== 'undefined') {
168
+ localStorage.setItem('lastViewedDocumentId', resourceIdSegment);
169
+ }
267
170
  }
268
- });
171
+ }, [resource, rUri, addResource]);
269
172
 
270
- // Generic detection context for all annotation types
271
- const detectionContext = {
272
- client,
173
+ // Real-time document events (SSE)
174
+ useResourceEvents({
273
175
  rUri,
274
- setDetectingMotivation,
275
- setMotivationDetectionProgress,
276
- detectionStreamRef,
277
- cacheManager,
278
- showSuccess,
279
- showError
280
- };
176
+ autoConnect: true,
177
+
178
+ // Annotation events - use debounced invalidation to batch rapid updates
179
+ onAnnotationAdded: useCallback((_event: any) => {
180
+ debouncedInvalidateAnnotations();
181
+ }, [debouncedInvalidateAnnotations]),
182
+
183
+ onAnnotationRemoved: useCallback((_event: any) => {
184
+ debouncedInvalidateAnnotations();
185
+ }, [debouncedInvalidateAnnotations]),
186
+
187
+ onAnnotationBodyUpdated: useCallback((event: any) => {
188
+ // Optimistically update annotations cache with body operations
189
+ queryClient.setQueryData(QUERY_KEYS.documents.annotations(rUri), (old: any) => {
190
+ if (!old) return old;
191
+ return {
192
+ ...old,
193
+ annotations: old.annotations.map((annotation: any) => {
194
+ const annotationIdSegment = uriToAnnotationId(annotation.id);
195
+ if (annotationIdSegment === event.payload.annotationId) {
196
+ let bodyArray = Array.isArray(annotation.body) ? [...annotation.body] : [];
197
+
198
+ for (const op of event.payload.operations || []) {
199
+ if (op.op === 'add') {
200
+ bodyArray.push(op.item);
201
+ } else if (op.op === 'remove') {
202
+ bodyArray = bodyArray.filter((item: any) =>
203
+ JSON.stringify(item) !== JSON.stringify(op.item)
204
+ );
205
+ } else if (op.op === 'replace') {
206
+ const index = bodyArray.findIndex((item: any) =>
207
+ JSON.stringify(item) === JSON.stringify(op.oldItem)
208
+ );
209
+ if (index !== -1) {
210
+ bodyArray[index] = op.newItem;
211
+ }
212
+ }
213
+ }
281
214
 
282
- // Generic cancel handler (works for all detection types)
283
- const handleCancelDetection = useCallback(
284
- () => createCancelDetectionHandler({
285
- detectionStreamRef,
286
- setDetectingMotivation,
287
- setMotivationDetectionProgress
288
- })(),
289
- []
290
- );
215
+ return {
216
+ ...annotation,
217
+ body: bodyArray,
218
+ };
219
+ }
220
+ return annotation;
221
+ }),
222
+ };
223
+ });
291
224
 
292
- // Handle document generation from stub reference
293
- const handleGenerateDocument = useCallback((
294
- referenceId: string,
295
- options: {
296
- title: string;
297
- prompt?: string;
298
- language?: string;
299
- temperature?: number;
300
- maxTokens?: number;
301
- context?: GenerationContext;
302
- }
303
- ) => {
304
- // Only open modal if this is the initial click (no context provided)
305
- if (!options.context) {
306
- setGenerationReferenceId(referenceId);
307
- setGenerationDefaultTitle(options.title);
308
- setGenerationModalOpen(true);
309
- return;
310
- }
225
+ queryClient.invalidateQueries({ queryKey: QUERY_KEYS.documents.events(rUri) });
226
+ }, [queryClient, rUri]),
227
+
228
+ // Document status events
229
+ onDocumentArchived: useCallback((_event: any) => {
230
+ refetchDocument();
231
+ showSuccess('This document has been archived');
232
+ debouncedInvalidateAnnotations();
233
+ }, [refetchDocument, showSuccess, debouncedInvalidateAnnotations]),
234
+
235
+ onDocumentUnarchived: useCallback((_event: any) => {
236
+ refetchDocument();
237
+ showSuccess('This document has been unarchived');
238
+ debouncedInvalidateAnnotations();
239
+ }, [refetchDocument, showSuccess, debouncedInvalidateAnnotations]),
240
+
241
+ // Entity tag events
242
+ onEntityTagAdded: useCallback((_event: any) => {
243
+ refetchDocument();
244
+ debouncedInvalidateAnnotations();
245
+ }, [refetchDocument, debouncedInvalidateAnnotations]),
246
+
247
+ onEntityTagRemoved: useCallback((_event: any) => {
248
+ refetchDocument();
249
+ debouncedInvalidateAnnotations();
250
+ }, [refetchDocument, debouncedInvalidateAnnotations]),
251
+
252
+ onError: useCallback((error: any) => {
253
+ console.error('[RealTime] Event stream error:', error);
254
+ }, []),
255
+ });
311
256
 
312
- // Modal submitted with full options including context - proceed with generation
313
- if (!resource) return;
314
-
315
- // Clear CSS sparkle animation if reference was recently created
316
- onClearNewAnnotationId(annotationUri(referenceId));
317
-
318
- // Use full resource URI (W3C Web Annotation spec requires URIs)
319
- const resourceUriStr = resource['@id'];
320
- startGeneration(annotationUri(referenceId), resourceUri(resourceUriStr), {
321
- ...options,
322
- // Use language from modal if provided, otherwise fall back to current locale
323
- language: options.language || locale,
324
- context: options.context
325
- });
326
- }, [startGeneration, resource, onClearNewAnnotationId, locale]);
327
-
328
- // Handle manual document creation from stub reference
329
- const handleCreateDocument = useCallback((
330
- annotationUri: string,
331
- title: string,
332
- entityTypes: string[]
333
- ) => {
334
- if (!resource) return;
335
-
336
- // Extract resource ID from URI
337
- const resourceId = rUri.split('/').pop() || '';
338
-
339
- // Navigate to compose page with reference context
340
- const entityTypesStr = entityTypes.join(',');
341
- const params = new URLSearchParams({
342
- name: title, // Compose page expects 'name' parameter
343
- annotationUri, // Pass full annotation URI, not just ID
344
- sourceDocumentId: resourceId,
345
- ...(entityTypes.length > 0 ? { entityTypes: entityTypesStr } : {}),
346
- });
347
-
348
- window.location.href = `/know/compose?${params.toString()}`;
349
- }, [resource, rUri]);
350
-
351
- // Handle search for documents to link to reference
352
- const handleSearchDocuments = useCallback((referenceId: string, searchTerm: string) => {
353
- setPendingReferenceId(referenceId);
354
- setSearchTerm(searchTerm);
355
- setSearchModalOpen(true);
356
- }, []);
257
+ // Event bus subscriptions (combined into single useEventSubscriptions call to prevent hook ordering issues)
258
+ useEventSubscriptions({
259
+ // Resource operations
260
+ 'resource:archive': async () => {
261
+ try {
262
+ await resources.update.useMutation().mutateAsync({
263
+ rUri,
264
+ data: { archived: true }
265
+ });
266
+ await refetchDocument();
267
+ showSuccess('Document archived');
268
+ } catch (err) {
269
+ console.error('Failed to archive document:', err);
270
+ showError('Failed to archive document');
271
+ }
272
+ },
273
+ 'resource:unarchive': async () => {
274
+ try {
275
+ await resources.update.useMutation().mutateAsync({
276
+ rUri,
277
+ data: { archived: false }
278
+ });
279
+ await refetchDocument();
280
+ showSuccess('Document unarchived');
281
+ } catch (err) {
282
+ console.error('Failed to unarchive document:', err);
283
+ showError('Failed to unarchive document');
284
+ }
285
+ },
286
+ 'resource:clone': async () => {
287
+ try {
288
+ const result = await resources.generateCloneToken.useMutation().mutateAsync(rUri);
289
+ const token = result.token;
290
+ const cloneUrl = `${typeof window !== 'undefined' ? window.location.origin : ''}/know/clone?token=${token}`;
291
+
292
+ await navigator.clipboard.writeText(cloneUrl);
293
+ showSuccess('Clone link copied to clipboard');
294
+ } catch (err) {
295
+ console.error('Failed to generate clone token:', err);
296
+ showError('Failed to generate clone link');
297
+ }
298
+ },
299
+
300
+ // Annotation operations
301
+ 'annotation:sparkle': ({ annotationId }) => {
302
+ triggerSparkleAnimation(annotationId);
303
+ },
304
+ 'annotation:created': ({ annotation }) => {
305
+ triggerSparkleAnimation(annotation.id);
306
+ debouncedInvalidateAnnotations();
307
+ },
308
+ 'annotation:deleted': debouncedInvalidateAnnotations,
309
+ 'annotation:create-failed': () => showError('Failed to create annotation'),
310
+ 'annotation:delete-failed': () => showError('Failed to delete annotation'),
311
+ 'annotation:body-updated': () => {
312
+ // Success - optimistic update already applied via useResourceEvents
313
+ },
314
+ 'annotation:body-update-failed': () => showError('Failed to update annotation'),
315
+
316
+ // Settings
317
+ 'settings:theme-changed': ({ theme }) => setTheme(theme),
318
+ 'settings:line-numbers-toggled': toggleLineNumbers,
319
+
320
+ // Detection/Generation
321
+ 'detection:complete': () => showSuccess('Detection complete'),
322
+ 'detection:failed': () => showError('Detection failed'),
323
+ 'generation:complete': () => showSuccess('Document generated'),
324
+ 'generation:failed': () => showError('Failed to generate document'),
325
+
326
+ // Navigation
327
+ 'navigation:reference-navigate': ({ documentId }: { documentId: string }) => {
328
+ // Navigate to the referenced document
329
+ if (routes.resource) {
330
+ const path = routes.resource.replace('[resourceId]', encodeURIComponent(documentId));
331
+ eventBus.emit('navigation:router-push', { path, reason: 'reference-link' });
332
+ }
333
+ },
334
+ 'navigation:entity-type-clicked': ({ entityType }: { entityType: string }) => {
335
+ // Navigate to discovery page filtered by entity type
336
+ if (routes.know) {
337
+ const path = `${routes.know}?entityType=${encodeURIComponent(entityType)}`;
338
+ eventBus.emit('navigation:router-push', { path, reason: 'entity-type-filter' });
339
+ }
340
+ },
341
+ });
357
342
 
343
+ // Resource loading announcements
344
+ const {
345
+ announceResourceLoading,
346
+ announceResourceLoaded
347
+ } = useResourceLoadingAnnouncements();
358
348
 
359
- // Announce content loading state changes
349
+ // Announce content loading state changes (app-level)
360
350
  useEffect(() => {
361
351
  if (contentLoading) {
362
352
  announceResourceLoading(resource.name);
@@ -365,211 +355,21 @@ function ResourceViewerPageInner({
365
355
  }
366
356
  }, [contentLoading, content, resource.name, announceResourceLoading, announceResourceLoaded]);
367
357
 
368
- // Unified annotation request handler - all human-created annotations flow through this
369
- const handleAnnotationRequested = useCallback((pending: PendingAnnotation) => {
370
- // Route to appropriate panel tab based on motivation
371
- const MOTIVATION_TO_TAB: Record<Motivation, string> = {
372
- highlighting: 'annotations',
373
- commenting: 'annotations',
374
- assessing: 'annotations',
375
- tagging: 'annotations',
376
- linking: 'annotations',
377
- bookmarking: 'annotations',
378
- classifying: 'annotations',
379
- describing: 'annotations',
380
- editing: 'annotations',
381
- identifying: 'annotations',
382
- moderating: 'annotations',
383
- questioning: 'annotations',
384
- replying: 'annotations',
385
- };
386
-
387
- setActivePanel(MOTIVATION_TO_TAB[pending.motivation] || 'annotations');
388
- setPendingAnnotation(pending);
389
- }, [setActivePanel]);
390
-
391
- // Subscribe to UI events from ResourceViewer
392
- useEffect(() => {
393
- const handleCommentRequested = (selection: any) => {
394
- handleAnnotationRequested({
395
- selector: {
396
- type: 'TextQuoteSelector',
397
- exact: selection.exact,
398
- start: selection.start,
399
- end: selection.end,
400
- ...(selection.prefix && { prefix: selection.prefix }),
401
- ...(selection.suffix && { suffix: selection.suffix })
402
- },
403
- motivation: 'commenting'
404
- });
405
- };
406
-
407
- const handleTagRequested = (selection: any) => {
408
- handleAnnotationRequested({
409
- selector: {
410
- type: 'TextQuoteSelector',
411
- exact: selection.exact,
412
- start: selection.start,
413
- end: selection.end,
414
- ...(selection.prefix && { prefix: selection.prefix }),
415
- ...(selection.suffix && { suffix: selection.suffix })
416
- },
417
- motivation: 'tagging'
418
- });
419
- };
420
-
421
- const handleAssessmentRequested = (selection: any) => {
422
- handleAnnotationRequested({
423
- selector: {
424
- type: 'TextQuoteSelector',
425
- exact: selection.exact,
426
- start: selection.start,
427
- end: selection.end,
428
- ...(selection.prefix && { prefix: selection.prefix }),
429
- ...(selection.suffix && { suffix: selection.suffix })
430
- },
431
- motivation: 'assessing'
432
- });
433
- };
434
-
435
- const handleReferenceRequested = (selection: any) => {
436
- // Build selector based on what's present in the selection
437
- let selector: any;
438
-
439
- if (selection.svgSelector) {
440
- selector = {
441
- type: 'SvgSelector',
442
- value: selection.svgSelector
443
- };
444
- } else if (selection.fragmentSelector) {
445
- selector = {
446
- type: 'FragmentSelector',
447
- value: selection.fragmentSelector,
448
- ...(selection.conformsTo && { conformsTo: selection.conformsTo })
449
- };
450
- } else {
451
- selector = {
452
- type: 'TextQuoteSelector',
453
- exact: selection.exact,
454
- start: selection.start,
455
- end: selection.end,
456
- ...(selection.prefix && { prefix: selection.prefix }),
457
- ...(selection.suffix && { suffix: selection.suffix })
458
- };
459
- }
460
-
461
- handleAnnotationRequested({
462
- selector,
463
- motivation: 'linking'
464
- });
465
- };
466
-
467
- // Handle cancel pending annotation
468
- const handleCancelPending = () => {
469
- setPendingAnnotation(null);
470
- };
471
-
472
- eventBus.on('ui:selection:comment-requested', handleCommentRequested);
473
- eventBus.on('ui:selection:tag-requested', handleTagRequested);
474
- eventBus.on('ui:selection:assessment-requested', handleAssessmentRequested);
475
- eventBus.on('ui:selection:reference-requested', handleReferenceRequested);
476
- eventBus.on('ui:annotation:cancel-pending', handleCancelPending);
477
-
478
- return () => {
479
- eventBus.off('ui:selection:comment-requested', handleCommentRequested);
480
- eventBus.off('ui:selection:tag-requested', handleTagRequested);
481
- eventBus.off('ui:selection:assessment-requested', handleAssessmentRequested);
482
- eventBus.off('ui:selection:reference-requested', handleReferenceRequested);
483
- eventBus.off('ui:annotation:cancel-pending', handleCancelPending);
484
- };
485
- }, [eventBus, handleAnnotationRequested]);
486
-
487
- // Manual tag creation handler
488
- // Shared UI handlers - same across all annotation types
489
- const handleAnnotationClick = useCallback((annotation: Annotation) => {
490
- setHoveredAnnotationId(annotation.id);
491
- setTimeout(() => setHoveredAnnotationId(null), 1500);
492
- }, []);
493
-
494
- const handleAnnotationHover = useCallback((annotationId: string | null) => {
495
- setHoveredAnnotationId(annotationId);
496
- }, []);
497
-
498
- // Single generic annotation creation handler - reads config from ANNOTATORS
499
- const handleCreateAnnotation = useCallback(async (
500
- motivation: Motivation,
501
- ...args: any[]
502
- ) => {
503
- if (!pendingAnnotation || pendingAnnotation.motivation !== motivation) return;
504
-
505
- // Find the config for this motivation
506
- const annotatorConfig = Object.values(ANNOTATORS).find(a => a.motivation === motivation);
507
- if (!annotatorConfig) return;
508
-
509
- try {
510
- let body: any[] = [];
511
- let selector = pendingAnnotation.selector;
512
-
513
- // Build body based on config
514
- switch (annotatorConfig.create.bodyBuilder) {
515
- case 'empty':
516
- // args[0] might be selector for highlight/assessment
517
- if (args[0]) selector = args[0];
518
- body = [];
519
- break;
520
-
521
- case 'text':
522
- // args[0] is commentText
523
- body = [{
524
- type: 'TextualBody',
525
- value: args[0],
526
- format: 'text/plain',
527
- purpose: 'commenting'
528
- }];
529
- break;
530
-
531
- case 'entityTag':
532
- // args[0] is optional entityType
533
- if (args[0]) {
534
- body = [{
535
- type: 'TextualBody',
536
- purpose: 'tagging',
537
- value: args[0]
538
- }];
539
- }
540
- break;
541
-
542
- case 'dualTag':
543
- // args[0] is schemaId, args[1] is category
544
- body = [
545
- {
546
- type: 'TextualBody',
547
- purpose: 'tagging',
548
- value: args[1] // category
549
- },
550
- {
551
- type: 'TextualBody',
552
- purpose: 'classifying',
553
- value: args[0] // schemaId
554
- }
555
- ];
556
- break;
557
- }
558
-
559
- await onCreateAnnotation(rUri, motivation, selector, body);
560
- setPendingAnnotation(null);
358
+ // Derived state
359
+ const documentEntityTypes = resource.entityTypes || [];
561
360
 
562
- // Cache invalidation now handled by annotation:added event
361
+ // Get primary representation metadata
362
+ const primaryRep = getPrimaryRepresentation(resource);
363
+ const primaryMediaType = primaryRep?.mediaType;
364
+ const primaryByteSize = primaryRep?.byteSize;
563
365
 
564
- if (annotatorConfig.create.successMessage) {
565
- const message = annotatorConfig.create.successMessage.replace('{value}', args[1] || '');
566
- showSuccess(message);
567
- }
568
- } catch (error) {
569
- console.error(`Failed to create ${annotatorConfig.internalType}:`, error);
570
- showError(`Failed to create ${annotatorConfig.displayName.toLowerCase()}`);
366
+ // Annotate mode state - local UI state only
367
+ const [annotateMode, _setAnnotateMode] = useState(() => {
368
+ if (typeof window !== 'undefined') {
369
+ return localStorage.getItem('annotateMode') === 'true';
571
370
  }
572
- }, [pendingAnnotation, onCreateAnnotation, rUri, showSuccess, showError]);
371
+ return false;
372
+ });
573
373
 
574
374
  // Group annotations by type using static ANNOTATORS
575
375
  const result = {
@@ -595,7 +395,16 @@ function ResourceViewerPageInner({
595
395
  // Combine resource with content
596
396
  const resourceWithContent = { ...resource, content };
597
397
 
598
- // handleAnnotationClickAndFocus removed - ResourceViewer now manages focus/click state internally
398
+ // Handlers for AnnotationHistory (legacy event-based interaction)
399
+ const handleEventHover = useCallback((annotationId: string | null) => {
400
+ if (annotationId) {
401
+ eventBus.emit('annotation:sparkle', { annotationId });
402
+ }
403
+ }, []); // eventBus is stable singleton - never in deps
404
+
405
+ const handleEventClick = useCallback((_annotationId: string | null) => {
406
+ // ResourceViewer now manages scroll state internally
407
+ }, []);
599
408
 
600
409
  // Document rendering
601
410
  return (
@@ -639,12 +448,11 @@ function ResourceViewerPageInner({
639
448
  ) : (
640
449
  <ResourceViewer
641
450
  resource={resourceWithContent}
642
- annotations={groups}
643
- onAnnotationRequested={handleAnnotationRequested}
644
- generatingReferenceId={generationProgress?.referenceId ?? null}
645
- showLineNumbers={showLineNumbers}
646
- annotators={ANNOTATORS}
647
- />
451
+ annotations={groups}
452
+ generatingReferenceId={generationProgress?.referenceId ?? null}
453
+ showLineNumbers={showLineNumbers}
454
+ hoveredAnnotationId={hoveredAnnotationId}
455
+ />
648
456
  )}
649
457
  </ErrorBoundary>
650
458
  </div>
@@ -656,9 +464,7 @@ function ResourceViewerPageInner({
656
464
  <ToolbarPanels
657
465
  activePanel={activePanel}
658
466
  theme={theme}
659
- onThemeChange={onThemeChange}
660
467
  showLineNumbers={showLineNumbers}
661
- onLineNumbersToggle={onLineNumbersToggle}
662
468
  width={
663
469
  activePanel === 'jsonld' ? 'w-[600px]' :
664
470
  activePanel === 'annotations' ? 'w-[400px]' :
@@ -679,26 +485,20 @@ function ResourceViewerPageInner({
679
485
  <UnifiedAnnotationsPanel
680
486
  annotations={annotations}
681
487
  annotators={ANNOTATORS}
682
- onCreateAnnotation={handleCreateAnnotation}
683
- detectionContext={detectionContext}
684
- focusedAnnotationId={focusedAnnotationId}
685
- hoveredAnnotationId={hoveredAnnotationId}
686
- onAnnotationClick={handleAnnotationClick}
687
- onAnnotationHover={handleAnnotationHover}
688
488
  annotateMode={annotateMode}
689
489
  detectingMotivation={detectingMotivation}
690
- detectionProgress={motivationDetectionProgress}
490
+ detectionProgress={detectionProgress}
691
491
  pendingAnnotation={pendingAnnotation}
692
492
  allEntityTypes={allEntityTypes}
693
- onGenerateDocument={handleGenerateDocument}
694
- onCreateDocument={handleCreateDocument}
695
493
  generatingReferenceId={generationProgress?.referenceId ?? null}
696
- onSearchDocuments={handleSearchDocuments}
697
- onCancelDetection={handleCancelDetection}
698
- {...(primaryMediaType ? { mediaType: primaryMediaType } : {})}
699
494
  referencedBy={referencedBy}
700
495
  referencedByLoading={referencedByLoading}
701
496
  resourceId={rUri.split('/').pop() || ''}
497
+ scrollToAnnotationId={scrollToAnnotationId}
498
+ hoveredAnnotationId={hoveredAnnotationId}
499
+ onScrollCompleted={onScrollCompleted}
500
+ initialTab={panelInitialTab?.tab as any}
501
+ initialTabGeneration={panelInitialTab?.generation}
702
502
  Link={Link}
703
503
  routes={routes}
704
504
  />
@@ -724,9 +524,6 @@ function ResourceViewerPageInner({
724
524
  primaryMediaType={primaryMediaType}
725
525
  primaryByteSize={primaryByteSize}
726
526
  isArchived={resource.archived ?? false}
727
- onClone={onClone}
728
- onArchive={onArchive}
729
- onUnarchive={onUnarchive}
730
527
  />
731
528
  )}
732
529
 
@@ -749,7 +546,6 @@ function ResourceViewerPageInner({
749
546
  context="document"
750
547
  activePanel={activePanel}
751
548
  isArchived={resource.archived ?? false}
752
- onPanelToggle={onPanelToggle}
753
549
  />
754
550
  </div>
755
551
  </div>
@@ -757,10 +553,7 @@ function ResourceViewerPageInner({
757
553
  {/* Search Resources Modal */}
758
554
  <SearchResourcesModal
759
555
  isOpen={searchModalOpen}
760
- onClose={() => {
761
- setSearchModalOpen(false);
762
- setPendingReferenceId(null);
763
- }}
556
+ onClose={onCloseSearchModal}
764
557
  onSelect={async (documentId: string) => {
765
558
  if (pendingReferenceId) {
766
559
  try {
@@ -772,7 +565,8 @@ function ResourceViewerPageInner({
772
565
  const resourceIdSegment = rUri.split('/').pop() || '';
773
566
  const nestedUri = `${window.location.origin}/resources/${resourceIdSegment}/annotations/${annotationIdShort}`;
774
567
 
775
- await onUpdateAnnotationBody(resourceAnnotationUri(nestedUri), {
568
+ eventBus.emit('annotation:update-body', {
569
+ annotationUri: resourceAnnotationUri(nestedUri),
776
570
  resourceId: resourceIdSegment,
777
571
  operations: [{
778
572
  op: 'add',
@@ -785,27 +579,22 @@ function ResourceViewerPageInner({
785
579
  });
786
580
  showSuccess('Reference linked successfully');
787
581
  // Cache invalidation now handled by annotation:updated event
788
- setSearchModalOpen(false);
789
- setPendingReferenceId(null);
582
+ onCloseSearchModal();
790
583
  } catch (error) {
791
584
  console.error('Failed to link reference:', error);
792
585
  showError('Failed to link reference');
793
586
  }
794
587
  }
795
588
  }}
796
- searchTerm={searchTerm}
797
589
  />
798
590
 
799
591
  {/* Generation Config Modal */}
800
592
  <GenerationConfigModal
801
593
  isOpen={generationModalOpen}
802
- onClose={() => {
803
- setGenerationModalOpen(false);
804
- setGenerationReferenceId(null);
805
- }}
594
+ onClose={onCloseGenerationModal}
806
595
  onGenerate={(options: GenerationOptions) => {
807
596
  if (generationReferenceId) {
808
- handleGenerateDocument(generationReferenceId, options);
597
+ onGenerateDocument(generationReferenceId, options);
809
598
  }
810
599
  }}
811
600
  referenceId={generationReferenceId || ''}
@@ -815,12 +604,3 @@ function ResourceViewerPageInner({
815
604
  </div>
816
605
  );
817
606
  }
818
-
819
- // Outer component that wraps MakeMeaningEventBusProvider
820
- export function ResourceViewerPage(props: ResourceViewerPageProps) {
821
- return (
822
- <MakeMeaningEventBusProvider rUri={props.rUri}>
823
- <ResourceViewerPageInner {...props} />
824
- </MakeMeaningEventBusProvider>
825
- );
826
- }