@semiont/react-ui 0.5.27 → 0.5.29

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 (192) hide show
  1. package/README.md +3 -3
  2. package/dist/{PdfAnnotationCanvas.client-EVUPTUWZ.js → PdfAnnotationCanvas.client-LSH3PZPY.js} +32 -5
  3. package/dist/{ar-OU3DQGC6.js → ar-AZBRADEB.js} +43 -23
  4. package/dist/{bn-PHWCR6AZ.js → bn-CCEW6AOS.js} +43 -23
  5. package/dist/{chunk-VXASXU4K.js → chunk-3ZBKCWUZ.js} +1 -1
  6. package/dist/{chunk-5VDTWRXJ.js → chunk-G4VHL4UA.js} +31 -31
  7. package/dist/{chunk-PCRRBU7H.js → chunk-SDKFZLEX.js} +43 -23
  8. package/dist/{cs-NEXYZ62I.js → cs-HXPF2CTE.js} +43 -23
  9. package/dist/{da-FAT43YCU.js → da-K5NTK7LH.js} +43 -23
  10. package/dist/{de-KRQ3WBCU.js → de-GMRQEDZI.js} +43 -23
  11. package/dist/{el-Q4IU3EKC.js → el-EUB4CI4G.js} +43 -23
  12. package/dist/{en-2KADE7N4.js → en-R5XA26NF.js} +2 -2
  13. package/dist/{es-2IO3R2MK.js → es-Z32DAZ6W.js} +43 -23
  14. package/dist/{fa-Q5GOQFXX.js → fa-G7HN6B32.js} +43 -23
  15. package/dist/{fi-R6NMRWBE.js → fi-3YBLZO7I.js} +43 -23
  16. package/dist/{fr-QY4D5E6J.js → fr-VUBH7BBS.js} +43 -23
  17. package/dist/{he-X6Y77XHY.js → he-WQQEY26B.js} +43 -23
  18. package/dist/{hi-XL7IOEQM.js → hi-HJZ6W4JB.js} +43 -23
  19. package/dist/{id-QZ4ZZS67.js → id-ZOGZ37MI.js} +43 -23
  20. package/dist/index.css +279 -74
  21. package/dist/index.d.ts +318 -393
  22. package/dist/index.js +2619 -2687
  23. package/dist/{it-ME2Q7OB6.js → it-DT6UW66F.js} +43 -23
  24. package/dist/{ja-HX7QASRI.js → ja-RUKQCOLO.js} +43 -23
  25. package/dist/{ko-EQO5AHAV.js → ko-ZH5ADKZM.js} +43 -23
  26. package/dist/{ms-UILFSF3I.js → ms-Y2AFPZTU.js} +43 -23
  27. package/dist/{nl-NNI45PMK.js → nl-OQEWIXNA.js} +43 -23
  28. package/dist/{no-MAL5F6P6.js → no-KPAYJZWY.js} +43 -23
  29. package/dist/{pl-4RMY6JAJ.js → pl-WSXK2UUD.js} +43 -23
  30. package/dist/{pt-VQH2X3QQ.js → pt-NWRJRUCK.js} +43 -23
  31. package/dist/{ro-XXCQO2OG.js → ro-3CGVMOC6.js} +43 -23
  32. package/dist/{sv-7Z6EJ6U4.js → sv-HGSEBA7S.js} +43 -23
  33. package/dist/test-utils.js +16 -7
  34. package/dist/{th-L3DXXYVQ.js → th-NZ4G352Z.js} +43 -23
  35. package/dist/{tr-P3DFVLFJ.js → tr-LGGS4DLW.js} +43 -23
  36. package/dist/{uk-7C5D3PXS.js → uk-AIX536L4.js} +43 -23
  37. package/dist/{vi-ZLTLEO7P.js → vi-V6CVQZLQ.js} +43 -23
  38. package/dist/{zh-5FHVWDIN.js → zh-AGKPGI3L.js} +43 -23
  39. package/package.json +16 -15
  40. package/src/components/AssistProgress.tsx +36 -2
  41. package/src/components/CodeMirrorRenderer.tsx +8 -8
  42. package/src/components/StatusDisplay.tsx +13 -13
  43. package/src/components/__tests__/AssistProgress.test.tsx +59 -0
  44. package/src/components/__tests__/StatusDisplay.test.tsx +9 -9
  45. package/src/components/modals/ComposeStep.tsx +233 -0
  46. package/src/components/modals/ConfigureGatherStep.tsx +10 -18
  47. package/src/components/modals/ConfigureGenerationStep.tsx +178 -34
  48. package/src/components/modals/ConfigureSearchStep.tsx +19 -0
  49. package/src/components/modals/ContextSummary.tsx +185 -72
  50. package/src/components/modals/CorpusPane.tsx +75 -0
  51. package/src/components/modals/DiscardPrompt.tsx +38 -0
  52. package/src/components/modals/GatherContextStep.tsx +237 -107
  53. package/src/components/modals/ReferenceWizardModal.tsx +252 -59
  54. package/src/components/modals/ResourceGenerateModal.tsx +225 -112
  55. package/src/components/modals/SearchModal.css +337 -101
  56. package/src/components/modals/SearchResultsStep.tsx +61 -68
  57. package/src/components/modals/WizardFooter.tsx +7 -1
  58. package/src/components/modals/__tests__/ComposeStep.test.tsx +152 -0
  59. package/src/components/modals/__tests__/ConfigureGatherStep.test.tsx +14 -26
  60. package/src/components/modals/__tests__/ConfigureGenerationStep.test.tsx +206 -8
  61. package/src/components/modals/__tests__/ConfigureSearchStep.test.tsx +24 -1
  62. package/src/components/modals/__tests__/GatherContextStep.resource.test.tsx +463 -25
  63. package/src/components/modals/__tests__/ReferenceWizardModal.test.tsx +442 -39
  64. package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +300 -86
  65. package/src/components/modals/__tests__/SearchResultsStep.test.tsx +61 -0
  66. package/src/components/modals/__tests__/WizardFooter.test.tsx +116 -9
  67. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +34 -1
  68. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +38 -1
  69. package/src/components/resource/AnnotateView.tsx +3 -3
  70. package/src/components/resource/BrowseView.tsx +8 -8
  71. package/src/components/resource/DownloadFileLink.tsx +1 -1
  72. package/src/components/resource/ResourceViewer.tsx +4 -4
  73. package/src/components/resource/__tests__/AnnotateView.embeddable.test.tsx +1 -1
  74. package/src/components/resource/__tests__/BrowseView.embeddable.test.tsx +1 -1
  75. package/src/components/resource/__tests__/BrowseView.reference-hover.test.tsx +1 -1
  76. package/src/components/resource/__tests__/BrowseView.test.tsx +11 -11
  77. package/src/components/resource/__tests__/download-link.test.tsx +6 -6
  78. package/src/components/resource/annotate-renderers.tsx +3 -3
  79. package/src/components/resource/panels/AssistSection.css +35 -0
  80. package/src/components/resource/panels/ReferenceEntry.tsx +11 -4
  81. package/src/components/resource/panels/ReferencesPanel.tsx +9 -1
  82. package/src/components/resource/panels/ResourceInfoPanel.tsx +89 -20
  83. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +7 -2
  84. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +41 -0
  85. package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +1 -1
  86. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +84 -3
  87. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +149 -7
  88. package/src/features/auth/__tests__/SignInForm.a11y.test.tsx +12 -12
  89. package/src/features/auth/components/SignInForm.tsx +53 -53
  90. package/src/features/auth-welcome/state/__tests__/welcome-state-unit.test.ts +2 -2
  91. package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +0 -65
  92. package/src/features/resource-compose/components/ResourceComposePage.tsx +35 -133
  93. package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +1 -100
  94. package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -57
  95. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +1 -1
  96. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +2 -2
  97. package/src/features/resource-discovery/state/discover-state-unit.ts +1 -1
  98. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +73 -17
  99. package/src/features/resource-viewer/__tests__/generation-options.test.ts +53 -0
  100. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +148 -83
  101. package/src/features/resource-viewer/generation-options.ts +29 -0
  102. package/src/styles/core/forms.css +35 -8
  103. package/src/styles/features/resource-viewer.css +0 -22
  104. package/src/styles/features/resource.css +0 -4
  105. package/src/styles/index.css +0 -1
  106. package/translations/ar.json +42 -22
  107. package/translations/bn.json +42 -22
  108. package/translations/cs.json +42 -22
  109. package/translations/da.json +42 -22
  110. package/translations/de.json +42 -22
  111. package/translations/el.json +42 -22
  112. package/translations/en.json +42 -22
  113. package/translations/es.json +42 -22
  114. package/translations/fa.json +42 -22
  115. package/translations/fi.json +42 -22
  116. package/translations/fr.json +42 -22
  117. package/translations/he.json +42 -22
  118. package/translations/hi.json +42 -22
  119. package/translations/id.json +42 -22
  120. package/translations/it.json +42 -22
  121. package/translations/ja.json +42 -22
  122. package/translations/ko.json +42 -22
  123. package/translations/ms.json +42 -22
  124. package/translations/nl.json +42 -22
  125. package/translations/no.json +42 -22
  126. package/translations/pl.json +42 -22
  127. package/translations/pt.json +42 -22
  128. package/translations/ro.json +42 -22
  129. package/translations/sv.json +42 -22
  130. package/translations/th.json +42 -22
  131. package/translations/tr.json +42 -22
  132. package/translations/uk.json +42 -22
  133. package/translations/vi.json +42 -22
  134. package/translations/zh.json +42 -22
  135. package/dist/PdfAnnotationCanvas.client-EVUPTUWZ.js.map +0 -1
  136. package/dist/PdfAnnotationCanvas.client-TGDPPGXJ.css.map +0 -1
  137. package/dist/ar-OU3DQGC6.js.map +0 -1
  138. package/dist/bn-PHWCR6AZ.js.map +0 -1
  139. package/dist/chunk-5VDTWRXJ.js.map +0 -1
  140. package/dist/chunk-BTUG3VJJ.js.map +0 -1
  141. package/dist/chunk-O23TLLXW.js.map +0 -1
  142. package/dist/chunk-PCRRBU7H.js.map +0 -1
  143. package/dist/chunk-QT7LYM72.js.map +0 -1
  144. package/dist/chunk-VXASXU4K.js.map +0 -1
  145. package/dist/cs-NEXYZ62I.js.map +0 -1
  146. package/dist/da-FAT43YCU.js.map +0 -1
  147. package/dist/de-KRQ3WBCU.js.map +0 -1
  148. package/dist/el-Q4IU3EKC.js.map +0 -1
  149. package/dist/en-2KADE7N4.js.map +0 -1
  150. package/dist/es-2IO3R2MK.js.map +0 -1
  151. package/dist/fa-Q5GOQFXX.js.map +0 -1
  152. package/dist/fi-R6NMRWBE.js.map +0 -1
  153. package/dist/fr-QY4D5E6J.js.map +0 -1
  154. package/dist/he-X6Y77XHY.js.map +0 -1
  155. package/dist/hi-XL7IOEQM.js.map +0 -1
  156. package/dist/id-QZ4ZZS67.js.map +0 -1
  157. package/dist/index.css.map +0 -1
  158. package/dist/index.js.map +0 -1
  159. package/dist/integrations/css-modules-helper.js.map +0 -1
  160. package/dist/integrations/styled-components-theme.js.map +0 -1
  161. package/dist/it-ME2Q7OB6.js.map +0 -1
  162. package/dist/ja-HX7QASRI.js.map +0 -1
  163. package/dist/ko-EQO5AHAV.js.map +0 -1
  164. package/dist/ms-UILFSF3I.js.map +0 -1
  165. package/dist/nl-NNI45PMK.js.map +0 -1
  166. package/dist/no-MAL5F6P6.js.map +0 -1
  167. package/dist/pl-4RMY6JAJ.js.map +0 -1
  168. package/dist/pt-VQH2X3QQ.js.map +0 -1
  169. package/dist/ro-XXCQO2OG.js.map +0 -1
  170. package/dist/sv-7Z6EJ6U4.js.map +0 -1
  171. package/dist/test-utils.css.map +0 -1
  172. package/dist/test-utils.js.map +0 -1
  173. package/dist/th-L3DXXYVQ.js.map +0 -1
  174. package/dist/tr-P3DFVLFJ.js.map +0 -1
  175. package/dist/uk-7C5D3PXS.js.map +0 -1
  176. package/dist/vi-ZLTLEO7P.js.map +0 -1
  177. package/dist/zh-5FHVWDIN.js.map +0 -1
  178. package/src/components/modals/ResourceSearchModal.tsx +0 -224
  179. package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +0 -163
  180. package/src/features/admin-exchange/__tests__/AdminExchangePage.test.tsx +0 -137
  181. package/src/features/admin-exchange/__tests__/ExportCard.test.tsx +0 -41
  182. package/src/features/admin-exchange/__tests__/ImportCard.test.tsx +0 -148
  183. package/src/features/admin-exchange/__tests__/ImportProgress.test.tsx +0 -117
  184. package/src/features/admin-exchange/components/AdminExchangePage.tsx +0 -117
  185. package/src/features/admin-exchange/components/ExportCard.tsx +0 -35
  186. package/src/features/admin-exchange/components/ImportCard.tsx +0 -183
  187. package/src/features/admin-exchange/components/ImportProgress.tsx +0 -80
  188. package/src/features/admin-exchange/state/__tests__/exchange-state-unit.test.ts +0 -185
  189. package/src/features/admin-exchange/state/exchange-state-unit.ts +0 -133
  190. package/src/features/moderation-linked-data/__tests__/LinkedDataPage.test.tsx +0 -118
  191. package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +0 -118
  192. package/src/styles/features/exchange.css +0 -374
@@ -2,15 +2,16 @@
2
2
  * ResourceComposePage Component
3
3
  *
4
4
  * Pure React component for creating and editing resources.
5
- * Supports three modes: new resource, clone, and reference completion.
5
+ * Supports two modes: new resource and clone. (Reference completion moved
6
+ * into the resolve wizard — COMPOSE-IN-MODAL.)
6
7
  * All dependencies passed as props - no Next.js hooks!
7
8
  */
8
9
 
9
10
  import React, { useState, useEffect } from 'react';
10
- import { deriveViews, type GatheredContext } from '@semiont/core';
11
- import { capabilitiesOf, AUTHORABLE_MEDIA_TYPES, MEDIA_TYPES, mediaTypeForExtension, isSupportedMediaType, LOCALES } from '@semiont/core';
11
+ import { capabilitiesOf, AUTHORABLE_MEDIA_TYPES, MEDIA_TYPES, mediaTypeForExtension, isSupportedMediaType, LOCALES, folderOf, proposeStoragePath, getStorageUri } from '@semiont/core';
12
+ import type { SupportedMediaType } from '@semiont/core';
12
13
  import type { UploadProgress } from '@semiont/sdk';
13
- import { type CloneData, type ReferenceData } from '../state/compose-page-state-unit';
14
+ import { type CloneData } from '../state/compose-page-state-unit';
14
15
  import { COMMON_PANELS, type ToolbarPanelType } from '../../../state/shell-state-unit';
15
16
  import { buttonStyles } from '../../../lib/button-styles';
16
17
  import { CodeMirrorRenderer } from '../../../components/CodeMirrorRenderer';
@@ -42,10 +43,8 @@ function detectUploadMediaType(file: File): string {
42
43
  }
43
44
 
44
45
  export interface ResourceComposePageProps {
45
- mode: 'new' | 'clone' | 'reference';
46
+ mode: 'new' | 'clone';
46
47
  cloneData?: CloneData | null;
47
- referenceData?: ReferenceData | null;
48
- gatheredContext?: GatheredContext | null;
49
48
 
50
49
  // Available options
51
50
  availableEntityTypes: string[];
@@ -74,10 +73,7 @@ export interface ResourceComposePageProps {
74
73
  translations: {
75
74
  title: string;
76
75
  titleEditClone: string;
77
- titleCompleteReference: string;
78
76
  subtitleClone: string;
79
- subtitleReference: string;
80
- linkedNoticePrefix: string;
81
77
  resourceName: string;
82
78
  resourceNamePlaceholder: string;
83
79
  entityTypes: string;
@@ -99,9 +95,7 @@ export interface ResourceComposePageProps {
99
95
  cancel: string;
100
96
  saving: string;
101
97
  creating: string;
102
- creatingAndLinking: string;
103
98
  saveClonedResource: string;
104
- createAndLinkResource: string;
105
99
  createResource: string;
106
100
  };
107
101
 
@@ -111,7 +105,7 @@ export interface ResourceComposePageProps {
111
105
  }
112
106
 
113
107
  export interface SaveResourceParams {
114
- mode: 'new' | 'clone' | 'reference';
108
+ mode: 'new' | 'clone';
115
109
  name: string;
116
110
  storageUri: string;
117
111
  content?: string;
@@ -121,15 +115,11 @@ export interface SaveResourceParams {
121
115
  entityTypes?: string[];
122
116
  language: string;
123
117
  archiveOriginal?: boolean;
124
- annotationUri?: string;
125
- sourceDocumentId?: string;
126
118
  }
127
119
 
128
120
  export function ResourceComposePage({
129
121
  mode,
130
122
  cloneData,
131
- referenceData,
132
- gatheredContext,
133
123
  availableEntityTypes,
134
124
  initialLocale,
135
125
  theme,
@@ -179,18 +169,27 @@ export function ResourceComposePage({
179
169
  // Archive original checkbox (for clones only)
180
170
  const [archiveOriginal, setArchiveOriginal] = useState(true);
181
171
 
172
+ // D11 — the Save location starts filled and follows the title until the user
173
+ // takes it over; `pathTouched` is derived, so clearing restores the proposal.
174
+ // A clone proposes beside its source; a new resource proposes at the root.
175
+ const composeFormat: SupportedMediaType =
176
+ isSupportedMediaType(selectedFormat) ? selectedFormat : 'text/markdown';
177
+ const pathTouched = storagePath !== '';
178
+ const effectivePath = pathTouched
179
+ ? storagePath
180
+ : proposeStoragePath(
181
+ mode === 'clone' && cloneData ? folderOf(getStorageUri(cloneData.sourceResource)) : '',
182
+ newResourceName,
183
+ composeFormat,
184
+ );
185
+
182
186
  // Initialize form data based on mode
183
187
  useEffect(() => {
184
188
  if (mode === 'clone' && cloneData) {
185
189
  setNewResourceName(cloneData.sourceResource.name);
186
190
  setNewResourceContent(cloneData.sourceContent);
187
- } else if (mode === 'reference' && referenceData) {
188
- setNewResourceName(referenceData.name);
189
- if (referenceData.entityTypes.length > 0) {
190
- setSelectedEntityTypes(referenceData.entityTypes);
191
- }
192
191
  }
193
- }, [mode, cloneData, referenceData]);
192
+ }, [mode, cloneData]);
194
193
 
195
194
  // Handle file upload
196
195
  const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -250,7 +249,7 @@ export function ResourceComposePage({
250
249
  const params: SaveResourceParams = {
251
250
  mode,
252
251
  name: newResourceName,
253
- storageUri: `file://${storagePath}`,
252
+ storageUri: `file://${effectivePath}`,
254
253
  content: newResourceContent,
255
254
  format: uploadedFile ? fileMimeType : selectedFormat,
256
255
  entityTypes: selectedEntityTypes,
@@ -266,20 +265,12 @@ export function ResourceComposePage({
266
265
  if (mode === 'clone') {
267
266
  params.archiveOriginal = archiveOriginal;
268
267
  }
269
- if (referenceData?.annotationUri) {
270
- params.annotationUri = referenceData.annotationUri;
271
- }
272
- if (referenceData?.sourceDocumentId) {
273
- params.sourceDocumentId = referenceData.sourceDocumentId;
274
- }
275
268
 
276
269
  await onSaveResource(params);
277
270
 
278
271
  // Announce success based on mode
279
272
  const successMessage = mode === 'clone'
280
273
  ? 'Resource cloned successfully'
281
- : mode === 'reference'
282
- ? 'Reference completed successfully'
283
274
  : 'Resource created successfully';
284
275
  announceFormSuccess(successMessage);
285
276
  } catch (error) {
@@ -291,13 +282,6 @@ export function ResourceComposePage({
291
282
  };
292
283
 
293
284
  const isClone = mode === 'clone';
294
- const isReferenceCompletion = mode === 'reference';
295
-
296
- // Gathered-context panel — annotation-focus only for now.
297
- const composeFocus = gatheredContext?.focus.kind === 'annotation' ? gatheredContext.focus : null;
298
- const composeConnections = gatheredContext && composeFocus
299
- ? deriveViews(gatheredContext.graph, String(composeFocus.sourceResource.id)).connections
300
- : [];
301
285
 
302
286
  return (
303
287
  <div className={`semiont-page${activePanel && COMMON_PANELS.includes(activePanel as ToolbarPanelType) ? ' semiont-page--panel-open' : ''}`}>
@@ -306,73 +290,15 @@ export function ResourceComposePage({
306
290
  {/* Page Title */}
307
291
  <div className="semiont-page__header">
308
292
  <h1 className="semiont-page__title">
309
- {isClone ? t.titleEditClone : isReferenceCompletion ? t.titleCompleteReference : t.title}
293
+ {isClone ? t.titleEditClone : t.title}
310
294
  </h1>
311
- {(isClone || isReferenceCompletion) && (
295
+ {isClone && (
312
296
  <p className="semiont-page__subtitle">
313
- {isClone ? t.subtitleClone : t.subtitleReference}
297
+ {t.subtitleClone}
314
298
  </p>
315
299
  )}
316
- {isReferenceCompletion && (
317
- <div className="semiont-page__reference-notice">
318
- <p className="semiont-page__reference-notice-text">
319
- {t.linkedNoticePrefix}
320
- </p>
321
- </div>
322
- )}
323
300
  </div>
324
301
 
325
- {/* Gathered Context Panel (from wizard) */}
326
- {gatheredContext && (
327
- <div className="semiont-form__field" style={{
328
- marginBottom: '1.5rem',
329
- padding: '1rem',
330
- backgroundColor: 'var(--semiont-bg-secondary)',
331
- borderRadius: 'var(--semiont-radius-md)',
332
- border: '1px solid var(--semiont-border-primary)',
333
- }}>
334
- <h3 className="semiont-form__label" style={{ marginBottom: '0.75rem' }}>Gathered Context</h3>
335
- {composeFocus?.selected && (
336
- <div style={{
337
- padding: '0.75rem',
338
- backgroundColor: 'var(--semiont-bg-primary)',
339
- borderRadius: 'var(--semiont-radius-md)',
340
- border: '1px solid var(--semiont-border-primary)',
341
- maxHeight: '150px',
342
- overflowY: 'auto',
343
- marginBottom: '0.5rem',
344
- }}>
345
- <div style={{ fontSize: 'var(--semiont-text-sm)', fontFamily: 'monospace', whiteSpace: 'pre-wrap', color: 'var(--semiont-text-secondary)' }}>
346
- {composeFocus.selected.before && <span>{composeFocus.selected.before}</span>}
347
- <span style={{
348
- backgroundColor: 'var(--semiont-color-primary-100)',
349
- padding: '0 0.25rem',
350
- fontWeight: 600,
351
- color: 'var(--semiont-color-primary-900)',
352
- }}>
353
- {composeFocus.selected.text}
354
- </span>
355
- {composeFocus.selected.after && <span>{composeFocus.selected.after}</span>}
356
- </div>
357
- </div>
358
- )}
359
- {composeConnections.length > 0 && (
360
- <div style={{
361
- padding: '0.5rem 0',
362
- fontSize: 'var(--semiont-text-sm)',
363
- color: 'var(--semiont-text-secondary)',
364
- }}>
365
- <span style={{ fontSize: 'var(--semiont-text-xs)', fontWeight: 500, marginRight: '0.5rem' }}>Connections:</span>
366
- {composeConnections.map(conn => (
367
- <span key={conn.resourceId} className="semiont-chip" style={{ fontSize: 'var(--semiont-text-xs)', padding: '0.125rem 0.5rem', marginRight: '0.25rem' }}>
368
- {conn.resourceName}
369
- </span>
370
- ))}
371
- </div>
372
- )}
373
- </div>
374
- )}
375
-
376
302
  {/* Create Form */}
377
303
  <div className="semiont-form">
378
304
  <form onSubmit={handleSaveResource} className="semiont-form__fields semiont-compose-grid">
@@ -405,7 +331,7 @@ export function ResourceComposePage({
405
331
  <input
406
332
  id="storagePath"
407
333
  type="text"
408
- value={storagePath}
334
+ value={effectivePath}
409
335
  onChange={(e) => setStoragePath(e.target.value)}
410
336
  placeholder="docs/my-resource.md"
411
337
  required
@@ -416,7 +342,7 @@ export function ResourceComposePage({
416
342
  </div>
417
343
 
418
344
  {/* Entity Types Selection */}
419
- {(!isReferenceCompletion || selectedEntityTypes.length === 0) && (
345
+ {(
420
346
  <div className="semiont-form__field semiont-form__entity-types">
421
347
  <div className="semiont-form__label">
422
348
  {t.entityTypes}
@@ -462,30 +388,6 @@ export function ResourceComposePage({
462
388
  </div>
463
389
  )}
464
390
 
465
- {/* Entity Types Display for reference completion */}
466
- {isReferenceCompletion && selectedEntityTypes.length > 0 && (
467
- <div className="semiont-form__field semiont-form__entity-types-display" role="region" aria-labelledby="selected-entity-types-label">
468
- <h3 id="selected-entity-types-label" className="semiont-form__label">
469
- {t.entityTypes}
470
- </h3>
471
- <div className="semiont-form__entity-type-tags" role="list">
472
- {selectedEntityTypes.map((type) => (
473
- <span
474
- key={type}
475
- role="listitem"
476
- className="semiont-form__entity-type-tag"
477
- aria-label={`Entity type: ${type}`}
478
- >
479
- {type}
480
- </span>
481
- ))}
482
- </div>
483
- <p className="semiont-form__helper-text" id="reference-entity-types-description">
484
- These entity types were selected when creating the reference
485
- </p>
486
- </div>
487
- )}
488
-
489
391
  {/* Language Selector */}
490
392
  <div className="semiont-form__field">
491
393
  <label htmlFor="language-select" className="semiont-form__label">
@@ -512,7 +414,7 @@ export function ResourceComposePage({
512
414
  <div className="semiont-compose-grid__content">
513
415
 
514
416
  {/* Content Source Toggle - only show for new resources */}
515
- {!isClone && !isReferenceCompletion && (
417
+ {!isClone && (
516
418
  <div className="semiont-form__field">
517
419
  <label className="semiont-form__label">
518
420
  {t.contentSource}
@@ -554,7 +456,7 @@ export function ResourceComposePage({
554
456
  )}
555
457
 
556
458
  {/* Upload File Section */}
557
- {!isClone && !isReferenceCompletion && inputMethod === 'upload' && (
459
+ {!isClone && inputMethod === 'upload' && (
558
460
  <div className="semiont-form__upload-section">
559
461
  <div>
560
462
  <div className="semiont-form__upload-container">
@@ -631,10 +533,10 @@ export function ResourceComposePage({
631
533
  )}
632
534
 
633
535
  {/* Write Content Section */}
634
- {(isClone || isReferenceCompletion || inputMethod === 'write') && (
536
+ {(isClone || inputMethod === 'write') && (
635
537
  <div className="semiont-form__write-section">
636
538
  {/* Format Selector - only for manual entry, not clones */}
637
- {!isClone && !isReferenceCompletion && (
539
+ {!isClone && (
638
540
  <div className="semiont-form__field">
639
541
  <label htmlFor="format-select" className="semiont-form__label">
640
542
  {t.format}
@@ -673,7 +575,7 @@ export function ResourceComposePage({
673
575
  </div>
674
576
 
675
577
  {/* Encoding Selector - only for manual text entry */}
676
- {!isClone && !isReferenceCompletion && (
578
+ {!isClone && (
677
579
  <div className="semiont-form__field">
678
580
  <label htmlFor="charset-select" className="semiont-form__label">
679
581
  {t.encoding}
@@ -731,8 +633,8 @@ export function ResourceComposePage({
731
633
  className={buttonStyles.primary.base}
732
634
  >
733
635
  {isCreating
734
- ? (isClone ? t.saving : isReferenceCompletion ? t.creatingAndLinking : t.creating)
735
- : (isClone ? t.saveClonedResource : isReferenceCompletion ? t.createAndLinkResource : t.createResource)}
636
+ ? (isClone ? t.saving : t.creating)
637
+ : (isClone ? t.saveClonedResource : t.createResource)}
736
638
  </button>
737
639
  </div>
738
640
 
@@ -64,80 +64,6 @@ describe('createComposePageStateUnit', () => {
64
64
  stateUnit.dispose();
65
65
  });
66
66
 
67
- it('detects reference mode from params', async () => {
68
- const stateUnit = createComposePageStateUnit(sessionOf(mockClient()), mockBrowse(), {
69
- annotationUri: 'ann-1',
70
- sourceDocumentId: 'doc-1',
71
- name: 'Reference Doc',
72
- entityTypes: 'Person,Place',
73
- });
74
-
75
- const mode = await firstValueFrom(stateUnit.mode$);
76
- expect(mode).toBe('reference');
77
-
78
- const ref = await firstValueFrom(stateUnit.referenceData$.pipe(filter((r) => r !== null)));
79
- expect(ref!.annotationUri).toBe('ann-1');
80
- expect(ref!.entityTypes).toEqual(['Person', 'Place']);
81
-
82
- stateUnit.dispose();
83
- });
84
-
85
- it('parses storedContext in reference mode', async () => {
86
- // A real `GatheredContext` shape. The old fixture here was
87
- // `{ annotation, sourceContext }` — not a context at all — and it round-tripped
88
- // because nothing checked. That is the hole the guard closes.
89
- const context = {
90
- focus: { kind: 'annotation', annotation: { id: 'ann-1' }, sourceResource: { id: 'doc-1' } },
91
- graph: { nodes: [], edges: [] },
92
- metadata: {},
93
- };
94
- const stateUnit = createComposePageStateUnit(sessionOf(mockClient()), mockBrowse(), {
95
- annotationUri: 'ann-1',
96
- sourceDocumentId: 'doc-1',
97
- name: 'Ref',
98
- storedContext: JSON.stringify(context),
99
- });
100
-
101
- const gathered = await firstValueFrom(stateUnit.gatheredContext$.pipe(filter((g) => g !== null)));
102
- expect(gathered).toEqual(context);
103
-
104
- stateUnit.dispose();
105
- });
106
-
107
- it('ignores a stash that parses but is not a GatheredContext', async () => {
108
- // The realistic case: an entry written by an older build, read back after a
109
- // deploy. It used to sail through and reach `deriveViews`, which maps
110
- // `graph.nodes` during render and throws instead of degrading.
111
- const stateUnit = createComposePageStateUnit(sessionOf(mockClient()), mockBrowse(), {
112
- annotationUri: 'ann-1',
113
- sourceDocumentId: 'doc-1',
114
- name: 'Ref',
115
- storedContext: JSON.stringify({ annotation: { id: 'ann-1' }, sourceContext: 'text' }),
116
- });
117
-
118
- // Treated as NO stash: the ordinary gather path owns the outcome from here.
119
- expect(await firstValueFrom(stateUnit.gatheredContext$)).toBeNull();
120
-
121
- stateUnit.dispose();
122
- });
123
-
124
- it('ignores malformed storedContext', async () => {
125
- const stateUnit = createComposePageStateUnit(sessionOf(mockClient()), mockBrowse(), {
126
- annotationUri: 'ann-1',
127
- sourceDocumentId: 'doc-1',
128
- name: 'Ref',
129
- storedContext: 'not-json{{{',
130
- });
131
-
132
- const loading = await firstValueFrom(stateUnit.loading$.pipe(filter((l) => !l)));
133
- expect(loading).toBe(false);
134
-
135
- const gathered = await firstValueFrom(stateUnit.gatheredContext$);
136
- expect(gathered).toBeNull();
137
-
138
- stateUnit.dispose();
139
- });
140
-
141
67
  it('exposes entity types', async () => {
142
68
  const stateUnit = createComposePageStateUnit(sessionOf(mockClient()), mockBrowse(), {});
143
69
 
@@ -166,31 +92,6 @@ describe('createComposePageStateUnit', () => {
166
92
  stateUnit.dispose();
167
93
  });
168
94
 
169
- it('save in reference mode calls yield.resource then bind.body', async () => {
170
- const resource = mockUpload('new-4');
171
- const body = vi.fn().mockResolvedValue(undefined);
172
- const stateUnit = createComposePageStateUnit(sessionOf(mockClient({ resource, body })), mockBrowse(), {
173
- annotationUri: 'ann-1',
174
- sourceDocumentId: 'doc-1',
175
- name: 'Ref',
176
- });
177
-
178
- const id = await stateUnit.save({
179
- mode: 'reference',
180
- name: 'Ref Doc',
181
- storageUri: '/docs/ref.md',
182
- content: 'content',
183
- language: 'en',
184
- annotationUri: 'ann-1',
185
- sourceDocumentId: 'doc-1',
186
- });
187
-
188
- expect(id).toBe('new-4');
189
- expect(body).toHaveBeenCalledOnce();
190
-
191
- stateUnit.dispose();
192
- });
193
-
194
95
  it('save in clone mode calls yield.createFromToken', async () => {
195
96
  const createFromToken = vi.fn().mockResolvedValue({ resourceId: 'cloned-1' });
196
97
  const stateUnit = createComposePageStateUnit(sessionOf(mockClient({ createFromToken })), mockBrowse(), {
@@ -220,7 +121,7 @@ describe('ComposePageStateUnit — StateUnit axioms', () => {
220
121
  const browse = disposeProbe();
221
122
  return { unit: createComposePageStateUnit(sessionOf(mockClient()), browse as unknown as ShellStateUnit, {}), passedIn: [browse] };
222
123
  },
223
- surfaces: (u) => [u.mode$, u.loading$, u.cloneData$, u.referenceData$, u.gatheredContext$, u.uploadProgress$],
124
+ surfaces: (u) => [u.mode$, u.loading$, u.cloneData$, u.uploadProgress$],
224
125
  });
225
126
  });
226
127
  });
@@ -1,6 +1,6 @@
1
1
  import { BehaviorSubject, type Observable } from 'rxjs';
2
- import type { GatheredContext, AnnotationId, AccessToken, ResourceDescriptor, ResourceId } from '@semiont/core';
3
- import { isGatheredContext, resourceId as makeResourceId, annotationId as makeAnnotationId } from '@semiont/core';
2
+ import type { AccessToken, ResourceDescriptor, ResourceId } from '@semiont/core';
3
+ import { resourceId as makeResourceId } from '@semiont/core';
4
4
  import { createDisposer } from '@semiont/sdk';
5
5
  import type { StateUnit } from '@semiont/core';
6
6
  import type { ShellStateUnit } from '../../../state/shell-state-unit';
@@ -9,16 +9,11 @@ import type { SemiontSession } from '@semiont/sdk';
9
9
  import { decodeWithCharset, extensionForMediaType } from '@semiont/core';
10
10
  import type { UploadProgress } from '@semiont/sdk';
11
11
 
12
- export type ComposeMode = 'new' | 'clone' | 'reference';
12
+ export type ComposeMode = 'new' | 'clone';
13
13
 
14
14
  export interface ComposeParams {
15
15
  mode?: string | undefined;
16
16
  token?: string | undefined;
17
- annotationUri?: string | undefined;
18
- sourceDocumentId?: string | undefined;
19
- name?: string | undefined;
20
- entityTypes?: string | undefined;
21
- storedContext?: string | undefined;
22
17
  }
23
18
 
24
19
  export interface CloneData {
@@ -26,13 +21,6 @@ export interface CloneData {
26
21
  sourceContent: string;
27
22
  }
28
23
 
29
- export interface ReferenceData {
30
- annotationUri: string;
31
- sourceDocumentId: string;
32
- name: string;
33
- entityTypes: string[];
34
- }
35
-
36
24
  export interface SaveResourceParams {
37
25
  mode: ComposeMode;
38
26
  name: string;
@@ -44,8 +32,6 @@ export interface SaveResourceParams {
44
32
  entityTypes?: string[];
45
33
  language: string;
46
34
  archiveOriginal?: boolean;
47
- annotationUri?: string;
48
- sourceDocumentId?: string;
49
35
  }
50
36
 
51
37
  export interface ComposePageStateUnit extends StateUnit {
@@ -53,8 +39,6 @@ export interface ComposePageStateUnit extends StateUnit {
53
39
  mode$: Observable<ComposeMode>;
54
40
  loading$: Observable<boolean>;
55
41
  cloneData$: Observable<CloneData | null>;
56
- referenceData$: Observable<ReferenceData | null>;
57
- gatheredContext$: Observable<GatheredContext | null>;
58
42
  entityTypes: ListState<string[]>;
59
43
  /**
60
44
  * Live upload-progress for the in-flight `save(...)` call. Emits the
@@ -78,43 +62,19 @@ export function createComposePageStateUnit(
78
62
  // (`useShellStateUnit`), not this unit — do NOT add it to the disposer (it's the
79
63
  // shared, app-scoped shell). See packages/sdk/docs/STATE-UNITS.md (composition rule).
80
64
 
81
- const isReferenceMode = Boolean(params.annotationUri && params.sourceDocumentId && params.name);
82
65
  const isCloneMode = params.mode === 'clone' && Boolean(params.token);
83
- const pageMode: ComposeMode = isCloneMode ? 'clone' : isReferenceMode ? 'reference' : 'new';
66
+ const pageMode: ComposeMode = isCloneMode ? 'clone' : 'new';
84
67
 
85
68
  const mode$ = new BehaviorSubject<ComposeMode>(pageMode);
86
69
  const loading$ = new BehaviorSubject<boolean>(true);
87
70
  const cloneData$ = new BehaviorSubject<CloneData | null>(null);
88
- const referenceData$ = new BehaviorSubject<ReferenceData | null>(null);
89
- const gatheredContext$ = new BehaviorSubject<GatheredContext | null>(null);
90
71
  const uploadProgress$ = new BehaviorSubject<UploadProgress | null>(null);
91
72
 
92
73
  const entityTypes = trackList<string[]>(() => client.browse.entityTypes(), []);
93
74
  disposer.add(entityTypes.dispose);
94
75
 
95
76
  // Initialize based on mode
96
- if (isReferenceMode) {
97
- const entityTypes = params.entityTypes ? params.entityTypes.split(',') : [];
98
- referenceData$.next({
99
- annotationUri: params.annotationUri!,
100
- sourceDocumentId: params.sourceDocumentId!,
101
- name: params.name!,
102
- entityTypes,
103
- });
104
- if (params.storedContext) {
105
- // The stash is the one place a `GatheredContext` arrives with its type
106
- // history severed (sessionStorage → JSON.parse), so it is checked here and
107
- // trusted everywhere after. A stale entry — a tab held open across a deploy
108
- // — used to reach `deriveViews`, which maps `graph.nodes` during render and
109
- // throws rather than degrading. A failed guard is treated as NO stash: the
110
- // ordinary gather path runs, with the loading and error states it already has.
111
- try {
112
- const parsed: unknown = JSON.parse(params.storedContext);
113
- if (isGatheredContext(parsed)) gatheredContext$.next(parsed);
114
- } catch { /* ignore malformed */ }
115
- }
116
- loading$.next(false);
117
- } else if (isCloneMode) {
77
+ if (isCloneMode) {
118
78
  void (async () => {
119
79
  try {
120
80
  const tokenResult = await client.yield.fromToken(params.token!);
@@ -184,14 +144,6 @@ export function createComposePageStateUnit(
184
144
  });
185
145
  });
186
146
 
187
- if (saveParams.mode === 'reference' && saveParams.annotationUri && saveParams.sourceDocumentId) {
188
- await client.bind.body(
189
- makeResourceId(saveParams.sourceDocumentId),
190
- makeAnnotationId(saveParams.annotationUri) as AnnotationId,
191
- [{ op: 'add', item: { type: 'SpecificResource' as const, source: newResourceId, purpose: 'linking' as const } }],
192
- );
193
- }
194
-
195
147
  return newResourceId;
196
148
  };
197
149
 
@@ -200,8 +152,6 @@ export function createComposePageStateUnit(
200
152
  mode$: mode$.asObservable(),
201
153
  loading$: loading$.asObservable(),
202
154
  cloneData$: cloneData$.asObservable(),
203
- referenceData$: referenceData$.asObservable(),
204
- gatheredContext$: gatheredContext$.asObservable(),
205
155
  entityTypes: entityTypes.state,
206
156
  uploadProgress$: uploadProgress$.asObservable(),
207
157
  save,
@@ -209,8 +159,6 @@ export function createComposePageStateUnit(
209
159
  mode$.complete();
210
160
  loading$.complete();
211
161
  cloneData$.complete();
212
- referenceData$.complete();
213
- gatheredContext$.complete();
214
162
  uploadProgress$.complete();
215
163
  disposer.dispose();
216
164
  },
@@ -354,7 +354,7 @@ describe('ResourceDiscoveryPage', () => {
354
354
  });
355
355
 
356
356
  it('renders the recentDocuments prop as-is without applying any post-filter', () => {
357
- // The component is now controlled — backend filtering means
357
+ // The component is now controlled — gateway filtering means
358
358
  // `recentDocuments` already contains only the resources matching the
359
359
  // active `selectedEntityType`. The component must not re-filter.
360
360
  const props = createMockProps({
@@ -44,7 +44,7 @@ export interface ResourceDiscoveryPageProps {
44
44
  onSearchQueryChange: (query: string) => void;
45
45
 
46
46
  // Controlled entity-type filter — owned by the state unit so filtering
47
- // pushes to the backend rather than running as a post-fetch array filter.
47
+ // pushes to the gateway rather than running as a post-fetch array filter.
48
48
  selectedEntityType: string;
49
49
  onSelectedEntityTypeChange: (entityType: string) => void;
50
50
 
@@ -106,7 +106,7 @@ export function ResourceDiscoveryPage({
106
106
  const hasSearchQuery = searchQuery.trim() !== '';
107
107
 
108
108
  // When searching, render search results; otherwise render recent.
109
- // Both already arrive entity-type-filtered from the backend — no post-filter here.
109
+ // Both already arrive entity-type-filtered from the gateway — no post-filter here.
110
110
  const filteredResources = hasSearchQuery ? searchDocuments : recentDocuments;
111
111
 
112
112
  // Roving tabindex for entity type filters
@@ -50,7 +50,7 @@ export function createDiscoverStateUnit(
50
50
 
51
51
  // Selected entity-type chip on the Discover page. Drives both the
52
52
  // `recent` list and the search results — filtering happens on the
53
- // backend, not via post-fetch array filtering.
53
+ // gateway, not via post-fetch array filtering.
54
54
  const selectedEntityType$ = new BehaviorSubject<string>('');
55
55
  disposer.add(() => selectedEntityType$.complete());
56
56