@semiont/react-ui 0.5.25 → 0.5.27

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 (267) hide show
  1. package/README.md +28 -4
  2. package/dist/PdfAnnotationCanvas.client-EVUPTUWZ.js +677 -0
  3. package/dist/PdfAnnotationCanvas.client-EVUPTUWZ.js.map +1 -0
  4. package/dist/PdfAnnotationCanvas.client-TGDPPGXJ.css +185 -0
  5. package/dist/PdfAnnotationCanvas.client-TGDPPGXJ.css.map +1 -0
  6. package/dist/{ar-CULRHLNG.js → ar-OU3DQGC6.js} +65 -7
  7. package/dist/ar-OU3DQGC6.js.map +1 -0
  8. package/dist/{bn-HYR7CEL2.js → bn-PHWCR6AZ.js} +65 -7
  9. package/dist/bn-PHWCR6AZ.js.map +1 -0
  10. package/dist/chunk-5VDTWRXJ.js +287 -0
  11. package/dist/chunk-5VDTWRXJ.js.map +1 -0
  12. package/dist/{chunk-TG2XQEC4.js → chunk-BTUG3VJJ.js} +18 -298
  13. package/dist/chunk-BTUG3VJJ.js.map +1 -0
  14. package/dist/{chunk-NH6L2GIL.js → chunk-PCRRBU7H.js} +65 -7
  15. package/dist/chunk-PCRRBU7H.js.map +1 -0
  16. package/dist/{cs-YBRWY7NH.js → cs-NEXYZ62I.js} +65 -7
  17. package/dist/cs-NEXYZ62I.js.map +1 -0
  18. package/dist/{da-PHDEOBV3.js → da-FAT43YCU.js} +65 -7
  19. package/dist/da-FAT43YCU.js.map +1 -0
  20. package/dist/{de-O4UCCVLJ.js → de-KRQ3WBCU.js} +65 -7
  21. package/dist/de-KRQ3WBCU.js.map +1 -0
  22. package/dist/{el-MVGO6WZV.js → el-Q4IU3EKC.js} +65 -7
  23. package/dist/el-Q4IU3EKC.js.map +1 -0
  24. package/dist/{en-2KQILFKQ.js → en-2KADE7N4.js} +6 -2
  25. package/dist/{es-USSLXQY7.js → es-2IO3R2MK.js} +65 -7
  26. package/dist/es-2IO3R2MK.js.map +1 -0
  27. package/dist/{fa-WMAREFWY.js → fa-Q5GOQFXX.js} +65 -7
  28. package/dist/fa-Q5GOQFXX.js.map +1 -0
  29. package/dist/{fi-B3NBNOTS.js → fi-R6NMRWBE.js} +65 -7
  30. package/dist/fi-R6NMRWBE.js.map +1 -0
  31. package/dist/{fr-TXYK5T5W.js → fr-QY4D5E6J.js} +65 -7
  32. package/dist/fr-QY4D5E6J.js.map +1 -0
  33. package/dist/{he-JCJACW5D.js → he-X6Y77XHY.js} +65 -7
  34. package/dist/he-X6Y77XHY.js.map +1 -0
  35. package/dist/{hi-VOQLZNJU.js → hi-XL7IOEQM.js} +65 -7
  36. package/dist/hi-XL7IOEQM.js.map +1 -0
  37. package/dist/{id-BFN5RZNZ.js → id-QZ4ZZS67.js} +65 -7
  38. package/dist/id-QZ4ZZS67.js.map +1 -0
  39. package/dist/index.css +241 -508
  40. package/dist/index.css.map +1 -1
  41. package/dist/index.d.ts +155 -54
  42. package/dist/index.js +1815 -1642
  43. package/dist/index.js.map +1 -1
  44. package/dist/integrations/css-modules-helper.js +1 -3
  45. package/dist/integrations/css-modules-helper.js.map +1 -1
  46. package/dist/{it-TWWDHKKM.js → it-ME2Q7OB6.js} +65 -7
  47. package/dist/it-ME2Q7OB6.js.map +1 -0
  48. package/dist/{ja-YRQLEBFM.js → ja-HX7QASRI.js} +65 -7
  49. package/dist/ja-HX7QASRI.js.map +1 -0
  50. package/dist/{ko-PG6B3CRB.js → ko-EQO5AHAV.js} +65 -7
  51. package/dist/ko-EQO5AHAV.js.map +1 -0
  52. package/dist/{ms-7EZECVKM.js → ms-UILFSF3I.js} +65 -7
  53. package/dist/ms-UILFSF3I.js.map +1 -0
  54. package/dist/{nl-HP753YAP.js → nl-NNI45PMK.js} +65 -7
  55. package/dist/nl-NNI45PMK.js.map +1 -0
  56. package/dist/{no-JUD5WX3N.js → no-MAL5F6P6.js} +65 -7
  57. package/dist/no-MAL5F6P6.js.map +1 -0
  58. package/dist/{pl-RVUL4CBM.js → pl-4RMY6JAJ.js} +65 -7
  59. package/dist/pl-4RMY6JAJ.js.map +1 -0
  60. package/dist/{pt-RPOVD4QM.js → pt-VQH2X3QQ.js} +65 -7
  61. package/dist/pt-VQH2X3QQ.js.map +1 -0
  62. package/dist/{ro-DMYWQTRD.js → ro-XXCQO2OG.js} +65 -7
  63. package/dist/ro-XXCQO2OG.js.map +1 -0
  64. package/dist/{sv-DMZ4FGXD.js → sv-7Z6EJ6U4.js} +65 -7
  65. package/dist/sv-7Z6EJ6U4.js.map +1 -0
  66. package/dist/test-utils.css +1 -1
  67. package/dist/test-utils.css.map +1 -1
  68. package/dist/test-utils.d.ts +1 -1
  69. package/dist/test-utils.js +5 -3
  70. package/dist/test-utils.js.map +1 -1
  71. package/dist/{th-4HVBJBQ2.js → th-L3DXXYVQ.js} +65 -7
  72. package/dist/th-L3DXXYVQ.js.map +1 -0
  73. package/dist/{tr-6BDPU5ML.js → tr-P3DFVLFJ.js} +65 -7
  74. package/dist/tr-P3DFVLFJ.js.map +1 -0
  75. package/dist/{uk-TBZBT2TL.js → uk-7C5D3PXS.js} +65 -7
  76. package/dist/uk-7C5D3PXS.js.map +1 -0
  77. package/dist/{vi-UMKZKZL3.js → vi-ZLTLEO7P.js} +65 -7
  78. package/dist/vi-ZLTLEO7P.js.map +1 -0
  79. package/dist/{zh-YBJWJJYB.js → zh-5FHVWDIN.js} +65 -7
  80. package/dist/zh-5FHVWDIN.js.map +1 -0
  81. package/package.json +7 -7
  82. package/src/components/AssistProgress.tsx +144 -134
  83. package/src/components/{EntityFoundLog.tsx → ItemFoundLog.tsx} +12 -7
  84. package/src/components/ProtectedErrorBoundary.css +3 -1
  85. package/src/components/StatusDisplay.css +35 -35
  86. package/src/components/Toast.css +3 -1
  87. package/src/components/__tests__/AssistProgress.test.tsx +216 -148
  88. package/src/components/annotation/annotation-entries.css +0 -4
  89. package/src/components/annotation/annotations.css +1 -1
  90. package/src/components/annotation/references.css +0 -4
  91. package/src/components/image-annotation/AnnotationOverlay.tsx +6 -6
  92. package/src/components/image-annotation/SvgDrawingCanvas.tsx +1 -1
  93. package/src/components/image-annotation/__tests__/AnnotationOverlay.click.test.tsx +7 -9
  94. package/src/components/image-annotation/__tests__/SvgDrawingCanvas.click.test.tsx +1 -2
  95. package/src/components/layout/Header.css +0 -4
  96. package/src/components/layout/LeftSidebar.css +0 -4
  97. package/src/components/modals/ConfigureGenerationStep.tsx +109 -48
  98. package/src/components/modals/ConfigureSearchStep.tsx +23 -40
  99. package/src/components/modals/ReferenceWizardModal.tsx +55 -16
  100. package/src/components/modals/ResourceGenerateModal.tsx +24 -8
  101. package/src/components/modals/ResourceSearchModal.tsx +1 -1
  102. package/src/components/modals/SearchModal.css +5 -17
  103. package/src/components/modals/SearchModal.tsx +1 -1
  104. package/src/components/modals/SearchResultsStep.tsx +2 -20
  105. package/src/components/modals/WizardFooter.tsx +63 -0
  106. package/src/components/modals/__tests__/ConfigureGenerationStep.test.tsx +255 -0
  107. package/src/components/modals/__tests__/ConfigureSearchStep.test.tsx +72 -0
  108. package/src/components/modals/__tests__/ReferenceWizardModal.test.tsx +256 -0
  109. package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +1 -0
  110. package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +6 -2
  111. package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +6 -2
  112. package/src/components/modals/__tests__/WizardFooter.test.tsx +235 -0
  113. package/src/components/modals/modals.css +16 -42
  114. package/src/components/navigation/NavigationTabs.css +0 -20
  115. package/src/components/pdf-annotation/PdfAnnotationCanvas.css +162 -6
  116. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +623 -232
  117. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +764 -7
  118. package/src/components/pdf-annotation/__tests__/estimate-slot-height.test.ts +53 -0
  119. package/src/components/pdf-annotation/estimate-slot-height.ts +31 -0
  120. package/src/components/resource/AnnotateView.tsx +16 -5
  121. package/src/components/resource/AnnotationHistory.tsx +2 -0
  122. package/src/components/resource/BrowseView.tsx +15 -4
  123. package/src/components/resource/ResourceViewer.tsx +23 -24
  124. package/src/components/resource/__tests__/AnnotateView.focus.test.tsx +50 -0
  125. package/src/components/resource/__tests__/AnnotationHistory.test.tsx +2 -0
  126. package/src/components/resource/__tests__/BrowseView.test.tsx +29 -3
  127. package/src/components/resource/__tests__/ResourceViewer.embeddable.test.tsx +47 -3
  128. package/src/components/resource/__tests__/browse-renderers.dispatch.test.tsx +33 -0
  129. package/src/components/resource/annotate-renderers.tsx +10 -1
  130. package/src/components/resource/browse-renderers.tsx +10 -1
  131. package/src/components/resource/event-formatting.ts +0 -3
  132. package/src/components/resource/panels/AssessmentEntry.tsx +1 -1
  133. package/src/components/resource/panels/AssessmentPanel.css +5 -6
  134. package/src/components/resource/panels/AssistSection.css +68 -271
  135. package/src/components/resource/panels/AssistSection.tsx +4 -4
  136. package/src/components/resource/panels/AssistShell.tsx +12 -4
  137. package/src/components/resource/panels/CollaborationPanel.css +47 -22
  138. package/src/components/resource/panels/CollaborationPanel.tsx +93 -2
  139. package/src/components/resource/panels/CommentEntry.tsx +1 -1
  140. package/src/components/resource/panels/CommentsPanel.css +0 -16
  141. package/src/components/resource/panels/HighlightEntry.tsx +1 -1
  142. package/src/components/resource/panels/HighlightPanel.css +0 -7
  143. package/src/components/resource/panels/JsonLdPanel.css +0 -4
  144. package/src/components/resource/panels/ReferenceEntry.tsx +1 -1
  145. package/src/components/resource/panels/ReferencesPanel.css +0 -12
  146. package/src/components/resource/panels/ReferencesPanel.tsx +12 -20
  147. package/src/components/resource/panels/ResourceInfoPanel.css +0 -32
  148. package/src/components/resource/panels/ResourceInfoPanel.tsx +1 -1
  149. package/src/components/resource/panels/StatisticsPanel.css +0 -20
  150. package/src/components/resource/panels/TagEntry.tsx +1 -1
  151. package/src/components/resource/panels/TaggingPanel.css +0 -4
  152. package/src/components/resource/panels/TaggingPanel.tsx +2 -10
  153. package/src/components/resource/panels/UnifiedAnnotationsPanel.css +0 -4
  154. package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +0 -1
  155. package/src/components/resource/panels/__tests__/AssistSection.test.tsx +48 -50
  156. package/src/components/resource/panels/__tests__/AssistShell.test.tsx +11 -6
  157. package/src/components/resource/panels/__tests__/CollaborationPanel.test.tsx +89 -0
  158. package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +1 -2
  159. package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +0 -1
  160. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +44 -36
  161. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +0 -1
  162. package/src/components/resource/panels/__tests__/ReferencesPanel.headless.test.tsx +1 -1
  163. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +34 -53
  164. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +37 -1
  165. package/src/components/resource/panels/__tests__/TagEntry.test.tsx +0 -1
  166. package/src/components/settings/SettingsPanel.css +0 -4
  167. package/src/features/auth/auth.css +9 -33
  168. package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +25 -1
  169. package/src/features/resource-compose/state/compose-page-state-unit.ts +11 -2
  170. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +55 -0
  171. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +29 -0
  172. package/src/features/resource-discovery/state/__tests__/discover-state-unit.test.ts +53 -3
  173. package/src/features/resource-discovery/state/discover-state-unit.ts +29 -8
  174. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +26 -0
  175. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +42 -21
  176. package/src/features/resource-viewer/hooks/__tests__/useResourceViewedReport.test.tsx +63 -0
  177. package/src/features/resource-viewer/hooks/useResourceViewedReport.ts +33 -0
  178. package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +2 -2
  179. package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +1 -1
  180. package/src/styles/core/buttons.css +3 -1
  181. package/src/styles/core/forms.css +0 -21
  182. package/src/styles/core/indicators.css +0 -4
  183. package/src/styles/core/inputs.css +1 -5
  184. package/src/styles/core/progress.css +2 -34
  185. package/src/styles/core/sliders.css +0 -4
  186. package/src/styles/core/toggles.css +0 -4
  187. package/src/styles/features/compose.css +1 -1
  188. package/src/styles/features/resource-discovery.css +13 -0
  189. package/src/styles/features/resource-viewer.css +1 -21
  190. package/src/styles/features/static-pages.css +11 -11
  191. package/src/styles/motivations/motivation-assessment.css +2 -2
  192. package/src/styles/motivations/motivation-comment.css +2 -2
  193. package/src/styles/motivations/motivation-reference.css +2 -2
  194. package/src/styles/panels/history-panel.css +0 -40
  195. package/src/styles/panels/user-panel.css +0 -3
  196. package/src/styles/patterns/panel-helpers.css +0 -27
  197. package/src/styles/patterns/panel-sections.css +0 -59
  198. package/src/styles/patterns/panels-base.css +4 -36
  199. package/src/styles/utilities/motion.css +15 -15
  200. package/src/styles/variables.css +29 -1
  201. package/translations/ar.json +59 -5
  202. package/translations/bn.json +59 -5
  203. package/translations/cs.json +59 -5
  204. package/translations/da.json +59 -5
  205. package/translations/de.json +59 -5
  206. package/translations/el.json +59 -5
  207. package/translations/en.json +59 -5
  208. package/translations/es.json +59 -5
  209. package/translations/fa.json +59 -5
  210. package/translations/fi.json +59 -5
  211. package/translations/fr.json +59 -5
  212. package/translations/he.json +59 -5
  213. package/translations/hi.json +59 -5
  214. package/translations/id.json +59 -5
  215. package/translations/it.json +59 -5
  216. package/translations/ja.json +59 -5
  217. package/translations/ko.json +59 -5
  218. package/translations/ms.json +59 -5
  219. package/translations/nl.json +59 -5
  220. package/translations/no.json +59 -5
  221. package/translations/pl.json +59 -5
  222. package/translations/pt.json +59 -5
  223. package/translations/ro.json +59 -5
  224. package/translations/sv.json +59 -5
  225. package/translations/th.json +59 -5
  226. package/translations/tr.json +59 -5
  227. package/translations/uk.json +59 -5
  228. package/translations/vi.json +59 -5
  229. package/translations/zh.json +59 -5
  230. package/dist/PdfAnnotationCanvas.client-EHQWF6BR.js +0 -462
  231. package/dist/PdfAnnotationCanvas.client-EHQWF6BR.js.map +0 -1
  232. package/dist/PdfAnnotationCanvas.client-SU5KNT7G.css +0 -98
  233. package/dist/PdfAnnotationCanvas.client-SU5KNT7G.css.map +0 -1
  234. package/dist/ar-CULRHLNG.js.map +0 -1
  235. package/dist/bn-HYR7CEL2.js.map +0 -1
  236. package/dist/chunk-NH6L2GIL.js.map +0 -1
  237. package/dist/chunk-TG2XQEC4.js.map +0 -1
  238. package/dist/chunk-XUDKYAVC.js +0 -21
  239. package/dist/chunk-XUDKYAVC.js.map +0 -1
  240. package/dist/cs-YBRWY7NH.js.map +0 -1
  241. package/dist/da-PHDEOBV3.js.map +0 -1
  242. package/dist/de-O4UCCVLJ.js.map +0 -1
  243. package/dist/el-MVGO6WZV.js.map +0 -1
  244. package/dist/es-USSLXQY7.js.map +0 -1
  245. package/dist/fa-WMAREFWY.js.map +0 -1
  246. package/dist/fi-B3NBNOTS.js.map +0 -1
  247. package/dist/fr-TXYK5T5W.js.map +0 -1
  248. package/dist/he-JCJACW5D.js.map +0 -1
  249. package/dist/hi-VOQLZNJU.js.map +0 -1
  250. package/dist/id-BFN5RZNZ.js.map +0 -1
  251. package/dist/it-TWWDHKKM.js.map +0 -1
  252. package/dist/ja-YRQLEBFM.js.map +0 -1
  253. package/dist/ko-PG6B3CRB.js.map +0 -1
  254. package/dist/ms-7EZECVKM.js.map +0 -1
  255. package/dist/nl-HP753YAP.js.map +0 -1
  256. package/dist/no-JUD5WX3N.js.map +0 -1
  257. package/dist/pl-RVUL4CBM.js.map +0 -1
  258. package/dist/pt-RPOVD4QM.js.map +0 -1
  259. package/dist/ro-DMYWQTRD.js.map +0 -1
  260. package/dist/sv-DMZ4FGXD.js.map +0 -1
  261. package/dist/th-4HVBJBQ2.js.map +0 -1
  262. package/dist/tr-6BDPU5ML.js.map +0 -1
  263. package/dist/uk-TBZBT2TL.js.map +0 -1
  264. package/dist/vi-UMKZKZL3.js.map +0 -1
  265. package/dist/zh-YBJWJJYB.js.map +0 -1
  266. package/src/integrations/tailwind-plugin.cjs +0 -224
  267. /package/dist/{en-2KQILFKQ.js.map → en-2KADE7N4.js.map} +0 -0
@@ -1,8 +1,28 @@
1
1
  'use client';
2
2
 
3
- import React, { useState } from 'react';
4
- import type { GatheredContext } from '@semiont/core';
3
+ import React, { useEffect } from 'react';
4
+ import { WizardFooter } from './WizardFooter';
5
+ import type { CollaboratorEntry, GatheredContext } from '@semiont/core';
5
6
  import { LOCALES } from '@semiont/core';
7
+
8
+ /**
9
+ * Bounds for the max-length control when no ceiling is known. These are the
10
+ * values this control has always shipped — the fallback is today's behaviour,
11
+ * not an unbounded field (INFERENCE-LIMITS-EXPOSURE D6, as corrected in review).
12
+ */
13
+ const MIN_MAX_TOKENS = 100;
14
+ const DEFAULT_MAX_TOKENS_CEILING = 4000;
15
+ /** The step's form values, owned by the wizard so Back cannot discard them (D3). */
16
+ export interface GenerationDraft {
17
+ title: string;
18
+ storagePath: string;
19
+ prompt: string;
20
+ language: string;
21
+ temperature: number;
22
+ /** Text, not number: the field is user-editable and may be mid-edit or empty. */
23
+ maxTokensText: string;
24
+ }
25
+
6
26
  export interface GenerationConfig {
7
27
  title: string;
8
28
  storagePath: string;
@@ -14,11 +34,11 @@ export interface GenerationConfig {
14
34
  }
15
35
 
16
36
  export interface ConfigureGenerationStepProps {
17
- defaultTitle: string;
18
- locale: string;
19
37
  context: GatheredContext;
38
+ /** Owned by the wizard so Back is lossless (WIZARD-NAVIGATION D3). */
39
+ config: GenerationDraft;
40
+ onConfigChange: (config: GenerationDraft) => void;
20
41
  onBack: () => void;
21
- onCancel: () => void;
22
42
  onGenerate: (config: GenerationConfig) => void;
23
43
  translations: {
24
44
  resourceTitle: string;
@@ -32,27 +52,65 @@ export interface ConfigureGenerationStepProps {
32
52
  creativityCreative: string;
33
53
  maxLength: string;
34
54
  maxLengthHelp: string;
35
- cancel: string;
55
+ /**
56
+ * Shown INSTEAD of `maxLengthHelp` when the ceiling is known, so the bound
57
+ * is legible rather than mysterious. Interpolates `{{maxOutputTokens}}`
58
+ * and `{{model}}`.
59
+ */
60
+ maxLengthCeiling: string;
36
61
  back: string;
37
62
  generate: string;
38
63
  };
64
+ /**
65
+ * The roster entry serving `generation` (from `useCollaborators`). When its
66
+ * `limits` are known the max-length control is hard-bounded by the model's
67
+ * output ceiling; absent — or present without `limits`, which is normal when
68
+ * discovery could not answer right now — the control keeps its default
69
+ * bounds and generation still submits (D3).
70
+ */
71
+ generationAgent?: CollaboratorEntry;
39
72
  }
40
73
 
41
74
  export function ConfigureGenerationStep({
42
- defaultTitle,
43
- locale,
44
75
  context,
76
+ config,
77
+ onConfigChange,
45
78
  onBack,
46
- onCancel,
47
79
  onGenerate,
48
80
  translations: t,
81
+ generationAgent,
49
82
  }: ConfigureGenerationStepProps) {
50
- const [title, setTitle] = useState(defaultTitle);
51
- const [storagePath, setStoragePath] = useState('');
52
- const [prompt, setPrompt] = useState('');
53
- const [language, setLanguage] = useState(locale);
54
- const [temperature, setTemperature] = useState(0.7);
55
- const [maxTokens, setMaxTokens] = useState(500);
83
+ // CONTROLLED (WIZARD-NAVIGATION D3): these were six local `useState`s, so Back
84
+ // unmounted the step and threw away every typed instruction, the save path and
85
+ // both sliders. `maxTokensText` stays TEXT rather than a number so the field can
86
+ // be cleared mid-edit — `parseInt` on an empty field yields NaN, which React
87
+ // warns about and which used to travel into the job config.
88
+ const { title, storagePath, prompt, language, temperature, maxTokensText } = config;
89
+ const set = (patch: Partial<GenerationDraft>) => onConfigChange({ ...config, ...patch });
90
+
91
+ const ceiling = generationAgent?.limits?.maxOutputTokens ?? DEFAULT_MAX_TOKENS_CEILING;
92
+
93
+ /**
94
+ * The value that will actually be submitted: always finite, always inside
95
+ * the bounds, whatever the field currently shows.
96
+ */
97
+ const clamp = (n: number): number =>
98
+ Math.min(Math.max(Number.isFinite(n) ? n : MIN_MAX_TOKENS, MIN_MAX_TOKENS), ceiling);
99
+
100
+ const submittedMaxTokens = clamp(parseInt(maxTokensText, 10));
101
+
102
+ // Re-clamp whenever the ceiling changes — which INCLUDES its first arrival.
103
+ // `browse.agents()` is asynchronous, so `generationAgent` is undefined on the
104
+ // first render and lands later; clamping only in the `useState` initializer
105
+ // would satisfy a mount-time test and do nothing in the running app.
106
+ useEffect(() => {
107
+ const n = parseInt(maxTokensText, 10);
108
+ // An empty/mid-edit field is left alone; submission clamps it anyway.
109
+ if (Number.isFinite(n) && n > ceiling) set({ maxTokensText: String(ceiling) });
110
+ // `set` and the draft are intentionally out of the dep list: this reacts to the
111
+ // CEILING arriving (browse.agents() is async), not to the user typing.
112
+ // eslint-disable-next-line react-hooks/exhaustive-deps
113
+ }, [ceiling]);
56
114
 
57
115
  const handleSubmit = (e: React.FormEvent) => {
58
116
  e.preventDefault();
@@ -63,7 +121,7 @@ export function ConfigureGenerationStep({
63
121
  ...(trimmedPrompt ? { prompt: trimmedPrompt } : {}),
64
122
  language,
65
123
  temperature,
66
- maxTokens,
124
+ maxTokens: submittedMaxTokens,
67
125
  context,
68
126
  });
69
127
  };
@@ -79,7 +137,7 @@ export function ConfigureGenerationStep({
79
137
  id="wizard-title"
80
138
  type="text"
81
139
  value={title}
82
- onChange={(e) => setTitle(e.target.value)}
140
+ onChange={(e) => set({ title: e.target.value })}
83
141
  required
84
142
  className="semiont-input"
85
143
  placeholder={t.resourceTitlePlaceholder}
@@ -97,7 +155,7 @@ export function ConfigureGenerationStep({
97
155
  id="wizard-storagePath"
98
156
  type="text"
99
157
  value={storagePath}
100
- onChange={(e) => setStoragePath(e.target.value)}
158
+ onChange={(e) => set({ storagePath: e.target.value })}
101
159
  required
102
160
  className="semiont-input semiont-input--addon"
103
161
  placeholder="generated/my-resource.md"
@@ -113,7 +171,7 @@ export function ConfigureGenerationStep({
113
171
  <textarea
114
172
  id="wizard-prompt"
115
173
  value={prompt}
116
- onChange={(e) => setPrompt(e.target.value)}
174
+ onChange={(e) => set({ prompt: e.target.value })}
117
175
  rows={2}
118
176
  className="semiont-textarea"
119
177
  placeholder={t.additionalInstructionsPlaceholder}
@@ -129,7 +187,7 @@ export function ConfigureGenerationStep({
129
187
  <select
130
188
  id="wizard-language"
131
189
  value={language}
132
- onChange={(e) => setLanguage(e.target.value)}
190
+ onChange={(e) => set({ language: e.target.value })}
133
191
  className="semiont-select"
134
192
  >
135
193
  {LOCALES.map((lang) => (
@@ -151,7 +209,7 @@ export function ConfigureGenerationStep({
151
209
  max="1"
152
210
  step="0.1"
153
211
  value={temperature}
154
- onChange={(e) => setTemperature(parseFloat(e.target.value))}
212
+ onChange={(e) => set({ temperature: parseFloat(e.target.value) })}
155
213
  className="semiont-slider"
156
214
  />
157
215
  <div className="semiont-slider__labels semiont-slider__labels--small">
@@ -167,39 +225,42 @@ export function ConfigureGenerationStep({
167
225
  <input
168
226
  id="wizard-maxTokens"
169
227
  type="number"
170
- min="100"
171
- max="4000"
228
+ min={MIN_MAX_TOKENS}
229
+ max={ceiling}
172
230
  step="100"
173
- value={maxTokens}
174
- onChange={(e) => setMaxTokens(parseInt(e.target.value))}
231
+ value={maxTokensText}
232
+ onChange={(e) => {
233
+ const raw = e.target.value;
234
+ // Empty is a legitimate transient state while retyping.
235
+ if (raw === '') { set({ maxTokensText: '' }); return; }
236
+ const n = parseInt(raw, 10);
237
+ if (!Number.isFinite(n)) return;
238
+ // `max` alone does not stop a larger value being typed — it only
239
+ // marks the field invalid. D6 says it cannot be entered, so the
240
+ // clamp lives here.
241
+ set({ maxTokensText: String(Math.min(n, ceiling)) });
242
+ }}
175
243
  className="semiont-input"
176
244
  />
245
+ <p className="semiont-form__help">
246
+ {generationAgent?.limits
247
+ ? t.maxLengthCeiling
248
+ .replace('{{maxOutputTokens}}', String(ceiling))
249
+ .replace(
250
+ '{{model}}',
251
+ (generationAgent.agent as { model?: string; name?: string }).model
252
+ ?? generationAgent.agent.name,
253
+ )
254
+ : t.maxLengthHelp}
255
+ </p>
177
256
  </div>
178
257
  </div>
179
258
 
180
- {/* Action Buttons */}
181
- <div className="semiont-modal__actions" style={{ paddingTop: '0.5rem' }}>
182
- <button
183
- type="button"
184
- onClick={onCancel}
185
- className="semiont-button--secondary semiont-button--flex"
186
- >
187
- ✕ {t.cancel}
188
- </button>
189
- <button
190
- type="button"
191
- onClick={onBack}
192
- className="semiont-button--secondary semiont-button--flex"
193
- >
194
- ◀ {t.back}
195
- </button>
196
- <button
197
- type="submit"
198
- className="semiont-button--primary semiont-button--flex"
199
- >
200
- ✨ {t.generate}
201
- </button>
202
- </div>
259
+ <WizardFooter
260
+ backLabel={t.back}
261
+ onBack={onBack}
262
+ primary={{ label: t.generate, type: 'submit' }}
263
+ />
203
264
  </form>
204
265
  );
205
266
  }
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
- import React, { useState } from 'react';
3
+ import React from 'react';
4
+ import { WizardFooter } from './WizardFooter';
4
5
 
5
6
  export interface SearchConfig {
6
7
  limit: number;
@@ -8,15 +9,21 @@ export interface SearchConfig {
8
9
  }
9
10
 
10
11
  export interface ConfigureSearchStepProps {
12
+ /**
13
+ * CONTROLLED (WIZARD-NAVIGATION D3). This used to be local `useState`, so stepping
14
+ * Back unmounted the component and silently discarded what the user had chosen —
15
+ * a Back button that costs you work is worse than no Back button, because it
16
+ * invites the press. The wizard owns it for the modal's lifetime instead.
17
+ */
18
+ config: SearchConfig;
19
+ onConfigChange: (config: SearchConfig) => void;
11
20
  isSearching?: boolean;
12
21
  onBack: () => void;
13
- onCancel: () => void;
14
22
  onSearch: (config: SearchConfig) => void;
15
23
  translations: {
16
24
  maxResults: string;
17
25
  semanticScoring: string;
18
26
  semanticScoringHelp: string;
19
- cancel: string;
20
27
  back: string;
21
28
  search: string;
22
29
  searching: string;
@@ -24,18 +31,16 @@ export interface ConfigureSearchStepProps {
24
31
  }
25
32
 
26
33
  export function ConfigureSearchStep({
34
+ config,
35
+ onConfigChange,
27
36
  isSearching = false,
28
37
  onBack,
29
- onCancel,
30
38
  onSearch,
31
39
  translations: t,
32
40
  }: ConfigureSearchStepProps) {
33
- const [limit, setLimit] = useState(10);
34
- const [useSemanticScoring, setUseSemanticScoring] = useState(true);
35
-
36
41
  const handleSubmit = (e: React.FormEvent) => {
37
42
  e.preventDefault();
38
- onSearch({ limit, useSemanticScoring });
43
+ onSearch(config);
39
44
  };
40
45
 
41
46
  return (
@@ -47,8 +52,8 @@ export function ConfigureSearchStep({
47
52
  </label>
48
53
  <select
49
54
  id="wizard-limit"
50
- value={limit}
51
- onChange={(e) => setLimit(parseInt(e.target.value))}
55
+ value={config.limit}
56
+ onChange={(e) => onConfigChange({ ...config, limit: parseInt(e.target.value) })}
52
57
  className="semiont-select"
53
58
  >
54
59
  <option value={1}>1</option>
@@ -60,11 +65,11 @@ export function ConfigureSearchStep({
60
65
 
61
66
  {/* Semantic Scoring Toggle */}
62
67
  <div className="semiont-form__field">
63
- <label className="semiont-form__label" style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
68
+ <label className="semiont-form__label semiont-form__label--inline">
64
69
  <input
65
70
  type="checkbox"
66
- checked={useSemanticScoring}
67
- onChange={(e) => setUseSemanticScoring(e.target.checked)}
71
+ checked={config.useSemanticScoring}
72
+ onChange={(e) => onConfigChange({ ...config, useSemanticScoring: e.target.checked })}
68
73
  />
69
74
  {t.semanticScoring}
70
75
  </label>
@@ -73,33 +78,11 @@ export function ConfigureSearchStep({
73
78
  </p>
74
79
  </div>
75
80
 
76
- {/* Action Buttons */}
77
- <div className="semiont-modal__actions" style={{ paddingTop: '0.5rem' }}>
78
- <button
79
- type="button"
80
- onClick={onCancel}
81
- className="semiont-button--secondary semiont-button--flex"
82
- disabled={isSearching}
83
- >
84
- ✕ {t.cancel}
85
- </button>
86
- <button
87
- type="button"
88
- onClick={onBack}
89
- className="semiont-button--secondary semiont-button--flex"
90
- disabled={isSearching}
91
- >
92
- ◀ {t.back}
93
- </button>
94
- <button
95
- type="submit"
96
- className="semiont-button--primary semiont-button--flex"
97
- disabled={isSearching}
98
- data-generating={isSearching ? 'true' : 'false'}
99
- >
100
- {isSearching ? `✨ ${t.searching}` : `🔍 ${t.search}`}
101
- </button>
102
- </div>
81
+ <WizardFooter
82
+ backLabel={t.back}
83
+ onBack={onBack}
84
+ primary={{ label: t.search, pendingLabel: t.searching, pending: isSearching, type: 'submit' }}
85
+ />
103
86
  </form>
104
87
  );
105
88
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useState, useEffect, useCallback } from 'react';
4
4
  import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react';
5
- import type { GatheredContext } from '@semiont/core';
5
+ import type { GatheredContext, CollaboratorEntry } from '@semiont/core';
6
6
  import { uuidV4 } from '@semiont/core';
7
7
  import { useSemiont } from '../../session/SemiontProvider';
8
8
  import { useObservable } from '../../hooks/useObservable';
@@ -12,6 +12,7 @@ import { ConfigureGenerationStep } from './ConfigureGenerationStep';
12
12
  import type { GenerationConfig } from './ConfigureGenerationStep';
13
13
  import { ConfigureSearchStep } from './ConfigureSearchStep';
14
14
  import type { SearchConfig } from './ConfigureSearchStep';
15
+ import type { GenerationDraft } from './ConfigureGenerationStep';
15
16
  import { SearchResultsStep } from './SearchResultsStep';
16
17
  import type { ScoredResult } from './SearchResultsStep';
17
18
 
@@ -22,6 +23,12 @@ type WizardStep =
22
23
  | { step: 'configure-generation' };
23
24
 
24
25
  export interface ReferenceWizardModalProps {
26
+ /**
27
+ * Roster entry serving `generation`, forwarded to ConfigureGenerationStep so
28
+ * the max-length control is bounded by the model's real output ceiling.
29
+ * Optional: absent means today's default bounds (INFERENCE-LIMITS-EXPOSURE D3).
30
+ */
31
+ generationAgent?: CollaboratorEntry;
25
32
  isOpen: boolean;
26
33
  onClose: () => void;
27
34
  /** The annotation being resolved */
@@ -76,6 +83,7 @@ export interface ReferenceWizardModalProps {
76
83
  creativityCreative: string;
77
84
  maxLength: string;
78
85
  maxLengthHelp: string;
86
+ maxLengthCeiling: string;
79
87
  maxResults: string;
80
88
  semanticScoring: string;
81
89
  semanticScoringHelp: string;
@@ -97,9 +105,18 @@ export function ReferenceWizardModal({
97
105
  onLinkResource,
98
106
  onComposeNavigate,
99
107
  translations: t,
108
+ generationAgent,
100
109
  }: ReferenceWizardModalProps) {
101
110
  const session = useObservable(useSemiont().activeSession$);
102
111
  const [wizardStep, setWizardStep] = useState<WizardStep>({ step: 'gather' });
112
+ // Both step drafts live HERE, not in the steps (WIZARD-NAVIGATION D3). Stepping
113
+ // back unmounts a step; if the step owned its values, Back would silently discard
114
+ // everything typed — which is exactly what it used to do.
115
+ 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
+ });
103
120
  const [isSearching, setIsSearching] = useState(false);
104
121
  const [userHint, setUserHint] = useState('');
105
122
 
@@ -107,6 +124,11 @@ export function ReferenceWizardModal({
107
124
  useEffect(() => {
108
125
  if (isOpen) {
109
126
  setWizardStep({ step: 'gather' });
127
+ setSearchConfig({ limit: 10, useSemanticScoring: true });
128
+ setGenerationDraft({
129
+ title: defaultTitle, storagePath: '', prompt: '', language: locale,
130
+ temperature: 0.7, maxTokensText: '500',
131
+ });
110
132
  setIsSearching(false);
111
133
  setUserHint('');
112
134
  }
@@ -125,25 +147,44 @@ export function ReferenceWizardModal({
125
147
  setWizardStep({ step: 'configure-search' });
126
148
  }, []);
127
149
 
150
+ /**
151
+ * The gather step's Hint, placed where the schema says it lives.
152
+ *
153
+ * Two defects, one line. (1) Search and compose each built this inline and
154
+ * generation did not, so picking the AI path silently discarded what the user had
155
+ * typed — and then showed them an empty "Additional Instructions" box.
156
+ * (2) Both existing sites wrote `{ ...context, userHint }`, a TOP-LEVEL key
157
+ * `GatheredContext` does not define; `GatheredContext.json` puts it at
158
+ * `focus.userHint` ("hint to supplement or replace the selected text for search and
159
+ * generation"). So the hint was misplaced on every path, not just missing on one.
160
+ *
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).
164
+ */
165
+ const contextWithHint = !context
166
+ ? null
167
+ : userHint
168
+ ? { ...context, focus: { ...context.focus, userHint } }
169
+ : context;
170
+
128
171
  const handleGenerate = useCallback(() => {
129
172
  setWizardStep({ step: 'configure-generation' });
130
173
  }, []);
131
174
 
132
175
  const handleCompose = useCallback(() => {
133
- if (!context || !annotationId || !resourceId) return;
134
- const contextWithHint = userHint ? { ...context, userHint } : context;
176
+ if (!contextWithHint || !annotationId || !resourceId) return;
135
177
  onComposeNavigate(contextWithHint, annotationId, resourceId, defaultTitle, entityTypes);
136
178
  onClose();
137
- }, [context, annotationId, resourceId, defaultTitle, entityTypes, onComposeNavigate, onClose, userHint]);
179
+ }, [contextWithHint, annotationId, resourceId, defaultTitle, entityTypes, onComposeNavigate, onClose]);
138
180
 
139
181
  const handleBackToGather = useCallback(() => {
140
182
  setWizardStep({ step: 'gather' });
141
183
  }, []);
142
184
 
143
185
  const handleSearchSubmit = useCallback((config: SearchConfig) => {
144
- if (!annotationId || !context || !resourceId) return;
186
+ if (!annotationId || !contextWithHint || !resourceId) return;
145
187
  setIsSearching(true);
146
- const contextWithHint = userHint ? { ...context, userHint } : context;
147
188
  session?.client.match.requestSearch({
148
189
  correlationId: uuidV4(),
149
190
  resourceId,
@@ -153,7 +194,7 @@ export function ReferenceWizardModal({
153
194
  useSemanticScoring: config.useSemanticScoring,
154
195
  });
155
196
  // Stay on configure-search until results arrive (subscription above handles transition)
156
- }, [annotationId, resourceId, context, session, userHint]);
197
+ }, [annotationId, resourceId, contextWithHint, session]);
157
198
 
158
199
  const handleGenerateSubmit = useCallback((config: GenerationConfig) => {
159
200
  if (!annotationId) return;
@@ -245,11 +286,11 @@ export function ReferenceWizardModal({
245
286
 
246
287
  {wizardStep.step === 'configure-generation' && context && (
247
288
  <ConfigureGenerationStep
248
- defaultTitle={defaultTitle}
249
- locale={locale}
250
- context={context}
289
+ {...(generationAgent ? { generationAgent } : {})}
290
+ context={contextWithHint ?? context}
291
+ config={generationDraft}
292
+ onConfigChange={setGenerationDraft}
251
293
  onBack={handleBackToGather}
252
- onCancel={onClose}
253
294
  onGenerate={handleGenerateSubmit}
254
295
  translations={{
255
296
  resourceTitle: t.resourceTitle,
@@ -263,7 +304,7 @@ export function ReferenceWizardModal({
263
304
  creativityCreative: t.creativityCreative,
264
305
  maxLength: t.maxLength,
265
306
  maxLengthHelp: t.maxLengthHelp,
266
- cancel: t.cancel,
307
+ maxLengthCeiling: t.maxLengthCeiling,
267
308
  back: t.back,
268
309
  generate: t.generate,
269
310
  }}
@@ -272,15 +313,15 @@ export function ReferenceWizardModal({
272
313
 
273
314
  {wizardStep.step === 'configure-search' && (
274
315
  <ConfigureSearchStep
316
+ config={searchConfig}
317
+ onConfigChange={setSearchConfig}
275
318
  isSearching={isSearching}
276
319
  onBack={handleBackToGather}
277
- onCancel={onClose}
278
320
  onSearch={handleSearchSubmit}
279
321
  translations={{
280
322
  maxResults: t.maxResults,
281
323
  semanticScoring: t.semanticScoring,
282
324
  semanticScoringHelp: t.semanticScoringHelp,
283
- cancel: t.cancel,
284
325
  back: t.back,
285
326
  search: t.search,
286
327
  searching: t.searching,
@@ -294,12 +335,10 @@ export function ReferenceWizardModal({
294
335
  context={context}
295
336
  onLink={handleLink}
296
337
  onBack={handleBackToGather}
297
- onCancel={onClose}
298
338
  translations={{
299
339
  noResults: t.noResults,
300
340
  link: t.link,
301
341
  back: t.back,
302
- cancel: t.cancel,
303
342
  score: t.score,
304
343
  sourceContextLabel: t.sourceContextLabel,
305
344
  connectionsLabel: t.connectionsLabel,
@@ -1,11 +1,12 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect, useCallback } from 'react';
4
+ import type { CollaboratorEntry } from '@semiont/core';
4
5
  import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react';
5
6
  import { useResourceGather } from '../../hooks/useResourceGather';
6
7
  import { ConfigureGatherStep, type ResourceGatherConfig } from './ConfigureGatherStep';
7
8
  import { GatherContextStep } from './GatherContextStep';
8
- import { ConfigureGenerationStep, type GenerationConfig } from './ConfigureGenerationStep';
9
+ import { ConfigureGenerationStep, type GenerationConfig, type GenerationDraft } from './ConfigureGenerationStep';
9
10
 
10
11
  export interface ResourceGenerateModalTranslations {
11
12
  // Step titles + nav
@@ -41,10 +42,17 @@ export interface ResourceGenerateModalTranslations {
41
42
  creativityCreative: string;
42
43
  maxLength: string;
43
44
  maxLengthHelp: string;
45
+ maxLengthCeiling: string;
44
46
  generate: string;
45
47
  }
46
48
 
47
49
  export interface ResourceGenerateModalProps {
50
+ /**
51
+ * Roster entry serving `generation`, forwarded to ConfigureGenerationStep so
52
+ * the max-length control is bounded by the model's real output ceiling.
53
+ * Optional: absent means today's default bounds (INFERENCE-LIMITS-EXPOSURE D3).
54
+ */
55
+ generationAgent?: CollaboratorEntry;
48
56
  isOpen: boolean;
49
57
  onClose: () => void;
50
58
  resourceId: string;
@@ -58,8 +66,8 @@ export interface ResourceGenerateModalProps {
58
66
  entityTypeOptions?: string[];
59
67
  /**
60
68
  * Emit the chosen generation config. The parent runs the job
61
- * (`client.yield.fromResource(resourceId, …).run(…)`) — mirrors how the
62
- * annotation wizard delegates `yield.fromAnnotation` to its parent.
69
+ * (`client.yield.fromContext(config.context, …)`) — mirrors how the
70
+ * annotation wizard delegates generation to its parent.
63
71
  */
64
72
  onGenerateSubmit: (resourceId: string, config: GenerationConfig) => void;
65
73
  translations: ResourceGenerateModalTranslations;
@@ -82,7 +90,15 @@ export function ResourceGenerateModal({
82
90
  entityTypeOptions = [],
83
91
  onGenerateSubmit,
84
92
  translations: t,
93
+ generationAgent,
85
94
  }: ResourceGenerateModalProps) {
95
+ // Same draft ownership as the wizard (WIZARD-NAVIGATION D3): the step is
96
+ // controlled, so stepping back through this modal keeps what was typed.
97
+ const [generationDraft, setGenerationDraft] = useState<GenerationDraft>({
98
+ title: defaultTitle, storagePath: '', prompt: '', language: locale,
99
+ temperature: 0.7, maxTokensText: '500',
100
+ });
101
+
86
102
  const [step, setStep] = useState<Step>('configure-gather');
87
103
  const { context, loading, error, gather, reset } = useResourceGather();
88
104
  // Supplied by the owner, which already tracks the list with its failure
@@ -153,12 +169,12 @@ export function ResourceGenerateModal({
153
169
  onGather={handleGather}
154
170
  onCancel={onClose}
155
171
  translations={{
172
+ cancel: t.cancel,
156
173
  intro: t.gatherIntro,
157
174
  includeContent: t.includeContent,
158
175
  includeSummary: t.includeSummary,
159
176
  depth: t.gatherDepth,
160
177
  maxResources: t.gatherMaxResources,
161
- cancel: t.cancel,
162
178
  gather: t.gatherButton,
163
179
  }}
164
180
  >
@@ -230,11 +246,11 @@ export function ResourceGenerateModal({
230
246
 
231
247
  {step === 'configure-generation' && context && (
232
248
  <ConfigureGenerationStep
233
- defaultTitle={defaultTitle}
234
- locale={locale}
249
+ {...(generationAgent ? { generationAgent } : {})}
235
250
  context={context}
251
+ config={generationDraft}
252
+ onConfigChange={setGenerationDraft}
236
253
  onBack={() => setStep('review')}
237
- onCancel={onClose}
238
254
  onGenerate={handleGenerate}
239
255
  translations={{
240
256
  resourceTitle: t.resourceTitle,
@@ -248,7 +264,7 @@ export function ResourceGenerateModal({
248
264
  creativityCreative: t.creativityCreative,
249
265
  maxLength: t.maxLength,
250
266
  maxLengthHelp: t.maxLengthHelp,
251
- cancel: t.cancel,
267
+ maxLengthCeiling: t.maxLengthCeiling,
252
268
  back: t.back,
253
269
  generate: t.generate,
254
270
  }}
@@ -77,7 +77,7 @@ export function ResourceSearchModal({
77
77
  (q) =>
78
78
  semiontRef.current!.browse.resources({ search: q, limit: SEARCH_LIMIT }).pipe(
79
79
  map((st) => {
80
- const resources = readyValue(st);
80
+ const resources = readyValue(st)?.resources;
81
81
  if (resources === undefined) return undefined;
82
82
  return resources
83
83
  .map(toSearchResult)