@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
@@ -15,7 +15,6 @@ export interface RecentDocumentsPageProps {
15
15
 
16
16
  // UI state
17
17
  theme: 'light' | 'dark' | 'system';
18
- showLineNumbers: boolean;
19
18
  activePanel: string | null;
20
19
 
21
20
  // Translations
@@ -37,7 +36,6 @@ export interface RecentDocumentsPageProps {
37
36
  export function RecentDocumentsPage({
38
37
  isLoading,
39
38
  theme,
40
- showLineNumbers,
41
39
  activePanel,
42
40
  translations: t,
43
41
  ToolbarPanels,
@@ -95,7 +93,6 @@ export function RecentDocumentsPage({
95
93
  <ToolbarPanels
96
94
  activePanel={activePanel}
97
95
  theme={theme}
98
- showLineNumbers={showLineNumbers}
99
96
  />
100
97
 
101
98
  <Toolbar
@@ -61,7 +61,6 @@ const createMockProps = (overrides?: Partial<TagSchemasPageProps>): TagSchemasPa
61
61
  schemas: mockSchemas,
62
62
  isLoading: false,
63
63
  theme: 'light',
64
- showLineNumbers: false,
65
64
  activePanel: null,
66
65
  translations: {
67
66
  pageTitle: 'Tag Schemas',
@@ -276,13 +275,13 @@ describe('TagSchemasPage', () => {
276
275
  );
277
276
  });
278
277
 
279
- it('passes showLineNumbers to toolbar panels', () => {
278
+ it('does not thread showLineNumbers to toolbar panels — the setting is context-backed', () => {
280
279
  const ToolbarPanels = vi.fn(() => <div data-testid="toolbar-panels" />);
281
- const props = createMockProps({ showLineNumbers: true, ToolbarPanels });
280
+ const props = createMockProps({ToolbarPanels });
282
281
  render(<TagSchemasPage {...props} />);
283
282
 
284
283
  expect(ToolbarPanels).toHaveBeenCalledWith(
285
- expect.objectContaining({ showLineNumbers: true }),
284
+ expect.not.objectContaining({ showLineNumbers: expect.anything() }),
286
285
  undefined,
287
286
  );
288
287
  });
@@ -420,7 +419,6 @@ describe('TagSchemasPage', () => {
420
419
  it('handles all props being defined', () => {
421
420
  const props = createMockProps({
422
421
  theme: 'dark',
423
- showLineNumbers: true,
424
422
  activePanel: 'settings',
425
423
  });
426
424
  render(<TagSchemasPage {...props} />);
@@ -22,7 +22,6 @@ export interface TagSchemasPageProps {
22
22
 
23
23
  // UI state
24
24
  theme: 'light' | 'dark' | 'system';
25
- showLineNumbers: boolean;
26
25
  activePanel: string | null;
27
26
 
28
27
  // Translations
@@ -54,7 +53,6 @@ export function TagSchemasPage({
54
53
  schemas,
55
54
  isLoading,
56
55
  theme,
57
- showLineNumbers,
58
56
  activePanel,
59
57
  translations: t,
60
58
  ToolbarPanels,
@@ -153,7 +151,6 @@ export function TagSchemasPage({
153
151
  <ToolbarPanels
154
152
  activePanel={activePanel}
155
153
  theme={theme}
156
- showLineNumbers={showLineNumbers}
157
154
  />
158
155
 
159
156
  <Toolbar
@@ -18,7 +18,6 @@ const createProps = (overrides?: Partial<LinkedDataPageProps>): LinkedDataPagePr
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: 'Export & Import',
@@ -39,7 +39,6 @@ export interface LinkedDataPageProps {
39
39
 
40
40
  // UI state
41
41
  theme: 'light' | 'dark' | 'system';
42
- showLineNumbers: boolean;
43
42
  activePanel: string | null;
44
43
 
45
44
  // Translations
@@ -63,7 +62,6 @@ export function LinkedDataPage({
63
62
  importMessage,
64
63
  importResult,
65
64
  theme,
66
- showLineNumbers,
67
65
  activePanel,
68
66
  translations: t,
69
67
  ToolbarPanels,
@@ -109,7 +107,6 @@ export function LinkedDataPage({
109
107
  <ToolbarPanels
110
108
  activePanel={activePanel}
111
109
  theme={theme}
112
- showLineNumbers={showLineNumbers}
113
110
  />
114
111
  <Toolbar
115
112
  context="simple"
@@ -10,12 +10,14 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
10
10
  import { ResourceComposePage } from '../components/ResourceComposePage';
11
11
  import type { ResourceComposePageProps } from '../components/ResourceComposePage';
12
12
  import { createTestSemiontWrapper } from '../../../test-utils';
13
+ import { LineNumbersProvider } from '../../../contexts/LineNumbersContext';
13
14
 
14
15
  // Mock CodeMirrorRenderer to avoid CodeMirror dependencies
15
16
  vi.mock('../../../components/CodeMirrorRenderer', () => ({
16
- CodeMirrorRenderer: ({ content, onChange, editable }: any) => (
17
+ CodeMirrorRenderer: ({ content, onChange, editable, showLineNumbers }: any) => (
17
18
  <textarea
18
19
  data-testid="code-editor"
20
+ data-line-numbers={String(showLineNumbers)}
19
21
  value={content}
20
22
  onChange={(e) => editable && onChange?.(e.target.value)}
21
23
  disabled={!editable}
@@ -62,7 +64,6 @@ const createMockProps = (overrides?: Partial<ResourceComposePageProps>): Resourc
62
64
  availableEntityTypes: ['Document', 'Article', 'Report'],
63
65
  initialLocale: 'en',
64
66
  theme: 'light',
65
- showLineNumbers: false,
66
67
  hoverDelayMs: 0,
67
68
  activePanel: null,
68
69
  onSaveResource: vi.fn().mockResolvedValue(undefined),
@@ -77,7 +78,11 @@ const createMockProps = (overrides?: Partial<ResourceComposePageProps>): Resourc
77
78
  // component reaches for via useSemiont/useObservable).
78
79
  const renderWithProviders = (ui: React.ReactElement) => {
79
80
  const { SemiontWrapper } = createTestSemiontWrapper();
80
- return render(<SemiontWrapper>{ui}</SemiontWrapper>);
81
+ return render(
82
+ <LineNumbersProvider>
83
+ <SemiontWrapper>{ui}</SemiontWrapper>
84
+ </LineNumbersProvider>,
85
+ );
81
86
  };
82
87
 
83
88
  describe('ResourceComposePage', () => {
@@ -500,11 +505,16 @@ describe('ResourceComposePage', () => {
500
505
  expect(editor.value).toBe('New content');
501
506
  });
502
507
 
503
- it('respects showLineNumbers prop', () => {
504
- const props = createMockProps({ showLineNumbers: true });
505
- renderWithProviders(<ResourceComposePage {...props} />);
508
+ it('feeds the editor the shared line-numbers setting from context', () => {
509
+ localStorage.setItem('showLineNumbers', 'true');
510
+ try {
511
+ const props = createMockProps({});
512
+ renderWithProviders(<ResourceComposePage {...props} />);
506
513
 
507
- expect(screen.getByTestId('code-editor')).toBeInTheDocument();
514
+ expect(screen.getByTestId('code-editor')).toHaveAttribute('data-line-numbers', 'true');
515
+ } finally {
516
+ localStorage.removeItem('showLineNumbers');
517
+ }
508
518
  });
509
519
  });
510
520
  });
@@ -15,6 +15,7 @@ import { COMMON_PANELS, type ToolbarPanelType } from '../../../state/shell-state
15
15
  import { buttonStyles } from '../../../lib/button-styles';
16
16
  import { CodeMirrorRenderer } from '../../../components/CodeMirrorRenderer';
17
17
  import { useFormAnnouncements } from '../../../components/LiveRegion';
18
+ import { useLineNumbers } from '../../../contexts/LineNumbersContext';
18
19
  import { UploadProgressBar } from './UploadProgressBar';
19
20
 
20
21
  /**
@@ -54,7 +55,6 @@ export interface ResourceComposePageProps {
54
55
 
55
56
  // UI state
56
57
  theme: 'light' | 'dark';
57
- showLineNumbers: boolean;
58
58
  hoverDelayMs: number;
59
59
  activePanel: string | null;
60
60
 
@@ -133,7 +133,6 @@ export function ResourceComposePage({
133
133
  availableEntityTypes,
134
134
  initialLocale,
135
135
  theme,
136
- showLineNumbers,
137
136
  hoverDelayMs,
138
137
  activePanel,
139
138
  onSaveResource,
@@ -146,6 +145,10 @@ export function ResourceComposePage({
146
145
  // Form announcements
147
146
  const { announceFormSubmitting, announceFormSuccess, announceFormError } = useFormAnnouncements();
148
147
 
148
+ // Shared display setting (LineNumbersContext) — was a prop threaded from
149
+ // the route, which desynced it from the Settings toggle.
150
+ const { showLineNumbers } = useLineNumbers();
151
+
149
152
  // Form state
150
153
  const [newResourceName, setNewResourceName] = useState('');
151
154
  const [newResourceContent, setNewResourceContent] = useState('');
@@ -746,7 +749,6 @@ export function ResourceComposePage({
746
749
  <ToolbarPanels
747
750
  activePanel={activePanel}
748
751
  theme={theme}
749
- showLineNumbers={showLineNumbers}
750
752
  />
751
753
 
752
754
  {/* Toolbar - Always visible on the right */}
@@ -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 { createComposePageStateUnit } from '../compose-page-state-unit';
9
- import { assertStateUnitAxioms, disposeProbe } from '@semiont/core/testing';
9
+ import { assertStateUnitAxioms, disposeProbe } from '@semiont/core/testing/axioms';
10
10
 
11
11
  /** Build an `UploadObservable`-shaped mock that emits started → finished. */
12
12
  function mockUpload(resourceId: string) {
@@ -36,7 +36,6 @@ const createMockProps = (overrides?: Partial<ResourceDiscoveryPageProps>): Resou
36
36
  selectedEntityType: '',
37
37
  onSelectedEntityTypeChange: vi.fn(),
38
38
  theme: 'light',
39
- showLineNumbers: false,
40
39
  activePanel: null,
41
40
  onNavigateToResource: vi.fn(),
42
41
  onNavigateToCompose: vi.fn(),
@@ -40,7 +40,6 @@ export interface ResourceDiscoveryPageProps {
40
40
 
41
41
  // UI state props
42
42
  theme: 'light' | 'dark';
43
- showLineNumbers: boolean;
44
43
  activePanel: string | null;
45
44
 
46
45
  // Navigation props
@@ -86,7 +85,6 @@ export function ResourceDiscoveryPage({
86
85
  selectedEntityType,
87
86
  onSelectedEntityTypeChange,
88
87
  theme,
89
- showLineNumbers,
90
88
  activePanel,
91
89
  onNavigateToResource,
92
90
  onNavigateToCompose,
@@ -286,7 +284,6 @@ export function ResourceDiscoveryPage({
286
284
  <ToolbarPanels
287
285
  activePanel={activePanel}
288
286
  theme={theme}
289
- showLineNumbers={showLineNumbers}
290
287
  />
291
288
 
292
289
  {/* Toolbar - Always visible on the right */}
@@ -6,7 +6,7 @@ import { filter, skip, take, toArray } from 'rxjs/operators';
6
6
  import type { SemiontClient } from '@semiont/sdk';
7
7
  import type { ShellStateUnit } from '../../../../state/shell-state-unit';
8
8
  import { createDiscoverStateUnit } from '../discover-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;
@@ -12,6 +12,7 @@ import { ResourceViewerPage } from '../components/ResourceViewerPage';
12
12
  import type { ResourceViewerPageProps } from '../components/ResourceViewerPage';
13
13
  import { ToastProvider } from '../../../components/Toast';
14
14
  import { ThemeProvider } from '../../../contexts/ThemeContext';
15
+ import { LineNumbersProvider } from '../../../contexts/LineNumbersContext';
15
16
  import { createTestSemiontWrapper } from '../../../test-utils';
16
17
  import type { ResourceId } from '@semiont/core';
17
18
 
@@ -40,7 +41,7 @@ vi.mock('../../../hooks/useResourceContent', () => ({
40
41
  // SemiontClient (wired to a dummy baseUrl). The real client surface lets
41
42
  // createResourceViewerPageStateUnit run against the full namespace API without us
42
43
  // hand-stubbing every method it touches.
43
- const { stubBrowser } = vi.hoisted(() => {
44
+ const { stubBrowser, stubClient } = vi.hoisted(() => {
44
45
  const { BehaviorSubject } = require('rxjs');
45
46
  const { SemiontClient, HttpTransport, HttpContentTransport } = require('@semiont/sdk');
46
47
  const { baseUrl } = require('@semiont/core');
@@ -62,7 +63,7 @@ const { stubBrowser } = vi.hoisted(() => {
62
63
  on: vi.fn(() => () => {}),
63
64
  stream: vi.fn(() => ({ subscribe: () => ({ unsubscribe: () => {} }) })),
64
65
  };
65
- return { stubBrowser };
66
+ return { stubBrowser, stubClient: client };
66
67
  });
67
68
 
68
69
  vi.mock('../../../session/SemiontProvider', async () => {
@@ -75,6 +76,8 @@ vi.mock('../../../session/SemiontProvider', async () => {
75
76
  };
76
77
  });
77
78
 
79
+ const capturedHistory = vi.hoisted(() => ({ props: null as any }));
80
+
78
81
  vi.mock('@semiont/react-ui', async () => {
79
82
  const actual = await vi.importActual('@semiont/react-ui');
80
83
  return {
@@ -83,7 +86,10 @@ vi.mock('@semiont/react-ui', async () => {
83
86
  Toolbar: () => <div data-testid="toolbar">Toolbar</div>,
84
87
  ToolbarPanels: ({ children }: any) => <div data-testid="toolbar-panels">{children}</div>,
85
88
  UnifiedAnnotationsPanel: () => <div data-testid="annotations-panel">Annotations</div>,
86
- AnnotationHistory: () => <div data-testid="history-panel">History</div>,
89
+ AnnotationHistory: (props: any) => {
90
+ capturedHistory.props = props;
91
+ return <div data-testid="history-panel">History</div>;
92
+ },
87
93
  ResourceInfoPanel: () => <div data-testid="info-panel">Info</div>,
88
94
  CollaborationPanel: () => <div data-testid="collaboration-panel">Collaboration</div>,
89
95
  JsonLdPanel: () => <div data-testid="jsonld-panel">JSON-LD</div>,
@@ -163,11 +169,13 @@ const renderWithProviders = (ui: React.ReactElement) => {
163
169
  const { SemiontWrapper } = createTestSemiontWrapper();
164
170
  return render(
165
171
  <ThemeProvider>
166
- <ToastProvider>
167
- <SemiontWrapper>
168
- {ui}
169
- </SemiontWrapper>
170
- </ToastProvider>
172
+ <LineNumbersProvider>
173
+ <ToastProvider>
174
+ <SemiontWrapper>
175
+ {ui}
176
+ </SemiontWrapper>
177
+ </ToastProvider>
178
+ </LineNumbersProvider>
171
179
  </ThemeProvider>
172
180
  );
173
181
  };
@@ -225,6 +233,30 @@ describe('ResourceViewerPage', () => {
225
233
  localStorage.clear();
226
234
  });
227
235
 
236
+ it('clicking a history event focuses that annotation in the content', () => {
237
+ // ASSIST-SURFACE-WARTS Lane D. `handleEventClick` used to be a no-op with
238
+ // a stale comment, while HistoryEvent still rendered a focusable button
239
+ // labelled "View annotation" — a promise to screen-reader users that
240
+ // nothing kept. `beckon:focus` is the existing "scroll to and highlight"
241
+ // contract (BrowseView already subscribes); this makes the page a producer.
242
+ localStorage.setItem('activeToolbarPanel', 'history');
243
+ capturedHistory.props = null;
244
+ const props = createMockProps();
245
+ renderWithProviders(<ResourceViewerPage {...props} />);
246
+
247
+ expect(capturedHistory.props).not.toBeNull();
248
+
249
+ const focused: Array<string | null> = [];
250
+ const sub = stubClient.bus.get('beckon:focus').subscribe(({ annotationId }: any) => focused.push(annotationId));
251
+
252
+ expect(capturedHistory.props?.onEventClick).toBeTypeOf('function');
253
+ capturedHistory.props!.onEventClick('ann-42');
254
+ expect(focused).toEqual(['ann-42']);
255
+
256
+ sub.unsubscribe();
257
+ localStorage.clear();
258
+ });
259
+
228
260
  it('shows history panel when activePanel is history', () => {
229
261
  localStorage.setItem('activeToolbarPanel', 'history');
230
262
  const props = createMockProps();
@@ -24,10 +24,11 @@ import type { BrowseMediaRenderers, AnnotateMediaRenderers } from '@semiont/reac
24
24
  import { useObservable } from '@semiont/react-ui';
25
25
  import { useResourceContent } from '../../../hooks/useResourceContent';
26
26
  import { useMediaToken } from '../../../hooks/useMediaToken';
27
+ import { mediaUrl } from '../../../lib/media-url';
27
28
  import { useToast } from '../../../components/Toast';
28
29
  import { useOutcomeToasts } from '../../../hooks/useOutcomeToasts';
29
30
  import { useTheme } from '../../../contexts/ThemeContext';
30
- import { useLineNumbers } from '../../../hooks/useLineNumbers';
31
+ import { useLineNumbers } from '../../../contexts/LineNumbersContext';
31
32
  import { useHoverDelay } from '../../../hooks/useHoverDelay';
32
33
  import { useEventSubscriptions } from '../../../contexts/useEventSubscription';
33
34
  import { useObservableExternalNavigation } from '../../../hooks/useObservableBrowse';
@@ -116,8 +117,6 @@ export interface ResourceViewerPageProps {
116
117
  * @subscribes yield:clone - Clone the current resource
117
118
  * @subscribes beckon:sparkle - Trigger sparkle animation
118
119
  * @subscribes mark:added - Annotation was created (sparkle)
119
- * @subscribes settings:theme-changed - UI theme changed
120
- * @subscribes settings:line-numbers-toggled - Line numbers display toggled
121
120
  * @subscribes browse:reference-navigate - Navigate to a referenced document
122
121
  * @subscribes browse:entity-type-clicked - Navigate filtered by entity type
123
122
  *
@@ -141,6 +140,7 @@ export function ResourceViewerPage({
141
140
  // Translations
142
141
  const tw = useTranslations('ReferenceWizard');
143
142
  const tg = useTranslations('ResourceGenerate');
143
+ const ta = useTranslations('AssistProgress');
144
144
 
145
145
  const browser = useSemiont();
146
146
  const session = useObservable(browser.activeSession$);
@@ -159,8 +159,8 @@ export function ResourceViewerPage({
159
159
 
160
160
  // UI state hooks
161
161
  const { showError, showSuccess } = useToast();
162
- const { theme, setTheme } = useTheme();
163
- const { showLineNumbers, toggleLineNumbers } = useLineNumbers();
162
+ const { theme } = useTheme();
163
+ const { showLineNumbers } = useLineNumbers();
164
164
  const { hoverDelayMs } = useHoverDelay();
165
165
  const { triggerSparkleAnimation, clearNewAnnotationId, newAnnotationIds } = useResourceAnnotations();
166
166
 
@@ -187,9 +187,7 @@ export function ResourceViewerPage({
187
187
 
188
188
  // Binary path: fetch short-lived media token, construct URL
189
189
  const { token: mediaToken, loading: mediaTokenLoading } = useMediaToken(semiont ?? null, rUri);
190
- const binaryContent = (isBinary && mediaToken && semiont)
191
- ? `${semiont.baseUrl}/api/resources/${rUri}?token=${mediaToken}`
192
- : '';
190
+ const binaryContent = (isBinary ? mediaUrl(semiont, rUri, mediaToken) : undefined) ?? '';
193
191
 
194
192
  const content = isBinary ? binaryContent : textContent;
195
193
  const contentLoading = isBinary ? mediaTokenLoading : textLoading;
@@ -380,8 +378,6 @@ export function ResourceViewerPage({
380
378
  triggerSparkleAnimation(stored.payload.annotation.id);
381
379
  }, [triggerSparkleAnimation]);
382
380
 
383
- const handleSettingsThemeChanged = useCallback(({ theme }: { theme: any }) => setTheme(theme), [setTheme]);
384
-
385
381
  const handleReferenceNavigate = useCallback(({ resourceId }: { resourceId: string }) => {
386
382
  if (routes.resourceDetail) {
387
383
  const path = routes.resourceDetail(resourceId);
@@ -411,8 +407,6 @@ export function ResourceViewerPage({
411
407
  'yield:clone': handleResourceClone,
412
408
  'beckon:sparkle': handleAnnotationSparkle,
413
409
  'mark:added': handleAnnotationAdded,
414
- 'settings:theme-changed': handleSettingsThemeChanged,
415
- 'settings:line-numbers-toggled': toggleLineNumbers,
416
410
  'browse:reference-navigate': handleReferenceNavigate,
417
411
  'browse:entity-type-clicked': handleEntityTypeClicked,
418
412
  });
@@ -450,9 +444,17 @@ export function ResourceViewerPage({
450
444
  }
451
445
  }, [session]);
452
446
 
453
- const handleEventClick = useCallback((_annotationId: string | null) => {
454
- // ResourceViewer now manages scroll state internally
455
- }, []);
447
+ // Clicking a history row reveals that annotation in the content. HistoryEvent
448
+ // renders these rows as buttons labelled "View annotation", so this used to be
449
+ // a focusable, screen-reader-announced control wired to a no-op.
450
+ // `beckon:focus` is the existing "scroll to and highlight" contract rather
451
+ // than a new prop chain — BrowseView already subscribed to it, AnnotateView
452
+ // now does too. See .plans/ASSIST-SURFACE-WARTS.md Lane D.
453
+ const handleEventClick = useCallback((id: string | null) => {
454
+ if (id) {
455
+ stateUnit?.beckon.focus(annotationId(id));
456
+ }
457
+ }, [stateUnit]);
456
458
 
457
459
  // Document rendering
458
460
  return (
@@ -481,6 +483,9 @@ export function ResourceViewerPage({
481
483
  inProgress: tg('progressInProgress'),
482
484
  complete: tg('progressComplete'),
483
485
  failed: tg('progressFailed'),
486
+ paramsTitle: ta('paramsTitle'),
487
+ processing: (label) => ta('processing', { label }),
488
+ close: ta('close'),
484
489
  }}
485
490
  />
486
491
  )}
@@ -542,7 +547,6 @@ export function ResourceViewerPage({
542
547
  <ToolbarPanels
543
548
  activePanel={activePanel}
544
549
  theme={theme}
545
- showLineNumbers={showLineNumbers}
546
550
  width={
547
551
  activePanel === 'jsonld' ? 'w-[600px]' :
548
552
  activePanel === 'annotations' ? 'w-[400px]' :
@@ -6,7 +6,7 @@ import { filter } from 'rxjs/operators';
6
6
  import { resourceId as makeResourceId } from '@semiont/core';
7
7
  import type { SemiontClient } from '@semiont/sdk';
8
8
  import { createResourceLoaderStateUnit } from '../resource-loader-state-unit';
9
- import { assertStateUnitAxioms } from '@semiont/core/testing';
9
+ import { assertStateUnitAxioms } from '@semiont/core/testing/axioms';
10
10
 
11
11
  const RID = makeResourceId('res-1');
12
12
 
@@ -5,7 +5,7 @@ import { filter } from 'rxjs/operators';
5
5
  import { annotationId, resourceId as makeResourceId } from '@semiont/core';
6
6
  import type { ShellStateUnit } from '../../../../state/shell-state-unit';
7
7
  import { createResourceViewerPageStateUnit, type ResourceViewerPageStateUnit } from '../resource-viewer-page-state-unit';
8
- import { assertStateUnitAxioms, disposeProbe } from '@semiont/core/testing';
8
+ import { assertStateUnitAxioms, disposeProbe } from '@semiont/core/testing/axioms';
9
9
  import { makeTestSession, type TestSession } from '../../../../__tests__/test-client';
10
10
 
11
11
  const RID = makeResourceId('res-1');
@@ -10,7 +10,7 @@ import { createGatherStateUnit, type GatherStateUnit } from '@semiont/sdk';
10
10
  import { createMatchStateUnit } from '@semiont/sdk';
11
11
  import { createYieldStateUnit, type YieldStateUnit } from '@semiont/sdk';
12
12
  import type { SemiontSession } from '@semiont/sdk';
13
- import { decodeWithCharset, textExtractionOf } from '@semiont/core';
13
+ import { decodeWithCharset, textExtractionOf, uuidV4 } from '@semiont/core';
14
14
  import { groupAnnotations } from '../../../lib/annotation-groups';
15
15
  import type { ReferencedByEntry } from '@semiont/sdk';
16
16
 
@@ -144,7 +144,7 @@ export function createResourceViewerPageStateUnit(
144
144
  entityTypes: event.entityTypes,
145
145
  });
146
146
  client.bus.get('gather:requested').next({
147
- correlationId: crypto.randomUUID(),
147
+ correlationId: uuidV4(),
148
148
  annotationId: event.annotationId,
149
149
  resourceId: event.resourceId,
150
150
  options: { contextWindow: 2000 },
@@ -1,4 +1,9 @@
1
1
  {
2
+ "AssistProgress": {
3
+ "close": "إغلاق",
4
+ "paramsTitle": "معاملات الطلب:",
5
+ "processing": "الحالي: {{label}}"
6
+ },
2
7
  "Toolbar": {
3
8
  "annotations": "التعليقات التوضيحية",
4
9
  "history": "السجل",
@@ -105,7 +110,9 @@
105
110
  "densityDense": "كثيف",
106
111
  "fragmentSelected": "تم تحديد جزء",
107
112
  "imageRegionSelected": "تم تحديد منطقة من الصورة",
108
- "closeProgress": "إغلاق"
113
+ "complete": "اكتمل التوضيح!",
114
+ "failed": "فشل التوضيح",
115
+ "densityPerWords": "{{density}} لكل 2000 كلمة"
109
116
  },
110
117
  "HighlightPanel": {
111
118
  "title": "التمييزات",
@@ -120,7 +127,9 @@
120
127
  "densityLabel": "الكثافة",
121
128
  "densitySparse": "متفرق",
122
129
  "densityDense": "كثيف",
123
- "closeProgress": "إغلاق"
130
+ "complete": "اكتمل التوضيح!",
131
+ "failed": "فشل التوضيح",
132
+ "densityPerWords": "{{density}} لكل 2000 كلمة"
124
133
  },
125
134
  "AssessmentPanel": {
126
135
  "title": "التقييمات",
@@ -145,7 +154,9 @@
145
154
  "densityDense": "كثيف",
146
155
  "fragmentSelected": "تم تحديد جزء",
147
156
  "imageRegionSelected": "تم تحديد منطقة من الصورة",
148
- "closeProgress": "إغلاق"
157
+ "complete": "اكتمل التوضيح!",
158
+ "failed": "فشل التوضيح",
159
+ "densityPerWords": "{{density}} لكل 2000 كلمة"
149
160
  },
150
161
  "TaggingPanel": {
151
162
  "title": "العلامات",
@@ -164,7 +175,9 @@
164
175
  "selectCategory": "اختيار الفئة",
165
176
  "chooseCategory": "اختر فئة...",
166
177
  "fragmentSelected": "تم تحديد جزء",
167
- "imageRegionSelected": "تم تحديد منطقة من الصورة"
178
+ "imageRegionSelected": "تم تحديد منطقة من الصورة",
179
+ "complete": "اكتمل التوضيح!",
180
+ "failed": "فشل التوضيح"
168
181
  },
169
182
  "UnifiedAnnotationsPanel": {
170
183
  "title": "التعليقات التوضيحية",
@@ -220,7 +233,6 @@
220
233
  "complete": "اكتمل التوضيح!",
221
234
  "failed": "فشل التوضيح",
222
235
  "current": "الحالي: {{entityType}}",
223
- "closeProgress": "إغلاق",
224
236
  "loading": "جارٍ التحميل...",
225
237
  "loadingEllipsis": "جارٍ التحميل...",
226
238
  "untitledResource": "مورد بدون عنوان",
@@ -1,4 +1,9 @@
1
1
  {
2
+ "AssistProgress": {
3
+ "close": "বন্ধ করুন",
4
+ "paramsTitle": "অনুরোধের প্যারামিটার:",
5
+ "processing": "বর্তমান: {{label}}"
6
+ },
2
7
  "Toolbar": {
3
8
  "annotations": "টীকাসমূহ",
4
9
  "history": "ইতিহাস",
@@ -105,7 +110,9 @@
105
110
  "densityDense": "ঘন",
106
111
  "fragmentSelected": "অংশ নির্বাচিত",
107
112
  "imageRegionSelected": "ছবির অঞ্চল নির্বাচিত",
108
- "closeProgress": "বন্ধ করুন"
113
+ "complete": "টীকাকরণ সম্পূর্ণ!",
114
+ "failed": "টীকাকরণ ব্যর্থ",
115
+ "densityPerWords": "প্রতি ২০০০ শব্দে {{density}}"
109
116
  },
110
117
  "HighlightPanel": {
111
118
  "title": "হাইলাইটসমূহ",
@@ -120,7 +127,9 @@
120
127
  "densityLabel": "ঘনত্ব",
121
128
  "densitySparse": "বিরল",
122
129
  "densityDense": "ঘন",
123
- "closeProgress": "বন্ধ করুন"
130
+ "complete": "টীকাকরণ সম্পূর্ণ!",
131
+ "failed": "টীকাকরণ ব্যর্থ",
132
+ "densityPerWords": "প্রতি ২০০০ শব্দে {{density}}"
124
133
  },
125
134
  "AssessmentPanel": {
126
135
  "title": "মূল্যায়নসমূহ",
@@ -145,7 +154,9 @@
145
154
  "densityDense": "ঘন",
146
155
  "fragmentSelected": "অংশ নির্বাচিত",
147
156
  "imageRegionSelected": "ছবির অঞ্চল নির্বাচিত",
148
- "closeProgress": "বন্ধ করুন"
157
+ "complete": "টীকাকরণ সম্পূর্ণ!",
158
+ "failed": "টীকাকরণ ব্যর্থ",
159
+ "densityPerWords": "প্রতি ২০০০ শব্দে {{density}}"
149
160
  },
150
161
  "TaggingPanel": {
151
162
  "title": "ট্যাগসমূহ",
@@ -164,7 +175,9 @@
164
175
  "selectCategory": "বিভাগ নির্বাচন করুন",
165
176
  "chooseCategory": "একটি বিভাগ বেছে নিন...",
166
177
  "fragmentSelected": "অংশ নির্বাচিত",
167
- "imageRegionSelected": "ছবির অঞ্চল নির্বাচিত"
178
+ "imageRegionSelected": "ছবির অঞ্চল নির্বাচিত",
179
+ "complete": "টীকাকরণ সম্পূর্ণ!",
180
+ "failed": "টীকাকরণ ব্যর্থ"
168
181
  },
169
182
  "UnifiedAnnotationsPanel": {
170
183
  "title": "টীকাসমূহ",
@@ -220,7 +233,6 @@
220
233
  "complete": "টীকাকরণ সম্পূর্ণ!",
221
234
  "failed": "টীকাকরণ ব্যর্থ",
222
235
  "current": "বর্তমান: {{entityType}}",
223
- "closeProgress": "বন্ধ করুন",
224
236
  "loading": "লোড হচ্ছে...",
225
237
  "loadingEllipsis": "লোড হচ্ছে...",
226
238
  "untitledResource": "শিরোনামবিহীন সম্পদ",