@semiont/react-ui 0.5.24 → 0.5.26

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.
Files changed (179) hide show
  1. package/dist/PdfAnnotationCanvas.client-6ZSF6ZTJ.js +677 -0
  2. package/dist/PdfAnnotationCanvas.client-6ZSF6ZTJ.js.map +1 -0
  3. package/dist/PdfAnnotationCanvas.client-JCJMXWWJ.css +180 -0
  4. package/dist/PdfAnnotationCanvas.client-JCJMXWWJ.css.map +1 -0
  5. package/dist/{ar-JRTKCMTF.js → ar-YTUUGK2O.js} +32 -6
  6. package/dist/ar-YTUUGK2O.js.map +1 -0
  7. package/dist/{bn-LO6EDSCC.js → bn-ASGNGPMD.js} +32 -6
  8. package/dist/bn-ASGNGPMD.js.map +1 -0
  9. package/dist/{chunk-4QMTEPQ3.js → chunk-BTUG3VJJ.js} +18 -298
  10. package/dist/chunk-BTUG3VJJ.js.map +1 -0
  11. package/dist/{chunk-5Y5JDCKC.js → chunk-L3WKRWP3.js} +32 -6
  12. package/dist/chunk-L3WKRWP3.js.map +1 -0
  13. package/dist/chunk-OBQOZCJE.js +287 -0
  14. package/dist/chunk-OBQOZCJE.js.map +1 -0
  15. package/dist/{cs-LCHHOR4O.js → cs-WMYVAOZY.js} +32 -6
  16. package/dist/cs-WMYVAOZY.js.map +1 -0
  17. package/dist/{da-TARGPMJR.js → da-FSHHN4D6.js} +32 -6
  18. package/dist/da-FSHHN4D6.js.map +1 -0
  19. package/dist/{de-SBH4QBPJ.js → de-FJFO3TPT.js} +32 -6
  20. package/dist/de-FJFO3TPT.js.map +1 -0
  21. package/dist/{el-2D2OLGWB.js → el-XOH4QIAG.js} +32 -6
  22. package/dist/el-XOH4QIAG.js.map +1 -0
  23. package/dist/{en-QFIEG3CA.js → en-3FCBW3YD.js} +6 -2
  24. package/dist/{es-U6YK2LX6.js → es-HDX4QI7E.js} +32 -6
  25. package/dist/es-HDX4QI7E.js.map +1 -0
  26. package/dist/{fa-S7SZE37M.js → fa-DJ6WTFTJ.js} +32 -6
  27. package/dist/fa-DJ6WTFTJ.js.map +1 -0
  28. package/dist/{fi-CBL3NIXN.js → fi-4S7H7W7U.js} +32 -6
  29. package/dist/fi-4S7H7W7U.js.map +1 -0
  30. package/dist/{fr-EABUHV6S.js → fr-6UJSYHXC.js} +32 -6
  31. package/dist/fr-6UJSYHXC.js.map +1 -0
  32. package/dist/{he-THWD244Y.js → he-ZZJOHJX2.js} +32 -6
  33. package/dist/he-ZZJOHJX2.js.map +1 -0
  34. package/dist/{hi-GHGFZJ44.js → hi-HZJTAW7U.js} +32 -6
  35. package/dist/hi-HZJTAW7U.js.map +1 -0
  36. package/dist/{id-POZOUMDW.js → id-E3I6ILEM.js} +32 -6
  37. package/dist/id-E3I6ILEM.js.map +1 -0
  38. package/dist/index.css +140 -94
  39. package/dist/index.css.map +1 -1
  40. package/dist/index.d.ts +85 -24
  41. package/dist/index.js +1782 -1697
  42. package/dist/index.js.map +1 -1
  43. package/dist/{it-KDYJADSH.js → it-ULAPAD7U.js} +32 -6
  44. package/dist/it-ULAPAD7U.js.map +1 -0
  45. package/dist/{ja-Z5Z6ZF3P.js → ja-SAHZQ4CZ.js} +32 -6
  46. package/dist/ja-SAHZQ4CZ.js.map +1 -0
  47. package/dist/{ko-7X3XUXRN.js → ko-DFU2ADQ4.js} +32 -6
  48. package/dist/ko-DFU2ADQ4.js.map +1 -0
  49. package/dist/{ms-BTWEIW43.js → ms-NWL76XNT.js} +32 -6
  50. package/dist/ms-NWL76XNT.js.map +1 -0
  51. package/dist/{nl-IS2CGF5I.js → nl-ATCM76WN.js} +32 -6
  52. package/dist/nl-ATCM76WN.js.map +1 -0
  53. package/dist/{no-AAMEVA26.js → no-UIAQ5RJF.js} +32 -6
  54. package/dist/no-UIAQ5RJF.js.map +1 -0
  55. package/dist/{pl-PVXUQYFT.js → pl-NOECQDXD.js} +32 -6
  56. package/dist/pl-NOECQDXD.js.map +1 -0
  57. package/dist/{pt-3ITQ76TL.js → pt-SVWIIUPC.js} +32 -6
  58. package/dist/pt-SVWIIUPC.js.map +1 -0
  59. package/dist/{ro-FRB4IIVS.js → ro-NR2DRTZG.js} +32 -6
  60. package/dist/ro-NR2DRTZG.js.map +1 -0
  61. package/dist/{sv-DS3RO24M.js → sv-STVJ37JP.js} +32 -6
  62. package/dist/sv-STVJ37JP.js.map +1 -0
  63. package/dist/test-utils.js +5 -3
  64. package/dist/test-utils.js.map +1 -1
  65. package/dist/{th-AX66MWWB.js → th-UWQR4K54.js} +32 -6
  66. package/dist/th-UWQR4K54.js.map +1 -0
  67. package/dist/{tr-7KTB67TT.js → tr-WNXZSD27.js} +32 -6
  68. package/dist/tr-WNXZSD27.js.map +1 -0
  69. package/dist/{uk-SMQS2MAQ.js → uk-SJRMB47Y.js} +32 -6
  70. package/dist/uk-SJRMB47Y.js.map +1 -0
  71. package/dist/{vi-D3JXL2EZ.js → vi-TMRQFES2.js} +32 -6
  72. package/dist/vi-TMRQFES2.js.map +1 -0
  73. package/dist/{zh-S754AWFS.js → zh-QOL3LYEM.js} +32 -6
  74. package/dist/zh-QOL3LYEM.js.map +1 -0
  75. package/package.json +8 -8
  76. package/src/components/AssistProgress.tsx +42 -30
  77. package/src/components/EntityFoundLog.tsx +38 -0
  78. package/src/components/StatusDisplay.css +35 -35
  79. package/src/components/__tests__/AssistProgress.test.tsx +86 -28
  80. package/src/components/annotation/annotations.css +1 -1
  81. package/src/components/modals/SearchModal.css +5 -5
  82. package/src/components/pdf-annotation/PdfAnnotationCanvas.css +143 -6
  83. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +689 -198
  84. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +1008 -3
  85. package/src/components/pdf-annotation/__tests__/estimate-slot-height.test.ts +53 -0
  86. package/src/components/pdf-annotation/estimate-slot-height.ts +31 -0
  87. package/src/components/resource/AnnotateView.tsx +14 -7
  88. package/src/components/resource/BrowseView.tsx +2 -7
  89. package/src/components/resource/DownloadFileLink.tsx +49 -0
  90. package/src/components/resource/__tests__/AnnotateView.focus.test.tsx +58 -0
  91. package/src/components/resource/__tests__/browse-renderers.dispatch.test.tsx +33 -0
  92. package/src/components/resource/__tests__/download-link.test.tsx +129 -0
  93. package/src/components/resource/annotate-renderers.tsx +10 -1
  94. package/src/components/resource/browse-renderers.tsx +10 -1
  95. package/src/components/resource/panels/AssistSection.css +0 -43
  96. package/src/components/resource/panels/AssistSection.tsx +11 -2
  97. package/src/components/resource/panels/AssistShell.tsx +5 -1
  98. package/src/components/resource/panels/ReferencesPanel.tsx +12 -13
  99. package/src/components/resource/panels/TaggingPanel.tsx +21 -1
  100. package/src/components/resource/panels/__tests__/AssistSection.test.tsx +2 -2
  101. package/src/components/resource/panels/__tests__/AssistShell.test.tsx +9 -4
  102. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +1 -1
  103. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +26 -0
  104. package/src/features/auth/auth.css +10 -10
  105. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +58 -3
  106. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +18 -7
  107. package/src/styles/core/inputs.css +1 -1
  108. package/src/styles/features/compose.css +1 -1
  109. package/src/styles/features/resource-viewer.css +1 -1
  110. package/src/styles/features/static-pages.css +11 -11
  111. package/src/styles/motivations/motivation-comment.css +2 -2
  112. package/src/styles/motivations/motivation-reference.css +2 -2
  113. package/src/styles/patterns/panels-base.css +4 -4
  114. package/src/styles/utilities/motion.css +15 -15
  115. package/src/styles/variables.css +29 -1
  116. package/translations/ar.json +27 -5
  117. package/translations/bn.json +27 -5
  118. package/translations/cs.json +27 -5
  119. package/translations/da.json +27 -5
  120. package/translations/de.json +27 -5
  121. package/translations/el.json +27 -5
  122. package/translations/en.json +27 -5
  123. package/translations/es.json +27 -5
  124. package/translations/fa.json +27 -5
  125. package/translations/fi.json +27 -5
  126. package/translations/fr.json +27 -5
  127. package/translations/he.json +27 -5
  128. package/translations/hi.json +27 -5
  129. package/translations/id.json +27 -5
  130. package/translations/it.json +27 -5
  131. package/translations/ja.json +27 -5
  132. package/translations/ko.json +27 -5
  133. package/translations/ms.json +27 -5
  134. package/translations/nl.json +27 -5
  135. package/translations/no.json +27 -5
  136. package/translations/pl.json +27 -5
  137. package/translations/pt.json +27 -5
  138. package/translations/ro.json +27 -5
  139. package/translations/sv.json +27 -5
  140. package/translations/th.json +27 -5
  141. package/translations/tr.json +27 -5
  142. package/translations/uk.json +27 -5
  143. package/translations/vi.json +27 -5
  144. package/translations/zh.json +27 -5
  145. package/dist/PdfAnnotationCanvas.client-SU5KNT7G.css +0 -98
  146. package/dist/PdfAnnotationCanvas.client-SU5KNT7G.css.map +0 -1
  147. package/dist/PdfAnnotationCanvas.client-V7Q3BEEQ.js +0 -427
  148. package/dist/PdfAnnotationCanvas.client-V7Q3BEEQ.js.map +0 -1
  149. package/dist/ar-JRTKCMTF.js.map +0 -1
  150. package/dist/bn-LO6EDSCC.js.map +0 -1
  151. package/dist/chunk-4QMTEPQ3.js.map +0 -1
  152. package/dist/chunk-5Y5JDCKC.js.map +0 -1
  153. package/dist/cs-LCHHOR4O.js.map +0 -1
  154. package/dist/da-TARGPMJR.js.map +0 -1
  155. package/dist/de-SBH4QBPJ.js.map +0 -1
  156. package/dist/el-2D2OLGWB.js.map +0 -1
  157. package/dist/es-U6YK2LX6.js.map +0 -1
  158. package/dist/fa-S7SZE37M.js.map +0 -1
  159. package/dist/fi-CBL3NIXN.js.map +0 -1
  160. package/dist/fr-EABUHV6S.js.map +0 -1
  161. package/dist/he-THWD244Y.js.map +0 -1
  162. package/dist/hi-GHGFZJ44.js.map +0 -1
  163. package/dist/id-POZOUMDW.js.map +0 -1
  164. package/dist/it-KDYJADSH.js.map +0 -1
  165. package/dist/ja-Z5Z6ZF3P.js.map +0 -1
  166. package/dist/ko-7X3XUXRN.js.map +0 -1
  167. package/dist/ms-BTWEIW43.js.map +0 -1
  168. package/dist/nl-IS2CGF5I.js.map +0 -1
  169. package/dist/no-AAMEVA26.js.map +0 -1
  170. package/dist/pl-PVXUQYFT.js.map +0 -1
  171. package/dist/pt-3ITQ76TL.js.map +0 -1
  172. package/dist/ro-FRB4IIVS.js.map +0 -1
  173. package/dist/sv-DS3RO24M.js.map +0 -1
  174. package/dist/th-AX66MWWB.js.map +0 -1
  175. package/dist/tr-7KTB67TT.js.map +0 -1
  176. package/dist/uk-SMQS2MAQ.js.map +0 -1
  177. package/dist/vi-D3JXL2EZ.js.map +0 -1
  178. package/dist/zh-S754AWFS.js.map +0 -1
  179. /package/dist/{en-QFIEG3CA.js.map → en-3FCBW3YD.js.map} +0 -0
@@ -161,7 +161,7 @@
161
161
 
162
162
  .semiont-toolbar-icon-large {
163
163
  font-size: 1.875rem; /* 30px - was 2xl/1.5rem */
164
- font-weight: var(--semiont-font-weight-bold);
164
+ font-weight: var(--semiont-font-bold);
165
165
  }
166
166
 
167
167
  /* Dark mode for Toolbar - Fixed to respect data-theme attribute */
@@ -206,7 +206,7 @@
206
206
  color: var(--semiont-color-primary-700);
207
207
  border: 1px solid var(--semiont-color-primary-200);
208
208
  border-radius: var(--semiont-radius-base);
209
- font-size: var(--semiont-font-size-xs);
209
+ font-size: var(--semiont-text-xs);
210
210
  }
211
211
 
212
212
  /* Editing affordances (RESOURCE-TAGS-INLINE-EDITING) */
@@ -217,7 +217,7 @@
217
217
  border: none;
218
218
  background: none;
219
219
  color: inherit;
220
- font-size: var(--semiont-font-size-xs);
220
+ font-size: var(--semiont-text-xs);
221
221
  cursor: pointer;
222
222
  opacity: 0.7;
223
223
  }
@@ -279,8 +279,8 @@
279
279
  }
280
280
 
281
281
  .semiont-error-boundary-title {
282
- font-size: var(--semiont-font-size-2xl);
283
- font-weight: var(--semiont-font-weight-bold);
282
+ font-size: var(--semiont-text-2xl);
283
+ font-weight: var(--semiont-font-bold);
284
284
  color: var(--semiont-color-neutral-900);
285
285
  margin-bottom: var(--semiont-spacing-xs);
286
286
  }
@@ -290,30 +290,30 @@
290
290
  }
291
291
 
292
292
  .semiont-error-boundary-details {
293
- background-color: var(--semiont-color-danger-50);
294
- border: 1px solid var(--semiont-color-danger-200);
293
+ background-color: var(--semiont-color-red-50);
294
+ border: 1px solid var(--semiont-color-red-200);
295
295
  border-radius: var(--semiont-radius-md);
296
296
  padding: var(--semiont-spacing-md);
297
297
  }
298
298
 
299
299
  .semiont-error-boundary-details-title {
300
- font-size: var(--semiont-font-size-sm);
301
- font-weight: var(--semiont-font-weight-semibold);
302
- color: var(--semiont-color-danger-800);
300
+ font-size: var(--semiont-text-sm);
301
+ font-weight: var(--semiont-font-semibold);
302
+ color: var(--semiont-color-red-800);
303
303
  margin-bottom: calc(var(--semiont-spacing-xs) / 2);
304
304
  }
305
305
 
306
306
  .semiont-error-boundary-details-message {
307
- font-size: var(--semiont-font-size-xs);
308
- color: var(--semiont-color-danger-700);
307
+ font-size: var(--semiont-text-xs);
308
+ color: var(--semiont-color-red-700);
309
309
  font-family: monospace;
310
310
  word-break: break-all;
311
311
  }
312
312
 
313
313
  .semiont-error-boundary-stack {
314
314
  margin-top: var(--semiont-spacing-xs);
315
- font-size: var(--semiont-font-size-xs);
316
- color: var(--semiont-color-danger-600);
315
+ font-size: var(--semiont-text-xs);
316
+ color: var(--semiont-color-red-600);
317
317
  overflow-x: auto;
318
318
  }
319
319
 
@@ -326,8 +326,8 @@
326
326
  /* AsyncErrorBoundary Component */
327
327
  .semiont-async-error-boundary {
328
328
  padding: var(--semiont-spacing-md);
329
- background-color: var(--semiont-color-warning-50);
330
- border: 1px solid var(--semiont-color-warning-200);
329
+ background-color: var(--semiont-color-amber-50);
330
+ border: 1px solid var(--semiont-color-amber-200);
331
331
  border-radius: var(--semiont-radius-md);
332
332
  }
333
333
 
@@ -343,7 +343,7 @@
343
343
  .semiont-icon-warning {
344
344
  height: 1.25rem;
345
345
  width: 1.25rem;
346
- color: var(--semiont-color-warning-400);
346
+ color: var(--semiont-color-amber-400);
347
347
  }
348
348
 
349
349
  .semiont-async-error-details {
@@ -352,21 +352,21 @@
352
352
  }
353
353
 
354
354
  .semiont-async-error-title {
355
- font-size: var(--semiont-font-size-sm);
356
- font-weight: var(--semiont-font-weight-medium);
357
- color: var(--semiont-color-warning-800);
355
+ font-size: var(--semiont-text-sm);
356
+ font-weight: var(--semiont-font-medium);
357
+ color: var(--semiont-color-amber-800);
358
358
  }
359
359
 
360
360
  .semiont-async-error-message {
361
361
  margin-top: calc(var(--semiont-spacing-xs) / 2);
362
- font-size: var(--semiont-font-size-sm);
363
- color: var(--semiont-color-warning-700);
362
+ font-size: var(--semiont-text-sm);
363
+ color: var(--semiont-color-amber-700);
364
364
  }
365
365
 
366
366
  .semiont-async-error-retry {
367
367
  margin-top: var(--semiont-spacing-xs);
368
- font-size: var(--semiont-font-size-sm);
369
- color: var(--semiont-color-warning-600);
368
+ font-size: var(--semiont-text-sm);
369
+ color: var(--semiont-color-amber-600);
370
370
  text-decoration: underline;
371
371
  background: none;
372
372
  border: none;
@@ -375,7 +375,7 @@
375
375
  }
376
376
 
377
377
  .semiont-async-error-retry:hover {
378
- color: var(--semiont-color-warning-500);
378
+ color: var(--semiont-color-amber-500);
379
379
  }
380
380
 
381
381
  /* Dark mode for ErrorBoundary */
@@ -393,43 +393,43 @@
393
393
 
394
394
  [data-theme="dark"] .semiont-error-boundary-details {
395
395
  background-color: rgba(239, 68, 68, 0.2);
396
- border-color: var(--semiont-color-danger-800);
396
+ border-color: var(--semiont-color-red-800);
397
397
  }
398
398
 
399
399
  [data-theme="dark"] .semiont-error-boundary-details-title {
400
- color: var(--semiont-color-danger-300);
400
+ color: var(--semiont-color-red-300);
401
401
  }
402
402
 
403
403
  [data-theme="dark"] .semiont-error-boundary-details-message {
404
- color: var(--semiont-color-danger-400);
404
+ color: var(--semiont-color-red-400);
405
405
  }
406
406
 
407
407
  [data-theme="dark"] .semiont-error-boundary-stack {
408
- color: var(--semiont-color-danger-500);
408
+ color: var(--semiont-color-red-500);
409
409
  }
410
410
 
411
411
  [data-theme="dark"] .semiont-async-error-boundary {
412
412
  background-color: rgba(245, 158, 11, 0.2);
413
- border-color: var(--semiont-color-warning-800);
413
+ border-color: var(--semiont-color-amber-800);
414
414
  }
415
415
 
416
416
  [data-theme="dark"] .semiont-icon-warning {
417
- color: var(--semiont-color-warning-400);
417
+ color: var(--semiont-color-amber-400);
418
418
  }
419
419
 
420
420
  [data-theme="dark"] .semiont-async-error-title {
421
- color: var(--semiont-color-warning-300);
421
+ color: var(--semiont-color-amber-300);
422
422
  }
423
423
 
424
424
  [data-theme="dark"] .semiont-async-error-message {
425
- color: var(--semiont-color-warning-400);
425
+ color: var(--semiont-color-amber-400);
426
426
  }
427
427
 
428
428
  [data-theme="dark"] .semiont-async-error-retry {
429
- color: var(--semiont-color-warning-400);
429
+ color: var(--semiont-color-amber-400);
430
430
  }
431
431
 
432
432
  [data-theme="dark"] .semiont-async-error-retry:hover {
433
- color: var(--semiont-color-warning-300);
433
+ color: var(--semiont-color-amber-300);
434
434
  }
435
435
 
@@ -7,13 +7,19 @@
7
7
  * cancel/dismiss arrive as callbacks the caller wires (job.cancelRequest /
8
8
  * mark.dismissProgress). Feature blocks are data-presence-driven so each call
9
9
  * site keeps its current visuals by passing what it always had.
10
+ *
11
+ * i18n contract (ASSIST-SURFACE-WARTS Lane A): every string this component
12
+ * renders comes from `translations`. There are NO English fallbacks — a
13
+ * missing key must be a type error at the call site, not a silent English
14
+ * leak in a Japanese UI. Tests use key-echo strings ('tr.complete') so an
15
+ * assertion failing means "rendered something other than what was passed".
10
16
  */
11
17
  import { describe, it, expect, vi } from 'vitest';
12
18
  import { render, screen } from '@testing-library/react';
13
19
  import userEvent from '@testing-library/user-event';
14
20
  import '@testing-library/jest-dom';
15
21
  import type { components } from '@semiont/core';
16
- import { AssistProgress } from '../AssistProgress';
22
+ import { AssistProgress, type AssistProgressTranslations } from '../AssistProgress';
17
23
 
18
24
  type JobProgress = components['schemas']['JobProgress'];
19
25
 
@@ -21,13 +27,26 @@ const running = (over: Partial<JobProgress> = {}): JobProgress => ({
21
27
  stage: 'analyzing', percentage: 40, message: 'working on it', ...over,
22
28
  });
23
29
 
30
+ /** Every required key, echoing its own name; override per test. */
31
+ const T = (over: Partial<AssistProgressTranslations> = {}): AssistProgressTranslations => ({
32
+ cancel: 'tr.cancel',
33
+ inProgress: 'tr.inProgress',
34
+ complete: 'tr.complete',
35
+ failed: 'tr.failed',
36
+ close: 'tr.close',
37
+ paramsTitle: 'tr.paramsTitle',
38
+ processing: (label: string) => `tr.processing(${label})`,
39
+ ...over,
40
+ });
41
+
24
42
  describe('AssistProgress', () => {
25
43
  it('renders provider-free: message, params, and data hooks', () => {
26
44
  const { container } = render(
27
45
  <AssistProgress
28
46
  progress={running({ requestParams: [{ label: 'Density', value: '5' }] })}
29
47
  dataType="comment"
30
- />,
48
+ translations={T()}
49
+ />,
31
50
  );
32
51
  expect(screen.getByText('working on it')).toBeInTheDocument();
33
52
  expect(screen.getByText(/Density/)).toBeInTheDocument();
@@ -37,12 +56,24 @@ describe('AssistProgress', () => {
37
56
  expect(root).toHaveAttribute('data-status', 'analyzing');
38
57
  });
39
58
 
59
+ it('renders the request-parameters title from translations, never a literal', () => {
60
+ render(
61
+ <AssistProgress
62
+ progress={running({ requestParams: [{ label: 'Density', value: '5' }] })}
63
+ dataType="comment"
64
+ translations={T()}
65
+ />,
66
+ );
67
+ expect(screen.getByText('tr.paramsTitle')).toBeInTheDocument();
68
+ expect(screen.queryByText('Request Parameters:')).not.toBeInTheDocument();
69
+ });
70
+
40
71
  it('renders a title header only when given one', () => {
41
72
  const { rerender } = render(
42
- <AssistProgress progress={running()} dataType="reference" translations={{ title: 'Annotating Entity References' }} />,
73
+ <AssistProgress progress={running()} dataType="reference" translations={T({ title: 'Annotating Entity References' })} />,
43
74
  );
44
75
  expect(screen.getByText('Annotating Entity References')).toBeInTheDocument();
45
- rerender(<AssistProgress progress={running()} dataType="comment" />);
76
+ rerender(<AssistProgress progress={running()} dataType="comment" translations={T()} />);
46
77
  expect(screen.queryByText('Annotating Entity References')).not.toBeInTheDocument();
47
78
  });
48
79
 
@@ -50,7 +81,7 @@ describe('AssistProgress', () => {
50
81
  // Cancel lives in the title header — both flows that offer cancel
51
82
  // (reference detection, generation) render the titled profile.
52
83
  const onCancel = vi.fn();
53
- const tr = { title: 'Generating Resource', cancel: 'Cancel Job' };
84
+ const tr = T({ title: 'Generating Resource', cancel: 'Cancel Job' });
54
85
  const { rerender } = render(
55
86
  <AssistProgress progress={running()} dataType="generation" onCancel={onCancel} translations={tr} />,
56
87
  );
@@ -68,13 +99,20 @@ describe('AssistProgress', () => {
68
99
  expect(screen.queryByTitle('Cancel Job')).not.toBeInTheDocument();
69
100
  });
70
101
 
71
- it('stage branching: complete shows + complete copy, error shows + message', () => {
102
+ it('stage branching: both terminal stages prefer the job message, then the translated copy', () => {
103
+ // The job composes detail no static string can ("Created 14 highlights"),
104
+ // so it wins on BOTH terminal stages; the translated copy covers the
105
+ // required-but-empty message case (pinned below).
72
106
  const { rerender } = render(
73
- <AssistProgress progress={running({ stage: 'complete' })} dataType="reference" translations={{ complete: 'All done!' }} />,
107
+ <AssistProgress progress={running({ stage: 'complete' })} dataType="reference" translations={T({ complete: 'All done!' })} />,
108
+ );
109
+ expect(screen.getByText('working on it')).toBeInTheDocument();
110
+ rerender(
111
+ <AssistProgress progress={running({ stage: 'complete', message: '' })} dataType="reference" translations={T({ complete: 'All done!' })} />,
74
112
  );
75
113
  expect(screen.getByText('All done!')).toBeInTheDocument();
76
114
  rerender(
77
- <AssistProgress progress={running({ stage: 'error', message: 'it broke' })} dataType="reference" translations={{ failed: 'Failed' }} />,
115
+ <AssistProgress progress={running({ stage: 'error', message: 'it broke' })} dataType="reference" translations={T({ failed: 'Failed' })} />,
78
116
  );
79
117
  expect(screen.getByText('it broke')).toBeInTheDocument();
80
118
  });
@@ -83,7 +121,7 @@ describe('AssistProgress', () => {
83
121
  render(
84
122
  <AssistProgress
85
123
  progress={running({ completedEntityTypes: [{ entityType: 'Person', foundCount: 3 }] })}
86
- dataType="reference" translations={{ found: (n) => `Found ${n}` }}
124
+ dataType="reference" translations={T({ found: (n) => `Found ${n}` })}
87
125
  />,
88
126
  );
89
127
  expect(screen.getByText('Person:')).toBeInTheDocument();
@@ -94,55 +132,75 @@ describe('AssistProgress', () => {
94
132
  const { rerender } = render(
95
133
  <AssistProgress
96
134
  progress={running({ currentEntityType: 'Location' })}
97
- dataType="reference" translations={{ current: (l) => `Processing: ${l}` }}
135
+ dataType="reference" translations={T({ current: (l) => `Current: ${l}` })}
98
136
  />,
99
137
  );
100
- expect(screen.getByText('Processing: Location')).toBeInTheDocument();
138
+ expect(screen.getByText('Current: Location')).toBeInTheDocument();
101
139
  rerender(
102
140
  <AssistProgress
103
141
  progress={running({ currentCategory: 'Rule', processedCategories: 2, totalCategories: 5 })}
104
- dataType="tag" />,
142
+ dataType="tag" translations={T()}
143
+ />,
105
144
  );
106
145
  expect(screen.getByText(/Rule/)).toBeInTheDocument();
107
146
  expect(screen.getByText(/2\/5/)).toBeInTheDocument();
108
147
  });
109
148
 
149
+ it('falls back to the generic processing formatter — never the English literal', () => {
150
+ // The reference flow passes its own `current` wording; every other flow
151
+ // (tag categories, and any flow without one) uses `processing`. Neither
152
+ // path may render a hardcoded "Processing: ".
153
+ const { rerender } = render(
154
+ <AssistProgress progress={running({ currentEntityType: 'Location' })} dataType="reference" translations={T()} />,
155
+ );
156
+ expect(screen.getByText('tr.processing(Location)')).toBeInTheDocument();
157
+ rerender(
158
+ <AssistProgress progress={running({ currentCategory: 'Rule' })} dataType="tag" translations={T()} />,
159
+ );
160
+ expect(screen.getByText(/tr\.processing\(Rule\)/)).toBeInTheDocument();
161
+ expect(screen.queryByText(/^Processing: /)).not.toBeInTheDocument();
162
+ });
163
+
110
164
  it('renders the percentage bar only when opted in', () => {
111
165
  const { container, rerender } = render(
112
- <AssistProgress progress={running({ percentage: 40 })} dataType="tag" showPercentBar />,
166
+ <AssistProgress progress={running({ percentage: 40 })} dataType="tag" showPercentBar translations={T()} />,
113
167
  );
114
168
  const fill = container.querySelector('.semiont-progress-bar__fill');
115
169
  expect(fill).toBeInTheDocument();
116
170
  expect(fill).toHaveStyle({ width: '40%' });
117
171
  rerender(
118
- <AssistProgress progress={running({ percentage: 40 })} dataType="reference" />,
172
+ <AssistProgress progress={running({ percentage: 40 })} dataType="reference" translations={T()} />,
119
173
  );
120
174
  expect(container.querySelector('.semiont-progress-bar__fill')).not.toBeInTheDocument();
121
175
  });
122
176
 
123
- it('cancel and dismiss carry accessible names, with safe fallbacks when untranslated', () => {
124
- // ✕ / × glyphs alone are meaningless to screen readers; the controls must
125
- // have an accessible name even when a caller forgets the translations.
177
+ it('cancel and dismiss take their accessible names from translations', () => {
178
+ // ✕ / × glyphs alone are meaningless to screen readers, and an English
179
+ // fallback is meaningless to a non-English reader. The labels are
180
+ // required keys, so there is nothing to fall back TO.
126
181
  render(
127
182
  <AssistProgress progress={running()} dataType="generation"
128
183
  onCancel={vi.fn()} onDismiss={vi.fn()}
129
- translations={{ title: 'Generating' }} />,
184
+ translations={T({ title: 'Generating' })} />,
130
185
  );
131
- expect(screen.getByLabelText('Cancel')).toBeInTheDocument();
132
- expect(screen.getByLabelText('Dismiss')).toBeInTheDocument();
186
+ expect(screen.getByLabelText('tr.cancel')).toBeInTheDocument();
187
+ expect(screen.getByLabelText('tr.close')).toBeInTheDocument();
188
+ expect(screen.queryByLabelText('Cancel')).not.toBeInTheDocument();
189
+ expect(screen.queryByLabelText('Dismiss')).not.toBeInTheDocument();
133
190
  });
134
191
 
135
- it('terminal stages never render a blank status line', () => {
192
+ it('terminal stages never render a blank status line, and never an English one', () => {
136
193
  // JobProgress.message is required but can be '' — a terminal display with
137
- // no text at all is worse than a generic word.
194
+ // no text at all is worse than a generic word, but that word must be the
195
+ // caller's translated one.
138
196
  const { rerender } = render(
139
- <AssistProgress progress={running({ stage: 'complete', message: '' })} dataType="reference" />,
197
+ <AssistProgress progress={running({ stage: 'complete', message: '' })} dataType="reference" translations={T()} />,
140
198
  );
141
- expect(screen.getByText('Complete')).toBeInTheDocument();
199
+ expect(screen.getByText('tr.complete')).toBeInTheDocument();
142
200
  rerender(
143
- <AssistProgress progress={running({ stage: 'error', message: '' })} dataType="reference" />,
201
+ <AssistProgress progress={running({ stage: 'error', message: '' })} dataType="reference" translations={T()} />,
144
202
  );
145
- expect(screen.getByText('Failed')).toBeInTheDocument();
203
+ expect(screen.getByText('tr.failed')).toBeInTheDocument();
146
204
  });
147
205
 
148
206
  it('renders dismiss whenever the caller offers it (WHEN is the caller\'s policy)', async () => {
@@ -151,12 +209,12 @@ describe('AssistProgress', () => {
151
209
  // "callback present → affordance rendered".
152
210
  const onDismiss = vi.fn();
153
211
  const { rerender } = render(
154
- <AssistProgress progress={running()} dataType="highlight" translations={{ close: 'Close' }} />,
212
+ <AssistProgress progress={running()} dataType="highlight" translations={T({ close: 'Close' })} />,
155
213
  );
156
214
  expect(screen.queryByLabelText('Close')).not.toBeInTheDocument();
157
215
  rerender(
158
216
  <AssistProgress progress={running()} dataType="highlight"
159
- onDismiss={onDismiss} translations={{ close: 'Close' }} />,
217
+ onDismiss={onDismiss} translations={T({ close: 'Close' })} />,
160
218
  );
161
219
  await userEvent.click(screen.getByLabelText('Close'));
162
220
  expect(onDismiss).toHaveBeenCalledOnce();
@@ -45,7 +45,7 @@
45
45
  * or a position-fallback. The hover tooltip (added by
46
46
  * `getAnnotationDecorationMeta`) names the strategy. */
47
47
  .annotation-low-confidence {
48
- text-decoration: underline dotted var(--semiont-color-warning-500, #d97706);
48
+ text-decoration: underline dotted var(--semiont-color-amber-500);
49
49
  text-decoration-thickness: 2px;
50
50
  text-underline-offset: 2px;
51
51
  }
@@ -211,11 +211,11 @@
211
211
  }
212
212
 
213
213
  .semiont-search-modal__result-icon--entity {
214
- color: var(--semiont-color-success-500);
214
+ color: var(--semiont-color-green-500);
215
215
  }
216
216
 
217
217
  [data-theme="dark"] .semiont-search-modal__result-icon--entity {
218
- color: var(--semiont-color-success-400);
218
+ color: var(--semiont-color-green-400);
219
219
  }
220
220
 
221
221
  /* Result content */
@@ -244,13 +244,13 @@
244
244
  font-size: 0.75rem;
245
245
  font-weight: 500;
246
246
  border-radius: 9999px;
247
- background-color: var(--semiont-color-success-100);
248
- color: var(--semiont-color-success-700);
247
+ background-color: var(--semiont-color-green-100);
248
+ color: var(--semiont-color-green-700);
249
249
  }
250
250
 
251
251
  [data-theme="dark"] .semiont-search-modal__result-badge {
252
252
  background-color: rgba(34, 197, 94, 0.3);
253
- color: var(--semiont-color-success-300);
253
+ color: var(--semiont-color-green-300);
254
254
  }
255
255
 
256
256
  .semiont-search-modal__result-description {
@@ -20,14 +20,151 @@
20
20
 
21
21
  /* stylelint-disable semiont/accessibility -- Error indicator added by semantic-indicators.css */
22
22
  .semiont-pdf-annotation-canvas__error {
23
- color: var(--error-color);
23
+ color: var(--semiont-color-error);
24
24
  }
25
25
 
26
26
  [data-theme="dark"] .semiont-pdf-annotation-canvas__error {
27
- color: var(--error-color);
27
+ color: var(--semiont-color-error);
28
28
  }
29
29
  /* stylelint-enable semiont/accessibility */
30
30
 
31
+ /*
32
+ * The scrolling column (PDF-CONTINUOUS-SCROLL S1). Every page owns a slot so
33
+ * the scrollbar reflects the whole document; only pages near the viewport are
34
+ * mounted, and an unmounted slot holds its space with an estimated height set
35
+ * inline. `max-height` is what makes this a scroll container at all — without
36
+ * it the column grows to its content and the page scrolls instead, which
37
+ * still works but defeats the windowing.
38
+ */
39
+ /*
40
+ * Viewport = the pages plus their strip, side by side. The strip LEADS — it
41
+ * hugs the content's leading edge, where a page rail conventionally lives
42
+ * (Preview, Acrobat), so the reader's eye finds it before the page.
43
+ */
44
+ .semiont-pdf-annotation-canvas__viewport {
45
+ display: flex;
46
+ gap: 0.5rem;
47
+ align-items: stretch;
48
+ }
49
+
50
+ .semiont-pdf-annotation-canvas__viewport[data-axis="horizontal"] {
51
+ flex-direction: column;
52
+ }
53
+
54
+ .semiont-pdf-annotation-canvas__column {
55
+ display: flex;
56
+ flex: 1 1 auto;
57
+ gap: 1rem;
58
+ align-items: center;
59
+ }
60
+
61
+ /*
62
+ * Both blocks below key off ONE attribute, so the strip can never end up
63
+ * running across the direction the document scrolls.
64
+ */
65
+ /*
66
+ * The column does NOT scroll. The pane the viewer sits in already does
67
+ * (`flex: 1; min-height: 0`), and giving the column a viewport-relative
68
+ * height of its own produced a scroller inside a scroller — two bars along
69
+ * one axis, the inner one redundant with the page rail beside it.
70
+ */
71
+ .semiont-pdf-annotation-canvas__column[data-axis="vertical"] {
72
+ flex-direction: column;
73
+ }
74
+
75
+ .semiont-pdf-annotation-canvas__column[data-axis="horizontal"] {
76
+ flex-direction: row;
77
+ }
78
+
79
+ .semiont-pdf-annotation-canvas__slot {
80
+ display: flex;
81
+ justify-content: center;
82
+ width: 100%;
83
+ }
84
+
85
+ /*
86
+ * The page strip: the document's shape in miniature. Rectangles only — no
87
+ * rasterization — so a 400-page scan costs 400 empty divs, not 400 renders.
88
+ */
89
+ /*
90
+ * The strip scrolls — a 70-page document cannot fit its numbered rectangles in
91
+ * one screen — but it must not show a scrollbar. Two scrollbars side by side
92
+ * read as two controls for one thing, and the strip's own moves the strip
93
+ * rather than the document, so it looks broken. Position is carried by the
94
+ * highlighted page, and the strip follows the reader on its own.
95
+ */
96
+ .semiont-pdf-annotation-canvas__strip {
97
+ display: flex;
98
+ flex: 0 0 auto;
99
+ gap: 2px;
100
+ padding: 0.5rem;
101
+ scrollbar-width: none; /* standard; Firefox + recent Chromium */
102
+ }
103
+
104
+ /* WebKit/Safari, which has no `scrollbar-width` support to rely on. */
105
+ .semiont-pdf-annotation-canvas__strip::-webkit-scrollbar {
106
+ display: none;
107
+ }
108
+
109
+ /*
110
+ * The rail hugs the leading edge of the content and stays put while the
111
+ * document scrolls past it — `sticky` rather than a fixed-height box, so it
112
+ * follows the reader without claiming a second scroll region in the layout.
113
+ */
114
+ .semiont-pdf-annotation-canvas__strip[data-axis="vertical"] {
115
+ position: sticky;
116
+ top: 0;
117
+ flex-direction: column;
118
+ align-self: flex-start;
119
+ align-items: center;
120
+ max-height: 100vh;
121
+ overflow-y: auto;
122
+ border-right: 1px solid var(--semiont-border-secondary);
123
+ }
124
+
125
+ .semiont-pdf-annotation-canvas__strip[data-axis="horizontal"] {
126
+ position: sticky;
127
+ top: 0;
128
+ flex-direction: row;
129
+ align-items: flex-end;
130
+ max-width: 100%;
131
+ overflow-x: auto;
132
+ border-bottom: 1px solid var(--semiont-border-secondary);
133
+ }
134
+
135
+ .semiont-pdf-annotation-canvas__strip-page {
136
+ display: flex;
137
+ flex: 0 0 auto;
138
+ align-items: center;
139
+ justify-content: center;
140
+ width: 30px;
141
+ padding: 0;
142
+ border: 1px solid var(--semiont-border-primary);
143
+ border-radius: 1px;
144
+ background: var(--semiont-bg-secondary);
145
+ color: var(--semiont-text-tertiary);
146
+ font-size: var(--semiont-text-2xs);
147
+ line-height: 1;
148
+ cursor: pointer;
149
+ }
150
+
151
+ .semiont-pdf-annotation-canvas__strip-page:hover {
152
+ background: var(--semiont-bg-hover);
153
+ }
154
+
155
+ /* The reader's position, legible at a glance across a long strip. */
156
+ .semiont-pdf-annotation-canvas__strip-page[aria-current="page"] {
157
+ border-color: var(--semiont-color-primary-600);
158
+ background: var(--semiont-color-primary-500);
159
+ color: var(--semiont-color-neutral-0);
160
+ font-weight: var(--semiont-font-semibold);
161
+ }
162
+
163
+ .semiont-pdf-annotation-canvas__strip-page:focus-visible {
164
+ outline: 2px solid var(--semiont-focus-ring);
165
+ outline-offset: 1px;
166
+ }
167
+
31
168
  .semiont-pdf-annotation-canvas__container {
32
169
  position: relative;
33
170
  display: inline-block;
@@ -83,12 +220,12 @@
83
220
  justify-content: center;
84
221
  gap: 1rem;
85
222
  padding: 1rem;
86
- border-top: 1px solid var(--border-color);
223
+ border-top: 1px solid var(--semiont-border-primary);
87
224
  }
88
225
 
89
226
  .semiont-pdf-annotation-canvas__button {
90
227
  padding: 0.5rem 1rem;
91
- border: 1px solid var(--border-color);
228
+ border: 1px solid var(--semiont-border-primary);
92
229
  border-radius: 4px;
93
230
  background: transparent;
94
231
  cursor: pointer;
@@ -101,11 +238,11 @@
101
238
  }
102
239
 
103
240
  .semiont-pdf-annotation-canvas__button:hover:not(:disabled) {
104
- background-color: var(--hover-color, rgba(0, 0, 0, 0.05));
241
+ background-color: var(--semiont-bg-hover);
105
242
  }
106
243
 
107
244
  .semiont-pdf-annotation-canvas__button:focus-visible {
108
- outline: 2px solid var(--focus-color, #0066cc);
245
+ outline: 2px solid var(--semiont-focus-ring);
109
246
  outline-offset: 2px;
110
247
  }
111
248