@semiont/react-ui 0.5.27 → 0.5.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{PdfAnnotationCanvas.client-EVUPTUWZ.js → PdfAnnotationCanvas.client-C2IGG4SW.js} +31 -4
- package/dist/{ar-OU3DQGC6.js → ar-FABPTJVB.js} +39 -23
- package/dist/{bn-PHWCR6AZ.js → bn-LKP6BDMH.js} +39 -23
- package/dist/{chunk-PCRRBU7H.js → chunk-2GOHTTHW.js} +39 -23
- package/dist/{chunk-5VDTWRXJ.js → chunk-2YHEE63S.js} +31 -31
- package/dist/{cs-NEXYZ62I.js → cs-YQYNWHRG.js} +39 -23
- package/dist/{da-FAT43YCU.js → da-QHZOYKRY.js} +39 -23
- package/dist/{de-KRQ3WBCU.js → de-GKA7RD7J.js} +39 -23
- package/dist/{el-Q4IU3EKC.js → el-45T257AL.js} +39 -23
- package/dist/{en-2KADE7N4.js → en-RVW5EQ66.js} +2 -2
- package/dist/{es-2IO3R2MK.js → es-AVER7RU4.js} +39 -23
- package/dist/{fa-Q5GOQFXX.js → fa-K3VPK3O2.js} +39 -23
- package/dist/{fi-R6NMRWBE.js → fi-5NEKF7EB.js} +39 -23
- package/dist/{fr-QY4D5E6J.js → fr-FUMNDLBZ.js} +39 -23
- package/dist/{he-X6Y77XHY.js → he-3JGNA3PR.js} +39 -23
- package/dist/{hi-XL7IOEQM.js → hi-IJY375DF.js} +39 -23
- package/dist/{id-QZ4ZZS67.js → id-LWHKJ7ZD.js} +39 -23
- package/dist/index.css +279 -74
- package/dist/index.d.ts +170 -145
- package/dist/index.js +2743 -2365
- package/dist/{it-ME2Q7OB6.js → it-EBVOV4RW.js} +39 -23
- package/dist/{ja-HX7QASRI.js → ja-KWRIX2NF.js} +39 -23
- package/dist/{ko-EQO5AHAV.js → ko-L5CJMFDS.js} +39 -23
- package/dist/{ms-UILFSF3I.js → ms-LSNMJDR2.js} +39 -23
- package/dist/{nl-NNI45PMK.js → nl-ZB32MB7U.js} +39 -23
- package/dist/{no-MAL5F6P6.js → no-4GTNY7M5.js} +39 -23
- package/dist/{pl-4RMY6JAJ.js → pl-LUHBLTHB.js} +39 -23
- package/dist/{pt-VQH2X3QQ.js → pt-C4H3UNQ6.js} +39 -23
- package/dist/{ro-XXCQO2OG.js → ro-GGITS5V4.js} +39 -23
- package/dist/{sv-7Z6EJ6U4.js → sv-T56RTLBP.js} +39 -23
- package/dist/test-utils.js +2 -2
- package/dist/{th-L3DXXYVQ.js → th-22X2IWCB.js} +39 -23
- package/dist/{tr-P3DFVLFJ.js → tr-77TFG62H.js} +39 -23
- package/dist/{uk-7C5D3PXS.js → uk-MIIL6NXT.js} +39 -23
- package/dist/{vi-ZLTLEO7P.js → vi-7JYOIGNV.js} +39 -23
- package/dist/{zh-5FHVWDIN.js → zh-ID6DRYU4.js} +39 -23
- package/package.json +9 -8
- package/src/components/AssistProgress.tsx +36 -2
- package/src/components/CodeMirrorRenderer.tsx +8 -8
- package/src/components/__tests__/AssistProgress.test.tsx +59 -0
- package/src/components/modals/ComposeStep.tsx +218 -0
- package/src/components/modals/ConfigureGatherStep.tsx +10 -18
- package/src/components/modals/ConfigureGenerationStep.tsx +35 -19
- 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 +238 -50
- package/src/components/modals/ResourceGenerateModal.tsx +216 -108
- 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 +35 -0
- 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 +417 -38
- package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +276 -85
- package/src/components/modals/__tests__/SearchResultsStep.test.tsx +61 -0
- package/src/components/modals/__tests__/WizardFooter.test.tsx +113 -4
- 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 +5 -5
- 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.test.tsx +9 -9
- 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 +9 -2
- package/src/components/resource/panels/ReferencesPanel.tsx +4 -0
- 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.test.tsx +33 -1
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +148 -6
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +0 -65
- package/src/features/resource-compose/components/ResourceComposePage.tsx +17 -130
- package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +1 -100
- package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -57
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +71 -15
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +133 -59
- 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/translations/ar.json +38 -22
- package/translations/bn.json +38 -22
- package/translations/cs.json +38 -22
- package/translations/da.json +38 -22
- package/translations/de.json +38 -22
- package/translations/el.json +38 -22
- package/translations/en.json +38 -22
- package/translations/es.json +38 -22
- package/translations/fa.json +38 -22
- package/translations/fi.json +38 -22
- package/translations/fr.json +38 -22
- package/translations/he.json +38 -22
- package/translations/hi.json +38 -22
- package/translations/id.json +38 -22
- package/translations/it.json +38 -22
- package/translations/ja.json +38 -22
- package/translations/ko.json +38 -22
- package/translations/ms.json +38 -22
- package/translations/nl.json +38 -22
- package/translations/no.json +38 -22
- package/translations/pl.json +38 -22
- package/translations/pt.json +38 -22
- package/translations/ro.json +38 -22
- package/translations/sv.json +38 -22
- package/translations/th.json +38 -22
- package/translations/tr.json +38 -22
- package/translations/uk.json +38 -22
- package/translations/vi.json +38 -22
- package/translations/zh.json +38 -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
|
@@ -16,6 +16,8 @@ import { WizardFooter } from '../WizardFooter';
|
|
|
16
16
|
import { ConfigureSearchStep } from '../ConfigureSearchStep';
|
|
17
17
|
import { ConfigureGenerationStep } from '../ConfigureGenerationStep';
|
|
18
18
|
import { SearchResultsStep } from '../SearchResultsStep';
|
|
19
|
+
import { ConfigureGatherStep } from '../ConfigureGatherStep';
|
|
20
|
+
import { GatherContextStep } from '../GatherContextStep';
|
|
19
21
|
import type { GatheredContext } from '@semiont/core';
|
|
20
22
|
|
|
21
23
|
/** Anything that reads as "get me out of here" belongs in the corner, not the footer. */
|
|
@@ -78,6 +80,25 @@ describe('WizardFooter', () => {
|
|
|
78
80
|
await userEvent.click(screen.getByText(/Back/));
|
|
79
81
|
expect(onBack).not.toHaveBeenCalled();
|
|
80
82
|
});
|
|
83
|
+
|
|
84
|
+
it('a disabled primary is not clickable, but Back stays live', async () => {
|
|
85
|
+
// `disabled` is the action's PRECONDITION loading (review step waiting on
|
|
86
|
+
// gather) — distinct from `pending`, which is the action itself running and
|
|
87
|
+
// rightly freezes retreat too. While a precondition loads, retreat must work.
|
|
88
|
+
const onBack = vi.fn();
|
|
89
|
+
const onAdvance = vi.fn();
|
|
90
|
+
render(
|
|
91
|
+
<WizardFooter
|
|
92
|
+
backLabel="Back"
|
|
93
|
+
onBack={onBack}
|
|
94
|
+
primary={{ label: 'Next', type: 'button', onClick: onAdvance, disabled: true }}
|
|
95
|
+
/>,
|
|
96
|
+
);
|
|
97
|
+
await userEvent.click(screen.getByText(/Next/));
|
|
98
|
+
expect(onAdvance).not.toHaveBeenCalled();
|
|
99
|
+
await userEvent.click(screen.getByText(/Back/));
|
|
100
|
+
expect(onBack).toHaveBeenCalledTimes(1);
|
|
101
|
+
});
|
|
81
102
|
});
|
|
82
103
|
|
|
83
104
|
// ─────────────────────────────────────────────────────────────────────
|
|
@@ -87,10 +108,11 @@ describe('WizardFooter', () => {
|
|
|
87
108
|
const SEARCH_T = {
|
|
88
109
|
maxResults: 'Max Results', semanticScoring: 'Semantic Scoring',
|
|
89
110
|
semanticScoringHelp: 'help', back: 'Back', search: 'Search', searching: 'Searching…',
|
|
111
|
+
searchFailed: 'Search failed',
|
|
90
112
|
};
|
|
91
113
|
|
|
92
114
|
const GEN_T = {
|
|
93
|
-
resourceTitle: 'Title', resourceTitlePlaceholder: '', additionalInstructions: 'Instructions',
|
|
115
|
+
resourceTitle: 'Title', resourceTitlePlaceholder: '', saveLocation: 'Save location', additionalInstructions: 'Instructions',
|
|
94
116
|
additionalInstructionsPlaceholder: '', language: 'Language', languageHelp: '',
|
|
95
117
|
creativity: 'Creativity', creativityFocused: 'Focused', creativityCreative: 'Creative',
|
|
96
118
|
maxLength: 'Max Length', maxLengthHelp: '', maxLengthCeiling: 'Limited to {{max}} by {{model}}.',
|
|
@@ -99,13 +121,18 @@ const GEN_T = {
|
|
|
99
121
|
|
|
100
122
|
const CONTEXT_T = {
|
|
101
123
|
sourceContextLabel: 'Source', connectionsLabel: 'Connections', citedByLabel: 'Cited by',
|
|
102
|
-
|
|
124
|
+
graphPaneTitle: 'In the graph', graphEmpty: 'No links yet.', resourceLinkLabel: 'Resource link',
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const GATHER_T = {
|
|
128
|
+
intro: 'Choose what to include.', includeContent: 'Content', includeSummary: 'Summary',
|
|
129
|
+
depth: 'Depth', maxResources: 'Max', gather: 'Gather',
|
|
103
130
|
};
|
|
104
131
|
|
|
105
132
|
const RESULTS_T = { noResults: 'None', score: 'Score', link: 'Link', back: 'Back', ...CONTEXT_T };
|
|
106
133
|
|
|
107
134
|
const CONTEXT = {
|
|
108
|
-
focus: { kind: 'annotation', annotation: { id: 'a1' }, sourceResource: { id: 'r1', name: 'Src' } },
|
|
135
|
+
focus: { kind: 'annotation', annotation: { id: 'a1' }, sourceResource: { '@id': 'r1', name: 'Src' } },
|
|
109
136
|
// `graph` is not optional in practice: ContextSummary hands it straight to
|
|
110
137
|
// `deriveViews`, which dereferences `.nodes`.
|
|
111
138
|
graph: { nodes: [], edges: [] },
|
|
@@ -130,7 +157,6 @@ describe('every step footer follows the grammar (A1, A2)', () => {
|
|
|
130
157
|
it('SearchResultsStep — footer is retreat only; Link lives on the rows', () => {
|
|
131
158
|
const { container } = render(
|
|
132
159
|
<SearchResultsStep
|
|
133
|
-
context={CONTEXT}
|
|
134
160
|
results={[{ id: 'r1', name: 'Black Sea', score: 54.22 } as never]}
|
|
135
161
|
onBack={vi.fn()}
|
|
136
162
|
onLink={vi.fn()}
|
|
@@ -161,6 +187,89 @@ describe('every step footer follows the grammar (A1, A2)', () => {
|
|
|
161
187
|
expect(labels).toHaveLength(2);
|
|
162
188
|
expect(labels.filter((l) => DISMISSAL.test(l))).toEqual([]);
|
|
163
189
|
});
|
|
190
|
+
|
|
191
|
+
it('ConfigureGatherStep — first step of the resource-generate flow: advance only (GFR A5)', () => {
|
|
192
|
+
const { container } = render(
|
|
193
|
+
<ConfigureGatherStep onGather={vi.fn()} translations={GATHER_T} />,
|
|
194
|
+
);
|
|
195
|
+
const labels = buttonsIn(footerOf(container)!).map((b) => b.textContent ?? '');
|
|
196
|
+
expect(labels).toHaveLength(1); // nowhere to go back to, and dismissal is the corner's job
|
|
197
|
+
expect(labels.filter((l) => DISMISSAL.test(l))).toEqual([]);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
202
|
+
// The THIRD footer species: the resolution CHOOSER. The one deliberate
|
|
203
|
+
// exception to the WizardFooter grammar — a three-way fork the
|
|
204
|
+
// one-retreat-plus-one-advance component cannot express. Its grammar is
|
|
205
|
+
// its own, and this pin is what keeps it a species instead of drift.
|
|
206
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
207
|
+
describe('the resolution chooser (GatherContextStep) — the named exception', () => {
|
|
208
|
+
const ANNOTATE = {
|
|
209
|
+
userHint: '',
|
|
210
|
+
onUserHintChange: vi.fn(),
|
|
211
|
+
onBind: vi.fn(),
|
|
212
|
+
onGenerate: vi.fn(),
|
|
213
|
+
onCompose: vi.fn(),
|
|
214
|
+
translations: {
|
|
215
|
+
search: 'Search', generate: 'Generate', compose: 'Compose',
|
|
216
|
+
resolutionStrategyLabel: 'Strategy', userHintLabel: 'Hint',
|
|
217
|
+
userHintEffect: 'steers Search and Generate', userHintPlaceholder: '',
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
const DISPLAY_T = {
|
|
221
|
+
loadingContext: 'Loading…', failedContext: 'Failed', ...CONTEXT_T,
|
|
222
|
+
corpusPaneTitle: 'In the corpus', corpusEmpty: 'Nothing similar.',
|
|
223
|
+
excludedReceipt: '{{types}} excluded', machineRead: 'OCR', score: 'Score',
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const chooserButtons = (container: HTMLElement) =>
|
|
227
|
+
Array.from(container.querySelectorAll('.semiont-gather__actions button'));
|
|
228
|
+
|
|
229
|
+
it('three equal-width peers, one of them quiet, none of them dismissal', () => {
|
|
230
|
+
const { container } = render(
|
|
231
|
+
<GatherContextStep
|
|
232
|
+
context={CONTEXT} contextLoading={false} contextError={null}
|
|
233
|
+
annotate={ANNOTATE} translations={DISPLAY_T}
|
|
234
|
+
/>,
|
|
235
|
+
);
|
|
236
|
+
const buttons = chooserButtons(container);
|
|
237
|
+
expect(buttons).toHaveLength(3);
|
|
238
|
+
// Equal width is HONEST here — these are actual peers. This is the same
|
|
239
|
+
// `--flex` the shared footer bans for making retreat read as advance's peer.
|
|
240
|
+
for (const b of buttons) expect(b.className).toContain('semiont-button--flex');
|
|
241
|
+
// The AI paths are primary; the manual path's demotion to secondary is the
|
|
242
|
+
// recorded convention, not drift.
|
|
243
|
+
const secondary = buttons.filter((b) => b.className.includes('semiont-button--secondary'));
|
|
244
|
+
expect(secondary).toHaveLength(1);
|
|
245
|
+
expect(secondary[0]!.textContent).toContain('Compose');
|
|
246
|
+
// Dismissal stays in the corner, as on every footer.
|
|
247
|
+
expect(buttons.map((b) => b.textContent ?? '').filter((l) => DISMISSAL.test(l))).toEqual([]);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('ellipses mark step-vs-act: the step-leading choices carry one, the acting one does not', () => {
|
|
251
|
+
// The "…" is component-owned markup, not translated copy: Search and
|
|
252
|
+
// Generate lead to another step; Compose acts immediately (navigates away).
|
|
253
|
+
const { container } = render(
|
|
254
|
+
<GatherContextStep
|
|
255
|
+
context={CONTEXT} contextLoading={false} contextError={null}
|
|
256
|
+
annotate={ANNOTATE} translations={DISPLAY_T}
|
|
257
|
+
/>,
|
|
258
|
+
);
|
|
259
|
+
const labels = chooserButtons(container).map((b) => (b.textContent ?? '').trim());
|
|
260
|
+
expect(labels.filter((l) => l.endsWith('…'))).toHaveLength(2);
|
|
261
|
+
expect(labels.find((l) => l.includes('Compose'))!.endsWith('…')).toBe(false);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('every choice waits for context', () => {
|
|
265
|
+
const { container } = render(
|
|
266
|
+
<GatherContextStep
|
|
267
|
+
context={CONTEXT} contextLoading contextError={null}
|
|
268
|
+
annotate={ANNOTATE} translations={DISPLAY_T}
|
|
269
|
+
/>,
|
|
270
|
+
);
|
|
271
|
+
for (const b of chooserButtons(container)) expect(b).toBeDisabled();
|
|
272
|
+
});
|
|
164
273
|
});
|
|
165
274
|
|
|
166
275
|
// ─────────────────────────────────────────────────────────────────────
|
|
@@ -685,7 +685,7 @@ export function PdfAnnotationCanvas({
|
|
|
685
685
|
|
|
686
686
|
const uri = resourceUri;
|
|
687
687
|
const outcome = session.client.browse.resourceAnchoredText(toResourceId(uri)).then(
|
|
688
|
-
(served) => (served &&
|
|
688
|
+
(served) => (served && served.kind === 'extracted' ? served : null),
|
|
689
689
|
() => {
|
|
690
690
|
if (resourceAnchoredRef.current?.uri === uri) resourceAnchoredRef.current = null;
|
|
691
691
|
return null;
|
|
@@ -861,6 +861,39 @@ export function PdfAnnotationCanvas({
|
|
|
861
861
|
|
|
862
862
|
const stripRef = useRef<HTMLDivElement>(null);
|
|
863
863
|
|
|
864
|
+
// The strip's CSS scrollport ceiling is 100vh — right when the nearest
|
|
865
|
+
// scroller is the window, a lie inside an inner-scrolled panel: the sticky
|
|
866
|
+
// strip then extends below the panel's clip, and `nearest` (below) considers
|
|
867
|
+
// a tick in that hidden band already in view, so the active page vanished
|
|
868
|
+
// whenever it crossed the strip's bottom (never the top, whose edges
|
|
869
|
+
// coincide). Size the scrollport to the scroller that actually clips it;
|
|
870
|
+
// with no such scroller the CSS ceiling stands.
|
|
871
|
+
useEffect(() => {
|
|
872
|
+
const strip = stripRef.current;
|
|
873
|
+
if (pageLayout !== 'scroll' || !strip) return;
|
|
874
|
+
let scroller: HTMLElement | null = strip.parentElement;
|
|
875
|
+
while (scroller) {
|
|
876
|
+
const overflowY = getComputedStyle(scroller).overflowY;
|
|
877
|
+
if (overflowY === 'auto' || overflowY === 'scroll') break;
|
|
878
|
+
scroller = scroller.parentElement;
|
|
879
|
+
}
|
|
880
|
+
if (!scroller) return;
|
|
881
|
+
const clip = scroller;
|
|
882
|
+
const size = () => { strip.style.maxHeight = `${clip.clientHeight}px`; };
|
|
883
|
+
size();
|
|
884
|
+
let observer: ResizeObserver | null = null;
|
|
885
|
+
try {
|
|
886
|
+
observer = new ResizeObserver(size);
|
|
887
|
+
observer.observe(clip);
|
|
888
|
+
} catch {
|
|
889
|
+
window.addEventListener('resize', size);
|
|
890
|
+
}
|
|
891
|
+
return () => {
|
|
892
|
+
if (observer) observer.disconnect();
|
|
893
|
+
else window.removeEventListener('resize', size);
|
|
894
|
+
};
|
|
895
|
+
}, [pageLayout, numPages, pageShape]);
|
|
896
|
+
|
|
864
897
|
useEffect(() => {
|
|
865
898
|
// `nearest` so following the reader never yanks the strip around; it
|
|
866
899
|
// scrolls only when the current rectangle would otherwise leave view.
|
|
@@ -508,6 +508,7 @@ describe('PdfAnnotationCanvas', () => {
|
|
|
508
508
|
beckon: { hover: vi.fn() },
|
|
509
509
|
browse: {
|
|
510
510
|
resourceAnchoredText: vi.fn().mockResolvedValue({
|
|
511
|
+
kind: 'extracted',
|
|
511
512
|
text: 'Hello world again',
|
|
512
513
|
items: [
|
|
513
514
|
{ start: 0, end: 5, page: 1, x: 72, y: 700, width: 30, height: 12 },
|
|
@@ -560,6 +561,7 @@ describe('PdfAnnotationCanvas', () => {
|
|
|
560
561
|
} as unknown as Awaited<ReturnType<typeof loadPdfDocument>>);
|
|
561
562
|
|
|
562
563
|
const resourceAnchoredText = vi.fn().mockResolvedValue({
|
|
564
|
+
kind: 'extracted',
|
|
563
565
|
text: 'Hello world again',
|
|
564
566
|
items: [
|
|
565
567
|
{ start: 0, end: 5, page: 1, x: 72, y: 700, width: 30, height: 12 },
|
|
@@ -727,6 +729,7 @@ describe('PdfAnnotationCanvas', () => {
|
|
|
727
729
|
scannedDoc();
|
|
728
730
|
|
|
729
731
|
const resourceAnchoredText = vi.fn().mockResolvedValue({
|
|
732
|
+
kind: 'extracted',
|
|
730
733
|
text: 'Hello world again',
|
|
731
734
|
items: [{ start: 0, end: 5, page: 1, x: 72, y: 700, width: 30, height: 12 }],
|
|
732
735
|
});
|
|
@@ -889,6 +892,40 @@ describe('PdfAnnotationCanvas', () => {
|
|
|
889
892
|
});
|
|
890
893
|
});
|
|
891
894
|
|
|
895
|
+
test('the strip sizes its scrollport to the panel that clips it — not the window', async () => {
|
|
896
|
+
// The strip is sticky with a CSS ceiling of 100vh — right when the
|
|
897
|
+
// nearest scroller is the window, a lie inside an inner-scrolled panel:
|
|
898
|
+
// there the strip's scrollport extends below the panel's clip, and
|
|
899
|
+
// `scrollIntoView({block:'nearest'})` considers a tick in that hidden
|
|
900
|
+
// band already in view — the active page vanished when it crossed the
|
|
901
|
+
// strip's bottom (never the top, whose edges coincide). Measured live:
|
|
902
|
+
// strip scrollport 126→846 vs panel 126→639. The fix sizes the
|
|
903
|
+
// scrollport to the scroller that actually clips it.
|
|
904
|
+
const io = stubIntersectionObserver();
|
|
905
|
+
scannedDoc();
|
|
906
|
+
|
|
907
|
+
const scroller = document.createElement('div');
|
|
908
|
+
scroller.style.overflowY = 'auto';
|
|
909
|
+
Object.defineProperty(scroller, 'clientHeight', { value: 513, configurable: true });
|
|
910
|
+
document.body.appendChild(scroller);
|
|
911
|
+
|
|
912
|
+
render(
|
|
913
|
+
<PdfAnnotationCanvas resourceUri="res-1"
|
|
914
|
+
pdfUrl={mockPdfUrl}
|
|
915
|
+
drawingMode={null}
|
|
916
|
+
pageLayout="scroll"
|
|
917
|
+
/>,
|
|
918
|
+
{ container: scroller },
|
|
919
|
+
);
|
|
920
|
+
await waitFor(() => {
|
|
921
|
+
expect(document.querySelectorAll('.semiont-pdf-annotation-canvas__strip-page')).toHaveLength(5);
|
|
922
|
+
});
|
|
923
|
+
io.fire([1]);
|
|
924
|
+
|
|
925
|
+
const strip = document.querySelector('.semiont-pdf-annotation-canvas__strip') as HTMLElement;
|
|
926
|
+
await waitFor(() => expect(strip.style.maxHeight).toBe('513px'));
|
|
927
|
+
});
|
|
928
|
+
|
|
892
929
|
test('clicking a strip rectangle jumps to that page', async () => {
|
|
893
930
|
const io = stubIntersectionObserver();
|
|
894
931
|
scannedDoc();
|
|
@@ -1244,7 +1281,7 @@ describe('PdfAnnotationCanvas', () => {
|
|
|
1244
1281
|
|
|
1245
1282
|
const resourceAnchoredText = vi.fn()
|
|
1246
1283
|
.mockRejectedValueOnce(new Error('transport down'))
|
|
1247
|
-
.mockResolvedValue({ text: 'later', items: [] });
|
|
1284
|
+
.mockResolvedValue({ kind: 'extracted', text: 'later', items: [] });
|
|
1248
1285
|
const session = {
|
|
1249
1286
|
client: { beckon: { hover: vi.fn() }, browse: { resourceAnchoredText } },
|
|
1250
1287
|
} as unknown as import('@semiont/sdk').SemiontSession;
|
|
@@ -39,7 +39,7 @@ interface Props {
|
|
|
39
39
|
/** Session for the shown resource — its client emits mark:* / mark.request; its bus feeds toolbar + beckon events. */
|
|
40
40
|
session: SemiontSession | null;
|
|
41
41
|
/** Recently-created annotation ids to sparkle (host-provided; was ResourceAnnotationsContext). */
|
|
42
|
-
|
|
42
|
+
sparkleAnnotationIds?: Set<string>;
|
|
43
43
|
/** The bar's Mode control reports the chosen mode here (the owner applies it). */
|
|
44
44
|
onModeChange?: (mode: boolean) => void;
|
|
45
45
|
/** Render the built-in bar (default). false → no bar; selection capture and drawing stay live. */
|
|
@@ -69,7 +69,7 @@ export function AnnotateView({
|
|
|
69
69
|
hoverDelayMs = 150,
|
|
70
70
|
annotateMode,
|
|
71
71
|
session,
|
|
72
|
-
|
|
72
|
+
sparkleAnnotationIds,
|
|
73
73
|
onModeChange,
|
|
74
74
|
showToolbar = true,
|
|
75
75
|
renderers,
|
|
@@ -267,7 +267,7 @@ export function AnnotateView({
|
|
|
267
267
|
selectedMotivation={selectedMotivation}
|
|
268
268
|
hoveredAnnotationId={hoveredAnnotationId}
|
|
269
269
|
hoverDelayMs={hoverDelayMs}
|
|
270
|
-
|
|
270
|
+
sparkleAnnotationIds={sparkleAnnotationIds}
|
|
271
271
|
scrollToAnnotationId={scrollToAnnotationId}
|
|
272
272
|
showLineNumbers={showLineNumbers}
|
|
273
273
|
enableWidgets={enableWidgets}
|
|
@@ -35,7 +35,7 @@ interface Props {
|
|
|
35
35
|
/** Session for the shown resource — emits browse:click / beckon:hover; its bus feeds beckon events. */
|
|
36
36
|
session: SemiontSession | null;
|
|
37
37
|
/** Recently-created annotation ids to sparkle (host-provided; was ResourceAnnotationsContext). */
|
|
38
|
-
|
|
38
|
+
sparkleAnnotationIds?: Set<string>;
|
|
39
39
|
/** Override the read-only media renderers (render mode → renderer); merged over the defaults. */
|
|
40
40
|
renderers?: BrowseMediaRenderers;
|
|
41
41
|
/** A content link (`<a href>` in the rendered content) was clicked. The viewer preventDefaults and
|
|
@@ -87,7 +87,7 @@ export const BrowseView = memo(function BrowseView({
|
|
|
87
87
|
annotateMode,
|
|
88
88
|
hoverDelayMs = 150,
|
|
89
89
|
session,
|
|
90
|
-
|
|
90
|
+
sparkleAnnotationIds,
|
|
91
91
|
renderers,
|
|
92
92
|
onLinkClick,
|
|
93
93
|
onReferenceHover,
|
|
@@ -227,11 +227,11 @@ export const BrowseView = memo(function BrowseView({
|
|
|
227
227
|
};
|
|
228
228
|
|
|
229
229
|
// Apply animation classes to new annotations
|
|
230
|
-
if (
|
|
230
|
+
if (sparkleAnnotationIds) {
|
|
231
231
|
const annotationSpans = container.querySelectorAll('[data-annotation-id]');
|
|
232
232
|
annotationSpans.forEach((span) => {
|
|
233
233
|
const annotationId = span.getAttribute('data-annotation-id');
|
|
234
|
-
if (annotationId &&
|
|
234
|
+
if (annotationId && sparkleAnnotationIds.has(annotationId)) {
|
|
235
235
|
span.classList.add('annotation-sparkle');
|
|
236
236
|
}
|
|
237
237
|
});
|
|
@@ -248,7 +248,7 @@ export const BrowseView = memo(function BrowseView({
|
|
|
248
248
|
cleanupHover();
|
|
249
249
|
referentSub?.unsubscribe(); // in-flight referent load dies with the effect
|
|
250
250
|
};
|
|
251
|
-
}, [content, allAnnotations,
|
|
251
|
+
}, [content, allAnnotations, sparkleAnnotationIds, hoverDelayMs, session, onReferenceHover]);
|
|
252
252
|
|
|
253
253
|
// Helper to scroll annotation into view with pulse effect
|
|
254
254
|
const scrollToAnnotation = useCallback((annotationId: string | null, removePulse = false) => {
|
|
@@ -50,7 +50,7 @@ interface Props {
|
|
|
50
50
|
/** Inline display variant: auto-height, no inner scroll, no pane chrome (browse path). Default: fill-the-pane. */
|
|
51
51
|
inline?: boolean;
|
|
52
52
|
/** Recently-created annotation ids to sparkle (threaded to the browse/annotate subtree). */
|
|
53
|
-
|
|
53
|
+
sparkleAnnotationIds?: Set<string>;
|
|
54
54
|
generatingReferenceId?: string | null;
|
|
55
55
|
showLineNumbers?: boolean;
|
|
56
56
|
hoverDelayMs?: number;
|
|
@@ -116,7 +116,7 @@ export function ResourceViewer({
|
|
|
116
116
|
onLinkClick,
|
|
117
117
|
onReferenceHover,
|
|
118
118
|
inline = false,
|
|
119
|
-
|
|
119
|
+
sparkleAnnotationIds,
|
|
120
120
|
generatingReferenceId,
|
|
121
121
|
showLineNumbers = false,
|
|
122
122
|
hoverDelayMs,
|
|
@@ -356,7 +356,7 @@ export function ResourceViewer({
|
|
|
356
356
|
onModeChange={changeAnnotateMode}
|
|
357
357
|
showToolbar={showToolbar}
|
|
358
358
|
session={session}
|
|
359
|
-
|
|
359
|
+
sparkleAnnotationIds={sparkleAnnotationIds}
|
|
360
360
|
{...(annotateRenderers && { renderers: annotateRenderers })}
|
|
361
361
|
/>
|
|
362
362
|
) : (
|
|
@@ -372,7 +372,7 @@ export function ResourceViewer({
|
|
|
372
372
|
onClickActionChange={changeClickAction}
|
|
373
373
|
showToolbar={showToolbar}
|
|
374
374
|
session={session}
|
|
375
|
-
|
|
375
|
+
sparkleAnnotationIds={sparkleAnnotationIds}
|
|
376
376
|
onLinkClick={onLinkClick}
|
|
377
377
|
onReferenceHover={onReferenceHover}
|
|
378
378
|
inline={inline}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* EMBEDDABLE-RESOURCE-VIEWER step 1b — AnnotateView + AnnotateToolbar provider-free.
|
|
3
3
|
*
|
|
4
|
-
* AnnotateView takes `session` + `
|
|
4
|
+
* AnnotateView takes `session` + `sparkleAnnotationIds` as props; the REAL
|
|
5
5
|
* AnnotateToolbar (not mocked — its decoupling is the crux of 1b) takes `session`
|
|
6
6
|
* as a prop. CodeMirrorRenderer is mocked (heavy; already prop-based).
|
|
7
7
|
*
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* EMBEDDABLE-RESOURCE-VIEWER step 1a — BrowseView renders provider-free.
|
|
3
3
|
*
|
|
4
|
-
* BrowseView takes its `session` + `
|
|
4
|
+
* BrowseView takes its `session` + `sparkleAnnotationIds` as props (not
|
|
5
5
|
* `useSemiont()` / `useResourceAnnotations()`), and subscribes to session-scoped
|
|
6
6
|
* beckon events via `session.subscribe`. `AnnotateToolbar` is mocked here — its
|
|
7
7
|
* own provider decoupling is step 1b — so this spec isolates BrowseView's body.
|
|
@@ -8,7 +8,7 @@ import { createTestSemiontWrapper } from '../../../test-utils';
|
|
|
8
8
|
|
|
9
9
|
import type { Annotation, AnnotationId } from '@semiont/core';
|
|
10
10
|
|
|
11
|
-
// BrowseView takes its `session` + `
|
|
11
|
+
// BrowseView takes its `session` + `sparkleAnnotationIds` as props now (step 1a) — no
|
|
12
12
|
// ResourceAnnotationsContext / SemiontProvider reach-in. `makeSession` (below)
|
|
13
13
|
// wraps the fake client so browse:click / beckon:hover land on the bus the
|
|
14
14
|
// trackers listen on, and session.subscribe registers beckon:* on that bus.
|
|
@@ -125,14 +125,14 @@ function makeSession(client: SemiontClient): SemiontSession {
|
|
|
125
125
|
|
|
126
126
|
const renderWithProviders = (
|
|
127
127
|
component: React.ReactElement<React.ComponentProps<typeof BrowseView>>,
|
|
128
|
-
options: {
|
|
128
|
+
options: { sparkleAnnotationIds?: Set<string> } = {}
|
|
129
129
|
) => {
|
|
130
130
|
const { SemiontWrapper, client } = createTestSemiontWrapper();
|
|
131
131
|
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
132
132
|
<SemiontWrapper>{children}</SemiontWrapper>
|
|
133
133
|
);
|
|
134
134
|
return render(
|
|
135
|
-
React.cloneElement(component, { session: makeSession(client),
|
|
135
|
+
React.cloneElement(component, { session: makeSession(client), sparkleAnnotationIds: options.sparkleAnnotationIds }),
|
|
136
136
|
{ wrapper: Wrapper },
|
|
137
137
|
);
|
|
138
138
|
};
|
|
@@ -140,7 +140,7 @@ const renderWithProviders = (
|
|
|
140
140
|
const renderWithEventTracking = (
|
|
141
141
|
component: React.ReactElement<React.ComponentProps<typeof BrowseView>>,
|
|
142
142
|
tracker: ReturnType<typeof createEventTracker>,
|
|
143
|
-
options: {
|
|
143
|
+
options: { sparkleAnnotationIds?: Set<string> } = {}
|
|
144
144
|
) => {
|
|
145
145
|
const { SemiontWrapper, eventBus, client } = createTestSemiontWrapper();
|
|
146
146
|
tracker._attach(eventBus);
|
|
@@ -148,7 +148,7 @@ const renderWithEventTracking = (
|
|
|
148
148
|
<SemiontWrapper>{children}</SemiontWrapper>
|
|
149
149
|
);
|
|
150
150
|
return render(
|
|
151
|
-
React.cloneElement(component, { session: makeSession(client),
|
|
151
|
+
React.cloneElement(component, { session: makeSession(client), sparkleAnnotationIds: options.sparkleAnnotationIds }),
|
|
152
152
|
{ wrapper: Wrapper },
|
|
153
153
|
);
|
|
154
154
|
};
|
|
@@ -545,7 +545,7 @@ describe('BrowseView Component', () => {
|
|
|
545
545
|
|
|
546
546
|
describe('Annotation Animation Classes', () => {
|
|
547
547
|
it('should apply sparkle class to new annotations', () => {
|
|
548
|
-
const
|
|
548
|
+
const sparkleAnnotationIds = new Set(['new-annotation-1']);
|
|
549
549
|
|
|
550
550
|
const annotations = {
|
|
551
551
|
...defaultProps.annotations,
|
|
@@ -553,12 +553,12 @@ describe('BrowseView Component', () => {
|
|
|
553
553
|
};
|
|
554
554
|
|
|
555
555
|
renderWithProviders(<BrowseView {...defaultProps} annotations={annotations} />, {
|
|
556
|
-
|
|
556
|
+
sparkleAnnotationIds
|
|
557
557
|
});
|
|
558
558
|
|
|
559
|
-
// Verify the
|
|
559
|
+
// Verify the sparkleAnnotationIds set contains the expected annotation
|
|
560
560
|
// In the actual component, this triggers the sparkle class application
|
|
561
|
-
expect(
|
|
561
|
+
expect(sparkleAnnotationIds.has('new-annotation-1')).toBe(true);
|
|
562
562
|
});
|
|
563
563
|
});
|
|
564
564
|
|
|
@@ -51,7 +51,7 @@ export interface AnnotateMediaRendererProps {
|
|
|
51
51
|
// Text-path extras. Optional on the shared interface: a custom image or PDF
|
|
52
52
|
// renderer simply ignores them, the same way `MediaRendererProps.session` is
|
|
53
53
|
// documented to be ignorable.
|
|
54
|
-
|
|
54
|
+
sparkleAnnotationIds?: Set<string>;
|
|
55
55
|
scrollToAnnotationId?: string | null;
|
|
56
56
|
showLineNumbers?: boolean;
|
|
57
57
|
enableWidgets?: boolean;
|
|
@@ -66,7 +66,7 @@ export function TextAnnotateRenderer({
|
|
|
66
66
|
content,
|
|
67
67
|
annotations,
|
|
68
68
|
session,
|
|
69
|
-
|
|
69
|
+
sparkleAnnotationIds,
|
|
70
70
|
hoveredAnnotationId,
|
|
71
71
|
scrollToAnnotationId,
|
|
72
72
|
showLineNumbers = false,
|
|
@@ -84,7 +84,7 @@ export function TextAnnotateRenderer({
|
|
|
84
84
|
content={content}
|
|
85
85
|
segments={segments}
|
|
86
86
|
editable={false}
|
|
87
|
-
|
|
87
|
+
sparkleAnnotationIds={sparkleAnnotationIds}
|
|
88
88
|
{...(hoveredAnnotationId !== undefined && { hoveredAnnotationId })}
|
|
89
89
|
{...(scrollToAnnotationId !== undefined && { scrollToAnnotationId })}
|
|
90
90
|
sourceView={true}
|
|
@@ -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;
|
|
@@ -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');
|
|
@@ -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) {
|
|
@@ -483,6 +486,7 @@ export function ReferencesPanel({
|
|
|
483
486
|
onOpenResource={onOpenResource}
|
|
484
487
|
annotateMode={annotateMode}
|
|
485
488
|
isGenerating={reference.id === generatingReferenceId}
|
|
489
|
+
sparkle={sparkleAnnotationIds?.has(reference.id) ?? false}
|
|
486
490
|
ref={(el) => setEntryRef(reference.id, el)}
|
|
487
491
|
/>
|
|
488
492
|
))
|