@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
|
@@ -12,25 +12,60 @@ import { GatherContextStep } from '../GatherContextStep';
|
|
|
12
12
|
import { ContextSummary } from '../ContextSummary';
|
|
13
13
|
|
|
14
14
|
const t = {
|
|
15
|
-
title: 'Gather',
|
|
16
15
|
loadingContext: 'Loading…',
|
|
17
16
|
failedContext: 'Failed',
|
|
18
|
-
search: 'Search',
|
|
19
|
-
generate: 'Generate',
|
|
20
|
-
compose: 'Compose',
|
|
21
|
-
resolutionStrategyLabel: 'Strategy',
|
|
22
17
|
sourceContextLabel: 'Source',
|
|
23
18
|
connectionsLabel: 'Connections',
|
|
24
19
|
citedByLabel: 'Cited by',
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
graphPaneTitle: 'In the graph',
|
|
21
|
+
graphEmpty: 'No links yet — resolving this reference creates the first.',
|
|
22
|
+
resourceLinkLabel: 'Resource link',
|
|
23
|
+
corpusPaneTitle: 'In the corpus',
|
|
24
|
+
corpusEmpty: 'Nothing similar in the corpus.',
|
|
25
|
+
excludedReceipt: '{{types}} excluded from this recall',
|
|
26
|
+
machineRead: 'OCR',
|
|
27
|
+
score: 'Score',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Post-P3 wire shape: resourceName required, entityTypes/machineRead optional. */
|
|
31
|
+
const MATCHES = [
|
|
32
|
+
{ text: 'a middling passage', resourceId: 'r-mid', resourceName: 'Middling Doc', score: 0.55 },
|
|
33
|
+
{ text: 'the best passage', resourceId: 'r-best', resourceName: 'Best Doc', score: 0.91,
|
|
34
|
+
entityTypes: ['Battle'], machineRead: true },
|
|
35
|
+
{ text: 'a weak passage', resourceId: 'r-weak', resourceName: 'Weak Doc', score: 0.12 },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
function withSemantic(ctx: GatheredContext, over: Record<string, unknown> = {}): GatheredContext {
|
|
39
|
+
return {
|
|
40
|
+
...(ctx as object),
|
|
41
|
+
semanticContext: { similar: MATCHES, excludedEntityTypes: ['Question'], ...over },
|
|
42
|
+
inferredRelationshipSummary: 'Cedar County sits inside the Black Hawk Purchase.',
|
|
43
|
+
} as unknown as GatheredContext;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The annotation-wizard controls travel as ONE optional group (GFR D2). */
|
|
47
|
+
const annotate = {
|
|
48
|
+
userHint: '',
|
|
49
|
+
onUserHintChange: () => {},
|
|
50
|
+
onBind: () => {},
|
|
51
|
+
onGenerate: () => {},
|
|
52
|
+
onCompose: () => {},
|
|
53
|
+
translations: {
|
|
54
|
+
search: 'Search',
|
|
55
|
+
generate: 'Generate',
|
|
56
|
+
compose: 'Compose',
|
|
57
|
+
resolutionStrategyLabel: 'Strategy',
|
|
58
|
+
userHintLabel: 'Hint',
|
|
59
|
+
userHintEffect: 'steers Search and Generate',
|
|
60
|
+
userHintPlaceholder: 'hint…',
|
|
61
|
+
},
|
|
27
62
|
};
|
|
28
63
|
|
|
29
64
|
function resourceContext(): GatheredContext {
|
|
30
65
|
return {
|
|
31
66
|
focus: {
|
|
32
67
|
kind: 'resource',
|
|
33
|
-
resource: { id: 'res-1', name: 'My Resource' },
|
|
68
|
+
resource: { '@id': 'res-1', name: 'My Resource' },
|
|
34
69
|
summary: 'A short summary',
|
|
35
70
|
suggestedReferences: ['Suggested Topic'],
|
|
36
71
|
content: { main: 'main content' },
|
|
@@ -58,6 +93,17 @@ describe('GatheredContext display — resource focus', () => {
|
|
|
58
93
|
expect(container.textContent).toContain('Related Resource'); // peer connection from deriveViews
|
|
59
94
|
});
|
|
60
95
|
|
|
96
|
+
it('loading without a pending preview stays the plain dots — the resource-gather path', () => {
|
|
97
|
+
// The composite Generate modal has its controls visible above the loading
|
|
98
|
+
// zone; only the annotation wizard passes `pending` (its known facts).
|
|
99
|
+
const { queryByText, getByText } = render(
|
|
100
|
+
<GatherContextStep context={null} contextLoading contextError={null} translations={t} />,
|
|
101
|
+
);
|
|
102
|
+
expect(getByText(t.loadingContext)).toBeInTheDocument();
|
|
103
|
+
expect(queryByText(t.graphPaneTitle)).toBeNull();
|
|
104
|
+
expect(queryByText(t.corpusPaneTitle)).toBeNull();
|
|
105
|
+
});
|
|
106
|
+
|
|
61
107
|
it('GatherContextStep shows the resource strip and hides the annotation-only footer', () => {
|
|
62
108
|
const { container } = render(
|
|
63
109
|
<GatherContextStep
|
|
@@ -69,7 +115,7 @@ describe('GatheredContext display — resource focus', () => {
|
|
|
69
115
|
);
|
|
70
116
|
expect(container.textContent).toContain('My Resource'); // focal resource name
|
|
71
117
|
expect(container.textContent).toContain('A short summary'); // resource summary
|
|
72
|
-
expect(container.textContent).toContain('Suggested Topic'); // suggestedReferences
|
|
118
|
+
expect(container.textContent).toContain('Suggested Topic'); // suggestedReferences entry
|
|
73
119
|
expect(container.textContent).toContain('Related Resource'); // graph view via ContextSummary
|
|
74
120
|
// annotation-only controls are gated out for a resource focus
|
|
75
121
|
expect(container.textContent).not.toContain('Strategy');
|
|
@@ -78,30 +124,422 @@ describe('GatheredContext display — resource focus', () => {
|
|
|
78
124
|
});
|
|
79
125
|
|
|
80
126
|
it('GatherContextStep still shows the footer for an annotation focus', () => {
|
|
81
|
-
const annotationContext = {
|
|
82
|
-
focus: {
|
|
83
|
-
kind: 'annotation',
|
|
84
|
-
annotation: { id: 'anno-1', motivation: 'linking' },
|
|
85
|
-
sourceResource: { id: 'res-1', name: 'Host' },
|
|
86
|
-
selected: { before: 'a ', text: 'term', after: ' b' },
|
|
87
|
-
},
|
|
88
|
-
graph: { nodes: [], edges: [] },
|
|
89
|
-
metadata: {},
|
|
90
|
-
} as unknown as GatheredContext;
|
|
91
127
|
const { container } = render(
|
|
92
128
|
<GatherContextStep
|
|
93
|
-
context={annotationContext}
|
|
129
|
+
context={annotationContext()}
|
|
94
130
|
contextLoading={false}
|
|
95
131
|
contextError={null}
|
|
96
|
-
|
|
97
|
-
onUserHintChange={() => {}}
|
|
98
|
-
onBind={() => {}}
|
|
99
|
-
onGenerate={() => {}}
|
|
100
|
-
onCompose={() => {}}
|
|
132
|
+
annotate={annotate}
|
|
101
133
|
translations={t}
|
|
102
134
|
/>,
|
|
103
135
|
);
|
|
104
136
|
expect(container.querySelector('.semiont-gather__footer')).not.toBeNull();
|
|
105
137
|
expect(container.textContent).toContain('Strategy');
|
|
106
138
|
});
|
|
139
|
+
|
|
140
|
+
it('the quotation is contiguous prose — nothing interleaved, no monospace (GEP P1a, D6/D7)', () => {
|
|
141
|
+
// The live bug: chips injected between `text` and `after` rendered
|
|
142
|
+
// "Black Hawk [Person] [linking]'s band." — metadata interrupting the
|
|
143
|
+
// quotation it describes, mid-possessive.
|
|
144
|
+
const { container } = render(
|
|
145
|
+
<GatherContextStep
|
|
146
|
+
context={annotationContextWithTypes()}
|
|
147
|
+
contextLoading={false}
|
|
148
|
+
contextError={null}
|
|
149
|
+
annotate={annotate}
|
|
150
|
+
translations={t}
|
|
151
|
+
/>,
|
|
152
|
+
);
|
|
153
|
+
const quote = container.querySelector('.semiont-gather__source-context');
|
|
154
|
+
expect(quote).not.toBeNull();
|
|
155
|
+
expect(quote!.textContent).toBe('a term b'); // before + text + after, verbatim
|
|
156
|
+
// D7: quoted document prose, not code.
|
|
157
|
+
expect(quote!.querySelector('[style*="monospace"]')).toBeNull();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('metadata sits on the label row; the span WEARS its motivation (GEP P1b, D6)', () => {
|
|
161
|
+
const { container } = render(
|
|
162
|
+
<GatherContextStep
|
|
163
|
+
context={annotationContextWithTypes()}
|
|
164
|
+
contextLoading={false}
|
|
165
|
+
contextError={null}
|
|
166
|
+
annotate={annotate}
|
|
167
|
+
translations={t}
|
|
168
|
+
/>,
|
|
169
|
+
);
|
|
170
|
+
const strip = container.querySelector('.semiont-gather__source-strip')!;
|
|
171
|
+
|
|
172
|
+
// Entity chips are tokens — they live on the label row, outside the quotation.
|
|
173
|
+
const chips = Array.from(strip.querySelectorAll('.semiont-chip'));
|
|
174
|
+
const topicChip = chips.find((c) => c.textContent === 'Topic');
|
|
175
|
+
expect(topicChip).toBeDefined();
|
|
176
|
+
expect(topicChip!.closest('.semiont-gather__source-context')).toBeNull();
|
|
177
|
+
|
|
178
|
+
// Motivation is WORN, never labeled: no chip says 'linking' —
|
|
179
|
+
expect(chips.filter((c) => c.textContent === 'linking')).toEqual([]);
|
|
180
|
+
// — the selected span carries the viewer's own class for the motivation
|
|
181
|
+
// (the registry's applied name; the motivation stylesheets declare it as a
|
|
182
|
+
// synonym of `semiont-motivation-reference`), with the hand-rolled inline
|
|
183
|
+
// highlight gone.
|
|
184
|
+
const span = strip.querySelector('.annotation-reference') as HTMLElement | null;
|
|
185
|
+
expect(span).not.toBeNull();
|
|
186
|
+
expect(span!.textContent).toBe('term');
|
|
187
|
+
expect(span!.style.backgroundColor).toBe('');
|
|
188
|
+
|
|
189
|
+
// Exactly ONE motivation-classed element: the context can place only the
|
|
190
|
+
// focal annotation, and the strip must not show more (D6 boundary).
|
|
191
|
+
const motivated = strip.querySelectorAll(
|
|
192
|
+
'.annotation-reference, .annotation-highlight, .annotation-comment, .annotation-assessment, .annotation-tag',
|
|
193
|
+
);
|
|
194
|
+
expect(motivated).toHaveLength(1);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('suggestedReferences render as a prose list, never chips (GFR A3)', () => {
|
|
198
|
+
// The live values are full research prompts — sentences. The chip vocabulary
|
|
199
|
+
// stays for tokens (entity types, categories, counts); a pill that wraps
|
|
200
|
+
// across three lines is not a pill (GENERATE-FROM-RESOURCE D3).
|
|
201
|
+
const { container } = render(
|
|
202
|
+
<GatherContextStep
|
|
203
|
+
context={resourceContext()}
|
|
204
|
+
contextLoading={false}
|
|
205
|
+
contextError={null}
|
|
206
|
+
translations={t}
|
|
207
|
+
/>,
|
|
208
|
+
);
|
|
209
|
+
const item = Array.from(container.querySelectorAll('li'))
|
|
210
|
+
.find((el) => el.textContent === 'Suggested Topic');
|
|
211
|
+
expect(item).toBeDefined();
|
|
212
|
+
expect(item!.className).not.toContain('semiont-chip');
|
|
213
|
+
// The entity-type tokens beside the summary stay chips.
|
|
214
|
+
const chipTexts = Array.from(container.querySelectorAll('.semiont-chip')).map((el) => el.textContent);
|
|
215
|
+
expect(chipTexts).toContain('Topic');
|
|
216
|
+
expect(chipTexts).not.toContain('Suggested Topic');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// ── GEP P2 — the evidence panes ─────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
it('the graph pane shows its title and the inferred summary as prose header (P2)', () => {
|
|
222
|
+
const { container } = render(
|
|
223
|
+
<GatherContextStep
|
|
224
|
+
context={withSemantic(resourceContext())}
|
|
225
|
+
contextLoading={false} contextError={null} translations={t}
|
|
226
|
+
/>,
|
|
227
|
+
);
|
|
228
|
+
const pane = container.querySelector('.semiont-gather-pane--graph');
|
|
229
|
+
expect(pane).not.toBeNull();
|
|
230
|
+
expect(pane!.textContent).toContain('In the graph');
|
|
231
|
+
expect(pane!.textContent).toContain('Cedar County sits inside the Black Hawk Purchase.');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('the corpus pane ranks match cards by descending score, named and marked (P2, D4)', () => {
|
|
235
|
+
const { container } = render(
|
|
236
|
+
<GatherContextStep
|
|
237
|
+
context={withSemantic(resourceContext())}
|
|
238
|
+
contextLoading={false} contextError={null} translations={t}
|
|
239
|
+
/>,
|
|
240
|
+
);
|
|
241
|
+
const cards = Array.from(container.querySelectorAll('.semiont-corpus__card'));
|
|
242
|
+
expect(cards).toHaveLength(3);
|
|
243
|
+
// Descending by score, whatever order the wire delivered.
|
|
244
|
+
expect(cards.map((c) => c.querySelector('.semiont-corpus__source')!.textContent))
|
|
245
|
+
.toEqual(['Best Doc', 'Middling Doc', 'Weak Doc']);
|
|
246
|
+
// The card names its source (D9's required field), shows the snippet, and
|
|
247
|
+
// formats the 0–1 cosine to two places (logged deviation — the search step's
|
|
248
|
+
// raw render is a different scale).
|
|
249
|
+
expect(cards[0]!.textContent).toContain('the best passage');
|
|
250
|
+
expect(cards[0]!.textContent).toContain('0.91');
|
|
251
|
+
// Match-level entityTypes are tokens — chips on the card.
|
|
252
|
+
expect(cards[0]!.querySelector('.semiont-chip')!.textContent).toBe('Battle');
|
|
253
|
+
// machineRead is a trust marker that appears ONLY when the flag travels.
|
|
254
|
+
expect(cards[0]!.textContent).toContain('OCR');
|
|
255
|
+
expect(cards[1]!.textContent).not.toContain('OCR');
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('the exclusion receipt renders when present, and only then (P2, D4)', () => {
|
|
259
|
+
const { container, rerender } = render(
|
|
260
|
+
<GatherContextStep
|
|
261
|
+
context={withSemantic(resourceContext())}
|
|
262
|
+
contextLoading={false} contextError={null} translations={t}
|
|
263
|
+
/>,
|
|
264
|
+
);
|
|
265
|
+
expect(container.querySelector('.semiont-corpus__receipt')!.textContent)
|
|
266
|
+
.toContain('Question excluded from this recall');
|
|
267
|
+
|
|
268
|
+
rerender(
|
|
269
|
+
<GatherContextStep
|
|
270
|
+
context={withSemantic(resourceContext(), { excludedEntityTypes: [] })}
|
|
271
|
+
contextLoading={false} contextError={null} translations={t}
|
|
272
|
+
/>,
|
|
273
|
+
);
|
|
274
|
+
expect(container.querySelector('.semiont-corpus__receipt')).toBeNull();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('emptiness is evidence: each pane renders its empty-state copy (P2, D1)', () => {
|
|
278
|
+
// resourceContext() has no semanticContext and a graph whose views resolve
|
|
279
|
+
// to a connection — so build a truly empty context for both panes.
|
|
280
|
+
const empty = {
|
|
281
|
+
...(resourceContext() as object),
|
|
282
|
+
graph: { nodes: [], edges: [] },
|
|
283
|
+
} as unknown as GatheredContext;
|
|
284
|
+
const { container } = render(
|
|
285
|
+
<GatherContextStep context={empty} contextLoading={false} contextError={null} translations={t} />,
|
|
286
|
+
);
|
|
287
|
+
expect(container.querySelector('.semiont-gather-pane--graph')!.textContent)
|
|
288
|
+
.toContain('No links yet');
|
|
289
|
+
expect(container.querySelector('.semiont-gather-pane--corpus')!.textContent)
|
|
290
|
+
.toContain('Nothing similar in the corpus.');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('the hint sits OUTSIDE the pane grid, full width above the footer (P2, D5)', () => {
|
|
294
|
+
const { container } = render(
|
|
295
|
+
<GatherContextStep
|
|
296
|
+
context={annotationContext()}
|
|
297
|
+
contextLoading={false} contextError={null}
|
|
298
|
+
annotate={annotate} translations={t}
|
|
299
|
+
/>,
|
|
300
|
+
);
|
|
301
|
+
const textarea = container.querySelector('.semiont-gather__hint-textarea');
|
|
302
|
+
expect(textarea).not.toBeNull();
|
|
303
|
+
expect(textarea!.closest('.semiont-gather__body')).toBeNull(); // out of the grid
|
|
304
|
+
expect(container.querySelector('.semiont-gather__hint-row')).not.toBeNull();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// ── GEP P4 — the graph pane's body is an actual graph ───────────────────────
|
|
308
|
+
// Post-P3 topology: a citation is its linking ANNOTATION (annotation-of → the
|
|
309
|
+
// citing resource, cites → the focal one); siblings are annotations ON the
|
|
310
|
+
// focal resource. The viz draws the derived neighborhood: focal + peers +
|
|
311
|
+
// citers + siblings; the citation's intermediary annotation collapses into
|
|
312
|
+
// its edge.
|
|
313
|
+
|
|
314
|
+
function vizContext(): GatheredContext {
|
|
315
|
+
return {
|
|
316
|
+
...(resourceContext() as object),
|
|
317
|
+
graph: {
|
|
318
|
+
nodes: [
|
|
319
|
+
{ id: 'res-1', type: 'resource', label: 'My Resource' },
|
|
320
|
+
{ id: 'res-2', type: 'resource', label: 'Peer Doc', entityTypes: ['Topic'] },
|
|
321
|
+
{ id: 'res-3', type: 'resource', label: 'Citing Doc' },
|
|
322
|
+
{ id: 'ann-c', type: 'annotation', label: 'linking',
|
|
323
|
+
annotation: { id: 'ann-c', motivation: 'linking' } },
|
|
324
|
+
{ id: 'ann-s', type: 'annotation', label: 'commenting', entityTypes: ['Person'],
|
|
325
|
+
annotation: { id: 'ann-s', motivation: 'commenting',
|
|
326
|
+
target: { source: 'res-1', selector: [
|
|
327
|
+
{ type: 'TextPositionSelector', start: 5, end: 22 },
|
|
328
|
+
{ type: 'TextQuoteSelector', exact: 'the disputed span' },
|
|
329
|
+
] },
|
|
330
|
+
body: [
|
|
331
|
+
{ type: 'TextualBody', value: 'Person', purpose: 'tagging' },
|
|
332
|
+
{ type: 'TextualBody', value: 'a sibling note' },
|
|
333
|
+
] } },
|
|
334
|
+
],
|
|
335
|
+
edges: [
|
|
336
|
+
{ source: 'res-1', target: 'res-2', type: 'related', bidirectional: true },
|
|
337
|
+
{ source: 'ann-c', target: 'res-3', type: 'annotation-of' },
|
|
338
|
+
{ source: 'ann-c', target: 'res-1', type: 'cites' },
|
|
339
|
+
{ source: 'ann-s', target: 'res-1', type: 'annotation-of' },
|
|
340
|
+
],
|
|
341
|
+
},
|
|
342
|
+
} as unknown as GatheredContext;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function renderViz() {
|
|
346
|
+
return render(
|
|
347
|
+
<GatherContextStep
|
|
348
|
+
context={vizContext()} contextLoading={false} contextError={null} translations={t}
|
|
349
|
+
/>,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
it('draws the neighborhood as SVG — the interim lists are gone (P4, D3)', () => {
|
|
354
|
+
const { container } = renderViz();
|
|
355
|
+
const pane = container.querySelector('.semiont-gather-pane--graph')!;
|
|
356
|
+
expect(pane.querySelector('svg')).not.toBeNull();
|
|
357
|
+
// focal + peer + citer + sibling; the citing annotation collapses into its edge
|
|
358
|
+
expect(pane.querySelectorAll('.semiont-graph__node')).toHaveLength(4);
|
|
359
|
+
expect(pane.querySelectorAll('.semiont-graph__edge')).toHaveLength(3);
|
|
360
|
+
expect(pane.querySelector('ul')).toBeNull(); // replaced, not joined
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('the focal node is visually distinct, and there is exactly one (P4)', () => {
|
|
364
|
+
const { container } = renderViz();
|
|
365
|
+
const focal = container.querySelectorAll('.semiont-graph__node--focal');
|
|
366
|
+
expect(focal).toHaveLength(1);
|
|
367
|
+
expect(focal[0]!.textContent).toContain('My Resource');
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('siblings are labeled by their quoted text — motivation and note stay in hover (P4, D11)', () => {
|
|
371
|
+
// A node that just says "linking" is uninterpretable; the annotation's
|
|
372
|
+
// identity is the text it wraps (TextQuoteSelector.exact, on the embedded
|
|
373
|
+
// W3C annotation). Motivation is conveyed by the node's styling and hover.
|
|
374
|
+
const { container } = renderViz();
|
|
375
|
+
const sibling = container.querySelector('[data-node-id="ann-s"]');
|
|
376
|
+
expect(sibling).not.toBeNull();
|
|
377
|
+
expect(sibling!.querySelector('text')!.textContent).toBe('the disputed span');
|
|
378
|
+
const title = sibling!.querySelector('title');
|
|
379
|
+
expect(title).not.toBeNull();
|
|
380
|
+
expect(title!.textContent).toContain('commenting');
|
|
381
|
+
expect(title!.textContent).toContain('a sibling note');
|
|
382
|
+
// The tagging-purpose body is the entity-type tag — hover lists the type
|
|
383
|
+
// ONCE (from entityTypes), not again as a body value.
|
|
384
|
+
expect(title!.textContent!.match(/Person/g)).toHaveLength(1);
|
|
385
|
+
// Peer hover carries its entity types.
|
|
386
|
+
expect(container.querySelector('[data-node-id="res-2"] title')!.textContent).toContain('Topic');
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
/** A resource-level provenance annotation: target has no selector (nothing
|
|
390
|
+
* to quote); the body's SpecificResource names what it links to. */
|
|
391
|
+
function provenanceContext(bodySource: string): GatheredContext {
|
|
392
|
+
return {
|
|
393
|
+
...(resourceContext() as object),
|
|
394
|
+
graph: {
|
|
395
|
+
nodes: [
|
|
396
|
+
{ id: 'res-1', type: 'resource', label: 'My Resource' },
|
|
397
|
+
{ id: 'res-9', type: 'resource', label: 'Generated Doc' },
|
|
398
|
+
{ id: 'ann-p', type: 'annotation', label: 'linking',
|
|
399
|
+
annotation: { id: 'ann-p', motivation: 'linking',
|
|
400
|
+
target: { source: 'res-1' },
|
|
401
|
+
body: [{ type: 'SpecificResource', source: bodySource, purpose: 'linking' }] } },
|
|
402
|
+
],
|
|
403
|
+
edges: [{ source: 'ann-p', target: 'res-1', type: 'annotation-of' }],
|
|
404
|
+
},
|
|
405
|
+
} as unknown as GatheredContext;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
it('a resource-level linking annotation is labeled by what it links to, not its motivation', () => {
|
|
409
|
+
const { container } = render(<ContextSummary context={provenanceContext('res-9')} translations={t} />);
|
|
410
|
+
const node = container.querySelector('[data-node-id="ann-p"]')!;
|
|
411
|
+
expect(node.querySelector('text')!.textContent).toBe('→ Generated Doc');
|
|
412
|
+
// Hover carries the full linked name; the motivation stays hover-only.
|
|
413
|
+
expect(node.querySelector('title')!.textContent).toContain('Generated Doc');
|
|
414
|
+
expect(node.querySelector('title')!.textContent).toContain('linking');
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it('an unresolvable link target falls back to the translated label — never raw vocabulary', () => {
|
|
418
|
+
const { container } = render(<ContextSummary context={provenanceContext('res-404')} translations={t} />);
|
|
419
|
+
const node = container.querySelector('[data-node-id="ann-p"]')!;
|
|
420
|
+
expect(node.querySelector('text')!.textContent).toBe('Resource link');
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('layout is deterministic — same context, same positions (P4, D3)', () => {
|
|
424
|
+
const positions = () => {
|
|
425
|
+
const { container, unmount } = renderViz();
|
|
426
|
+
const snap = Array.from(container.querySelectorAll('.semiont-graph__node rect'))
|
|
427
|
+
.map((r) => `${r.closest('[data-node-id]')!.getAttribute('data-node-id')}@${r.getAttribute('x')},${r.getAttribute('y')}`)
|
|
428
|
+
.join('|');
|
|
429
|
+
unmount();
|
|
430
|
+
return snap;
|
|
431
|
+
};
|
|
432
|
+
const first = positions();
|
|
433
|
+
expect(first.length).toBeGreaterThan(0);
|
|
434
|
+
expect(positions()).toBe(first);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// ── Graph geometry — compaction + wrap, everything draws (no-clipping) ─────
|
|
438
|
+
// The layout adapts to what's populated: absent citer/peer layers free their
|
|
439
|
+
// columns for the sibling band to wrap into, and the viewBox shrinks to the
|
|
440
|
+
// columns actually used. Nothing is ever capped or hidden — overflow rides
|
|
441
|
+
// the wizard pane's scroll.
|
|
442
|
+
|
|
443
|
+
/** Focal resource + N sibling annotations, no citers, no peers. */
|
|
444
|
+
function middleOnlyContext(siblingCount: number): GatheredContext {
|
|
445
|
+
return {
|
|
446
|
+
...(resourceContext() as object),
|
|
447
|
+
graph: {
|
|
448
|
+
nodes: [
|
|
449
|
+
{ id: 'res-1', type: 'resource', label: 'My Resource' },
|
|
450
|
+
...Array.from({ length: siblingCount }, (_, i) => ({
|
|
451
|
+
id: `ann-${i}`, type: 'annotation', label: 'commenting',
|
|
452
|
+
annotation: { id: `ann-${i}`, motivation: 'commenting',
|
|
453
|
+
target: { source: 'res-1', selector: [
|
|
454
|
+
{ type: 'TextQuoteSelector', exact: `span ${i}` },
|
|
455
|
+
] },
|
|
456
|
+
body: [] },
|
|
457
|
+
})),
|
|
458
|
+
],
|
|
459
|
+
edges: Array.from({ length: siblingCount }, (_, i) => ({
|
|
460
|
+
source: `ann-${i}`, target: 'res-1', type: 'annotation-of',
|
|
461
|
+
})),
|
|
462
|
+
},
|
|
463
|
+
} as unknown as GatheredContext;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
describe('graph geometry adapts to the populated layers', () => {
|
|
467
|
+
it('a lone sibling collapses to one natural-size column — no dead thirds', () => {
|
|
468
|
+
const { container } = render(<ContextSummary context={middleOnlyContext(1)} translations={t} />);
|
|
469
|
+
const svg = container.querySelector('svg')!;
|
|
470
|
+
// One 180-wide column + margins; two rows (focal above, sibling below).
|
|
471
|
+
expect(svg.getAttribute('viewBox')).toBe('0 0 196 120');
|
|
472
|
+
// Natural-size cap: 1 viewBox unit never exceeds 1px, so a narrow graph
|
|
473
|
+
// renders small instead of stretching to fill the pane.
|
|
474
|
+
expect((svg as SVGElement).style.maxWidth).toBe('196px');
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it('siblings wrap into the freed columns instead of chaining (5 → 3-across grid)', () => {
|
|
478
|
+
const { container } = render(<ContextSummary context={middleOnlyContext(5)} translations={t} />);
|
|
479
|
+
const svg = container.querySelector('svg')!;
|
|
480
|
+
// Three columns, three rows (focal + two sibling rows) — not six rows.
|
|
481
|
+
expect(svg.getAttribute('viewBox')).toBe('0 0 628 172');
|
|
482
|
+
const rects = Array.from(container.querySelectorAll('.semiont-graph__node--annotation rect'));
|
|
483
|
+
expect(new Set(rects.map((r) => r.getAttribute('x')))).toEqual(new Set(['8', '224', '440']));
|
|
484
|
+
expect(Math.max(...rects.map((r) => Number(r.getAttribute('y'))))).toBe(112); // row 2
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('wrap compacts, never clips — every sibling and edge still draws', () => {
|
|
488
|
+
const { container } = render(<ContextSummary context={middleOnlyContext(11)} translations={t} />);
|
|
489
|
+
expect(container.querySelectorAll('.semiont-graph__node--annotation')).toHaveLength(11);
|
|
490
|
+
expect(container.querySelectorAll('.semiont-graph__edge--annotation-of')).toHaveLength(11);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it('a fully populated graph keeps the three-layer full-width shape', () => {
|
|
494
|
+
// vizContext has citers AND peers — no freed columns, the middle band
|
|
495
|
+
// stays a single column and the chain is the correct rendering.
|
|
496
|
+
const { container } = renderViz();
|
|
497
|
+
const svg = container.querySelector('svg')!;
|
|
498
|
+
expect(svg.getAttribute('viewBox')!.startsWith('0 0 628 ')).toBe(true);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it('the floating cited-line caption is gone — the drawn citer nodes ARE the count', () => {
|
|
502
|
+
const { container } = renderViz();
|
|
503
|
+
const pane = container.querySelector('.semiont-gather-pane--graph')!;
|
|
504
|
+
expect(container.querySelector('[data-node-id="res-3"]')).not.toBeNull(); // citer drawn
|
|
505
|
+
expect(pane.querySelector('.semiont-graph__cited-line')).toBeNull();
|
|
506
|
+
expect(pane.textContent).not.toContain('Cited by (1)');
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('an annotation focus WITHOUT the annotate group renders display-only (GFR A2)', () => {
|
|
511
|
+
// The resolution controls belong to the caller that can serve them. A
|
|
512
|
+
// display-only caller must never get a hint textarea wired to nothing —
|
|
513
|
+
// which is what the old always-on gate produced for an annotation focus.
|
|
514
|
+
const { container } = render(
|
|
515
|
+
<GatherContextStep
|
|
516
|
+
context={annotationContext()}
|
|
517
|
+
contextLoading={false}
|
|
518
|
+
contextError={null}
|
|
519
|
+
translations={t}
|
|
520
|
+
/>,
|
|
521
|
+
);
|
|
522
|
+
expect(container.querySelector('.semiont-gather__footer')).toBeNull();
|
|
523
|
+
expect(container.querySelector('.semiont-gather__hint-textarea')).toBeNull();
|
|
524
|
+
});
|
|
107
525
|
});
|
|
526
|
+
|
|
527
|
+
function annotationContext(): GatheredContext {
|
|
528
|
+
return {
|
|
529
|
+
focus: {
|
|
530
|
+
kind: 'annotation',
|
|
531
|
+
annotation: { id: 'anno-1', motivation: 'linking' },
|
|
532
|
+
sourceResource: { '@id': 'res-1', name: 'Host' },
|
|
533
|
+
selected: { before: 'a ', text: 'term', after: ' b' },
|
|
534
|
+
},
|
|
535
|
+
graph: { nodes: [], edges: [] },
|
|
536
|
+
metadata: {},
|
|
537
|
+
} as unknown as GatheredContext;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** Same annotation focus, with anchor entity types — the GEP P1 chip fixtures. */
|
|
541
|
+
function annotationContextWithTypes(): GatheredContext {
|
|
542
|
+
const ctx = annotationContext() as GatheredContext & { metadata: { entityTypes?: string[] } };
|
|
543
|
+
ctx.metadata = { entityTypes: ['Topic'] };
|
|
544
|
+
return ctx;
|
|
545
|
+
}
|