@semiont/react-ui 0.5.23 → 0.5.25

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 (190) hide show
  1. package/dist/{PdfAnnotationCanvas.client-V7Q3BEEQ.js → PdfAnnotationCanvas.client-EHQWF6BR.js} +45 -10
  2. package/dist/PdfAnnotationCanvas.client-EHQWF6BR.js.map +1 -0
  3. package/dist/{ar-JRTKCMTF.js → ar-CULRHLNG.js} +20 -6
  4. package/dist/ar-CULRHLNG.js.map +1 -0
  5. package/dist/{bn-LO6EDSCC.js → bn-HYR7CEL2.js} +20 -6
  6. package/dist/bn-HYR7CEL2.js.map +1 -0
  7. package/dist/{chunk-5Y5JDCKC.js → chunk-NH6L2GIL.js} +20 -6
  8. package/dist/chunk-NH6L2GIL.js.map +1 -0
  9. package/dist/{chunk-RO5CRVIA.js → chunk-TG2XQEC4.js} +92 -60
  10. package/dist/chunk-TG2XQEC4.js.map +1 -0
  11. package/dist/{cs-LCHHOR4O.js → cs-YBRWY7NH.js} +20 -6
  12. package/dist/cs-YBRWY7NH.js.map +1 -0
  13. package/dist/{da-TARGPMJR.js → da-PHDEOBV3.js} +20 -6
  14. package/dist/da-PHDEOBV3.js.map +1 -0
  15. package/dist/{de-SBH4QBPJ.js → de-O4UCCVLJ.js} +20 -6
  16. package/dist/de-O4UCCVLJ.js.map +1 -0
  17. package/dist/{el-2D2OLGWB.js → el-MVGO6WZV.js} +20 -6
  18. package/dist/el-MVGO6WZV.js.map +1 -0
  19. package/dist/{en-QFIEG3CA.js → en-2KQILFKQ.js} +4 -2
  20. package/dist/{es-U6YK2LX6.js → es-USSLXQY7.js} +20 -6
  21. package/dist/es-USSLXQY7.js.map +1 -0
  22. package/dist/{fa-S7SZE37M.js → fa-WMAREFWY.js} +20 -6
  23. package/dist/fa-WMAREFWY.js.map +1 -0
  24. package/dist/{fi-CBL3NIXN.js → fi-B3NBNOTS.js} +20 -6
  25. package/dist/fi-B3NBNOTS.js.map +1 -0
  26. package/dist/{fr-EABUHV6S.js → fr-TXYK5T5W.js} +20 -6
  27. package/dist/fr-TXYK5T5W.js.map +1 -0
  28. package/dist/{he-THWD244Y.js → he-JCJACW5D.js} +20 -6
  29. package/dist/he-JCJACW5D.js.map +1 -0
  30. package/dist/{hi-GHGFZJ44.js → hi-VOQLZNJU.js} +20 -6
  31. package/dist/hi-VOQLZNJU.js.map +1 -0
  32. package/dist/{id-POZOUMDW.js → id-BFN5RZNZ.js} +20 -6
  33. package/dist/id-BFN5RZNZ.js.map +1 -0
  34. package/dist/index.css +0 -36
  35. package/dist/index.css.map +1 -1
  36. package/dist/index.d.ts +102 -53
  37. package/dist/index.js +2132 -2097
  38. package/dist/index.js.map +1 -1
  39. package/dist/{it-KDYJADSH.js → it-TWWDHKKM.js} +20 -6
  40. package/dist/it-TWWDHKKM.js.map +1 -0
  41. package/dist/{ja-Z5Z6ZF3P.js → ja-YRQLEBFM.js} +20 -6
  42. package/dist/ja-YRQLEBFM.js.map +1 -0
  43. package/dist/{ko-7X3XUXRN.js → ko-PG6B3CRB.js} +20 -6
  44. package/dist/ko-PG6B3CRB.js.map +1 -0
  45. package/dist/{ms-BTWEIW43.js → ms-7EZECVKM.js} +20 -6
  46. package/dist/ms-7EZECVKM.js.map +1 -0
  47. package/dist/{nl-IS2CGF5I.js → nl-HP753YAP.js} +20 -6
  48. package/dist/nl-HP753YAP.js.map +1 -0
  49. package/dist/{no-AAMEVA26.js → no-JUD5WX3N.js} +20 -6
  50. package/dist/no-JUD5WX3N.js.map +1 -0
  51. package/dist/{pl-PVXUQYFT.js → pl-RVUL4CBM.js} +20 -6
  52. package/dist/pl-RVUL4CBM.js.map +1 -0
  53. package/dist/{pt-3ITQ76TL.js → pt-RPOVD4QM.js} +20 -6
  54. package/dist/pt-RPOVD4QM.js.map +1 -0
  55. package/dist/{ro-FRB4IIVS.js → ro-DMYWQTRD.js} +20 -6
  56. package/dist/ro-DMYWQTRD.js.map +1 -0
  57. package/dist/{sv-DS3RO24M.js → sv-DMZ4FGXD.js} +20 -6
  58. package/dist/sv-DMZ4FGXD.js.map +1 -0
  59. package/dist/test-utils.js +4 -3
  60. package/dist/test-utils.js.map +1 -1
  61. package/dist/{th-AX66MWWB.js → th-4HVBJBQ2.js} +20 -6
  62. package/dist/th-4HVBJBQ2.js.map +1 -0
  63. package/dist/{tr-7KTB67TT.js → tr-6BDPU5ML.js} +20 -6
  64. package/dist/tr-6BDPU5ML.js.map +1 -0
  65. package/dist/{uk-SMQS2MAQ.js → uk-TBZBT2TL.js} +20 -6
  66. package/dist/uk-TBZBT2TL.js.map +1 -0
  67. package/dist/{vi-D3JXL2EZ.js → vi-UMKZKZL3.js} +20 -6
  68. package/dist/vi-UMKZKZL3.js.map +1 -0
  69. package/dist/{zh-S754AWFS.js → zh-YBJWJJYB.js} +20 -6
  70. package/dist/zh-YBJWJJYB.js.map +1 -0
  71. package/package.json +8 -8
  72. package/src/components/AssistProgress.tsx +42 -30
  73. package/src/components/EntityFoundLog.tsx +38 -0
  74. package/src/components/__tests__/AssistProgress.test.tsx +86 -28
  75. package/src/components/annotation-popups/JsonLdView.tsx +1 -1
  76. package/src/components/annotation-popups/__tests__/JsonLdView.test.tsx +8 -2
  77. package/src/components/modals/ReferenceWizardModal.tsx +2 -1
  78. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +112 -12
  79. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +247 -1
  80. package/src/components/resource/AnnotateView.tsx +14 -7
  81. package/src/components/resource/BrowseView.tsx +2 -7
  82. package/src/components/resource/DownloadFileLink.tsx +49 -0
  83. package/src/components/resource/__tests__/AnnotateView.focus.test.tsx +58 -0
  84. package/src/components/resource/__tests__/download-link.test.tsx +129 -0
  85. package/src/components/resource/panels/AssistSection.css +0 -43
  86. package/src/components/resource/panels/AssistSection.tsx +11 -2
  87. package/src/components/resource/panels/AssistShell.tsx +5 -1
  88. package/src/components/resource/panels/JsonLdPanel.tsx +1 -1
  89. package/src/components/resource/panels/ReferencesPanel.tsx +12 -13
  90. package/src/components/resource/panels/TaggingPanel.tsx +21 -1
  91. package/src/components/resource/panels/__tests__/AssistSection.test.tsx +2 -2
  92. package/src/components/resource/panels/__tests__/AssistShell.test.tsx +9 -4
  93. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +1 -1
  94. package/src/components/resource/panels/__tests__/JsonLdPanel.test.tsx +8 -2
  95. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +26 -0
  96. package/src/components/settings/SettingsPanel.tsx +6 -2
  97. package/src/components/settings/__tests__/SettingsPanel.test.tsx +28 -3
  98. package/src/features/admin-devops/__tests__/AdminDevOpsPage.test.tsx +3 -11
  99. package/src/features/admin-devops/components/AdminDevOpsPage.tsx +0 -3
  100. package/src/features/admin-exchange/__tests__/AdminExchangePage.test.tsx +0 -1
  101. package/src/features/admin-exchange/components/AdminExchangePage.tsx +0 -3
  102. package/src/features/admin-exchange/state/__tests__/exchange-state-unit.test.ts +1 -1
  103. package/src/features/admin-security/__tests__/AdminSecurityPage.test.tsx +0 -8
  104. package/src/features/admin-security/components/AdminSecurityPage.tsx +0 -3
  105. package/src/features/admin-security/state/__tests__/admin-security-state-unit.test.ts +1 -1
  106. package/src/features/admin-users/__tests__/AdminUsersPage.test.tsx +0 -1
  107. package/src/features/admin-users/components/AdminUsersPage.tsx +0 -3
  108. package/src/features/admin-users/state/__tests__/admin-users-state-unit.test.ts +1 -1
  109. package/src/features/auth-welcome/state/__tests__/welcome-state-unit.test.ts +1 -1
  110. package/src/features/moderate-entity-tags/__tests__/EntityTagsPage.test.tsx +0 -2
  111. package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +0 -3
  112. package/src/features/moderate-entity-tags/state/__tests__/entity-tags-state-unit.test.ts +1 -1
  113. package/src/features/moderate-recent/__tests__/RecentDocumentsPage.test.tsx +3 -5
  114. package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +0 -3
  115. package/src/features/moderate-tag-schemas/__tests__/TagSchemasPage.test.tsx +3 -5
  116. package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +0 -3
  117. package/src/features/moderation-linked-data/__tests__/LinkedDataPage.test.tsx +0 -1
  118. package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +0 -3
  119. package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +17 -7
  120. package/src/features/resource-compose/components/ResourceComposePage.tsx +5 -3
  121. package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +1 -1
  122. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +0 -1
  123. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +0 -3
  124. package/src/features/resource-discovery/state/__tests__/discover-state-unit.test.ts +1 -1
  125. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +40 -8
  126. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +20 -16
  127. package/src/features/resource-viewer/state/__tests__/resource-loader-state-unit.test.ts +1 -1
  128. package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +1 -1
  129. package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +2 -2
  130. package/translations/ar.json +17 -5
  131. package/translations/bn.json +17 -5
  132. package/translations/cs.json +17 -5
  133. package/translations/da.json +17 -5
  134. package/translations/de.json +17 -5
  135. package/translations/el.json +17 -5
  136. package/translations/en.json +17 -5
  137. package/translations/es.json +17 -5
  138. package/translations/fa.json +17 -5
  139. package/translations/fi.json +17 -5
  140. package/translations/fr.json +17 -5
  141. package/translations/he.json +17 -5
  142. package/translations/hi.json +17 -5
  143. package/translations/id.json +17 -5
  144. package/translations/it.json +17 -5
  145. package/translations/ja.json +17 -5
  146. package/translations/ko.json +17 -5
  147. package/translations/ms.json +17 -5
  148. package/translations/nl.json +17 -5
  149. package/translations/no.json +17 -5
  150. package/translations/pl.json +17 -5
  151. package/translations/pt.json +17 -5
  152. package/translations/ro.json +17 -5
  153. package/translations/sv.json +17 -5
  154. package/translations/th.json +17 -5
  155. package/translations/tr.json +17 -5
  156. package/translations/uk.json +17 -5
  157. package/translations/vi.json +17 -5
  158. package/translations/zh.json +17 -5
  159. package/dist/PdfAnnotationCanvas.client-V7Q3BEEQ.js.map +0 -1
  160. package/dist/ar-JRTKCMTF.js.map +0 -1
  161. package/dist/bn-LO6EDSCC.js.map +0 -1
  162. package/dist/chunk-5Y5JDCKC.js.map +0 -1
  163. package/dist/chunk-RO5CRVIA.js.map +0 -1
  164. package/dist/cs-LCHHOR4O.js.map +0 -1
  165. package/dist/da-TARGPMJR.js.map +0 -1
  166. package/dist/de-SBH4QBPJ.js.map +0 -1
  167. package/dist/el-2D2OLGWB.js.map +0 -1
  168. package/dist/es-U6YK2LX6.js.map +0 -1
  169. package/dist/fa-S7SZE37M.js.map +0 -1
  170. package/dist/fi-CBL3NIXN.js.map +0 -1
  171. package/dist/fr-EABUHV6S.js.map +0 -1
  172. package/dist/he-THWD244Y.js.map +0 -1
  173. package/dist/hi-GHGFZJ44.js.map +0 -1
  174. package/dist/id-POZOUMDW.js.map +0 -1
  175. package/dist/it-KDYJADSH.js.map +0 -1
  176. package/dist/ja-Z5Z6ZF3P.js.map +0 -1
  177. package/dist/ko-7X3XUXRN.js.map +0 -1
  178. package/dist/ms-BTWEIW43.js.map +0 -1
  179. package/dist/nl-IS2CGF5I.js.map +0 -1
  180. package/dist/no-AAMEVA26.js.map +0 -1
  181. package/dist/pl-PVXUQYFT.js.map +0 -1
  182. package/dist/pt-3ITQ76TL.js.map +0 -1
  183. package/dist/ro-FRB4IIVS.js.map +0 -1
  184. package/dist/sv-DS3RO24M.js.map +0 -1
  185. package/dist/th-AX66MWWB.js.map +0 -1
  186. package/dist/tr-7KTB67TT.js.map +0 -1
  187. package/dist/uk-SMQS2MAQ.js.map +0 -1
  188. package/dist/vi-D3JXL2EZ.js.map +0 -1
  189. package/dist/zh-S754AWFS.js.map +0 -1
  190. /package/dist/{en-QFIEG3CA.js.map → en-2KQILFKQ.js.map} +0 -0
@@ -19,8 +19,12 @@ export interface AssistShellProps {
19
19
  * Pass-through config for the progress renderer (cancel/dismiss wiring,
20
20
  * translations, percent bar). Dismiss policy lives HERE: the shell forwards
21
21
  * `onDismiss` only once the assist is no longer running.
22
+ *
23
+ * Required because `translations` is: the shell renders the progress display
24
+ * itself, so a caller that omitted this would render untranslated chrome —
25
+ * the failure Lane A exists to make impossible.
22
26
  */
23
- progressProps?: Omit<AssistProgressProps, 'progress' | 'dataType'>;
27
+ progressProps: Omit<AssistProgressProps, 'progress' | 'dataType'>;
24
28
  }
25
29
 
26
30
  /**
@@ -7,7 +7,7 @@ import { json } from '@codemirror/lang-json';
7
7
  import { oneDark } from '@codemirror/theme-one-dark';
8
8
  import { syntaxHighlighting } from '@codemirror/language';
9
9
  import { jsonLightTheme, jsonLightHighlightStyle } from '../../../lib/codemirror-json-theme';
10
- import { useLineNumbers } from '../../../hooks/useLineNumbers';
10
+ import { useLineNumbers } from '../../../contexts/LineNumbersContext';
11
11
  import { useResourceGraph } from '../../../hooks/useResourceGraph';
12
12
  import type { ResourceId } from '@semiont/core';
13
13
  import './JsonLdPanel.css';
@@ -6,6 +6,7 @@ import type { SemiontSession } from '@semiont/sdk';
6
6
  import { useSessionEventSubscriptions } from '../../../hooks/useSessionEventSubscriptions';
7
7
  import type { RouteBuilder, LinkComponentProps } from '../../../contexts/RoutingContext';
8
8
  import { AssistShell } from './AssistShell';
9
+ import { EntityFoundLog } from '../../EntityFoundLog';
9
10
  import { ReferenceEntry } from './ReferenceEntry';
10
11
  import type { components, Selector } from '@semiont/core';
11
12
  import { getTextPositionSelector, getTargetSelector } from '@semiont/core';
@@ -121,6 +122,9 @@ export function ReferencesPanel({
121
122
  sourceLanguage,
122
123
  }: Props) {
123
124
  const t = useTranslations('ReferencesPanel');
125
+ // Chrome shared by every assist surface (heading, current-work line, dismiss)
126
+ // lives in one namespace so the same words aren't re-translated per panel.
127
+ const ta = useTranslations('AssistProgress');
124
128
  const [selectedEntityTypes, setSelectedEntityTypes] = useState<string[]>([]);
125
129
  const [lastAnnotationLog, setLastDetectionLog] = useState<Array<{ entityType: string; foundCount: number }> | null>(null);
126
130
  const [pendingEntityTypes, setPendingEntityTypes] = useState<string[]>([]);
@@ -375,24 +379,19 @@ export function ReferencesPanel({
375
379
  failed: t('failed'),
376
380
  found: (count) => t('found', { count }),
377
381
  current: (entityType) => t('current', { entityType }),
378
- close: t('closeProgress'),
382
+ paramsTitle: ta('paramsTitle'),
383
+ processing: (label) => ta('processing', { label }),
384
+ close: ta('close'),
379
385
  },
380
386
  }}
381
387
  form={
382
388
  <>
383
389
  {/* Completed annotation log - shown after completion */}
384
- {lastAnnotationLog && lastAnnotationLog.length > 0 && (
385
- <div className="semiont-assist-widget__log">
386
- <div className="semiont-assist-widget__log-items">
387
- {lastAnnotationLog.map((item, index) => (
388
- <div key={index} className="semiont-assist-widget__log-item">
389
- <span className="semiont-assist-widget__log-check">✓</span>
390
- <span className="semiont-assist-widget__log-type">{item.entityType}:</span>
391
- <span>{t('found', { count: item.foundCount })}</span>
392
- </div>
393
- ))}
394
- </div>
395
- </div>
390
+ {lastAnnotationLog && (
391
+ <EntityFoundLog
392
+ entries={lastAnnotationLog}
393
+ formatFound={(count) => t('found', { count })}
394
+ />
396
395
  )}
397
396
 
398
397
  {/* Entity Types Selection */}
@@ -82,6 +82,14 @@ export function TaggingPanel({
82
82
  sourceLanguage,
83
83
  }: TaggingPanelProps) {
84
84
  const t = useTranslations('TaggingPanel');
85
+ const ta = useTranslations('AssistProgress');
86
+
87
+ // Dismiss parity: tag was the one assist surface with no way to clear a
88
+ // finished progress display, because it had no `closeProgress` key to
89
+ // label the control. Same wiring as every other surface.
90
+ const handleDismissProgress = useCallback(() => {
91
+ session?.client.mark.dismissProgress();
92
+ }, [session]);
85
93
 
86
94
  // Subscribe to the per-KB tag-schema registry. Schemas are runtime-
87
95
  // registered by the KB at session start (see frame.addTagSchema).
@@ -355,7 +363,19 @@ export function TaggingPanel({
355
363
  title={t('annotateTags')}
356
364
  isAssisting={isAssisting}
357
365
  progress={progress}
358
- progressProps={{ showPercentBar: true }}
366
+ progressProps={{
367
+ showPercentBar: true,
368
+ onDismiss: handleDismissProgress,
369
+ translations: {
370
+ cancel: t('cancel'),
371
+ inProgress: t('annotating'),
372
+ complete: t('complete'),
373
+ failed: t('failed'),
374
+ paramsTitle: ta('paramsTitle'),
375
+ processing: (label) => ta('processing', { label }),
376
+ close: ta('close'),
377
+ },
378
+ }}
359
379
  form={
360
380
  <>
361
381
  {/* Empty-state — registry has resolved with no schemas. */}
@@ -126,7 +126,7 @@ describe('AssistSection', () => {
126
126
  />
127
127
  );
128
128
 
129
- expect(screen.getByText('Request Parameters:')).toBeInTheDocument();
129
+ expect(screen.getByText('paramsTitle')).toBeInTheDocument();
130
130
  expect(screen.getByText('Find important points')).toBeInTheDocument();
131
131
  expect(screen.getByText(/Instructions:/)).toBeInTheDocument();
132
132
  expect(screen.getByText('5')).toBeInTheDocument();
@@ -506,7 +506,7 @@ describe('AssistSection', () => {
506
506
  />
507
507
  );
508
508
 
509
- expect(screen.queryByText('Request Parameters:')).not.toBeInTheDocument();
509
+ expect(screen.queryByText('paramsTitle')).not.toBeInTheDocument();
510
510
  });
511
511
  });
512
512
  });
@@ -13,6 +13,11 @@ import { AssistShell } from '../AssistShell';
13
13
 
14
14
  const progress = { stage: 'analyzing', percentage: 50, message: 'working' };
15
15
 
16
+ /** AssistProgress requires a full translation set; the shell just passes it through. */
17
+ const TR = { cancel: 'tr.cancel', inProgress: 'tr.inProgress', complete: 'tr.complete',
18
+ failed: 'tr.failed', close: 'tr.close', paramsTitle: 'tr.paramsTitle',
19
+ processing: (l: string) => `tr.processing(${l})` };
20
+
16
21
  describe('AssistShell', () => {
17
22
  beforeEach(() => {
18
23
  localStorage.clear();
@@ -21,12 +26,12 @@ describe('AssistShell', () => {
21
26
  it('renders the form when there is no progress, the progress when there is', () => {
22
27
  const { rerender } = render(
23
28
  <AssistShell assistType="tag" title="Annotate Tags" isAssisting={false} progress={null}
24
- form={<button type="button">the form</button>} />,
29
+ form={<button type="button">the form</button>} progressProps={{ translations: TR }} />,
25
30
  );
26
31
  expect(screen.getByText('the form')).toBeInTheDocument();
27
32
  rerender(
28
33
  <AssistShell assistType="tag" title="Annotate Tags" isAssisting={true} progress={progress}
29
- form={<button type="button">the form</button>} />,
34
+ form={<button type="button">the form</button>} progressProps={{ translations: TR }} />,
30
35
  );
31
36
  expect(screen.queryByText('the form')).not.toBeInTheDocument();
32
37
  expect(screen.getByText('working')).toBeInTheDocument();
@@ -37,7 +42,7 @@ describe('AssistShell', () => {
37
42
  const props = {
38
43
  assistType: 'highlight', title: 'Annotate Highlights', progress,
39
44
  form: <span>form</span>,
40
- progressProps: { onDismiss, translations: { close: 'Close' } },
45
+ progressProps: { onDismiss, translations: { ...TR, close: 'Close' } },
41
46
  };
42
47
  const { rerender } = render(<AssistShell {...props} isAssisting={true} />);
43
48
  expect(screen.queryByLabelText('Close')).not.toBeInTheDocument();
@@ -50,7 +55,7 @@ describe('AssistShell', () => {
50
55
  it('persists the expand state per assist type', async () => {
51
56
  render(
52
57
  <AssistShell assistType="reference" title="Annotate References" isAssisting={false} progress={null}
53
- form={<span>form</span>} />,
58
+ form={<span>form</span>} progressProps={{ translations: TR }} />,
54
59
  );
55
60
  await userEvent.click(screen.getByRole('button', { name: /Annotate References/ }));
56
61
  expect(screen.queryByText('form')).not.toBeInTheDocument();
@@ -169,7 +169,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
169
169
  />
170
170
  );
171
171
 
172
- expect(screen.getByText('Request Parameters:')).toBeInTheDocument();
172
+ expect(screen.getByText('paramsTitle')).toBeInTheDocument();
173
173
  expect(screen.getByText('Find important points')).toBeInTheDocument();
174
174
  expect(screen.getByText('5')).toBeInTheDocument();
175
175
  });
@@ -32,9 +32,15 @@ vi.mock('../../../lib/codemirror-json-theme', () => ({
32
32
  }));
33
33
 
34
34
  // Mock the hooks the panel consumes — NOT the session/client stack.
35
- vi.mock('@/hooks/useLineNumbers');
35
+ vi.mock('@/contexts/LineNumbersContext', async () => {
36
+ const actual = await vi.importActual('@/contexts/LineNumbersContext');
37
+ // Mock ONLY the hook: renderWithProviders mounts the real
38
+ // LineNumbersProvider, which an auto-mock would swallow (rendering
39
+ // nothing), while these tests want a fixed showLineNumbers value.
40
+ return { ...actual, useLineNumbers: vi.fn() };
41
+ });
36
42
  vi.mock('@/hooks/useResourceGraph');
37
- import { useLineNumbers } from '@/hooks/useLineNumbers';
43
+ import { useLineNumbers } from '@/contexts/LineNumbersContext';
38
44
  import { useResourceGraph } from '@/hooks/useResourceGraph';
39
45
 
40
46
  const RID = resourceId('test-resource-1');
@@ -409,6 +409,32 @@ describe('ReferencesPanel Component', () => {
409
409
  expect(screen.getByText('Organization:')).toBeInTheDocument();
410
410
  });
411
411
 
412
+ it('renders the entity log with the SAME markup the progress display uses', () => {
413
+ // ASSIST-SURFACE-WARTS Lane B: this form-side log and AssistProgress's
414
+ // completed-entity log are the same concept. They had two class families
415
+ // (semiont-assist-widget__log* here, semiont-annotation-log* there) one
416
+ // panel apart — one concept, one markup.
417
+ const { container, rerender } = renderWithEventBus(
418
+ <ReferencesPanel
419
+ {...panelProps()}
420
+ isAssisting={false}
421
+ progress={{
422
+ stage: 'complete',
423
+ percentage: 100,
424
+ message: 'Annotation complete',
425
+ completedEntityTypes: [{ entityType: 'Person', foundCount: 5 }],
426
+ }}
427
+ />
428
+ );
429
+ rerender(
430
+ <ReferencesPanel {...panelProps()} isAssisting={false} progress={null} />
431
+ );
432
+
433
+ expect(container.querySelector('.semiont-annotation-log')).toBeInTheDocument();
434
+ expect(container.querySelector('.semiont-annotation-log-item')).toBeInTheDocument();
435
+ expect(container.querySelector('.semiont-assist-widget__log')).not.toBeInTheDocument();
436
+ });
437
+
412
438
  it('should hide entity type selection during detection', () => {
413
439
  renderWithEventBus(
414
440
  <ReferencesPanel
@@ -5,10 +5,10 @@ import { LOCALES } from '@semiont/core';
5
5
  import { useTranslations } from '../../contexts/TranslationContext';
6
6
  import { useLanguageChangeAnnouncements } from '../LiveRegion';
7
7
  import { useSemiont } from '../../session/SemiontProvider';
8
+ import { useLineNumbers } from '../../contexts/LineNumbersContext';
8
9
  import './SettingsPanel.css';
9
10
 
10
11
  interface SettingsPanelProps {
11
- showLineNumbers: boolean;
12
12
  theme: 'light' | 'dark' | 'system';
13
13
  locale: string;
14
14
  isPendingLocaleChange?: boolean;
@@ -24,7 +24,6 @@ interface SettingsPanelProps {
24
24
  * @emits settings:hover-delay-changed - Hover delay changed by user. Payload: { hoverDelayMs: number }
25
25
  */
26
26
  export function SettingsPanel({
27
- showLineNumbers,
28
27
  theme,
29
28
  locale,
30
29
  isPendingLocaleChange = false,
@@ -32,6 +31,11 @@ export function SettingsPanel({
32
31
  }: SettingsPanelProps) {
33
32
  const t = useTranslations('Settings');
34
33
  const semiont = useSemiont();
34
+ // The switch renders the SHARED line-numbers state, not a prop snapshot:
35
+ // its toggle is applied by a bus subscriber in ToolbarPanels, so a prop
36
+ // fed from any per-route source is a second copy that never updates.
37
+ // See .plans/bugs/line-numbers-toggle-desynced-by-hoist.md
38
+ const { showLineNumbers } = useLineNumbers();
35
39
  const { announceLanguageChanging, announceLanguageChanged } = useLanguageChangeAnnouncements();
36
40
 
37
41
  // Track previous locale to detect changes
@@ -3,6 +3,7 @@ import { screen, fireEvent } from '@testing-library/react';
3
3
  import { renderWithProviders } from '../../../test-utils';
4
4
  import '@testing-library/jest-dom';
5
5
  import { SettingsPanel } from '../SettingsPanel';
6
+ import { useLineNumbers } from '../../../contexts/LineNumbersContext';
6
7
 
7
8
  // Mock LiveRegion
8
9
  vi.mock('../../LiveRegion', () => ({
@@ -27,7 +28,6 @@ vi.mock('@semiont/core', async () => {
27
28
 
28
29
  describe('SettingsPanel', () => {
29
30
  const defaultProps = {
30
- showLineNumbers: true,
31
31
  theme: 'light' as const,
32
32
  locale: 'en',
33
33
  hoverDelayMs: 150,
@@ -35,6 +35,7 @@ describe('SettingsPanel', () => {
35
35
 
36
36
  beforeEach(() => {
37
37
  vi.clearAllMocks();
38
+ localStorage.clear();
38
39
  });
39
40
 
40
41
  it('renders settings title', () => {
@@ -49,13 +50,14 @@ describe('SettingsPanel', () => {
49
50
  });
50
51
 
51
52
  it('shows toggle as checked when line numbers enabled', () => {
52
- renderWithProviders(<SettingsPanel {...defaultProps} showLineNumbers={true} />);
53
+ localStorage.setItem('showLineNumbers', 'true');
54
+ renderWithProviders(<SettingsPanel {...defaultProps} />);
53
55
  const toggle = screen.getByRole('switch');
54
56
  expect(toggle).toHaveAttribute('aria-checked', 'true');
55
57
  });
56
58
 
57
59
  it('shows toggle as unchecked when line numbers disabled', () => {
58
- renderWithProviders(<SettingsPanel {...defaultProps} showLineNumbers={false} />);
60
+ renderWithProviders(<SettingsPanel {...defaultProps} />);
59
61
  const toggle = screen.getByRole('switch');
60
62
  expect(toggle).toHaveAttribute('aria-checked', 'false');
61
63
  });
@@ -72,6 +74,29 @@ describe('SettingsPanel', () => {
72
74
  expect(handler).toHaveBeenCalled();
73
75
  sub.unsubscribe();
74
76
  });
77
+
78
+ it('renders the SHARED line-numbers state — a toggle applied elsewhere flips the switch', () => {
79
+ // The e2e chain is: switch click → bus emit → ToolbarPanels applies
80
+ // toggleLineNumbers → the switch must flip. The apply step happens in
81
+ // a DIFFERENT component, so the switch can only flip if all
82
+ // useLineNumbers consumers share one value. A prop-fed switch over a
83
+ // per-caller useState is how e2e 13:77/:134/:189 went red.
84
+ // See .plans/bugs/line-numbers-toggle-desynced-by-hoist.md
85
+ function OtherConsumer() {
86
+ const { toggleLineNumbers } = useLineNumbers();
87
+ return <button data-testid="other-consumer" onClick={toggleLineNumbers} />;
88
+ }
89
+ renderWithProviders(
90
+ <>
91
+ <OtherConsumer />
92
+ <SettingsPanel {...defaultProps} />
93
+ </>
94
+ );
95
+
96
+ expect(screen.getByRole('switch')).toHaveAttribute('aria-checked', 'false');
97
+ fireEvent.click(screen.getByTestId('other-consumer'));
98
+ expect(screen.getByRole('switch')).toHaveAttribute('aria-checked', 'true');
99
+ });
75
100
  });
76
101
 
77
102
  describe('theme selection', () => {
@@ -34,7 +34,6 @@ const createMockProps = (overrides?: Partial<AdminDevOpsPageProps>): AdminDevOps
34
34
  },
35
35
  ],
36
36
  theme: 'light',
37
- showLineNumbers: false,
38
37
  activePanel: null,
39
38
  translations: {
40
39
  title: 'DevOps',
@@ -289,13 +288,13 @@ describe('AdminDevOpsPage', () => {
289
288
  );
290
289
  });
291
290
 
292
- it('passes showLineNumbers to toolbar panels', () => {
291
+ it('does not thread showLineNumbers to toolbar panels — the setting is context-backed', () => {
293
292
  const ToolbarPanels = vi.fn(() => <div data-testid="toolbar-panels" />);
294
- const props = createMockProps({ showLineNumbers: true, ToolbarPanels });
293
+ const props = createMockProps({ToolbarPanels });
295
294
  render(<AdminDevOpsPage {...props} />);
296
295
 
297
296
  expect(ToolbarPanels).toHaveBeenCalledWith(
298
- expect.objectContaining({ showLineNumbers: true }),
297
+ expect.not.objectContaining({ showLineNumbers: expect.anything() }),
299
298
  undefined,
300
299
  );
301
300
  });
@@ -384,13 +383,6 @@ describe('AdminDevOpsPage', () => {
384
383
  expect(screen.getByText('DevOps')).toBeInTheDocument();
385
384
  });
386
385
 
387
- it('renders with line numbers enabled', () => {
388
- const props = createMockProps({ showLineNumbers: true });
389
- render(<AdminDevOpsPage {...props} />);
390
-
391
- expect(screen.getByText('DevOps')).toBeInTheDocument();
392
- });
393
-
394
386
  it('handles all callbacks being defined', () => {
395
387
  const props = createMockProps();
396
388
  render(<AdminDevOpsPage {...props} />);
@@ -23,7 +23,6 @@ export interface AdminDevOpsPageProps {
23
23
 
24
24
  // UI state
25
25
  theme: 'light' | 'dark' | 'system';
26
- showLineNumbers: boolean;
27
26
  activePanel: string | null;
28
27
 
29
28
  // Translations
@@ -46,7 +45,6 @@ export interface AdminDevOpsPageProps {
46
45
  export function AdminDevOpsPage({
47
46
  suggestedFeatures,
48
47
  theme,
49
- showLineNumbers,
50
48
  activePanel,
51
49
  translations: t,
52
50
  StatusDisplay,
@@ -132,7 +130,6 @@ export function AdminDevOpsPage({
132
130
  <ToolbarPanels
133
131
  activePanel={activePanel}
134
132
  theme={theme}
135
- showLineNumbers={showLineNumbers}
136
133
  />
137
134
 
138
135
  <Toolbar
@@ -18,7 +18,6 @@ const createProps = (overrides?: Partial<AdminExchangePageProps>): AdminExchange
18
18
  isImporting: false,
19
19
  importPhase: null,
20
20
  theme: 'light',
21
- showLineNumbers: false,
22
21
  activePanel: null,
23
22
  translations: {
24
23
  title: 'Backup & Restore',
@@ -38,7 +38,6 @@ export interface AdminExchangePageProps {
38
38
 
39
39
  // UI state
40
40
  theme: 'light' | 'dark' | 'system';
41
- showLineNumbers: boolean;
42
41
  activePanel: string | null;
43
42
 
44
43
  // Translations
@@ -62,7 +61,6 @@ export function AdminExchangePage({
62
61
  importMessage,
63
62
  importResult,
64
63
  theme,
65
- showLineNumbers,
66
64
  activePanel,
67
65
  translations: t,
68
66
  ToolbarPanels,
@@ -108,7 +106,6 @@ export function AdminExchangePage({
108
106
  <ToolbarPanels
109
107
  activePanel={activePanel}
110
108
  theme={theme}
111
- showLineNumbers={showLineNumbers}
112
109
  />
113
110
  <Toolbar
114
111
  context="simple"
@@ -2,7 +2,7 @@ import { describe, it, expect, vi } from 'vitest';
2
2
  import { firstValueFrom, of, throwError } from 'rxjs';
3
3
  import type { ShellStateUnit } from '../../../../state/shell-state-unit';
4
4
  import { createExchangeStateUnit } from '../exchange-state-unit';
5
- import { assertStateUnitAxioms, disposeProbe } from '@semiont/core/testing';
5
+ import { assertStateUnitAxioms, disposeProbe } from '@semiont/core/testing/axioms';
6
6
 
7
7
  function mockBrowse(): ShellStateUnit {
8
8
  return { dispose: vi.fn() } as unknown as ShellStateUnit;
@@ -21,7 +21,6 @@ const createMockProps = (overrides?: Partial<AdminSecurityPageProps>): AdminSecu
21
21
  allowedDomains: ['example.com'],
22
22
  isLoading: false,
23
23
  theme: 'light',
24
- showLineNumbers: false,
25
24
  activePanel: null,
26
25
  translations: {
27
26
  title: 'Security Settings',
@@ -347,12 +346,5 @@ describe('AdminSecurityPage', () => {
347
346
 
348
347
  expect(screen.getByText('Security Settings')).toBeInTheDocument();
349
348
  });
350
-
351
- it('renders with line numbers enabled', () => {
352
- const props = createMockProps({ showLineNumbers: true });
353
- render(<AdminSecurityPage {...props} />);
354
-
355
- expect(screen.getByText('Security Settings')).toBeInTheDocument();
356
- });
357
349
  });
358
350
  });
@@ -26,7 +26,6 @@ export interface AdminSecurityPageProps {
26
26
 
27
27
  // UI state
28
28
  theme: 'light' | 'dark' | 'system';
29
- showLineNumbers: boolean;
30
29
  activePanel: string | null;
31
30
 
32
31
  // Translations
@@ -60,7 +59,6 @@ export function AdminSecurityPage({
60
59
  allowedDomains,
61
60
  isLoading,
62
61
  theme,
63
- showLineNumbers,
64
62
  activePanel,
65
63
  translations: t,
66
64
  ToolbarPanels,
@@ -176,7 +174,6 @@ export function AdminSecurityPage({
176
174
  <ToolbarPanels
177
175
  activePanel={activePanel}
178
176
  theme={theme}
179
- showLineNumbers={showLineNumbers}
180
177
  />
181
178
 
182
179
  <Toolbar
@@ -5,7 +5,7 @@ import { filter } from 'rxjs/operators';
5
5
  import type { SemiontClient } from '@semiont/sdk';
6
6
  import type { ShellStateUnit } from '../../../../state/shell-state-unit';
7
7
  import { createAdminSecurityStateUnit } from '../admin-security-state-unit';
8
- import { assertStateUnitAxioms, disposeProbe } from '@semiont/core/testing';
8
+ import { assertStateUnitAxioms, disposeProbe } from '@semiont/core/testing/axioms';
9
9
 
10
10
  function mockBrowse(): ShellStateUnit {
11
11
  return { dispose: vi.fn() } as unknown as ShellStateUnit;
@@ -78,7 +78,6 @@ const createMockProps = (overrides?: Partial<AdminUsersPageProps>): AdminUsersPa
78
78
  onAddUser: vi.fn(),
79
79
  onExportUsers: vi.fn(),
80
80
  theme: 'light',
81
- showLineNumbers: false,
82
81
  activePanel: null,
83
82
  translations: createMockTranslations(),
84
83
  ToolbarPanels: ({ children }: any) => <div data-testid="toolbar-panels">{children}</div>,
@@ -152,7 +152,6 @@ export interface AdminUsersPageProps {
152
152
 
153
153
  // UI state
154
154
  theme: 'light' | 'dark' | 'system';
155
- showLineNumbers: boolean;
156
155
  activePanel: string | null;
157
156
 
158
157
  // Translations
@@ -213,7 +212,6 @@ export function AdminUsersPage({
213
212
  onAddUser,
214
213
  onExportUsers,
215
214
  theme,
216
- showLineNumbers,
217
215
  activePanel,
218
216
  translations: t,
219
217
  ToolbarPanels,
@@ -453,7 +451,6 @@ export function AdminUsersPage({
453
451
  <ToolbarPanels
454
452
  activePanel={activePanel}
455
453
  theme={theme}
456
- showLineNumbers={showLineNumbers}
457
454
  />
458
455
 
459
456
  <Toolbar
@@ -5,7 +5,7 @@ import { filter } from 'rxjs/operators';
5
5
  import type { SemiontClient } from '@semiont/sdk';
6
6
  import type { ShellStateUnit } from '../../../../state/shell-state-unit';
7
7
  import { createAdminUsersStateUnit } from '../admin-users-state-unit';
8
- import { assertStateUnitAxioms, disposeProbe } from '@semiont/core/testing';
8
+ import { assertStateUnitAxioms, disposeProbe } from '@semiont/core/testing/axioms';
9
9
 
10
10
  function mockBrowse(): ShellStateUnit {
11
11
  return { dispose: vi.fn() } as unknown as ShellStateUnit;
@@ -5,7 +5,7 @@ import type { SemiontSession } from '@semiont/sdk';
5
5
  import { AuthNamespace } from '@semiont/sdk';
6
6
  import { createTestSession, stubBackend } from '@semiont/sdk/testing';
7
7
  import { createWelcomeStateUnit } from '../welcome-state-unit';
8
- import { assertStateUnitAxioms } from '@semiont/core/testing';
8
+ import { assertStateUnitAxioms } from '@semiont/core/testing/axioms';
9
9
 
10
10
  // Real session over the scriptable transport (SESSION-TYPED-FACTORIES pilot);
11
11
  // auth behavior scripted by prototype spies, the AuthShell precedent — the
@@ -19,7 +19,6 @@ const createMockProps = (overrides?: Partial<EntityTagsPageProps>): EntityTagsPa
19
19
  onAddTag: vi.fn(),
20
20
  isAddingTag: false,
21
21
  theme: 'light',
22
- showLineNumbers: false,
23
22
  activePanel: null,
24
23
  translations: {
25
24
  pageTitle: 'Entity Tags',
@@ -344,7 +343,6 @@ describe('EntityTagsPage', () => {
344
343
  error: 'Error',
345
344
  isAddingTag: true,
346
345
  theme: 'dark',
347
- showLineNumbers: true,
348
346
  activePanel: 'settings',
349
347
  });
350
348
  render(<EntityTagsPage {...props} />);
@@ -28,7 +28,6 @@ export interface EntityTagsPageProps {
28
28
 
29
29
  // UI state
30
30
  theme: 'light' | 'dark' | 'system';
31
- showLineNumbers: boolean;
32
31
  activePanel: string | null;
33
32
 
34
33
  // Translations
@@ -55,7 +54,6 @@ export function EntityTagsPage({
55
54
  onAddTag,
56
55
  isAddingTag,
57
56
  theme,
58
- showLineNumbers,
59
57
  activePanel,
60
58
  translations: t,
61
59
  ToolbarPanels,
@@ -150,7 +148,6 @@ export function EntityTagsPage({
150
148
  <ToolbarPanels
151
149
  activePanel={activePanel}
152
150
  theme={theme}
153
- showLineNumbers={showLineNumbers}
154
151
  />
155
152
 
156
153
  <Toolbar
@@ -6,7 +6,7 @@ import { filter } from 'rxjs/operators';
6
6
  import type { SemiontClient } from '@semiont/sdk';
7
7
  import type { ShellStateUnit } from '../../../../state/shell-state-unit';
8
8
  import { createEntityTagsStateUnit } from '../entity-tags-state-unit';
9
- import { assertStateUnitAxioms, disposeProbe } from '@semiont/core/testing';
9
+ import { assertStateUnitAxioms, disposeProbe } from '@semiont/core/testing/axioms';
10
10
 
11
11
  function mockBrowse(): ShellStateUnit {
12
12
  return { dispose: vi.fn() } as unknown as ShellStateUnit;
@@ -14,7 +14,6 @@ const createMockProps = (overrides?: Partial<RecentDocumentsPageProps>): RecentD
14
14
  hasDocuments: false,
15
15
  isLoading: false,
16
16
  theme: 'light',
17
- showLineNumbers: false,
18
17
  activePanel: null,
19
18
  translations: {
20
19
  pageTitle: 'Recent Documents',
@@ -205,13 +204,13 @@ describe('RecentDocumentsPage', () => {
205
204
  );
206
205
  });
207
206
 
208
- it('passes showLineNumbers to toolbar panels', () => {
207
+ it('does not thread showLineNumbers to toolbar panels — the setting is context-backed', () => {
209
208
  const ToolbarPanels = vi.fn(() => <div data-testid="toolbar-panels" />);
210
- const props = createMockProps({ showLineNumbers: true, ToolbarPanels });
209
+ const props = createMockProps({ToolbarPanels });
211
210
  render(<RecentDocumentsPage {...props} />);
212
211
 
213
212
  expect(ToolbarPanels).toHaveBeenCalledWith(
214
- expect.objectContaining({ showLineNumbers: true }),
213
+ expect.not.objectContaining({ showLineNumbers: expect.anything() }),
215
214
  undefined,
216
215
  );
217
216
  });
@@ -342,7 +341,6 @@ describe('RecentDocumentsPage', () => {
342
341
  const props = createMockProps({
343
342
  hasDocuments: true,
344
343
  theme: 'dark',
345
- showLineNumbers: true,
346
344
  activePanel: 'settings',
347
345
  });
348
346
  render(<RecentDocumentsPage {...props} />);