@semiont/react-ui 0.5.26 → 0.5.28

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 (242) hide show
  1. package/README.md +28 -4
  2. package/dist/{PdfAnnotationCanvas.client-6ZSF6ZTJ.js → PdfAnnotationCanvas.client-C2IGG4SW.js} +33 -6
  3. package/dist/{PdfAnnotationCanvas.client-JCJMXWWJ.css → PdfAnnotationCanvas.client-TGDPPGXJ.css} +6 -1
  4. package/dist/{ar-YTUUGK2O.js → ar-FABPTJVB.js} +88 -26
  5. package/dist/{bn-ASGNGPMD.js → bn-LKP6BDMH.js} +88 -26
  6. package/dist/{chunk-L3WKRWP3.js → chunk-2GOHTTHW.js} +88 -26
  7. package/dist/{chunk-OBQOZCJE.js → chunk-2YHEE63S.js} +31 -31
  8. package/dist/{cs-WMYVAOZY.js → cs-YQYNWHRG.js} +88 -26
  9. package/dist/{da-FSHHN4D6.js → da-QHZOYKRY.js} +88 -26
  10. package/dist/{de-FJFO3TPT.js → de-GKA7RD7J.js} +88 -26
  11. package/dist/{el-XOH4QIAG.js → el-45T257AL.js} +88 -26
  12. package/dist/{en-3FCBW3YD.js → en-RVW5EQ66.js} +4 -2
  13. package/dist/{es-HDX4QI7E.js → es-AVER7RU4.js} +88 -26
  14. package/dist/{fa-DJ6WTFTJ.js → fa-K3VPK3O2.js} +88 -26
  15. package/dist/{fi-4S7H7W7U.js → fi-5NEKF7EB.js} +88 -26
  16. package/dist/{fr-6UJSYHXC.js → fr-FUMNDLBZ.js} +88 -26
  17. package/dist/{he-ZZJOHJX2.js → he-3JGNA3PR.js} +88 -26
  18. package/dist/{hi-HZJTAW7U.js → hi-IJY375DF.js} +88 -26
  19. package/dist/{id-E3I6ILEM.js → id-LWHKJ7ZD.js} +88 -26
  20. package/dist/index.css +380 -524
  21. package/dist/index.d.ts +318 -192
  22. package/dist/index.js +2960 -2421
  23. package/dist/integrations/css-modules-helper.js +1 -3
  24. package/dist/{it-ULAPAD7U.js → it-EBVOV4RW.js} +88 -26
  25. package/dist/{ja-SAHZQ4CZ.js → ja-KWRIX2NF.js} +88 -26
  26. package/dist/{ko-DFU2ADQ4.js → ko-L5CJMFDS.js} +88 -26
  27. package/dist/{ms-NWL76XNT.js → ms-LSNMJDR2.js} +88 -26
  28. package/dist/{nl-ATCM76WN.js → nl-ZB32MB7U.js} +88 -26
  29. package/dist/{no-UIAQ5RJF.js → no-4GTNY7M5.js} +88 -26
  30. package/dist/{pl-NOECQDXD.js → pl-LUHBLTHB.js} +88 -26
  31. package/dist/{pt-SVWIIUPC.js → pt-C4H3UNQ6.js} +88 -26
  32. package/dist/{ro-NR2DRTZG.js → ro-GGITS5V4.js} +88 -26
  33. package/dist/{sv-STVJ37JP.js → sv-T56RTLBP.js} +88 -26
  34. package/dist/test-utils.css +1 -1
  35. package/dist/test-utils.d.ts +1 -1
  36. package/dist/test-utils.js +2 -2
  37. package/dist/{th-UWQR4K54.js → th-22X2IWCB.js} +88 -26
  38. package/dist/{tr-WNXZSD27.js → tr-77TFG62H.js} +88 -26
  39. package/dist/{uk-SJRMB47Y.js → uk-MIIL6NXT.js} +88 -26
  40. package/dist/{vi-TMRQFES2.js → vi-7JYOIGNV.js} +88 -26
  41. package/dist/{zh-QOL3LYEM.js → zh-ID6DRYU4.js} +88 -26
  42. package/package.json +11 -10
  43. package/src/components/AssistProgress.tsx +178 -134
  44. package/src/components/CodeMirrorRenderer.tsx +8 -8
  45. package/src/components/{EntityFoundLog.tsx → ItemFoundLog.tsx} +12 -7
  46. package/src/components/ProtectedErrorBoundary.css +3 -1
  47. package/src/components/StatusDisplay.css +1 -1
  48. package/src/components/Toast.css +3 -1
  49. package/src/components/__tests__/AssistProgress.test.tsx +268 -141
  50. package/src/components/annotation/annotation-entries.css +0 -4
  51. package/src/components/annotation/references.css +0 -4
  52. package/src/components/image-annotation/AnnotationOverlay.tsx +6 -6
  53. package/src/components/image-annotation/SvgDrawingCanvas.tsx +1 -1
  54. package/src/components/image-annotation/__tests__/AnnotationOverlay.click.test.tsx +7 -9
  55. package/src/components/image-annotation/__tests__/SvgDrawingCanvas.click.test.tsx +1 -2
  56. package/src/components/layout/Header.css +0 -4
  57. package/src/components/layout/LeftSidebar.css +0 -4
  58. package/src/components/modals/ComposeStep.tsx +218 -0
  59. package/src/components/modals/ConfigureGatherStep.tsx +10 -18
  60. package/src/components/modals/ConfigureGenerationStep.tsx +131 -54
  61. package/src/components/modals/ConfigureSearchStep.tsx +42 -40
  62. package/src/components/modals/ContextSummary.tsx +185 -72
  63. package/src/components/modals/CorpusPane.tsx +75 -0
  64. package/src/components/modals/DiscardPrompt.tsx +38 -0
  65. package/src/components/modals/GatherContextStep.tsx +237 -107
  66. package/src/components/modals/ReferenceWizardModal.tsx +287 -60
  67. package/src/components/modals/ResourceGenerateModal.tsx +233 -109
  68. package/src/components/modals/SearchModal.css +335 -111
  69. package/src/components/modals/SearchModal.tsx +1 -1
  70. package/src/components/modals/SearchResultsStep.tsx +63 -88
  71. package/src/components/modals/WizardFooter.tsx +69 -0
  72. package/src/components/modals/__tests__/ComposeStep.test.tsx +152 -0
  73. package/src/components/modals/__tests__/ConfigureGatherStep.test.tsx +14 -26
  74. package/src/components/modals/__tests__/ConfigureGenerationStep.test.tsx +290 -0
  75. package/src/components/modals/__tests__/ConfigureSearchStep.test.tsx +95 -0
  76. package/src/components/modals/__tests__/GatherContextStep.resource.test.tsx +463 -25
  77. package/src/components/modals/__tests__/ReferenceWizardModal.test.tsx +635 -0
  78. package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +277 -85
  79. package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +6 -2
  80. package/src/components/modals/__tests__/SearchResultsStep.test.tsx +61 -0
  81. package/src/components/modals/__tests__/WizardFooter.test.tsx +344 -0
  82. package/src/components/modals/modals.css +16 -42
  83. package/src/components/navigation/NavigationTabs.css +0 -20
  84. package/src/components/pdf-annotation/PdfAnnotationCanvas.css +21 -2
  85. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +36 -3
  86. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +41 -6
  87. package/src/components/resource/AnnotateView.tsx +19 -8
  88. package/src/components/resource/AnnotationHistory.tsx +2 -0
  89. package/src/components/resource/BrowseView.tsx +20 -9
  90. package/src/components/resource/ResourceViewer.tsx +27 -28
  91. package/src/components/resource/__tests__/AnnotateView.embeddable.test.tsx +1 -1
  92. package/src/components/resource/__tests__/AnnotateView.focus.test.tsx +50 -0
  93. package/src/components/resource/__tests__/AnnotationHistory.test.tsx +2 -0
  94. package/src/components/resource/__tests__/BrowseView.embeddable.test.tsx +1 -1
  95. package/src/components/resource/__tests__/BrowseView.test.tsx +38 -12
  96. package/src/components/resource/__tests__/ResourceViewer.embeddable.test.tsx +47 -3
  97. package/src/components/resource/annotate-renderers.tsx +3 -3
  98. package/src/components/resource/event-formatting.ts +0 -3
  99. package/src/components/resource/panels/AssessmentEntry.tsx +1 -1
  100. package/src/components/resource/panels/AssessmentPanel.css +5 -6
  101. package/src/components/resource/panels/AssistSection.css +95 -263
  102. package/src/components/resource/panels/AssistSection.tsx +4 -4
  103. package/src/components/resource/panels/AssistShell.tsx +12 -4
  104. package/src/components/resource/panels/CollaborationPanel.css +47 -22
  105. package/src/components/resource/panels/CollaborationPanel.tsx +93 -2
  106. package/src/components/resource/panels/CommentEntry.tsx +1 -1
  107. package/src/components/resource/panels/CommentsPanel.css +0 -16
  108. package/src/components/resource/panels/HighlightEntry.tsx +1 -1
  109. package/src/components/resource/panels/HighlightPanel.css +0 -7
  110. package/src/components/resource/panels/JsonLdPanel.css +0 -4
  111. package/src/components/resource/panels/ReferenceEntry.tsx +10 -3
  112. package/src/components/resource/panels/ReferencesPanel.css +0 -12
  113. package/src/components/resource/panels/ReferencesPanel.tsx +16 -20
  114. package/src/components/resource/panels/ResourceInfoPanel.css +0 -32
  115. package/src/components/resource/panels/ResourceInfoPanel.tsx +89 -20
  116. package/src/components/resource/panels/StatisticsPanel.css +0 -20
  117. package/src/components/resource/panels/TagEntry.tsx +1 -1
  118. package/src/components/resource/panels/TaggingPanel.css +0 -4
  119. package/src/components/resource/panels/TaggingPanel.tsx +2 -10
  120. package/src/components/resource/panels/UnifiedAnnotationsPanel.css +0 -4
  121. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +7 -2
  122. package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +0 -1
  123. package/src/components/resource/panels/__tests__/AssistSection.test.tsx +48 -50
  124. package/src/components/resource/panels/__tests__/AssistShell.test.tsx +11 -6
  125. package/src/components/resource/panels/__tests__/CollaborationPanel.test.tsx +89 -0
  126. package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +1 -2
  127. package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +0 -1
  128. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +44 -36
  129. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +41 -1
  130. package/src/components/resource/panels/__tests__/ReferencesPanel.headless.test.tsx +1 -1
  131. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +67 -54
  132. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +185 -7
  133. package/src/components/resource/panels/__tests__/TagEntry.test.tsx +0 -1
  134. package/src/components/settings/SettingsPanel.css +0 -4
  135. package/src/features/auth/auth.css +2 -26
  136. package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +0 -65
  137. package/src/features/resource-compose/components/ResourceComposePage.tsx +17 -130
  138. package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +1 -76
  139. package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -48
  140. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +55 -0
  141. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +29 -0
  142. package/src/features/resource-discovery/state/__tests__/discover-state-unit.test.ts +53 -3
  143. package/src/features/resource-discovery/state/discover-state-unit.ts +29 -8
  144. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +71 -15
  145. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +166 -71
  146. package/src/features/resource-viewer/hooks/__tests__/useResourceViewedReport.test.tsx +63 -0
  147. package/src/features/resource-viewer/hooks/useResourceViewedReport.ts +33 -0
  148. package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +2 -2
  149. package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +1 -1
  150. package/src/styles/core/buttons.css +3 -1
  151. package/src/styles/core/forms.css +35 -29
  152. package/src/styles/core/indicators.css +0 -4
  153. package/src/styles/core/inputs.css +0 -4
  154. package/src/styles/core/progress.css +2 -34
  155. package/src/styles/core/sliders.css +0 -4
  156. package/src/styles/core/toggles.css +0 -4
  157. package/src/styles/features/resource-discovery.css +13 -0
  158. package/src/styles/features/resource-viewer.css +0 -42
  159. package/src/styles/features/resource.css +0 -4
  160. package/src/styles/motivations/motivation-assessment.css +2 -2
  161. package/src/styles/panels/history-panel.css +0 -40
  162. package/src/styles/panels/user-panel.css +0 -3
  163. package/src/styles/patterns/panel-helpers.css +0 -27
  164. package/src/styles/patterns/panel-sections.css +0 -59
  165. package/src/styles/patterns/panels-base.css +0 -32
  166. package/translations/ar.json +84 -24
  167. package/translations/bn.json +84 -24
  168. package/translations/cs.json +84 -24
  169. package/translations/da.json +84 -24
  170. package/translations/de.json +84 -24
  171. package/translations/el.json +84 -24
  172. package/translations/en.json +84 -24
  173. package/translations/es.json +84 -24
  174. package/translations/fa.json +84 -24
  175. package/translations/fi.json +84 -24
  176. package/translations/fr.json +84 -24
  177. package/translations/he.json +84 -24
  178. package/translations/hi.json +84 -24
  179. package/translations/id.json +84 -24
  180. package/translations/it.json +84 -24
  181. package/translations/ja.json +84 -24
  182. package/translations/ko.json +84 -24
  183. package/translations/ms.json +84 -24
  184. package/translations/nl.json +84 -24
  185. package/translations/no.json +84 -24
  186. package/translations/pl.json +84 -24
  187. package/translations/pt.json +84 -24
  188. package/translations/ro.json +84 -24
  189. package/translations/sv.json +84 -24
  190. package/translations/th.json +84 -24
  191. package/translations/tr.json +84 -24
  192. package/translations/uk.json +84 -24
  193. package/translations/vi.json +84 -24
  194. package/translations/zh.json +84 -24
  195. package/dist/PdfAnnotationCanvas.client-6ZSF6ZTJ.js.map +0 -1
  196. package/dist/PdfAnnotationCanvas.client-JCJMXWWJ.css.map +0 -1
  197. package/dist/ar-YTUUGK2O.js.map +0 -1
  198. package/dist/bn-ASGNGPMD.js.map +0 -1
  199. package/dist/chunk-BTUG3VJJ.js.map +0 -1
  200. package/dist/chunk-L3WKRWP3.js.map +0 -1
  201. package/dist/chunk-O23TLLXW.js.map +0 -1
  202. package/dist/chunk-OBQOZCJE.js.map +0 -1
  203. package/dist/chunk-QT7LYM72.js.map +0 -1
  204. package/dist/chunk-VXASXU4K.js.map +0 -1
  205. package/dist/chunk-XUDKYAVC.js +0 -21
  206. package/dist/chunk-XUDKYAVC.js.map +0 -1
  207. package/dist/cs-WMYVAOZY.js.map +0 -1
  208. package/dist/da-FSHHN4D6.js.map +0 -1
  209. package/dist/de-FJFO3TPT.js.map +0 -1
  210. package/dist/el-XOH4QIAG.js.map +0 -1
  211. package/dist/en-3FCBW3YD.js.map +0 -1
  212. package/dist/es-HDX4QI7E.js.map +0 -1
  213. package/dist/fa-DJ6WTFTJ.js.map +0 -1
  214. package/dist/fi-4S7H7W7U.js.map +0 -1
  215. package/dist/fr-6UJSYHXC.js.map +0 -1
  216. package/dist/he-ZZJOHJX2.js.map +0 -1
  217. package/dist/hi-HZJTAW7U.js.map +0 -1
  218. package/dist/id-E3I6ILEM.js.map +0 -1
  219. package/dist/index.css.map +0 -1
  220. package/dist/index.js.map +0 -1
  221. package/dist/integrations/css-modules-helper.js.map +0 -1
  222. package/dist/integrations/styled-components-theme.js.map +0 -1
  223. package/dist/it-ULAPAD7U.js.map +0 -1
  224. package/dist/ja-SAHZQ4CZ.js.map +0 -1
  225. package/dist/ko-DFU2ADQ4.js.map +0 -1
  226. package/dist/ms-NWL76XNT.js.map +0 -1
  227. package/dist/nl-ATCM76WN.js.map +0 -1
  228. package/dist/no-UIAQ5RJF.js.map +0 -1
  229. package/dist/pl-NOECQDXD.js.map +0 -1
  230. package/dist/pt-SVWIIUPC.js.map +0 -1
  231. package/dist/ro-NR2DRTZG.js.map +0 -1
  232. package/dist/sv-STVJ37JP.js.map +0 -1
  233. package/dist/test-utils.css.map +0 -1
  234. package/dist/test-utils.js.map +0 -1
  235. package/dist/th-UWQR4K54.js.map +0 -1
  236. package/dist/tr-WNXZSD27.js.map +0 -1
  237. package/dist/uk-SJRMB47Y.js.map +0 -1
  238. package/dist/vi-TMRQFES2.js.map +0 -1
  239. package/dist/zh-QOL3LYEM.js.map +0 -1
  240. package/src/components/modals/ResourceSearchModal.tsx +0 -224
  241. package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +0 -159
  242. package/src/integrations/tailwind-plugin.cjs +0 -224
@@ -75,10 +75,6 @@
75
75
  color: var(--semiont-text-primary);
76
76
  }
77
77
 
78
- [data-theme="dark"] .semiont-unified-header__branding-button:hover {
79
- background: var(--semiont-bg-tertiary);
80
- }
81
-
82
78
  .semiont-unified-header__branding-button:focus-visible {
83
79
  outline: 2px solid var(--semiont-color-primary-500);
84
80
  outline-offset: 2px;
@@ -51,10 +51,6 @@
51
51
  color: var(--semiont-text-primary);
52
52
  }
53
53
 
54
- [data-theme="dark"] .semiont-left-sidebar__branding-button:hover {
55
- background: var(--semiont-bg-tertiary);
56
- }
57
-
58
54
  .semiont-left-sidebar__branding-button:focus-visible {
59
55
  outline: 2px solid var(--semiont-color-primary-500);
60
56
  outline-offset: 2px;
@@ -0,0 +1,218 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+ import { LOCALES } from '@semiont/core';
5
+ import { CodeMirrorRenderer } from '../CodeMirrorRenderer';
6
+ import { WizardFooter } from './WizardFooter';
7
+
8
+ /**
9
+ * COMPOSE-IN-MODAL P1 — the compose strategy's form, as a pure step.
10
+ *
11
+ * The from-reference slice of the compose page, lifted: name, save location,
12
+ * entity types, language, editor. Deliberately none of the page's
13
+ * upload/format/encoding machinery (the modal composes text; uploads stay on
14
+ * the standalone page — plan non-goal). The draft is CONTROLLED by the host
15
+ * (WIZARD-NAVIGATION D3), and the evidence display is the host's job (A3).
16
+ */
17
+
18
+ export interface ComposeDraft {
19
+ name: string;
20
+ /** Bare working-tree path; the file:// prefix is applied at submit. */
21
+ storagePath: string;
22
+ content: string;
23
+ /** Picked types — consulted only when the reference fixed none (D6). */
24
+ entityTypes: string[];
25
+ language: string;
26
+ }
27
+
28
+ export interface ComposeParams {
29
+ name: string;
30
+ /** Full file:// URI. */
31
+ storagePath: string;
32
+ content: string;
33
+ entityTypes: string[];
34
+ language: string;
35
+ }
36
+
37
+ export interface ComposeStepProps {
38
+ draft: ComposeDraft;
39
+ onDraftChange: (patch: Partial<ComposeDraft>) => void;
40
+ /** Entity types fixed when the reference was created — read-only tags when
41
+ * non-empty (D6): they are not this step's to change. */
42
+ referenceEntityTypes: string[];
43
+ /** Picker vocabulary when the reference fixed none. Owner-supplied, so a
44
+ * failed load cannot surface as an empty vocabulary. */
45
+ entityTypeOptions: string[];
46
+ showLineNumbers: boolean;
47
+ hoverDelayMs: number;
48
+ onBack: () => void;
49
+ /** Create-and-link. Async: the footer pends until it settles; rejection
50
+ * re-enables it (the host surfaces the error — same posture as Link). */
51
+ onCompose: (params: ComposeParams) => Promise<void>;
52
+ translations: {
53
+ resourceTitle: string;
54
+ resourceTitlePlaceholder: string;
55
+ saveLocation: string;
56
+ entityTypes: string;
57
+ language: string;
58
+ contentLabel: string;
59
+ back: string;
60
+ createAndLink: string;
61
+ creatingAndLinking: string;
62
+ };
63
+ }
64
+
65
+ export function ComposeStep({
66
+ draft,
67
+ onDraftChange,
68
+ referenceEntityTypes,
69
+ entityTypeOptions,
70
+ showLineNumbers,
71
+ hoverDelayMs,
72
+ onBack,
73
+ onCompose,
74
+ translations: t,
75
+ }: ComposeStepProps) {
76
+ const [isCreating, setIsCreating] = useState(false);
77
+ const fixedTypes = referenceEntityTypes.length > 0;
78
+
79
+ const handleSubmit = async (e: React.FormEvent) => {
80
+ e.preventDefault();
81
+ if (!draft.name.trim() || !draft.storagePath.trim()) return;
82
+ setIsCreating(true);
83
+ try {
84
+ await onCompose({
85
+ name: draft.name,
86
+ storagePath: `file://${draft.storagePath}`,
87
+ content: draft.content,
88
+ entityTypes: fixedTypes ? referenceEntityTypes : draft.entityTypes,
89
+ language: draft.language,
90
+ });
91
+ } catch {
92
+ // The rejection's job ends here: it kept the wizard's close from
93
+ // running and the host already surfaced the failure. Letting it
94
+ // escape a React event handler is an unhandled rejection.
95
+ } finally {
96
+ setIsCreating(false);
97
+ }
98
+ };
99
+
100
+ return (
101
+ <form onSubmit={handleSubmit} className="semiont-form">
102
+ {/* Name */}
103
+ <div className="semiont-form__field">
104
+ <label htmlFor="compose-name" className="semiont-form__label">
105
+ {t.resourceTitle}
106
+ </label>
107
+ <input
108
+ id="compose-name"
109
+ type="text"
110
+ value={draft.name}
111
+ onChange={(e) => onDraftChange({ name: e.target.value })}
112
+ placeholder={t.resourceTitlePlaceholder}
113
+ required
114
+ className="semiont-input"
115
+ disabled={isCreating}
116
+ />
117
+ </div>
118
+
119
+ {/* Storage URI */}
120
+ <div className="semiont-form__field">
121
+ <label htmlFor="compose-storagePath" className="semiont-form__label">
122
+ {t.saveLocation}
123
+ </label>
124
+ <div className="semiont-input-addon">
125
+ <span className="semiont-input-addon__prefix">file://</span>
126
+ <input
127
+ id="compose-storagePath"
128
+ type="text"
129
+ value={draft.storagePath}
130
+ onChange={(e) => onDraftChange({ storagePath: e.target.value })}
131
+ placeholder="people/my-resource.md"
132
+ required
133
+ className="semiont-input semiont-input--addon"
134
+ disabled={isCreating}
135
+ />
136
+ </div>
137
+ </div>
138
+
139
+ {/* Entity types: fixed at reference creation → tags; otherwise picker */}
140
+ <div className="semiont-form__field">
141
+ <div className="semiont-form__label">{t.entityTypes}</div>
142
+ {fixedTypes ? (
143
+ <div className="semiont-form__entity-type-tags" role="list">
144
+ {referenceEntityTypes.map((type) => (
145
+ <span key={type} role="listitem" className="semiont-form__entity-type-tag">
146
+ {type}
147
+ </span>
148
+ ))}
149
+ </div>
150
+ ) : (
151
+ <div className="semiont-form__entity-type-buttons" role="group">
152
+ {entityTypeOptions.map((type) => {
153
+ const isSelected = draft.entityTypes.includes(type);
154
+ return (
155
+ <button
156
+ key={type}
157
+ type="button"
158
+ onClick={() => onDraftChange({
159
+ entityTypes: isSelected
160
+ ? draft.entityTypes.filter((x) => x !== type)
161
+ : [...draft.entityTypes, type],
162
+ })}
163
+ className="semiont-form__entity-type-button"
164
+ data-selected={isSelected}
165
+ aria-pressed={isSelected}
166
+ disabled={isCreating}
167
+ >
168
+ {type}
169
+ </button>
170
+ );
171
+ })}
172
+ </div>
173
+ )}
174
+ </div>
175
+
176
+ {/* Language */}
177
+ <div className="semiont-form__field">
178
+ <label htmlFor="compose-language" className="semiont-form__label">
179
+ {t.language}
180
+ </label>
181
+ <select
182
+ id="compose-language"
183
+ value={draft.language}
184
+ onChange={(e) => onDraftChange({ language: e.target.value })}
185
+ disabled={isCreating}
186
+ className="semiont-select"
187
+ >
188
+ {LOCALES.map((lang) => (
189
+ <option key={lang.code} value={lang.code}>
190
+ {lang.nativeName}
191
+ </option>
192
+ ))}
193
+ </select>
194
+ </div>
195
+
196
+ {/* Content editor */}
197
+ <div className="semiont-form__field semiont-form__editor">
198
+ <label className="semiont-form__label">{t.contentLabel}</label>
199
+ <div className="semiont-form__editor-wrapper" lang={draft.language}>
200
+ <CodeMirrorRenderer
201
+ content={draft.content}
202
+ editable={!isCreating}
203
+ sourceView={true}
204
+ showLineNumbers={showLineNumbers}
205
+ hoverDelayMs={hoverDelayMs}
206
+ onChange={(content: string) => onDraftChange({ content })}
207
+ />
208
+ </div>
209
+ </div>
210
+
211
+ <WizardFooter
212
+ backLabel={t.back}
213
+ onBack={onBack}
214
+ primary={{ label: t.createAndLink, pendingLabel: t.creatingAndLinking, pending: isCreating, type: 'submit' }}
215
+ />
216
+ </form>
217
+ );
218
+ }
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import React, { useState } from 'react';
4
+ import { WizardFooter } from './WizardFooter';
4
5
 
5
6
  export interface ResourceGatherConfig {
6
7
  depth: number;
@@ -10,16 +11,13 @@ export interface ResourceGatherConfig {
10
11
  }
11
12
 
12
13
  export interface ConfigureGatherStepProps {
13
- defaults?: Partial<ResourceGatherConfig>;
14
14
  onGather: (config: ResourceGatherConfig) => void;
15
- onCancel: () => void;
16
15
  translations: {
17
16
  intro: string;
18
17
  includeContent: string;
19
18
  includeSummary: string;
20
19
  depth: string;
21
20
  maxResources: string;
22
- cancel: string;
23
21
  gather: string;
24
22
  };
25
23
  /** Slot for the exclusion multi-select (GENERATE-FROM-BUTTON Phase 4). */
@@ -29,13 +27,14 @@ export interface ConfigureGatherStepProps {
29
27
  /**
30
28
  * First step of the resource-generate flow: pick the gather options before
31
29
  * `gather.resource` runs. The `children` slot hosts the entity-type exclusion
32
- * multi-select (Phase 4).
30
+ * multi-select (Phase 4). Dismissal is the modal's corner ✕/Esc/backdrop
31
+ * (WIZARD-NAVIGATION D1), so the footer carries the advance alone.
33
32
  */
34
- export function ConfigureGatherStep({ defaults, onGather, onCancel, translations: t, children }: ConfigureGatherStepProps) {
35
- const [includeContent, setIncludeContent] = useState(defaults?.includeContent ?? true);
36
- const [includeSummary, setIncludeSummary] = useState(defaults?.includeSummary ?? true);
37
- const [depth, setDepth] = useState(defaults?.depth ?? 2);
38
- const [maxResources, setMaxResources] = useState(defaults?.maxResources ?? 10);
33
+ export function ConfigureGatherStep({ onGather, translations: t, children }: ConfigureGatherStepProps) {
34
+ const [includeContent, setIncludeContent] = useState(true);
35
+ const [includeSummary, setIncludeSummary] = useState(true);
36
+ const [depth, setDepth] = useState(2);
37
+ const [maxResources, setMaxResources] = useState(10);
39
38
 
40
39
  const handleSubmit = (e: React.FormEvent) => {
41
40
  e.preventDefault();
@@ -43,7 +42,7 @@ export function ConfigureGatherStep({ defaults, onGather, onCancel, translations
43
42
  };
44
43
 
45
44
  return (
46
- <form onSubmit={handleSubmit} className="semiont-form semiont-form--scrollable">
45
+ <form onSubmit={handleSubmit} className="semiont-form">
47
46
  <p className="semiont-form__helper-text">{t.intro}</p>
48
47
 
49
48
  <div className="semiont-form__checkbox-field">
@@ -89,14 +88,7 @@ export function ConfigureGatherStep({ defaults, onGather, onCancel, translations
89
88
  {/* Exclusion multi-select slot (Phase 4) */}
90
89
  {children}
91
90
 
92
- <div className="semiont-modal__actions" style={{ paddingTop: '0.5rem' }}>
93
- <button type="button" onClick={onCancel} className="semiont-button--secondary semiont-button--flex">
94
- ✕ {t.cancel}
95
- </button>
96
- <button type="submit" className="semiont-button--primary semiont-button--flex">
97
- ✨ {t.gather}
98
- </button>
99
- </div>
91
+ <WizardFooter primary={{ label: t.gather, type: 'submit' }} />
100
92
  </form>
101
93
  );
102
94
  }
@@ -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,15 +34,19 @@ export interface GenerationConfig {
14
34
  }
15
35
 
16
36
  export interface ConfigureGenerationStepProps {
17
- defaultTitle: string;
18
- locale: string;
19
37
  context: GatheredContext;
20
- onBack: () => void;
21
- onCancel: () => void;
38
+ /** Echo of the gather step's hint — the thing being steered stays visible (GEP D8). */
39
+ hintEcho?: { label: string; value: string };
40
+ /** Owned by the wizard so Back is lossless (WIZARD-NAVIGATION D3). */
41
+ config: GenerationDraft;
42
+ onConfigChange: (config: GenerationDraft) => void;
43
+ /** Absent in a single-stack host (GATHER-AT-THE-TOP D6) — the footer then renders no retreat. */
44
+ onBack?: () => void;
22
45
  onGenerate: (config: GenerationConfig) => void;
23
46
  translations: {
24
47
  resourceTitle: string;
25
48
  resourceTitlePlaceholder: string;
49
+ saveLocation: string;
26
50
  additionalInstructions: string;
27
51
  additionalInstructionsPlaceholder: string;
28
52
  language: string;
@@ -32,27 +56,66 @@ export interface ConfigureGenerationStepProps {
32
56
  creativityCreative: string;
33
57
  maxLength: string;
34
58
  maxLengthHelp: string;
35
- cancel: string;
36
- back: string;
59
+ /**
60
+ * Shown INSTEAD of `maxLengthHelp` when the ceiling is known, so the bound
61
+ * is legible rather than mysterious. Interpolates `{{maxOutputTokens}}`
62
+ * and `{{model}}`.
63
+ */
64
+ maxLengthCeiling: string;
65
+ back?: string;
37
66
  generate: string;
38
67
  };
68
+ /**
69
+ * The roster entry serving `generation` (from `useCollaborators`). When its
70
+ * `limits` are known the max-length control is hard-bounded by the model's
71
+ * output ceiling; absent — or present without `limits`, which is normal when
72
+ * discovery could not answer right now — the control keeps its default
73
+ * bounds and generation still submits (D3).
74
+ */
75
+ generationAgent?: CollaboratorEntry;
39
76
  }
40
77
 
41
78
  export function ConfigureGenerationStep({
42
- defaultTitle,
43
- locale,
44
79
  context,
80
+ hintEcho,
81
+ config,
82
+ onConfigChange,
45
83
  onBack,
46
- onCancel,
47
84
  onGenerate,
48
85
  translations: t,
86
+ generationAgent,
49
87
  }: 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);
88
+ // CONTROLLED (WIZARD-NAVIGATION D3): these were six local `useState`s, so Back
89
+ // unmounted the step and threw away every typed instruction, the save path and
90
+ // both sliders. `maxTokensText` stays TEXT rather than a number so the field can
91
+ // be cleared mid-edit — `parseInt` on an empty field yields NaN, which React
92
+ // warns about and which used to travel into the job config.
93
+ const { title, storagePath, prompt, language, temperature, maxTokensText } = config;
94
+ const set = (patch: Partial<GenerationDraft>) => onConfigChange({ ...config, ...patch });
95
+
96
+ const ceiling = generationAgent?.limits?.maxOutputTokens ?? DEFAULT_MAX_TOKENS_CEILING;
97
+
98
+ /**
99
+ * The value that will actually be submitted: always finite, always inside
100
+ * the bounds, whatever the field currently shows.
101
+ */
102
+ const clamp = (n: number): number =>
103
+ Math.min(Math.max(Number.isFinite(n) ? n : MIN_MAX_TOKENS, MIN_MAX_TOKENS), ceiling);
104
+
105
+ const submittedMaxTokens = clamp(parseInt(maxTokensText, 10));
106
+
107
+ // Re-clamp whenever the ceiling changes — which INCLUDES its first arrival.
108
+ // `browse.agents()` is asynchronous, so `generationAgent` is undefined on the
109
+ // first render and lands later; clamping only in the `useState` initializer
110
+ // would satisfy a mount-time test and do nothing in the running app.
111
+ useEffect(() => {
112
+ const n = parseInt(maxTokensText, 10);
113
+ // An empty/mid-edit field is left alone; submission clamps it anyway.
114
+ if (Number.isFinite(n) && n > ceiling) set({ maxTokensText: String(ceiling) });
115
+ // `set` and the draft are intentionally out of the dep list: this reacts to the
116
+ // CEILING arriving (browse.agents() is async), not to the user typing.
117
+ // eslint-disable-next-line react-hooks/exhaustive-deps
118
+ }, [ceiling]);
56
119
 
57
120
  const handleSubmit = (e: React.FormEvent) => {
58
121
  e.preventDefault();
@@ -63,13 +126,21 @@ export function ConfigureGenerationStep({
63
126
  ...(trimmedPrompt ? { prompt: trimmedPrompt } : {}),
64
127
  language,
65
128
  temperature,
66
- maxTokens,
129
+ maxTokens: submittedMaxTokens,
67
130
  context,
68
131
  });
69
132
  };
70
133
 
71
134
  return (
72
- <form onSubmit={handleSubmit} className="semiont-form semiont-form--scrollable">
135
+ // No --scrollable: this form renders below the evidence display inside the
136
+ // host's single step-scroll pane — an independent scroll region here is
137
+ // what squeezed the parameters out of view (the measured failure).
138
+ <form onSubmit={handleSubmit} className="semiont-form">
139
+ {hintEcho && (
140
+ <p className="semiont-wizard__hint-echo">
141
+ {hintEcho.label}: {hintEcho.value}
142
+ </p>
143
+ )}
73
144
  {/* Resource Title */}
74
145
  <div className="semiont-form__field">
75
146
  <label htmlFor="wizard-title" className="semiont-form__label">
@@ -79,7 +150,7 @@ export function ConfigureGenerationStep({
79
150
  id="wizard-title"
80
151
  type="text"
81
152
  value={title}
82
- onChange={(e) => setTitle(e.target.value)}
153
+ onChange={(e) => set({ title: e.target.value })}
83
154
  required
84
155
  className="semiont-input"
85
156
  placeholder={t.resourceTitlePlaceholder}
@@ -89,7 +160,7 @@ export function ConfigureGenerationStep({
89
160
  {/* Storage URI */}
90
161
  <div className="semiont-form__field">
91
162
  <label htmlFor="wizard-storagePath" className="semiont-form__label">
92
- Save location
163
+ {t.saveLocation}
93
164
  </label>
94
165
  <div className="semiont-input-addon">
95
166
  <span className="semiont-input-addon__prefix">file://</span>
@@ -97,7 +168,7 @@ export function ConfigureGenerationStep({
97
168
  id="wizard-storagePath"
98
169
  type="text"
99
170
  value={storagePath}
100
- onChange={(e) => setStoragePath(e.target.value)}
171
+ onChange={(e) => set({ storagePath: e.target.value })}
101
172
  required
102
173
  className="semiont-input semiont-input--addon"
103
174
  placeholder="generated/my-resource.md"
@@ -113,7 +184,7 @@ export function ConfigureGenerationStep({
113
184
  <textarea
114
185
  id="wizard-prompt"
115
186
  value={prompt}
116
- onChange={(e) => setPrompt(e.target.value)}
187
+ onChange={(e) => set({ prompt: e.target.value })}
117
188
  rows={2}
118
189
  className="semiont-textarea"
119
190
  placeholder={t.additionalInstructionsPlaceholder}
@@ -129,7 +200,7 @@ export function ConfigureGenerationStep({
129
200
  <select
130
201
  id="wizard-language"
131
202
  value={language}
132
- onChange={(e) => setLanguage(e.target.value)}
203
+ onChange={(e) => set({ language: e.target.value })}
133
204
  className="semiont-select"
134
205
  >
135
206
  {LOCALES.map((lang) => (
@@ -140,7 +211,7 @@ export function ConfigureGenerationStep({
140
211
  </select>
141
212
  </div>
142
213
 
143
- <div className="semiont-form__field semiont-form__field--inline semiont-form__field--grow">
214
+ <div className="semiont-form__field semiont-form__field--inline semiont-form__field--slider">
144
215
  <label htmlFor="wizard-temperature" className="semiont-form__label">
145
216
  {t.creativity} ({temperature.toFixed(1)})
146
217
  </label>
@@ -151,7 +222,7 @@ export function ConfigureGenerationStep({
151
222
  max="1"
152
223
  step="0.1"
153
224
  value={temperature}
154
- onChange={(e) => setTemperature(parseFloat(e.target.value))}
225
+ onChange={(e) => set({ temperature: parseFloat(e.target.value) })}
155
226
  className="semiont-slider"
156
227
  />
157
228
  <div className="semiont-slider__labels semiont-slider__labels--small">
@@ -160,46 +231,52 @@ export function ConfigureGenerationStep({
160
231
  </div>
161
232
  </div>
162
233
 
163
- <div className="semiont-form__field semiont-form__field--inline semiont-form__field--narrow">
234
+ <div className="semiont-form__field semiont-form__field--inline semiont-form__field--narrow semiont-form__field--end">
164
235
  <label htmlFor="wizard-maxTokens" className="semiont-form__label">
165
236
  {t.maxLength}
166
237
  </label>
167
238
  <input
168
239
  id="wizard-maxTokens"
169
240
  type="number"
170
- min="100"
171
- max="4000"
241
+ min={MIN_MAX_TOKENS}
242
+ max={ceiling}
172
243
  step="100"
173
- value={maxTokens}
174
- onChange={(e) => setMaxTokens(parseInt(e.target.value))}
244
+ value={maxTokensText}
245
+ onChange={(e) => {
246
+ const raw = e.target.value;
247
+ // Empty is a legitimate transient state while retyping.
248
+ if (raw === '') { set({ maxTokensText: '' }); return; }
249
+ const n = parseInt(raw, 10);
250
+ if (!Number.isFinite(n)) return;
251
+ // `max` alone does not stop a larger value being typed — it only
252
+ // marks the field invalid. D6 says it cannot be entered, so the
253
+ // clamp lives here.
254
+ set({ maxTokensText: String(Math.min(n, ceiling)) });
255
+ }}
175
256
  className="semiont-input"
176
257
  />
177
258
  </div>
178
259
  </div>
179
260
 
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>
261
+ {/* The bound, as a full sentence — which is why it lives UNDER the row,
262
+ never inside the 5.5rem Max Length column: a sentence in that box
263
+ wraps one word per line and its height taxes the whole flex row. */}
264
+ <p className="semiont-form__help semiont-form__help--row">
265
+ {generationAgent?.limits
266
+ ? t.maxLengthCeiling
267
+ .replace('{{maxOutputTokens}}', String(ceiling))
268
+ .replace(
269
+ '{{model}}',
270
+ (generationAgent.agent as { model?: string; name?: string }).model
271
+ ?? generationAgent.agent.name,
272
+ )
273
+ : t.maxLengthHelp}
274
+ </p>
275
+
276
+ <WizardFooter
277
+ {...(onBack && t.back ? { backLabel: t.back, onBack } : {})}
278
+ primary={{ label: t.generate, type: 'submit' }}
279
+ />
203
280
  </form>
204
281
  );
205
282
  }