@semiont/react-ui 0.2.46 → 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 (214) 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 +351 -107
  39. package/dist/index.mjs +3111 -1811
  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 +4 -7
  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/CommentEntry.tsx +10 -12
  102. package/src/components/resource/panels/HighlightEntry.tsx +9 -11
  103. package/src/components/resource/panels/ReferenceEntry.tsx +57 -104
  104. package/src/components/resource/panels/ReferencesPanel.css +85 -13
  105. package/src/components/resource/panels/ReferencesPanel.tsx +1 -2
  106. package/src/components/resource/panels/TagEntry.tsx +9 -11
  107. package/src/components/resource/panels/__tests__/AssistSection.test.tsx +7 -7
  108. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +2 -2
  109. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +64 -101
  110. package/src/components/resource/panels/__tests__/StatisticsPanel.test.tsx +1 -1
  111. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +3 -3
  112. package/src/components/viewers/ImageViewer.tsx +3 -6
  113. package/src/components/viewers/__tests__/ImageViewer.test.tsx +3 -3
  114. package/src/features/admin-devops/__tests__/AdminDevOpsPage.test.tsx +5 -5
  115. package/src/features/admin-exchange/__tests__/AdminExchangePage.test.tsx +141 -0
  116. package/src/features/admin-exchange/__tests__/ExportCard.test.tsx +41 -0
  117. package/src/features/admin-exchange/__tests__/ImportCard.test.tsx +148 -0
  118. package/src/features/admin-exchange/__tests__/ImportProgress.test.tsx +106 -0
  119. package/src/features/admin-exchange/components/AdminExchangePage.tsx +120 -0
  120. package/src/features/admin-exchange/components/ExportCard.tsx +35 -0
  121. package/src/features/admin-exchange/components/ImportCard.tsx +188 -0
  122. package/src/features/admin-exchange/components/ImportProgress.tsx +86 -0
  123. package/src/features/admin-security/__tests__/AdminSecurityPage.test.tsx +3 -3
  124. package/src/features/moderate-entity-tags/__tests__/EntityTagsPage.test.tsx +2 -2
  125. package/src/features/moderate-recent/__tests__/RecentDocumentsPage.test.tsx +4 -4
  126. package/src/features/moderate-tag-schemas/__tests__/TagSchemasPage.test.tsx +3 -3
  127. package/src/features/moderation-linked-data/__tests__/LinkedDataPage.test.tsx +117 -0
  128. package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +121 -0
  129. package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +5 -5
  130. package/src/features/resource-compose/components/ResourceComposePage.tsx +56 -1
  131. package/src/features/resource-discovery/__tests__/ResourceCard.test.tsx +1 -1
  132. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +2 -2
  133. package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +3 -3
  134. package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +11 -10
  135. package/src/features/resource-viewer/__tests__/AnnotationProgressDismissal.test.tsx +2 -2
  136. package/src/features/resource-viewer/__tests__/BindFlowIntegration.test.tsx +22 -115
  137. package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +3 -3
  138. package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +20 -20
  139. package/src/features/resource-viewer/__tests__/ResourceMutations.test.tsx +7 -7
  140. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +5 -21
  141. package/src/features/resource-viewer/__tests__/ToastNotifications.test.tsx +2 -2
  142. package/src/features/resource-viewer/__tests__/YieldFlowIntegration.test.tsx +45 -82
  143. package/src/features/resource-viewer/__tests__/annotation-progress-flow.test.tsx +4 -4
  144. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +144 -72
  145. package/src/integrations/tailwind-plugin.js +3 -3
  146. package/src/styles/core/buttons.css +31 -0
  147. package/src/styles/features/exchange.css +404 -0
  148. package/src/styles/index.css +1 -0
  149. package/translations/ar.json +42 -4
  150. package/translations/bn.json +42 -4
  151. package/translations/cs.json +42 -4
  152. package/translations/da.json +128 -90
  153. package/translations/de.json +122 -84
  154. package/translations/el.json +42 -4
  155. package/translations/en.json +42 -4
  156. package/translations/es.json +42 -4
  157. package/translations/fa.json +42 -4
  158. package/translations/fi.json +68 -30
  159. package/translations/fr.json +42 -4
  160. package/translations/he.json +42 -4
  161. package/translations/hi.json +42 -4
  162. package/translations/id.json +43 -5
  163. package/translations/it.json +62 -24
  164. package/translations/ja.json +43 -5
  165. package/translations/ko.json +42 -4
  166. package/translations/ms.json +43 -5
  167. package/translations/nl.json +41 -3
  168. package/translations/no.json +104 -66
  169. package/translations/pl.json +42 -4
  170. package/translations/pt.json +43 -5
  171. package/translations/ro.json +42 -4
  172. package/translations/sv.json +42 -4
  173. package/translations/th.json +42 -4
  174. package/translations/tr.json +42 -4
  175. package/translations/uk.json +42 -4
  176. package/translations/vi.json +42 -4
  177. package/translations/zh.json +42 -4
  178. package/dist/PdfAnnotationCanvas.client-COQREPXU.mjs.map +0 -1
  179. package/dist/ar-7SUXNE34.mjs.map +0 -1
  180. package/dist/bn-XOET3DOI.mjs.map +0 -1
  181. package/dist/chunk-JH7BXE2P.mjs.map +0 -1
  182. package/dist/cs-X63DXX7L.mjs.map +0 -1
  183. package/dist/da-OWTCV57A.mjs.map +0 -1
  184. package/dist/de-77BMFDVF.mjs.map +0 -1
  185. package/dist/el-FIBNLH2V.mjs.map +0 -1
  186. package/dist/es-726NTS53.mjs.map +0 -1
  187. package/dist/fa-3N4CIWE6.mjs.map +0 -1
  188. package/dist/fi-JOM3M7Z4.mjs.map +0 -1
  189. package/dist/fr-56QSXS7E.mjs.map +0 -1
  190. package/dist/he-SNAXPJEK.mjs.map +0 -1
  191. package/dist/hi-CRBRD5TB.mjs.map +0 -1
  192. package/dist/id-BRCVLICF.mjs.map +0 -1
  193. package/dist/it-M2Z27BNB.mjs.map +0 -1
  194. package/dist/ja-TZUKW7HD.mjs.map +0 -1
  195. package/dist/ko-NKBGGOL6.mjs.map +0 -1
  196. package/dist/ms-XFXPN6RX.mjs.map +0 -1
  197. package/dist/nl-MVYXAS5C.mjs.map +0 -1
  198. package/dist/no-XOLO4JPV.mjs.map +0 -1
  199. package/dist/pl-TRWLMMC4.mjs.map +0 -1
  200. package/dist/pt-M3TE24UI.mjs.map +0 -1
  201. package/dist/ro-QBFG2T64.mjs.map +0 -1
  202. package/dist/sv-IUECBXWX.mjs.map +0 -1
  203. package/dist/th-US7KIN5Q.mjs.map +0 -1
  204. package/dist/tr-DWJ2FFUK.mjs.map +0 -1
  205. package/dist/uk-M4ZE4DPZ.mjs.map +0 -1
  206. package/dist/zh-3J2I3WYK.mjs.map +0 -1
  207. package/src/examples/ButtonUsageExample.tsx +0 -242
  208. package/src/examples/button-css-modules.module.css +0 -164
  209. package/src/examples/button-styled-components.tsx +0 -215
  210. package/src/examples/button-tailwind.css +0 -51
  211. /package/dist/{chunk-Q2KV6Y2J.mjs.map → chunk-7GEYABC6.mjs.map} +0 -0
  212. /package/dist/{chunk-3JTO27MH.mjs.map → chunk-D4GAAQMM.mjs.map} +0 -0
  213. /package/dist/{en-XWEPVTB4.mjs.map → en-HAKDCFKL.mjs.map} +0 -0
  214. /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
  }
@@ -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
+ }
@@ -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({
@@ -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)', () => {