@semiont/react-ui 0.5.10 → 0.5.12

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 (81) hide show
  1. package/README.md +1 -1
  2. package/dist/{PdfAnnotationCanvas.client-P37SXBWQ.js → PdfAnnotationCanvas.client-75GY2EDR.js} +15 -6
  3. package/dist/PdfAnnotationCanvas.client-75GY2EDR.js.map +1 -0
  4. package/dist/{chunk-5LFFAVDI.js → chunk-PFUXQPWJ.js} +96 -96
  5. package/dist/{chunk-5LFFAVDI.js.map → chunk-PFUXQPWJ.js.map} +1 -1
  6. package/dist/{chunk-O2MD7TGE.js → chunk-VXASXU4K.js} +10 -2
  7. package/dist/chunk-VXASXU4K.js.map +1 -0
  8. package/dist/index.css +150 -112
  9. package/dist/index.css.map +1 -1
  10. package/dist/index.d.ts +334 -48
  11. package/dist/index.js +2494 -2201
  12. package/dist/index.js.map +1 -1
  13. package/dist/test-utils.d.ts +5 -1
  14. package/dist/test-utils.js +10 -4
  15. package/dist/test-utils.js.map +1 -1
  16. package/package.json +7 -7
  17. package/src/components/ResourceTagsInline.tsx +94 -11
  18. package/src/components/StatusDisplay.css +36 -0
  19. package/src/components/__tests__/ResourceTagsInline.test.tsx +101 -3
  20. package/src/components/annotation/AnnotateToolbar.tsx +53 -28
  21. package/src/components/annotation/__tests__/AnnotateToolbar.display.test.tsx +76 -0
  22. package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +124 -360
  23. package/src/components/image-annotation/AnnotationOverlay.tsx +6 -6
  24. package/src/components/image-annotation/SvgDrawingCanvas.tsx +17 -5
  25. package/src/components/image-annotation/__tests__/AnnotationOverlay.click.test.tsx +137 -0
  26. package/src/components/image-annotation/__tests__/SvgDrawingCanvas.click.test.tsx +108 -0
  27. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +17 -4
  28. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +143 -7
  29. package/src/components/resource/AnnotateView.tsx +59 -38
  30. package/src/components/resource/BrowseView.tsx +172 -138
  31. package/src/components/resource/ResourceViewer.tsx +123 -130
  32. package/src/components/resource/__tests__/AnnotateView.embeddable.test.tsx +55 -0
  33. package/src/components/resource/__tests__/BrowseView.embeddable.test.tsx +49 -0
  34. package/src/components/resource/__tests__/BrowseView.links.test.tsx +60 -0
  35. package/src/components/resource/__tests__/BrowseView.overlay-async.test.tsx +113 -0
  36. package/src/components/resource/__tests__/BrowseView.reference-hover.test.tsx +161 -0
  37. package/src/components/resource/__tests__/BrowseView.registry.test.tsx +43 -0
  38. package/src/components/resource/__tests__/BrowseView.test.tsx +133 -53
  39. package/src/components/resource/__tests__/ResourceViewer.embeddable.test.tsx +102 -0
  40. package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +12 -7
  41. package/src/components/resource/__tests__/ResourceViewer.toolbar-opt-out.test.tsx +135 -0
  42. package/src/components/resource/__tests__/ResourceViewer.toolbar-prefs.test.tsx +200 -0
  43. package/src/components/resource/__tests__/browse-renderers.dispatch.test.tsx +113 -0
  44. package/src/components/resource/__tests__/browse-renderers.test.tsx +34 -0
  45. package/src/components/resource/__tests__/inline-embedding.test.tsx +81 -0
  46. package/src/components/resource/__tests__/media-completeness.test.tsx +88 -0
  47. package/src/components/resource/browse-renderers.tsx +85 -0
  48. package/src/components/resource/panels/AssessmentEntry.tsx +5 -4
  49. package/src/components/resource/panels/AssessmentPanel.tsx +12 -5
  50. package/src/components/resource/panels/AssistSection.tsx +4 -3
  51. package/src/components/resource/panels/CommentEntry.tsx +5 -4
  52. package/src/components/resource/panels/CommentsPanel.tsx +12 -5
  53. package/src/components/resource/panels/HighlightEntry.tsx +5 -4
  54. package/src/components/resource/panels/HighlightPanel.tsx +12 -5
  55. package/src/components/resource/panels/ReferenceEntry.tsx +9 -10
  56. package/src/components/resource/panels/ReferencesPanel.tsx +15 -6
  57. package/src/components/resource/panels/ResourceInfoPanel.tsx +4 -3
  58. package/src/components/resource/panels/TagEntry.tsx +5 -3
  59. package/src/components/resource/panels/TaggingPanel.tsx +11 -4
  60. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +15 -2
  61. package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +28 -20
  62. package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +39 -28
  63. package/src/components/resource/panels/__tests__/AssistSection.test.tsx +46 -17
  64. package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +65 -57
  65. package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +54 -24
  66. package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +27 -17
  67. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +23 -17
  68. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +48 -77
  69. package/src/components/resource/panels/__tests__/ReferencesPanel.headless.test.tsx +87 -0
  70. package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +15 -9
  71. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +78 -59
  72. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +11 -4
  73. package/src/components/resource/panels/__tests__/TagEntry.test.tsx +33 -19
  74. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +16 -7
  75. package/src/components/toolbar/Toolbar.css +14 -0
  76. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +7 -17
  77. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +43 -17
  78. package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +2 -14
  79. package/src/styles/features/resource-viewer.css +19 -0
  80. package/dist/PdfAnnotationCanvas.client-P37SXBWQ.js.map +0 -1
  81. package/dist/chunk-O2MD7TGE.js.map +0 -1
@@ -5,6 +5,7 @@ import userEvent from '@testing-library/user-event';
5
5
  import '@testing-library/jest-dom';
6
6
  import { ReferencesPanel } from '../ReferencesPanel';
7
7
  import type { EventBus } from '@semiont/core';
8
+ import type { SemiontSession } from '@semiont/sdk';
8
9
  import { createTestSemiontWrapper } from '../../../../test-utils';
9
10
 
10
11
  // Composition-based event tracker
@@ -29,8 +30,20 @@ function createEventTracker() {
29
30
  };
30
31
  }
31
32
 
33
+ // Per-test session/wrapper: created in beforeEach — test-utils disposes every
34
+ // created client in a module-scope afterEach, so a module-scope factory call
35
+ // would hand tests after the first a disposed client. The component is
36
+ // provider-free: the `session` passed as a prop (same factory call as the
37
+ // eventBus the tracker attaches to) is the only session it sees.
38
+ let session: SemiontSession;
39
+ let eventBus: EventBus;
40
+ let SemiontWrapper: React.ComponentType<{ children: React.ReactNode }>;
41
+
42
+ beforeEach(() => {
43
+ ({ session, eventBus, SemiontWrapper } = createTestSemiontWrapper());
44
+ });
45
+
32
46
  const renderWithEventBus = (component: React.ReactElement, tracker?: ReturnType<typeof createEventTracker>) => {
33
- const { SemiontWrapper, eventBus } = createTestSemiontWrapper();
34
47
  if (tracker) tracker._attach(eventBus);
35
48
  const Wrapper = ({ children }: { children: React.ReactNode }) => (
36
49
  <SemiontWrapper>{children}</SemiontWrapper>
@@ -86,6 +99,7 @@ describe('ReferencesPanel Component', () => {
86
99
  } as any;
87
100
 
88
101
  const defaultProps = {
102
+ resourceId: 'res-1',
89
103
  allEntityTypes: ['Person', 'Organization', 'Location', 'Date'],
90
104
  isAssisting: false,
91
105
  progress: null,
@@ -95,6 +109,11 @@ describe('ReferencesPanel Component', () => {
95
109
  pendingAnnotation: null,
96
110
  };
97
111
 
112
+ // Per-test props: merges the beforeEach-created session at render time.
113
+ // (Module/describe-scope defaultProps must stay session-free — disposal
114
+ // hazard, see note above renderWithEventBus.)
115
+ const panelProps = () => ({ ...defaultProps, session });
116
+
98
117
  beforeEach(() => {
99
118
  vi.clearAllMocks();
100
119
  });
@@ -105,13 +124,13 @@ describe('ReferencesPanel Component', () => {
105
124
 
106
125
  describe('Rendering', () => {
107
126
  it('should render panel with title', () => {
108
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
127
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
109
128
 
110
129
  expect(screen.getByText('Annotate References')).toBeInTheDocument();
111
130
  });
112
131
 
113
132
  it('should render all entity type buttons', () => {
114
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
133
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
115
134
 
116
135
  expect(screen.getByText('Person')).toBeInTheDocument();
117
136
  expect(screen.getByText('Organization')).toBeInTheDocument();
@@ -120,13 +139,13 @@ describe('ReferencesPanel Component', () => {
120
139
  });
121
140
 
122
141
  it('should show message when no entity types available', () => {
123
- renderWithEventBus(<ReferencesPanel {...defaultProps} allEntityTypes={[]} />);
142
+ renderWithEventBus(<ReferencesPanel {...panelProps()} allEntityTypes={[]} />);
124
143
 
125
144
  expect(screen.getByText('No entity types available')).toBeInTheDocument();
126
145
  });
127
146
 
128
147
  it('should render start detection button', () => {
129
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
148
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
130
149
 
131
150
  expect(screen.getByTitle('Annotate')).toBeInTheDocument();
132
151
  });
@@ -134,7 +153,7 @@ describe('ReferencesPanel Component', () => {
134
153
 
135
154
  describe('Entity Type Selection', () => {
136
155
  it('should toggle entity type selection on click', async () => {
137
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
156
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
138
157
 
139
158
  const personButton = screen.getByText('Person');
140
159
 
@@ -153,7 +172,7 @@ describe('ReferencesPanel Component', () => {
153
172
  });
154
173
 
155
174
  it('should allow multiple selections', async () => {
156
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
175
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
157
176
 
158
177
  const personButton = screen.getByText('Person');
159
178
  const orgButton = screen.getByText('Organization');
@@ -169,7 +188,7 @@ describe('ReferencesPanel Component', () => {
169
188
  });
170
189
 
171
190
  it('should deselect when clicking selected type', async () => {
172
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
191
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
173
192
 
174
193
  const personButton = screen.getByText('Person');
175
194
 
@@ -181,7 +200,7 @@ describe('ReferencesPanel Component', () => {
181
200
  });
182
201
 
183
202
  it('should show selected count', async () => {
184
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
203
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
185
204
 
186
205
  const personButton = screen.getByText('Person');
187
206
  const orgButton = screen.getByText('Organization');
@@ -198,7 +217,7 @@ describe('ReferencesPanel Component', () => {
198
217
  });
199
218
 
200
219
  it('should not show selected count when none selected', () => {
201
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
220
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
202
221
 
203
222
  expect(screen.queryByText(/selected/i)).not.toBeInTheDocument();
204
223
  });
@@ -206,7 +225,7 @@ describe('ReferencesPanel Component', () => {
206
225
 
207
226
  describe('Button Styling', () => {
208
227
  it('should style selected buttons differently', async () => {
209
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
228
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
210
229
 
211
230
  const personButton = screen.getByText('Person');
212
231
 
@@ -221,7 +240,7 @@ describe('ReferencesPanel Component', () => {
221
240
  });
222
241
 
223
242
  it('should have proper ARIA attributes', () => {
224
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
243
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
225
244
 
226
245
  const personButton = screen.getByText('Person');
227
246
 
@@ -230,7 +249,7 @@ describe('ReferencesPanel Component', () => {
230
249
  });
231
250
 
232
251
  it('should have focus styles', () => {
233
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
252
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
234
253
 
235
254
  const personButton = screen.getByText('Person');
236
255
 
@@ -240,7 +259,7 @@ describe('ReferencesPanel Component', () => {
240
259
 
241
260
  describe('Start Annotate Button', () => {
242
261
  it('should be disabled when no types selected', () => {
243
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
262
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
244
263
 
245
264
  const startButton = screen.getByTitle('Annotate');
246
265
 
@@ -248,7 +267,7 @@ describe('ReferencesPanel Component', () => {
248
267
  });
249
268
 
250
269
  it('should be enabled when types are selected', async () => {
251
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
270
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
252
271
 
253
272
  const personButton = screen.getByText('Person');
254
273
  await userEvent.click(personButton);
@@ -260,7 +279,7 @@ describe('ReferencesPanel Component', () => {
260
279
 
261
280
  it('should emit annotate:detect-request event with selected types and includeDescriptiveReferences', async () => {
262
281
  const tracker = createEventTracker();
263
- renderWithEventBus(<ReferencesPanel {...defaultProps} />, tracker);
282
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />, tracker);
264
283
 
265
284
  await userEvent.click(screen.getByText('Person'));
266
285
  await userEvent.click(screen.getByText('Organization'));
@@ -281,7 +300,7 @@ describe('ReferencesPanel Component', () => {
281
300
 
282
301
  it('should emit annotate:detect-request event with includeDescriptiveReferences when checkbox is checked', async () => {
283
302
  const tracker = createEventTracker();
284
- renderWithEventBus(<ReferencesPanel {...defaultProps} />, tracker);
303
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />, tracker);
285
304
 
286
305
  await userEvent.click(screen.getByText('Person'));
287
306
 
@@ -304,7 +323,7 @@ describe('ReferencesPanel Component', () => {
304
323
  });
305
324
 
306
325
  it('should clear selected types after detection starts', async () => {
307
- const { rerender } = renderWithEventBus(<ReferencesPanel {...defaultProps} />);
326
+ const { rerender } = renderWithEventBus(<ReferencesPanel {...panelProps()} />);
308
327
 
309
328
  await userEvent.click(screen.getByText('Person'));
310
329
 
@@ -314,7 +333,7 @@ describe('ReferencesPanel Component', () => {
314
333
  // Simulate detection starting
315
334
  rerender(
316
335
  <ReferencesPanel
317
- {...defaultProps}
336
+ {...panelProps()}
318
337
  isAssisting={true}
319
338
  progress={{ stage: 'analyzing', percentage: 0, message: 'Detecting references...', completedEntityTypes: [] }}
320
339
  />
@@ -323,7 +342,7 @@ describe('ReferencesPanel Component', () => {
323
342
  // Simulate detection completing
324
343
  rerender(
325
344
  <ReferencesPanel
326
- {...defaultProps}
345
+ {...panelProps()}
327
346
  isAssisting={false}
328
347
  progress={{
329
348
  stage: 'complete',
@@ -339,7 +358,7 @@ describe('ReferencesPanel Component', () => {
339
358
  });
340
359
 
341
360
  it('should have proper styling when disabled', () => {
342
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
361
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
343
362
 
344
363
  const startButton = screen.getByTitle('Annotate');
345
364
 
@@ -350,7 +369,7 @@ describe('ReferencesPanel Component', () => {
350
369
  });
351
370
 
352
371
  it('should have proper styling when enabled', async () => {
353
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
372
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
354
373
 
355
374
  await userEvent.click(screen.getByText('Person'));
356
375
 
@@ -367,7 +386,7 @@ describe('ReferencesPanel Component', () => {
367
386
  it('should show progress widget when detecting', () => {
368
387
  renderWithEventBus(
369
388
  <ReferencesPanel
370
- {...defaultProps}
389
+ {...panelProps()}
371
390
  isAssisting={true}
372
391
  progress={{ stage: 'analyzing', percentage: 0, message: 'Detecting references...', completedEntityTypes: [] }}
373
392
  />
@@ -389,7 +408,7 @@ describe('ReferencesPanel Component', () => {
389
408
 
390
409
  renderWithEventBus(
391
410
  <ReferencesPanel
392
- {...defaultProps}
411
+ {...panelProps()}
393
412
  isAssisting={true}
394
413
  progress={progress}
395
414
  />
@@ -403,7 +422,7 @@ describe('ReferencesPanel Component', () => {
403
422
  it('should hide entity type selection during detection', () => {
404
423
  renderWithEventBus(
405
424
  <ReferencesPanel
406
- {...defaultProps}
425
+ {...panelProps()}
407
426
  isAssisting={true}
408
427
  progress={{ stage: 'analyzing', percentage: 0, message: 'Detecting references...', completedEntityTypes: [] }}
409
428
  />
@@ -416,7 +435,7 @@ describe('ReferencesPanel Component', () => {
416
435
  it('should render cancel button when detecting', async () => {
417
436
  renderWithEventBus(
418
437
  <ReferencesPanel
419
- {...defaultProps}
438
+ {...panelProps()}
420
439
  isAssisting={true}
421
440
  progress={{ stage: 'analyzing', percentage: 0, message: 'Detecting references...', completedEntityTypes: [] }}
422
441
  />
@@ -431,7 +450,7 @@ describe('ReferencesPanel Component', () => {
431
450
  it('should show completed log after detection finishes', () => {
432
451
  const { rerender } = renderWithEventBus(
433
452
  <ReferencesPanel
434
- {...defaultProps}
453
+ {...panelProps()}
435
454
  isAssisting={false}
436
455
  progress={{
437
456
  stage: 'complete',
@@ -448,7 +467,7 @@ describe('ReferencesPanel Component', () => {
448
467
  // Parent clears progress after completion
449
468
  rerender(
450
469
  <ReferencesPanel
451
- {...defaultProps}
470
+ {...panelProps()}
452
471
  isAssisting={false}
453
472
  progress={null}
454
473
  />
@@ -461,7 +480,7 @@ describe('ReferencesPanel Component', () => {
461
480
  it('should show found counts in log', () => {
462
481
  const { rerender } = renderWithEventBus(
463
482
  <ReferencesPanel
464
- {...defaultProps}
483
+ {...panelProps()}
465
484
  isAssisting={false}
466
485
  progress={{
467
486
  stage: 'complete',
@@ -473,7 +492,7 @@ describe('ReferencesPanel Component', () => {
473
492
  );
474
493
 
475
494
  rerender(
476
- <ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
495
+ <ReferencesPanel {...panelProps()} isAssisting={false} progress={null} />
477
496
  );
478
497
  expect(screen.getByText(/Found.*5/i)).toBeInTheDocument();
479
498
  });
@@ -481,7 +500,7 @@ describe('ReferencesPanel Component', () => {
481
500
  it('should show checkmarks for completed types', () => {
482
501
  const { rerender } = renderWithEventBus(
483
502
  <ReferencesPanel
484
- {...defaultProps}
503
+ {...panelProps()}
485
504
  isAssisting={false}
486
505
  progress={{
487
506
  stage: 'complete',
@@ -493,7 +512,7 @@ describe('ReferencesPanel Component', () => {
493
512
  );
494
513
 
495
514
  rerender(
496
- <ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
515
+ <ReferencesPanel {...panelProps()} isAssisting={false} progress={null} />
497
516
  );
498
517
  expect(screen.getByText('✓')).toBeInTheDocument();
499
518
  });
@@ -501,7 +520,7 @@ describe('ReferencesPanel Component', () => {
501
520
  it('should show detection log and selection UI together after completion', () => {
502
521
  const { rerender } = renderWithEventBus(
503
522
  <ReferencesPanel
504
- {...defaultProps}
523
+ {...panelProps()}
505
524
  isAssisting={false}
506
525
  progress={{
507
526
  stage: 'complete',
@@ -513,7 +532,7 @@ describe('ReferencesPanel Component', () => {
513
532
  );
514
533
 
515
534
  rerender(
516
- <ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
535
+ <ReferencesPanel {...panelProps()} isAssisting={false} progress={null} />
517
536
  );
518
537
 
519
538
  // Should show both the completed log AND the selection UI
@@ -524,7 +543,7 @@ describe('ReferencesPanel Component', () => {
524
543
  it('should show selection UI immediately after detection completes', async () => {
525
544
  const { rerender } = renderWithEventBus(
526
545
  <ReferencesPanel
527
- {...defaultProps}
546
+ {...panelProps()}
528
547
  isAssisting={false}
529
548
  progress={{
530
549
  stage: 'complete',
@@ -536,7 +555,7 @@ describe('ReferencesPanel Component', () => {
536
555
  );
537
556
 
538
557
  rerender(
539
- <ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
558
+ <ReferencesPanel {...panelProps()} isAssisting={false} progress={null} />
540
559
  );
541
560
 
542
561
  // Selection UI should be immediately available (no button click needed)
@@ -547,7 +566,7 @@ describe('ReferencesPanel Component', () => {
547
566
  it('should not show log when empty', () => {
548
567
  renderWithEventBus(
549
568
  <ReferencesPanel
550
- {...defaultProps}
569
+ {...panelProps()}
551
570
  isAssisting={false}
552
571
  progress={{
553
572
  stage: 'complete',
@@ -566,7 +585,7 @@ describe('ReferencesPanel Component', () => {
566
585
 
567
586
  describe('State Transitions', () => {
568
587
  it('should transition from idle to detecting', () => {
569
- const { rerender } = renderWithEventBus(<ReferencesPanel {...defaultProps} />);
588
+ const { rerender } = renderWithEventBus(<ReferencesPanel {...panelProps()} />);
570
589
 
571
590
  // Idle state
572
591
  expect(screen.getByText('Select entity types')).toBeInTheDocument();
@@ -574,7 +593,7 @@ describe('ReferencesPanel Component', () => {
574
593
  // Start detecting
575
594
  rerender(
576
595
  <ReferencesPanel
577
- {...defaultProps}
596
+ {...panelProps()}
578
597
  isAssisting={true}
579
598
  progress={{ stage: 'analyzing', percentage: 0, message: 'Detecting references...', completedEntityTypes: [] }}
580
599
  />
@@ -588,7 +607,7 @@ describe('ReferencesPanel Component', () => {
588
607
  it('should transition from detecting to complete', () => {
589
608
  const { rerender } = renderWithEventBus(
590
609
  <ReferencesPanel
591
- {...defaultProps}
610
+ {...panelProps()}
592
611
  isAssisting={true}
593
612
  progress={{ stage: 'analyzing', percentage: 0, message: 'Detecting references...', completedEntityTypes: [] }}
594
613
  />
@@ -600,7 +619,7 @@ describe('ReferencesPanel Component', () => {
600
619
  // Complete - first trigger useEffect to copy to lastDetectionLog
601
620
  rerender(
602
621
  <ReferencesPanel
603
- {...defaultProps}
622
+ {...panelProps()}
604
623
  isAssisting={false}
605
624
  progress={{
606
625
  stage: 'complete',
@@ -613,7 +632,7 @@ describe('ReferencesPanel Component', () => {
613
632
 
614
633
  // Then clear progress to show the log
615
634
  rerender(
616
- <ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
635
+ <ReferencesPanel {...panelProps()} isAssisting={false} progress={null} />
617
636
  );
618
637
 
619
638
  expect(screen.queryByTestId('annotation-progress-widget')).not.toBeInTheDocument();
@@ -625,7 +644,7 @@ describe('ReferencesPanel Component', () => {
625
644
  it('should show selection UI after detection completes', async () => {
626
645
  const { rerender } = renderWithEventBus(
627
646
  <ReferencesPanel
628
- {...defaultProps}
647
+ {...panelProps()}
629
648
  isAssisting={false}
630
649
  progress={{
631
650
  stage: 'complete',
@@ -638,14 +657,14 @@ describe('ReferencesPanel Component', () => {
638
657
 
639
658
  // Clear progress to show the log
640
659
  rerender(
641
- <ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
660
+ <ReferencesPanel {...panelProps()} isAssisting={false} progress={null} />
642
661
  );
643
662
 
644
663
  // Selection UI should be immediately available
645
664
  expect(screen.getByText('Select entity types')).toBeInTheDocument();
646
665
 
647
666
  rerender(
648
- <ReferencesPanel {...defaultProps} />
667
+ <ReferencesPanel {...panelProps()} />
649
668
  );
650
669
 
651
670
  expect(screen.getByText('Select entity types')).toBeInTheDocument();
@@ -655,7 +674,7 @@ describe('ReferencesPanel Component', () => {
655
674
  describe('Edge Cases', () => {
656
675
  it('should handle empty entity types array', () => {
657
676
  expect(() => {
658
- renderWithEventBus(<ReferencesPanel {...defaultProps} allEntityTypes={[]} />);
677
+ renderWithEventBus(<ReferencesPanel {...panelProps()} allEntityTypes={[]} />);
659
678
  }).not.toThrow();
660
679
  });
661
680
 
@@ -663,7 +682,7 @@ describe('ReferencesPanel Component', () => {
663
682
  const manyTypes = Array.from({ length: 50 }, (_, i) => `Type${i}`);
664
683
 
665
684
  expect(() => {
666
- renderWithEventBus(<ReferencesPanel {...defaultProps} allEntityTypes={manyTypes} />);
685
+ renderWithEventBus(<ReferencesPanel {...panelProps()} allEntityTypes={manyTypes} />);
667
686
  }).not.toThrow();
668
687
 
669
688
  expect(screen.getByText('Type0')).toBeInTheDocument();
@@ -673,7 +692,7 @@ describe('ReferencesPanel Component', () => {
673
692
  it('should handle entity types with special characters', () => {
674
693
  const specialTypes = ['Type-A', 'Type_B', 'Type.C', 'Type/D'];
675
694
 
676
- renderWithEventBus(<ReferencesPanel {...defaultProps} allEntityTypes={specialTypes} />);
695
+ renderWithEventBus(<ReferencesPanel {...panelProps()} allEntityTypes={specialTypes} />);
677
696
 
678
697
  specialTypes.forEach(type => {
679
698
  expect(screen.getByText(type)).toBeInTheDocument();
@@ -681,7 +700,7 @@ describe('ReferencesPanel Component', () => {
681
700
  });
682
701
 
683
702
  it('should handle selecting and deselecting all types', async () => {
684
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
703
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
685
704
 
686
705
  // Select all
687
706
  for (const type of defaultProps.allEntityTypes) {
@@ -703,7 +722,7 @@ describe('ReferencesPanel Component', () => {
703
722
  });
704
723
 
705
724
  it('should handle rapid selection changes', async () => {
706
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
725
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
707
726
 
708
727
  const personButton = screen.getByText('Person');
709
728
 
@@ -719,7 +738,7 @@ describe('ReferencesPanel Component', () => {
719
738
  it('should handle zero found count in results', () => {
720
739
  renderWithEventBus(
721
740
  <ReferencesPanel
722
- {...defaultProps}
741
+ {...panelProps()}
723
742
  isAssisting={false}
724
743
  progress={{
725
744
  stage: 'complete',
@@ -737,7 +756,7 @@ describe('ReferencesPanel Component', () => {
737
756
  expect(() => {
738
757
  renderWithEventBus(
739
758
  <ReferencesPanel
740
- {...defaultProps}
759
+ {...panelProps()}
741
760
  isAssisting={false}
742
761
  progress={undefined as any}
743
762
  />
@@ -748,21 +767,21 @@ describe('ReferencesPanel Component', () => {
748
767
 
749
768
  describe('Styling and Appearance', () => {
750
769
  it('should have proper panel structure', () => {
751
- const { container } = renderWithEventBus(<ReferencesPanel {...defaultProps} />);
770
+ const { container } = renderWithEventBus(<ReferencesPanel {...panelProps()} />);
752
771
 
753
772
  const panel = container.firstChild as HTMLElement;
754
773
  expect(panel).toHaveClass('semiont-panel');
755
774
  });
756
775
 
757
776
  it('should support dark mode', () => {
758
- const { container } = renderWithEventBus(<ReferencesPanel {...defaultProps} />);
777
+ const { container } = renderWithEventBus(<ReferencesPanel {...panelProps()} />);
759
778
 
760
779
  const panel = container.firstChild as HTMLElement;
761
780
  expect(panel).toHaveClass('semiont-panel');
762
781
  });
763
782
 
764
783
  it('should have title without emoji', () => {
765
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
784
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
766
785
 
767
786
  // The emoji is no longer in the title (it's only in the tab now)
768
787
  const title = screen.getByRole('heading', { level: 2 });
@@ -771,7 +790,7 @@ describe('ReferencesPanel Component', () => {
771
790
  });
772
791
 
773
792
  it('should have proper button layout', () => {
774
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
793
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
775
794
 
776
795
  const buttonContainer = screen.getByText('Person').parentElement;
777
796
  expect(buttonContainer).toHaveClass('semiont-assist-widget__chips');
@@ -780,7 +799,7 @@ describe('ReferencesPanel Component', () => {
780
799
 
781
800
  describe('Accessibility', () => {
782
801
  it('should have proper ARIA labels for selection', async () => {
783
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
802
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
784
803
 
785
804
  const personButton = screen.getByText('Person');
786
805
 
@@ -794,7 +813,7 @@ describe('ReferencesPanel Component', () => {
794
813
  });
795
814
 
796
815
  it('should have proper ARIA pressed states', async () => {
797
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
816
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
798
817
 
799
818
  const personButton = screen.getByText('Person');
800
819
 
@@ -806,7 +825,7 @@ describe('ReferencesPanel Component', () => {
806
825
  });
807
826
 
808
827
  it('should be keyboard navigable', () => {
809
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
828
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
810
829
 
811
830
  const personButton = screen.getByText('Person');
812
831
  personButton.focus();
@@ -3,7 +3,7 @@ import React from 'react';
3
3
  import { render, screen, fireEvent, waitFor } from '@testing-library/react';
4
4
  import '@testing-library/jest-dom';
5
5
  import type { EventBus } from '@semiont/core';
6
- import type { SemiontClient } from '@semiont/sdk';
6
+ import type { SemiontClient, SemiontSession } from '@semiont/sdk';
7
7
  import { ResourceInfoPanel } from '../ResourceInfoPanel';
8
8
  import { createTestSemiontWrapper } from '../../../../test-utils';
9
9
 
@@ -87,13 +87,15 @@ function createEventTracker() {
87
87
  };
88
88
  }
89
89
 
90
- const renderWithEventBus = (component: React.ReactElement, tracker?: ReturnType<typeof createEventTracker>) => {
91
- const { SemiontWrapper, eventBus, client } = createTestSemiontWrapper();
90
+ const renderWithEventBus = (component: React.ReactElement<{ session: SemiontSession | null }>, tracker?: ReturnType<typeof createEventTracker>) => {
91
+ const { SemiontWrapper, eventBus, client, session } = createTestSemiontWrapper();
92
92
  if (tracker) tracker._attach(eventBus, client);
93
93
  const Wrapper = ({ children }: { children: React.ReactNode }) => (
94
94
  <SemiontWrapper>{children}</SemiontWrapper>
95
95
  );
96
- return render(component, { wrapper: Wrapper });
96
+ // The component is provider-free: inject the factory session so it is the
97
+ // SAME session whose client the tracker spies on / whose bus it subscribes.
98
+ return render(React.cloneElement(component, { session }), { wrapper: Wrapper });
97
99
  };
98
100
 
99
101
  describe('ResourceInfoPanel Component', () => {
@@ -103,6 +105,11 @@ describe('ResourceInfoPanel Component', () => {
103
105
  documentLocale: undefined,
104
106
  primaryMediaType: undefined,
105
107
  primaryByteSize: undefined,
108
+ // Satisfies JSX at element construction only; renderWithEventBus always
109
+ // replaces it (via cloneElement) with the per-test factory session. Never
110
+ // put a live session here — module-scope clients get disposed after the
111
+ // first test.
112
+ session: null,
106
113
  };
107
114
 
108
115
  beforeEach(() => {