@semiont/react-ui 0.4.14 → 0.4.15

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 (49) hide show
  1. package/README.md +18 -12
  2. package/dist/KnowledgeBaseSessionContext-CpYaCbnC.d.mts +174 -0
  3. package/dist/{PdfAnnotationCanvas.client-CW6SKH2U.mjs → PdfAnnotationCanvas.client-CHDCGQBR.mjs} +3 -3
  4. package/dist/{chunk-HNZOXH4L.mjs → chunk-OZICDVH7.mjs} +5 -3
  5. package/dist/chunk-OZICDVH7.mjs.map +1 -0
  6. package/dist/chunk-R2U7P4TK.mjs +865 -0
  7. package/dist/chunk-R2U7P4TK.mjs.map +1 -0
  8. package/dist/{chunk-BQJWOK4C.mjs → chunk-VN5NY4SN.mjs} +9 -8
  9. package/dist/chunk-VN5NY4SN.mjs.map +1 -0
  10. package/dist/index.d.mts +139 -169
  11. package/dist/index.mjs +2197 -1947
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/test-utils.d.mts +13 -62
  14. package/dist/test-utils.mjs +40 -21
  15. package/dist/test-utils.mjs.map +1 -1
  16. package/package.json +5 -3
  17. package/src/components/ProtectedErrorBoundary.tsx +95 -0
  18. package/src/components/__tests__/ProtectedErrorBoundary.test.tsx +197 -0
  19. package/src/components/modals/PermissionDeniedModal.tsx +140 -0
  20. package/src/components/modals/ReferenceWizardModal.tsx +3 -2
  21. package/src/components/modals/SessionExpiredModal.tsx +101 -0
  22. package/src/components/modals/__tests__/PermissionDeniedModal.test.tsx +150 -0
  23. package/src/components/modals/__tests__/SessionExpiredModal.test.tsx +115 -0
  24. package/src/components/resource/AnnotationHistory.tsx +5 -6
  25. package/src/components/resource/HistoryEvent.tsx +7 -7
  26. package/src/components/resource/__tests__/AnnotationHistory.test.tsx +33 -34
  27. package/src/components/resource/__tests__/HistoryEvent.test.tsx +17 -19
  28. package/src/components/resource/__tests__/event-formatting.test.ts +70 -94
  29. package/src/components/resource/event-formatting.ts +56 -56
  30. package/src/components/resource/panels/ReferenceEntry.tsx +7 -5
  31. package/src/components/resource/panels/ResourceInfoPanel.tsx +8 -6
  32. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +12 -12
  33. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +1 -0
  34. package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +1 -1
  35. package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +4 -4
  36. package/src/features/resource-viewer/__tests__/AnnotationProgressDismissal.test.tsx +5 -10
  37. package/src/features/resource-viewer/__tests__/BindFlowIntegration.test.tsx +23 -54
  38. package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +6 -6
  39. package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +7 -19
  40. package/src/features/resource-viewer/__tests__/ToastNotifications.test.tsx +1 -1
  41. package/src/features/resource-viewer/__tests__/YieldFlowIntegration.test.tsx +18 -44
  42. package/src/features/resource-viewer/__tests__/annotation-progress-flow.test.tsx +6 -6
  43. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +24 -26
  44. package/dist/TranslationManager-CudgH3gw.d.mts +0 -107
  45. package/dist/chunk-BQJWOK4C.mjs.map +0 -1
  46. package/dist/chunk-HNZOXH4L.mjs.map +0 -1
  47. package/dist/chunk-OL5UST25.mjs +0 -413
  48. package/dist/chunk-OL5UST25.mjs.map +0 -1
  49. /package/dist/{PdfAnnotationCanvas.client-CW6SKH2U.mjs.map → PdfAnnotationCanvas.client-CHDCGQBR.mjs.map} +0 -0
@@ -26,41 +26,41 @@ describe('event-formatting', () => {
26
26
 
27
27
  describe('formatEventType', () => {
28
28
  it('returns translation key for resource events', () => {
29
- expect(formatEventType('resource.created', t)).toBe('resourceCreated');
30
- expect(formatEventType('resource.cloned', t)).toBe('resourceCloned');
31
- expect(formatEventType('resource.archived', t)).toBe('resourceArchived');
32
- expect(formatEventType('resource.unarchived', t)).toBe('resourceUnarchived');
29
+ expect(formatEventType('yield:created', t)).toBe('resourceCreated');
30
+ expect(formatEventType('yield:cloned', t)).toBe('resourceCloned');
31
+ expect(formatEventType('mark:archived', t)).toBe('resourceArchived');
32
+ expect(formatEventType('mark:unarchived', t)).toBe('resourceUnarchived');
33
33
  });
34
34
 
35
- it('returns motivation-specific key for annotation.added', () => {
36
- expect(formatEventType('annotation.added', t, { annotation: { motivation: 'highlighting' } })).toBe('highlightAdded');
37
- expect(formatEventType('annotation.added', t, { annotation: { motivation: 'linking' } })).toBe('referenceCreated');
38
- expect(formatEventType('annotation.added', t, { annotation: { motivation: 'assessing' } })).toBe('assessmentAdded');
39
- expect(formatEventType('annotation.added', t, { annotation: { motivation: 'commenting' } })).toBe('annotationAdded');
35
+ it('returns motivation-specific key for mark:added', () => {
36
+ expect(formatEventType('mark:added', t, { annotation: { motivation: 'highlighting' } })).toBe('highlightAdded');
37
+ expect(formatEventType('mark:added', t, { annotation: { motivation: 'linking' } })).toBe('referenceCreated');
38
+ expect(formatEventType('mark:added', t, { annotation: { motivation: 'assessing' } })).toBe('assessmentAdded');
39
+ expect(formatEventType('mark:added', t, { annotation: { motivation: 'commenting' } })).toBe('annotationAdded');
40
40
  });
41
41
 
42
- it('returns annotationRemoved for annotation.removed', () => {
43
- expect(formatEventType('annotation.removed', t)).toBe('annotationRemoved');
42
+ it('returns annotationRemoved for mark:removed', () => {
43
+ expect(formatEventType('mark:removed', t)).toBe('annotationRemoved');
44
44
  });
45
45
 
46
- it('returns annotationBodyUpdated for annotation.body.updated', () => {
47
- expect(formatEventType('annotation.body.updated', t)).toBe('annotationBodyUpdated');
46
+ it('returns annotationBodyUpdated for mark:body-updated', () => {
47
+ expect(formatEventType('mark:body-updated', t)).toBe('annotationBodyUpdated');
48
48
  });
49
49
 
50
50
  it('returns entitytag keys', () => {
51
- expect(formatEventType('entitytag.added', t)).toBe('entitytagAdded');
52
- expect(formatEventType('entitytag.removed', t)).toBe('entitytagRemoved');
51
+ expect(formatEventType('mark:entity-tag-added', t)).toBe('entitytagAdded');
52
+ expect(formatEventType('mark:entity-tag-removed', t)).toBe('entitytagRemoved');
53
53
  });
54
54
 
55
55
  it('returns jobEvent for job types', () => {
56
- expect(formatEventType('job.completed', t)).toBe('jobEvent');
57
- expect(formatEventType('job.started', t)).toBe('jobEvent');
58
- expect(formatEventType('job.failed', t)).toBe('jobEvent');
56
+ expect(formatEventType('job:completed', t)).toBe('jobEvent');
57
+ expect(formatEventType('job:started', t)).toBe('jobEvent');
58
+ expect(formatEventType('job:failed', t)).toBe('jobEvent');
59
59
  });
60
60
 
61
- it('returns representationEvent for representation types', () => {
62
- expect(formatEventType('representation.added', t)).toBe('representationEvent');
63
- expect(formatEventType('representation.removed', t)).toBe('representationEvent');
61
+ it('returns representationEvent for yield:representation types', () => {
62
+ expect(formatEventType('yield:representation-added', t)).toBe('representationEvent');
63
+ expect(formatEventType('yield:representation-removed', t)).toBe('representationEvent');
64
64
  });
65
65
 
66
66
  it('returns raw type for unknown event types', () => {
@@ -70,33 +70,33 @@ describe('event-formatting', () => {
70
70
 
71
71
  describe('getEventEmoji', () => {
72
72
  it('returns document emoji for resource events', () => {
73
- expect(getEventEmoji('resource.created')).toBe('📄');
74
- expect(getEventEmoji('resource.cloned')).toBe('📄');
73
+ expect(getEventEmoji('yield:created')).toBe('📄');
74
+ expect(getEventEmoji('yield:cloned')).toBe('📄');
75
75
  });
76
76
 
77
- it('returns motivation-specific emoji for annotation.added', () => {
78
- expect(getEventEmoji('annotation.added', { annotation: { motivation: 'highlighting' } })).toBe('🟡');
79
- expect(getEventEmoji('annotation.added', { annotation: { motivation: 'linking' } })).toBeTruthy();
80
- expect(getEventEmoji('annotation.added', { annotation: { motivation: 'assessing' } })).toBe('🔴');
77
+ it('returns motivation-specific emoji for mark:added', () => {
78
+ expect(getEventEmoji('mark:added', { annotation: { motivation: 'highlighting' } })).toBe('🟡');
79
+ expect(getEventEmoji('mark:added', { annotation: { motivation: 'linking' } })).toBeTruthy();
80
+ expect(getEventEmoji('mark:added', { annotation: { motivation: 'assessing' } })).toBe('🔴');
81
81
  });
82
82
 
83
- it('returns trash emoji for annotation.removed', () => {
84
- expect(getEventEmoji('annotation.removed')).toBe('🗑️');
83
+ it('returns trash emoji for mark:removed', () => {
84
+ expect(getEventEmoji('mark:removed')).toBe('🗑️');
85
85
  });
86
86
 
87
- it('returns pencil emoji for annotation.body.updated', () => {
88
- expect(getEventEmoji('annotation.body.updated')).toBe('✏️');
87
+ it('returns pencil emoji for mark:body-updated', () => {
88
+ expect(getEventEmoji('mark:body-updated')).toBe('✏️');
89
89
  });
90
90
 
91
91
  it('returns tag emoji for entitytag events', () => {
92
- expect(getEventEmoji('entitytag.added')).toBe('🏷️');
93
- expect(getEventEmoji('entitytag.removed')).toBe('🏷️');
92
+ expect(getEventEmoji('mark:entity-tag-added')).toBe('🏷️');
93
+ expect(getEventEmoji('mark:entity-tag-removed')).toBe('🏷️');
94
94
  });
95
95
 
96
96
  it('returns appropriate emoji for job events', () => {
97
- expect(getEventEmoji('job.completed')).toBe('🔗');
98
- expect(getEventEmoji('job.started')).toBe('⚙️');
99
- expect(getEventEmoji('job.failed')).toBe('❌');
97
+ expect(getEventEmoji('job:completed')).toBe('🔗');
98
+ expect(getEventEmoji('job:started')).toBe('⚙️');
99
+ expect(getEventEmoji('job:failed')).toBe('❌');
100
100
  });
101
101
 
102
102
  it('returns default emoji for unknown', () => {
@@ -137,55 +137,43 @@ describe('event-formatting', () => {
137
137
  });
138
138
 
139
139
  describe('getEventDisplayContent', () => {
140
- it('returns resource name for resource.created', () => {
141
- const event = {
142
- event: { type: 'resource.created' as const, payload: { name: 'My Document' }, userId: 'u1', timestamp: '' },
143
- } as any;
140
+ it('returns resource name for yield:created', () => {
141
+ const event = { type: 'yield:created' as const, payload: { name: 'My Document' }, userId: 'u1', timestamp: '' } as any;
144
142
  const result = getEventDisplayContent(event, [], []);
145
143
  expect(result).toEqual({ exact: 'My Document', isQuoted: false, isTag: false });
146
144
  });
147
145
 
148
- it('returns resource name for resource.cloned', () => {
149
- const event = {
150
- event: { type: 'resource.cloned' as const, payload: { name: 'Cloned Doc' }, userId: 'u1', timestamp: '' },
151
- } as any;
146
+ it('returns resource name for yield:cloned', () => {
147
+ const event = { type: 'yield:cloned' as const, payload: { name: 'Cloned Doc' }, userId: 'u1', timestamp: '' } as any;
152
148
  const result = getEventDisplayContent(event, [], []);
153
149
  expect(result).toEqual({ exact: 'Cloned Doc', isQuoted: false, isTag: false });
154
150
  });
155
151
 
156
152
  it('returns entity type for entitytag events', () => {
157
- const event = {
158
- event: { type: 'entitytag.added' as const, payload: { entityType: 'Person' }, userId: 'u1', timestamp: '' },
159
- } as any;
153
+ const event = { type: 'mark:entity-tag-added' as const, payload: { entityType: 'Person' }, userId: 'u1', timestamp: '' } as any;
160
154
  const result = getEventDisplayContent(event, [], []);
161
155
  expect(result).toEqual({ exact: 'Person', isQuoted: false, isTag: true });
162
156
  });
163
157
 
164
- it('returns null for job.started', () => {
165
- const event = {
166
- event: { type: 'job.started' as const, payload: {}, userId: 'u1', timestamp: '' },
167
- } as any;
158
+ it('returns null for job:started', () => {
159
+ const event = { type: 'job:started' as const, payload: {}, userId: 'u1', timestamp: '' } as any;
168
160
  expect(getEventDisplayContent(event, [], [])).toBeNull();
169
161
  });
170
162
 
171
- it('returns null for representation events', () => {
172
- const event = {
173
- event: { type: 'representation.added' as const, payload: {}, userId: 'u1', timestamp: '' },
174
- } as any;
163
+ it('returns null for yield:representation events', () => {
164
+ const event = { type: 'yield:representation-added' as const, payload: {}, userId: 'u1', timestamp: '' } as any;
175
165
  expect(getEventDisplayContent(event, [], [])).toBeNull();
176
166
  });
177
167
  });
178
168
 
179
169
  describe('getEventEntityTypes', () => {
180
- it('returns entity types from annotation.added with linking motivation', () => {
170
+ it('returns entity types from mark:added with linking motivation', () => {
181
171
  const event = {
182
- event: {
183
- type: 'annotation.added' as const,
184
- payload: {
185
- annotation: {
186
- motivation: 'linking',
187
- body: { entityTypes: ['Person', 'Place'] },
188
- },
172
+ type: 'mark:added' as const,
173
+ payload: {
174
+ annotation: {
175
+ motivation: 'linking',
176
+ body: { entityTypes: ['Person', 'Place'] },
189
177
  },
190
178
  },
191
179
  } as any;
@@ -194,33 +182,27 @@ describe('event-formatting', () => {
194
182
 
195
183
  it('returns empty array for non-linking annotations', () => {
196
184
  const event = {
197
- event: {
198
- type: 'annotation.added' as const,
199
- payload: {
200
- annotation: { motivation: 'highlighting', body: null },
201
- },
185
+ type: 'mark:added' as const,
186
+ payload: {
187
+ annotation: { motivation: 'highlighting', body: null },
202
188
  },
203
189
  } as any;
204
190
  expect(getEventEntityTypes(event)).toEqual([]);
205
191
  });
206
192
 
207
193
  it('returns empty array for non-annotation events', () => {
208
- const event = {
209
- event: { type: 'resource.created' as const, payload: { name: 'test' } },
210
- } as any;
194
+ const event = { type: 'yield:created' as const, payload: { name: 'test' } } as any;
211
195
  expect(getEventEntityTypes(event)).toEqual([]);
212
196
  });
213
197
  });
214
198
 
215
199
  describe('getResourceCreationDetails', () => {
216
- it('returns created details for resource.created', () => {
200
+ it('returns created details for yield:created', () => {
217
201
  const event = {
218
- event: {
219
- type: 'resource.created' as const,
220
- payload: { name: 'Doc', creationMethod: 'upload' },
221
- userId: 'user-1',
222
- timestamp: '',
223
- },
202
+ type: 'yield:created' as const,
203
+ payload: { name: 'Doc', creationMethod: 'upload' },
204
+ userId: 'user-1',
205
+ timestamp: '',
224
206
  } as any;
225
207
  const result = getResourceCreationDetails(event);
226
208
  expect(result).toEqual({
@@ -231,14 +213,12 @@ describe('event-formatting', () => {
231
213
  });
232
214
  });
233
215
 
234
- it('returns cloned details for resource.cloned', () => {
216
+ it('returns cloned details for yield:cloned', () => {
235
217
  const event = {
236
- event: {
237
- type: 'resource.cloned' as const,
238
- payload: { name: 'Clone', creationMethod: 'clone', parentResourceId: 'parent-1' },
239
- userId: 'user-2',
240
- timestamp: '',
241
- },
218
+ type: 'yield:cloned' as const,
219
+ payload: { name: 'Clone', creationMethod: 'clone', parentResourceId: 'parent-1' },
220
+ userId: 'user-2',
221
+ timestamp: '',
242
222
  } as any;
243
223
  const result = getResourceCreationDetails(event);
244
224
  expect(result).toEqual({
@@ -253,20 +233,16 @@ describe('event-formatting', () => {
253
233
 
254
234
  it('uses fallback method when creationMethod missing', () => {
255
235
  const event = {
256
- event: {
257
- type: 'resource.created' as const,
258
- payload: { name: 'Doc' },
259
- userId: 'u1',
260
- timestamp: '',
261
- },
236
+ type: 'yield:created' as const,
237
+ payload: { name: 'Doc' },
238
+ userId: 'u1',
239
+ timestamp: '',
262
240
  } as any;
263
241
  expect(getResourceCreationDetails(event)?.method).toBe('unknown');
264
242
  });
265
243
 
266
244
  it('returns null for non-creation events', () => {
267
- const event = {
268
- event: { type: 'annotation.added' as const, payload: {} },
269
- } as any;
245
+ const event = { type: 'mark:added' as const, payload: {} } as any;
270
246
  expect(getResourceCreationDetails(event)).toBeNull();
271
247
  });
272
248
  });
@@ -5,7 +5,7 @@
5
5
  * No React dependencies - safe to use in any JavaScript environment.
6
6
  */
7
7
 
8
- import type { StoredEventLike, ResourceEventType } from '@semiont/core';
8
+ import type { StoredEventLike, PersistedEventType } from '@semiont/core';
9
9
  import type { components } from '@semiont/core';
10
10
  import { getExactText, getTargetSelector } from '@semiont/api-client';
11
11
  import { ANNOTATORS } from '../../lib/annotation-registry';
@@ -20,44 +20,44 @@ type TranslateFn = (key: string, params?: Record<string, string | number>) => st
20
20
  /**
21
21
  * Format event type for display with i18n support
22
22
  */
23
- export function formatEventType(type: ResourceEventType, t: TranslateFn, payload?: any): string {
23
+ export function formatEventType(type: PersistedEventType, t: TranslateFn, payload?: any): string {
24
24
  switch (type) {
25
- case 'resource.created':
25
+ case 'yield:created':
26
26
  return t('resourceCreated');
27
- case 'resource.cloned':
27
+ case 'yield:cloned':
28
28
  return t('resourceCloned');
29
- case 'resource.archived':
29
+ case 'mark:archived':
30
30
  return t('resourceArchived');
31
- case 'resource.unarchived':
31
+ case 'mark:unarchived':
32
32
  return t('resourceUnarchived');
33
33
 
34
- case 'annotation.added': {
34
+ case 'mark:added': {
35
35
  const motivation = payload?.annotation?.motivation;
36
36
  if (motivation === 'highlighting') return t('highlightAdded');
37
37
  if (motivation === 'linking') return t('referenceCreated');
38
38
  if (motivation === 'assessing') return t('assessmentAdded');
39
39
  return t('annotationAdded');
40
40
  }
41
- case 'annotation.removed': {
41
+ case 'mark:removed': {
42
42
  return t('annotationRemoved');
43
43
  }
44
- case 'annotation.body.updated': {
44
+ case 'mark:body-updated': {
45
45
  return t('annotationBodyUpdated');
46
46
  }
47
47
 
48
- case 'entitytag.added':
48
+ case 'mark:entity-tag-added':
49
49
  return t('entitytagAdded');
50
- case 'entitytag.removed':
50
+ case 'mark:entity-tag-removed':
51
51
  return t('entitytagRemoved');
52
52
 
53
- case 'job.completed':
54
- case 'job.started':
55
- case 'job.progress':
56
- case 'job.failed':
53
+ case 'job:completed':
54
+ case 'job:started':
55
+ case 'job:progress':
56
+ case 'job:failed':
57
57
  return t('jobEvent');
58
58
 
59
- case 'representation.added':
60
- case 'representation.removed':
59
+ case 'yield:representation-added':
60
+ case 'yield:representation-removed':
61
61
  return t('representationEvent');
62
62
 
63
63
  default:
@@ -69,15 +69,15 @@ export function formatEventType(type: ResourceEventType, t: TranslateFn, payload
69
69
  * Get emoji for event type
70
70
  * For unified annotation events, pass the payload to determine motivation
71
71
  */
72
- export function getEventEmoji(type: ResourceEventType, payload?: any): string {
72
+ export function getEventEmoji(type: PersistedEventType, payload?: any): string {
73
73
  switch (type) {
74
- case 'resource.created':
75
- case 'resource.cloned':
76
- case 'resource.archived':
77
- case 'resource.unarchived':
74
+ case 'yield:created':
75
+ case 'yield:cloned':
76
+ case 'mark:archived':
77
+ case 'mark:unarchived':
78
78
  return '📄';
79
79
 
80
- case 'annotation.added': {
80
+ case 'mark:added': {
81
81
  const motivation = payload?.annotation?.motivation;
82
82
  // Use annotation registry as single source of truth for emojis
83
83
  if (motivation === 'highlighting') return ANNOTATORS.highlight.iconEmoji || '📝';
@@ -85,27 +85,27 @@ export function getEventEmoji(type: ResourceEventType, payload?: any): string {
85
85
  if (motivation === 'assessing') return ANNOTATORS.assessment.iconEmoji || '📝';
86
86
  return '📝';
87
87
  }
88
- case 'annotation.removed': {
88
+ case 'mark:removed': {
89
89
  return '🗑️';
90
90
  }
91
- case 'annotation.body.updated': {
91
+ case 'mark:body-updated': {
92
92
  return '✏️';
93
93
  }
94
94
 
95
- case 'entitytag.added':
96
- case 'entitytag.removed':
95
+ case 'mark:entity-tag-added':
96
+ case 'mark:entity-tag-removed':
97
97
  return '🏷️';
98
98
 
99
- case 'job.completed':
99
+ case 'job:completed':
100
100
  return '🔗'; // Link emoji for linked document creation
101
- case 'job.started':
102
- case 'job.progress':
101
+ case 'job:started':
102
+ case 'job:progress':
103
103
  return '⚙️'; // Gear for job processing
104
- case 'job.failed':
104
+ case 'job:failed':
105
105
  return '❌'; // X mark for failed jobs
106
106
 
107
- case 'representation.added':
108
- case 'representation.removed':
107
+ case 'yield:representation-added':
108
+ case 'yield:representation-removed':
109
109
  return '📄';
110
110
 
111
111
  default:
@@ -148,18 +148,18 @@ export function getEventDisplayContent(
148
148
  annotations: Annotation[], // Unified annotations array (all types)
149
149
  allEvents: StoredEventLike[]
150
150
  ): { exact: string; isQuoted: boolean; isTag: boolean } | null {
151
- const eventData = event.event;
151
+ const eventData = event;
152
152
  const payload = eventData.payload as any;
153
153
 
154
154
  // Use type discriminators for proper narrowing
155
155
  switch (eventData.type) {
156
- case 'resource.created':
157
- case 'resource.cloned': {
156
+ case 'yield:created':
157
+ case 'yield:cloned': {
158
158
  return { exact: payload.name, isQuoted: false, isTag: false };
159
159
  }
160
160
 
161
161
  // Unified annotation events
162
- case 'annotation.body.updated': {
162
+ case 'mark:body-updated': {
163
163
  // Find current annotation to get its text
164
164
  // payload.annotationId is just the UUID, but annotation.id is the full URI
165
165
  const annotation = annotations.find(a =>
@@ -180,16 +180,16 @@ export function getEventDisplayContent(
180
180
  return null;
181
181
  }
182
182
 
183
- case 'annotation.removed': {
183
+ case 'mark:removed': {
184
184
  // Find the original annotation.added event to get the text
185
185
  // payload.annotationId is just the UUID, but annotation.id in the added event is the full URI
186
186
  const addedEvent = allEvents.find(e =>
187
- e.event.type === 'annotation.added' &&
188
- (e.event.payload as any).annotation.id.endsWith(`/annotations/${payload.annotationId}`)
187
+ e.type === 'mark:added' &&
188
+ (e.payload as any).annotation.id.endsWith(`/annotations/${payload.annotationId}`)
189
189
  );
190
- if (addedEvent && addedEvent.event.type === 'annotation.added') {
190
+ if (addedEvent && addedEvent.type === 'mark:added') {
191
191
  try {
192
- const target = (addedEvent.event.payload as any).annotation.target;
192
+ const target = (addedEvent.payload as any).annotation.target;
193
193
  if (typeof target !== 'string' && target.selector) {
194
194
  const exact = getExactText(target.selector);
195
195
  if (exact) {
@@ -203,7 +203,7 @@ export function getEventDisplayContent(
203
203
  return null;
204
204
  }
205
205
 
206
- case 'annotation.added': {
206
+ case 'mark:added': {
207
207
  // New unified event structure - annotation is in payload
208
208
  try {
209
209
  const target = payload.annotation.target;
@@ -219,12 +219,12 @@ export function getEventDisplayContent(
219
219
  return null;
220
220
  }
221
221
 
222
- case 'entitytag.added':
223
- case 'entitytag.removed': {
222
+ case 'mark:entity-tag-added':
223
+ case 'mark:entity-tag-removed': {
224
224
  return { exact: payload.entityType, isQuoted: false, isTag: true };
225
225
  }
226
226
 
227
- case 'job.completed': {
227
+ case 'job:completed': {
228
228
  // Find the annotation that was used to generate the resource
229
229
  if (payload.annotationUri) {
230
230
  const annotation = annotations.find(a =>
@@ -246,11 +246,11 @@ export function getEventDisplayContent(
246
246
  return null;
247
247
  }
248
248
 
249
- case 'job.started':
250
- case 'job.progress':
251
- case 'job.failed':
252
- case 'representation.added':
253
- case 'representation.removed':
249
+ case 'job:started':
250
+ case 'job:progress':
251
+ case 'job:failed':
252
+ case 'yield:representation-added':
253
+ case 'yield:representation-removed':
254
254
  return null;
255
255
 
256
256
  default:
@@ -262,10 +262,10 @@ export function getEventDisplayContent(
262
262
  * Get entity types from event payload
263
263
  */
264
264
  export function getEventEntityTypes(event: StoredEventLike): string[] {
265
- const eventData = event.event;
265
+ const eventData = event;
266
266
  const payload = eventData.payload as any;
267
267
 
268
- if (eventData.type === 'annotation.added') {
268
+ if (eventData.type === 'mark:added') {
269
269
  const motivation = payload.annotation.motivation;
270
270
  const body = payload.annotation.body;
271
271
  if (motivation === 'linking' && body && 'entityTypes' in body) {
@@ -292,10 +292,10 @@ export interface ResourceCreationDetails {
292
292
  * Get resource creation details from event
293
293
  */
294
294
  export function getResourceCreationDetails(event: StoredEventLike): ResourceCreationDetails | null {
295
- const eventData = event.event;
295
+ const eventData = event;
296
296
  const payload = eventData.payload as any;
297
297
 
298
- if (eventData.type === 'resource.created') {
298
+ if (eventData.type === 'yield:created') {
299
299
  return {
300
300
  type: 'created',
301
301
  method: payload.creationMethod || 'unknown',
@@ -304,7 +304,7 @@ export function getResourceCreationDetails(event: StoredEventLike): ResourceCrea
304
304
  };
305
305
  }
306
306
 
307
- if (eventData.type === 'resource.cloned') {
307
+ if (eventData.type === 'yield:cloned') {
308
308
  return {
309
309
  type: 'cloned',
310
310
  method: payload.creationMethod || 'clone',
@@ -9,6 +9,7 @@ import { getAnnotationExactText, isBodyResolved, getBodySource, getFragmentSelec
9
9
  import { getEntityTypes } from '@semiont/ontology';
10
10
  import { getResourceIcon } from '../../../lib/resource-utils';
11
11
  import { useEventBus } from '../../../contexts/EventBusContext';
12
+ import { useApiClient } from '../../../contexts/ApiClientContext';
12
13
  import { useObservableExternalNavigation } from '../../../hooks/useObservableBrowse';
13
14
  import { useHoverEmitter } from '../../../hooks/useBeckonFlow';
14
15
 
@@ -41,6 +42,7 @@ export function ReferenceEntry({
41
42
  }: ReferenceEntryProps) {
42
43
  const t = useTranslations('ReferencesPanel');
43
44
  const eventBus = useEventBus();
45
+ const semiont = useApiClient();
44
46
  const navigate = useObservableExternalNavigation();
45
47
  const hoverProps = useHoverEmitter(reference.id);
46
48
 
@@ -74,11 +76,11 @@ export function ReferenceEntry({
74
76
 
75
77
  const handleUnlink = () => {
76
78
  if (source && resolvedResourceUri) {
77
- eventBus.get('bind:update-body').next({
78
- annotationId: annotationId(reference.id),
79
- resourceId: resourceId(source),
80
- operations: [{ op: 'remove', item: { type: 'SpecificResource', source: resolvedResourceUri } }],
81
- });
79
+ semiont.bind.body(
80
+ resourceId(source),
81
+ annotationId(reference.id),
82
+ [{ op: 'remove', item: { type: 'SpecificResource', source: resolvedResourceUri } }],
83
+ ).catch(() => { /* error handled by events-stream */ });
82
84
  }
83
85
  };
84
86
 
@@ -3,12 +3,13 @@
3
3
  import { useTranslations } from '../../../contexts/TranslationContext';
4
4
  import { useEventBus } from '../../../contexts/EventBusContext';
5
5
  import { formatLocaleDisplay } from '@semiont/api-client';
6
- import type { components } from '@semiont/core';
6
+ import { resourceId as makeResourceId, type components } from '@semiont/core';
7
7
  import './ResourceInfoPanel.css';
8
8
 
9
9
  type Agent = components['schemas']['Agent'];
10
10
 
11
11
  interface Props {
12
+ resourceId: string;
12
13
  documentEntityTypes: string[];
13
14
  documentLocale?: string | undefined;
14
15
  primaryMediaType?: string | undefined;
@@ -25,11 +26,12 @@ interface Props {
25
26
  /**
26
27
  * Panel for displaying resource metadata and management actions
27
28
  *
28
- * @emits yield:clone - Clone this resource. Payload: undefined
29
- * @emits mark:unarchive - Unarchive this resource. Payload: undefined
30
- * @emits mark:archive - Archive this resource. Payload: undefined
29
+ * @emits yield:clone - Clone this resource
30
+ * @emits mark:unarchive - Unarchive this resource
31
+ * @emits mark:archive - Archive this resource
31
32
  */
32
33
  export function ResourceInfoPanel({
34
+ resourceId,
33
35
  documentEntityTypes,
34
36
  documentLocale,
35
37
  primaryMediaType,
@@ -194,7 +196,7 @@ export function ResourceInfoPanel({
194
196
  {isArchived ? (
195
197
  <>
196
198
  <button
197
- onClick={() => eventBus.get('mark:unarchive').next(undefined)}
199
+ onClick={() => eventBus.get('mark:unarchive').next({ resourceId: makeResourceId(resourceId) })}
198
200
  className="semiont-resource-button semiont-resource-button--secondary"
199
201
  >
200
202
  📤 {t('unarchive')}
@@ -206,7 +208,7 @@ export function ResourceInfoPanel({
206
208
  ) : (
207
209
  <>
208
210
  <button
209
- onClick={() => eventBus.get('mark:archive').next(undefined)}
211
+ onClick={() => eventBus.get('mark:archive').next({ resourceId: makeResourceId(resourceId) })}
210
212
  className="semiont-resource-button semiont-resource-button--archive"
211
213
  >
212
214
  📦 {t('archive')}
@@ -5,6 +5,7 @@ import '@testing-library/jest-dom';
5
5
  import { renderWithProviders } from '../../../../test-utils';
6
6
  import userEvent from '@testing-library/user-event';
7
7
  import type { components } from '@semiont/core';
8
+ import { SemiontApiClient } from '@semiont/api-client';
8
9
  import type { RouteBuilder } from '../../../../contexts/RoutingContext';
9
10
 
10
11
  type Annotation = components['schemas']['Annotation'];
@@ -303,28 +304,27 @@ describe('ReferenceEntry', () => {
303
304
  expect(unlinkButton).not.toBeInTheDocument();
304
305
  });
305
306
 
306
- it('should emit bind:update-body on unlink click', async () => {
307
+ it('should call client.bind.body on unlink click', async () => {
307
308
  mockIsBodyResolved.mockReturnValue(true);
308
309
  mockGetBodySource.mockReturnValue('linked-doc');
309
- const unlinkHandler = vi.fn();
310
310
 
311
- const { container, eventBus } = renderWithProviders(
311
+ const bindSpy = vi.spyOn(SemiontApiClient.prototype, 'bindAnnotation').mockResolvedValue({ correlationId: 'c1' });
312
+
313
+ const { container } = renderWithProviders(
312
314
  <ReferenceEntry {...defaultProps} annotateMode={true} />,
313
- { returnEventBus: true }
314
315
  );
315
316
 
316
- const subscription = eventBus!.get('bind:update-body').subscribe(unlinkHandler);
317
-
318
317
  const unlinkButton = container.querySelector('.semiont-reference-unlink')!;
319
318
  await userEvent.click(unlinkButton);
320
319
 
321
- expect(unlinkHandler).toHaveBeenCalledWith({
322
- annotationId: 'ref-1',
323
- resourceId: 'resource-1',
324
- operations: [{ op: 'remove', item: { type: 'SpecificResource', source: 'linked-doc' } }],
325
- });
320
+ expect(bindSpy).toHaveBeenCalledWith(
321
+ 'resource-1',
322
+ 'ref-1',
323
+ { operations: [{ op: 'remove', item: { type: 'SpecificResource', source: 'linked-doc' } }] },
324
+ expect.anything(),
325
+ );
326
326
 
327
- subscription.unsubscribe();
327
+ bindSpy.mockRestore();
328
328
  });
329
329
  });
330
330