@semiont/react-ui 0.5.22 → 0.5.24

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 (197) hide show
  1. package/dist/{ar-ZHKO4XDR.js → ar-JRTKCMTF.js} +11 -4
  2. package/dist/ar-JRTKCMTF.js.map +1 -0
  3. package/dist/{bn-3BUDKAK5.js → bn-LO6EDSCC.js} +11 -4
  4. package/dist/bn-LO6EDSCC.js.map +1 -0
  5. package/dist/{chunk-PFUXQPWJ.js → chunk-4QMTEPQ3.js} +92 -60
  6. package/dist/chunk-4QMTEPQ3.js.map +1 -0
  7. package/dist/{chunk-R52YAGHK.js → chunk-5Y5JDCKC.js} +11 -4
  8. package/dist/chunk-5Y5JDCKC.js.map +1 -0
  9. package/dist/{cs-2EFEUKTW.js → cs-LCHHOR4O.js} +11 -4
  10. package/dist/cs-LCHHOR4O.js.map +1 -0
  11. package/dist/{da-SUD3Q44Q.js → da-TARGPMJR.js} +11 -4
  12. package/dist/da-TARGPMJR.js.map +1 -0
  13. package/dist/{de-6IC5QRX7.js → de-SBH4QBPJ.js} +11 -4
  14. package/dist/de-SBH4QBPJ.js.map +1 -0
  15. package/dist/{el-Q4NAXFRC.js → el-2D2OLGWB.js} +11 -4
  16. package/dist/el-2D2OLGWB.js.map +1 -0
  17. package/dist/{en-P2L4X27N.js → en-QFIEG3CA.js} +2 -2
  18. package/dist/{es-YUKZBD4W.js → es-U6YK2LX6.js} +11 -4
  19. package/dist/es-U6YK2LX6.js.map +1 -0
  20. package/dist/{fa-OJ47LILE.js → fa-S7SZE37M.js} +11 -4
  21. package/dist/fa-S7SZE37M.js.map +1 -0
  22. package/dist/{fi-QFFL5CUB.js → fi-CBL3NIXN.js} +11 -4
  23. package/dist/fi-CBL3NIXN.js.map +1 -0
  24. package/dist/{fr-BOQZZWP4.js → fr-EABUHV6S.js} +11 -4
  25. package/dist/fr-EABUHV6S.js.map +1 -0
  26. package/dist/{he-LXDY3TFA.js → he-THWD244Y.js} +11 -4
  27. package/dist/he-THWD244Y.js.map +1 -0
  28. package/dist/{hi-HOJER77V.js → hi-GHGFZJ44.js} +11 -4
  29. package/dist/hi-GHGFZJ44.js.map +1 -0
  30. package/dist/{id-I5WQ6EWD.js → id-POZOUMDW.js} +11 -4
  31. package/dist/id-POZOUMDW.js.map +1 -0
  32. package/dist/index.d.ts +269 -69
  33. package/dist/index.js +2080 -1861
  34. package/dist/index.js.map +1 -1
  35. package/dist/{it-UYH642IJ.js → it-KDYJADSH.js} +11 -4
  36. package/dist/it-KDYJADSH.js.map +1 -0
  37. package/dist/{ja-JTVWAC4L.js → ja-Z5Z6ZF3P.js} +11 -4
  38. package/dist/ja-Z5Z6ZF3P.js.map +1 -0
  39. package/dist/{ko-IHXFMOIZ.js → ko-7X3XUXRN.js} +11 -4
  40. package/dist/ko-7X3XUXRN.js.map +1 -0
  41. package/dist/{ms-JH25VFLO.js → ms-BTWEIW43.js} +11 -4
  42. package/dist/ms-BTWEIW43.js.map +1 -0
  43. package/dist/{nl-SURXW7IQ.js → nl-IS2CGF5I.js} +11 -4
  44. package/dist/nl-IS2CGF5I.js.map +1 -0
  45. package/dist/{no-HXIKQFDZ.js → no-AAMEVA26.js} +11 -4
  46. package/dist/no-AAMEVA26.js.map +1 -0
  47. package/dist/{pl-7A4KU6XK.js → pl-PVXUQYFT.js} +11 -4
  48. package/dist/pl-PVXUQYFT.js.map +1 -0
  49. package/dist/{pt-6MGYP7Q5.js → pt-3ITQ76TL.js} +11 -4
  50. package/dist/pt-3ITQ76TL.js.map +1 -0
  51. package/dist/{ro-IROCHNIA.js → ro-FRB4IIVS.js} +11 -4
  52. package/dist/ro-FRB4IIVS.js.map +1 -0
  53. package/dist/{sv-WDJD75CV.js → sv-DS3RO24M.js} +11 -4
  54. package/dist/sv-DS3RO24M.js.map +1 -0
  55. package/dist/test-utils.js +9 -3
  56. package/dist/test-utils.js.map +1 -1
  57. package/dist/{th-RIO2E6B3.js → th-AX66MWWB.js} +11 -4
  58. package/dist/th-AX66MWWB.js.map +1 -0
  59. package/dist/{tr-TZ7ERCIN.js → tr-7KTB67TT.js} +11 -4
  60. package/dist/tr-7KTB67TT.js.map +1 -0
  61. package/dist/{uk-ZFWDKLUE.js → uk-SMQS2MAQ.js} +11 -4
  62. package/dist/uk-SMQS2MAQ.js.map +1 -0
  63. package/dist/{vi-UIOSP2JG.js → vi-D3JXL2EZ.js} +11 -4
  64. package/dist/vi-D3JXL2EZ.js.map +1 -0
  65. package/dist/{zh-6R6YUY46.js → zh-S754AWFS.js} +11 -4
  66. package/dist/zh-S754AWFS.js.map +1 -0
  67. package/package.json +4 -4
  68. package/src/components/annotation-popups/JsonLdView.tsx +1 -1
  69. package/src/components/annotation-popups/__tests__/JsonLdView.test.tsx +8 -2
  70. package/src/components/modals/ReferenceWizardModal.tsx +2 -1
  71. package/src/components/modals/ResourceGenerateModal.tsx +10 -4
  72. package/src/components/modals/ResourceSearchModal.tsx +3 -1
  73. package/src/components/modals/SearchModal.tsx +3 -1
  74. package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +5 -0
  75. package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +2 -1
  76. package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +2 -1
  77. package/src/components/resource/AnnotateView.tsx +70 -138
  78. package/src/components/resource/AnnotationHistory.tsx +53 -17
  79. package/src/components/resource/BrowseView.tsx +3 -2
  80. package/src/components/resource/ResourceViewer.tsx +39 -22
  81. package/src/components/resource/__tests__/AnnotateView.registry.test.tsx +114 -0
  82. package/src/components/resource/__tests__/AnnotationHistory.test.tsx +63 -41
  83. package/src/components/resource/__tests__/BrowseView.reference-hover.test.tsx +2 -1
  84. package/src/components/resource/__tests__/ResourceViewer.multi-mount-invalidation.test.tsx +140 -0
  85. package/src/components/resource/__tests__/ResourceViewer.registry.test.tsx +89 -0
  86. package/src/components/resource/annotate-renderers.tsx +155 -0
  87. package/src/components/resource/panels/JsonLdPanel.tsx +1 -1
  88. package/src/components/resource/panels/ReferencesPanel.tsx +38 -3
  89. package/src/components/resource/panels/TagEntry.tsx +5 -1
  90. package/src/components/resource/panels/TaggingPanel.tsx +6 -1
  91. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +41 -0
  92. package/src/components/resource/panels/__tests__/JsonLdPanel.test.tsx +8 -2
  93. package/src/components/resource/panels/__tests__/ReferencesPanel.headless.test.tsx +90 -0
  94. package/src/components/resource/panels/__tests__/TagEntry.test.tsx +2 -2
  95. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +2 -2
  96. package/src/components/resource/panels/__tests__/UnifiedAnnotationsPanel.failure.test.tsx +78 -0
  97. package/src/components/settings/SettingsPanel.tsx +6 -2
  98. package/src/components/settings/__tests__/SettingsPanel.test.tsx +28 -3
  99. package/src/features/admin-devops/__tests__/AdminDevOpsPage.test.tsx +3 -11
  100. package/src/features/admin-devops/components/AdminDevOpsPage.tsx +0 -3
  101. package/src/features/admin-exchange/__tests__/AdminExchangePage.test.tsx +0 -1
  102. package/src/features/admin-exchange/components/AdminExchangePage.tsx +0 -3
  103. package/src/features/admin-exchange/state/__tests__/exchange-state-unit.test.ts +1 -1
  104. package/src/features/admin-security/__tests__/AdminSecurityPage.test.tsx +0 -8
  105. package/src/features/admin-security/components/AdminSecurityPage.tsx +0 -3
  106. package/src/features/admin-security/state/__tests__/admin-security-state-unit.test.ts +10 -9
  107. package/src/features/admin-security/state/admin-security-state-unit.ts +3 -2
  108. package/src/features/admin-users/__tests__/AdminUsersPage.test.tsx +0 -1
  109. package/src/features/admin-users/components/AdminUsersPage.tsx +0 -3
  110. package/src/features/admin-users/state/__tests__/admin-users-state-unit.test.ts +7 -6
  111. package/src/features/admin-users/state/admin-users-state-unit.ts +3 -2
  112. package/src/features/auth-welcome/state/__tests__/welcome-state-unit.test.ts +36 -26
  113. package/src/features/auth-welcome/state/welcome-state-unit.ts +7 -2
  114. package/src/features/moderate-entity-tags/__tests__/EntityTagsPage.test.tsx +0 -2
  115. package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +0 -3
  116. package/src/features/moderate-entity-tags/state/__tests__/entity-tags-state-unit.test.ts +37 -12
  117. package/src/features/moderate-entity-tags/state/entity-tags-state-unit.ts +9 -10
  118. package/src/features/moderate-recent/__tests__/RecentDocumentsPage.test.tsx +3 -5
  119. package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +0 -3
  120. package/src/features/moderate-tag-schemas/__tests__/TagSchemasPage.test.tsx +3 -5
  121. package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +0 -3
  122. package/src/features/moderation-linked-data/__tests__/LinkedDataPage.test.tsx +0 -1
  123. package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +0 -3
  124. package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +17 -7
  125. package/src/features/resource-compose/components/ResourceComposePage.tsx +5 -3
  126. package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +33 -12
  127. package/src/features/resource-compose/state/compose-page-state-unit.ts +9 -8
  128. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +3 -2
  129. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +25 -3
  130. package/src/features/resource-discovery/state/__tests__/discover-state-unit.test.ts +90 -25
  131. package/src/features/resource-discovery/state/discover-state-unit.ts +30 -30
  132. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +9 -5
  133. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +70 -40
  134. package/src/features/resource-viewer/state/__tests__/resource-loader-state-unit.test.ts +111 -8
  135. package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +167 -27
  136. package/src/features/resource-viewer/state/resource-loader-state-unit.ts +74 -9
  137. package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +29 -28
  138. package/translations/ar.json +10 -3
  139. package/translations/bn.json +10 -3
  140. package/translations/cs.json +10 -3
  141. package/translations/da.json +10 -3
  142. package/translations/de.json +10 -3
  143. package/translations/el.json +10 -3
  144. package/translations/en.json +10 -3
  145. package/translations/es.json +10 -3
  146. package/translations/fa.json +10 -3
  147. package/translations/fi.json +10 -3
  148. package/translations/fr.json +10 -3
  149. package/translations/he.json +10 -3
  150. package/translations/hi.json +10 -3
  151. package/translations/id.json +10 -3
  152. package/translations/it.json +10 -3
  153. package/translations/ja.json +10 -3
  154. package/translations/ko.json +10 -3
  155. package/translations/ms.json +10 -3
  156. package/translations/nl.json +10 -3
  157. package/translations/no.json +10 -3
  158. package/translations/pl.json +10 -3
  159. package/translations/pt.json +10 -3
  160. package/translations/ro.json +10 -3
  161. package/translations/sv.json +10 -3
  162. package/translations/th.json +10 -3
  163. package/translations/tr.json +10 -3
  164. package/translations/uk.json +10 -3
  165. package/translations/vi.json +10 -3
  166. package/translations/zh.json +10 -3
  167. package/dist/ar-ZHKO4XDR.js.map +0 -1
  168. package/dist/bn-3BUDKAK5.js.map +0 -1
  169. package/dist/chunk-PFUXQPWJ.js.map +0 -1
  170. package/dist/chunk-R52YAGHK.js.map +0 -1
  171. package/dist/cs-2EFEUKTW.js.map +0 -1
  172. package/dist/da-SUD3Q44Q.js.map +0 -1
  173. package/dist/de-6IC5QRX7.js.map +0 -1
  174. package/dist/el-Q4NAXFRC.js.map +0 -1
  175. package/dist/es-YUKZBD4W.js.map +0 -1
  176. package/dist/fa-OJ47LILE.js.map +0 -1
  177. package/dist/fi-QFFL5CUB.js.map +0 -1
  178. package/dist/fr-BOQZZWP4.js.map +0 -1
  179. package/dist/he-LXDY3TFA.js.map +0 -1
  180. package/dist/hi-HOJER77V.js.map +0 -1
  181. package/dist/id-I5WQ6EWD.js.map +0 -1
  182. package/dist/it-UYH642IJ.js.map +0 -1
  183. package/dist/ja-JTVWAC4L.js.map +0 -1
  184. package/dist/ko-IHXFMOIZ.js.map +0 -1
  185. package/dist/ms-JH25VFLO.js.map +0 -1
  186. package/dist/nl-SURXW7IQ.js.map +0 -1
  187. package/dist/no-HXIKQFDZ.js.map +0 -1
  188. package/dist/pl-7A4KU6XK.js.map +0 -1
  189. package/dist/pt-6MGYP7Q5.js.map +0 -1
  190. package/dist/ro-IROCHNIA.js.map +0 -1
  191. package/dist/sv-WDJD75CV.js.map +0 -1
  192. package/dist/th-RIO2E6B3.js.map +0 -1
  193. package/dist/tr-TZ7ERCIN.js.map +0 -1
  194. package/dist/uk-ZFWDKLUE.js.map +0 -1
  195. package/dist/vi-UIOSP2JG.js.map +0 -1
  196. package/dist/zh-6R6YUY46.js.map +0 -1
  197. /package/dist/{en-P2L4X27N.js.map → en-QFIEG3CA.js.map} +0 -0
@@ -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
@@ -1,10 +1,11 @@
1
1
  import { describe, it, expect, vi } from 'vitest';
2
+ import { sessionOf } from '../../../../__tests__/test-client';
2
3
  import { firstValueFrom } from 'rxjs';
3
4
  import { filter } from 'rxjs/operators';
4
5
  import type { SemiontClient } from '@semiont/sdk';
5
6
  import type { ShellStateUnit } from '../../../../state/shell-state-unit';
6
7
  import { createAdminSecurityStateUnit } from '../admin-security-state-unit';
7
- import { assertStateUnitAxioms, disposeProbe } from '@semiont/core/testing';
8
+ import { assertStateUnitAxioms, disposeProbe } from '@semiont/core/testing/axioms';
8
9
 
9
10
  function mockBrowse(): ShellStateUnit {
10
11
  return { dispose: vi.fn() } as unknown as ShellStateUnit;
@@ -20,7 +21,7 @@ describe('createAdminSecurityStateUnit', () => {
20
21
  providers: [{ name: 'google' }],
21
22
  allowedDomains: ['example.com'],
22
23
  });
23
- const stateUnit = createAdminSecurityStateUnit(mockClient(getOAuthConfig), mockBrowse());
24
+ const stateUnit = createAdminSecurityStateUnit(sessionOf(mockClient(getOAuthConfig)), mockBrowse());
24
25
 
25
26
  const providers = await firstValueFrom(stateUnit.providers$.pipe(filter((p) => p.length > 0)));
26
27
  expect(providers).toEqual([{ name: 'google' }]);
@@ -32,8 +33,8 @@ describe('createAdminSecurityStateUnit', () => {
32
33
  });
33
34
 
34
35
  it('starts loading, resolves to false', async () => {
35
- const stateUnit = createAdminSecurityStateUnit(
36
- mockClient(vi.fn().mockResolvedValue({ providers: [], allowedDomains: [] })),
36
+ const stateUnit = createAdminSecurityStateUnit(sessionOf(
37
+ mockClient(vi.fn().mockResolvedValue({ providers: [], allowedDomains: [] }))),
37
38
  mockBrowse(),
38
39
  );
39
40
 
@@ -42,8 +43,8 @@ describe('createAdminSecurityStateUnit', () => {
42
43
  });
43
44
 
44
45
  it('sets loading false on error', async () => {
45
- const stateUnit = createAdminSecurityStateUnit(
46
- mockClient(vi.fn().mockRejectedValue(new Error('fail'))),
46
+ const stateUnit = createAdminSecurityStateUnit(sessionOf(
47
+ mockClient(vi.fn().mockRejectedValue(new Error('fail')))),
47
48
  mockBrowse(),
48
49
  );
49
50
 
@@ -52,8 +53,8 @@ describe('createAdminSecurityStateUnit', () => {
52
53
  });
53
54
 
54
55
  it('defaults to empty arrays when response has no providers/domains', async () => {
55
- const stateUnit = createAdminSecurityStateUnit(
56
- mockClient(vi.fn().mockResolvedValue({})),
56
+ const stateUnit = createAdminSecurityStateUnit(sessionOf(
57
+ mockClient(vi.fn().mockResolvedValue({}))),
57
58
  mockBrowse(),
58
59
  );
59
60
 
@@ -74,7 +75,7 @@ describe('AdminSecurityStateUnit — StateUnit axioms', () => {
74
75
  setup: () => {
75
76
  const browse = disposeProbe();
76
77
  const client = mockClient(vi.fn().mockResolvedValue({ providers: [], allowedDomains: [] }));
77
- return { unit: createAdminSecurityStateUnit(client, browse as unknown as ShellStateUnit), passedIn: [browse] };
78
+ return { unit: createAdminSecurityStateUnit(sessionOf(client), browse as unknown as ShellStateUnit), passedIn: [browse] };
78
79
  },
79
80
  surfaces: (u) => [u.providers$, u.allowedDomains$, u.isLoading$],
80
81
  });
@@ -2,7 +2,7 @@ import { BehaviorSubject, type Observable } from 'rxjs';
2
2
  import { createDisposer } from '@semiont/sdk';
3
3
  import type { StateUnit } from '@semiont/core';
4
4
  import type { ShellStateUnit } from '../../../state/shell-state-unit';
5
- import type { SemiontClient } from '@semiont/sdk';
5
+ import type { SemiontSession } from '@semiont/sdk';
6
6
 
7
7
  export interface AdminSecurityStateUnit extends StateUnit {
8
8
  browse: ShellStateUnit;
@@ -12,9 +12,10 @@ export interface AdminSecurityStateUnit extends StateUnit {
12
12
  }
13
13
 
14
14
  export function createAdminSecurityStateUnit(
15
- client: SemiontClient,
15
+ session: SemiontSession,
16
16
  browse: ShellStateUnit,
17
17
  ): AdminSecurityStateUnit {
18
+ const { client } = session;
18
19
  const disposer = createDisposer();
19
20
  // `browse` (ShellStateUnit) is a *passed-in* dependency owned by the caller
20
21
  // (`useShellStateUnit`), not this unit — do NOT add it to the disposer (it's the
@@ -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
@@ -1,10 +1,11 @@
1
1
  import { describe, it, expect, vi } from 'vitest';
2
+ import { sessionOf } from '../../../../__tests__/test-client';
2
3
  import { firstValueFrom } from 'rxjs';
3
4
  import { filter } from 'rxjs/operators';
4
5
  import type { SemiontClient } from '@semiont/sdk';
5
6
  import type { ShellStateUnit } from '../../../../state/shell-state-unit';
6
7
  import { createAdminUsersStateUnit } from '../admin-users-state-unit';
7
- import { assertStateUnitAxioms, disposeProbe } from '@semiont/core/testing';
8
+ import { assertStateUnitAxioms, disposeProbe } from '@semiont/core/testing/axioms';
8
9
 
9
10
  function mockBrowse(): ShellStateUnit {
10
11
  return { dispose: vi.fn() } as unknown as ShellStateUnit;
@@ -28,7 +29,7 @@ describe('createAdminUsersStateUnit', () => {
28
29
  it('fetches users and stats on creation', async () => {
29
30
  const listUsers = vi.fn().mockResolvedValue({ users: [{ id: 'u1' }] });
30
31
  const getUserStats = vi.fn().mockResolvedValue({ stats: { total: 1 } });
31
- const stateUnit = createAdminUsersStateUnit(mockClient({ listUsers, getUserStats }), mockBrowse());
32
+ const stateUnit = createAdminUsersStateUnit(sessionOf(mockClient({ listUsers, getUserStats })), mockBrowse());
32
33
 
33
34
  const users = await firstValueFrom(stateUnit.users$.pipe(filter((u) => u.length > 0)));
34
35
  expect(users).toEqual([{ id: 'u1' }]);
@@ -40,7 +41,7 @@ describe('createAdminUsersStateUnit', () => {
40
41
  });
41
42
 
42
43
  it('starts with loading true, resolves to false', async () => {
43
- const stateUnit = createAdminUsersStateUnit(mockClient(), mockBrowse());
44
+ const stateUnit = createAdminUsersStateUnit(sessionOf(mockClient()), mockBrowse());
44
45
 
45
46
  await firstValueFrom(stateUnit.usersLoading$.pipe(filter((l) => !l)));
46
47
  await firstValueFrom(stateUnit.statsLoading$.pipe(filter((l) => !l)));
@@ -51,7 +52,7 @@ describe('createAdminUsersStateUnit', () => {
51
52
  it('sets loading false on fetch error', async () => {
52
53
  const listUsers = vi.fn().mockRejectedValue(new Error('fail'));
53
54
  const getUserStats = vi.fn().mockRejectedValue(new Error('fail'));
54
- const stateUnit = createAdminUsersStateUnit(mockClient({ listUsers, getUserStats }), mockBrowse());
55
+ const stateUnit = createAdminUsersStateUnit(sessionOf(mockClient({ listUsers, getUserStats })), mockBrowse());
55
56
 
56
57
  await firstValueFrom(stateUnit.usersLoading$.pipe(filter((l) => !l)));
57
58
  await firstValueFrom(stateUnit.statsLoading$.pipe(filter((l) => !l)));
@@ -63,7 +64,7 @@ describe('createAdminUsersStateUnit', () => {
63
64
  const listUsers = vi.fn().mockResolvedValue({ users: [] });
64
65
  const getUserStats = vi.fn().mockResolvedValue({ stats: null });
65
66
  const updateUser = vi.fn().mockResolvedValue(undefined);
66
- const stateUnit = createAdminUsersStateUnit(mockClient({ listUsers, getUserStats, updateUser }), mockBrowse());
67
+ const stateUnit = createAdminUsersStateUnit(sessionOf(mockClient({ listUsers, getUserStats, updateUser })), mockBrowse());
67
68
 
68
69
  await firstValueFrom(stateUnit.usersLoading$.pipe(filter((l) => !l)));
69
70
  listUsers.mockClear();
@@ -85,7 +86,7 @@ describe('AdminUsersStateUnit — StateUnit axioms', () => {
85
86
  assertStateUnitAxioms({
86
87
  setup: () => {
87
88
  const browse = disposeProbe();
88
- return { unit: createAdminUsersStateUnit(mockClient(), browse as unknown as ShellStateUnit), passedIn: [browse] };
89
+ return { unit: createAdminUsersStateUnit(sessionOf(mockClient()), browse as unknown as ShellStateUnit), passedIn: [browse] };
89
90
  },
90
91
  surfaces: (u) => [u.users$, u.stats$, u.usersLoading$, u.statsLoading$],
91
92
  });
@@ -3,7 +3,7 @@ import { userDID } from '@semiont/core';
3
3
  import { createDisposer } from '@semiont/sdk';
4
4
  import type { StateUnit } from '@semiont/core';
5
5
  import type { ShellStateUnit } from '../../../state/shell-state-unit';
6
- import type { SemiontClient } from '@semiont/sdk';
6
+ import type { SemiontSession } from '@semiont/sdk';
7
7
 
8
8
  export interface AdminUsersStateUnit extends StateUnit {
9
9
  browse: ShellStateUnit;
@@ -15,9 +15,10 @@ export interface AdminUsersStateUnit extends StateUnit {
15
15
  }
16
16
 
17
17
  export function createAdminUsersStateUnit(
18
- client: SemiontClient,
18
+ session: SemiontSession,
19
19
  browse: ShellStateUnit,
20
20
  ): AdminUsersStateUnit {
21
+ const { client } = session;
21
22
  const disposer = createDisposer();
22
23
  // `browse` (ShellStateUnit) is a *passed-in* dependency owned by the caller
23
24
  // (`useShellStateUnit`), not this unit — do NOT add it to the disposer (it's the
@@ -1,26 +1,37 @@
1
- import { describe, it, expect, vi } from 'vitest';
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
2
  import { firstValueFrom } from 'rxjs';
3
3
  import { filter } from 'rxjs/operators';
4
- import type { SemiontClient } from '@semiont/sdk';
4
+ import type { SemiontSession } from '@semiont/sdk';
5
+ import { AuthNamespace } from '@semiont/sdk';
6
+ import { createTestSession, stubBackend } from '@semiont/sdk/testing';
5
7
  import { createWelcomeStateUnit } from '../welcome-state-unit';
6
- import { assertStateUnitAxioms } from '@semiont/core/testing';
7
-
8
- function mockClient(overrides: {
9
- getMe?: ReturnType<typeof vi.fn>;
10
- acceptTerms?: ReturnType<typeof vi.fn>;
11
- } = {}): SemiontClient {
12
- return {
13
- auth: {
14
- me: overrides.getMe ?? vi.fn().mockResolvedValue({ termsAcceptedAt: undefined }),
15
- acceptTerms: overrides.acceptTerms ?? vi.fn().mockResolvedValue(undefined),
16
- },
17
- } as unknown as SemiontClient;
8
+ import { assertStateUnitAxioms } from '@semiont/core/testing/axioms';
9
+
10
+ // Real session over the scriptable transport (SESSION-TYPED-FACTORIES pilot);
11
+ // auth behavior scripted by prototype spies, the AuthShell precedent — the
12
+ // previous `as unknown as SemiontClient` hand-mock was the M1 disease.
13
+ let getMe: ReturnType<typeof vi.spyOn>;
14
+ let acceptTermsSpy: ReturnType<typeof vi.spyOn>;
15
+
16
+ beforeEach(() => {
17
+ getMe = vi.spyOn(AuthNamespace.prototype, 'me');
18
+ getMe.mockResolvedValue({ termsAcceptedAt: undefined } as never);
19
+ acceptTermsSpy = vi.spyOn(AuthNamespace.prototype, 'acceptTerms');
20
+ acceptTermsSpy.mockResolvedValue(undefined as never);
21
+ });
22
+
23
+ afterEach(() => {
24
+ vi.restoreAllMocks();
25
+ });
26
+
27
+ function makeSession(): SemiontSession {
28
+ return createTestSession({ backend: stubBackend() }).session;
18
29
  }
19
30
 
20
31
  describe('createWelcomeStateUnit', () => {
21
32
  it('fetches user data on creation', async () => {
22
- const getMe = vi.fn().mockResolvedValue({ termsAcceptedAt: '2026-01-01' });
23
- const stateUnit = createWelcomeStateUnit(mockClient({ getMe }));
33
+ getMe.mockResolvedValue({ termsAcceptedAt: '2026-01-01' } as never);
34
+ const stateUnit = createWelcomeStateUnit(makeSession());
24
35
 
25
36
  const data = await firstValueFrom(stateUnit.userData$.pipe(filter((d) => d !== null)));
26
37
  expect(data).toEqual({ termsAcceptedAt: '2026-01-01' });
@@ -29,8 +40,8 @@ describe('createWelcomeStateUnit', () => {
29
40
  });
30
41
 
31
42
  it('initializes with null userData and not processing', async () => {
32
- const getMe = vi.fn().mockReturnValue(new Promise(() => {}));
33
- const stateUnit = createWelcomeStateUnit(mockClient({ getMe }));
43
+ getMe.mockReturnValue(new Promise(() => {}) as never);
44
+ const stateUnit = createWelcomeStateUnit(makeSession());
34
45
 
35
46
  const data = await firstValueFrom(stateUnit.userData$);
36
47
  const processing = await firstValueFrom(stateUnit.isProcessing$);
@@ -41,14 +52,13 @@ describe('createWelcomeStateUnit', () => {
41
52
  });
42
53
 
43
54
  it('acceptTerms sets isProcessing and updates userData', async () => {
44
- const acceptTerms = vi.fn().mockResolvedValue(undefined);
45
- const stateUnit = createWelcomeStateUnit(mockClient({ acceptTerms }));
55
+ const stateUnit = createWelcomeStateUnit(makeSession());
46
56
 
47
57
  await firstValueFrom(stateUnit.userData$.pipe(filter((d) => d !== null)));
48
58
 
49
59
  await stateUnit.acceptTerms();
50
60
 
51
- expect(acceptTerms).toHaveBeenCalledOnce();
61
+ expect(acceptTermsSpy).toHaveBeenCalledOnce();
52
62
 
53
63
  const data = await firstValueFrom(stateUnit.userData$);
54
64
  expect(data?.termsAcceptedAt).toBeDefined();
@@ -60,8 +70,8 @@ describe('createWelcomeStateUnit', () => {
60
70
  });
61
71
 
62
72
  it('acceptTerms resets isProcessing on error', async () => {
63
- const acceptTerms = vi.fn().mockRejectedValue(new Error('fail'));
64
- const stateUnit = createWelcomeStateUnit(mockClient({ acceptTerms }));
73
+ acceptTermsSpy.mockRejectedValue(new Error('fail') as never);
74
+ const stateUnit = createWelcomeStateUnit(makeSession());
65
75
 
66
76
  await firstValueFrom(stateUnit.userData$.pipe(filter((d) => d !== null)));
67
77
 
@@ -74,8 +84,8 @@ describe('createWelcomeStateUnit', () => {
74
84
  });
75
85
 
76
86
  it('handles getMe failure gracefully', async () => {
77
- const getMe = vi.fn().mockRejectedValue(new Error('unauthorized'));
78
- const stateUnit = createWelcomeStateUnit(mockClient({ getMe }));
87
+ getMe.mockRejectedValue(new Error('unauthorized') as never);
88
+ const stateUnit = createWelcomeStateUnit(makeSession());
79
89
 
80
90
  await vi.waitFor(() => expect(getMe).toHaveBeenCalled());
81
91
 
@@ -89,7 +99,7 @@ describe('createWelcomeStateUnit', () => {
89
99
  describe('WelcomeStateUnit — StateUnit axioms', () => {
90
100
  it('satisfies the StateUnit axioms', () => {
91
101
  assertStateUnitAxioms({
92
- setup: () => createWelcomeStateUnit(mockClient()),
102
+ setup: () => createWelcomeStateUnit(makeSession()),
93
103
  surfaces: (u) => [u.userData$, u.isProcessing$],
94
104
  invocations: (u) => [() => u.acceptTerms()],
95
105
  });
@@ -1,7 +1,7 @@
1
1
  import { BehaviorSubject, type Observable } from 'rxjs';
2
2
  import { createDisposer } from '@semiont/sdk';
3
3
  import type { StateUnit } from '@semiont/core';
4
- import type { SemiontClient } from '@semiont/sdk';
4
+ import type { SemiontSession } from '@semiont/sdk';
5
5
 
6
6
  export interface WelcomeStateUnit extends StateUnit {
7
7
  userData$: Observable<{ termsAcceptedAt?: string } | null>;
@@ -9,9 +9,14 @@ export interface WelcomeStateUnit extends StateUnit {
9
9
  acceptTerms(): Promise<void>;
10
10
  }
11
11
 
12
+ // Session-typed (SESSION-TYPED-FACTORIES.md D1): the parameter is the
13
+ // lifetime this unit must not outlive. This page crashed in production when
14
+ // the unit was constructed with `undefined` during session activation —
15
+ // `useSessionStateUnit` + this signature make that unrepresentable.
12
16
  export function createWelcomeStateUnit(
13
- client: SemiontClient,
17
+ session: SemiontSession,
14
18
  ): WelcomeStateUnit {
19
+ const { client } = session;
15
20
  const disposer = createDisposer();
16
21
 
17
22
  const userData$ = new BehaviorSubject<{ termsAcceptedAt?: string } | null>(null);
@@ -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