@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
|
@@ -173,6 +173,41 @@
|
|
|
173
173
|
font-variant-numeric: tabular-nums;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
/* D8: the finished run's artifact, offered as a link. */
|
|
177
|
+
/* The finished run's artifact — the ended frame's primary affordance
|
|
178
|
+
(GENERATION-ARRIVAL P1, D3's fallback): a button-shaped invitation to open
|
|
179
|
+
what was made, not an underlined footnote beside the bar. */
|
|
180
|
+
.semiont-assist-progress__outcome {
|
|
181
|
+
align-self: stretch;
|
|
182
|
+
padding: 0.5rem 0.75rem;
|
|
183
|
+
border: 1px solid var(--semiont-color-primary-300);
|
|
184
|
+
border-radius: 0.5rem;
|
|
185
|
+
background: var(--semiont-color-primary-50);
|
|
186
|
+
cursor: pointer;
|
|
187
|
+
font-size: var(--semiont-text-sm);
|
|
188
|
+
font-weight: var(--semiont-font-medium);
|
|
189
|
+
color: var(--semiont-color-primary-700);
|
|
190
|
+
text-align: left;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.semiont-assist-progress__outcome:hover {
|
|
194
|
+
border-color: var(--semiont-color-primary-400);
|
|
195
|
+
background: var(--semiont-color-primary-100);
|
|
196
|
+
color: var(--semiont-color-primary-800);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
[data-theme="dark"] .semiont-assist-progress__outcome {
|
|
200
|
+
border-color: var(--semiont-color-primary-700);
|
|
201
|
+
background: var(--semiont-color-primary-900);
|
|
202
|
+
color: var(--semiont-color-primary-300);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
[data-theme="dark"] .semiont-assist-progress__outcome:hover {
|
|
206
|
+
border-color: var(--semiont-color-primary-600);
|
|
207
|
+
background: var(--semiont-color-primary-800);
|
|
208
|
+
color: var(--semiont-color-primary-200);
|
|
209
|
+
}
|
|
210
|
+
|
|
176
211
|
.semiont-assist-progress__params {
|
|
177
212
|
display: flex;
|
|
178
213
|
flex-wrap: wrap;
|
|
@@ -11,7 +11,7 @@ import type { SemiontSession } from '@semiont/sdk';
|
|
|
11
11
|
import { renderAgentLabel } from './agent-label';
|
|
12
12
|
import { useHoverEmitter } from '../../../hooks/useHoverEmitter';
|
|
13
13
|
|
|
14
|
-
// Extended annotation type with runtime properties added by
|
|
14
|
+
// Extended annotation type with runtime properties added by gateway enrichment
|
|
15
15
|
interface EnrichedAnnotation extends Annotation {
|
|
16
16
|
_resolvedDocumentName?: string;
|
|
17
17
|
_resolvedDocumentMediaType?: string;
|
|
@@ -27,6 +27,8 @@ interface ReferenceEntryProps {
|
|
|
27
27
|
onOpenResource?: (resourceId: string) => void;
|
|
28
28
|
annotateMode?: boolean;
|
|
29
29
|
isGenerating?: boolean;
|
|
30
|
+
/** The reference just got attention-worthy (created or resolved) — glow the icon (RESOLUTION-SPARKLE D6). */
|
|
31
|
+
sparkle?: boolean;
|
|
30
32
|
ref?: Ref<HTMLDivElement>;
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -38,6 +40,7 @@ export function ReferenceEntry({
|
|
|
38
40
|
onOpenResource,
|
|
39
41
|
annotateMode = true,
|
|
40
42
|
isGenerating = false,
|
|
43
|
+
sparkle = false,
|
|
41
44
|
ref,
|
|
42
45
|
}: ReferenceEntryProps) {
|
|
43
46
|
const t = useTranslations('ReferencesPanel');
|
|
@@ -55,7 +58,7 @@ export function ReferenceEntry({
|
|
|
55
58
|
const hasSvgSelector = getSvgSelector(selector);
|
|
56
59
|
const annotationType = hasFragmentSelector ? 'Fragment annotation' : hasSvgSelector ? 'Image annotation' : 'Annotation';
|
|
57
60
|
|
|
58
|
-
// Extract resolved document name and media type if enriched by
|
|
61
|
+
// Extract resolved document name and media type if enriched by gateway
|
|
59
62
|
const enrichedReference = reference as EnrichedAnnotation;
|
|
60
63
|
const resolvedDocumentName = enrichedReference._resolvedDocumentName;
|
|
61
64
|
const resolvedDocumentMediaType = enrichedReference._resolvedDocumentMediaType;
|
|
@@ -125,7 +128,7 @@ export function ReferenceEntry({
|
|
|
125
128
|
<div className="semiont-annotation-entry__header">
|
|
126
129
|
<div className="semiont-reference-icon-group">
|
|
127
130
|
<button
|
|
128
|
-
className={`semiont-reference-icon${iconIsClickable ? ' semiont-reference-icon--clickable' : ''}`}
|
|
131
|
+
className={`semiont-reference-icon${iconIsClickable ? ' semiont-reference-icon--clickable' : ''}${sparkle ? ' annotation-sparkle' : ''}`}
|
|
129
132
|
title={isResolved ? t('open') : annotateMode ? t('resolve') : t('stub')}
|
|
130
133
|
onClick={iconIsClickable ? handleIconClick : undefined}
|
|
131
134
|
data-generating={!isResolved && isGenerating ? 'true' : 'false'}
|
|
@@ -151,7 +154,11 @@ export function ReferenceEntry({
|
|
|
151
154
|
)}
|
|
152
155
|
{!selectedText && (
|
|
153
156
|
<div className="semiont-annotation-entry__meta">
|
|
154
|
-
{
|
|
157
|
+
{/* A resource-level annotation with a resolved, named target is
|
|
158
|
+
a derivation (the shape generation mints — the vocabulary
|
|
159
|
+
deliberately has no 'deriving' purpose): qualify the link
|
|
160
|
+
line below instead of showing the generic type label. */}
|
|
161
|
+
{!selector && resolvedDocumentName ? t('derived') : annotationType}
|
|
155
162
|
</div>
|
|
156
163
|
)}
|
|
157
164
|
{resolvedDocumentName && (
|
|
@@ -83,6 +83,8 @@ interface Props {
|
|
|
83
83
|
scrollToAnnotationId?: string | null;
|
|
84
84
|
onScrollCompleted?: () => void;
|
|
85
85
|
hoveredAnnotationId?: string | null;
|
|
86
|
+
/** Annotations currently sparkling (recently created or resolved) — entries in the set glow their icon (RESOLUTION-SPARKLE D6). */
|
|
87
|
+
sparkleAnnotationIds?: Set<string>;
|
|
86
88
|
|
|
87
89
|
/** User UI locale — stamped on the unresolved-reference body's `language` field. */
|
|
88
90
|
locale?: string;
|
|
@@ -119,6 +121,7 @@ export function ReferencesPanel({
|
|
|
119
121
|
scrollToAnnotationId,
|
|
120
122
|
onScrollCompleted,
|
|
121
123
|
hoveredAnnotationId,
|
|
124
|
+
sparkleAnnotationIds,
|
|
122
125
|
locale,
|
|
123
126
|
sourceLanguage,
|
|
124
127
|
}: Props) {
|
|
@@ -276,7 +279,11 @@ export function ReferencesPanel({
|
|
|
276
279
|
source: resourceId,
|
|
277
280
|
motivation: 'linking',
|
|
278
281
|
selector: pendingAnnotation.selector,
|
|
279
|
-
|
|
282
|
+
// Entity types are optional, and MarkSubmitEvent.body is minItems:1 —
|
|
283
|
+
// an empty array is a 400, not an empty annotation.
|
|
284
|
+
...(entityType
|
|
285
|
+
? { body: [{ type: 'TextualBody' as const, value: entityType, purpose: 'tagging' as const }] }
|
|
286
|
+
: {}),
|
|
280
287
|
});
|
|
281
288
|
setPendingEntityTypes([]);
|
|
282
289
|
}
|
|
@@ -483,6 +490,7 @@ export function ReferencesPanel({
|
|
|
483
490
|
onOpenResource={onOpenResource}
|
|
484
491
|
annotateMode={annotateMode}
|
|
485
492
|
isGenerating={reference.id === generatingReferenceId}
|
|
493
|
+
sparkle={sparkleAnnotationIds?.has(reference.id) ?? false}
|
|
486
494
|
ref={(el) => setEntryRef(reference.id, el)}
|
|
487
495
|
/>
|
|
488
496
|
))
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useTranslations } from '../../../contexts/TranslationContext';
|
|
4
|
-
import type
|
|
4
|
+
import { readyValue, type SemiontSession, type YieldOutcome } from '@semiont/sdk';
|
|
5
5
|
import { formatLocaleDisplay } from '@semiont/core';
|
|
6
6
|
import { resourceId as makeResourceId, type components } from '@semiont/core';
|
|
7
|
+
import { useObservable } from '../../../hooks/useObservable';
|
|
7
8
|
import { renderAgentLabel } from './agent-label';
|
|
9
|
+
import { AssistShell } from './AssistShell';
|
|
10
|
+
import { assistProgressTranslations } from '../../../lib/assist-progress-copy';
|
|
8
11
|
import './ResourceInfoPanel.css';
|
|
9
12
|
|
|
10
13
|
type Agent = components['schemas']['Agent'];
|
|
14
|
+
type JobProgress = components['schemas']['JobProgress'];
|
|
11
15
|
|
|
12
16
|
interface Props {
|
|
13
17
|
/** Session carrying the client and event bus; null renders inert. */
|
|
@@ -30,6 +34,17 @@ interface Props {
|
|
|
30
34
|
* to a verb). Omit to hide the Generate action.
|
|
31
35
|
*/
|
|
32
36
|
onGenerate?: () => void;
|
|
37
|
+
/**
|
|
38
|
+
* Generation lifecycle, page-held (`yield.isGenerating$` / `yield.progress$`
|
|
39
|
+
* / `yield.outcome$`): this panel is generation's progress surface
|
|
40
|
+
* (GENERATE-FROM-RESOURCE D7) — the run reports where it was started.
|
|
41
|
+
*/
|
|
42
|
+
isGenerating?: boolean;
|
|
43
|
+
generationProgress?: JobProgress | null;
|
|
44
|
+
/** The finished run's result; the ended frame links it by name (D8). */
|
|
45
|
+
generationOutcome?: YieldOutcome | null;
|
|
46
|
+
/** Clear the finished display — wires `yield.dismissProgress()`. */
|
|
47
|
+
onDismissProgress?: () => void;
|
|
33
48
|
}
|
|
34
49
|
|
|
35
50
|
/**
|
|
@@ -54,8 +69,13 @@ export function ResourceInfoPanel({
|
|
|
54
69
|
wasDerivedFrom,
|
|
55
70
|
generator,
|
|
56
71
|
onGenerate,
|
|
72
|
+
isGenerating = false,
|
|
73
|
+
generationProgress = null,
|
|
74
|
+
generationOutcome = null,
|
|
75
|
+
onDismissProgress,
|
|
57
76
|
}: Props) {
|
|
58
77
|
const t = useTranslations('ResourceInfoPanel');
|
|
78
|
+
const ta = useTranslations('AssistProgress');
|
|
59
79
|
|
|
60
80
|
// Single attribution surface. `wasAttributedTo` is the canonical list
|
|
61
81
|
// of responsible parties; if a producer set only `generator` we
|
|
@@ -152,13 +172,7 @@ export function ResourceInfoPanel({
|
|
|
152
172
|
<span className="semiont-resource-info-panel__label">{t('derivedFrom')}</span>
|
|
153
173
|
<span className="semiont-resource-info-panel__value">
|
|
154
174
|
{(Array.isArray(wasDerivedFrom) ? wasDerivedFrom : [wasDerivedFrom]).map((id, i) => (
|
|
155
|
-
<
|
|
156
|
-
key={id}
|
|
157
|
-
className="semiont-resource-info-panel__link"
|
|
158
|
-
onClick={() => session?.client.browse.openResource(makeResourceId(id))}
|
|
159
|
-
>
|
|
160
|
-
{i > 0 && ', '}{id}
|
|
161
|
-
</button>
|
|
175
|
+
<DerivedFromLink key={id} session={session} id={id} first={i === 0} />
|
|
162
176
|
))}
|
|
163
177
|
</span>
|
|
164
178
|
</div>
|
|
@@ -185,19 +199,47 @@ export function ResourceInfoPanel({
|
|
|
185
199
|
</div>
|
|
186
200
|
)}
|
|
187
201
|
|
|
188
|
-
{/* Generate
|
|
202
|
+
{/* Generate — the run reports where it was started (GENERATE-FROM-RESOURCE
|
|
203
|
+
D7): the same AssistShell every assist uses, its form the Generate
|
|
204
|
+
control, the progress frame in its place while a generation runs. */}
|
|
189
205
|
{onGenerate && (
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
{
|
|
199
|
-
|
|
200
|
-
|
|
206
|
+
<AssistShell
|
|
207
|
+
assistType="generation"
|
|
208
|
+
title={t('generate')}
|
|
209
|
+
isAssisting={isGenerating}
|
|
210
|
+
progress={generationProgress}
|
|
211
|
+
progressProps={{
|
|
212
|
+
onCancel: () => session?.client.job.cancelRequest('generation'),
|
|
213
|
+
...(onDismissProgress ? { onDismiss: onDismissProgress } : {}),
|
|
214
|
+
...(generationOutcome ? {
|
|
215
|
+
outcome: {
|
|
216
|
+
label: generationOutcome.resourceName,
|
|
217
|
+
onOpen: () => session?.client.browse.openResource(generationOutcome.resourceId),
|
|
218
|
+
},
|
|
219
|
+
// The terminal sentence derives from the OUTCOME, not the final
|
|
220
|
+
// progress frame — whose fire-and-forget emit can lose the race
|
|
221
|
+
// with job:complete and leave "creating…" beside a ✅
|
|
222
|
+
// (GENERATION-ARRIVAL D4/D5).
|
|
223
|
+
endedMessage: generationOutcome.truncated
|
|
224
|
+
? ta('codeCompleteGeneratedTruncated')
|
|
225
|
+
: ta('codeCompleteGenerated'),
|
|
226
|
+
} : {}),
|
|
227
|
+
translations: assistProgressTranslations(ta),
|
|
228
|
+
}}
|
|
229
|
+
form={
|
|
230
|
+
<>
|
|
231
|
+
<button
|
|
232
|
+
onClick={onGenerate}
|
|
233
|
+
className="semiont-resource-button semiont-resource-button--secondary"
|
|
234
|
+
>
|
|
235
|
+
✨ {t('generate')}
|
|
236
|
+
</button>
|
|
237
|
+
<p className="semiont-resource-info-panel__description">
|
|
238
|
+
{t('generateDescription')}
|
|
239
|
+
</p>
|
|
240
|
+
</>
|
|
241
|
+
}
|
|
242
|
+
/>
|
|
201
243
|
)}
|
|
202
244
|
|
|
203
245
|
{/* Clone Action */}
|
|
@@ -244,3 +286,30 @@ export function ResourceInfoPanel({
|
|
|
244
286
|
</div>
|
|
245
287
|
);
|
|
246
288
|
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* One derived-from entry: linked by id (wire identity), labeled by the source
|
|
292
|
+
* resource's NAME once `browse.resource` serves its descriptor. The raw id is
|
|
293
|
+
* the honest label until then — and stays if the descriptor never resolves
|
|
294
|
+
* (deleted source, no session). Own component because the id list maps in a
|
|
295
|
+
* loop and the subscription is a hook.
|
|
296
|
+
*/
|
|
297
|
+
function DerivedFromLink({ session, id, first }: {
|
|
298
|
+
session: SemiontSession | null;
|
|
299
|
+
id: string;
|
|
300
|
+
first: boolean;
|
|
301
|
+
}) {
|
|
302
|
+
// `browse.resource` is stable per id (cache + scope wrappers memoize), so
|
|
303
|
+
// subscribing straight off the render read is safe; `failed` is an emission,
|
|
304
|
+
// never a stream error, so `readyValue` covers every unresolved state.
|
|
305
|
+
const descriptor = useObservable(session ? session.client.browse.resource(makeResourceId(id)) : null);
|
|
306
|
+
const name = descriptor ? readyValue(descriptor)?.name : undefined;
|
|
307
|
+
return (
|
|
308
|
+
<button
|
|
309
|
+
className="semiont-resource-info-panel__link"
|
|
310
|
+
onClick={() => session?.client.browse.openResource(makeResourceId(id))}
|
|
311
|
+
>
|
|
312
|
+
{!first && ', '}{name ?? id}
|
|
313
|
+
</button>
|
|
314
|
+
);
|
|
315
|
+
}
|
|
@@ -6,7 +6,7 @@ import type { components, Selector } from '@semiont/core';
|
|
|
6
6
|
type JobProgress = components['schemas']['JobProgress'];
|
|
7
7
|
import type { RouteBuilder, LinkComponentProps } from '../../../contexts/RoutingContext';
|
|
8
8
|
import type { SemiontSession } from '@semiont/sdk';
|
|
9
|
-
import { annotatorKeyForMotivation, type Annotator } from '../../../lib/annotation-registry';
|
|
9
|
+
import { annotatorKeyForMotivation, type Annotator, type AnnotatorKey } from '../../../lib/annotation-registry';
|
|
10
10
|
import { StatisticsPanel } from './StatisticsPanel';
|
|
11
11
|
import { HighlightPanel } from './HighlightPanel';
|
|
12
12
|
import { ReferencesPanel } from './ReferencesPanel';
|
|
@@ -17,7 +17,9 @@ import './UnifiedAnnotationsPanel.css';
|
|
|
17
17
|
|
|
18
18
|
import type { Annotation } from '@semiont/core';
|
|
19
19
|
type Motivation = components['schemas']['Motivation'];
|
|
20
|
-
|
|
20
|
+
// Derived from the annotator registry (the single motivation↔annotator
|
|
21
|
+
// source) — a hand-written union here could drift from it.
|
|
22
|
+
type TabKey = 'statistics' | AnnotatorKey;
|
|
21
23
|
|
|
22
24
|
// Unified pending annotation type
|
|
23
25
|
interface PendingAnnotation {
|
|
@@ -76,6 +78,8 @@ interface UnifiedAnnotationsPanelProps {
|
|
|
76
78
|
entityTypesError?: Error | null;
|
|
77
79
|
referencedByError?: Error | null;
|
|
78
80
|
onRetryReferencedBy?: () => void;
|
|
81
|
+
/** Annotations currently sparkling (recently created or resolved) — RESOLUTION-SPARKLE D6. */
|
|
82
|
+
sparkleAnnotationIds?: Set<string>;
|
|
79
83
|
|
|
80
84
|
// Resource context — threaded to every per-motivation panel, which stamps it
|
|
81
85
|
// as `source` on mark:submit (multi-viewer routing).
|
|
@@ -328,6 +332,7 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
328
332
|
referencedByLoading={props.referencedByLoading}
|
|
329
333
|
referencedByError={props.referencedByError}
|
|
330
334
|
onRetryReferencedBy={props.onRetryReferencedBy}
|
|
335
|
+
sparkleAnnotationIds={props.sparkleAnnotationIds}
|
|
331
336
|
Link={props.Link}
|
|
332
337
|
routes={props.routes}
|
|
333
338
|
/>
|
|
@@ -94,6 +94,24 @@ describe('ReferenceEntry', () => {
|
|
|
94
94
|
mockGetEntityTypes.mockReturnValue([]);
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
+
// RESOLUTION-SPARKLE D6: a just-resolved reference announces itself on the
|
|
98
|
+
// panel entry's icon with the same `.annotation-sparkle` glow the document
|
|
99
|
+
// span uses. The host decides WHEN (membership in the sparkle set); the
|
|
100
|
+
// entry only renders the boolean.
|
|
101
|
+
describe('Resolution sparkle', () => {
|
|
102
|
+
it('carries the sparkle animation on the icon when sparkle is set', () => {
|
|
103
|
+
const { container } = renderEntry({ sparkle: true });
|
|
104
|
+
|
|
105
|
+
expect(container.querySelector('.semiont-reference-icon')).toHaveClass('annotation-sparkle');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('does not sparkle by default', () => {
|
|
109
|
+
const { container } = renderEntry();
|
|
110
|
+
|
|
111
|
+
expect(container.querySelector('.semiont-reference-icon')).not.toHaveClass('annotation-sparkle');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
97
115
|
describe('Rendering', () => {
|
|
98
116
|
it('should render the selected text in quotes', () => {
|
|
99
117
|
renderEntry();
|
|
@@ -172,6 +190,29 @@ describe('ReferenceEntry', () => {
|
|
|
172
190
|
|
|
173
191
|
expect(screen.getByText(/My Linked Document/)).toBeInTheDocument();
|
|
174
192
|
});
|
|
193
|
+
|
|
194
|
+
it('a resource-level derivation headlines Derived, never the generic type label', () => {
|
|
195
|
+
// The provenance edge minted by resource-focus generation: no selector
|
|
196
|
+
// (nothing to quote), resolved body naming the derived resource. The
|
|
197
|
+
// generic "Annotation" label said nothing — the qualifier says what
|
|
198
|
+
// the entry IS; the link line below says what it points at. Inference
|
|
199
|
+
// is from shape (the wire vocabulary deliberately has no 'deriving'
|
|
200
|
+
// purpose); the unresolved case keeps the plain type label.
|
|
201
|
+
mockGetAnnotationExactText.mockReturnValue('');
|
|
202
|
+
mockIsBodyResolved.mockReturnValue(true);
|
|
203
|
+
mockGetBodySource.mockReturnValue('gen-doc');
|
|
204
|
+
|
|
205
|
+
const provenanceRef = {
|
|
206
|
+
...createMockReference({ target: { source: 'resource-1' } }),
|
|
207
|
+
_resolvedDocumentName: 'generated from Cedar County, Iowa',
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
renderEntry({ reference: provenanceRef as Annotation });
|
|
211
|
+
|
|
212
|
+
expect(screen.getByText('ReferencesPanel.derived')).toBeInTheDocument();
|
|
213
|
+
expect(screen.queryByText('Annotation')).not.toBeInTheDocument();
|
|
214
|
+
expect(screen.getByText(/generated from Cedar County, Iowa/)).toBeInTheDocument();
|
|
215
|
+
});
|
|
175
216
|
});
|
|
176
217
|
|
|
177
218
|
describe('Entity types', () => {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Written after an e2e failure (test 05) where `ReferencesPanel` rendered
|
|
5
5
|
* "No entity types available" even though the client provably received a
|
|
6
|
-
* 9-string entity-types array from the
|
|
6
|
+
* 9-string entity-types array from the gateway.
|
|
7
7
|
*
|
|
8
8
|
* This test closes the Layer 5-6 gap: the existing `ResourceViewerPage.test.tsx`
|
|
9
9
|
* stubs `UnifiedAnnotationsPanel`/`ReferencesPanel` as `<div data-testid>`s,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import { render, screen, waitFor, within } from '@testing-library/react';
|
|
4
4
|
import userEvent from '@testing-library/user-event';
|
|
5
5
|
import '@testing-library/jest-dom';
|
|
6
6
|
import { ReferencesPanel } from '../ReferencesPanel';
|
|
7
|
-
import type { EventBus } from '@semiont/core';
|
|
7
|
+
import type { Annotation, AnnotationId, EventBus } from '@semiont/core';
|
|
8
8
|
import type { SemiontSession } from '@semiont/sdk';
|
|
9
9
|
import { createTestSemiontWrapper } from '../../../../test-utils';
|
|
10
10
|
|
|
@@ -20,7 +20,7 @@ function createEventTracker() {
|
|
|
20
20
|
events,
|
|
21
21
|
clear: () => { events.length = 0; },
|
|
22
22
|
_attach(eventBus: EventBus) {
|
|
23
|
-
const panelEvents = ['mark:assist-request'] as const;
|
|
23
|
+
const panelEvents = ['mark:assist-request', 'mark:submit'] as const;
|
|
24
24
|
panelEvents.forEach((eventName) => {
|
|
25
25
|
eventBus.get(eventName).subscribe((payload: any) => {
|
|
26
26
|
events.push({ event: eventName, payload });
|
|
@@ -66,6 +66,7 @@ vi.mock('../../../../contexts/TranslationContext', () => ({
|
|
|
66
66
|
includeDescriptiveReferences: 'Include descriptive references',
|
|
67
67
|
descriptiveReferencesTooltip: 'Also find phrases like \'the CEO\', \'the tech giant\', \'the physicist\' (in addition to names)',
|
|
68
68
|
cancel: 'Cancel',
|
|
69
|
+
createReference: 'Create Reference',
|
|
69
70
|
annotating: 'Annotating...',
|
|
70
71
|
// CLEAN-PROGRESS D3: the widget's own strings come from the
|
|
71
72
|
// AssistProgress namespace now, not from this panel's.
|
|
@@ -118,6 +119,38 @@ describe('ReferencesPanel Component', () => {
|
|
|
118
119
|
vi.restoreAllMocks();
|
|
119
120
|
});
|
|
120
121
|
|
|
122
|
+
// RESOLUTION-SPARKLE D6: the panel threads the host's sparkle set down to
|
|
123
|
+
// the entries — exactly the ids in the set glow, nothing else.
|
|
124
|
+
describe('Resolution sparkle threading', () => {
|
|
125
|
+
const linkingReference = (id: string): Annotation => ({
|
|
126
|
+
'@context': 'http://www.w3.org/ns/anno.jsonld',
|
|
127
|
+
id: id as AnnotationId,
|
|
128
|
+
type: 'Annotation',
|
|
129
|
+
motivation: 'linking',
|
|
130
|
+
created: '2026-08-21T12:00:00Z',
|
|
131
|
+
modified: '2026-08-21T12:00:00Z',
|
|
132
|
+
target: {
|
|
133
|
+
source: 'res-1',
|
|
134
|
+
selector: { type: 'TextQuoteSelector', exact: `text of ${id}` },
|
|
135
|
+
},
|
|
136
|
+
body: { type: 'SpecificResource', source: 'linked-doc', purpose: 'linking' },
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('sparkles exactly the entries named by sparkleAnnotationIds', () => {
|
|
140
|
+
const { container } = renderWithEventBus(
|
|
141
|
+
<ReferencesPanel
|
|
142
|
+
{...panelProps()}
|
|
143
|
+
annotations={[linkingReference('ann-dark'), linkingReference('ann-lit')]}
|
|
144
|
+
sparkleAnnotationIds={new Set(['ann-lit'])}
|
|
145
|
+
/>,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const sparkling = container.querySelectorAll('.semiont-reference-icon.annotation-sparkle');
|
|
149
|
+
expect(sparkling).toHaveLength(1);
|
|
150
|
+
expect(sparkling[0]!.closest('.semiont-annotation-entry')).toHaveTextContent('text of ann-lit');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
121
154
|
describe('Rendering', () => {
|
|
122
155
|
it('should render panel with title', () => {
|
|
123
156
|
renderWithEventBus(<ReferencesPanel {...panelProps()} />);
|
|
@@ -147,6 +180,54 @@ describe('ReferencesPanel Component', () => {
|
|
|
147
180
|
});
|
|
148
181
|
});
|
|
149
182
|
|
|
183
|
+
// Entity types are optional on a reference, but `MarkSubmitEvent.body` is
|
|
184
|
+
// `minItems: 1` — so "no types selected" must OMIT body, not send `[]`.
|
|
185
|
+
// Sending `[]` 400s at /bus/emit, and `mark.submit` is fire-and-forget, so
|
|
186
|
+
// the failure is silent: the button looks inert. Found live 2026-08-24.
|
|
187
|
+
describe('Create Reference payload', () => {
|
|
188
|
+
const pendingLinking = {
|
|
189
|
+
motivation: 'linking' as const,
|
|
190
|
+
selector: { type: 'TextQuoteSelector' as const, exact: 'thylakoid' },
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const submitPayloads = (tracker: ReturnType<typeof createEventTracker>) =>
|
|
194
|
+
tracker.events.filter(e => e.event === 'mark:submit').map(e => e.payload);
|
|
195
|
+
|
|
196
|
+
// The panel's own entity-type chips, not AssistSection's identically
|
|
197
|
+
// labelled ones.
|
|
198
|
+
const renderPrompt = (tracker: ReturnType<typeof createEventTracker>) => {
|
|
199
|
+
const { container } = renderWithEventBus(
|
|
200
|
+
<ReferencesPanel {...panelProps()} pendingAnnotation={pendingLinking} />,
|
|
201
|
+
tracker,
|
|
202
|
+
);
|
|
203
|
+
return within(container.querySelector('.semiont-annotation-prompt') as HTMLElement);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
it('omits body entirely when no entity type is selected', async () => {
|
|
207
|
+
const tracker = createEventTracker();
|
|
208
|
+
const prompt = renderPrompt(tracker);
|
|
209
|
+
|
|
210
|
+
await userEvent.click(prompt.getByRole('button', { name: /create reference/i }));
|
|
211
|
+
|
|
212
|
+
const [payload] = submitPayloads(tracker);
|
|
213
|
+
expect(payload).toBeDefined();
|
|
214
|
+
expect(payload).not.toHaveProperty('body');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('sends a one-item body when an entity type is selected', async () => {
|
|
218
|
+
const tracker = createEventTracker();
|
|
219
|
+
const prompt = renderPrompt(tracker);
|
|
220
|
+
|
|
221
|
+
await userEvent.click(prompt.getByRole('button', { name: 'Person' }));
|
|
222
|
+
await userEvent.click(prompt.getByRole('button', { name: /create reference/i }));
|
|
223
|
+
|
|
224
|
+
const [payload] = submitPayloads(tracker);
|
|
225
|
+
expect(payload.body).toEqual([
|
|
226
|
+
{ type: 'TextualBody', value: 'Person', purpose: 'tagging' },
|
|
227
|
+
]);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
150
231
|
describe('Entity Type Selection', () => {
|
|
151
232
|
it('should toggle entity type selection on click', async () => {
|
|
152
233
|
renderWithEventBus(<ReferencesPanel {...panelProps()} />);
|