@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.
- package/README.md +28 -4
- package/dist/{PdfAnnotationCanvas.client-6ZSF6ZTJ.js → PdfAnnotationCanvas.client-C2IGG4SW.js} +33 -6
- package/dist/{PdfAnnotationCanvas.client-JCJMXWWJ.css → PdfAnnotationCanvas.client-TGDPPGXJ.css} +6 -1
- package/dist/{ar-YTUUGK2O.js → ar-FABPTJVB.js} +88 -26
- package/dist/{bn-ASGNGPMD.js → bn-LKP6BDMH.js} +88 -26
- package/dist/{chunk-L3WKRWP3.js → chunk-2GOHTTHW.js} +88 -26
- package/dist/{chunk-OBQOZCJE.js → chunk-2YHEE63S.js} +31 -31
- package/dist/{cs-WMYVAOZY.js → cs-YQYNWHRG.js} +88 -26
- package/dist/{da-FSHHN4D6.js → da-QHZOYKRY.js} +88 -26
- package/dist/{de-FJFO3TPT.js → de-GKA7RD7J.js} +88 -26
- package/dist/{el-XOH4QIAG.js → el-45T257AL.js} +88 -26
- package/dist/{en-3FCBW3YD.js → en-RVW5EQ66.js} +4 -2
- package/dist/{es-HDX4QI7E.js → es-AVER7RU4.js} +88 -26
- package/dist/{fa-DJ6WTFTJ.js → fa-K3VPK3O2.js} +88 -26
- package/dist/{fi-4S7H7W7U.js → fi-5NEKF7EB.js} +88 -26
- package/dist/{fr-6UJSYHXC.js → fr-FUMNDLBZ.js} +88 -26
- package/dist/{he-ZZJOHJX2.js → he-3JGNA3PR.js} +88 -26
- package/dist/{hi-HZJTAW7U.js → hi-IJY375DF.js} +88 -26
- package/dist/{id-E3I6ILEM.js → id-LWHKJ7ZD.js} +88 -26
- package/dist/index.css +380 -524
- package/dist/index.d.ts +318 -192
- package/dist/index.js +2960 -2421
- package/dist/integrations/css-modules-helper.js +1 -3
- package/dist/{it-ULAPAD7U.js → it-EBVOV4RW.js} +88 -26
- package/dist/{ja-SAHZQ4CZ.js → ja-KWRIX2NF.js} +88 -26
- package/dist/{ko-DFU2ADQ4.js → ko-L5CJMFDS.js} +88 -26
- package/dist/{ms-NWL76XNT.js → ms-LSNMJDR2.js} +88 -26
- package/dist/{nl-ATCM76WN.js → nl-ZB32MB7U.js} +88 -26
- package/dist/{no-UIAQ5RJF.js → no-4GTNY7M5.js} +88 -26
- package/dist/{pl-NOECQDXD.js → pl-LUHBLTHB.js} +88 -26
- package/dist/{pt-SVWIIUPC.js → pt-C4H3UNQ6.js} +88 -26
- package/dist/{ro-NR2DRTZG.js → ro-GGITS5V4.js} +88 -26
- package/dist/{sv-STVJ37JP.js → sv-T56RTLBP.js} +88 -26
- package/dist/test-utils.css +1 -1
- package/dist/test-utils.d.ts +1 -1
- package/dist/test-utils.js +2 -2
- package/dist/{th-UWQR4K54.js → th-22X2IWCB.js} +88 -26
- package/dist/{tr-WNXZSD27.js → tr-77TFG62H.js} +88 -26
- package/dist/{uk-SJRMB47Y.js → uk-MIIL6NXT.js} +88 -26
- package/dist/{vi-TMRQFES2.js → vi-7JYOIGNV.js} +88 -26
- package/dist/{zh-QOL3LYEM.js → zh-ID6DRYU4.js} +88 -26
- package/package.json +11 -10
- package/src/components/AssistProgress.tsx +178 -134
- package/src/components/CodeMirrorRenderer.tsx +8 -8
- package/src/components/{EntityFoundLog.tsx → ItemFoundLog.tsx} +12 -7
- package/src/components/ProtectedErrorBoundary.css +3 -1
- package/src/components/StatusDisplay.css +1 -1
- package/src/components/Toast.css +3 -1
- package/src/components/__tests__/AssistProgress.test.tsx +268 -141
- package/src/components/annotation/annotation-entries.css +0 -4
- package/src/components/annotation/references.css +0 -4
- package/src/components/image-annotation/AnnotationOverlay.tsx +6 -6
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +1 -1
- package/src/components/image-annotation/__tests__/AnnotationOverlay.click.test.tsx +7 -9
- package/src/components/image-annotation/__tests__/SvgDrawingCanvas.click.test.tsx +1 -2
- package/src/components/layout/Header.css +0 -4
- package/src/components/layout/LeftSidebar.css +0 -4
- package/src/components/modals/ComposeStep.tsx +218 -0
- package/src/components/modals/ConfigureGatherStep.tsx +10 -18
- package/src/components/modals/ConfigureGenerationStep.tsx +131 -54
- package/src/components/modals/ConfigureSearchStep.tsx +42 -40
- package/src/components/modals/ContextSummary.tsx +185 -72
- package/src/components/modals/CorpusPane.tsx +75 -0
- package/src/components/modals/DiscardPrompt.tsx +38 -0
- package/src/components/modals/GatherContextStep.tsx +237 -107
- package/src/components/modals/ReferenceWizardModal.tsx +287 -60
- package/src/components/modals/ResourceGenerateModal.tsx +233 -109
- package/src/components/modals/SearchModal.css +335 -111
- package/src/components/modals/SearchModal.tsx +1 -1
- package/src/components/modals/SearchResultsStep.tsx +63 -88
- package/src/components/modals/WizardFooter.tsx +69 -0
- package/src/components/modals/__tests__/ComposeStep.test.tsx +152 -0
- package/src/components/modals/__tests__/ConfigureGatherStep.test.tsx +14 -26
- package/src/components/modals/__tests__/ConfigureGenerationStep.test.tsx +290 -0
- package/src/components/modals/__tests__/ConfigureSearchStep.test.tsx +95 -0
- package/src/components/modals/__tests__/GatherContextStep.resource.test.tsx +463 -25
- package/src/components/modals/__tests__/ReferenceWizardModal.test.tsx +635 -0
- package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +277 -85
- package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +6 -2
- package/src/components/modals/__tests__/SearchResultsStep.test.tsx +61 -0
- package/src/components/modals/__tests__/WizardFooter.test.tsx +344 -0
- package/src/components/modals/modals.css +16 -42
- package/src/components/navigation/NavigationTabs.css +0 -20
- package/src/components/pdf-annotation/PdfAnnotationCanvas.css +21 -2
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +36 -3
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +41 -6
- package/src/components/resource/AnnotateView.tsx +19 -8
- package/src/components/resource/AnnotationHistory.tsx +2 -0
- package/src/components/resource/BrowseView.tsx +20 -9
- package/src/components/resource/ResourceViewer.tsx +27 -28
- package/src/components/resource/__tests__/AnnotateView.embeddable.test.tsx +1 -1
- package/src/components/resource/__tests__/AnnotateView.focus.test.tsx +50 -0
- package/src/components/resource/__tests__/AnnotationHistory.test.tsx +2 -0
- package/src/components/resource/__tests__/BrowseView.embeddable.test.tsx +1 -1
- package/src/components/resource/__tests__/BrowseView.test.tsx +38 -12
- package/src/components/resource/__tests__/ResourceViewer.embeddable.test.tsx +47 -3
- package/src/components/resource/annotate-renderers.tsx +3 -3
- package/src/components/resource/event-formatting.ts +0 -3
- package/src/components/resource/panels/AssessmentEntry.tsx +1 -1
- package/src/components/resource/panels/AssessmentPanel.css +5 -6
- package/src/components/resource/panels/AssistSection.css +95 -263
- package/src/components/resource/panels/AssistSection.tsx +4 -4
- package/src/components/resource/panels/AssistShell.tsx +12 -4
- package/src/components/resource/panels/CollaborationPanel.css +47 -22
- package/src/components/resource/panels/CollaborationPanel.tsx +93 -2
- package/src/components/resource/panels/CommentEntry.tsx +1 -1
- package/src/components/resource/panels/CommentsPanel.css +0 -16
- package/src/components/resource/panels/HighlightEntry.tsx +1 -1
- package/src/components/resource/panels/HighlightPanel.css +0 -7
- package/src/components/resource/panels/JsonLdPanel.css +0 -4
- package/src/components/resource/panels/ReferenceEntry.tsx +10 -3
- package/src/components/resource/panels/ReferencesPanel.css +0 -12
- package/src/components/resource/panels/ReferencesPanel.tsx +16 -20
- package/src/components/resource/panels/ResourceInfoPanel.css +0 -32
- package/src/components/resource/panels/ResourceInfoPanel.tsx +89 -20
- package/src/components/resource/panels/StatisticsPanel.css +0 -20
- package/src/components/resource/panels/TagEntry.tsx +1 -1
- package/src/components/resource/panels/TaggingPanel.css +0 -4
- package/src/components/resource/panels/TaggingPanel.tsx +2 -10
- package/src/components/resource/panels/UnifiedAnnotationsPanel.css +0 -4
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +7 -2
- package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +0 -1
- package/src/components/resource/panels/__tests__/AssistSection.test.tsx +48 -50
- package/src/components/resource/panels/__tests__/AssistShell.test.tsx +11 -6
- package/src/components/resource/panels/__tests__/CollaborationPanel.test.tsx +89 -0
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +1 -2
- package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +0 -1
- package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +44 -36
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +41 -1
- package/src/components/resource/panels/__tests__/ReferencesPanel.headless.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +67 -54
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +185 -7
- package/src/components/resource/panels/__tests__/TagEntry.test.tsx +0 -1
- package/src/components/settings/SettingsPanel.css +0 -4
- package/src/features/auth/auth.css +2 -26
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +0 -65
- package/src/features/resource-compose/components/ResourceComposePage.tsx +17 -130
- package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +1 -76
- package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -48
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +55 -0
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +29 -0
- package/src/features/resource-discovery/state/__tests__/discover-state-unit.test.ts +53 -3
- package/src/features/resource-discovery/state/discover-state-unit.ts +29 -8
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +71 -15
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +166 -71
- package/src/features/resource-viewer/hooks/__tests__/useResourceViewedReport.test.tsx +63 -0
- package/src/features/resource-viewer/hooks/useResourceViewedReport.ts +33 -0
- package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +2 -2
- package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +1 -1
- package/src/styles/core/buttons.css +3 -1
- package/src/styles/core/forms.css +35 -29
- package/src/styles/core/indicators.css +0 -4
- package/src/styles/core/inputs.css +0 -4
- package/src/styles/core/progress.css +2 -34
- package/src/styles/core/sliders.css +0 -4
- package/src/styles/core/toggles.css +0 -4
- package/src/styles/features/resource-discovery.css +13 -0
- package/src/styles/features/resource-viewer.css +0 -42
- package/src/styles/features/resource.css +0 -4
- package/src/styles/motivations/motivation-assessment.css +2 -2
- package/src/styles/panels/history-panel.css +0 -40
- package/src/styles/panels/user-panel.css +0 -3
- package/src/styles/patterns/panel-helpers.css +0 -27
- package/src/styles/patterns/panel-sections.css +0 -59
- package/src/styles/patterns/panels-base.css +0 -32
- package/translations/ar.json +84 -24
- package/translations/bn.json +84 -24
- package/translations/cs.json +84 -24
- package/translations/da.json +84 -24
- package/translations/de.json +84 -24
- package/translations/el.json +84 -24
- package/translations/en.json +84 -24
- package/translations/es.json +84 -24
- package/translations/fa.json +84 -24
- package/translations/fi.json +84 -24
- package/translations/fr.json +84 -24
- package/translations/he.json +84 -24
- package/translations/hi.json +84 -24
- package/translations/id.json +84 -24
- package/translations/it.json +84 -24
- package/translations/ja.json +84 -24
- package/translations/ko.json +84 -24
- package/translations/ms.json +84 -24
- package/translations/nl.json +84 -24
- package/translations/no.json +84 -24
- package/translations/pl.json +84 -24
- package/translations/pt.json +84 -24
- package/translations/ro.json +84 -24
- package/translations/sv.json +84 -24
- package/translations/th.json +84 -24
- package/translations/tr.json +84 -24
- package/translations/uk.json +84 -24
- package/translations/vi.json +84 -24
- package/translations/zh.json +84 -24
- package/dist/PdfAnnotationCanvas.client-6ZSF6ZTJ.js.map +0 -1
- package/dist/PdfAnnotationCanvas.client-JCJMXWWJ.css.map +0 -1
- package/dist/ar-YTUUGK2O.js.map +0 -1
- package/dist/bn-ASGNGPMD.js.map +0 -1
- package/dist/chunk-BTUG3VJJ.js.map +0 -1
- package/dist/chunk-L3WKRWP3.js.map +0 -1
- package/dist/chunk-O23TLLXW.js.map +0 -1
- package/dist/chunk-OBQOZCJE.js.map +0 -1
- package/dist/chunk-QT7LYM72.js.map +0 -1
- package/dist/chunk-VXASXU4K.js.map +0 -1
- package/dist/chunk-XUDKYAVC.js +0 -21
- package/dist/chunk-XUDKYAVC.js.map +0 -1
- package/dist/cs-WMYVAOZY.js.map +0 -1
- package/dist/da-FSHHN4D6.js.map +0 -1
- package/dist/de-FJFO3TPT.js.map +0 -1
- package/dist/el-XOH4QIAG.js.map +0 -1
- package/dist/en-3FCBW3YD.js.map +0 -1
- package/dist/es-HDX4QI7E.js.map +0 -1
- package/dist/fa-DJ6WTFTJ.js.map +0 -1
- package/dist/fi-4S7H7W7U.js.map +0 -1
- package/dist/fr-6UJSYHXC.js.map +0 -1
- package/dist/he-ZZJOHJX2.js.map +0 -1
- package/dist/hi-HZJTAW7U.js.map +0 -1
- package/dist/id-E3I6ILEM.js.map +0 -1
- package/dist/index.css.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/integrations/css-modules-helper.js.map +0 -1
- package/dist/integrations/styled-components-theme.js.map +0 -1
- package/dist/it-ULAPAD7U.js.map +0 -1
- package/dist/ja-SAHZQ4CZ.js.map +0 -1
- package/dist/ko-DFU2ADQ4.js.map +0 -1
- package/dist/ms-NWL76XNT.js.map +0 -1
- package/dist/nl-ATCM76WN.js.map +0 -1
- package/dist/no-UIAQ5RJF.js.map +0 -1
- package/dist/pl-NOECQDXD.js.map +0 -1
- package/dist/pt-SVWIIUPC.js.map +0 -1
- package/dist/ro-NR2DRTZG.js.map +0 -1
- package/dist/sv-STVJ37JP.js.map +0 -1
- package/dist/test-utils.css.map +0 -1
- package/dist/test-utils.js.map +0 -1
- package/dist/th-UWQR4K54.js.map +0 -1
- package/dist/tr-WNXZSD27.js.map +0 -1
- package/dist/uk-SJRMB47Y.js.map +0 -1
- package/dist/vi-TMRQFES2.js.map +0 -1
- package/dist/zh-QOL3LYEM.js.map +0 -1
- package/src/components/modals/ResourceSearchModal.tsx +0 -224
- package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +0 -159
- package/src/integrations/tailwind-plugin.cjs +0 -224
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
* ResourceComposePage Component
|
|
3
3
|
*
|
|
4
4
|
* Pure React component for creating and editing resources.
|
|
5
|
-
* Supports
|
|
5
|
+
* Supports two modes: new resource and clone. (Reference completion moved
|
|
6
|
+
* into the resolve wizard — COMPOSE-IN-MODAL.)
|
|
6
7
|
* All dependencies passed as props - no Next.js hooks!
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
import React, { useState, useEffect } from 'react';
|
|
10
|
-
import { deriveViews, type GatheredContext } from '@semiont/core';
|
|
11
11
|
import { capabilitiesOf, AUTHORABLE_MEDIA_TYPES, MEDIA_TYPES, mediaTypeForExtension, isSupportedMediaType, LOCALES } from '@semiont/core';
|
|
12
12
|
import type { UploadProgress } from '@semiont/sdk';
|
|
13
|
-
import { type CloneData
|
|
13
|
+
import { type CloneData } from '../state/compose-page-state-unit';
|
|
14
14
|
import { COMMON_PANELS, type ToolbarPanelType } from '../../../state/shell-state-unit';
|
|
15
15
|
import { buttonStyles } from '../../../lib/button-styles';
|
|
16
16
|
import { CodeMirrorRenderer } from '../../../components/CodeMirrorRenderer';
|
|
@@ -42,10 +42,8 @@ function detectUploadMediaType(file: File): string {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export interface ResourceComposePageProps {
|
|
45
|
-
mode: 'new' | 'clone'
|
|
45
|
+
mode: 'new' | 'clone';
|
|
46
46
|
cloneData?: CloneData | null;
|
|
47
|
-
referenceData?: ReferenceData | null;
|
|
48
|
-
gatheredContext?: GatheredContext | null;
|
|
49
47
|
|
|
50
48
|
// Available options
|
|
51
49
|
availableEntityTypes: string[];
|
|
@@ -74,10 +72,7 @@ export interface ResourceComposePageProps {
|
|
|
74
72
|
translations: {
|
|
75
73
|
title: string;
|
|
76
74
|
titleEditClone: string;
|
|
77
|
-
titleCompleteReference: string;
|
|
78
75
|
subtitleClone: string;
|
|
79
|
-
subtitleReference: string;
|
|
80
|
-
linkedNoticePrefix: string;
|
|
81
76
|
resourceName: string;
|
|
82
77
|
resourceNamePlaceholder: string;
|
|
83
78
|
entityTypes: string;
|
|
@@ -99,9 +94,7 @@ export interface ResourceComposePageProps {
|
|
|
99
94
|
cancel: string;
|
|
100
95
|
saving: string;
|
|
101
96
|
creating: string;
|
|
102
|
-
creatingAndLinking: string;
|
|
103
97
|
saveClonedResource: string;
|
|
104
|
-
createAndLinkResource: string;
|
|
105
98
|
createResource: string;
|
|
106
99
|
};
|
|
107
100
|
|
|
@@ -111,7 +104,7 @@ export interface ResourceComposePageProps {
|
|
|
111
104
|
}
|
|
112
105
|
|
|
113
106
|
export interface SaveResourceParams {
|
|
114
|
-
mode: 'new' | 'clone'
|
|
107
|
+
mode: 'new' | 'clone';
|
|
115
108
|
name: string;
|
|
116
109
|
storageUri: string;
|
|
117
110
|
content?: string;
|
|
@@ -121,15 +114,11 @@ export interface SaveResourceParams {
|
|
|
121
114
|
entityTypes?: string[];
|
|
122
115
|
language: string;
|
|
123
116
|
archiveOriginal?: boolean;
|
|
124
|
-
annotationUri?: string;
|
|
125
|
-
sourceDocumentId?: string;
|
|
126
117
|
}
|
|
127
118
|
|
|
128
119
|
export function ResourceComposePage({
|
|
129
120
|
mode,
|
|
130
121
|
cloneData,
|
|
131
|
-
referenceData,
|
|
132
|
-
gatheredContext,
|
|
133
122
|
availableEntityTypes,
|
|
134
123
|
initialLocale,
|
|
135
124
|
theme,
|
|
@@ -184,13 +173,8 @@ export function ResourceComposePage({
|
|
|
184
173
|
if (mode === 'clone' && cloneData) {
|
|
185
174
|
setNewResourceName(cloneData.sourceResource.name);
|
|
186
175
|
setNewResourceContent(cloneData.sourceContent);
|
|
187
|
-
} else if (mode === 'reference' && referenceData) {
|
|
188
|
-
setNewResourceName(referenceData.name);
|
|
189
|
-
if (referenceData.entityTypes.length > 0) {
|
|
190
|
-
setSelectedEntityTypes(referenceData.entityTypes);
|
|
191
|
-
}
|
|
192
176
|
}
|
|
193
|
-
}, [mode, cloneData
|
|
177
|
+
}, [mode, cloneData]);
|
|
194
178
|
|
|
195
179
|
// Handle file upload
|
|
196
180
|
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
@@ -266,20 +250,12 @@ export function ResourceComposePage({
|
|
|
266
250
|
if (mode === 'clone') {
|
|
267
251
|
params.archiveOriginal = archiveOriginal;
|
|
268
252
|
}
|
|
269
|
-
if (referenceData?.annotationUri) {
|
|
270
|
-
params.annotationUri = referenceData.annotationUri;
|
|
271
|
-
}
|
|
272
|
-
if (referenceData?.sourceDocumentId) {
|
|
273
|
-
params.sourceDocumentId = referenceData.sourceDocumentId;
|
|
274
|
-
}
|
|
275
253
|
|
|
276
254
|
await onSaveResource(params);
|
|
277
255
|
|
|
278
256
|
// Announce success based on mode
|
|
279
257
|
const successMessage = mode === 'clone'
|
|
280
258
|
? 'Resource cloned successfully'
|
|
281
|
-
: mode === 'reference'
|
|
282
|
-
? 'Reference completed successfully'
|
|
283
259
|
: 'Resource created successfully';
|
|
284
260
|
announceFormSuccess(successMessage);
|
|
285
261
|
} catch (error) {
|
|
@@ -291,13 +267,6 @@ export function ResourceComposePage({
|
|
|
291
267
|
};
|
|
292
268
|
|
|
293
269
|
const isClone = mode === 'clone';
|
|
294
|
-
const isReferenceCompletion = mode === 'reference';
|
|
295
|
-
|
|
296
|
-
// Gathered-context panel — annotation-focus only for now.
|
|
297
|
-
const composeFocus = gatheredContext?.focus.kind === 'annotation' ? gatheredContext.focus : null;
|
|
298
|
-
const composeConnections = gatheredContext && composeFocus
|
|
299
|
-
? deriveViews(gatheredContext.graph, String(composeFocus.sourceResource.id)).connections
|
|
300
|
-
: [];
|
|
301
270
|
|
|
302
271
|
return (
|
|
303
272
|
<div className={`semiont-page${activePanel && COMMON_PANELS.includes(activePanel as ToolbarPanelType) ? ' semiont-page--panel-open' : ''}`}>
|
|
@@ -306,73 +275,15 @@ export function ResourceComposePage({
|
|
|
306
275
|
{/* Page Title */}
|
|
307
276
|
<div className="semiont-page__header">
|
|
308
277
|
<h1 className="semiont-page__title">
|
|
309
|
-
{isClone ? t.titleEditClone :
|
|
278
|
+
{isClone ? t.titleEditClone : t.title}
|
|
310
279
|
</h1>
|
|
311
|
-
{
|
|
280
|
+
{isClone && (
|
|
312
281
|
<p className="semiont-page__subtitle">
|
|
313
|
-
{
|
|
282
|
+
{t.subtitleClone}
|
|
314
283
|
</p>
|
|
315
284
|
)}
|
|
316
|
-
{isReferenceCompletion && (
|
|
317
|
-
<div className="semiont-page__reference-notice">
|
|
318
|
-
<p className="semiont-page__reference-notice-text">
|
|
319
|
-
{t.linkedNoticePrefix}
|
|
320
|
-
</p>
|
|
321
|
-
</div>
|
|
322
|
-
)}
|
|
323
285
|
</div>
|
|
324
286
|
|
|
325
|
-
{/* Gathered Context Panel (from wizard) */}
|
|
326
|
-
{gatheredContext && (
|
|
327
|
-
<div className="semiont-form__field" style={{
|
|
328
|
-
marginBottom: '1.5rem',
|
|
329
|
-
padding: '1rem',
|
|
330
|
-
backgroundColor: 'var(--semiont-bg-secondary)',
|
|
331
|
-
borderRadius: 'var(--semiont-radius-md)',
|
|
332
|
-
border: '1px solid var(--semiont-border-primary)',
|
|
333
|
-
}}>
|
|
334
|
-
<h3 className="semiont-form__label" style={{ marginBottom: '0.75rem' }}>Gathered Context</h3>
|
|
335
|
-
{composeFocus?.selected && (
|
|
336
|
-
<div style={{
|
|
337
|
-
padding: '0.75rem',
|
|
338
|
-
backgroundColor: 'var(--semiont-bg-primary)',
|
|
339
|
-
borderRadius: 'var(--semiont-radius-md)',
|
|
340
|
-
border: '1px solid var(--semiont-border-primary)',
|
|
341
|
-
maxHeight: '150px',
|
|
342
|
-
overflowY: 'auto',
|
|
343
|
-
marginBottom: '0.5rem',
|
|
344
|
-
}}>
|
|
345
|
-
<div style={{ fontSize: 'var(--semiont-text-sm)', fontFamily: 'monospace', whiteSpace: 'pre-wrap', color: 'var(--semiont-text-secondary)' }}>
|
|
346
|
-
{composeFocus.selected.before && <span>{composeFocus.selected.before}</span>}
|
|
347
|
-
<span style={{
|
|
348
|
-
backgroundColor: 'var(--semiont-color-primary-100)',
|
|
349
|
-
padding: '0 0.25rem',
|
|
350
|
-
fontWeight: 600,
|
|
351
|
-
color: 'var(--semiont-color-primary-900)',
|
|
352
|
-
}}>
|
|
353
|
-
{composeFocus.selected.text}
|
|
354
|
-
</span>
|
|
355
|
-
{composeFocus.selected.after && <span>{composeFocus.selected.after}</span>}
|
|
356
|
-
</div>
|
|
357
|
-
</div>
|
|
358
|
-
)}
|
|
359
|
-
{composeConnections.length > 0 && (
|
|
360
|
-
<div style={{
|
|
361
|
-
padding: '0.5rem 0',
|
|
362
|
-
fontSize: 'var(--semiont-text-sm)',
|
|
363
|
-
color: 'var(--semiont-text-secondary)',
|
|
364
|
-
}}>
|
|
365
|
-
<span style={{ fontSize: 'var(--semiont-text-xs)', fontWeight: 500, marginRight: '0.5rem' }}>Connections:</span>
|
|
366
|
-
{composeConnections.map(conn => (
|
|
367
|
-
<span key={conn.resourceId} className="semiont-chip" style={{ fontSize: 'var(--semiont-text-xs)', padding: '0.125rem 0.5rem', marginRight: '0.25rem' }}>
|
|
368
|
-
{conn.resourceName}
|
|
369
|
-
</span>
|
|
370
|
-
))}
|
|
371
|
-
</div>
|
|
372
|
-
)}
|
|
373
|
-
</div>
|
|
374
|
-
)}
|
|
375
|
-
|
|
376
287
|
{/* Create Form */}
|
|
377
288
|
<div className="semiont-form">
|
|
378
289
|
<form onSubmit={handleSaveResource} className="semiont-form__fields semiont-compose-grid">
|
|
@@ -416,7 +327,7 @@ export function ResourceComposePage({
|
|
|
416
327
|
</div>
|
|
417
328
|
|
|
418
329
|
{/* Entity Types Selection */}
|
|
419
|
-
{(
|
|
330
|
+
{(
|
|
420
331
|
<div className="semiont-form__field semiont-form__entity-types">
|
|
421
332
|
<div className="semiont-form__label">
|
|
422
333
|
{t.entityTypes}
|
|
@@ -462,30 +373,6 @@ export function ResourceComposePage({
|
|
|
462
373
|
</div>
|
|
463
374
|
)}
|
|
464
375
|
|
|
465
|
-
{/* Entity Types Display for reference completion */}
|
|
466
|
-
{isReferenceCompletion && selectedEntityTypes.length > 0 && (
|
|
467
|
-
<div className="semiont-form__field semiont-form__entity-types-display" role="region" aria-labelledby="selected-entity-types-label">
|
|
468
|
-
<h3 id="selected-entity-types-label" className="semiont-form__label">
|
|
469
|
-
{t.entityTypes}
|
|
470
|
-
</h3>
|
|
471
|
-
<div className="semiont-form__entity-type-tags" role="list">
|
|
472
|
-
{selectedEntityTypes.map((type) => (
|
|
473
|
-
<span
|
|
474
|
-
key={type}
|
|
475
|
-
role="listitem"
|
|
476
|
-
className="semiont-form__entity-type-tag"
|
|
477
|
-
aria-label={`Entity type: ${type}`}
|
|
478
|
-
>
|
|
479
|
-
{type}
|
|
480
|
-
</span>
|
|
481
|
-
))}
|
|
482
|
-
</div>
|
|
483
|
-
<p className="semiont-form__helper-text" id="reference-entity-types-description">
|
|
484
|
-
These entity types were selected when creating the reference
|
|
485
|
-
</p>
|
|
486
|
-
</div>
|
|
487
|
-
)}
|
|
488
|
-
|
|
489
376
|
{/* Language Selector */}
|
|
490
377
|
<div className="semiont-form__field">
|
|
491
378
|
<label htmlFor="language-select" className="semiont-form__label">
|
|
@@ -512,7 +399,7 @@ export function ResourceComposePage({
|
|
|
512
399
|
<div className="semiont-compose-grid__content">
|
|
513
400
|
|
|
514
401
|
{/* Content Source Toggle - only show for new resources */}
|
|
515
|
-
{!isClone &&
|
|
402
|
+
{!isClone && (
|
|
516
403
|
<div className="semiont-form__field">
|
|
517
404
|
<label className="semiont-form__label">
|
|
518
405
|
{t.contentSource}
|
|
@@ -554,7 +441,7 @@ export function ResourceComposePage({
|
|
|
554
441
|
)}
|
|
555
442
|
|
|
556
443
|
{/* Upload File Section */}
|
|
557
|
-
{!isClone &&
|
|
444
|
+
{!isClone && inputMethod === 'upload' && (
|
|
558
445
|
<div className="semiont-form__upload-section">
|
|
559
446
|
<div>
|
|
560
447
|
<div className="semiont-form__upload-container">
|
|
@@ -631,10 +518,10 @@ export function ResourceComposePage({
|
|
|
631
518
|
)}
|
|
632
519
|
|
|
633
520
|
{/* Write Content Section */}
|
|
634
|
-
{(isClone ||
|
|
521
|
+
{(isClone || inputMethod === 'write') && (
|
|
635
522
|
<div className="semiont-form__write-section">
|
|
636
523
|
{/* Format Selector - only for manual entry, not clones */}
|
|
637
|
-
{!isClone &&
|
|
524
|
+
{!isClone && (
|
|
638
525
|
<div className="semiont-form__field">
|
|
639
526
|
<label htmlFor="format-select" className="semiont-form__label">
|
|
640
527
|
{t.format}
|
|
@@ -673,7 +560,7 @@ export function ResourceComposePage({
|
|
|
673
560
|
</div>
|
|
674
561
|
|
|
675
562
|
{/* Encoding Selector - only for manual text entry */}
|
|
676
|
-
{!isClone &&
|
|
563
|
+
{!isClone && (
|
|
677
564
|
<div className="semiont-form__field">
|
|
678
565
|
<label htmlFor="charset-select" className="semiont-form__label">
|
|
679
566
|
{t.encoding}
|
|
@@ -731,8 +618,8 @@ export function ResourceComposePage({
|
|
|
731
618
|
className={buttonStyles.primary.base}
|
|
732
619
|
>
|
|
733
620
|
{isCreating
|
|
734
|
-
? (isClone ? t.saving :
|
|
735
|
-
: (isClone ? t.saveClonedResource :
|
|
621
|
+
? (isClone ? t.saving : t.creating)
|
|
622
|
+
: (isClone ? t.saveClonedResource : t.createResource)}
|
|
736
623
|
</button>
|
|
737
624
|
</div>
|
|
738
625
|
|
|
@@ -64,56 +64,6 @@ describe('createComposePageStateUnit', () => {
|
|
|
64
64
|
stateUnit.dispose();
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
it('detects reference mode from params', async () => {
|
|
68
|
-
const stateUnit = createComposePageStateUnit(sessionOf(mockClient()), mockBrowse(), {
|
|
69
|
-
annotationUri: 'ann-1',
|
|
70
|
-
sourceDocumentId: 'doc-1',
|
|
71
|
-
name: 'Reference Doc',
|
|
72
|
-
entityTypes: 'Person,Place',
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
const mode = await firstValueFrom(stateUnit.mode$);
|
|
76
|
-
expect(mode).toBe('reference');
|
|
77
|
-
|
|
78
|
-
const ref = await firstValueFrom(stateUnit.referenceData$.pipe(filter((r) => r !== null)));
|
|
79
|
-
expect(ref!.annotationUri).toBe('ann-1');
|
|
80
|
-
expect(ref!.entityTypes).toEqual(['Person', 'Place']);
|
|
81
|
-
|
|
82
|
-
stateUnit.dispose();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('parses storedContext in reference mode', async () => {
|
|
86
|
-
const context = { annotation: { id: 'ann-1' }, sourceContext: 'text' };
|
|
87
|
-
const stateUnit = createComposePageStateUnit(sessionOf(mockClient()), mockBrowse(), {
|
|
88
|
-
annotationUri: 'ann-1',
|
|
89
|
-
sourceDocumentId: 'doc-1',
|
|
90
|
-
name: 'Ref',
|
|
91
|
-
storedContext: JSON.stringify(context),
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const gathered = await firstValueFrom(stateUnit.gatheredContext$.pipe(filter((g) => g !== null)));
|
|
95
|
-
expect(gathered).toEqual(context);
|
|
96
|
-
|
|
97
|
-
stateUnit.dispose();
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('ignores malformed storedContext', async () => {
|
|
101
|
-
const stateUnit = createComposePageStateUnit(sessionOf(mockClient()), mockBrowse(), {
|
|
102
|
-
annotationUri: 'ann-1',
|
|
103
|
-
sourceDocumentId: 'doc-1',
|
|
104
|
-
name: 'Ref',
|
|
105
|
-
storedContext: 'not-json{{{',
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
const loading = await firstValueFrom(stateUnit.loading$.pipe(filter((l) => !l)));
|
|
109
|
-
expect(loading).toBe(false);
|
|
110
|
-
|
|
111
|
-
const gathered = await firstValueFrom(stateUnit.gatheredContext$);
|
|
112
|
-
expect(gathered).toBeNull();
|
|
113
|
-
|
|
114
|
-
stateUnit.dispose();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
67
|
it('exposes entity types', async () => {
|
|
118
68
|
const stateUnit = createComposePageStateUnit(sessionOf(mockClient()), mockBrowse(), {});
|
|
119
69
|
|
|
@@ -142,31 +92,6 @@ describe('createComposePageStateUnit', () => {
|
|
|
142
92
|
stateUnit.dispose();
|
|
143
93
|
});
|
|
144
94
|
|
|
145
|
-
it('save in reference mode calls yield.resource then bind.body', async () => {
|
|
146
|
-
const resource = mockUpload('new-4');
|
|
147
|
-
const body = vi.fn().mockResolvedValue(undefined);
|
|
148
|
-
const stateUnit = createComposePageStateUnit(sessionOf(mockClient({ resource, body })), mockBrowse(), {
|
|
149
|
-
annotationUri: 'ann-1',
|
|
150
|
-
sourceDocumentId: 'doc-1',
|
|
151
|
-
name: 'Ref',
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
const id = await stateUnit.save({
|
|
155
|
-
mode: 'reference',
|
|
156
|
-
name: 'Ref Doc',
|
|
157
|
-
storageUri: '/docs/ref.md',
|
|
158
|
-
content: 'content',
|
|
159
|
-
language: 'en',
|
|
160
|
-
annotationUri: 'ann-1',
|
|
161
|
-
sourceDocumentId: 'doc-1',
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
expect(id).toBe('new-4');
|
|
165
|
-
expect(body).toHaveBeenCalledOnce();
|
|
166
|
-
|
|
167
|
-
stateUnit.dispose();
|
|
168
|
-
});
|
|
169
|
-
|
|
170
95
|
it('save in clone mode calls yield.createFromToken', async () => {
|
|
171
96
|
const createFromToken = vi.fn().mockResolvedValue({ resourceId: 'cloned-1' });
|
|
172
97
|
const stateUnit = createComposePageStateUnit(sessionOf(mockClient({ createFromToken })), mockBrowse(), {
|
|
@@ -196,7 +121,7 @@ describe('ComposePageStateUnit — StateUnit axioms', () => {
|
|
|
196
121
|
const browse = disposeProbe();
|
|
197
122
|
return { unit: createComposePageStateUnit(sessionOf(mockClient()), browse as unknown as ShellStateUnit, {}), passedIn: [browse] };
|
|
198
123
|
},
|
|
199
|
-
surfaces: (u) => [u.mode$, u.loading$, u.cloneData$, u.
|
|
124
|
+
surfaces: (u) => [u.mode$, u.loading$, u.cloneData$, u.uploadProgress$],
|
|
200
125
|
});
|
|
201
126
|
});
|
|
202
127
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BehaviorSubject, type Observable } from 'rxjs';
|
|
2
|
-
import type {
|
|
3
|
-
import { resourceId as makeResourceId
|
|
2
|
+
import type { AccessToken, ResourceDescriptor, ResourceId } from '@semiont/core';
|
|
3
|
+
import { resourceId as makeResourceId } from '@semiont/core';
|
|
4
4
|
import { createDisposer } from '@semiont/sdk';
|
|
5
5
|
import type { StateUnit } from '@semiont/core';
|
|
6
6
|
import type { ShellStateUnit } from '../../../state/shell-state-unit';
|
|
@@ -9,16 +9,11 @@ import type { SemiontSession } from '@semiont/sdk';
|
|
|
9
9
|
import { decodeWithCharset, extensionForMediaType } from '@semiont/core';
|
|
10
10
|
import type { UploadProgress } from '@semiont/sdk';
|
|
11
11
|
|
|
12
|
-
export type ComposeMode = 'new' | 'clone'
|
|
12
|
+
export type ComposeMode = 'new' | 'clone';
|
|
13
13
|
|
|
14
14
|
export interface ComposeParams {
|
|
15
15
|
mode?: string | undefined;
|
|
16
16
|
token?: string | undefined;
|
|
17
|
-
annotationUri?: string | undefined;
|
|
18
|
-
sourceDocumentId?: string | undefined;
|
|
19
|
-
name?: string | undefined;
|
|
20
|
-
entityTypes?: string | undefined;
|
|
21
|
-
storedContext?: string | undefined;
|
|
22
17
|
}
|
|
23
18
|
|
|
24
19
|
export interface CloneData {
|
|
@@ -26,13 +21,6 @@ export interface CloneData {
|
|
|
26
21
|
sourceContent: string;
|
|
27
22
|
}
|
|
28
23
|
|
|
29
|
-
export interface ReferenceData {
|
|
30
|
-
annotationUri: string;
|
|
31
|
-
sourceDocumentId: string;
|
|
32
|
-
name: string;
|
|
33
|
-
entityTypes: string[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
24
|
export interface SaveResourceParams {
|
|
37
25
|
mode: ComposeMode;
|
|
38
26
|
name: string;
|
|
@@ -44,8 +32,6 @@ export interface SaveResourceParams {
|
|
|
44
32
|
entityTypes?: string[];
|
|
45
33
|
language: string;
|
|
46
34
|
archiveOriginal?: boolean;
|
|
47
|
-
annotationUri?: string;
|
|
48
|
-
sourceDocumentId?: string;
|
|
49
35
|
}
|
|
50
36
|
|
|
51
37
|
export interface ComposePageStateUnit extends StateUnit {
|
|
@@ -53,8 +39,6 @@ export interface ComposePageStateUnit extends StateUnit {
|
|
|
53
39
|
mode$: Observable<ComposeMode>;
|
|
54
40
|
loading$: Observable<boolean>;
|
|
55
41
|
cloneData$: Observable<CloneData | null>;
|
|
56
|
-
referenceData$: Observable<ReferenceData | null>;
|
|
57
|
-
gatheredContext$: Observable<GatheredContext | null>;
|
|
58
42
|
entityTypes: ListState<string[]>;
|
|
59
43
|
/**
|
|
60
44
|
* Live upload-progress for the in-flight `save(...)` call. Emits the
|
|
@@ -78,34 +62,19 @@ export function createComposePageStateUnit(
|
|
|
78
62
|
// (`useShellStateUnit`), not this unit — do NOT add it to the disposer (it's the
|
|
79
63
|
// shared, app-scoped shell). See packages/sdk/docs/STATE-UNITS.md (composition rule).
|
|
80
64
|
|
|
81
|
-
const isReferenceMode = Boolean(params.annotationUri && params.sourceDocumentId && params.name);
|
|
82
65
|
const isCloneMode = params.mode === 'clone' && Boolean(params.token);
|
|
83
|
-
const pageMode: ComposeMode = isCloneMode ? 'clone' :
|
|
66
|
+
const pageMode: ComposeMode = isCloneMode ? 'clone' : 'new';
|
|
84
67
|
|
|
85
68
|
const mode$ = new BehaviorSubject<ComposeMode>(pageMode);
|
|
86
69
|
const loading$ = new BehaviorSubject<boolean>(true);
|
|
87
70
|
const cloneData$ = new BehaviorSubject<CloneData | null>(null);
|
|
88
|
-
const referenceData$ = new BehaviorSubject<ReferenceData | null>(null);
|
|
89
|
-
const gatheredContext$ = new BehaviorSubject<GatheredContext | null>(null);
|
|
90
71
|
const uploadProgress$ = new BehaviorSubject<UploadProgress | null>(null);
|
|
91
72
|
|
|
92
73
|
const entityTypes = trackList<string[]>(() => client.browse.entityTypes(), []);
|
|
93
74
|
disposer.add(entityTypes.dispose);
|
|
94
75
|
|
|
95
76
|
// Initialize based on mode
|
|
96
|
-
if (
|
|
97
|
-
const entityTypes = params.entityTypes ? params.entityTypes.split(',') : [];
|
|
98
|
-
referenceData$.next({
|
|
99
|
-
annotationUri: params.annotationUri!,
|
|
100
|
-
sourceDocumentId: params.sourceDocumentId!,
|
|
101
|
-
name: params.name!,
|
|
102
|
-
entityTypes,
|
|
103
|
-
});
|
|
104
|
-
if (params.storedContext) {
|
|
105
|
-
try { gatheredContext$.next(JSON.parse(params.storedContext)); } catch { /* ignore malformed */ }
|
|
106
|
-
}
|
|
107
|
-
loading$.next(false);
|
|
108
|
-
} else if (isCloneMode) {
|
|
77
|
+
if (isCloneMode) {
|
|
109
78
|
void (async () => {
|
|
110
79
|
try {
|
|
111
80
|
const tokenResult = await client.yield.fromToken(params.token!);
|
|
@@ -175,14 +144,6 @@ export function createComposePageStateUnit(
|
|
|
175
144
|
});
|
|
176
145
|
});
|
|
177
146
|
|
|
178
|
-
if (saveParams.mode === 'reference' && saveParams.annotationUri && saveParams.sourceDocumentId) {
|
|
179
|
-
await client.bind.body(
|
|
180
|
-
makeResourceId(saveParams.sourceDocumentId),
|
|
181
|
-
makeAnnotationId(saveParams.annotationUri) as AnnotationId,
|
|
182
|
-
[{ op: 'add', item: { type: 'SpecificResource' as const, source: newResourceId, purpose: 'linking' as const } }],
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
147
|
return newResourceId;
|
|
187
148
|
};
|
|
188
149
|
|
|
@@ -191,8 +152,6 @@ export function createComposePageStateUnit(
|
|
|
191
152
|
mode$: mode$.asObservable(),
|
|
192
153
|
loading$: loading$.asObservable(),
|
|
193
154
|
cloneData$: cloneData$.asObservable(),
|
|
194
|
-
referenceData$: referenceData$.asObservable(),
|
|
195
|
-
gatheredContext$: gatheredContext$.asObservable(),
|
|
196
155
|
entityTypes: entityTypes.state,
|
|
197
156
|
uploadProgress$: uploadProgress$.asObservable(),
|
|
198
157
|
save,
|
|
@@ -200,8 +159,6 @@ export function createComposePageStateUnit(
|
|
|
200
159
|
mode$.complete();
|
|
201
160
|
loading$.complete();
|
|
202
161
|
cloneData$.complete();
|
|
203
|
-
referenceData$.complete();
|
|
204
|
-
gatheredContext$.complete();
|
|
205
162
|
uploadProgress$.complete();
|
|
206
163
|
disposer.dispose();
|
|
207
164
|
},
|
|
@@ -45,6 +45,7 @@ const createMockProps = (overrides?: Partial<ResourceDiscoveryPageProps>): Resou
|
|
|
45
45
|
searchPlaceholder: 'Search resources...',
|
|
46
46
|
searchButton: 'Search',
|
|
47
47
|
searching: 'Searching...',
|
|
48
|
+
semanticFallbackNotice: 'No title matches — showing related documents.',
|
|
48
49
|
filterByEntityType: 'Filter by type',
|
|
49
50
|
all: 'All',
|
|
50
51
|
recentResources: 'Recent Resources',
|
|
@@ -70,6 +71,60 @@ const renderWithProviders = (ui: React.ReactElement) => {
|
|
|
70
71
|
};
|
|
71
72
|
|
|
72
73
|
describe('ResourceDiscoveryPage', () => {
|
|
74
|
+
// SEMANTIC-FALLBACK P3b / S11. The distinction is asserted through a stable
|
|
75
|
+
// hook, never the sentence: decision 3 set the wording and it will be revised
|
|
76
|
+
// from use, so a copy-pinned test would rot on the first edit and teach the
|
|
77
|
+
// next reader to weaken it.
|
|
78
|
+
describe('semantic fallback notice', () => {
|
|
79
|
+
const NOTICE = 'semiont-semantic-match-notice';
|
|
80
|
+
|
|
81
|
+
it('marks a semantic result set as distinct from a lexical one', () => {
|
|
82
|
+
const props = createMockProps({
|
|
83
|
+
searchQuery: 'kitten',
|
|
84
|
+
searchDocuments: [createMockResource('r1', 'All about cats')],
|
|
85
|
+
searchMatchKind: 'semantic',
|
|
86
|
+
});
|
|
87
|
+
renderWithProviders(<ResourceDiscoveryPage {...props} />);
|
|
88
|
+
|
|
89
|
+
expect(screen.getByTestId(NOTICE)).toBeTruthy();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('says nothing when the answer was lexical', () => {
|
|
93
|
+
const props = createMockProps({
|
|
94
|
+
searchQuery: 'cats',
|
|
95
|
+
searchDocuments: [createMockResource('r1', 'All about cats')],
|
|
96
|
+
searchMatchKind: 'lexical',
|
|
97
|
+
});
|
|
98
|
+
renderWithProviders(<ResourceDiscoveryPage {...props} />);
|
|
99
|
+
|
|
100
|
+
expect(screen.queryByTestId(NOTICE)).toBeNull();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('does not carry a stale label onto the no-query view', () => {
|
|
104
|
+
// Clearing the box returns to "recent", which no label describes; a
|
|
105
|
+
// leftover `semantic` must not annotate that list.
|
|
106
|
+
const props = createMockProps({
|
|
107
|
+
searchQuery: '',
|
|
108
|
+
searchDocuments: [],
|
|
109
|
+
searchMatchKind: 'semantic',
|
|
110
|
+
});
|
|
111
|
+
renderWithProviders(<ResourceDiscoveryPage {...props} />);
|
|
112
|
+
|
|
113
|
+
expect(screen.queryByTestId(NOTICE)).toBeNull();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('renders exactly as today when the prop is absent', () => {
|
|
117
|
+
const props = createMockProps({
|
|
118
|
+
searchQuery: 'cats',
|
|
119
|
+
searchDocuments: [createMockResource('r1', 'All about cats')],
|
|
120
|
+
});
|
|
121
|
+
renderWithProviders(<ResourceDiscoveryPage {...props} />);
|
|
122
|
+
|
|
123
|
+
expect(screen.queryByTestId(NOTICE)).toBeNull();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
|
|
73
128
|
beforeEach(() => {
|
|
74
129
|
});
|
|
75
130
|
|
|
@@ -28,6 +28,16 @@ export interface ResourceDiscoveryPageProps {
|
|
|
28
28
|
recentError?: Error | null;
|
|
29
29
|
onRetryRecent?: () => void;
|
|
30
30
|
isSearching: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* How the current search answer was produced — `'lexical'` (title/metadata
|
|
33
|
+
* matching) or `'semantic'` (the vector fallback that runs only when lexical
|
|
34
|
+
* matching returned nothing). Undefined before the first answer, and on the
|
|
35
|
+
* no-query view where `recent` renders and no label describes it.
|
|
36
|
+
*
|
|
37
|
+
* Optional so hosts that never wire it — including external consumers —
|
|
38
|
+
* render exactly as before (SEMANTIC-FALLBACK P3b).
|
|
39
|
+
*/
|
|
40
|
+
searchMatchKind?: 'lexical' | 'semantic';
|
|
31
41
|
|
|
32
42
|
// Controlled search state
|
|
33
43
|
searchQuery: string;
|
|
@@ -53,6 +63,7 @@ export interface ResourceDiscoveryPageProps {
|
|
|
53
63
|
searchPlaceholder: string;
|
|
54
64
|
searchButton: string;
|
|
55
65
|
searching: string;
|
|
66
|
+
semanticFallbackNotice: string;
|
|
56
67
|
filterByEntityType: string;
|
|
57
68
|
all: string;
|
|
58
69
|
recentResources: string;
|
|
@@ -80,6 +91,7 @@ export function ResourceDiscoveryPage({
|
|
|
80
91
|
recentError = null,
|
|
81
92
|
onRetryRecent,
|
|
82
93
|
isSearching,
|
|
94
|
+
searchMatchKind,
|
|
83
95
|
searchQuery,
|
|
84
96
|
onSearchQueryChange,
|
|
85
97
|
selectedEntityType,
|
|
@@ -232,6 +244,23 @@ export function ResourceDiscoveryPage({
|
|
|
232
244
|
}
|
|
233
245
|
</h3>
|
|
234
246
|
|
|
247
|
+
{/* Decision 3: label it, minimally. Someone who searches "kitten"
|
|
248
|
+
and gets documents about cats otherwise has no way to know why,
|
|
249
|
+
and an unexplained answer is a worse trust outcome than the
|
|
250
|
+
empty page this replaced. Only ever appears where the
|
|
251
|
+
alternative was that empty page (S5), so it never competes with
|
|
252
|
+
a better answer. `aria-live` because the results it explains
|
|
253
|
+
arrive without any control being activated. */}
|
|
254
|
+
{hasSearchQuery && searchMatchKind === 'semantic' && searchDocuments.length > 0 && (
|
|
255
|
+
<p
|
|
256
|
+
className="semiont-card__documents-note"
|
|
257
|
+
data-testid="semiont-semantic-match-notice"
|
|
258
|
+
aria-live="polite"
|
|
259
|
+
>
|
|
260
|
+
{t.semanticFallbackNotice}
|
|
261
|
+
</p>
|
|
262
|
+
)}
|
|
263
|
+
|
|
235
264
|
{showNoResultsWarning && (
|
|
236
265
|
<div className="semiont-card__warning">
|
|
237
266
|
<p className="semiont-card__warning-text">
|