@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
@@ -1,9 +1,10 @@
1
1
  'use client';
2
2
 
3
- import { forwardRef } from 'react';
3
+ import type { Ref } from 'react';
4
4
  import type { RouteBuilder } from '../../../contexts/RoutingContext';
5
5
  import { useTranslations } from '../../../contexts/TranslationContext';
6
6
  import type { components } from '@semiont/core';
7
+ import { annotationId, resourceId } from '@semiont/core';
7
8
  import { getAnnotationExactText, isBodyResolved, getBodySource, getFragmentSelector, getSvgSelector, getTargetSelector } from '@semiont/api-client';
8
9
  import { getEntityTypes } from '@semiont/ontology';
9
10
  import { getResourceIcon } from '../../../lib/resource-utils';
@@ -26,20 +27,18 @@ interface ReferenceEntryProps {
26
27
  routes: RouteBuilder;
27
28
  annotateMode?: boolean;
28
29
  isGenerating?: boolean;
30
+ ref?: Ref<HTMLDivElement>;
29
31
  }
30
32
 
31
- export const ReferenceEntry = forwardRef<HTMLDivElement, ReferenceEntryProps>(
32
- function ReferenceEntry(
33
- {
34
- reference,
35
- isFocused,
36
- isHovered = false,
37
- routes,
38
- annotateMode = true,
39
- isGenerating = false,
40
- },
41
- ref
42
- ) {
33
+ export function ReferenceEntry({
34
+ reference,
35
+ isFocused,
36
+ isHovered = false,
37
+ routes,
38
+ annotateMode = true,
39
+ isGenerating = false,
40
+ ref,
41
+ }: ReferenceEntryProps) {
43
42
  const t = useTranslations('ReferencesPanel');
44
43
  const eventBus = useEventBus();
45
44
  const navigate = useObservableExternalNavigation();
@@ -64,56 +63,46 @@ export const ReferenceEntry = forwardRef<HTMLDivElement, ReferenceEntryProps>(
64
63
 
65
64
  const handleOpen = () => {
66
65
  if (resolvedResourceUri) {
67
- const resourceId = resolvedResourceUri.split('/resources/')[1];
68
- if (resourceId) {
69
- // Use observable navigation - emits 'browse:external-navigate' event
70
- navigate(routes.resourceDetail(resourceId), { resourceId });
71
- }
66
+ // resolvedResourceUri is already a bare resource ID
67
+ navigate(routes.resourceDetail(resolvedResourceUri), { resourceId: resolvedResourceUri });
72
68
  }
73
69
  };
74
70
 
75
- const handleComposeDocument = () => {
76
- eventBus.get('bind:create-manual').next({
77
- annotationUri: reference.id,
78
- title: selectedText,
79
- entityTypes,
80
- });
81
- };
71
+ const source = typeof reference.target === 'object' && 'source' in reference.target
72
+ ? reference.target.source
73
+ : '';
82
74
 
83
75
  const handleUnlink = () => {
84
- // Unlinking removes all body items from the reference annotation
85
- const sourceUri = typeof reference.target === 'object' && 'source' in reference.target
86
- ? reference.target.source
87
- : '';
88
- if (sourceUri) {
76
+ if (source) {
89
77
  eventBus.get('bind:update-body').next({
90
- annotationUri: reference.id,
91
- resourceId: sourceUri.split('/resources/')[1] || '',
92
- operations: [{ op: 'remove' }], // Remove all body items
78
+ annotationId: annotationId(reference.id),
79
+ resourceId: resourceId(source),
80
+ operations: [{ op: 'remove' }],
93
81
  });
94
82
  }
95
83
  };
96
84
 
97
- const handleGenerate = () => {
98
- const resourceUri = typeof reference.target === 'object' && 'source' in reference.target
99
- ? reference.target.source
100
- : '';
101
-
102
- // Emit request to open generation modal
103
- eventBus.get('yield:modal-open').next({
104
- annotationUri: reference.id,
105
- resourceUri,
85
+ const handleInitiateWizard = () => {
86
+ eventBus.get('bind:initiate').next({
87
+ annotationId: annotationId(reference.id),
88
+ resourceId: resourceId(source),
106
89
  defaultTitle: selectedText,
90
+ entityTypes,
107
91
  });
108
92
  };
109
93
 
110
- const handleSearch = () => {
111
- eventBus.get('bind:link').next({
112
- annotationUri: reference.id,
113
- searchTerm: selectedText,
114
- });
94
+ // Status icon click handler depends on state and mode
95
+ const handleIconClick = (e: React.MouseEvent) => {
96
+ e.stopPropagation();
97
+ if (isResolved) {
98
+ handleOpen();
99
+ } else if (annotateMode) {
100
+ handleInitiateWizard();
101
+ }
115
102
  };
116
103
 
104
+ const iconIsClickable = isResolved || annotateMode;
105
+
117
106
  return (
118
107
  <div
119
108
  ref={ref}
@@ -127,9 +116,26 @@ export const ReferenceEntry = forwardRef<HTMLDivElement, ReferenceEntryProps>(
127
116
  >
128
117
  {/* Status indicator and text quote */}
129
118
  <div className="semiont-annotation-entry__header">
130
- <span className="semiont-reference-icon" title={isResolved ? t('resolved') : t('stub')}>
131
- {isResolved ? '🔗' : '❓'}
132
- </span>
119
+ <div className="semiont-reference-icon-group">
120
+ <button
121
+ className={`semiont-reference-icon${iconIsClickable ? ' semiont-reference-icon--clickable' : ''}`}
122
+ title={isResolved ? t('open') : annotateMode ? t('resolve') : t('stub')}
123
+ onClick={iconIsClickable ? handleIconClick : undefined}
124
+ data-generating={!isResolved && isGenerating ? 'true' : 'false'}
125
+ tabIndex={iconIsClickable ? 0 : -1}
126
+ >
127
+ {isResolved ? '🔗' : '❓'}
128
+ </button>
129
+ {annotateMode && isResolved && (
130
+ <button
131
+ className="semiont-reference-unlink"
132
+ title={t('unlink')}
133
+ onClick={(e) => { e.stopPropagation(); handleUnlink(); }}
134
+ >
135
+ ⛓️‍💥
136
+ </button>
137
+ )}
138
+ </div>
133
139
  <div className="semiont-annotation-entry__content">
134
140
  {selectedText && (
135
141
  <div className="semiont-annotation-entry__quote" data-type="reference">
@@ -163,59 +169,6 @@ export const ReferenceEntry = forwardRef<HTMLDivElement, ReferenceEntryProps>(
163
169
  ))}
164
170
  </div>
165
171
  )}
166
-
167
- {/* Actions based on state - only show curation actions in Annotate mode */}
168
- <div className="semiont-annotation-entry__actions" onClick={(e) => e.stopPropagation()}>
169
- {isResolved ? (
170
- // Resolved reference actions
171
- <div className="semiont-annotation-entry__action-row">
172
- <button
173
- onClick={handleOpen}
174
- className={`semiont-reference-button semiont-reference-button--primary ${annotateMode ? 'semiont-reference-button--full' : 'semiont-reference-button--wide'}`}
175
- title={t('open')}
176
- >
177
- 🔗
178
- </button>
179
- {annotateMode && (
180
- <button
181
- onClick={handleUnlink}
182
- className="semiont-reference-button semiont-reference-button--primary"
183
- title={t('unlink')}
184
- >
185
- ⛓️‍💥
186
- </button>
187
- )}
188
- </div>
189
- ) : (
190
- // Stub reference actions - only in Annotate mode
191
- annotateMode && (
192
- <div className="semiont-annotation-entry__action-row">
193
- <button
194
- onClick={handleGenerate}
195
- className="semiont-reference-button semiont-reference-button--primary semiont-reference-button--full"
196
- title={t('generate')}
197
- data-generating={isGenerating ? 'true' : 'false'}
198
- >
199
-
200
- </button>
201
- <button
202
- onClick={handleSearch}
203
- className="semiont-reference-button semiont-reference-button--primary semiont-reference-button--full"
204
- title={t('find')}
205
- >
206
- 🔍
207
- </button>
208
- <button
209
- onClick={handleComposeDocument}
210
- className="semiont-reference-button semiont-reference-button--primary semiont-reference-button--full"
211
- title={t('create')}
212
- >
213
- ✏️
214
- </button>
215
- </div>
216
- )
217
- )}
218
- </div>
219
172
  </div>
220
173
  );
221
- });
174
+ }
@@ -61,25 +61,97 @@
61
61
  color: var(--semiont-text-tertiary);
62
62
  }
63
63
 
64
- /* Reference Entry Buttons */
65
- .semiont-reference-button-group {
66
- display: flex;
67
- gap: var(--semiont-panel-gap-sm);
68
- width: 100%;
64
+ /* Reference Icon Group — status icon + hover-reveal unlink */
65
+ .semiont-reference-icon-group {
66
+ position: relative;
67
+ display: inline-flex;
68
+ align-items: center;
69
+ flex-shrink: 0;
69
70
  }
70
71
 
71
- .semiont-reference-button {
72
- display: flex;
72
+ .semiont-reference-icon {
73
+ display: inline-flex;
73
74
  align-items: center;
74
75
  justify-content: center;
75
- padding: var(--semiont-panel-padding-xs) var(--semiont-panel-padding-sm);
76
- font-size: var(--semiont-panel-icon-size);
76
+ width: 1.75rem;
77
+ height: 1.75rem;
78
+ font-size: 0.875rem;
79
+ line-height: 1;
80
+ border-radius: var(--semiont-radius-md);
81
+ border: 1px solid var(--semiont-color-gray-300);
82
+ background: var(--semiont-bg-secondary);
83
+ color: var(--semiont-text-secondary);
84
+ transition: all 0.15s ease;
85
+ }
86
+
87
+ [data-theme="dark"] .semiont-reference-icon {
88
+ border-color: var(--semiont-color-gray-600);
89
+ background: var(--semiont-color-gray-700);
90
+ }
91
+
92
+ .semiont-reference-icon--clickable {
93
+ cursor: pointer;
94
+ }
95
+
96
+ .semiont-reference-icon--clickable:hover {
97
+ background: var(--semiont-gradient-primary);
98
+ border-color: var(--semiont-color-primary-300);
99
+ transform: scale(1.1);
100
+ }
101
+
102
+ [data-theme="dark"] .semiont-reference-icon--clickable:hover {
103
+ background: var(--semiont-gradient-primary-dark);
104
+ border-color: var(--semiont-color-primary-500);
105
+ }
106
+
107
+ .semiont-reference-icon--clickable:focus-visible {
108
+ outline: 2px solid var(--semiont-color-primary-500);
109
+ outline-offset: 2px;
110
+ }
111
+
112
+ [data-theme="dark"] .semiont-reference-icon--clickable:focus-visible {
113
+ outline-color: var(--semiont-color-primary-400);
114
+ }
115
+
116
+ /* Generating state pulse on stub icon */
117
+ .semiont-reference-icon[data-generating="true"] {
118
+ animation: semiont-icon-pulse 1.5s ease-in-out infinite;
119
+ }
120
+
121
+ @keyframes semiont-icon-pulse {
122
+ 0%, 100% { transform: scale(1); }
123
+ 50% { transform: scale(1.2); }
124
+ }
125
+
126
+ /* Hover-reveal unlink button */
127
+ .semiont-reference-unlink {
128
+ position: absolute;
129
+ right: -1.25rem;
130
+ top: 50%;
131
+ transform: translateY(-50%);
132
+ background: none;
133
+ border: none;
134
+ padding: 0;
135
+ font-size: 0.75rem;
136
+ cursor: pointer;
137
+ opacity: 0;
138
+ pointer-events: none;
139
+ transition: opacity 0.15s ease;
140
+ }
141
+
142
+ .semiont-annotation-entry:hover .semiont-reference-unlink {
143
+ opacity: 1;
144
+ pointer-events: auto;
77
145
  }
78
146
 
79
- .semiont-reference-button--full {
80
- width: 100%;
147
+ .semiont-reference-unlink:hover {
148
+ transform: translateY(-50%) scale(1.2);
81
149
  }
82
150
 
83
- .semiont-reference-button--flex {
84
- flex: 1;
151
+ .semiont-reference-unlink:focus-visible {
152
+ opacity: 1;
153
+ pointer-events: auto;
154
+ outline: 2px solid var(--semiont-color-primary-500);
155
+ outline-offset: 2px;
156
+ border-radius: 2px;
85
157
  }
@@ -245,7 +245,7 @@ export function ReferencesPanel({
245
245
  const handleCreateReference = () => {
246
246
  if (pendingAnnotation) {
247
247
  const entityType = pendingEntityTypes.join(',') || undefined;
248
- eventBus.get('mark:create').next({
248
+ eventBus.get('mark:submit').next({
249
249
  motivation: 'linking',
250
250
  selector: pendingAnnotation.selector,
251
251
  body: entityType ? [{ type: 'TextualBody', value: entityType, purpose: 'tagging' }] : [],
@@ -497,8 +497,7 @@ export function ReferencesPanel({
497
497
  {referencedBy.length > 0 ? (
498
498
  <div className="semiont-panel__list">
499
499
  {referencedBy.map((ref) => {
500
- // Extract resource ID from full URI (e.g., "http://localhost:4000/resources/abc123" -> "abc123")
501
- const resourceId = ref.target.source.split('/').pop() || '';
500
+ const resourceId = ref.target.source;
502
501
 
503
502
  return (
504
503
  <div key={ref.id} className="semiont-reference-item semiont-reference-item--incoming">
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { forwardRef } from 'react';
3
+ import type { Ref } from 'react';
4
4
  import type { components } from '@semiont/core';
5
5
  import { getAnnotationExactText } from '@semiont/api-client';
6
6
  import { getTagCategory, getTagSchemaId } from '@semiont/ontology';
@@ -14,17 +14,15 @@ interface TagEntryProps {
14
14
  tag: Annotation;
15
15
  isFocused: boolean;
16
16
  isHovered?: boolean;
17
+ ref?: Ref<HTMLDivElement>;
17
18
  }
18
19
 
19
- export const TagEntry = forwardRef<HTMLDivElement, TagEntryProps>(
20
- function TagEntry(
21
- {
22
- tag,
23
- isFocused,
24
- isHovered = false,
25
- },
26
- ref
27
- ) {
20
+ export function TagEntry({
21
+ tag,
22
+ isFocused,
23
+ isHovered = false,
24
+ ref,
25
+ }: TagEntryProps) {
28
26
  const eventBus = useEventBus();
29
27
  const hoverProps = useHoverEmitter(tag.id);
30
28
 
@@ -62,4 +60,4 @@ export const TagEntry = forwardRef<HTMLDivElement, TagEntryProps>(
62
60
  </div>
63
61
  </div>
64
62
  );
65
- });
63
+ }
@@ -274,7 +274,7 @@ export function TaggingPanel({
274
274
  className="semiont-select"
275
275
  onChange={(e) => {
276
276
  if (e.target.value && pendingAnnotation) {
277
- eventBus.get('mark:create').next({
277
+ eventBus.get('mark:submit').next({
278
278
  motivation: 'tagging',
279
279
  selector: pendingAnnotation.selector,
280
280
  body: [
@@ -29,7 +29,7 @@ function createEventTracker() {
29
29
  events.push({ event: eventName, payload });
30
30
  };
31
31
 
32
- const panelEvents = ['mark:create'] as const;
32
+ const panelEvents = ['mark:submit'] as const;
33
33
 
34
34
  panelEvents.forEach(eventName => {
35
35
  const handler = trackEvent(eventName);
@@ -359,7 +359,7 @@ describe('AssessmentPanel Component', () => {
359
359
  expect(textarea).toHaveFocus();
360
360
  });
361
361
 
362
- it('should emit mark:createevent when save is clicked', async () => {
362
+ it('should emit mark:submitevent when save is clicked', async () => {
363
363
  const tracker = createEventTracker();
364
364
  const pendingAnnotation = createPendingAnnotation('Selected text');
365
365
 
@@ -379,7 +379,7 @@ describe('AssessmentPanel Component', () => {
379
379
 
380
380
  await waitFor(() => {
381
381
  expect(tracker.events.some(e =>
382
- e.event === 'mark:create' &&
382
+ e.event === 'mark:submit' &&
383
383
  e.payload?.motivation === 'assessing' &&
384
384
  e.payload?.body?.[0]?.value === 'My assessment'
385
385
  )).toBe(true);
@@ -420,7 +420,7 @@ describe('AssessmentPanel Component', () => {
420
420
 
421
421
  await waitFor(() => {
422
422
  expect(tracker.events.some(e =>
423
- e.event === 'mark:create' &&
423
+ e.event === 'mark:submit' &&
424
424
  e.payload?.motivation === 'assessing' &&
425
425
  Array.isArray(e.payload?.body) &&
426
426
  e.payload.body.length === 0
@@ -135,7 +135,7 @@ describe('AssistSection', () => {
135
135
 
136
136
  // Form should not be visible
137
137
  expect(screen.queryByPlaceholderText('Enter custom instructions...')).not.toBeInTheDocument();
138
- expect(screen.queryByRole('button', { name: /✨ Annotate/ })).not.toBeInTheDocument();
138
+ expect(screen.queryByRole('button', { name: /✨\s*Annotate/ })).not.toBeInTheDocument();
139
139
  });
140
140
 
141
141
  it('should show form when progress is null', () => {
@@ -149,7 +149,7 @@ describe('AssistSection', () => {
149
149
 
150
150
  // Form should be visible
151
151
  expect(screen.getByPlaceholderText('Enter custom instructions...')).toBeInTheDocument();
152
- expect(screen.getByRole('button', { name: /✨ Annotate/ })).toBeInTheDocument();
152
+ expect(screen.getByRole('button', { name: /✨\s*Annotate/ })).toBeInTheDocument();
153
153
  });
154
154
 
155
155
  it('should show form when progress is undefined', () => {
@@ -163,7 +163,7 @@ describe('AssistSection', () => {
163
163
 
164
164
  // Form should be visible
165
165
  expect(screen.getByPlaceholderText('Enter custom instructions...')).toBeInTheDocument();
166
- expect(screen.getByRole('button', { name: /✨ Annotate/ })).toBeInTheDocument();
166
+ expect(screen.getByRole('button', { name: /✨\s*Annotate/ })).toBeInTheDocument();
167
167
  });
168
168
 
169
169
  it('should keep progress visible after detection completes (isAssisting=false but progress exists)', () => {
@@ -279,7 +279,7 @@ describe('AssistSection', () => {
279
279
 
280
280
  const subscription = eventBus!.get('mark:assist-request').subscribe(detectionHandler);
281
281
 
282
- const annotateButton = screen.getByRole('button', { name: /✨ Annotate/ });
282
+ const annotateButton = screen.getByRole('button', { name: /✨\s*Annotate/ });
283
283
  await user.click(annotateButton);
284
284
 
285
285
  expect(detectionHandler).toHaveBeenCalledWith({
@@ -305,7 +305,7 @@ describe('AssistSection', () => {
305
305
 
306
306
  const subscription = eventBus!.get('mark:assist-request').subscribe(detectionHandler);
307
307
 
308
- const annotateButton = screen.getByRole('button', { name: /✨ Annotate/ });
308
+ const annotateButton = screen.getByRole('button', { name: /✨\s*Annotate/ });
309
309
  await user.click(annotateButton);
310
310
 
311
311
  expect(detectionHandler).toHaveBeenCalledWith({
@@ -331,7 +331,7 @@ describe('AssistSection', () => {
331
331
 
332
332
  const subscription = eventBus!.get('mark:assist-request').subscribe(detectionHandler);
333
333
 
334
- const annotateButton = screen.getByRole('button', { name: /✨ Annotate/ });
334
+ const annotateButton = screen.getByRole('button', { name: /✨\s*Annotate/ });
335
335
  await user.click(annotateButton);
336
336
 
337
337
  expect(detectionHandler).toHaveBeenCalledWith({
@@ -360,7 +360,7 @@ describe('AssistSection', () => {
360
360
  const textarea = screen.getByPlaceholderText('Enter custom instructions...');
361
361
  await user.type(textarea, 'Find key concepts');
362
362
 
363
- const annotateButton = screen.getByRole('button', { name: /✨ Annotate/ });
363
+ const annotateButton = screen.getByRole('button', { name: /✨\s*Annotate/ });
364
364
  await user.click(annotateButton);
365
365
 
366
366
  expect(detectionHandler).toHaveBeenCalledWith({
@@ -29,7 +29,7 @@ function createEventTracker() {
29
29
  events.push({ event: eventName, payload });
30
30
  };
31
31
 
32
- const panelEvents = ['mark:create'] as const;
32
+ const panelEvents = ['mark:submit'] as const;
33
33
 
34
34
  panelEvents.forEach(eventName => {
35
35
  const handler = trackEvent(eventName);
@@ -396,7 +396,7 @@ describe('CommentsPanel Component', () => {
396
396
  expect(textarea).toHaveFocus();
397
397
  });
398
398
 
399
- it('should emit mark:createevent when save is clicked', async () => {
399
+ it('should emit mark:submitevent when save is clicked', async () => {
400
400
  const tracker = createEventTracker();
401
401
  const pendingAnnotation = createPendingAnnotation('Selected text');
402
402
 
@@ -416,7 +416,7 @@ describe('CommentsPanel Component', () => {
416
416
 
417
417
  await waitFor(() => {
418
418
  expect(tracker.events.some(e =>
419
- e.event === 'mark:create' &&
419
+ e.event === 'mark:submit' &&
420
420
  e.payload?.motivation === 'commenting' &&
421
421
  e.payload?.body?.[0]?.value === 'My new comment'
422
422
  )).toBe(true);
@@ -106,7 +106,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
106
106
 
107
107
  // Form should be visible (meaning progress was null)
108
108
  expect(screen.getByPlaceholderText('Enter custom instructions...')).toBeInTheDocument();
109
- expect(screen.getByRole('button', { name: /✨ Annotate/ })).toBeInTheDocument();
109
+ expect(screen.getByRole('button', { name: /✨\s*Annotate/ })).toBeInTheDocument();
110
110
  });
111
111
 
112
112
  it('should pass undefined progress to AssistSection', () => {
@@ -122,7 +122,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
122
122
 
123
123
  // Form should be visible (meaning progress was undefined)
124
124
  expect(screen.getByPlaceholderText('Enter custom instructions...')).toBeInTheDocument();
125
- expect(screen.getByRole('button', { name: /✨ Annotate/ })).toBeInTheDocument();
125
+ expect(screen.getByRole('button', { name: /✨\s*Annotate/ })).toBeInTheDocument();
126
126
  });
127
127
 
128
128
  it('should keep progress visible after detection completes (isAssisting=false)', () => {