@semiont/react-ui 0.5.26 → 0.5.28

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 (242) hide show
  1. package/README.md +28 -4
  2. package/dist/{PdfAnnotationCanvas.client-6ZSF6ZTJ.js → PdfAnnotationCanvas.client-C2IGG4SW.js} +33 -6
  3. package/dist/{PdfAnnotationCanvas.client-JCJMXWWJ.css → PdfAnnotationCanvas.client-TGDPPGXJ.css} +6 -1
  4. package/dist/{ar-YTUUGK2O.js → ar-FABPTJVB.js} +88 -26
  5. package/dist/{bn-ASGNGPMD.js → bn-LKP6BDMH.js} +88 -26
  6. package/dist/{chunk-L3WKRWP3.js → chunk-2GOHTTHW.js} +88 -26
  7. package/dist/{chunk-OBQOZCJE.js → chunk-2YHEE63S.js} +31 -31
  8. package/dist/{cs-WMYVAOZY.js → cs-YQYNWHRG.js} +88 -26
  9. package/dist/{da-FSHHN4D6.js → da-QHZOYKRY.js} +88 -26
  10. package/dist/{de-FJFO3TPT.js → de-GKA7RD7J.js} +88 -26
  11. package/dist/{el-XOH4QIAG.js → el-45T257AL.js} +88 -26
  12. package/dist/{en-3FCBW3YD.js → en-RVW5EQ66.js} +4 -2
  13. package/dist/{es-HDX4QI7E.js → es-AVER7RU4.js} +88 -26
  14. package/dist/{fa-DJ6WTFTJ.js → fa-K3VPK3O2.js} +88 -26
  15. package/dist/{fi-4S7H7W7U.js → fi-5NEKF7EB.js} +88 -26
  16. package/dist/{fr-6UJSYHXC.js → fr-FUMNDLBZ.js} +88 -26
  17. package/dist/{he-ZZJOHJX2.js → he-3JGNA3PR.js} +88 -26
  18. package/dist/{hi-HZJTAW7U.js → hi-IJY375DF.js} +88 -26
  19. package/dist/{id-E3I6ILEM.js → id-LWHKJ7ZD.js} +88 -26
  20. package/dist/index.css +380 -524
  21. package/dist/index.d.ts +318 -192
  22. package/dist/index.js +2960 -2421
  23. package/dist/integrations/css-modules-helper.js +1 -3
  24. package/dist/{it-ULAPAD7U.js → it-EBVOV4RW.js} +88 -26
  25. package/dist/{ja-SAHZQ4CZ.js → ja-KWRIX2NF.js} +88 -26
  26. package/dist/{ko-DFU2ADQ4.js → ko-L5CJMFDS.js} +88 -26
  27. package/dist/{ms-NWL76XNT.js → ms-LSNMJDR2.js} +88 -26
  28. package/dist/{nl-ATCM76WN.js → nl-ZB32MB7U.js} +88 -26
  29. package/dist/{no-UIAQ5RJF.js → no-4GTNY7M5.js} +88 -26
  30. package/dist/{pl-NOECQDXD.js → pl-LUHBLTHB.js} +88 -26
  31. package/dist/{pt-SVWIIUPC.js → pt-C4H3UNQ6.js} +88 -26
  32. package/dist/{ro-NR2DRTZG.js → ro-GGITS5V4.js} +88 -26
  33. package/dist/{sv-STVJ37JP.js → sv-T56RTLBP.js} +88 -26
  34. package/dist/test-utils.css +1 -1
  35. package/dist/test-utils.d.ts +1 -1
  36. package/dist/test-utils.js +2 -2
  37. package/dist/{th-UWQR4K54.js → th-22X2IWCB.js} +88 -26
  38. package/dist/{tr-WNXZSD27.js → tr-77TFG62H.js} +88 -26
  39. package/dist/{uk-SJRMB47Y.js → uk-MIIL6NXT.js} +88 -26
  40. package/dist/{vi-TMRQFES2.js → vi-7JYOIGNV.js} +88 -26
  41. package/dist/{zh-QOL3LYEM.js → zh-ID6DRYU4.js} +88 -26
  42. package/package.json +11 -10
  43. package/src/components/AssistProgress.tsx +178 -134
  44. package/src/components/CodeMirrorRenderer.tsx +8 -8
  45. package/src/components/{EntityFoundLog.tsx → ItemFoundLog.tsx} +12 -7
  46. package/src/components/ProtectedErrorBoundary.css +3 -1
  47. package/src/components/StatusDisplay.css +1 -1
  48. package/src/components/Toast.css +3 -1
  49. package/src/components/__tests__/AssistProgress.test.tsx +268 -141
  50. package/src/components/annotation/annotation-entries.css +0 -4
  51. package/src/components/annotation/references.css +0 -4
  52. package/src/components/image-annotation/AnnotationOverlay.tsx +6 -6
  53. package/src/components/image-annotation/SvgDrawingCanvas.tsx +1 -1
  54. package/src/components/image-annotation/__tests__/AnnotationOverlay.click.test.tsx +7 -9
  55. package/src/components/image-annotation/__tests__/SvgDrawingCanvas.click.test.tsx +1 -2
  56. package/src/components/layout/Header.css +0 -4
  57. package/src/components/layout/LeftSidebar.css +0 -4
  58. package/src/components/modals/ComposeStep.tsx +218 -0
  59. package/src/components/modals/ConfigureGatherStep.tsx +10 -18
  60. package/src/components/modals/ConfigureGenerationStep.tsx +131 -54
  61. package/src/components/modals/ConfigureSearchStep.tsx +42 -40
  62. package/src/components/modals/ContextSummary.tsx +185 -72
  63. package/src/components/modals/CorpusPane.tsx +75 -0
  64. package/src/components/modals/DiscardPrompt.tsx +38 -0
  65. package/src/components/modals/GatherContextStep.tsx +237 -107
  66. package/src/components/modals/ReferenceWizardModal.tsx +287 -60
  67. package/src/components/modals/ResourceGenerateModal.tsx +233 -109
  68. package/src/components/modals/SearchModal.css +335 -111
  69. package/src/components/modals/SearchModal.tsx +1 -1
  70. package/src/components/modals/SearchResultsStep.tsx +63 -88
  71. package/src/components/modals/WizardFooter.tsx +69 -0
  72. package/src/components/modals/__tests__/ComposeStep.test.tsx +152 -0
  73. package/src/components/modals/__tests__/ConfigureGatherStep.test.tsx +14 -26
  74. package/src/components/modals/__tests__/ConfigureGenerationStep.test.tsx +290 -0
  75. package/src/components/modals/__tests__/ConfigureSearchStep.test.tsx +95 -0
  76. package/src/components/modals/__tests__/GatherContextStep.resource.test.tsx +463 -25
  77. package/src/components/modals/__tests__/ReferenceWizardModal.test.tsx +635 -0
  78. package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +277 -85
  79. package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +6 -2
  80. package/src/components/modals/__tests__/SearchResultsStep.test.tsx +61 -0
  81. package/src/components/modals/__tests__/WizardFooter.test.tsx +344 -0
  82. package/src/components/modals/modals.css +16 -42
  83. package/src/components/navigation/NavigationTabs.css +0 -20
  84. package/src/components/pdf-annotation/PdfAnnotationCanvas.css +21 -2
  85. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +36 -3
  86. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +41 -6
  87. package/src/components/resource/AnnotateView.tsx +19 -8
  88. package/src/components/resource/AnnotationHistory.tsx +2 -0
  89. package/src/components/resource/BrowseView.tsx +20 -9
  90. package/src/components/resource/ResourceViewer.tsx +27 -28
  91. package/src/components/resource/__tests__/AnnotateView.embeddable.test.tsx +1 -1
  92. package/src/components/resource/__tests__/AnnotateView.focus.test.tsx +50 -0
  93. package/src/components/resource/__tests__/AnnotationHistory.test.tsx +2 -0
  94. package/src/components/resource/__tests__/BrowseView.embeddable.test.tsx +1 -1
  95. package/src/components/resource/__tests__/BrowseView.test.tsx +38 -12
  96. package/src/components/resource/__tests__/ResourceViewer.embeddable.test.tsx +47 -3
  97. package/src/components/resource/annotate-renderers.tsx +3 -3
  98. package/src/components/resource/event-formatting.ts +0 -3
  99. package/src/components/resource/panels/AssessmentEntry.tsx +1 -1
  100. package/src/components/resource/panels/AssessmentPanel.css +5 -6
  101. package/src/components/resource/panels/AssistSection.css +95 -263
  102. package/src/components/resource/panels/AssistSection.tsx +4 -4
  103. package/src/components/resource/panels/AssistShell.tsx +12 -4
  104. package/src/components/resource/panels/CollaborationPanel.css +47 -22
  105. package/src/components/resource/panels/CollaborationPanel.tsx +93 -2
  106. package/src/components/resource/panels/CommentEntry.tsx +1 -1
  107. package/src/components/resource/panels/CommentsPanel.css +0 -16
  108. package/src/components/resource/panels/HighlightEntry.tsx +1 -1
  109. package/src/components/resource/panels/HighlightPanel.css +0 -7
  110. package/src/components/resource/panels/JsonLdPanel.css +0 -4
  111. package/src/components/resource/panels/ReferenceEntry.tsx +10 -3
  112. package/src/components/resource/panels/ReferencesPanel.css +0 -12
  113. package/src/components/resource/panels/ReferencesPanel.tsx +16 -20
  114. package/src/components/resource/panels/ResourceInfoPanel.css +0 -32
  115. package/src/components/resource/panels/ResourceInfoPanel.tsx +89 -20
  116. package/src/components/resource/panels/StatisticsPanel.css +0 -20
  117. package/src/components/resource/panels/TagEntry.tsx +1 -1
  118. package/src/components/resource/panels/TaggingPanel.css +0 -4
  119. package/src/components/resource/panels/TaggingPanel.tsx +2 -10
  120. package/src/components/resource/panels/UnifiedAnnotationsPanel.css +0 -4
  121. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +7 -2
  122. package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +0 -1
  123. package/src/components/resource/panels/__tests__/AssistSection.test.tsx +48 -50
  124. package/src/components/resource/panels/__tests__/AssistShell.test.tsx +11 -6
  125. package/src/components/resource/panels/__tests__/CollaborationPanel.test.tsx +89 -0
  126. package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +1 -2
  127. package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +0 -1
  128. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +44 -36
  129. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +41 -1
  130. package/src/components/resource/panels/__tests__/ReferencesPanel.headless.test.tsx +1 -1
  131. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +67 -54
  132. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +185 -7
  133. package/src/components/resource/panels/__tests__/TagEntry.test.tsx +0 -1
  134. package/src/components/settings/SettingsPanel.css +0 -4
  135. package/src/features/auth/auth.css +2 -26
  136. package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +0 -65
  137. package/src/features/resource-compose/components/ResourceComposePage.tsx +17 -130
  138. package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +1 -76
  139. package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -48
  140. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +55 -0
  141. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +29 -0
  142. package/src/features/resource-discovery/state/__tests__/discover-state-unit.test.ts +53 -3
  143. package/src/features/resource-discovery/state/discover-state-unit.ts +29 -8
  144. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +71 -15
  145. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +166 -71
  146. package/src/features/resource-viewer/hooks/__tests__/useResourceViewedReport.test.tsx +63 -0
  147. package/src/features/resource-viewer/hooks/useResourceViewedReport.ts +33 -0
  148. package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +2 -2
  149. package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +1 -1
  150. package/src/styles/core/buttons.css +3 -1
  151. package/src/styles/core/forms.css +35 -29
  152. package/src/styles/core/indicators.css +0 -4
  153. package/src/styles/core/inputs.css +0 -4
  154. package/src/styles/core/progress.css +2 -34
  155. package/src/styles/core/sliders.css +0 -4
  156. package/src/styles/core/toggles.css +0 -4
  157. package/src/styles/features/resource-discovery.css +13 -0
  158. package/src/styles/features/resource-viewer.css +0 -42
  159. package/src/styles/features/resource.css +0 -4
  160. package/src/styles/motivations/motivation-assessment.css +2 -2
  161. package/src/styles/panels/history-panel.css +0 -40
  162. package/src/styles/panels/user-panel.css +0 -3
  163. package/src/styles/patterns/panel-helpers.css +0 -27
  164. package/src/styles/patterns/panel-sections.css +0 -59
  165. package/src/styles/patterns/panels-base.css +0 -32
  166. package/translations/ar.json +84 -24
  167. package/translations/bn.json +84 -24
  168. package/translations/cs.json +84 -24
  169. package/translations/da.json +84 -24
  170. package/translations/de.json +84 -24
  171. package/translations/el.json +84 -24
  172. package/translations/en.json +84 -24
  173. package/translations/es.json +84 -24
  174. package/translations/fa.json +84 -24
  175. package/translations/fi.json +84 -24
  176. package/translations/fr.json +84 -24
  177. package/translations/he.json +84 -24
  178. package/translations/hi.json +84 -24
  179. package/translations/id.json +84 -24
  180. package/translations/it.json +84 -24
  181. package/translations/ja.json +84 -24
  182. package/translations/ko.json +84 -24
  183. package/translations/ms.json +84 -24
  184. package/translations/nl.json +84 -24
  185. package/translations/no.json +84 -24
  186. package/translations/pl.json +84 -24
  187. package/translations/pt.json +84 -24
  188. package/translations/ro.json +84 -24
  189. package/translations/sv.json +84 -24
  190. package/translations/th.json +84 -24
  191. package/translations/tr.json +84 -24
  192. package/translations/uk.json +84 -24
  193. package/translations/vi.json +84 -24
  194. package/translations/zh.json +84 -24
  195. package/dist/PdfAnnotationCanvas.client-6ZSF6ZTJ.js.map +0 -1
  196. package/dist/PdfAnnotationCanvas.client-JCJMXWWJ.css.map +0 -1
  197. package/dist/ar-YTUUGK2O.js.map +0 -1
  198. package/dist/bn-ASGNGPMD.js.map +0 -1
  199. package/dist/chunk-BTUG3VJJ.js.map +0 -1
  200. package/dist/chunk-L3WKRWP3.js.map +0 -1
  201. package/dist/chunk-O23TLLXW.js.map +0 -1
  202. package/dist/chunk-OBQOZCJE.js.map +0 -1
  203. package/dist/chunk-QT7LYM72.js.map +0 -1
  204. package/dist/chunk-VXASXU4K.js.map +0 -1
  205. package/dist/chunk-XUDKYAVC.js +0 -21
  206. package/dist/chunk-XUDKYAVC.js.map +0 -1
  207. package/dist/cs-WMYVAOZY.js.map +0 -1
  208. package/dist/da-FSHHN4D6.js.map +0 -1
  209. package/dist/de-FJFO3TPT.js.map +0 -1
  210. package/dist/el-XOH4QIAG.js.map +0 -1
  211. package/dist/en-3FCBW3YD.js.map +0 -1
  212. package/dist/es-HDX4QI7E.js.map +0 -1
  213. package/dist/fa-DJ6WTFTJ.js.map +0 -1
  214. package/dist/fi-4S7H7W7U.js.map +0 -1
  215. package/dist/fr-6UJSYHXC.js.map +0 -1
  216. package/dist/he-ZZJOHJX2.js.map +0 -1
  217. package/dist/hi-HZJTAW7U.js.map +0 -1
  218. package/dist/id-E3I6ILEM.js.map +0 -1
  219. package/dist/index.css.map +0 -1
  220. package/dist/index.js.map +0 -1
  221. package/dist/integrations/css-modules-helper.js.map +0 -1
  222. package/dist/integrations/styled-components-theme.js.map +0 -1
  223. package/dist/it-ULAPAD7U.js.map +0 -1
  224. package/dist/ja-SAHZQ4CZ.js.map +0 -1
  225. package/dist/ko-DFU2ADQ4.js.map +0 -1
  226. package/dist/ms-NWL76XNT.js.map +0 -1
  227. package/dist/nl-ATCM76WN.js.map +0 -1
  228. package/dist/no-UIAQ5RJF.js.map +0 -1
  229. package/dist/pl-NOECQDXD.js.map +0 -1
  230. package/dist/pt-SVWIIUPC.js.map +0 -1
  231. package/dist/ro-NR2DRTZG.js.map +0 -1
  232. package/dist/sv-STVJ37JP.js.map +0 -1
  233. package/dist/test-utils.css.map +0 -1
  234. package/dist/test-utils.js.map +0 -1
  235. package/dist/th-UWQR4K54.js.map +0 -1
  236. package/dist/tr-WNXZSD27.js.map +0 -1
  237. package/dist/uk-SJRMB47Y.js.map +0 -1
  238. package/dist/vi-TMRQFES2.js.map +0 -1
  239. package/dist/zh-QOL3LYEM.js.map +0 -1
  240. package/src/components/modals/ResourceSearchModal.tsx +0 -224
  241. package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +0 -159
  242. package/src/integrations/tailwind-plugin.cjs +0 -224
@@ -23,200 +23,327 @@ import { AssistProgress, type AssistProgressTranslations } from '../AssistProgre
23
23
 
24
24
  type JobProgress = components['schemas']['JobProgress'];
25
25
 
26
- const running = (over: Partial<JobProgress> = {}): JobProgress => ({
27
- stage: 'analyzing', percentage: 40, message: 'working on it', ...over,
28
- });
29
-
30
- /** Every required key, echoing its own name; override per test. */
31
- const T = (over: Partial<AssistProgressTranslations> = {}): AssistProgressTranslations => ({
32
- cancel: 'tr.cancel',
33
- inProgress: 'tr.inProgress',
34
- complete: 'tr.complete',
35
- failed: 'tr.failed',
36
- close: 'tr.close',
37
- paramsTitle: 'tr.paramsTitle',
38
- processing: (label: string) => `tr.processing(${label})`,
39
- ...over,
40
- });
41
-
42
26
  describe('AssistProgress', () => {
43
- it('renders provider-free: message, params, and data hooks', () => {
27
+ // Trimmed by P3 (2026-08-12). Five tests were DELETED rather than adapted
28
+ // because they pinned surfaces this phase removes, and each has a successor
29
+ // in the P3 block below:
30
+ // • paramsTitle copy → H1 (the label is gone; the line is conditional)
31
+ // • "title header only when given one" → A2 (the section header is the title)
32
+ // • "cancel in the header / hidden once complete" → A3 ×2 (one control)
33
+ // • "stage branching" → A8 + A3-ended (D7: no producer emits a
34
+ // terminal stage; terminality is a prop)
35
+ // • "percentage bar only when opted in" → A4 (a bar follows the data, not a flag)
36
+ // What survives here is what P3 does NOT change.
37
+
38
+ it('renders provider-free — no session, no context, no providers', () => {
39
+ // The embeddable contract: this must render standalone. If it ever reaches
40
+ // for a provider, this throws rather than silently degrading.
44
41
  const { container } = render(
45
- <AssistProgress
46
- progress={running({ requestParams: [{ label: 'Density', value: '5' }] })}
47
- dataType="comment"
48
- translations={T()}
42
+ <AssistProgress ended={false} progress={detecting()} dataType="reference" translations={T3()} />,
43
+ );
44
+ expect(container.querySelector('.semiont-assist-progress')).toBeInTheDocument();
45
+ expect(container.querySelector('[data-type="reference"]')).toBeInTheDocument();
46
+ });
47
+
48
+ it('renders the completed entity-type log when data + formatter are present', () => {
49
+ render(
50
+ <AssistProgress ended={false}
51
+ progress={detecting({
52
+ completedItems: [{ value: 'Person', foundCount: 3 }],
53
+ })}
54
+ dataType="reference"
55
+ translations={T3()}
49
56
  />,
50
57
  );
51
- expect(screen.getByText('working on it')).toBeInTheDocument();
52
- expect(screen.getByText(/Density/)).toBeInTheDocument();
53
- expect(screen.getByText('5')).toBeInTheDocument();
54
- const root = container.querySelector('.semiont-annotation-progress');
55
- expect(root).toHaveAttribute('data-type', 'comment');
56
- expect(root).toHaveAttribute('data-status', 'analyzing');
58
+ expect(screen.getByText('Person:')).toBeInTheDocument();
59
+ expect(screen.getByText('tr.found(3)')).toBeInTheDocument();
57
60
  });
58
61
 
59
- it('renders the request-parameters title from translations, never a literal', () => {
62
+ it('omits the entity-type log when the formatter is absent (non-reference flows)', () => {
63
+ const tr = T3();
64
+ delete (tr as Partial<AssistProgressTranslations>).found;
60
65
  render(
61
66
  <AssistProgress
62
- progress={running({ requestParams: [{ label: 'Density', value: '5' }] })}
67
+ progress={detecting({ completedItems: [{ value: 'Person', foundCount: 3 }] })}
63
68
  dataType="comment"
64
- translations={T()}
69
+ ended={false}
70
+ translations={tr}
65
71
  />,
66
72
  );
67
- expect(screen.getByText('tr.paramsTitle')).toBeInTheDocument();
68
- expect(screen.queryByText('Request Parameters:')).not.toBeInTheDocument();
73
+ expect(screen.queryByText('Person:')).toBeNull();
69
74
  });
70
75
 
71
- it('renders a title header only when given one', () => {
76
+ it('the control takes its accessible name from translations, per lifecycle', () => {
72
77
  const { rerender } = render(
73
- <AssistProgress progress={running()} dataType="reference" translations={T({ title: 'Annotating Entity References' })} />,
78
+ <AssistProgress ended={false}
79
+ progress={detecting()} dataType="reference"
80
+ onCancel={vi.fn()} onDismiss={vi.fn()} translations={T3()}
81
+ />,
74
82
  );
75
- expect(screen.getByText('Annotating Entity References')).toBeInTheDocument();
76
- rerender(<AssistProgress progress={running()} dataType="comment" translations={T()} />);
77
- expect(screen.queryByText('Annotating Entity References')).not.toBeInTheDocument();
78
- });
83
+ expect(screen.getByLabelText('tr.cancel')).toBeInTheDocument();
79
84
 
80
- it('shows cancel in the header while running, hides it once complete', async () => {
81
- // Cancel lives in the title header — both flows that offer cancel
82
- // (reference detection, generation) render the titled profile.
83
- const onCancel = vi.fn();
84
- const tr = T({ title: 'Generating Resource', cancel: 'Cancel Job' });
85
- const { rerender } = render(
86
- <AssistProgress progress={running()} dataType="generation" onCancel={onCancel} translations={tr} />,
87
- );
88
- await userEvent.click(screen.getByTitle('Cancel Job'));
89
- expect(onCancel).toHaveBeenCalledOnce();
90
- rerender(
91
- <AssistProgress progress={running({ stage: 'complete' })} dataType="generation" onCancel={onCancel} translations={tr} />,
92
- );
93
- expect(screen.queryByTitle('Cancel Job')).not.toBeInTheDocument();
94
- // Error is terminal too — offering cancel on a dead job is misleading and
95
- // invites redundant cancel requests.
96
85
  rerender(
97
- <AssistProgress progress={running({ stage: 'error', message: 'it broke' })} dataType="generation" onCancel={onCancel} translations={tr} />,
86
+ <AssistProgress
87
+ progress={detecting()} dataType="reference" ended
88
+ onCancel={vi.fn()} onDismiss={vi.fn()} translations={T3()}
89
+ />,
98
90
  );
99
- expect(screen.queryByTitle('Cancel Job')).not.toBeInTheDocument();
91
+ expect(screen.getByLabelText('tr.close')).toBeInTheDocument();
100
92
  });
101
93
 
102
- it('stage branching: both terminal stages prefer the job message, then the translated copy', () => {
103
- // The job composes detail no static string can ("Created 14 highlights"),
104
- // so it wins on BOTH terminal stages; the translated copy covers the
105
- // required-but-empty message case (pinned below).
106
- const { rerender } = render(
107
- <AssistProgress progress={running({ stage: 'complete' })} dataType="reference" translations={T({ complete: 'All done!' })} />,
108
- );
109
- expect(screen.getByText('working on it')).toBeInTheDocument();
110
- rerender(
111
- <AssistProgress progress={running({ stage: 'complete', message: '' })} dataType="reference" translations={T({ complete: 'All done!' })} />,
94
+ it('offers no control at all when the caller wires neither callback', () => {
95
+ render(<AssistProgress ended={false} progress={detecting()} dataType="reference" translations={T3()} />);
96
+ expect(screen.queryByTestId('semiont-assist-control')).toBeNull();
97
+ });
98
+
99
+ it('falls back to the generic in-progress copy when no code has arrived', () => {
100
+ // `JobProgress.message` is optional: a pure liveness heartbeat carries none.
101
+ const noCode = { percentage: 5 } as JobProgress;
102
+ render(<AssistProgress ended={false} progress={noCode} dataType="comment" translations={T3()} />);
103
+ expect(screen.getByTestId('semiont-assist-status').textContent).toBe('tr.inProgress');
104
+ });
105
+ });
106
+
107
+ // ─────────────────────────────────────────────────────────────────────────────
108
+ // ASSIST-PROGRESS-CONSOLIDATION P3 — the RED wave (A1-A5, A8).
109
+ //
110
+ // These assert STRUCTURE, never copy: H3 set the wording ("Marking…", subject
111
+ // beneath) and it will be revised from use. A test pinned to a sentence rots on
112
+ // the first edit and teaches the next reader to weaken it.
113
+ // ─────────────────────────────────────────────────────────────────────────────
114
+ const STATUS = 'semiont-assist-status';
115
+ const SUBJECT = 'semiont-assist-subject';
116
+ const CONTROL = 'semiont-assist-control';
117
+ const BAR = 'semiont-assist-bar';
118
+ const PARAMS = 'semiont-assist-params';
119
+
120
+ /** Post-P3 translations: one function for the coded copy, plus structure keys. */
121
+ const T3 = (over: Partial<AssistProgressTranslations> = {}): AssistProgressTranslations =>
122
+ ({
123
+ cancel: 'tr.cancel',
124
+ close: 'tr.close',
125
+ inProgress: 'tr.inProgress',
126
+ message: (m: any) => `tr.code(${m.code})`,
127
+ subject: (current: { kind: string; value: string }, done?: number, total?: number) =>
128
+ done === undefined
129
+ ? `tr.subject(${current.kind}:${current.value})`
130
+ : `tr.subject(${current.kind}:${current.value}|${done}/${total})`,
131
+ paramLabel: (code: string) => `tr.param(${code})`,
132
+ found: (n: number) => `tr.found(${n})`,
133
+ ...over,
134
+ }) as AssistProgressTranslations;
135
+
136
+ const detecting = (over: Partial<JobProgress> = {}): JobProgress =>
137
+ ({
138
+ percentage: 40,
139
+ message: { code: 'detecting-entities', entityType: 'Person' },
140
+ current: { kind: 'entity-type', value: 'Person' },
141
+ processed: 1,
142
+ total: 3,
143
+ ...over,
144
+ }) as JobProgress;
145
+
146
+ describe('AssistProgress — P3 consolidation', () => {
147
+ it('A1: renders the subject exactly once for one progress event', () => {
148
+ // Defect 1: the status line and the detail line both called
149
+ // `currentLabel(currentEntityType)`. Post-P1 they produce the IDENTICAL
150
+ // string, which is why two tests here had to use getAllByText. Singular
151
+ // `getByText` throws on multiple matches — that IS the assertion.
152
+ render(<AssistProgress progress={detecting()} dataType="reference" ended={false} translations={T3()} />);
153
+
154
+ expect(screen.getByText(/tr\.subject\(entity-type:Person/)).toBeInTheDocument();
155
+ // And the status line is the CODE's copy, not a second copy of the subject.
156
+ expect(screen.getByTestId(STATUS).textContent).toContain('tr.code(detecting-entities)');
157
+ expect(screen.getByTestId(STATUS).textContent).not.toContain('tr.subject');
158
+ });
159
+
160
+ it('A2: renders no heading of its own — the section header is the title', () => {
161
+ const { container } = render(
162
+ <AssistProgress ended={false} progress={detecting()} dataType="reference" translations={T3()} />,
112
163
  );
113
- expect(screen.getByText('All done!')).toBeInTheDocument();
114
- rerender(
115
- <AssistProgress progress={running({ stage: 'error', message: 'it broke' })} dataType="reference" translations={T({ failed: 'Failed' })} />,
164
+ expect(container.querySelector('h1,h2,h3,h4,h5,h6')).toBeNull();
165
+ });
166
+
167
+ it('A3: offers exactly one control, and it means cancel while running', async () => {
168
+ const onCancel = vi.fn();
169
+ const onDismiss = vi.fn();
170
+ render(
171
+ <AssistProgress ended={false}
172
+ progress={detecting()} dataType="reference"
173
+ onCancel={onCancel} onDismiss={onDismiss} translations={T3()}
174
+ />,
116
175
  );
117
- expect(screen.getByText('it broke')).toBeInTheDocument();
176
+ const controls = screen.getAllByTestId(CONTROL);
177
+ expect(controls).toHaveLength(1);
178
+
179
+ await userEvent.click(controls[0]!);
180
+ expect(onCancel).toHaveBeenCalledTimes(1);
181
+ expect(onDismiss).not.toHaveBeenCalled();
118
182
  });
119
183
 
120
- it('renders the completed entity-type log when data + formatter are present', () => {
184
+ it('A3: the same single control means dismiss once the run has ENDED', async () => {
185
+ // D7: terminality is the owner's fact, arriving as a prop. The component
186
+ // never reads `progress.stage` — no producer emits a terminal stage.
187
+ const onCancel = vi.fn();
188
+ const onDismiss = vi.fn();
121
189
  render(
122
190
  <AssistProgress
123
- progress={running({ completedEntityTypes: [{ entityType: 'Person', foundCount: 3 }] })}
124
- dataType="reference" translations={T({ found: (n) => `Found ${n}` })}
191
+ progress={detecting({ message: { code: 'complete-created', count: 7, kind: 'reference' } } as any)}
192
+ dataType="reference" ended
193
+ onCancel={onCancel} onDismiss={onDismiss} translations={T3()}
125
194
  />,
126
195
  );
127
- expect(screen.getByText('Person:')).toBeInTheDocument();
128
- expect(screen.getByText('Found 3')).toBeInTheDocument();
196
+ const controls = screen.getAllByTestId(CONTROL);
197
+ expect(controls).toHaveLength(1);
198
+
199
+ await userEvent.click(controls[0]!);
200
+ expect(onDismiss).toHaveBeenCalledTimes(1);
201
+ expect(onCancel).not.toHaveBeenCalled();
129
202
  });
130
203
 
131
- it('renders the current-work detail line: entity type via formatter, category with counts', () => {
132
- const { rerender } = render(
133
- <AssistProgress
134
- progress={running({ currentEntityType: 'Location' })}
135
- dataType="reference" translations={T({ current: (l) => `Current: ${l}` })}
204
+ it('A4: the REFERENCE flow renders a fraction and a bar data it already receives', () => {
205
+ render(<AssistProgress ended={false} progress={detecting()} dataType="reference" translations={T3()} />);
206
+
207
+ expect(screen.getByTestId(SUBJECT).textContent).toBe('tr.subject(entity-type:Person|1/3)');
208
+ expect(screen.getByTestId(BAR)).toBeInTheDocument();
209
+ });
210
+
211
+ it('A4: the TAG flow gets a bar from percentage alone — it sends no fraction', () => {
212
+ // The REAL tag shape. `processTagJob` emits percentage and nothing else:
213
+ // no currentCategory, no processed, no total. An
214
+ // earlier version of this test invented those fields and so "passed" while
215
+ // the tag flow had silently lost its bar (PR #1179 review).
216
+ render(
217
+ <AssistProgress ended={false}
218
+ progress={{
219
+ percentage: 60,
220
+ message: { code: 'creating-tag-annotations', count: 4 },
221
+ } as JobProgress}
222
+ dataType="tag" translations={T3()}
136
223
  />,
137
224
  );
138
- expect(screen.getByText('Current: Location')).toBeInTheDocument();
139
- rerender(
140
- <AssistProgress
141
- progress={running({ currentCategory: 'Rule', processedCategories: 2, totalCategories: 5 })}
142
- dataType="tag" translations={T()}
225
+ expect(screen.getByTestId(BAR)).toBeInTheDocument();
226
+ // No fraction to show, so no subject line — and that is correct, not a gap.
227
+ expect(screen.queryByTestId(SUBJECT)).toBeNull();
228
+ });
229
+
230
+ it('A4: the bar is unconditional — percentage is required on every event', () => {
231
+ // Replaces a test that asserted "no bar when nothing fills it". That state
232
+ // is unreachable: `percentage` is a REQUIRED field on JobProgress, so there
233
+ // is always something to fill a bar with. Asserting an impossible case is
234
+ // how the tag regression hid.
235
+ render(
236
+ <AssistProgress ended={false}
237
+ progress={{ percentage: 10, message: { code: 'loading' } } as JobProgress}
238
+ dataType="comment" translations={T3()}
143
239
  />,
144
240
  );
145
- expect(screen.getByText(/Rule/)).toBeInTheDocument();
146
- expect(screen.getByText(/2\/5/)).toBeInTheDocument();
241
+ expect(screen.getByTestId(BAR)).toBeInTheDocument();
147
242
  });
148
243
 
149
- it('falls back to the generic processing formatter — never the English literal', () => {
150
- // The reference flow passes its own `current` wording; every other flow
151
- // (tag categories, and any flow without one) uses `processing`. Neither
152
- // path may render a hardcoded "Processing: ".
153
- const { rerender } = render(
154
- <AssistProgress progress={running({ currentEntityType: 'Location' })} dataType="reference" translations={T()} />,
244
+ it('A5: every rendered string is traceable to translations', () => {
245
+ const { container } = render(
246
+ <AssistProgress ended={false} progress={detecting()} dataType="reference" translations={T3()} />,
155
247
  );
156
- expect(screen.getByText('tr.processing(Location)')).toBeInTheDocument();
157
- rerender(
158
- <AssistProgress progress={running({ currentCategory: 'Rule' })} dataType="tag" translations={T()} />,
248
+ // Key-echo strings mean any text NOT starting `tr.` came from the component.
249
+ const stray = Array.from(container.querySelectorAll('*'))
250
+ .filter((el) => el.children.length === 0)
251
+ .map((el) => el.textContent?.trim() ?? '')
252
+ .filter((t) => t.length > 0 && !t.startsWith('tr.') && !/^[\s✨✅✓×✕()0-9/of]+$/.test(t));
253
+ expect(stray).toEqual([]);
254
+ });
255
+
256
+ it('A8: renders no failure UI of its own — job:fail owns that surface', () => {
257
+ // Revised by D7. `stage: 'error'` has no producer anywhere in the repo;
258
+ // failure reaches the user through useOutcomeToasts' job:fail handler.
259
+ // The widget must not resurrect a branch for a state it never sees.
260
+ render(
261
+ <AssistProgress ended={false}
262
+ progress={detecting({ stage: 'error' } as Partial<JobProgress>)}
263
+ dataType="reference" translations={T3()}
264
+ />,
159
265
  );
160
- expect(screen.getByText(/tr\.processing\(Rule\)/)).toBeInTheDocument();
161
- expect(screen.queryByText(/^Processing: /)).not.toBeInTheDocument();
266
+ // Still the ordinary running render — no special-cased error text.
267
+ expect(screen.getByTestId(STATUS).textContent).toContain('tr.code(detecting-entities)');
162
268
  });
163
269
 
164
- it('renders the percentage bar only when opted in', () => {
165
- const { container, rerender } = render(
166
- <AssistProgress progress={running({ percentage: 40 })} dataType="tag" showPercentBar translations={T()} />,
270
+ it('the outcome link renders only in the ended frame (GENERATE-FROM-RESOURCE P2, D8)', async () => {
271
+ // The label is the resource's name — user content, deliberately NOT a
272
+ // translation. While the run is live there is no outcome to offer, even if
273
+ // a caller wires the prop early.
274
+ const onOpen = vi.fn();
275
+ const outcome = { label: 'Summary of PB', onOpen };
276
+ const { rerender } = render(
277
+ <AssistProgress ended={false} progress={detecting()} dataType="generation"
278
+ outcome={outcome} translations={T3()} />,
167
279
  );
168
- const fill = container.querySelector('.semiont-progress-bar__fill');
169
- expect(fill).toBeInTheDocument();
170
- expect(fill).toHaveStyle({ width: '40%' });
280
+ expect(screen.queryByText('Summary of PB')).toBeNull();
281
+
171
282
  rerender(
172
- <AssistProgress progress={running({ percentage: 40 })} dataType="reference" translations={T()} />,
283
+ <AssistProgress ended progress={detecting()} dataType="generation"
284
+ outcome={outcome} translations={T3()} />,
173
285
  );
174
- expect(container.querySelector('.semiont-progress-bar__fill')).not.toBeInTheDocument();
286
+ await userEvent.click(screen.getByRole('button', { name: 'Summary of PB' }));
287
+ expect(onOpen).toHaveBeenCalledTimes(1);
175
288
  });
176
289
 
177
- it('cancel and dismiss take their accessible names from translations', () => {
178
- // / × glyphs alone are meaningless to screen readers, and an English
179
- // fallback is meaningless to a non-English reader. The labels are
180
- // required keys, so there is nothing to fall back TO.
181
- render(
182
- <AssistProgress progress={running()} dataType="generation"
183
- onCancel={vi.fn()} onDismiss={vi.fn()}
184
- translations={T({ title: 'Generating' })} />,
290
+ it('H1: the params line appears only when it adds information', () => {
291
+ // The discriminator is the COUNT, not the copy splitting a localized
292
+ // string on commas to decide whether to show it would be its own defect.
293
+ const oneType = detecting({
294
+ requestParams: [{ label: 'entity-types', value: 'Person' }],
295
+ total: 1,
296
+ });
297
+ const { unmount } = render(
298
+ <AssistProgress ended={false} progress={oneType} dataType="reference" translations={T3()} />,
185
299
  );
186
- expect(screen.getByLabelText('tr.cancel')).toBeInTheDocument();
187
- expect(screen.getByLabelText('tr.close')).toBeInTheDocument();
188
- expect(screen.queryByLabelText('Cancel')).not.toBeInTheDocument();
189
- expect(screen.queryByLabelText('Dismiss')).not.toBeInTheDocument();
300
+ expect(screen.queryByTestId(PARAMS)).toBeNull();
301
+ unmount();
302
+
303
+ const many = detecting({
304
+ requestParams: [{ label: 'entity-types', value: 'Person, Organization, Location' }],
305
+ total: 3,
306
+ });
307
+ render(<AssistProgress ended={false} progress={many} dataType="reference" translations={T3()} />);
308
+ expect(screen.getByTestId(PARAMS)).toBeInTheDocument();
190
309
  });
310
+ });
191
311
 
192
- it('terminal stages never render a blank status line, and never an English one', () => {
193
- // JobProgress.message is required but can be '' a terminal display with
194
- // no text at all is worse than a generic word, but that word must be the
195
- // caller's translated one.
196
- const { rerender } = render(
197
- <AssistProgress progress={running({ stage: 'complete', message: '' })} dataType="reference" translations={T()} />,
312
+ // ─────────────────────────────────────────────────────────────────────────────
313
+ // GENERATION-ARRIVAL P1 the honest ended frame. The producer's terminal
314
+ // 100% frame races job:complete and can lose (its emit is a fire-and-forget
315
+ // heartbeat); terminality is the OWNER's fact (D7), so the ended rendering
316
+ // stops trusting the last payload: full bar, and the owner's terminal
317
+ // sentence when it supplies one.
318
+ // ─────────────────────────────────────────────────────────────────────────────
319
+ describe('AssistProgress — the honest ended frame (GENERATION-ARRIVAL P1)', () => {
320
+ it('A1: an ended frame renders a FULL bar whatever the last payload said', () => {
321
+ const { container } = render(
322
+ <AssistProgress ended progress={detecting({ percentage: 95 })} dataType="generation" translations={T3()} />,
198
323
  );
199
- expect(screen.getByText('tr.complete')).toBeInTheDocument();
200
- rerender(
201
- <AssistProgress progress={running({ stage: 'error', message: '' })} dataType="reference" translations={T()} />,
324
+ const fill = container.querySelector('.semiont-progress-bar__fill') as HTMLElement;
325
+ expect(fill.style.width).toBe('100%');
326
+ });
327
+
328
+ it('a live frame keeps the payload percentage', () => {
329
+ const { container } = render(
330
+ <AssistProgress ended={false} progress={detecting({ percentage: 95 })} dataType="generation" translations={T3()} />,
202
331
  );
203
- expect(screen.getByText('tr.failed')).toBeInTheDocument();
332
+ const fill = container.querySelector('.semiont-progress-bar__fill') as HTMLElement;
333
+ expect(fill.style.width).toBe('95%');
204
334
  });
205
335
 
206
- it('renders dismiss whenever the caller offers it (WHEN is the caller\'s policy)', async () => {
207
- // AssistShell withholds onDismiss while the assist is running — that
208
- // gate is pinned in AssistShell.test; here the contract is just
209
- // "callback present → affordance rendered".
210
- const onDismiss = vi.fn();
211
- const { rerender } = render(
212
- <AssistProgress progress={running()} dataType="highlight" translations={T({ close: 'Close' })} />,
336
+ it('endedMessage replaces the stale payload copy once ended', () => {
337
+ render(
338
+ <AssistProgress ended endedMessage="tr.ended" progress={detecting({ percentage: 95 })} dataType="generation" translations={T3()} />,
213
339
  );
214
- expect(screen.queryByLabelText('Close')).not.toBeInTheDocument();
215
- rerender(
216
- <AssistProgress progress={running()} dataType="highlight"
217
- onDismiss={onDismiss} translations={T({ close: 'Close' })} />,
340
+ expect(screen.getByTestId(STATUS).textContent).toBe('tr.ended');
341
+ });
342
+
343
+ it('endedMessage is inert while the run is live', () => {
344
+ render(
345
+ <AssistProgress ended={false} endedMessage="tr.ended" progress={detecting()} dataType="generation" translations={T3()} />,
218
346
  );
219
- await userEvent.click(screen.getByLabelText('Close'));
220
- expect(onDismiss).toHaveBeenCalledOnce();
347
+ expect(screen.getByTestId(STATUS).textContent).toBe('tr.code(detecting-entities)');
221
348
  });
222
349
  });
@@ -133,10 +133,6 @@
133
133
  color: var(--semiont-text-tertiary);
134
134
  }
135
135
 
136
- [data-theme="dark"] .semiont-annotation-entry__char-count {
137
- color: var(--semiont-text-tertiary);
138
- }
139
-
140
136
  /* Tags within entries */
141
137
  .semiont-annotation-entry__tags {
142
138
  display: flex;
@@ -105,10 +105,6 @@
105
105
  background-color: var(--semiont-bg-tertiary);
106
106
  }
107
107
 
108
- [data-theme="dark"] .semiont-chip--selectable[data-selected="false"]:hover {
109
- background-color: var(--semiont-bg-tertiary);
110
- }
111
-
112
108
  .semiont-chip--selectable[data-selected="true"] {
113
109
  background-color: rgba(59, 130, 246, 0.1);
114
110
  color: var(--semiont-color-blue-700);
@@ -128,7 +128,7 @@ export function AnnotationOverlay({
128
128
  className="semiont-annotation-overlay__shape"
129
129
  data-hovered={isHovered ? 'true' : 'false'}
130
130
  data-selected={isSelected ? 'true' : 'false'}
131
- onClick={(e) => session?.client.browse.click(annotation.id, annotation.motivation, e.currentTarget.getBoundingClientRect())}
131
+ onClick={(e) => session?.client.browse.click(annotation.id, e.currentTarget.getBoundingClientRect())}
132
132
  onMouseEnter={() => handleMouseEnter(annotation.id)}
133
133
  onMouseLeave={handleMouseLeave}
134
134
  />
@@ -141,7 +141,7 @@ export function AnnotationOverlay({
141
141
  style={{ userSelect: 'none' }}
142
142
  onClick={(e) => {
143
143
  e.stopPropagation();
144
- session?.client.browse.click(annotation.id, annotation.motivation, e.currentTarget.getBoundingClientRect());
144
+ session?.client.browse.click(annotation.id, e.currentTarget.getBoundingClientRect());
145
145
  }}
146
146
  onMouseEnter={() => handleMouseEnter(annotation.id)}
147
147
  onMouseLeave={handleMouseLeave}
@@ -175,7 +175,7 @@ export function AnnotationOverlay({
175
175
  className="semiont-annotation-overlay__shape"
176
176
  data-hovered={isHovered ? 'true' : 'false'}
177
177
  data-selected={isSelected ? 'true' : 'false'}
178
- onClick={(e) => session?.client.browse.click(annotation.id, annotation.motivation, e.currentTarget.getBoundingClientRect())}
178
+ onClick={(e) => session?.client.browse.click(annotation.id, e.currentTarget.getBoundingClientRect())}
179
179
  onMouseEnter={() => handleMouseEnter(annotation.id)}
180
180
  onMouseLeave={handleMouseLeave}
181
181
  />
@@ -188,7 +188,7 @@ export function AnnotationOverlay({
188
188
  style={{ userSelect: 'none' }}
189
189
  onClick={(e) => {
190
190
  e.stopPropagation();
191
- session?.client.browse.click(annotation.id, annotation.motivation, e.currentTarget.getBoundingClientRect());
191
+ session?.client.browse.click(annotation.id, e.currentTarget.getBoundingClientRect());
192
192
  }}
193
193
  onMouseEnter={() => handleMouseEnter(annotation.id)}
194
194
  onMouseLeave={handleMouseLeave}
@@ -235,7 +235,7 @@ export function AnnotationOverlay({
235
235
  className="semiont-annotation-overlay__shape"
236
236
  data-hovered={isHovered ? 'true' : 'false'}
237
237
  data-selected={isSelected ? 'true' : 'false'}
238
- onClick={(e) => session?.client.browse.click(annotation.id, annotation.motivation, e.currentTarget.getBoundingClientRect())}
238
+ onClick={(e) => session?.client.browse.click(annotation.id, e.currentTarget.getBoundingClientRect())}
239
239
  onMouseEnter={() => handleMouseEnter(annotation.id)}
240
240
  onMouseLeave={handleMouseLeave}
241
241
  />
@@ -248,7 +248,7 @@ export function AnnotationOverlay({
248
248
  style={{ userSelect: 'none' }}
249
249
  onClick={(e) => {
250
250
  e.stopPropagation();
251
- session?.client.browse.click(annotation.id, annotation.motivation, e.currentTarget.getBoundingClientRect());
251
+ session?.client.browse.click(annotation.id, e.currentTarget.getBoundingClientRect());
252
252
  }}
253
253
  onMouseEnter={() => handleMouseEnter(annotation.id)}
254
254
  onMouseLeave={handleMouseLeave}
@@ -221,7 +221,7 @@ export function SvgDrawingCanvas({
221
221
  });
222
222
 
223
223
  if (clickedAnnotation) {
224
- session?.client.browse.click(clickedAnnotation.id, clickedAnnotation.motivation, hitRect);
224
+ session?.client.browse.click(clickedAnnotation.id, hitRect);
225
225
  setIsDrawing(false);
226
226
  setStartPoint(null);
227
227
  setCurrentPoint(null);
@@ -2,7 +2,7 @@
2
2
  * A1 anchor thread (HEADLESS-ANNOTATION-PANELS Phase 3) — image overlay pin.
3
3
  *
4
4
  * The overlay's shape elements own their on-screen geometry: a click passes
5
- * the element's viewport rect as browse.click's third argument so hosts can
5
+ * the element's viewport rect as browse.click's second argument so hosts can
6
6
  * anchor popovers. Runtime-only view geometry; no schema involvement.
7
7
  */
8
8
  import { describe, it, expect, vi } from 'vitest';
@@ -43,7 +43,7 @@ function sessionDouble() {
43
43
  }
44
44
 
45
45
  describe('AnnotationOverlay — anchorRect on click', () => {
46
- it('passes the shape element rect as browse.click third argument', () => {
46
+ it('passes the shape element rect as browse.click second argument', () => {
47
47
  const { session, click } = sessionDouble();
48
48
 
49
49
  const { container } = render(
@@ -65,8 +65,7 @@ describe('AnnotationOverlay — anchorRect on click', () => {
65
65
 
66
66
  expect(click).toHaveBeenCalledTimes(1);
67
67
  expect(click.mock.calls[0]?.[0]).toBe('img-ann-1');
68
- expect(click.mock.calls[0]?.[1]).toBe('highlighting');
69
- const anchorRect = click.mock.calls[0]?.[2];
68
+ const anchorRect = click.mock.calls[0]?.[1];
70
69
  expect(anchorRect).toBeDefined();
71
70
  expect(typeof anchorRect.width).toBe('number');
72
71
  expect(typeof anchorRect.left).toBe('number');
@@ -77,7 +76,7 @@ describe('AnnotationOverlay — anchorRect on click', () => {
77
76
  it.each([
78
77
  ['circle', svgAnnotation('img-circle-1', 'commenting', '<svg><circle cx="30" cy="30" r="10"/></svg>')],
79
78
  ['polygon', svgAnnotation('img-polygon-1', 'assessing', '<svg><polygon points="10,10 30,10 20,30"/></svg>')],
80
- ] as const)('passes the %s element rect as browse.click third argument', (_kind, annotation) => {
79
+ ] as const)('passes the %s element rect as browse.click second argument', (_kind, annotation) => {
81
80
  const { session, click } = sessionDouble();
82
81
 
83
82
  const { container } = render(
@@ -99,12 +98,12 @@ describe('AnnotationOverlay — anchorRect on click', () => {
99
98
 
100
99
  expect(click).toHaveBeenCalledTimes(1);
101
100
  expect(click.mock.calls[0]?.[0]).toBe(annotation.id);
102
- const anchorRect = click.mock.calls[0]?.[2];
101
+ const anchorRect = click.mock.calls[0]?.[1];
103
102
  expect(anchorRect).toBeDefined();
104
103
  expect(typeof anchorRect.width).toBe('number');
105
104
  });
106
105
 
107
- it('passes the status-indicator element rect as browse.click third argument', () => {
106
+ it('passes the status-indicator element rect as browse.click second argument', () => {
108
107
  // Only references render a status indicator (🔗/❓).
109
108
  const reference = svgAnnotation('img-ref-1', 'linking', '<svg><rect x="10" y="10" width="20" height="20"/></svg>');
110
109
  const { session, click } = sessionDouble();
@@ -129,8 +128,7 @@ describe('AnnotationOverlay — anchorRect on click', () => {
129
128
  // stopPropagation: the indicator's own handler emits, exactly once.
130
129
  expect(click).toHaveBeenCalledTimes(1);
131
130
  expect(click.mock.calls[0]?.[0]).toBe('img-ref-1');
132
- expect(click.mock.calls[0]?.[1]).toBe('linking');
133
- const anchorRect = click.mock.calls[0]?.[2];
131
+ const anchorRect = click.mock.calls[0]?.[1];
134
132
  expect(anchorRect).toBeDefined();
135
133
  expect(typeof anchorRect.width).toBe('number');
136
134
  });
@@ -100,8 +100,7 @@ describe('SvgDrawingCanvas — drawing-path hit-test anchorRect', () => {
100
100
 
101
101
  expect(click).toHaveBeenCalledTimes(1);
102
102
  expect(click.mock.calls[0]?.[0]).toBe('canvas-ann-1');
103
- expect(click.mock.calls[0]?.[1]).toBe('highlighting');
104
- expect(click.mock.calls[0]?.[2]).toEqual({
103
+ expect(click.mock.calls[0]?.[1]).toEqual({
105
104
  x: 20, y: 20, left: 20, top: 20, width: 40, height: 40, right: 60, bottom: 60,
106
105
  });
107
106
  });