@semiont/react-ui 0.5.27 → 0.5.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/{PdfAnnotationCanvas.client-EVUPTUWZ.js → PdfAnnotationCanvas.client-LSH3PZPY.js} +32 -5
- package/dist/{ar-OU3DQGC6.js → ar-AZBRADEB.js} +43 -23
- package/dist/{bn-PHWCR6AZ.js → bn-CCEW6AOS.js} +43 -23
- package/dist/{chunk-VXASXU4K.js → chunk-3ZBKCWUZ.js} +1 -1
- package/dist/{chunk-5VDTWRXJ.js → chunk-G4VHL4UA.js} +31 -31
- package/dist/{chunk-PCRRBU7H.js → chunk-SDKFZLEX.js} +43 -23
- package/dist/{cs-NEXYZ62I.js → cs-HXPF2CTE.js} +43 -23
- package/dist/{da-FAT43YCU.js → da-K5NTK7LH.js} +43 -23
- package/dist/{de-KRQ3WBCU.js → de-GMRQEDZI.js} +43 -23
- package/dist/{el-Q4IU3EKC.js → el-EUB4CI4G.js} +43 -23
- package/dist/{en-2KADE7N4.js → en-R5XA26NF.js} +2 -2
- package/dist/{es-2IO3R2MK.js → es-Z32DAZ6W.js} +43 -23
- package/dist/{fa-Q5GOQFXX.js → fa-G7HN6B32.js} +43 -23
- package/dist/{fi-R6NMRWBE.js → fi-3YBLZO7I.js} +43 -23
- package/dist/{fr-QY4D5E6J.js → fr-VUBH7BBS.js} +43 -23
- package/dist/{he-X6Y77XHY.js → he-WQQEY26B.js} +43 -23
- package/dist/{hi-XL7IOEQM.js → hi-HJZ6W4JB.js} +43 -23
- package/dist/{id-QZ4ZZS67.js → id-ZOGZ37MI.js} +43 -23
- package/dist/index.css +279 -74
- package/dist/index.d.ts +318 -393
- package/dist/index.js +2619 -2687
- package/dist/{it-ME2Q7OB6.js → it-DT6UW66F.js} +43 -23
- package/dist/{ja-HX7QASRI.js → ja-RUKQCOLO.js} +43 -23
- package/dist/{ko-EQO5AHAV.js → ko-ZH5ADKZM.js} +43 -23
- package/dist/{ms-UILFSF3I.js → ms-Y2AFPZTU.js} +43 -23
- package/dist/{nl-NNI45PMK.js → nl-OQEWIXNA.js} +43 -23
- package/dist/{no-MAL5F6P6.js → no-KPAYJZWY.js} +43 -23
- package/dist/{pl-4RMY6JAJ.js → pl-WSXK2UUD.js} +43 -23
- package/dist/{pt-VQH2X3QQ.js → pt-NWRJRUCK.js} +43 -23
- package/dist/{ro-XXCQO2OG.js → ro-3CGVMOC6.js} +43 -23
- package/dist/{sv-7Z6EJ6U4.js → sv-HGSEBA7S.js} +43 -23
- package/dist/test-utils.js +16 -7
- package/dist/{th-L3DXXYVQ.js → th-NZ4G352Z.js} +43 -23
- package/dist/{tr-P3DFVLFJ.js → tr-LGGS4DLW.js} +43 -23
- package/dist/{uk-7C5D3PXS.js → uk-AIX536L4.js} +43 -23
- package/dist/{vi-ZLTLEO7P.js → vi-V6CVQZLQ.js} +43 -23
- package/dist/{zh-5FHVWDIN.js → zh-AGKPGI3L.js} +43 -23
- package/package.json +16 -15
- package/src/components/AssistProgress.tsx +36 -2
- package/src/components/CodeMirrorRenderer.tsx +8 -8
- package/src/components/StatusDisplay.tsx +13 -13
- package/src/components/__tests__/AssistProgress.test.tsx +59 -0
- package/src/components/__tests__/StatusDisplay.test.tsx +9 -9
- package/src/components/modals/ComposeStep.tsx +233 -0
- package/src/components/modals/ConfigureGatherStep.tsx +10 -18
- package/src/components/modals/ConfigureGenerationStep.tsx +178 -34
- package/src/components/modals/ConfigureSearchStep.tsx +19 -0
- 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 +252 -59
- package/src/components/modals/ResourceGenerateModal.tsx +225 -112
- package/src/components/modals/SearchModal.css +337 -101
- package/src/components/modals/SearchResultsStep.tsx +61 -68
- package/src/components/modals/WizardFooter.tsx +7 -1
- 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 +206 -8
- package/src/components/modals/__tests__/ConfigureSearchStep.test.tsx +24 -1
- package/src/components/modals/__tests__/GatherContextStep.resource.test.tsx +463 -25
- package/src/components/modals/__tests__/ReferenceWizardModal.test.tsx +442 -39
- package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +300 -86
- package/src/components/modals/__tests__/SearchResultsStep.test.tsx +61 -0
- package/src/components/modals/__tests__/WizardFooter.test.tsx +116 -9
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +34 -1
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +38 -1
- package/src/components/resource/AnnotateView.tsx +3 -3
- package/src/components/resource/BrowseView.tsx +8 -8
- package/src/components/resource/DownloadFileLink.tsx +1 -1
- package/src/components/resource/ResourceViewer.tsx +4 -4
- package/src/components/resource/__tests__/AnnotateView.embeddable.test.tsx +1 -1
- package/src/components/resource/__tests__/BrowseView.embeddable.test.tsx +1 -1
- package/src/components/resource/__tests__/BrowseView.reference-hover.test.tsx +1 -1
- package/src/components/resource/__tests__/BrowseView.test.tsx +11 -11
- package/src/components/resource/__tests__/download-link.test.tsx +6 -6
- package/src/components/resource/annotate-renderers.tsx +3 -3
- package/src/components/resource/panels/AssistSection.css +35 -0
- package/src/components/resource/panels/ReferenceEntry.tsx +11 -4
- package/src/components/resource/panels/ReferencesPanel.tsx +9 -1
- package/src/components/resource/panels/ResourceInfoPanel.tsx +89 -20
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +7 -2
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +41 -0
- package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +84 -3
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +149 -7
- package/src/features/auth/__tests__/SignInForm.a11y.test.tsx +12 -12
- package/src/features/auth/components/SignInForm.tsx +53 -53
- package/src/features/auth-welcome/state/__tests__/welcome-state-unit.test.ts +2 -2
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +0 -65
- package/src/features/resource-compose/components/ResourceComposePage.tsx +35 -133
- package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +1 -100
- package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -57
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +1 -1
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +2 -2
- package/src/features/resource-discovery/state/discover-state-unit.ts +1 -1
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +73 -17
- package/src/features/resource-viewer/__tests__/generation-options.test.ts +53 -0
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +148 -83
- package/src/features/resource-viewer/generation-options.ts +29 -0
- package/src/styles/core/forms.css +35 -8
- package/src/styles/features/resource-viewer.css +0 -22
- package/src/styles/features/resource.css +0 -4
- package/src/styles/index.css +0 -1
- package/translations/ar.json +42 -22
- package/translations/bn.json +42 -22
- package/translations/cs.json +42 -22
- package/translations/da.json +42 -22
- package/translations/de.json +42 -22
- package/translations/el.json +42 -22
- package/translations/en.json +42 -22
- package/translations/es.json +42 -22
- package/translations/fa.json +42 -22
- package/translations/fi.json +42 -22
- package/translations/fr.json +42 -22
- package/translations/he.json +42 -22
- package/translations/hi.json +42 -22
- package/translations/id.json +42 -22
- package/translations/it.json +42 -22
- package/translations/ja.json +42 -22
- package/translations/ko.json +42 -22
- package/translations/ms.json +42 -22
- package/translations/nl.json +42 -22
- package/translations/no.json +42 -22
- package/translations/pl.json +42 -22
- package/translations/pt.json +42 -22
- package/translations/ro.json +42 -22
- package/translations/sv.json +42 -22
- package/translations/th.json +42 -22
- package/translations/tr.json +42 -22
- package/translations/uk.json +42 -22
- package/translations/vi.json +42 -22
- package/translations/zh.json +42 -22
- package/dist/PdfAnnotationCanvas.client-EVUPTUWZ.js.map +0 -1
- package/dist/PdfAnnotationCanvas.client-TGDPPGXJ.css.map +0 -1
- package/dist/ar-OU3DQGC6.js.map +0 -1
- package/dist/bn-PHWCR6AZ.js.map +0 -1
- package/dist/chunk-5VDTWRXJ.js.map +0 -1
- package/dist/chunk-BTUG3VJJ.js.map +0 -1
- package/dist/chunk-O23TLLXW.js.map +0 -1
- package/dist/chunk-PCRRBU7H.js.map +0 -1
- package/dist/chunk-QT7LYM72.js.map +0 -1
- package/dist/chunk-VXASXU4K.js.map +0 -1
- package/dist/cs-NEXYZ62I.js.map +0 -1
- package/dist/da-FAT43YCU.js.map +0 -1
- package/dist/de-KRQ3WBCU.js.map +0 -1
- package/dist/el-Q4IU3EKC.js.map +0 -1
- package/dist/en-2KADE7N4.js.map +0 -1
- package/dist/es-2IO3R2MK.js.map +0 -1
- package/dist/fa-Q5GOQFXX.js.map +0 -1
- package/dist/fi-R6NMRWBE.js.map +0 -1
- package/dist/fr-QY4D5E6J.js.map +0 -1
- package/dist/he-X6Y77XHY.js.map +0 -1
- package/dist/hi-XL7IOEQM.js.map +0 -1
- package/dist/id-QZ4ZZS67.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-ME2Q7OB6.js.map +0 -1
- package/dist/ja-HX7QASRI.js.map +0 -1
- package/dist/ko-EQO5AHAV.js.map +0 -1
- package/dist/ms-UILFSF3I.js.map +0 -1
- package/dist/nl-NNI45PMK.js.map +0 -1
- package/dist/no-MAL5F6P6.js.map +0 -1
- package/dist/pl-4RMY6JAJ.js.map +0 -1
- package/dist/pt-VQH2X3QQ.js.map +0 -1
- package/dist/ro-XXCQO2OG.js.map +0 -1
- package/dist/sv-7Z6EJ6U4.js.map +0 -1
- package/dist/test-utils.css.map +0 -1
- package/dist/test-utils.js.map +0 -1
- package/dist/th-L3DXXYVQ.js.map +0 -1
- package/dist/tr-P3DFVLFJ.js.map +0 -1
- package/dist/uk-7C5D3PXS.js.map +0 -1
- package/dist/vi-ZLTLEO7P.js.map +0 -1
- package/dist/zh-5FHVWDIN.js.map +0 -1
- package/src/components/modals/ResourceSearchModal.tsx +0 -224
- package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +0 -163
- package/src/features/admin-exchange/__tests__/AdminExchangePage.test.tsx +0 -137
- package/src/features/admin-exchange/__tests__/ExportCard.test.tsx +0 -41
- package/src/features/admin-exchange/__tests__/ImportCard.test.tsx +0 -148
- package/src/features/admin-exchange/__tests__/ImportProgress.test.tsx +0 -117
- package/src/features/admin-exchange/components/AdminExchangePage.tsx +0 -117
- package/src/features/admin-exchange/components/ExportCard.tsx +0 -35
- package/src/features/admin-exchange/components/ImportCard.tsx +0 -183
- package/src/features/admin-exchange/components/ImportProgress.tsx +0 -80
- package/src/features/admin-exchange/state/__tests__/exchange-state-unit.test.ts +0 -185
- package/src/features/admin-exchange/state/exchange-state-unit.ts +0 -133
- package/src/features/moderation-linked-data/__tests__/LinkedDataPage.test.tsx +0 -118
- package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +0 -118
- package/src/styles/features/exchange.css +0 -374
|
@@ -2,15 +2,16 @@
|
|
|
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 {
|
|
11
|
-
import {
|
|
11
|
+
import { capabilitiesOf, AUTHORABLE_MEDIA_TYPES, MEDIA_TYPES, mediaTypeForExtension, isSupportedMediaType, LOCALES, folderOf, proposeStoragePath, getStorageUri } from '@semiont/core';
|
|
12
|
+
import type { SupportedMediaType } from '@semiont/core';
|
|
12
13
|
import type { UploadProgress } from '@semiont/sdk';
|
|
13
|
-
import { type CloneData
|
|
14
|
+
import { type CloneData } from '../state/compose-page-state-unit';
|
|
14
15
|
import { COMMON_PANELS, type ToolbarPanelType } from '../../../state/shell-state-unit';
|
|
15
16
|
import { buttonStyles } from '../../../lib/button-styles';
|
|
16
17
|
import { CodeMirrorRenderer } from '../../../components/CodeMirrorRenderer';
|
|
@@ -42,10 +43,8 @@ function detectUploadMediaType(file: File): string {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export interface ResourceComposePageProps {
|
|
45
|
-
mode: 'new' | 'clone'
|
|
46
|
+
mode: 'new' | 'clone';
|
|
46
47
|
cloneData?: CloneData | null;
|
|
47
|
-
referenceData?: ReferenceData | null;
|
|
48
|
-
gatheredContext?: GatheredContext | null;
|
|
49
48
|
|
|
50
49
|
// Available options
|
|
51
50
|
availableEntityTypes: string[];
|
|
@@ -74,10 +73,7 @@ export interface ResourceComposePageProps {
|
|
|
74
73
|
translations: {
|
|
75
74
|
title: string;
|
|
76
75
|
titleEditClone: string;
|
|
77
|
-
titleCompleteReference: string;
|
|
78
76
|
subtitleClone: string;
|
|
79
|
-
subtitleReference: string;
|
|
80
|
-
linkedNoticePrefix: string;
|
|
81
77
|
resourceName: string;
|
|
82
78
|
resourceNamePlaceholder: string;
|
|
83
79
|
entityTypes: string;
|
|
@@ -99,9 +95,7 @@ export interface ResourceComposePageProps {
|
|
|
99
95
|
cancel: string;
|
|
100
96
|
saving: string;
|
|
101
97
|
creating: string;
|
|
102
|
-
creatingAndLinking: string;
|
|
103
98
|
saveClonedResource: string;
|
|
104
|
-
createAndLinkResource: string;
|
|
105
99
|
createResource: string;
|
|
106
100
|
};
|
|
107
101
|
|
|
@@ -111,7 +105,7 @@ export interface ResourceComposePageProps {
|
|
|
111
105
|
}
|
|
112
106
|
|
|
113
107
|
export interface SaveResourceParams {
|
|
114
|
-
mode: 'new' | 'clone'
|
|
108
|
+
mode: 'new' | 'clone';
|
|
115
109
|
name: string;
|
|
116
110
|
storageUri: string;
|
|
117
111
|
content?: string;
|
|
@@ -121,15 +115,11 @@ export interface SaveResourceParams {
|
|
|
121
115
|
entityTypes?: string[];
|
|
122
116
|
language: string;
|
|
123
117
|
archiveOriginal?: boolean;
|
|
124
|
-
annotationUri?: string;
|
|
125
|
-
sourceDocumentId?: string;
|
|
126
118
|
}
|
|
127
119
|
|
|
128
120
|
export function ResourceComposePage({
|
|
129
121
|
mode,
|
|
130
122
|
cloneData,
|
|
131
|
-
referenceData,
|
|
132
|
-
gatheredContext,
|
|
133
123
|
availableEntityTypes,
|
|
134
124
|
initialLocale,
|
|
135
125
|
theme,
|
|
@@ -179,18 +169,27 @@ export function ResourceComposePage({
|
|
|
179
169
|
// Archive original checkbox (for clones only)
|
|
180
170
|
const [archiveOriginal, setArchiveOriginal] = useState(true);
|
|
181
171
|
|
|
172
|
+
// D11 — the Save location starts filled and follows the title until the user
|
|
173
|
+
// takes it over; `pathTouched` is derived, so clearing restores the proposal.
|
|
174
|
+
// A clone proposes beside its source; a new resource proposes at the root.
|
|
175
|
+
const composeFormat: SupportedMediaType =
|
|
176
|
+
isSupportedMediaType(selectedFormat) ? selectedFormat : 'text/markdown';
|
|
177
|
+
const pathTouched = storagePath !== '';
|
|
178
|
+
const effectivePath = pathTouched
|
|
179
|
+
? storagePath
|
|
180
|
+
: proposeStoragePath(
|
|
181
|
+
mode === 'clone' && cloneData ? folderOf(getStorageUri(cloneData.sourceResource)) : '',
|
|
182
|
+
newResourceName,
|
|
183
|
+
composeFormat,
|
|
184
|
+
);
|
|
185
|
+
|
|
182
186
|
// Initialize form data based on mode
|
|
183
187
|
useEffect(() => {
|
|
184
188
|
if (mode === 'clone' && cloneData) {
|
|
185
189
|
setNewResourceName(cloneData.sourceResource.name);
|
|
186
190
|
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
191
|
}
|
|
193
|
-
}, [mode, cloneData
|
|
192
|
+
}, [mode, cloneData]);
|
|
194
193
|
|
|
195
194
|
// Handle file upload
|
|
196
195
|
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
@@ -250,7 +249,7 @@ export function ResourceComposePage({
|
|
|
250
249
|
const params: SaveResourceParams = {
|
|
251
250
|
mode,
|
|
252
251
|
name: newResourceName,
|
|
253
|
-
storageUri: `file://${
|
|
252
|
+
storageUri: `file://${effectivePath}`,
|
|
254
253
|
content: newResourceContent,
|
|
255
254
|
format: uploadedFile ? fileMimeType : selectedFormat,
|
|
256
255
|
entityTypes: selectedEntityTypes,
|
|
@@ -266,20 +265,12 @@ export function ResourceComposePage({
|
|
|
266
265
|
if (mode === 'clone') {
|
|
267
266
|
params.archiveOriginal = archiveOriginal;
|
|
268
267
|
}
|
|
269
|
-
if (referenceData?.annotationUri) {
|
|
270
|
-
params.annotationUri = referenceData.annotationUri;
|
|
271
|
-
}
|
|
272
|
-
if (referenceData?.sourceDocumentId) {
|
|
273
|
-
params.sourceDocumentId = referenceData.sourceDocumentId;
|
|
274
|
-
}
|
|
275
268
|
|
|
276
269
|
await onSaveResource(params);
|
|
277
270
|
|
|
278
271
|
// Announce success based on mode
|
|
279
272
|
const successMessage = mode === 'clone'
|
|
280
273
|
? 'Resource cloned successfully'
|
|
281
|
-
: mode === 'reference'
|
|
282
|
-
? 'Reference completed successfully'
|
|
283
274
|
: 'Resource created successfully';
|
|
284
275
|
announceFormSuccess(successMessage);
|
|
285
276
|
} catch (error) {
|
|
@@ -291,13 +282,6 @@ export function ResourceComposePage({
|
|
|
291
282
|
};
|
|
292
283
|
|
|
293
284
|
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
285
|
|
|
302
286
|
return (
|
|
303
287
|
<div className={`semiont-page${activePanel && COMMON_PANELS.includes(activePanel as ToolbarPanelType) ? ' semiont-page--panel-open' : ''}`}>
|
|
@@ -306,73 +290,15 @@ export function ResourceComposePage({
|
|
|
306
290
|
{/* Page Title */}
|
|
307
291
|
<div className="semiont-page__header">
|
|
308
292
|
<h1 className="semiont-page__title">
|
|
309
|
-
{isClone ? t.titleEditClone :
|
|
293
|
+
{isClone ? t.titleEditClone : t.title}
|
|
310
294
|
</h1>
|
|
311
|
-
{
|
|
295
|
+
{isClone && (
|
|
312
296
|
<p className="semiont-page__subtitle">
|
|
313
|
-
{
|
|
297
|
+
{t.subtitleClone}
|
|
314
298
|
</p>
|
|
315
299
|
)}
|
|
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
300
|
</div>
|
|
324
301
|
|
|
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
302
|
{/* Create Form */}
|
|
377
303
|
<div className="semiont-form">
|
|
378
304
|
<form onSubmit={handleSaveResource} className="semiont-form__fields semiont-compose-grid">
|
|
@@ -405,7 +331,7 @@ export function ResourceComposePage({
|
|
|
405
331
|
<input
|
|
406
332
|
id="storagePath"
|
|
407
333
|
type="text"
|
|
408
|
-
value={
|
|
334
|
+
value={effectivePath}
|
|
409
335
|
onChange={(e) => setStoragePath(e.target.value)}
|
|
410
336
|
placeholder="docs/my-resource.md"
|
|
411
337
|
required
|
|
@@ -416,7 +342,7 @@ export function ResourceComposePage({
|
|
|
416
342
|
</div>
|
|
417
343
|
|
|
418
344
|
{/* Entity Types Selection */}
|
|
419
|
-
{(
|
|
345
|
+
{(
|
|
420
346
|
<div className="semiont-form__field semiont-form__entity-types">
|
|
421
347
|
<div className="semiont-form__label">
|
|
422
348
|
{t.entityTypes}
|
|
@@ -462,30 +388,6 @@ export function ResourceComposePage({
|
|
|
462
388
|
</div>
|
|
463
389
|
)}
|
|
464
390
|
|
|
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
391
|
{/* Language Selector */}
|
|
490
392
|
<div className="semiont-form__field">
|
|
491
393
|
<label htmlFor="language-select" className="semiont-form__label">
|
|
@@ -512,7 +414,7 @@ export function ResourceComposePage({
|
|
|
512
414
|
<div className="semiont-compose-grid__content">
|
|
513
415
|
|
|
514
416
|
{/* Content Source Toggle - only show for new resources */}
|
|
515
|
-
{!isClone &&
|
|
417
|
+
{!isClone && (
|
|
516
418
|
<div className="semiont-form__field">
|
|
517
419
|
<label className="semiont-form__label">
|
|
518
420
|
{t.contentSource}
|
|
@@ -554,7 +456,7 @@ export function ResourceComposePage({
|
|
|
554
456
|
)}
|
|
555
457
|
|
|
556
458
|
{/* Upload File Section */}
|
|
557
|
-
{!isClone &&
|
|
459
|
+
{!isClone && inputMethod === 'upload' && (
|
|
558
460
|
<div className="semiont-form__upload-section">
|
|
559
461
|
<div>
|
|
560
462
|
<div className="semiont-form__upload-container">
|
|
@@ -631,10 +533,10 @@ export function ResourceComposePage({
|
|
|
631
533
|
)}
|
|
632
534
|
|
|
633
535
|
{/* Write Content Section */}
|
|
634
|
-
{(isClone ||
|
|
536
|
+
{(isClone || inputMethod === 'write') && (
|
|
635
537
|
<div className="semiont-form__write-section">
|
|
636
538
|
{/* Format Selector - only for manual entry, not clones */}
|
|
637
|
-
{!isClone &&
|
|
539
|
+
{!isClone && (
|
|
638
540
|
<div className="semiont-form__field">
|
|
639
541
|
<label htmlFor="format-select" className="semiont-form__label">
|
|
640
542
|
{t.format}
|
|
@@ -673,7 +575,7 @@ export function ResourceComposePage({
|
|
|
673
575
|
</div>
|
|
674
576
|
|
|
675
577
|
{/* Encoding Selector - only for manual text entry */}
|
|
676
|
-
{!isClone &&
|
|
578
|
+
{!isClone && (
|
|
677
579
|
<div className="semiont-form__field">
|
|
678
580
|
<label htmlFor="charset-select" className="semiont-form__label">
|
|
679
581
|
{t.encoding}
|
|
@@ -731,8 +633,8 @@ export function ResourceComposePage({
|
|
|
731
633
|
className={buttonStyles.primary.base}
|
|
732
634
|
>
|
|
733
635
|
{isCreating
|
|
734
|
-
? (isClone ? t.saving :
|
|
735
|
-
: (isClone ? t.saveClonedResource :
|
|
636
|
+
? (isClone ? t.saving : t.creating)
|
|
637
|
+
: (isClone ? t.saveClonedResource : t.createResource)}
|
|
736
638
|
</button>
|
|
737
639
|
</div>
|
|
738
640
|
|
|
@@ -64,80 +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
|
-
// A real `GatheredContext` shape. The old fixture here was
|
|
87
|
-
// `{ annotation, sourceContext }` — not a context at all — and it round-tripped
|
|
88
|
-
// because nothing checked. That is the hole the guard closes.
|
|
89
|
-
const context = {
|
|
90
|
-
focus: { kind: 'annotation', annotation: { id: 'ann-1' }, sourceResource: { id: 'doc-1' } },
|
|
91
|
-
graph: { nodes: [], edges: [] },
|
|
92
|
-
metadata: {},
|
|
93
|
-
};
|
|
94
|
-
const stateUnit = createComposePageStateUnit(sessionOf(mockClient()), mockBrowse(), {
|
|
95
|
-
annotationUri: 'ann-1',
|
|
96
|
-
sourceDocumentId: 'doc-1',
|
|
97
|
-
name: 'Ref',
|
|
98
|
-
storedContext: JSON.stringify(context),
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const gathered = await firstValueFrom(stateUnit.gatheredContext$.pipe(filter((g) => g !== null)));
|
|
102
|
-
expect(gathered).toEqual(context);
|
|
103
|
-
|
|
104
|
-
stateUnit.dispose();
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('ignores a stash that parses but is not a GatheredContext', async () => {
|
|
108
|
-
// The realistic case: an entry written by an older build, read back after a
|
|
109
|
-
// deploy. It used to sail through and reach `deriveViews`, which maps
|
|
110
|
-
// `graph.nodes` during render and throws instead of degrading.
|
|
111
|
-
const stateUnit = createComposePageStateUnit(sessionOf(mockClient()), mockBrowse(), {
|
|
112
|
-
annotationUri: 'ann-1',
|
|
113
|
-
sourceDocumentId: 'doc-1',
|
|
114
|
-
name: 'Ref',
|
|
115
|
-
storedContext: JSON.stringify({ annotation: { id: 'ann-1' }, sourceContext: 'text' }),
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// Treated as NO stash: the ordinary gather path owns the outcome from here.
|
|
119
|
-
expect(await firstValueFrom(stateUnit.gatheredContext$)).toBeNull();
|
|
120
|
-
|
|
121
|
-
stateUnit.dispose();
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('ignores malformed storedContext', async () => {
|
|
125
|
-
const stateUnit = createComposePageStateUnit(sessionOf(mockClient()), mockBrowse(), {
|
|
126
|
-
annotationUri: 'ann-1',
|
|
127
|
-
sourceDocumentId: 'doc-1',
|
|
128
|
-
name: 'Ref',
|
|
129
|
-
storedContext: 'not-json{{{',
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
const loading = await firstValueFrom(stateUnit.loading$.pipe(filter((l) => !l)));
|
|
133
|
-
expect(loading).toBe(false);
|
|
134
|
-
|
|
135
|
-
const gathered = await firstValueFrom(stateUnit.gatheredContext$);
|
|
136
|
-
expect(gathered).toBeNull();
|
|
137
|
-
|
|
138
|
-
stateUnit.dispose();
|
|
139
|
-
});
|
|
140
|
-
|
|
141
67
|
it('exposes entity types', async () => {
|
|
142
68
|
const stateUnit = createComposePageStateUnit(sessionOf(mockClient()), mockBrowse(), {});
|
|
143
69
|
|
|
@@ -166,31 +92,6 @@ describe('createComposePageStateUnit', () => {
|
|
|
166
92
|
stateUnit.dispose();
|
|
167
93
|
});
|
|
168
94
|
|
|
169
|
-
it('save in reference mode calls yield.resource then bind.body', async () => {
|
|
170
|
-
const resource = mockUpload('new-4');
|
|
171
|
-
const body = vi.fn().mockResolvedValue(undefined);
|
|
172
|
-
const stateUnit = createComposePageStateUnit(sessionOf(mockClient({ resource, body })), mockBrowse(), {
|
|
173
|
-
annotationUri: 'ann-1',
|
|
174
|
-
sourceDocumentId: 'doc-1',
|
|
175
|
-
name: 'Ref',
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
const id = await stateUnit.save({
|
|
179
|
-
mode: 'reference',
|
|
180
|
-
name: 'Ref Doc',
|
|
181
|
-
storageUri: '/docs/ref.md',
|
|
182
|
-
content: 'content',
|
|
183
|
-
language: 'en',
|
|
184
|
-
annotationUri: 'ann-1',
|
|
185
|
-
sourceDocumentId: 'doc-1',
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
expect(id).toBe('new-4');
|
|
189
|
-
expect(body).toHaveBeenCalledOnce();
|
|
190
|
-
|
|
191
|
-
stateUnit.dispose();
|
|
192
|
-
});
|
|
193
|
-
|
|
194
95
|
it('save in clone mode calls yield.createFromToken', async () => {
|
|
195
96
|
const createFromToken = vi.fn().mockResolvedValue({ resourceId: 'cloned-1' });
|
|
196
97
|
const stateUnit = createComposePageStateUnit(sessionOf(mockClient({ createFromToken })), mockBrowse(), {
|
|
@@ -220,7 +121,7 @@ describe('ComposePageStateUnit — StateUnit axioms', () => {
|
|
|
220
121
|
const browse = disposeProbe();
|
|
221
122
|
return { unit: createComposePageStateUnit(sessionOf(mockClient()), browse as unknown as ShellStateUnit, {}), passedIn: [browse] };
|
|
222
123
|
},
|
|
223
|
-
surfaces: (u) => [u.mode$, u.loading$, u.cloneData$, u.
|
|
124
|
+
surfaces: (u) => [u.mode$, u.loading$, u.cloneData$, u.uploadProgress$],
|
|
224
125
|
});
|
|
225
126
|
});
|
|
226
127
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BehaviorSubject, type Observable } from 'rxjs';
|
|
2
|
-
import type {
|
|
3
|
-
import {
|
|
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,43 +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
|
-
// The stash is the one place a `GatheredContext` arrives with its type
|
|
106
|
-
// history severed (sessionStorage → JSON.parse), so it is checked here and
|
|
107
|
-
// trusted everywhere after. A stale entry — a tab held open across a deploy
|
|
108
|
-
// — used to reach `deriveViews`, which maps `graph.nodes` during render and
|
|
109
|
-
// throws rather than degrading. A failed guard is treated as NO stash: the
|
|
110
|
-
// ordinary gather path runs, with the loading and error states it already has.
|
|
111
|
-
try {
|
|
112
|
-
const parsed: unknown = JSON.parse(params.storedContext);
|
|
113
|
-
if (isGatheredContext(parsed)) gatheredContext$.next(parsed);
|
|
114
|
-
} catch { /* ignore malformed */ }
|
|
115
|
-
}
|
|
116
|
-
loading$.next(false);
|
|
117
|
-
} else if (isCloneMode) {
|
|
77
|
+
if (isCloneMode) {
|
|
118
78
|
void (async () => {
|
|
119
79
|
try {
|
|
120
80
|
const tokenResult = await client.yield.fromToken(params.token!);
|
|
@@ -184,14 +144,6 @@ export function createComposePageStateUnit(
|
|
|
184
144
|
});
|
|
185
145
|
});
|
|
186
146
|
|
|
187
|
-
if (saveParams.mode === 'reference' && saveParams.annotationUri && saveParams.sourceDocumentId) {
|
|
188
|
-
await client.bind.body(
|
|
189
|
-
makeResourceId(saveParams.sourceDocumentId),
|
|
190
|
-
makeAnnotationId(saveParams.annotationUri) as AnnotationId,
|
|
191
|
-
[{ op: 'add', item: { type: 'SpecificResource' as const, source: newResourceId, purpose: 'linking' as const } }],
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
147
|
return newResourceId;
|
|
196
148
|
};
|
|
197
149
|
|
|
@@ -200,8 +152,6 @@ export function createComposePageStateUnit(
|
|
|
200
152
|
mode$: mode$.asObservable(),
|
|
201
153
|
loading$: loading$.asObservable(),
|
|
202
154
|
cloneData$: cloneData$.asObservable(),
|
|
203
|
-
referenceData$: referenceData$.asObservable(),
|
|
204
|
-
gatheredContext$: gatheredContext$.asObservable(),
|
|
205
155
|
entityTypes: entityTypes.state,
|
|
206
156
|
uploadProgress$: uploadProgress$.asObservable(),
|
|
207
157
|
save,
|
|
@@ -209,8 +159,6 @@ export function createComposePageStateUnit(
|
|
|
209
159
|
mode$.complete();
|
|
210
160
|
loading$.complete();
|
|
211
161
|
cloneData$.complete();
|
|
212
|
-
referenceData$.complete();
|
|
213
|
-
gatheredContext$.complete();
|
|
214
162
|
uploadProgress$.complete();
|
|
215
163
|
disposer.dispose();
|
|
216
164
|
},
|
|
@@ -354,7 +354,7 @@ describe('ResourceDiscoveryPage', () => {
|
|
|
354
354
|
});
|
|
355
355
|
|
|
356
356
|
it('renders the recentDocuments prop as-is without applying any post-filter', () => {
|
|
357
|
-
// The component is now controlled —
|
|
357
|
+
// The component is now controlled — gateway filtering means
|
|
358
358
|
// `recentDocuments` already contains only the resources matching the
|
|
359
359
|
// active `selectedEntityType`. The component must not re-filter.
|
|
360
360
|
const props = createMockProps({
|
|
@@ -44,7 +44,7 @@ export interface ResourceDiscoveryPageProps {
|
|
|
44
44
|
onSearchQueryChange: (query: string) => void;
|
|
45
45
|
|
|
46
46
|
// Controlled entity-type filter — owned by the state unit so filtering
|
|
47
|
-
// pushes to the
|
|
47
|
+
// pushes to the gateway rather than running as a post-fetch array filter.
|
|
48
48
|
selectedEntityType: string;
|
|
49
49
|
onSelectedEntityTypeChange: (entityType: string) => void;
|
|
50
50
|
|
|
@@ -106,7 +106,7 @@ export function ResourceDiscoveryPage({
|
|
|
106
106
|
const hasSearchQuery = searchQuery.trim() !== '';
|
|
107
107
|
|
|
108
108
|
// When searching, render search results; otherwise render recent.
|
|
109
|
-
// Both already arrive entity-type-filtered from the
|
|
109
|
+
// Both already arrive entity-type-filtered from the gateway — no post-filter here.
|
|
110
110
|
const filteredResources = hasSearchQuery ? searchDocuments : recentDocuments;
|
|
111
111
|
|
|
112
112
|
// Roving tabindex for entity type filters
|
|
@@ -50,7 +50,7 @@ export function createDiscoverStateUnit(
|
|
|
50
50
|
|
|
51
51
|
// Selected entity-type chip on the Discover page. Drives both the
|
|
52
52
|
// `recent` list and the search results — filtering happens on the
|
|
53
|
-
//
|
|
53
|
+
// gateway, not via post-fetch array filtering.
|
|
54
54
|
const selectedEntityType$ = new BehaviorSubject<string>('');
|
|
55
55
|
disposer.add(() => selectedEntityType$.complete());
|
|
56
56
|
|