@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
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useState, useEffect, useCallback } from 'react';
3
+ import { useState, useEffect, useCallback, useRef } from 'react';
4
4
  import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react';
5
5
  import type { GatheredContext, CollaboratorEntry } from '@semiont/core';
6
6
  import { uuidV4 } from '@semiont/core';
@@ -8,19 +8,24 @@ import { useSemiont } from '../../session/SemiontProvider';
8
8
  import { useObservable } from '../../hooks/useObservable';
9
9
  import { useEventSubscription } from '../../contexts/useEventSubscription';
10
10
  import { GatherContextStep } from './GatherContextStep';
11
- import { ConfigureGenerationStep } from './ConfigureGenerationStep';
11
+ import { ConfigureGenerationStep, freshGenerationDraft } from './ConfigureGenerationStep';
12
12
  import type { GenerationConfig } from './ConfigureGenerationStep';
13
13
  import { ConfigureSearchStep } from './ConfigureSearchStep';
14
14
  import type { SearchConfig } from './ConfigureSearchStep';
15
15
  import type { GenerationDraft } from './ConfigureGenerationStep';
16
16
  import { SearchResultsStep } from './SearchResultsStep';
17
+ import { ComposeStep } from './ComposeStep';
18
+ import type { ComposeDraft, ComposeParams } from './ComposeStep';
19
+ import { DiscardPrompt } from './DiscardPrompt';
20
+ import { useLineNumbers } from '../../contexts/LineNumbersContext';
17
21
  import type { ScoredResult } from './SearchResultsStep';
18
22
 
19
23
  type WizardStep =
20
24
  | { step: 'gather' }
21
25
  | { step: 'configure-search' }
22
26
  | { step: 'search-results'; results: ScoredResult[] }
23
- | { step: 'configure-generation' };
27
+ | { step: 'configure-generation' }
28
+ | { step: 'compose' };
24
29
 
25
30
  export interface ReferenceWizardModalProps {
26
31
  /**
@@ -37,8 +42,16 @@ export interface ReferenceWizardModalProps {
37
42
  resourceId: string | null;
38
43
  /** Default title (selected text) */
39
44
  defaultTitle: string;
45
+ /** Folder of the source resource, so new files land beside it (D11). */
46
+ defaultFolder?: string;
40
47
  /** Entity types from the annotation */
41
48
  entityTypes: string[];
49
+ /**
50
+ * The source resource's display name — a page fact (the wizard opens FROM
51
+ * that resource), shown in the gather skeleton's source line before the
52
+ * gathered context can say it itself.
53
+ */
54
+ resourceName?: string;
42
55
  /** Current locale for generation defaults */
43
56
  locale: string;
44
57
  /** Gathered context state */
@@ -48,21 +61,36 @@ export interface ReferenceWizardModalProps {
48
61
  /** Callbacks */
49
62
  onGenerateSubmit: (referenceId: string, config: GenerationConfig) => void;
50
63
  onLinkResource: (referenceId: string, targetResourceId: string) => void;
51
- onComposeNavigate: (context: GatheredContext, annotationId: string, resourceId: string, title: string, entityTypes: string[]) => void;
64
+ /**
65
+ * Create-and-link (COMPOSE-IN-MODAL): the host runs `yield.resource` then
66
+ * `bind.body` and settles the promise; rejection keeps the modal open with
67
+ * the compose footer re-enabled. Replaces the old navigate-to-page flow.
68
+ */
69
+ onComposeSubmit: (referenceId: string, params: ComposeParams) => Promise<void>;
70
+ /** Picker vocabulary for compose when the reference fixed no entity types. */
71
+ entityTypeOptions?: string[];
72
+ /** Editor hover delay for the compose step's CodeMirror. */
73
+ hoverDelayMs?: number;
52
74
  /** Translation strings */
53
75
  translations: {
54
- gatherTitle: string;
55
- configureGenerationTitle: string;
56
- configureSearchTitle: string;
57
- searchResultsTitle: string;
76
+ /** The ONE title, all steps — the flow is resolving a reference; the
77
+ strategy band below says where in it you are. */
78
+ resolveTitle: string;
58
79
  sourceContextLabel: string;
59
80
  connectionsLabel: string;
60
81
  citedByLabel: string;
82
+ graphPaneTitle: string;
83
+ graphEmpty: string;
84
+ resourceLinkLabel: string;
85
+ corpusPaneTitle: string;
86
+ corpusEmpty: string;
87
+ excludedReceipt: string;
88
+ machineRead: string;
61
89
  userHintLabel: string;
90
+ userHintEffect: string;
62
91
  userHintPlaceholder: string;
63
92
  loadingContext: string;
64
93
  failedContext: string;
65
- cancel: string;
66
94
  search: string;
67
95
  searching: string;
68
96
  generate: string;
@@ -74,6 +102,7 @@ export interface ReferenceWizardModalProps {
74
102
  noResults: string;
75
103
  resourceTitle: string;
76
104
  resourceTitlePlaceholder: string;
105
+ saveLocation: string;
77
106
  additionalInstructions: string;
78
107
  additionalInstructionsPlaceholder: string;
79
108
  language: string;
@@ -84,9 +113,19 @@ export interface ReferenceWizardModalProps {
84
113
  maxLength: string;
85
114
  maxLengthHelp: string;
86
115
  maxLengthCeiling: string;
116
+ outputFormat: string;
117
+ formatExtensionMismatch: string;
87
118
  maxResults: string;
88
119
  semanticScoring: string;
89
120
  semanticScoringHelp: string;
121
+ searchFailed: string;
122
+ entityTypes: string;
123
+ contentLabel: string;
124
+ createAndLink: string;
125
+ creatingAndLinking: string;
126
+ discardDraftPrompt: string;
127
+ discardDraft: string;
128
+ keepEditing: string;
90
129
  };
91
130
  }
92
131
 
@@ -96,14 +135,18 @@ export function ReferenceWizardModal({
96
135
  annotationId,
97
136
  resourceId,
98
137
  defaultTitle,
138
+ defaultFolder,
99
139
  entityTypes,
140
+ resourceName,
100
141
  locale,
101
142
  context,
102
143
  contextLoading,
103
144
  contextError,
104
145
  onGenerateSubmit,
105
146
  onLinkResource,
106
- onComposeNavigate,
147
+ onComposeSubmit,
148
+ entityTypeOptions = [],
149
+ hoverDelayMs = 300,
107
150
  translations: t,
108
151
  generationAgent,
109
152
  }: ReferenceWizardModalProps) {
@@ -113,24 +156,39 @@ export function ReferenceWizardModal({
113
156
  // back unmounts a step; if the step owned its values, Back would silently discard
114
157
  // everything typed — which is exactly what it used to do.
115
158
  const [searchConfig, setSearchConfig] = useState<SearchConfig>({ limit: 10, useSemanticScoring: true });
116
- const [generationDraft, setGenerationDraft] = useState<GenerationDraft>({
117
- title: defaultTitle, storagePath: '', prompt: '', language: locale,
118
- temperature: 0.7, maxTokensText: '500',
119
- });
159
+ const [generationDraft, setGenerationDraft] = useState<GenerationDraft>(
160
+ () => freshGenerationDraft(defaultTitle, locale),
161
+ );
120
162
  const [isSearching, setIsSearching] = useState(false);
163
+ const [searchError, setSearchError] = useState<string | null>(null);
121
164
  const [userHint, setUserHint] = useState('');
165
+ const freshComposeDraft = (): ComposeDraft => ({
166
+ name: defaultTitle, storagePath: '', content: '', entityTypes: [], language: locale,
167
+ });
168
+ const [composeDraft, setComposeDraft] = useState<ComposeDraft>(freshComposeDraft);
169
+ const [showDiscardPrompt, setShowDiscardPrompt] = useState(false);
170
+ // The editor consumes the shared display setting; the step itself stays
171
+ // provider-free, so the wizard resolves it and passes it down.
172
+ const { showLineNumbers } = useLineNumbers();
173
+
174
+ // The dirty baseline is what was SEEDED, not the live prop: defaultTitle
175
+ // derives from observable wizard state and can re-emit while the modal is
176
+ // open; an untouched draft must not read as dirty because the baseline
177
+ // moved under it.
178
+ const seededTitle = useRef(defaultTitle);
122
179
 
123
180
  // Reset to gather step when modal opens
124
181
  useEffect(() => {
125
182
  if (isOpen) {
183
+ seededTitle.current = defaultTitle;
126
184
  setWizardStep({ step: 'gather' });
127
185
  setSearchConfig({ limit: 10, useSemanticScoring: true });
128
- setGenerationDraft({
129
- title: defaultTitle, storagePath: '', prompt: '', language: locale,
130
- temperature: 0.7, maxTokensText: '500',
131
- });
186
+ setGenerationDraft(freshGenerationDraft(defaultTitle, locale));
132
187
  setIsSearching(false);
188
+ setSearchError(null);
133
189
  setUserHint('');
190
+ setComposeDraft(freshComposeDraft());
191
+ setShowDiscardPrompt(false);
134
192
  }
135
193
  }, [isOpen]);
136
194
 
@@ -143,6 +201,17 @@ export function ReferenceWizardModal({
143
201
  }
144
202
  });
145
203
 
204
+ // …and to failures, same scoping. A refused emit (/bus/emit 4xx), a matcher
205
+ // error, and the match unit's timeout all land here — without this
206
+ // subscription every one of them left the button on "Searching…" forever.
207
+ useEventSubscription('match:search-failed', (event) => {
208
+ if (!isOpen) return;
209
+ if (annotationId && event.referenceId === annotationId) {
210
+ setIsSearching(false);
211
+ setSearchError(event.error);
212
+ }
213
+ });
214
+
146
215
  const handleBind = useCallback(() => {
147
216
  setWizardStep({ step: 'configure-search' });
148
217
  }, []);
@@ -158,9 +227,9 @@ export function ReferenceWizardModal({
158
227
  * `focus.userHint` ("hint to supplement or replace the selected text for search and
159
228
  * generation"). So the hint was misplaced on every path, not just missing on one.
160
229
  *
161
- * NOTE: no production code reads `focus.userHint` yet this is correct plumbing
162
- * for a field nothing consumes. Making the hint actually steer search or generation
163
- * is a separate change (see WIZARD-NAVIGATION's execution log).
230
+ * Both consumers read it: the matcher folds it into its search term and its
231
+ * LLM-scoring passage (matcher.ts, since #911), and generation's prompt builder
232
+ * renders it in the annotation section (resource-generation.ts).
164
233
  */
165
234
  const contextWithHint = !context
166
235
  ? null
@@ -173,18 +242,57 @@ export function ReferenceWizardModal({
173
242
  }, []);
174
243
 
175
244
  const handleCompose = useCallback(() => {
176
- if (!contextWithHint || !annotationId || !resourceId) return;
177
- onComposeNavigate(contextWithHint, annotationId, resourceId, defaultTitle, entityTypes);
245
+ if (!context || !annotationId || !resourceId) return;
246
+ setWizardStep({ step: 'compose' });
247
+ }, [context, annotationId, resourceId]);
248
+
249
+ const handleComposeSubmit = useCallback(async (params: ComposeParams) => {
250
+ if (!annotationId) return;
251
+ // Rejection propagates to ComposeStep (footer re-enables); the host
252
+ // surfaces the error. Success closes UNCONDITIONALLY — the dirty guard
253
+ // protects dismissal, not completion.
254
+ await onComposeSubmit(annotationId, params);
255
+ onClose();
256
+ }, [annotationId, onComposeSubmit, onClose]);
257
+
258
+ // D4 (widened by GATHER-AT-THE-TOP): a modal dies on ✕/Escape/backdrop, but
259
+ // typed work must not die with it — WHEREVER the user currently is (drafts
260
+ // survive stepping away, so the guard must too). Typed text only (D5):
261
+ // seeded titles, toggles, and sliders never nag.
262
+ const draftDirty =
263
+ composeDraft.content.trim() !== '' ||
264
+ composeDraft.storagePath.trim() !== '' ||
265
+ composeDraft.name !== seededTitle.current ||
266
+ generationDraft.title !== seededTitle.current ||
267
+ generationDraft.storagePath.trim() !== '' ||
268
+ generationDraft.prompt.trim() !== '';
269
+ const handleDismiss = useCallback(() => {
270
+ if (draftDirty) {
271
+ setShowDiscardPrompt(true);
272
+ return;
273
+ }
178
274
  onClose();
179
- }, [contextWithHint, annotationId, resourceId, defaultTitle, entityTypes, onComposeNavigate, onClose]);
275
+ }, [draftDirty, onClose]);
180
276
 
181
277
  const handleBackToGather = useCallback(() => {
182
278
  setWizardStep({ step: 'gather' });
183
279
  }, []);
184
280
 
281
+ // The strategy steps stack evidence + step content in ONE scroll pane. On
282
+ // entry, start at the BOTTOM: the step's own content (form, results) shows
283
+ // in full, the evidence tucks up under the modal top, reachable by
284
+ // scrolling up. jsdom has no layout (scrollHeight 0), so this is a no-op
285
+ // in tests — the same posture as the viewer's guarded scrolls.
286
+ const stepScrollRef = useRef<HTMLDivElement>(null);
287
+ useEffect(() => {
288
+ const el = stepScrollRef.current;
289
+ if (el) el.scrollTop = el.scrollHeight;
290
+ }, [wizardStep.step]);
291
+
185
292
  const handleSearchSubmit = useCallback((config: SearchConfig) => {
186
293
  if (!annotationId || !contextWithHint || !resourceId) return;
187
294
  setIsSearching(true);
295
+ setSearchError(null);
188
296
  session?.client.match.requestSearch({
189
297
  correlationId: uuidV4(),
190
298
  resourceId,
@@ -208,18 +316,29 @@ export function ReferenceWizardModal({
208
316
  onClose();
209
317
  }, [annotationId, onLinkResource, onClose]);
210
318
 
211
- // Determine title based on step
212
- const stepTitle = wizardStep.step === 'gather'
213
- ? t.gatherTitle
214
- : wizardStep.step === 'configure-generation'
215
- ? t.configureGenerationTitle
216
- : wizardStep.step === 'configure-search'
217
- ? t.configureSearchTitle
218
- : t.searchResultsTitle;
319
+ // The evidence display's translations — shared by the gather step and the
320
+ // strategy steps, which keep the context in view (display-only, GFR A2)
321
+ // above their forms rather than navigating away from it.
322
+ const displayTranslations = {
323
+ loadingContext: t.loadingContext,
324
+ failedContext: t.failedContext,
325
+ sourceContextLabel: t.sourceContextLabel,
326
+ connectionsLabel: t.connectionsLabel,
327
+ citedByLabel: t.citedByLabel,
328
+ graphPaneTitle: t.graphPaneTitle,
329
+ graphEmpty: t.graphEmpty,
330
+ resourceLinkLabel: t.resourceLinkLabel,
331
+ corpusPaneTitle: t.corpusPaneTitle,
332
+ corpusEmpty: t.corpusEmpty,
333
+ excludedReceipt: t.excludedReceipt,
334
+ machineRead: t.machineRead,
335
+ score: t.score,
336
+ };
337
+
219
338
 
220
339
  return (
221
340
  <Transition appear show={isOpen}>
222
- <Dialog as="div" className="semiont-search-modal" onClose={onClose}>
341
+ <Dialog as="div" className="semiont-search-modal" onClose={handleDismiss}>
223
342
  {/* Backdrop */}
224
343
  <TransitionChild
225
344
  enter="ease-out duration-200"
@@ -243,13 +362,15 @@ export function ReferenceWizardModal({
243
362
  leaveFrom="opacity-100 scale-100"
244
363
  leaveTo="opacity-0 scale-95"
245
364
  >
246
- <DialogPanel className={`semiont-search-modal__panel semiont-search-modal__panel--with-border${wizardStep.step === 'search-results' ? ' semiont-search-modal__panel--wide' : ''}${wizardStep.step === 'gather' ? ' semiont-search-modal__panel--gather' : ''}`}>
365
+ {/* Every step shows the evidence display now, so every step is
366
+ wide and gets the gather layout (flex column, capped height). */}
367
+ <DialogPanel className="semiont-search-modal__panel semiont-search-modal__panel--with-border semiont-search-modal__panel--gather semiont-search-modal__panel--wide">
247
368
  <div className="semiont-search-modal__header">
248
369
  <DialogTitle className="semiont-search-modal__title">
249
- {stepTitle}
370
+ {t.resolveTitle}
250
371
  </DialogTitle>
251
372
  <button
252
- onClick={onClose}
373
+ onClick={handleDismiss}
253
374
  className="semiont-search-modal__close-button"
254
375
  aria-label="Close"
255
376
  >
@@ -257,44 +378,98 @@ export function ReferenceWizardModal({
257
378
  </button>
258
379
  </div>
259
380
 
381
+ {showDiscardPrompt && (
382
+ <DiscardPrompt
383
+ prompt={t.discardDraftPrompt}
384
+ discardLabel={t.discardDraft}
385
+ keepLabel={t.keepEditing}
386
+ onDiscard={() => { setShowDiscardPrompt(false); onClose(); }}
387
+ onKeepEditing={() => setShowDiscardPrompt(false)}
388
+ />
389
+ )}
390
+
260
391
  {wizardStep.step === 'gather' && (
261
392
  <GatherContextStep
262
393
  context={context}
263
394
  contextLoading={contextLoading}
264
395
  contextError={contextError}
265
- userHint={userHint}
266
- onUserHintChange={setUserHint}
267
- onBind={handleBind}
268
- onGenerate={handleGenerate}
269
- onCompose={handleCompose}
396
+ pending={{ exact: defaultTitle, entityTypes, ...(resourceName ? { resourceName } : {}) }}
397
+ annotate={{
398
+ userHint,
399
+ onUserHintChange: setUserHint,
400
+ onBind: handleBind,
401
+ onGenerate: handleGenerate,
402
+ onCompose: handleCompose,
403
+ translations: {
404
+ search: t.search,
405
+ generate: t.generate,
406
+ compose: t.compose,
407
+ resolutionStrategyLabel: t.resolutionStrategyLabel,
408
+ userHintLabel: t.userHintLabel,
409
+ userHintEffect: t.userHintEffect,
410
+ userHintPlaceholder: t.userHintPlaceholder,
411
+ },
412
+ }}
413
+ translations={displayTranslations}
414
+ />
415
+ )}
416
+
417
+ {wizardStep.step === 'compose' && context && (
418
+ <div className="semiont-wizard__step-scroll" ref={stepScrollRef}>
419
+ <GatherContextStep
420
+ context={context}
421
+ contextLoading={contextLoading}
422
+ contextError={contextError}
423
+ chosenStrategy={{ label: t.resolutionStrategyLabel, value: `✍️ ${t.compose}` }}
424
+ translations={displayTranslations}
425
+ />
426
+ <ComposeStep
427
+ draft={composeDraft}
428
+ onDraftChange={(patch) => setComposeDraft((d) => ({ ...d, ...patch }))}
429
+ defaultFolder={defaultFolder}
430
+ referenceEntityTypes={entityTypes}
431
+ entityTypeOptions={entityTypeOptions}
432
+ showLineNumbers={showLineNumbers}
433
+ hoverDelayMs={hoverDelayMs}
434
+ onBack={handleBackToGather}
435
+ onCompose={handleComposeSubmit}
270
436
  translations={{
271
- title: t.gatherTitle,
272
- sourceContextLabel: t.sourceContextLabel,
273
- connectionsLabel: t.connectionsLabel,
274
- citedByLabel: t.citedByLabel,
275
- userHintLabel: t.userHintLabel,
276
- userHintPlaceholder: t.userHintPlaceholder,
277
- loadingContext: t.loadingContext,
278
- failedContext: t.failedContext,
279
- search: t.search,
280
- generate: t.generate,
281
- compose: t.compose,
282
- resolutionStrategyLabel: t.resolutionStrategyLabel,
437
+ resourceTitle: t.resourceTitle,
438
+ resourceTitlePlaceholder: t.resourceTitlePlaceholder,
439
+ saveLocation: t.saveLocation,
440
+ entityTypes: t.entityTypes,
441
+ language: t.language,
442
+ contentLabel: t.contentLabel,
443
+ back: t.back,
444
+ createAndLink: t.createAndLink,
445
+ creatingAndLinking: t.creatingAndLinking,
283
446
  }}
284
447
  />
448
+ </div>
285
449
  )}
286
450
 
287
451
  {wizardStep.step === 'configure-generation' && context && (
452
+ <div className="semiont-wizard__step-scroll" ref={stepScrollRef}>
453
+ <GatherContextStep
454
+ context={context}
455
+ contextLoading={contextLoading}
456
+ contextError={contextError}
457
+ chosenStrategy={{ label: t.resolutionStrategyLabel, value: `✨ ${t.generate}` }}
458
+ translations={displayTranslations}
459
+ />
288
460
  <ConfigureGenerationStep
289
461
  {...(generationAgent ? { generationAgent } : {})}
462
+ {...(userHint ? { hintEcho: { label: t.userHintLabel, value: userHint } } : {})}
290
463
  context={contextWithHint ?? context}
291
464
  config={generationDraft}
292
465
  onConfigChange={setGenerationDraft}
466
+ defaultFolder={defaultFolder}
293
467
  onBack={handleBackToGather}
294
468
  onGenerate={handleGenerateSubmit}
295
469
  translations={{
296
470
  resourceTitle: t.resourceTitle,
297
471
  resourceTitlePlaceholder: t.resourceTitlePlaceholder,
472
+ saveLocation: t.saveLocation,
298
473
  additionalInstructions: t.additionalInstructions,
299
474
  additionalInstructionsPlaceholder: t.additionalInstructionsPlaceholder,
300
475
  language: t.language,
@@ -305,17 +480,30 @@ export function ReferenceWizardModal({
305
480
  maxLength: t.maxLength,
306
481
  maxLengthHelp: t.maxLengthHelp,
307
482
  maxLengthCeiling: t.maxLengthCeiling,
483
+ outputFormat: t.outputFormat,
484
+ formatExtensionMismatch: t.formatExtensionMismatch,
308
485
  back: t.back,
309
486
  generate: t.generate,
310
487
  }}
311
488
  />
489
+ </div>
312
490
  )}
313
491
 
314
492
  {wizardStep.step === 'configure-search' && (
493
+ <div className="semiont-wizard__step-scroll" ref={stepScrollRef}>
494
+ <GatherContextStep
495
+ context={context}
496
+ contextLoading={contextLoading}
497
+ contextError={contextError}
498
+ chosenStrategy={{ label: t.resolutionStrategyLabel, value: `🔍 ${t.search}` }}
499
+ translations={displayTranslations}
500
+ />
315
501
  <ConfigureSearchStep
316
502
  config={searchConfig}
503
+ {...(userHint ? { hintEcho: { label: t.userHintLabel, value: userHint } } : {})}
317
504
  onConfigChange={setSearchConfig}
318
505
  isSearching={isSearching}
506
+ searchError={searchError}
319
507
  onBack={handleBackToGather}
320
508
  onSearch={handleSearchSubmit}
321
509
  translations={{
@@ -325,14 +513,23 @@ export function ReferenceWizardModal({
325
513
  back: t.back,
326
514
  search: t.search,
327
515
  searching: t.searching,
516
+ searchFailed: t.searchFailed,
328
517
  }}
329
518
  />
519
+ </div>
330
520
  )}
331
521
 
332
522
  {wizardStep.step === 'search-results' && context && (
523
+ <div className="semiont-wizard__step-scroll" ref={stepScrollRef}>
524
+ <GatherContextStep
525
+ context={context}
526
+ contextLoading={contextLoading}
527
+ contextError={contextError}
528
+ chosenStrategy={{ label: t.resolutionStrategyLabel, value: `🔍 ${t.search}` }}
529
+ translations={displayTranslations}
530
+ />
333
531
  <SearchResultsStep
334
532
  results={wizardStep.results}
335
- context={context}
336
533
  onLink={handleLink}
337
534
  onBack={handleBackToGather}
338
535
  translations={{
@@ -340,13 +537,9 @@ export function ReferenceWizardModal({
340
537
  link: t.link,
341
538
  back: t.back,
342
539
  score: t.score,
343
- sourceContextLabel: t.sourceContextLabel,
344
- connectionsLabel: t.connectionsLabel,
345
- citedByLabel: t.citedByLabel,
346
- userHintLabel: t.userHintLabel,
347
- userHintPlaceholder: t.userHintPlaceholder,
348
540
  }}
349
541
  />
542
+ </div>
350
543
  )}
351
544
  </DialogPanel>
352
545
  </TransitionChild>