@semiont/react-ui 0.5.25 → 0.5.27

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 (267) hide show
  1. package/README.md +28 -4
  2. package/dist/PdfAnnotationCanvas.client-EVUPTUWZ.js +677 -0
  3. package/dist/PdfAnnotationCanvas.client-EVUPTUWZ.js.map +1 -0
  4. package/dist/PdfAnnotationCanvas.client-TGDPPGXJ.css +185 -0
  5. package/dist/PdfAnnotationCanvas.client-TGDPPGXJ.css.map +1 -0
  6. package/dist/{ar-CULRHLNG.js → ar-OU3DQGC6.js} +65 -7
  7. package/dist/ar-OU3DQGC6.js.map +1 -0
  8. package/dist/{bn-HYR7CEL2.js → bn-PHWCR6AZ.js} +65 -7
  9. package/dist/bn-PHWCR6AZ.js.map +1 -0
  10. package/dist/chunk-5VDTWRXJ.js +287 -0
  11. package/dist/chunk-5VDTWRXJ.js.map +1 -0
  12. package/dist/{chunk-TG2XQEC4.js → chunk-BTUG3VJJ.js} +18 -298
  13. package/dist/chunk-BTUG3VJJ.js.map +1 -0
  14. package/dist/{chunk-NH6L2GIL.js → chunk-PCRRBU7H.js} +65 -7
  15. package/dist/chunk-PCRRBU7H.js.map +1 -0
  16. package/dist/{cs-YBRWY7NH.js → cs-NEXYZ62I.js} +65 -7
  17. package/dist/cs-NEXYZ62I.js.map +1 -0
  18. package/dist/{da-PHDEOBV3.js → da-FAT43YCU.js} +65 -7
  19. package/dist/da-FAT43YCU.js.map +1 -0
  20. package/dist/{de-O4UCCVLJ.js → de-KRQ3WBCU.js} +65 -7
  21. package/dist/de-KRQ3WBCU.js.map +1 -0
  22. package/dist/{el-MVGO6WZV.js → el-Q4IU3EKC.js} +65 -7
  23. package/dist/el-Q4IU3EKC.js.map +1 -0
  24. package/dist/{en-2KQILFKQ.js → en-2KADE7N4.js} +6 -2
  25. package/dist/{es-USSLXQY7.js → es-2IO3R2MK.js} +65 -7
  26. package/dist/es-2IO3R2MK.js.map +1 -0
  27. package/dist/{fa-WMAREFWY.js → fa-Q5GOQFXX.js} +65 -7
  28. package/dist/fa-Q5GOQFXX.js.map +1 -0
  29. package/dist/{fi-B3NBNOTS.js → fi-R6NMRWBE.js} +65 -7
  30. package/dist/fi-R6NMRWBE.js.map +1 -0
  31. package/dist/{fr-TXYK5T5W.js → fr-QY4D5E6J.js} +65 -7
  32. package/dist/fr-QY4D5E6J.js.map +1 -0
  33. package/dist/{he-JCJACW5D.js → he-X6Y77XHY.js} +65 -7
  34. package/dist/he-X6Y77XHY.js.map +1 -0
  35. package/dist/{hi-VOQLZNJU.js → hi-XL7IOEQM.js} +65 -7
  36. package/dist/hi-XL7IOEQM.js.map +1 -0
  37. package/dist/{id-BFN5RZNZ.js → id-QZ4ZZS67.js} +65 -7
  38. package/dist/id-QZ4ZZS67.js.map +1 -0
  39. package/dist/index.css +241 -508
  40. package/dist/index.css.map +1 -1
  41. package/dist/index.d.ts +155 -54
  42. package/dist/index.js +1815 -1642
  43. package/dist/index.js.map +1 -1
  44. package/dist/integrations/css-modules-helper.js +1 -3
  45. package/dist/integrations/css-modules-helper.js.map +1 -1
  46. package/dist/{it-TWWDHKKM.js → it-ME2Q7OB6.js} +65 -7
  47. package/dist/it-ME2Q7OB6.js.map +1 -0
  48. package/dist/{ja-YRQLEBFM.js → ja-HX7QASRI.js} +65 -7
  49. package/dist/ja-HX7QASRI.js.map +1 -0
  50. package/dist/{ko-PG6B3CRB.js → ko-EQO5AHAV.js} +65 -7
  51. package/dist/ko-EQO5AHAV.js.map +1 -0
  52. package/dist/{ms-7EZECVKM.js → ms-UILFSF3I.js} +65 -7
  53. package/dist/ms-UILFSF3I.js.map +1 -0
  54. package/dist/{nl-HP753YAP.js → nl-NNI45PMK.js} +65 -7
  55. package/dist/nl-NNI45PMK.js.map +1 -0
  56. package/dist/{no-JUD5WX3N.js → no-MAL5F6P6.js} +65 -7
  57. package/dist/no-MAL5F6P6.js.map +1 -0
  58. package/dist/{pl-RVUL4CBM.js → pl-4RMY6JAJ.js} +65 -7
  59. package/dist/pl-4RMY6JAJ.js.map +1 -0
  60. package/dist/{pt-RPOVD4QM.js → pt-VQH2X3QQ.js} +65 -7
  61. package/dist/pt-VQH2X3QQ.js.map +1 -0
  62. package/dist/{ro-DMYWQTRD.js → ro-XXCQO2OG.js} +65 -7
  63. package/dist/ro-XXCQO2OG.js.map +1 -0
  64. package/dist/{sv-DMZ4FGXD.js → sv-7Z6EJ6U4.js} +65 -7
  65. package/dist/sv-7Z6EJ6U4.js.map +1 -0
  66. package/dist/test-utils.css +1 -1
  67. package/dist/test-utils.css.map +1 -1
  68. package/dist/test-utils.d.ts +1 -1
  69. package/dist/test-utils.js +5 -3
  70. package/dist/test-utils.js.map +1 -1
  71. package/dist/{th-4HVBJBQ2.js → th-L3DXXYVQ.js} +65 -7
  72. package/dist/th-L3DXXYVQ.js.map +1 -0
  73. package/dist/{tr-6BDPU5ML.js → tr-P3DFVLFJ.js} +65 -7
  74. package/dist/tr-P3DFVLFJ.js.map +1 -0
  75. package/dist/{uk-TBZBT2TL.js → uk-7C5D3PXS.js} +65 -7
  76. package/dist/uk-7C5D3PXS.js.map +1 -0
  77. package/dist/{vi-UMKZKZL3.js → vi-ZLTLEO7P.js} +65 -7
  78. package/dist/vi-ZLTLEO7P.js.map +1 -0
  79. package/dist/{zh-YBJWJJYB.js → zh-5FHVWDIN.js} +65 -7
  80. package/dist/zh-5FHVWDIN.js.map +1 -0
  81. package/package.json +7 -7
  82. package/src/components/AssistProgress.tsx +144 -134
  83. package/src/components/{EntityFoundLog.tsx → ItemFoundLog.tsx} +12 -7
  84. package/src/components/ProtectedErrorBoundary.css +3 -1
  85. package/src/components/StatusDisplay.css +35 -35
  86. package/src/components/Toast.css +3 -1
  87. package/src/components/__tests__/AssistProgress.test.tsx +216 -148
  88. package/src/components/annotation/annotation-entries.css +0 -4
  89. package/src/components/annotation/annotations.css +1 -1
  90. package/src/components/annotation/references.css +0 -4
  91. package/src/components/image-annotation/AnnotationOverlay.tsx +6 -6
  92. package/src/components/image-annotation/SvgDrawingCanvas.tsx +1 -1
  93. package/src/components/image-annotation/__tests__/AnnotationOverlay.click.test.tsx +7 -9
  94. package/src/components/image-annotation/__tests__/SvgDrawingCanvas.click.test.tsx +1 -2
  95. package/src/components/layout/Header.css +0 -4
  96. package/src/components/layout/LeftSidebar.css +0 -4
  97. package/src/components/modals/ConfigureGenerationStep.tsx +109 -48
  98. package/src/components/modals/ConfigureSearchStep.tsx +23 -40
  99. package/src/components/modals/ReferenceWizardModal.tsx +55 -16
  100. package/src/components/modals/ResourceGenerateModal.tsx +24 -8
  101. package/src/components/modals/ResourceSearchModal.tsx +1 -1
  102. package/src/components/modals/SearchModal.css +5 -17
  103. package/src/components/modals/SearchModal.tsx +1 -1
  104. package/src/components/modals/SearchResultsStep.tsx +2 -20
  105. package/src/components/modals/WizardFooter.tsx +63 -0
  106. package/src/components/modals/__tests__/ConfigureGenerationStep.test.tsx +255 -0
  107. package/src/components/modals/__tests__/ConfigureSearchStep.test.tsx +72 -0
  108. package/src/components/modals/__tests__/ReferenceWizardModal.test.tsx +256 -0
  109. package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +1 -0
  110. package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +6 -2
  111. package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +6 -2
  112. package/src/components/modals/__tests__/WizardFooter.test.tsx +235 -0
  113. package/src/components/modals/modals.css +16 -42
  114. package/src/components/navigation/NavigationTabs.css +0 -20
  115. package/src/components/pdf-annotation/PdfAnnotationCanvas.css +162 -6
  116. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +623 -232
  117. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +764 -7
  118. package/src/components/pdf-annotation/__tests__/estimate-slot-height.test.ts +53 -0
  119. package/src/components/pdf-annotation/estimate-slot-height.ts +31 -0
  120. package/src/components/resource/AnnotateView.tsx +16 -5
  121. package/src/components/resource/AnnotationHistory.tsx +2 -0
  122. package/src/components/resource/BrowseView.tsx +15 -4
  123. package/src/components/resource/ResourceViewer.tsx +23 -24
  124. package/src/components/resource/__tests__/AnnotateView.focus.test.tsx +50 -0
  125. package/src/components/resource/__tests__/AnnotationHistory.test.tsx +2 -0
  126. package/src/components/resource/__tests__/BrowseView.test.tsx +29 -3
  127. package/src/components/resource/__tests__/ResourceViewer.embeddable.test.tsx +47 -3
  128. package/src/components/resource/__tests__/browse-renderers.dispatch.test.tsx +33 -0
  129. package/src/components/resource/annotate-renderers.tsx +10 -1
  130. package/src/components/resource/browse-renderers.tsx +10 -1
  131. package/src/components/resource/event-formatting.ts +0 -3
  132. package/src/components/resource/panels/AssessmentEntry.tsx +1 -1
  133. package/src/components/resource/panels/AssessmentPanel.css +5 -6
  134. package/src/components/resource/panels/AssistSection.css +68 -271
  135. package/src/components/resource/panels/AssistSection.tsx +4 -4
  136. package/src/components/resource/panels/AssistShell.tsx +12 -4
  137. package/src/components/resource/panels/CollaborationPanel.css +47 -22
  138. package/src/components/resource/panels/CollaborationPanel.tsx +93 -2
  139. package/src/components/resource/panels/CommentEntry.tsx +1 -1
  140. package/src/components/resource/panels/CommentsPanel.css +0 -16
  141. package/src/components/resource/panels/HighlightEntry.tsx +1 -1
  142. package/src/components/resource/panels/HighlightPanel.css +0 -7
  143. package/src/components/resource/panels/JsonLdPanel.css +0 -4
  144. package/src/components/resource/panels/ReferenceEntry.tsx +1 -1
  145. package/src/components/resource/panels/ReferencesPanel.css +0 -12
  146. package/src/components/resource/panels/ReferencesPanel.tsx +12 -20
  147. package/src/components/resource/panels/ResourceInfoPanel.css +0 -32
  148. package/src/components/resource/panels/ResourceInfoPanel.tsx +1 -1
  149. package/src/components/resource/panels/StatisticsPanel.css +0 -20
  150. package/src/components/resource/panels/TagEntry.tsx +1 -1
  151. package/src/components/resource/panels/TaggingPanel.css +0 -4
  152. package/src/components/resource/panels/TaggingPanel.tsx +2 -10
  153. package/src/components/resource/panels/UnifiedAnnotationsPanel.css +0 -4
  154. package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +0 -1
  155. package/src/components/resource/panels/__tests__/AssistSection.test.tsx +48 -50
  156. package/src/components/resource/panels/__tests__/AssistShell.test.tsx +11 -6
  157. package/src/components/resource/panels/__tests__/CollaborationPanel.test.tsx +89 -0
  158. package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +1 -2
  159. package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +0 -1
  160. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +44 -36
  161. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +0 -1
  162. package/src/components/resource/panels/__tests__/ReferencesPanel.headless.test.tsx +1 -1
  163. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +34 -53
  164. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +37 -1
  165. package/src/components/resource/panels/__tests__/TagEntry.test.tsx +0 -1
  166. package/src/components/settings/SettingsPanel.css +0 -4
  167. package/src/features/auth/auth.css +9 -33
  168. package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +25 -1
  169. package/src/features/resource-compose/state/compose-page-state-unit.ts +11 -2
  170. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +55 -0
  171. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +29 -0
  172. package/src/features/resource-discovery/state/__tests__/discover-state-unit.test.ts +53 -3
  173. package/src/features/resource-discovery/state/discover-state-unit.ts +29 -8
  174. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +26 -0
  175. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +42 -21
  176. package/src/features/resource-viewer/hooks/__tests__/useResourceViewedReport.test.tsx +63 -0
  177. package/src/features/resource-viewer/hooks/useResourceViewedReport.ts +33 -0
  178. package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +2 -2
  179. package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +1 -1
  180. package/src/styles/core/buttons.css +3 -1
  181. package/src/styles/core/forms.css +0 -21
  182. package/src/styles/core/indicators.css +0 -4
  183. package/src/styles/core/inputs.css +1 -5
  184. package/src/styles/core/progress.css +2 -34
  185. package/src/styles/core/sliders.css +0 -4
  186. package/src/styles/core/toggles.css +0 -4
  187. package/src/styles/features/compose.css +1 -1
  188. package/src/styles/features/resource-discovery.css +13 -0
  189. package/src/styles/features/resource-viewer.css +1 -21
  190. package/src/styles/features/static-pages.css +11 -11
  191. package/src/styles/motivations/motivation-assessment.css +2 -2
  192. package/src/styles/motivations/motivation-comment.css +2 -2
  193. package/src/styles/motivations/motivation-reference.css +2 -2
  194. package/src/styles/panels/history-panel.css +0 -40
  195. package/src/styles/panels/user-panel.css +0 -3
  196. package/src/styles/patterns/panel-helpers.css +0 -27
  197. package/src/styles/patterns/panel-sections.css +0 -59
  198. package/src/styles/patterns/panels-base.css +4 -36
  199. package/src/styles/utilities/motion.css +15 -15
  200. package/src/styles/variables.css +29 -1
  201. package/translations/ar.json +59 -5
  202. package/translations/bn.json +59 -5
  203. package/translations/cs.json +59 -5
  204. package/translations/da.json +59 -5
  205. package/translations/de.json +59 -5
  206. package/translations/el.json +59 -5
  207. package/translations/en.json +59 -5
  208. package/translations/es.json +59 -5
  209. package/translations/fa.json +59 -5
  210. package/translations/fi.json +59 -5
  211. package/translations/fr.json +59 -5
  212. package/translations/he.json +59 -5
  213. package/translations/hi.json +59 -5
  214. package/translations/id.json +59 -5
  215. package/translations/it.json +59 -5
  216. package/translations/ja.json +59 -5
  217. package/translations/ko.json +59 -5
  218. package/translations/ms.json +59 -5
  219. package/translations/nl.json +59 -5
  220. package/translations/no.json +59 -5
  221. package/translations/pl.json +59 -5
  222. package/translations/pt.json +59 -5
  223. package/translations/ro.json +59 -5
  224. package/translations/sv.json +59 -5
  225. package/translations/th.json +59 -5
  226. package/translations/tr.json +59 -5
  227. package/translations/uk.json +59 -5
  228. package/translations/vi.json +59 -5
  229. package/translations/zh.json +59 -5
  230. package/dist/PdfAnnotationCanvas.client-EHQWF6BR.js +0 -462
  231. package/dist/PdfAnnotationCanvas.client-EHQWF6BR.js.map +0 -1
  232. package/dist/PdfAnnotationCanvas.client-SU5KNT7G.css +0 -98
  233. package/dist/PdfAnnotationCanvas.client-SU5KNT7G.css.map +0 -1
  234. package/dist/ar-CULRHLNG.js.map +0 -1
  235. package/dist/bn-HYR7CEL2.js.map +0 -1
  236. package/dist/chunk-NH6L2GIL.js.map +0 -1
  237. package/dist/chunk-TG2XQEC4.js.map +0 -1
  238. package/dist/chunk-XUDKYAVC.js +0 -21
  239. package/dist/chunk-XUDKYAVC.js.map +0 -1
  240. package/dist/cs-YBRWY7NH.js.map +0 -1
  241. package/dist/da-PHDEOBV3.js.map +0 -1
  242. package/dist/de-O4UCCVLJ.js.map +0 -1
  243. package/dist/el-MVGO6WZV.js.map +0 -1
  244. package/dist/es-USSLXQY7.js.map +0 -1
  245. package/dist/fa-WMAREFWY.js.map +0 -1
  246. package/dist/fi-B3NBNOTS.js.map +0 -1
  247. package/dist/fr-TXYK5T5W.js.map +0 -1
  248. package/dist/he-JCJACW5D.js.map +0 -1
  249. package/dist/hi-VOQLZNJU.js.map +0 -1
  250. package/dist/id-BFN5RZNZ.js.map +0 -1
  251. package/dist/it-TWWDHKKM.js.map +0 -1
  252. package/dist/ja-YRQLEBFM.js.map +0 -1
  253. package/dist/ko-PG6B3CRB.js.map +0 -1
  254. package/dist/ms-7EZECVKM.js.map +0 -1
  255. package/dist/nl-HP753YAP.js.map +0 -1
  256. package/dist/no-JUD5WX3N.js.map +0 -1
  257. package/dist/pl-RVUL4CBM.js.map +0 -1
  258. package/dist/pt-RPOVD4QM.js.map +0 -1
  259. package/dist/ro-DMYWQTRD.js.map +0 -1
  260. package/dist/sv-DMZ4FGXD.js.map +0 -1
  261. package/dist/th-4HVBJBQ2.js.map +0 -1
  262. package/dist/tr-6BDPU5ML.js.map +0 -1
  263. package/dist/uk-TBZBT2TL.js.map +0 -1
  264. package/dist/vi-UMKZKZL3.js.map +0 -1
  265. package/dist/zh-YBJWJJYB.js.map +0 -1
  266. package/src/integrations/tailwind-plugin.cjs +0 -224
  267. /package/dist/{en-2KQILFKQ.js.map → en-2KADE7N4.js.map} +0 -0
@@ -0,0 +1,53 @@
1
+ /**
2
+ * PDF-CONTINUOUS-SCROLL S1b.
3
+ *
4
+ * The scroll column reserves space for unmounted pages. If the reservation is
5
+ * wrong, every mount and unmount changes the column's height and the scrollbar
6
+ * jumps — which is what S1 shipped, by reserving the raster height rather than
7
+ * the displayed one.
8
+ *
9
+ * These pins hold the estimate to the CSS the image is actually under:
10
+ * `max-width: 100%; height: auto`.
11
+ */
12
+ import { describe, it, expect } from 'vitest';
13
+ import { estimateSlotHeight } from '../estimate-slot-height';
14
+
15
+ // US Letter at scale 1.5: 918 × 1188 raster, aspect 792/612.
16
+ const ASPECT = 792 / 612;
17
+ const RASTER_W = 918;
18
+
19
+ describe('estimateSlotHeight', () => {
20
+ it('scales to the column when the column is narrower than the raster', () => {
21
+ // The common case: `max-width: 100%` shrinks the image to the column.
22
+ expect(estimateSlotHeight(600, RASTER_W, ASPECT)).toBe(Math.round(600 * ASPECT));
23
+ });
24
+
25
+ it('never upscales past the raster — max-width shrinks only', () => {
26
+ // A column wider than the raster leaves the image at natural size, so the
27
+ // height stops growing. Reserving columnWidth * aspect here would over-
28
+ // reserve and leave a gap under every page.
29
+ expect(estimateSlotHeight(1600, RASTER_W, ASPECT)).toBe(Math.round(RASTER_W * ASPECT));
30
+ });
31
+
32
+ it('is exactly the raster height when the column matches the raster', () => {
33
+ expect(estimateSlotHeight(RASTER_W, RASTER_W, ASPECT)).toBe(Math.round(RASTER_W * ASPECT));
34
+ });
35
+
36
+ it('does NOT use the raster height as a width-independent constant', () => {
37
+ // The S1 defect, stated as a pin: the raster is 1188px tall, but in a
38
+ // 600px column the page occupies ~776px. Reserving 1188 is what made the
39
+ // column's height lurch on every mount.
40
+ const rasterHeight = Math.round(RASTER_W * ASPECT); // 1188
41
+ expect(estimateSlotHeight(600, RASTER_W, ASPECT)).not.toBe(rasterHeight);
42
+ expect(estimateSlotHeight(600, RASTER_W, ASPECT)).toBeLessThan(rasterHeight);
43
+ });
44
+
45
+ it('answers null until every measurement is in, rather than guessing', () => {
46
+ // A wrong reservation is worse than none: an unsized slot is stable, a
47
+ // mis-sized one moves when the page lands.
48
+ expect(estimateSlotHeight(null, RASTER_W, ASPECT)).toBeNull();
49
+ expect(estimateSlotHeight(600, null, ASPECT)).toBeNull();
50
+ expect(estimateSlotHeight(600, RASTER_W, null)).toBeNull();
51
+ expect(estimateSlotHeight(0, RASTER_W, ASPECT)).toBeNull();
52
+ });
53
+ });
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The height a page will occupy once rendered, computed BEFORE rendering it.
3
+ *
4
+ * The scrolling column reserves space for pages it has not mounted. If the
5
+ * reserved height differs from the height the page actually takes, every mount
6
+ * and unmount changes the column's total height and the scrollbar jumps under
7
+ * the reader's cursor — the defect S1 shipped by reserving the *raster* height
8
+ * (`getViewport({ scale }).height`) instead of the *displayed* one.
9
+ *
10
+ * This models the CSS the image is under exactly:
11
+ *
12
+ * .semiont-pdf-annotation-canvas__image { max-width: 100%; height: auto; }
13
+ *
14
+ * `max-width` shrinks but never upscales, so the displayed width is
15
+ * `min(columnWidth, rasterWidth)` and the height follows the page's aspect
16
+ * ratio. Aspect is scale-invariant, so it can come from any viewport.
17
+ *
18
+ * @param columnWidth Measured inner width of the scroll column, in CSS px.
19
+ * @param rasterWidth Width of the rendered raster, in px (`viewport(scale).width`).
20
+ * @param aspect Page height ÷ width, from any viewport scale.
21
+ * @returns Reserved height in CSS px, or null when a measurement is not in yet.
22
+ */
23
+ export function estimateSlotHeight(
24
+ columnWidth: number | null,
25
+ rasterWidth: number | null,
26
+ aspect: number | null,
27
+ ): number | null {
28
+ if (!columnWidth || !rasterWidth || !aspect) return null;
29
+ if (columnWidth <= 0 || rasterWidth <= 0 || aspect <= 0) return null;
30
+ return Math.round(Math.min(columnWidth, rasterWidth) * aspect);
31
+ }
@@ -53,7 +53,7 @@ interface Props {
53
53
  *
54
54
  * @emits mark:requested - User requested to create annotation. Payload: { selector: Selector | Selector[], motivation: SelectionMotivation }
55
55
  * @subscribes beckon:hover - Annotation hovered. Payload: { annotationId: string | null }
56
- * @subscribes beckon:focus - Scroll to and highlight annotation. Payload: { annotationId: string }
56
+ * @subscribes beckon:focus - Scroll to and highlight annotation, unless `resourceId` names a different resource (D7). Payload: { annotationId: string, resourceId?: string }
57
57
  */
58
58
  export function AnnotateView({
59
59
  content,
@@ -111,10 +111,21 @@ export function AnnotateView({
111
111
  // serves, so the behaviour no longer depends on which view mode is active.
112
112
  // The `scrollToAnnotationId` prop path (uiState → renderer) stays as the
113
113
  // host-facing capability it is; this is the in-app producer's route.
114
- const handleAnnotationFocus = useCallback(({ annotationId }: { annotationId?: string | null }) => {
115
- if (!containerRef.current) return;
116
- scrollAnnotationIntoView(annotationId ?? null, containerRef.current, { pulse: true });
117
- }, []);
114
+ // `resourceId` is a GUARD, not navigation (GUIDED-TOUR D7): it names the
115
+ // resource this focus applies to, and a viewer showing a different one
116
+ // ignores it. Absent means unscoped — the in-app emitters (history panel,
117
+ // annotation list) omit it because they are already scoped to the open
118
+ // resource, so treating absence as "not mine" would silence all of them.
119
+ // Focus never navigates; driving the Browser to a resource is
120
+ // `browse:resource-open`'s job.
121
+ const handleAnnotationFocus = useCallback(
122
+ ({ annotationId, resourceId }: { annotationId?: string | null; resourceId?: string }) => {
123
+ if (resourceId && resourceId !== resourceUri) return;
124
+ if (!containerRef.current) return;
125
+ scrollAnnotationIntoView(annotationId ?? null, containerRef.current, { pulse: true });
126
+ },
127
+ [resourceUri],
128
+ );
118
129
 
119
130
  // Annotation hover (session-scoped). Toolbar preference changes flow through
120
131
  // props/callbacks, not the bus (TOOLBAR-PREFS-AS-PROPS).
@@ -51,6 +51,8 @@ export function AnnotationHistory({
51
51
 
52
52
  // Sort events by oldest first (most recent at bottom)
53
53
  // Filter out job events - they're represented by mark:body-updated events instead
54
+ // `job:progress` is no longer a persisted event type, but logs written before it
55
+ // was retired still hold them; naming it here keeps those out of the history.
54
56
  const events: StoredEventLike[] = eventsData
55
57
  .filter((e) => {
56
58
  return e.type !== 'job:started' && e.type !== 'job:progress' && e.type !== 'job:completed';
@@ -155,7 +155,7 @@ export const BrowseView = memo(function BrowseView({
155
155
  if (annotation) {
156
156
  // The emission site owns the geometry: the clicked span's viewport
157
157
  // rect rides the event so hosts can anchor popovers (A1).
158
- session.client.browse.click(annotation.id, annotation.motivation, annotationElement.getBoundingClientRect());
158
+ session.client.browse.click(annotation.id, annotationElement.getBoundingClientRect());
159
159
  }
160
160
  }
161
161
  };
@@ -263,9 +263,20 @@ export const BrowseView = memo(function BrowseView({
263
263
  scrollToAnnotation(annotationId);
264
264
  }, [scrollToAnnotation]);
265
265
 
266
- const handleAnnotationFocus = useCallback(({ annotationId }: { annotationId?: string | null }) => {
267
- scrollToAnnotation(annotationId ?? null, true);
268
- }, [scrollToAnnotation]);
266
+ // `resourceId` is a GUARD, not navigation (GUIDED-TOUR D7): it names the
267
+ // resource this focus applies to, and a viewer showing a different one
268
+ // ignores it. Absent means unscoped — the in-app emitters (history panel,
269
+ // annotation list) omit it because they are already scoped to the open
270
+ // resource, so treating absence as "not mine" would silence all of them.
271
+ // Focus never navigates; driving the Browser to a resource is
272
+ // `browse:resource-open`'s job.
273
+ const handleAnnotationFocus = useCallback(
274
+ ({ annotationId, resourceId }: { annotationId?: string | null; resourceId?: string }) => {
275
+ if (resourceId && resourceId !== resourceUri) return;
276
+ scrollToAnnotation(annotationId ?? null, true);
277
+ },
278
+ [scrollToAnnotation, resourceUri],
279
+ );
269
280
 
270
281
  useSessionEventSubscriptions(session, {
271
282
  'beckon:hover': handleAnnotationHover,
@@ -8,7 +8,7 @@ import { BrowseView, type ReferenceHover } from './BrowseView';
8
8
  import type { BrowseMediaRenderers } from './browse-renderers';
9
9
  import { PopupContainer } from '../annotation-popups/SharedPopupElements';
10
10
  import { JsonLdView } from '../annotation-popups/JsonLdView';
11
- import type { Annotation, AnnotationId, ResourceDescriptor as SemiontResource, components, EventMap, AnchorRect } from '@semiont/core';
11
+ import type { Annotation, AnnotationId, ResourceDescriptor as SemiontResource, EventMap, AnchorRect } from '@semiont/core';
12
12
  import { getExactText, getTargetSelector, getPrimaryMediaType, isHighlight, isAssessment, isReference, isComment, isTag, getBodySource } from '@semiont/core';
13
13
  import type { SemiontSession } from '@semiont/sdk';
14
14
  import { useSessionEventSubscriptions } from '../../hooks/useSessionEventSubscriptions';
@@ -102,7 +102,10 @@ interface Props {
102
102
  * panel opening invokes the host's `onOpenPanel` callback — the host owns
103
103
  * the panel and any `panel:open` emission.
104
104
  *
105
- * @subscribes browse:click - User clicked on annotation. Payload: { annotationId: string, motivation, anchorRect? }
105
+ * @subscribes browse:click - An annotation was clicked locally, or driven over
106
+ * the wire by another participant. Payload: { annotationId: string, anchorRect? };
107
+ * the motivation is derived from the annotation the id names, and an id this
108
+ * viewer has not loaded is a no-op.
106
109
  */
107
110
  export function ResourceViewer({
108
111
  resource,
@@ -270,38 +273,34 @@ export function ResourceViewer({
270
273
  }, [annotateMode, selectedClick, onOpenResource]);
271
274
 
272
275
  // Annotation click coordinator - handles panel opening and scrolling
273
- const handleAnnotationClickEvent = useCallback(({ annotationId, motivation, anchorRect }: {
276
+ const handleAnnotationClickEvent = useCallback(({ annotationId, anchorRect }: {
274
277
  annotationId: string;
275
- motivation: components['schemas']['Motivation'];
276
278
  anchorRect?: AnchorRect;
277
279
  }) => {
278
- // Find the annotation metadata
279
- const metadata = Object.values(ANNOTATORS).find(a => a.matchesAnnotation({ motivation } as Annotation));
280
-
281
- if (!metadata?.hasSidePanel) {
282
- // Annotation doesn't have a side panel - let handleAnnotationClick handle it
283
- const allAnnotations = [...highlights, ...references, ...assessments, ...comments, ...tags];
284
- const annotation = allAnnotations.find(a => a.id === annotationId);
285
- if (annotation) {
286
- handleAnnotationClick(annotation);
287
- }
288
- return;
289
- }
280
+ // Resolve the annotation FIRST. The id is the whole address, and the
281
+ // motivation is derived from the annotation it names rather than carried
282
+ // beside it (TOUR-CLICK D2) — one fact, one encoding. Resolving first also
283
+ // means a click naming an annotation this viewer has not loaded (a remote
284
+ // drive that arrived while the participant was elsewhere) finds nothing
285
+ // and does nothing, which is why the channel needs no resourceId guard.
286
+ const allAnnotations = [...highlights, ...references, ...assessments, ...comments, ...tags];
287
+ const annotation = allAnnotations.find(a => a.id === annotationId);
288
+ if (!annotation) return;
289
+
290
+ // The real annotation, not a `{ motivation } as Annotation` stand-in.
291
+ const metadata = Object.values(ANNOTATORS).find(a => a.matchesAnnotation(annotation));
290
292
 
291
- if (selectedClick !== 'detail') {
292
- // Only open panels in detail mode - for other modes, let handleAnnotationClick handle it
293
- const allAnnotations = [...highlights, ...references, ...assessments, ...comments, ...tags];
294
- const annotation = allAnnotations.find(a => a.id === annotationId);
295
- if (annotation) {
296
- handleAnnotationClick(annotation);
297
- }
293
+ // No side panel, or a click mode other than detail: this is a plain
294
+ // annotation click, not a panel-opening one.
295
+ if (!metadata?.hasSidePanel || selectedClick !== 'detail') {
296
+ handleAnnotationClick(annotation);
298
297
  return;
299
298
  }
300
299
 
301
300
  // All annotations open the unified annotations panel — the host owns the panel.
302
301
  // The panel internally switches tabs based on the motivation → tab mapping in UnifiedAnnotationsPanel.
303
302
  // View geometry passes through untouched: the emitter owned it, the host anchors with it.
304
- onOpenPanel?.({ panel: 'annotations', scrollToAnnotationId: annotationId, motivation, ...(anchorRect ? { anchorRect } : {}) });
303
+ onOpenPanel?.({ panel: 'annotations', scrollToAnnotationId: annotationId, motivation: annotation.motivation, ...(anchorRect ? { anchorRect } : {}) });
305
304
  }, [highlights, references, assessments, comments, tags, handleAnnotationClick, selectedClick, onOpenPanel]);
306
305
 
307
306
  // Single subscription call per file (see scripts/compliance/audit-hooks-ordering.ts).
@@ -56,3 +56,53 @@ describe('AnnotateView — beckon:focus scrolls the content', () => {
56
56
  expect(scrollSpy.mock.calls[0]?.[0]).toBe('ann-7');
57
57
  });
58
58
  });
59
+
60
+ // ─────────────────────────────────────────────────────────────────────
61
+ // GUIDED-TOUR P6 (D7) — `resourceId` is a GUARD, not navigation.
62
+ //
63
+ // The schema now says so out loud: "it names the resource this focus applies
64
+ // to, and a viewer currently showing a different resource ignores the event".
65
+ // Without the comparison the field was decorative — a guide beckoning a
66
+ // reference in doc B scrolled every participant's doc A.
67
+ // ─────────────────────────────────────────────────────────────────────
68
+ describe('AnnotateView — beckon:focus is guarded by resourceId (P6/D7)', () => {
69
+ beforeEach(() => { scrollSpy.mockClear(); });
70
+
71
+ const renderAt = (resourceUri: string) => {
72
+ const { session, client } = createTestSemiontWrapper();
73
+ render(
74
+ <AnnotateView
75
+ content="hello world"
76
+ mimeType="text/plain"
77
+ resourceUri={resourceUri}
78
+ annotations={emptyAnnotations}
79
+ uiState={uiState}
80
+ session={session}
81
+ annotateMode
82
+ />,
83
+ );
84
+ return client;
85
+ };
86
+
87
+ it('ignores a focus aimed at a DIFFERENT resource', () => {
88
+ const client = renderAt('res-1');
89
+ client.bus.get('beckon:focus').next({ annotationId: 'ann-7', resourceId: 'res-2' });
90
+ expect(scrollSpy).not.toHaveBeenCalled();
91
+ });
92
+
93
+ it('still scrolls when the resource matches', () => {
94
+ const client = renderAt('res-1');
95
+ client.bus.get('beckon:focus').next({ annotationId: 'ann-7', resourceId: 'res-1' });
96
+ expect(scrollSpy).toHaveBeenCalledTimes(1);
97
+ });
98
+
99
+ it('still scrolls when the event names no resource at all', () => {
100
+ // `resourceId` is optional in the schema (`required: []`), and the in-app
101
+ // emitters — the history panel, the annotation list — omit it because they
102
+ // are already scoped to the open resource. A guard that treated absence as
103
+ // "not mine" would break every one of them.
104
+ const client = renderAt('res-1');
105
+ client.bus.get('beckon:focus').next({ annotationId: 'ann-7' });
106
+ expect(scrollSpy).toHaveBeenCalledTimes(1);
107
+ });
108
+ });
@@ -173,6 +173,8 @@ describe('AnnotationHistory', () => {
173
173
  });
174
174
 
175
175
  it('filters out job events', () => {
176
+ // e3 is `job:progress`, a retired event type. Logs written before it was
177
+ // retired still hold them, so the filter must keep excluding it.
176
178
  const events = [
177
179
  makeStoredEvent('e1', 'mark:added', 1),
178
180
  makeStoredEvent('e2', 'job:started', 2),
@@ -427,7 +427,7 @@ describe('BrowseView Component', () => {
427
427
  { id: 'comment-1', type: 'comment', motivation: 'commenting' },
428
428
  ] as const;
429
429
 
430
- for (const { id, type, motivation } of clickCases) {
430
+ for (const { id, type } of clickCases) {
431
431
  const el = document.createElement('span');
432
432
  el.setAttribute('data-annotation-id', id);
433
433
  el.setAttribute('data-annotation-type', type);
@@ -439,8 +439,7 @@ describe('BrowseView Component', () => {
439
439
  await waitFor(() => {
440
440
  expect(tracker.events.some(e =>
441
441
  e.event === 'browse:click' &&
442
- e.payload?.annotationId === id &&
443
- e.payload?.motivation === motivation
442
+ e.payload?.annotationId === id
444
443
  )).toBe(true);
445
444
  });
446
445
  }
@@ -653,3 +652,30 @@ describe('BrowseView Component', () => {
653
652
  });
654
653
  });
655
654
  });
655
+
656
+ // ─────────────────────────────────────────────────────────────────────
657
+ // GUIDED-TOUR P6 (D7) — the same guard BrowseView's sibling carries.
658
+ // BrowseView subscribed to `beckon:focus` first, so it is the one that has
659
+ // been scrolling on other people's resources the longest.
660
+ // ─────────────────────────────────────────────────────────────────────
661
+ describe('BrowseView — beckon:focus is guarded by resourceId (P6/D7)', () => {
662
+ it('ignores a focus aimed at a different resource, honours one that matches', () => {
663
+ const { session, client } = createTestSemiontWrapper();
664
+ const { container } = render(
665
+ <BrowseView
666
+ content="hello world"
667
+ mimeType="text/plain"
668
+ resourceUri="res-1"
669
+ annotations={{ highlights: [], references: [], assessments: [], comments: [], tags: [] }}
670
+ session={session}
671
+ annotateMode={false}
672
+ />,
673
+ );
674
+ expect(container).toBeTruthy();
675
+
676
+ // Neither call may throw; the assertion that matters is the guard's
677
+ // existence, pinned in AnnotateView where the scroll helper is mockable.
678
+ client.bus.get('beckon:focus').next({ annotationId: 'ann-7', resourceId: 'res-2' });
679
+ client.bus.get('beckon:focus').next({ annotationId: 'ann-7', resourceId: 'res-1' });
680
+ });
681
+ });
@@ -15,7 +15,8 @@ import { describe, it, expect, vi } from 'vitest';
15
15
  import { render, screen, act, waitFor } from '@testing-library/react';
16
16
  import '@testing-library/jest-dom';
17
17
  import type { SemiontSession } from '@semiont/sdk';
18
- import type { ResourceDescriptor as SemiontResource, ResourceId } from '@semiont/core';
18
+ import type { Annotation, ResourceDescriptor as SemiontResource, ResourceId } from '@semiont/core';
19
+ import { annotationId } from '@semiont/core';
19
20
  import { createTestSemiontWrapper } from '../../../test-utils';
20
21
  import { ResourceViewer } from '../ResourceViewer';
21
22
 
@@ -46,6 +47,23 @@ const resource: SemiontResource & { content: string } = {
46
47
 
47
48
  const annotations = { highlights: [], references: [], assessments: [], comments: [], tags: [] };
48
49
 
50
+ /**
51
+ * A highlight the viewer has actually loaded. Required since TOUR-CLICK D2: the
52
+ * click handler resolves the annotation by id and derives the motivation from
53
+ * it, so a click for an id absent from this collection is a no-op. Before that,
54
+ * the wire carried the motivation and the viewer never looked — which is why
55
+ * the anchorRect test below used to pass against an empty collection.
56
+ */
57
+ const loadedHighlight: Annotation = {
58
+ '@context': 'http://www.w3.org/ns/anno.jsonld',
59
+ type: 'Annotation',
60
+ id: annotationId('ann-1'),
61
+ motivation: 'highlighting',
62
+ target: { source: 'res-1', selector: { type: 'TextPositionSelector', start: 0, end: 10 } },
63
+ };
64
+
65
+ const annotationsWithHighlight = { ...annotations, highlights: [loadedHighlight] };
66
+
49
67
  describe('ResourceViewer — embeddable (bring-your-own-session, no providers)', () => {
50
68
  // GREEN: the whole browse-mode subtree (ResourceViewer → BrowseView →
51
69
  // AnnotateToolbar) renders provider-free from a bare session.
@@ -73,7 +91,7 @@ describe('ResourceViewer — embeddable (bring-your-own-session, no providers)',
73
91
  <ResourceViewer
74
92
  session={session}
75
93
  resource={resource}
76
- annotations={annotations}
94
+ annotations={annotationsWithHighlight}
77
95
  onOpenResource={vi.fn()}
78
96
  onOpenPanel={onOpenPanel}
79
97
  />,
@@ -86,7 +104,6 @@ describe('ResourceViewer — embeddable (bring-your-own-session, no providers)',
86
104
  act(() => {
87
105
  eventBus.get('browse:click').next({
88
106
  annotationId: 'ann-1',
89
- motivation: 'highlighting',
90
107
  anchorRect,
91
108
  });
92
109
  });
@@ -95,8 +112,35 @@ describe('ResourceViewer — embeddable (bring-your-own-session, no providers)',
95
112
  expect(onOpenPanel).toHaveBeenCalledWith(expect.objectContaining({
96
113
  panel: 'annotations',
97
114
  scrollToAnnotationId: 'ann-1',
115
+ // Derived from the annotation the id names, never carried on the wire.
116
+ motivation: 'highlighting',
98
117
  anchorRect: expect.objectContaining({ left: 5, width: 7 }),
99
118
  }));
100
119
  });
101
120
  });
121
+
122
+ it('ignores a browse:click naming an annotation this viewer has not loaded', async () => {
123
+ // A remote drive can arrive while the participant is looking at something
124
+ // else. Resolving the annotation FIRST makes that a no-op by construction,
125
+ // which is why the channel carries no resourceId guard (TOUR-CLICK D3).
126
+ const { session, eventBus } = createTestSemiontWrapper();
127
+ const onOpenPanel = vi.fn();
128
+
129
+ render(
130
+ <ResourceViewer
131
+ session={session}
132
+ resource={resource}
133
+ annotations={annotationsWithHighlight}
134
+ onOpenResource={vi.fn()}
135
+ onOpenPanel={onOpenPanel}
136
+ />,
137
+ );
138
+
139
+ act(() => {
140
+ eventBus.get('browse:click').next({ annotationId: 'ann-not-here' });
141
+ });
142
+
143
+ await new Promise((r) => setTimeout(r, 20));
144
+ expect(onOpenPanel).not.toHaveBeenCalled();
145
+ });
102
146
  });
@@ -109,5 +109,38 @@ describe('browse-renderers — annotation + session forwarding (dispatch contrac
109
109
  expect(props.existingAnnotations).toEqual([shapeAnnotation()]); // pinned — worked before
110
110
  expect(props.drawingMode).toBeNull();
111
111
  expect(props.session).toBe(session); // NEW — was a session-less no-op
112
+ // PDF-CONTINUOUS-SCROLL S1: browse asks for the scrolling column.
113
+ expect(props.pageLayout).toBe('scroll');
114
+ });
115
+
116
+ // S2. Annotate is the mode people actually work in
117
+ // (.plans/PDF-CONTINUOUS-SCROLL.md D3, second correction), so it gets the
118
+ // column too — leaving it on Previous/Next made the feature invisible to
119
+ // its primary audience.
120
+ it('the annotate registry asks for the column as well, with the live tool', async () => {
121
+ const session = fakeSession();
122
+ const { AnnotateView } = await import('../AnnotateView');
123
+ const { container } = render(
124
+ <AnnotateView
125
+ content="blob:pdf-url"
126
+ mimeType="application/pdf"
127
+ resourceUri="res-1"
128
+ annotations={emptyAnnotations}
129
+ uiState={{
130
+ selectedMotivation: 'highlighting',
131
+ selectedClick: 'detail',
132
+ selectedShape: 'rectangle',
133
+ hoveredAnnotationId: null,
134
+ scrollToAnnotationId: null,
135
+ }}
136
+ annotateMode
137
+ session={session}
138
+ />,
139
+ );
140
+
141
+ await within(container).findByText('pdf-canvas-mock');
142
+ const props = captured.pdf.at(-1)!;
143
+ expect(props.pageLayout).toBe('scroll');
144
+ expect(props.drawingMode).toBe('rectangle'); // the tool still reaches the pages
112
145
  });
113
146
  });
@@ -7,10 +7,18 @@ import { SvgDrawingCanvas } from '../image-annotation/SvgDrawingCanvas';
7
7
  import { CodeMirrorRenderer } from '../CodeMirrorRenderer';
8
8
  import { segmentTextWithAnnotations } from '../../lib/text-segmentation';
9
9
  import type { SelectionMotivation, ShapeType } from '../annotation/AnnotateToolbar';
10
+ import { useTranslations } from '../../contexts/TranslationContext';
10
11
 
11
12
  // Lazy-load the PDF component to avoid SSR issues with browser PDF.js loading.
12
13
  // Kept lazy here deliberately: hoisting it to a static import would make it
13
14
  // eager again and reintroduce the SSR problem this indirection exists for.
15
+ /** The lazy chunk's fallback needs its own component: a hook cannot run
16
+ * inside a `fallback` prop expression. */
17
+ function PdfViewerLoading() {
18
+ const t = useTranslations('PdfViewer');
19
+ return <>{t('viewerLoading')}</>;
20
+ }
21
+
14
22
  const PdfAnnotationCanvas = lazy(() =>
15
23
  import('../pdf-annotation/PdfAnnotationCanvas.client').then((mod) => ({ default: mod.PdfAnnotationCanvas })),
16
24
  );
@@ -127,7 +135,7 @@ export function PdfAnnotateRenderer({
127
135
  }: AnnotateMediaRendererProps) {
128
136
  if (!content) return null;
129
137
  return (
130
- <Suspense fallback={<div className="semiont-annotate-view__loading">Loading PDF viewer...</div>}>
138
+ <Suspense fallback={<div className="semiont-annotate-view__loading"><PdfViewerLoading /></div>}>
131
139
  <PdfAnnotationCanvas
132
140
  pdfUrl={content}
133
141
  resourceUri={resourceUri}
@@ -137,6 +145,7 @@ export function PdfAnnotateRenderer({
137
145
  session={session}
138
146
  hoveredAnnotationId={hoveredAnnotationId || null}
139
147
  hoverDelayMs={hoverDelayMs}
148
+ pageLayout="scroll"
140
149
  />
141
150
  </Suspense>
142
151
  );
@@ -6,8 +6,16 @@ import remarkGfm from 'remark-gfm';
6
6
  import type { Annotation } from '@semiont/core';
7
7
  import type { SemiontSession } from '@semiont/sdk';
8
8
  import { SvgDrawingCanvas } from '../image-annotation/SvgDrawingCanvas';
9
+ import { useTranslations } from '../../contexts/TranslationContext';
9
10
 
10
11
  // Lazy-load the PDF component to avoid SSR issues with browser PDF.js loading.
12
+ /** The lazy chunk's fallback needs its own component: a hook cannot run
13
+ * inside a `fallback` prop expression. */
14
+ function PdfViewerLoading() {
15
+ const t = useTranslations('PdfViewer');
16
+ return <>{t('viewerLoading')}</>;
17
+ }
18
+
11
19
  const PdfAnnotationCanvas = lazy(() => import('../pdf-annotation/PdfAnnotationCanvas.client').then(mod => ({ default: mod.PdfAnnotationCanvas })));
12
20
 
13
21
  /**
@@ -60,7 +68,7 @@ export function ImageBrowseRenderer({ content, resourceUri, annotations, session
60
68
 
61
69
  export function PdfBrowseRenderer({ content, resourceUri, annotations, session }: MediaRendererProps) {
62
70
  return (
63
- <Suspense fallback={<div className="semiont-browse-view__loading">Loading PDF viewer...</div>}>
71
+ <Suspense fallback={<div className="semiont-browse-view__loading"><PdfViewerLoading /></div>}>
64
72
  <PdfAnnotationCanvas
65
73
  pdfUrl={content}
66
74
  resourceUri={resourceUri}
@@ -68,6 +76,7 @@ export function PdfBrowseRenderer({ content, resourceUri, annotations, session }
68
76
  drawingMode={null}
69
77
  selectedMotivation={null}
70
78
  session={session}
79
+ pageLayout="scroll"
71
80
  />
72
81
  </Suspense>
73
82
  );
@@ -81,7 +81,6 @@ export function formatEventType(type: PersistedEventType, t: TranslateFn, payloa
81
81
 
82
82
  case 'job:completed':
83
83
  case 'job:started':
84
- case 'job:progress':
85
84
  case 'job:failed':
86
85
  return t('jobEvent');
87
86
 
@@ -128,7 +127,6 @@ export function getEventEmoji(type: PersistedEventType, payload?: any): string {
128
127
  case 'job:completed':
129
128
  return '🔗'; // Link emoji for linked document creation
130
129
  case 'job:started':
131
- case 'job:progress':
132
130
  return '⚙️'; // Gear for job processing
133
131
  case 'job:failed':
134
132
  return '❌'; // X mark for failed jobs
@@ -276,7 +274,6 @@ export function getEventDisplayContent(
276
274
  }
277
275
 
278
276
  case 'job:started':
279
- case 'job:progress':
280
277
  case 'job:failed':
281
278
  case 'yield:representation-added':
282
279
  case 'yield:representation-removed':
@@ -88,7 +88,7 @@ export function AssessmentEntry({
88
88
  data-type="assessment"
89
89
  data-focused={isFocused ? 'true' : 'false'}
90
90
  onClick={() => {
91
- session?.client.browse.click(assessment.id, assessment.motivation);
91
+ session?.client.browse.click(assessment.id);
92
92
  }}
93
93
  {...hoverProps}
94
94
  >
@@ -30,10 +30,6 @@
30
30
  letter-spacing: 0.05em;
31
31
  }
32
32
 
33
- [data-theme="dark"] .semiont-assessment-panel__label {
34
- color: var(--semiont-text-secondary);
35
- }
36
-
37
33
  .semiont-assessment-panel__rating {
38
34
  display: flex;
39
35
  gap: var(--semiont-panel-padding-xs);
@@ -46,8 +42,11 @@
46
42
  transition: color 0.2s ease;
47
43
  }
48
44
 
45
+ /* An UNRATED star must recede. gray-300 is near-white: on a dark panel the
46
+ empty stars outshone the filled ones, inverting the reading. Dark takes a
47
+ dim grey; the filled star keeps the warning hue. */
49
48
  [data-theme="dark"] .semiont-assessment-panel__star {
50
- color: var(--semiont-color-gray-300);
49
+ color: var(--semiont-color-gray-600);
51
50
  }
52
51
 
53
52
  .semiont-assessment-panel__star:hover {
@@ -59,7 +58,7 @@
59
58
  }
60
59
 
61
60
  [data-theme="dark"] .semiont-assessment-panel__star--filled {
62
- color: var(--semiont-color-warning);
61
+ color: var(--semiont-color-warning-light);
63
62
  }
64
63
 
65
64
  .semiont-assessment-panel__textarea {