@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
@@ -1,124 +1,99 @@
1
1
  'use client';
2
+ import { WizardFooter } from './WizardFooter';
2
3
 
3
- import type { GatheredContext, ResourceDescriptor } from '@semiont/core';
4
- import { ContextSummary } from './ContextSummary';
5
- import type { ContextSummaryTranslations } from './ContextSummary';
4
+ import type { ResourceDescriptor } from '@semiont/core';
6
5
 
7
6
  export type ScoredResult = ResourceDescriptor & {
8
7
  score?: number;
9
8
  matchReason?: string;
10
9
  };
11
10
 
11
+ /**
12
+ * Pure RESULTS (GEP D10, amended): ranked rows with scores and Link buttons.
13
+ * The evidence display is the HOST's job — the wizard stacks the full
14
+ * display-only GatherContextStep (with the collapsed strategy band) above
15
+ * this step, the same grammar as the configure steps.
16
+ */
12
17
  export interface SearchResultsStepProps {
13
18
  results: ScoredResult[];
14
- context: GatheredContext;
15
19
  onLink: (resourceId: string) => void;
16
20
  onBack: () => void;
17
- onCancel: () => void;
18
21
  translations: {
19
22
  noResults: string;
20
23
  link: string;
21
24
  back: string;
22
- cancel: string;
23
25
  score: string;
24
- } & ContextSummaryTranslations;
26
+ };
25
27
  }
26
28
 
27
29
  export function SearchResultsStep({
28
30
  results,
29
- context,
30
31
  onLink,
31
32
  onBack,
32
- onCancel,
33
33
  translations: t,
34
34
  }: SearchResultsStepProps) {
35
35
  return (
36
36
  <>
37
- {/* Two-pane layout: context left, results right */}
38
- <div className="semiont-search-results__two-pane">
39
- {/* Left: Gathered Context */}
40
- <div className="semiont-search-results__context-pane">
41
- <ContextSummary context={context} translations={t} />
42
- </div>
43
-
44
- {/* Right: Results List */}
45
- <div className="semiont-search-results__results-pane">
46
- {results.length === 0 ? (
47
- <div className="semiont-modal__empty-state" style={{ textAlign: 'center', padding: '2rem 0' }}>
48
- {t.noResults}
49
- </div>
50
- ) : (
51
- results.map((result) => {
52
- const id = result['@id'];
53
- return (
54
- <div
55
- key={id}
56
- style={{
57
- padding: '0.75rem',
58
- borderBottom: '1px solid var(--semiont-border-primary)',
59
- display: 'flex',
60
- alignItems: 'center',
61
- justifyContent: 'space-between',
62
- gap: '0.75rem',
63
- }}
64
- >
65
- <div style={{ flex: 1, minWidth: 0 }}>
66
- <div style={{ fontWeight: 500, color: 'var(--semiont-text-primary)' }}>
67
- {result.name}
37
+ <div className="semiont-search-results">
38
+ {results.length === 0 ? (
39
+ <div className="semiont-modal__empty-state" style={{ textAlign: 'center', padding: '2rem 0' }}>
40
+ {t.noResults}
41
+ </div>
42
+ ) : (
43
+ results.map((result) => {
44
+ const id = result['@id'];
45
+ return (
46
+ <div
47
+ key={id}
48
+ style={{
49
+ padding: '0.75rem',
50
+ borderBottom: '1px solid var(--semiont-border-primary)',
51
+ display: 'flex',
52
+ alignItems: 'center',
53
+ justifyContent: 'space-between',
54
+ gap: '0.75rem',
55
+ }}
56
+ >
57
+ <div style={{ flex: 1, minWidth: 0 }}>
58
+ <div style={{ fontWeight: 500, color: 'var(--semiont-text-primary)' }}>
59
+ {result.name}
60
+ </div>
61
+ {result.description && (
62
+ <div style={{
63
+ fontSize: 'var(--semiont-text-sm)',
64
+ color: 'var(--semiont-text-secondary)',
65
+ marginTop: '0.25rem',
66
+ overflow: 'hidden',
67
+ textOverflow: 'ellipsis',
68
+ whiteSpace: 'nowrap',
69
+ }}>
70
+ {result.description}
68
71
  </div>
69
- {result.description && (
70
- <div style={{
71
- fontSize: 'var(--semiont-text-sm)',
72
- color: 'var(--semiont-text-secondary)',
73
- marginTop: '0.25rem',
74
- overflow: 'hidden',
75
- textOverflow: 'ellipsis',
76
- whiteSpace: 'nowrap',
77
- }}>
78
- {result.description}
79
- </div>
72
+ )}
73
+ <div style={{ fontSize: 'var(--semiont-text-xs)', color: 'var(--semiont-text-tertiary)', marginTop: '0.25rem', display: 'flex', gap: '0.5rem' }}>
74
+ {result.score !== undefined && (
75
+ <span>{t.score}: {result.score}</span>
76
+ )}
77
+ {result.matchReason && (
78
+ <span>{result.matchReason}</span>
80
79
  )}
81
- <div style={{ fontSize: 'var(--semiont-text-xs)', color: 'var(--semiont-text-tertiary)', marginTop: '0.25rem', display: 'flex', gap: '0.5rem' }}>
82
- {result.score !== undefined && (
83
- <span>{t.score}: {result.score}</span>
84
- )}
85
- {result.matchReason && (
86
- <span>{result.matchReason}</span>
87
- )}
88
- </div>
89
80
  </div>
90
- <button
91
- type="button"
92
- onClick={() => onLink(id)}
93
- className="semiont-button--primary"
94
- style={{ flexShrink: 0 }}
95
- >
96
- 🔗 {t.link}
97
- </button>
98
81
  </div>
99
- );
100
- })
101
- )}
102
- </div>
82
+ <button
83
+ type="button"
84
+ onClick={() => onLink(id)}
85
+ className="semiont-button--primary"
86
+ style={{ flexShrink: 0 }}
87
+ >
88
+ 🔗 {t.link}
89
+ </button>
90
+ </div>
91
+ );
92
+ })
93
+ )}
103
94
  </div>
104
95
 
105
- {/* Action Buttons */}
106
- <div className="semiont-modal__actions" style={{ paddingTop: '0.5rem' }}>
107
- <button
108
- type="button"
109
- onClick={onCancel}
110
- className="semiont-button--secondary semiont-button--flex"
111
- >
112
- ✕ {t.cancel}
113
- </button>
114
- <button
115
- type="button"
116
- onClick={onBack}
117
- className="semiont-button--secondary semiont-button--flex"
118
- >
119
- ◀ {t.back}
120
- </button>
121
- </div>
96
+ <WizardFooter backLabel={t.back} onBack={onBack} />
122
97
  </>
123
98
  );
124
99
  }
@@ -0,0 +1,69 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * The one wizard footer (WIZARD-NAVIGATION D7).
5
+ *
6
+ * Four steps used to hand-build this markup, which is how four different footers
7
+ * happened: three carried a `✕ Cancel` that duplicated the corner control, one had no
8
+ * footer at all, and every button stretched to equal width so "leave" read as a peer of
9
+ * "do the thing".
10
+ *
11
+ * The grammar, settled in D1/D2:
12
+ * • Dismissal is NOT here. The corner ✕, Esc and the backdrop are the way out, and
13
+ * they are the same on every step including the one with no footer.
14
+ * • Retreat is chrome: quiet, left, no emoji.
15
+ * • Advance is the decision: filled, right, one verb.
16
+ */
17
+
18
+ export interface WizardFooterPrimary {
19
+ label: string;
20
+ /** Shown while `pending`; falls back to `label`. */
21
+ pendingLabel?: string;
22
+ pending?: boolean;
23
+ /**
24
+ * The action's PRECONDITION is not met yet (e.g. the review step waiting on
25
+ * gather). Unlike `pending` — the action itself running — this disables only
26
+ * the primary: retreat must stay live while a precondition loads.
27
+ */
28
+ disabled?: boolean;
29
+ type: 'submit' | 'button';
30
+ onClick?: () => void;
31
+ }
32
+
33
+ export interface WizardFooterProps {
34
+ /** Omit both to render no retreat — correct on the first step. */
35
+ backLabel?: string;
36
+ onBack?: () => void;
37
+ /** Omit on steps whose action lives elsewhere (search results links per row). */
38
+ primary?: WizardFooterPrimary;
39
+ }
40
+
41
+ export function WizardFooter({ backLabel, onBack, primary }: WizardFooterProps) {
42
+ const pending = primary?.pending ?? false;
43
+
44
+ return (
45
+ <div className="semiont-modal__actions semiont-modal__actions--wizard">
46
+ {onBack && backLabel && (
47
+ <button
48
+ type="button"
49
+ onClick={onBack}
50
+ className="semiont-button--secondary semiont-wizard-footer__back"
51
+ disabled={pending}
52
+ >
53
+ ◀ {backLabel}
54
+ </button>
55
+ )}
56
+ {primary && (
57
+ <button
58
+ type={primary.type}
59
+ {...(primary.onClick ? { onClick: primary.onClick } : {})}
60
+ className="semiont-button--primary"
61
+ disabled={pending || (primary.disabled ?? false)}
62
+ data-pending={pending ? 'true' : 'false'}
63
+ >
64
+ {pending ? (primary.pendingLabel ?? primary.label) : primary.label}
65
+ </button>
66
+ )}
67
+ </div>
68
+ );
69
+ }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * COMPOSE-IN-MODAL P1 — the compose strategy's form, as a pure step.
3
+ *
4
+ * The from-reference slice of the compose page, lifted: name, save location,
5
+ * entity types (read-only tags when the reference fixed them, picker
6
+ * otherwise), language, and the editor. Deliberately NONE of the page's
7
+ * upload/format/encoding machinery — the fences reference mode already
8
+ * proved are here as absence pins. The draft is CONTROLLED by the host
9
+ * (WIZARD-NAVIGATION D3: stepping Back must not discard typed work), and
10
+ * the evidence display is the host's job (plan A3: no context redux here).
11
+ */
12
+ import { describe, it, expect, vi } from 'vitest';
13
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
14
+ import '@testing-library/jest-dom';
15
+ import { ComposeStep, type ComposeDraft } from '../ComposeStep';
16
+
17
+ // Mock CodeMirrorRenderer to avoid CodeMirror dependencies (same mock the
18
+ // compose page's own tests use).
19
+ vi.mock('../../CodeMirrorRenderer', () => ({
20
+ CodeMirrorRenderer: ({ content, onChange, editable }: any) => (
21
+ <textarea
22
+ data-testid="code-editor"
23
+ value={content}
24
+ disabled={!editable}
25
+ onChange={(e) => onChange?.(e.target.value)}
26
+ />
27
+ ),
28
+ }));
29
+
30
+ const T = {
31
+ resourceTitle: 'New resource title',
32
+ resourceTitlePlaceholder: 'Title…',
33
+ saveLocation: 'Save location',
34
+ entityTypes: 'Entity types',
35
+ language: 'Language',
36
+ contentLabel: 'Content',
37
+ back: 'Back',
38
+ createAndLink: 'Create & Link',
39
+ creatingAndLinking: 'Creating…',
40
+ };
41
+
42
+ const DRAFT: ComposeDraft = {
43
+ name: 'Black Hawk',
44
+ storagePath: 'people/black-hawk.md',
45
+ content: 'Sauk leader.',
46
+ entityTypes: [],
47
+ language: 'en',
48
+ };
49
+
50
+ function renderStep(over: Partial<React.ComponentProps<typeof ComposeStep>> = {}) {
51
+ const onDraftChange = vi.fn();
52
+ const onBack = vi.fn();
53
+ const onCompose = vi.fn<(p: unknown) => Promise<void>>().mockResolvedValue(undefined);
54
+ const utils = render(
55
+ <ComposeStep
56
+ draft={DRAFT}
57
+ onDraftChange={onDraftChange}
58
+ referenceEntityTypes={['Person']}
59
+ entityTypeOptions={['Person', 'Topic', 'Location']}
60
+ showLineNumbers={false}
61
+ hoverDelayMs={300}
62
+ onBack={onBack}
63
+ onCompose={onCompose}
64
+ translations={T}
65
+ {...over}
66
+ />,
67
+ );
68
+ return { ...utils, onDraftChange, onBack, onCompose };
69
+ }
70
+
71
+ describe('ComposeStep — the from-reference slice, and nothing else', () => {
72
+ it('renders the draft: name, save location, content, language', () => {
73
+ renderStep();
74
+ expect(screen.getByLabelText(T.resourceTitle)).toHaveValue('Black Hawk');
75
+ expect(screen.getByLabelText(T.saveLocation)).toHaveValue('people/black-hawk.md');
76
+ expect(screen.getByTestId('code-editor')).toHaveValue('Sauk leader.');
77
+ expect(screen.getByLabelText(T.language)).toBeInTheDocument();
78
+ });
79
+
80
+ it('edits flow through onDraftChange — the host owns the draft (D3)', () => {
81
+ const { onDraftChange } = renderStep();
82
+ fireEvent.change(screen.getByLabelText(T.resourceTitle), { target: { value: 'Black Hawk (Sauk)' } });
83
+ expect(onDraftChange).toHaveBeenCalledWith({ name: 'Black Hawk (Sauk)' });
84
+ fireEvent.change(screen.getByTestId('code-editor'), { target: { value: 'A Sauk leader.' } });
85
+ expect(onDraftChange).toHaveBeenCalledWith({ content: 'A Sauk leader.' });
86
+ });
87
+
88
+ it('reference-fixed entity types render as read-only tags, not a picker (D6)', () => {
89
+ renderStep(); // referenceEntityTypes: ['Person']
90
+ expect(screen.getByText('Person')).toBeInTheDocument();
91
+ // No toggling: the types were chosen when the reference was created.
92
+ expect(screen.queryByRole('button', { name: /Person/ })).not.toBeInTheDocument();
93
+ expect(screen.queryByRole('button', { name: /Topic/ })).not.toBeInTheDocument();
94
+ });
95
+
96
+ it('an empty reference set falls back to the picker over the owner-supplied options (D6)', () => {
97
+ const { onDraftChange } = renderStep({ referenceEntityTypes: [] });
98
+ const topic = screen.getByRole('button', { name: /Topic/ });
99
+ fireEvent.click(topic);
100
+ expect(onDraftChange).toHaveBeenCalledWith({ entityTypes: ['Topic'] });
101
+ });
102
+
103
+ it('carries NONE of the page-only machinery: no upload, no format, no encoding', () => {
104
+ const { container } = renderStep();
105
+ expect(container.querySelector('.semiont-form__upload-dropzone')).toBeNull();
106
+ expect(container.querySelector('.semiont-form__content-source-toggle')).toBeNull();
107
+ expect(container.querySelector('#format-select')).toBeNull();
108
+ expect(container.querySelector('#charset-select')).toBeNull();
109
+ });
110
+
111
+ it('renders no context of its own — the evidence is host-stacked (A3)', () => {
112
+ const { container } = renderStep();
113
+ expect(container.querySelector('.semiont-gather-pane')).toBeNull();
114
+ expect(container.querySelector('.semiont-gather__outer')).toBeNull();
115
+ });
116
+
117
+ it('submit emits the full params with the file:// prefix applied at the seam', async () => {
118
+ const { onCompose } = renderStep({ referenceEntityTypes: ['Person'] });
119
+ fireEvent.click(screen.getByRole('button', { name: T.createAndLink }));
120
+ await waitFor(() => expect(onCompose).toHaveBeenCalledTimes(1));
121
+ expect(onCompose).toHaveBeenCalledWith({
122
+ name: 'Black Hawk',
123
+ storagePath: 'file://people/black-hawk.md',
124
+ content: 'Sauk leader.',
125
+ entityTypes: ['Person'],
126
+ language: 'en',
127
+ });
128
+ });
129
+
130
+ it('the footer is a wizard footer: Back + Create, pending while in flight, re-enabled on rejection (A4)', async () => {
131
+ let reject!: (e: Error) => void;
132
+ const onCompose = vi.fn(() => new Promise<void>((_, r) => { reject = r; }));
133
+ const { container } = renderStep({ onCompose });
134
+
135
+ expect(container.querySelector('.semiont-modal__actions--wizard')).not.toBeNull();
136
+ fireEvent.click(screen.getByRole('button', { name: T.createAndLink }));
137
+ expect(await screen.findByRole('button', { name: T.creatingAndLinking })).toBeDisabled();
138
+
139
+ reject(new Error('boom'));
140
+ expect(await screen.findByRole('button', { name: T.createAndLink })).toBeEnabled();
141
+ });
142
+
143
+ it('Back is the only retreat and dismissal never lives in the footer (A4)', () => {
144
+ const { container, onBack } = renderStep();
145
+ const footerButtons = Array.from(
146
+ container.querySelectorAll('.semiont-modal__actions button'),
147
+ ).map((b) => b.textContent ?? '');
148
+ expect(footerButtons.filter((l) => /cancel|✕/i.test(l))).toEqual([]);
149
+ fireEvent.click(screen.getByRole('button', { name: `◀ ${T.back}` }));
150
+ expect(onBack).toHaveBeenCalledTimes(1);
151
+ });
152
+ });
@@ -16,13 +16,12 @@ const t = {
16
16
  includeSummary: 'Include summary',
17
17
  depth: 'Depth',
18
18
  maxResources: 'Max resources',
19
- cancel: 'Cancel',
20
19
  gather: 'Gather',
21
20
  };
22
21
 
23
22
  describe('ConfigureGatherStep', () => {
24
23
  it('renders the intro and the default option values', () => {
25
- render(<ConfigureGatherStep onGather={vi.fn()} onCancel={vi.fn()} translations={t} />);
24
+ render(<ConfigureGatherStep onGather={vi.fn()} translations={t} />);
26
25
  expect(screen.getByText('Choose what to include.')).toBeInTheDocument();
27
26
  expect(screen.getByLabelText('Include content')).toBeChecked();
28
27
  expect(screen.getByLabelText('Include summary')).toBeChecked();
@@ -32,14 +31,14 @@ describe('ConfigureGatherStep', () => {
32
31
 
33
32
  it('emits the default config on submit', () => {
34
33
  const onGather = vi.fn();
35
- render(<ConfigureGatherStep onGather={onGather} onCancel={vi.fn()} translations={t} />);
34
+ render(<ConfigureGatherStep onGather={onGather} translations={t} />);
36
35
  fireEvent.click(screen.getByRole('button', { name: /Gather/ }));
37
36
  expect(onGather).toHaveBeenCalledWith({ includeContent: true, includeSummary: true, depth: 2, maxResources: 10 });
38
37
  });
39
38
 
40
39
  it('reflects edits in the emitted config', () => {
41
40
  const onGather = vi.fn();
42
- render(<ConfigureGatherStep onGather={onGather} onCancel={vi.fn()} translations={t} />);
41
+ render(<ConfigureGatherStep onGather={onGather} translations={t} />);
43
42
  fireEvent.click(screen.getByLabelText('Include content')); // uncheck
44
43
  fireEvent.click(screen.getByLabelText('Include summary')); // uncheck
45
44
  fireEvent.change(screen.getByLabelText('Depth'), { target: { value: '4' } });
@@ -48,32 +47,21 @@ describe('ConfigureGatherStep', () => {
48
47
  expect(onGather).toHaveBeenCalledWith({ includeContent: false, includeSummary: false, depth: 4, maxResources: 25 });
49
48
  });
50
49
 
51
- it('honors the defaults prop', () => {
52
- const onGather = vi.fn();
53
- render(
54
- <ConfigureGatherStep
55
- defaults={{ includeSummary: false, depth: 1, maxResources: 5 }}
56
- onGather={onGather}
57
- onCancel={vi.fn()}
58
- translations={t}
59
- />,
60
- );
61
- expect(screen.getByLabelText('Include summary')).not.toBeChecked();
62
- expect(screen.getByLabelText('Depth')).toHaveValue(1);
63
- fireEvent.click(screen.getByRole('button', { name: /Gather/ }));
64
- expect(onGather).toHaveBeenCalledWith({ includeContent: true, includeSummary: false, depth: 1, maxResources: 5 });
65
- });
66
-
67
- it('calls onCancel from the cancel button', () => {
68
- const onCancel = vi.fn();
69
- render(<ConfigureGatherStep onGather={vi.fn()} onCancel={onCancel} translations={t} />);
70
- fireEvent.click(screen.getByRole('button', { name: /Cancel/ }));
71
- expect(onCancel).toHaveBeenCalledTimes(1);
50
+ it('the footer is the wizard footer: advance only, no dismissal, no flex (GFR A5)', () => {
51
+ // Dismissal lives on the modal's corner ✕/Esc/backdrop, never in a step
52
+ // footer (WIZARD-NAVIGATION D1); this is the first step, so no retreat either.
53
+ const { container } = render(<ConfigureGatherStep onGather={vi.fn()} translations={t} />);
54
+ const footer = container.querySelector('.semiont-modal__actions--wizard');
55
+ expect(footer).not.toBeNull();
56
+ const buttons = Array.from(footer!.querySelectorAll('button'));
57
+ expect(buttons).toHaveLength(1);
58
+ expect(buttons[0]!.textContent).not.toMatch(/cancel|✕/i);
59
+ expect(buttons[0]!.className).not.toContain('semiont-button--flex');
72
60
  });
73
61
 
74
62
  it('renders the children slot (exclusion picker)', () => {
75
63
  render(
76
- <ConfigureGatherStep onGather={vi.fn()} onCancel={vi.fn()} translations={t}>
64
+ <ConfigureGatherStep onGather={vi.fn()} translations={t}>
77
65
  <div>EXCLUDE-SLOT</div>
78
66
  </ConfigureGatherStep>,
79
67
  );