@prenta/admin 0.92.0 → 0.94.0

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 (425) hide show
  1. package/CHANGELOG.md +663 -0
  2. package/design-system/tokens.css +173 -71
  3. package/design-system/tokens.json +244 -63
  4. package/design-system/tokens.scss +203 -98
  5. package/design-system/tokens.tailwind.cjs +93 -38
  6. package/dist/AdminRoot.d.ts +27 -6
  7. package/dist/AdminRoot.d.ts.map +1 -1
  8. package/dist/AdminRoot.js.map +1 -1
  9. package/dist/__tests__/components/breakpoint-controls.render.test.d.ts +2 -0
  10. package/dist/__tests__/components/breakpoint-controls.render.test.d.ts.map +1 -0
  11. package/dist/__tests__/components/breakpoint-controls.render.test.js +51 -0
  12. package/dist/__tests__/components/breakpoint-controls.render.test.js.map +1 -0
  13. package/dist/__tests__/components/canvas-edit-tip.render.test.d.ts +2 -0
  14. package/dist/__tests__/components/canvas-edit-tip.render.test.d.ts.map +1 -0
  15. package/dist/__tests__/components/canvas-edit-tip.render.test.js +37 -0
  16. package/dist/__tests__/components/canvas-edit-tip.render.test.js.map +1 -0
  17. package/dist/__tests__/components/editor-rail.render.test.d.ts +2 -0
  18. package/dist/__tests__/components/editor-rail.render.test.d.ts.map +1 -0
  19. package/dist/__tests__/components/editor-rail.render.test.js +138 -0
  20. package/dist/__tests__/components/editor-rail.render.test.js.map +1 -0
  21. package/dist/__tests__/components/media-block.render.test.d.ts +2 -0
  22. package/dist/__tests__/components/media-block.render.test.d.ts.map +1 -0
  23. package/dist/__tests__/components/media-block.render.test.js +108 -0
  24. package/dist/__tests__/components/media-block.render.test.js.map +1 -0
  25. package/dist/__tests__/components/page-audit-panel.render.test.d.ts +2 -0
  26. package/dist/__tests__/components/page-audit-panel.render.test.d.ts.map +1 -0
  27. package/dist/__tests__/components/page-audit-panel.render.test.js +59 -0
  28. package/dist/__tests__/components/page-audit-panel.render.test.js.map +1 -0
  29. package/dist/__tests__/components/reflow-meter.render.test.d.ts +2 -0
  30. package/dist/__tests__/components/reflow-meter.render.test.d.ts.map +1 -0
  31. package/dist/__tests__/components/reflow-meter.render.test.js +94 -0
  32. package/dist/__tests__/components/reflow-meter.render.test.js.map +1 -0
  33. package/dist/__tests__/components/seo-char-count.test.js +24 -6
  34. package/dist/__tests__/components/seo-char-count.test.js.map +1 -1
  35. package/dist/__tests__/components/serp-preview.render.test.d.ts +2 -0
  36. package/dist/__tests__/components/serp-preview.render.test.d.ts.map +1 -0
  37. package/dist/__tests__/components/serp-preview.render.test.js +25 -0
  38. package/dist/__tests__/components/serp-preview.render.test.js.map +1 -0
  39. package/dist/__tests__/lib/breakpoints.test.d.ts +2 -0
  40. package/dist/__tests__/lib/breakpoints.test.d.ts.map +1 -0
  41. package/dist/__tests__/lib/breakpoints.test.js +67 -0
  42. package/dist/__tests__/lib/breakpoints.test.js.map +1 -0
  43. package/dist/__tests__/lib/canvas-edit-chrome.test.d.ts +2 -0
  44. package/dist/__tests__/lib/canvas-edit-chrome.test.d.ts.map +1 -0
  45. package/dist/__tests__/lib/canvas-edit-chrome.test.js +44 -0
  46. package/dist/__tests__/lib/canvas-edit-chrome.test.js.map +1 -0
  47. package/dist/__tests__/lib/media-format.test.d.ts +2 -0
  48. package/dist/__tests__/lib/media-format.test.d.ts.map +1 -0
  49. package/dist/__tests__/lib/media-format.test.js +70 -0
  50. package/dist/__tests__/lib/media-format.test.js.map +1 -0
  51. package/dist/__tests__/lib/page-audit-input.test.d.ts +2 -0
  52. package/dist/__tests__/lib/page-audit-input.test.d.ts.map +1 -0
  53. package/dist/__tests__/lib/page-audit-input.test.js +122 -0
  54. package/dist/__tests__/lib/page-audit-input.test.js.map +1 -0
  55. package/dist/__tests__/lib/page-editor-service.test.js +2 -0
  56. package/dist/__tests__/lib/page-editor-service.test.js.map +1 -1
  57. package/dist/__tests__/lib/page-redirect.test.d.ts +2 -0
  58. package/dist/__tests__/lib/page-redirect.test.d.ts.map +1 -0
  59. package/dist/__tests__/lib/page-redirect.test.js +26 -0
  60. package/dist/__tests__/lib/page-redirect.test.js.map +1 -0
  61. package/dist/__tests__/lib/rail-width.test.d.ts +2 -0
  62. package/dist/__tests__/lib/rail-width.test.d.ts.map +1 -0
  63. package/dist/__tests__/lib/rail-width.test.js +69 -0
  64. package/dist/__tests__/lib/rail-width.test.js.map +1 -0
  65. package/dist/__tests__/lib/reflow-baseline.test.d.ts +2 -0
  66. package/dist/__tests__/lib/reflow-baseline.test.d.ts.map +1 -0
  67. package/dist/__tests__/lib/reflow-baseline.test.js +56 -0
  68. package/dist/__tests__/lib/reflow-baseline.test.js.map +1 -0
  69. package/dist/__tests__/lib/reflow-probe.test.d.ts +2 -0
  70. package/dist/__tests__/lib/reflow-probe.test.d.ts.map +1 -0
  71. package/dist/__tests__/lib/reflow-probe.test.js +73 -0
  72. package/dist/__tests__/lib/reflow-probe.test.js.map +1 -0
  73. package/dist/__tests__/lib/version-diff.test.d.ts +2 -0
  74. package/dist/__tests__/lib/version-diff.test.d.ts.map +1 -0
  75. package/dist/__tests__/lib/version-diff.test.js +230 -0
  76. package/dist/__tests__/lib/version-diff.test.js.map +1 -0
  77. package/dist/__tests__/lib/version-history.test.d.ts +2 -0
  78. package/dist/__tests__/lib/version-history.test.d.ts.map +1 -0
  79. package/dist/__tests__/lib/version-history.test.js +125 -0
  80. package/dist/__tests__/lib/version-history.test.js.map +1 -0
  81. package/dist/__tests__/styles/canvas-container-queries.test.d.ts +2 -0
  82. package/dist/__tests__/styles/canvas-container-queries.test.d.ts.map +1 -0
  83. package/dist/__tests__/styles/canvas-container-queries.test.js +87 -0
  84. package/dist/__tests__/styles/canvas-container-queries.test.js.map +1 -0
  85. package/dist/__tests__/styles/packaging.test.d.ts +2 -0
  86. package/dist/__tests__/styles/packaging.test.d.ts.map +1 -0
  87. package/dist/__tests__/styles/packaging.test.js +31 -0
  88. package/dist/__tests__/styles/packaging.test.js.map +1 -0
  89. package/dist/__tests__/styles/responsive-layer.test.d.ts +2 -0
  90. package/dist/__tests__/styles/responsive-layer.test.d.ts.map +1 -0
  91. package/dist/__tests__/styles/responsive-layer.test.js +127 -0
  92. package/dist/__tests__/styles/responsive-layer.test.js.map +1 -0
  93. package/dist/__tests__/styles/tokens-drift.test.d.ts +2 -0
  94. package/dist/__tests__/styles/tokens-drift.test.d.ts.map +1 -0
  95. package/dist/__tests__/styles/tokens-drift.test.js +92 -0
  96. package/dist/__tests__/styles/tokens-drift.test.js.map +1 -0
  97. package/dist/__tests__/views/admin-renderer-compat.test.d.ts +2 -0
  98. package/dist/__tests__/views/admin-renderer-compat.test.d.ts.map +1 -0
  99. package/dist/__tests__/views/admin-renderer-compat.test.js +43 -0
  100. package/dist/__tests__/views/admin-renderer-compat.test.js.map +1 -0
  101. package/dist/__tests__/views/admin-section-renderers.test.js +52 -4
  102. package/dist/__tests__/views/admin-section-renderers.test.js.map +1 -1
  103. package/dist/__tests__/views/canvas-review-mode.render.test.d.ts +2 -0
  104. package/dist/__tests__/views/canvas-review-mode.render.test.d.ts.map +1 -0
  105. package/dist/__tests__/views/canvas-review-mode.render.test.js +70 -0
  106. package/dist/__tests__/views/canvas-review-mode.render.test.js.map +1 -0
  107. package/dist/__tests__/views/editor-top-bar.render.test.js +30 -1
  108. package/dist/__tests__/views/editor-top-bar.render.test.js.map +1 -1
  109. package/dist/__tests__/views/page-tab.render.test.d.ts +2 -0
  110. package/dist/__tests__/views/page-tab.render.test.d.ts.map +1 -0
  111. package/dist/__tests__/views/page-tab.render.test.js +151 -0
  112. package/dist/__tests__/views/page-tab.render.test.js.map +1 -0
  113. package/dist/__tests__/views/section-class-parity.test.d.ts +2 -0
  114. package/dist/__tests__/views/section-class-parity.test.d.ts.map +1 -0
  115. package/dist/__tests__/views/section-class-parity.test.js +81 -0
  116. package/dist/__tests__/views/section-class-parity.test.js.map +1 -0
  117. package/dist/__tests__/views/sections-panel-design-owned.render.test.d.ts +2 -0
  118. package/dist/__tests__/views/sections-panel-design-owned.render.test.d.ts.map +1 -0
  119. package/dist/__tests__/views/sections-panel-design-owned.render.test.js +86 -0
  120. package/dist/__tests__/views/sections-panel-design-owned.render.test.js.map +1 -0
  121. package/dist/__tests__/views/version-history-panel.render.test.d.ts +2 -0
  122. package/dist/__tests__/views/version-history-panel.render.test.d.ts.map +1 -0
  123. package/dist/__tests__/views/version-history-panel.render.test.js +153 -0
  124. package/dist/__tests__/views/version-history-panel.render.test.js.map +1 -0
  125. package/dist/components/BreakpointPicker.d.ts +18 -0
  126. package/dist/components/BreakpointPicker.d.ts.map +1 -0
  127. package/dist/components/BreakpointPicker.js +31 -0
  128. package/dist/components/BreakpointPicker.js.map +1 -0
  129. package/dist/components/CanvasEditTip.d.ts +6 -0
  130. package/dist/components/CanvasEditTip.d.ts.map +1 -0
  131. package/dist/components/CanvasEditTip.js +22 -0
  132. package/dist/components/CanvasEditTip.js.map +1 -0
  133. package/dist/components/EditorRail.d.ts +36 -0
  134. package/dist/components/EditorRail.d.ts.map +1 -0
  135. package/dist/components/EditorRail.js +104 -0
  136. package/dist/components/EditorRail.js.map +1 -0
  137. package/dist/components/EditorRailTabs.d.ts +32 -0
  138. package/dist/components/EditorRailTabs.d.ts.map +1 -0
  139. package/dist/components/EditorRailTabs.js +45 -0
  140. package/dist/components/EditorRailTabs.js.map +1 -0
  141. package/dist/components/MediaBlock.d.ts +49 -0
  142. package/dist/components/MediaBlock.d.ts.map +1 -0
  143. package/dist/components/MediaBlock.js +50 -0
  144. package/dist/components/MediaBlock.js.map +1 -0
  145. package/dist/components/ReflowMeter.d.ts +28 -0
  146. package/dist/components/ReflowMeter.d.ts.map +1 -0
  147. package/dist/components/ReflowMeter.js +42 -0
  148. package/dist/components/ReflowMeter.js.map +1 -0
  149. package/dist/components/ZoomChip.d.ts +17 -0
  150. package/dist/components/ZoomChip.d.ts.map +1 -0
  151. package/dist/components/ZoomChip.js +17 -0
  152. package/dist/components/ZoomChip.js.map +1 -0
  153. package/dist/components/breakpoint-icons.d.ts +3 -0
  154. package/dist/components/breakpoint-icons.d.ts.map +1 -0
  155. package/dist/components/breakpoint-icons.js +16 -0
  156. package/dist/components/breakpoint-icons.js.map +1 -0
  157. package/dist/components/page-tab/AuthorPicker.d.ts +31 -0
  158. package/dist/components/page-tab/AuthorPicker.d.ts.map +1 -0
  159. package/dist/components/page-tab/AuthorPicker.js +95 -0
  160. package/dist/components/page-tab/AuthorPicker.js.map +1 -0
  161. package/dist/components/page-tab/DirectiveSwitch.d.ts +38 -0
  162. package/dist/components/page-tab/DirectiveSwitch.d.ts.map +1 -0
  163. package/dist/components/page-tab/DirectiveSwitch.js +33 -0
  164. package/dist/components/page-tab/DirectiveSwitch.js.map +1 -0
  165. package/dist/components/seo/EditorSeoScoreBadge.d.ts +8 -1
  166. package/dist/components/seo/EditorSeoScoreBadge.d.ts.map +1 -1
  167. package/dist/components/seo/EditorSeoScoreBadge.js +2 -2
  168. package/dist/components/seo/EditorSeoScoreBadge.js.map +1 -1
  169. package/dist/components/seo/PageAuditPanel.d.ts +14 -0
  170. package/dist/components/seo/PageAuditPanel.d.ts.map +1 -0
  171. package/dist/components/seo/PageAuditPanel.js +59 -0
  172. package/dist/components/seo/PageAuditPanel.js.map +1 -0
  173. package/dist/components/seo/SeoCharCount.d.ts +19 -7
  174. package/dist/components/seo/SeoCharCount.d.ts.map +1 -1
  175. package/dist/components/seo/SeoCharCount.js +30 -16
  176. package/dist/components/seo/SeoCharCount.js.map +1 -1
  177. package/dist/components/seo/SeoEditorPane.d.ts +15 -1
  178. package/dist/components/seo/SeoEditorPane.d.ts.map +1 -1
  179. package/dist/components/seo/SeoEditorPane.js +118 -21
  180. package/dist/components/seo/SeoEditorPane.js.map +1 -1
  181. package/dist/components/seo/SerpPreview.d.ts +20 -0
  182. package/dist/components/seo/SerpPreview.d.ts.map +1 -0
  183. package/dist/components/seo/SerpPreview.js +6 -0
  184. package/dist/components/seo/SerpPreview.js.map +1 -0
  185. package/dist/hooks/usePageAudit.d.ts +41 -0
  186. package/dist/hooks/usePageAudit.d.ts.map +1 -0
  187. package/dist/hooks/usePageAudit.js +68 -0
  188. package/dist/hooks/usePageAudit.js.map +1 -0
  189. package/dist/hooks/useReflowGuard.d.ts +47 -0
  190. package/dist/hooks/useReflowGuard.d.ts.map +1 -0
  191. package/dist/hooks/useReflowGuard.js +52 -0
  192. package/dist/hooks/useReflowGuard.js.map +1 -0
  193. package/dist/layout/primitives/AdminShell.d.ts.map +1 -1
  194. package/dist/layout/primitives/AdminShell.js +2 -2
  195. package/dist/layout/primitives/AdminShell.js.map +1 -1
  196. package/dist/lib/breakpoints.d.ts +51 -0
  197. package/dist/lib/breakpoints.d.ts.map +1 -0
  198. package/dist/lib/breakpoints.js +84 -0
  199. package/dist/lib/breakpoints.js.map +1 -0
  200. package/dist/lib/canvas-edit-chrome.d.ts +22 -0
  201. package/dist/lib/canvas-edit-chrome.d.ts.map +1 -0
  202. package/dist/lib/canvas-edit-chrome.js +81 -0
  203. package/dist/lib/canvas-edit-chrome.js.map +1 -0
  204. package/dist/lib/media-format.d.ts +55 -0
  205. package/dist/lib/media-format.d.ts.map +1 -0
  206. package/dist/lib/media-format.js +93 -0
  207. package/dist/lib/media-format.js.map +1 -0
  208. package/dist/lib/page-audit-input.d.ts +45 -0
  209. package/dist/lib/page-audit-input.d.ts.map +1 -0
  210. package/dist/lib/page-audit-input.js +53 -0
  211. package/dist/lib/page-audit-input.js.map +1 -0
  212. package/dist/lib/page-editor-service.d.ts +12 -0
  213. package/dist/lib/page-editor-service.d.ts.map +1 -1
  214. package/dist/lib/page-editor-service.js +6 -0
  215. package/dist/lib/page-editor-service.js.map +1 -1
  216. package/dist/lib/page-redirect.d.ts +39 -0
  217. package/dist/lib/page-redirect.d.ts.map +1 -0
  218. package/dist/lib/page-redirect.js +38 -0
  219. package/dist/lib/page-redirect.js.map +1 -0
  220. package/dist/lib/rail-width.d.ts +23 -0
  221. package/dist/lib/rail-width.d.ts.map +1 -0
  222. package/dist/lib/rail-width.js +59 -0
  223. package/dist/lib/rail-width.js.map +1 -0
  224. package/dist/lib/reflow-baseline.d.ts +45 -0
  225. package/dist/lib/reflow-baseline.d.ts.map +1 -0
  226. package/dist/lib/reflow-baseline.js +38 -0
  227. package/dist/lib/reflow-baseline.js.map +1 -0
  228. package/dist/lib/reflow-probe.d.ts +159 -0
  229. package/dist/lib/reflow-probe.d.ts.map +1 -0
  230. package/dist/lib/reflow-probe.js +228 -0
  231. package/dist/lib/reflow-probe.js.map +1 -0
  232. package/dist/lib/seo-service.d.ts +10 -0
  233. package/dist/lib/seo-service.d.ts.map +1 -1
  234. package/dist/lib/seo-service.js.map +1 -1
  235. package/dist/lib/version-diff.d.ts +50 -0
  236. package/dist/lib/version-diff.d.ts.map +1 -0
  237. package/dist/lib/version-diff.js +294 -0
  238. package/dist/lib/version-diff.js.map +1 -0
  239. package/dist/lib/version-history.d.ts +73 -0
  240. package/dist/lib/version-history.d.ts.map +1 -0
  241. package/dist/lib/version-history.js +126 -0
  242. package/dist/lib/version-history.js.map +1 -0
  243. package/dist/prenta-admin.css +1 -1
  244. package/dist/views/page-editor/CanvasMetaLine.d.ts +19 -0
  245. package/dist/views/page-editor/CanvasMetaLine.d.ts.map +1 -0
  246. package/dist/views/page-editor/CanvasMetaLine.js +23 -0
  247. package/dist/views/page-editor/CanvasMetaLine.js.map +1 -0
  248. package/dist/views/page-editor/EditorCanvas.d.ts +47 -8
  249. package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -1
  250. package/dist/views/page-editor/EditorCanvas.js +68 -34
  251. package/dist/views/page-editor/EditorCanvas.js.map +1 -1
  252. package/dist/views/page-editor/EditorTopBar.d.ts +21 -2
  253. package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
  254. package/dist/views/page-editor/EditorTopBar.js +13 -20
  255. package/dist/views/page-editor/EditorTopBar.js.map +1 -1
  256. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  257. package/dist/views/page-editor/PageSectionEditor.js +383 -78
  258. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  259. package/dist/views/page-editor/PageTab.d.ts +36 -0
  260. package/dist/views/page-editor/PageTab.d.ts.map +1 -0
  261. package/dist/views/page-editor/PageTab.js +65 -0
  262. package/dist/views/page-editor/PageTab.js.map +1 -0
  263. package/dist/views/page-editor/ReviewBar.d.ts +21 -0
  264. package/dist/views/page-editor/ReviewBar.d.ts.map +1 -0
  265. package/dist/views/page-editor/ReviewBar.js +22 -0
  266. package/dist/views/page-editor/ReviewBar.js.map +1 -0
  267. package/dist/views/page-editor/SectionInspector.d.ts.map +1 -1
  268. package/dist/views/page-editor/SectionInspector.js +64 -20
  269. package/dist/views/page-editor/SectionInspector.js.map +1 -1
  270. package/dist/views/page-editor/SectionsPanel.d.ts +30 -5
  271. package/dist/views/page-editor/SectionsPanel.d.ts.map +1 -1
  272. package/dist/views/page-editor/SectionsPanel.js +38 -7
  273. package/dist/views/page-editor/SectionsPanel.js.map +1 -1
  274. package/dist/views/page-editor/SitePreviewFrame.d.ts.map +1 -1
  275. package/dist/views/page-editor/SitePreviewFrame.js +3 -0
  276. package/dist/views/page-editor/SitePreviewFrame.js.map +1 -1
  277. package/dist/views/page-editor/VersionHistoryPanel.d.ts +29 -0
  278. package/dist/views/page-editor/VersionHistoryPanel.d.ts.map +1 -0
  279. package/dist/views/page-editor/VersionHistoryPanel.js +81 -0
  280. package/dist/views/page-editor/VersionHistoryPanel.js.map +1 -0
  281. package/dist/views/page-editor/reflow-context.d.ts +28 -0
  282. package/dist/views/page-editor/reflow-context.d.ts.map +1 -0
  283. package/dist/views/page-editor/reflow-context.js +14 -0
  284. package/dist/views/page-editor/reflow-context.js.map +1 -0
  285. package/dist/views/page-editor/sections/ByTheNumbersSection.d.ts.map +1 -1
  286. package/dist/views/page-editor/sections/ByTheNumbersSection.js +1 -1
  287. package/dist/views/page-editor/sections/ByTheNumbersSection.js.map +1 -1
  288. package/dist/views/page-editor/sections/CtaBannerSection.d.ts.map +1 -1
  289. package/dist/views/page-editor/sections/CtaBannerSection.js +1 -1
  290. package/dist/views/page-editor/sections/CtaBannerSection.js.map +1 -1
  291. package/dist/views/page-editor/sections/FeatureHighlightSection.d.ts.map +1 -1
  292. package/dist/views/page-editor/sections/FeatureHighlightSection.js +2 -2
  293. package/dist/views/page-editor/sections/FeatureHighlightSection.js.map +1 -1
  294. package/dist/views/page-editor/sections/GallerySection.d.ts.map +1 -1
  295. package/dist/views/page-editor/sections/GallerySection.js +7 -3
  296. package/dist/views/page-editor/sections/GallerySection.js.map +1 -1
  297. package/dist/views/page-editor/sections/HeroBannerSection.js +1 -1
  298. package/dist/views/page-editor/sections/HeroBannerSection.js.map +1 -1
  299. package/dist/views/page-editor/sections/MissionSection.d.ts.map +1 -1
  300. package/dist/views/page-editor/sections/MissionSection.js +1 -1
  301. package/dist/views/page-editor/sections/MissionSection.js.map +1 -1
  302. package/dist/views/page-editor/sections/PullQuoteSection.d.ts.map +1 -1
  303. package/dist/views/page-editor/sections/PullQuoteSection.js +1 -1
  304. package/dist/views/page-editor/sections/PullQuoteSection.js.map +1 -1
  305. package/dist/views/page-editor/sections/RelatedPostsSection.d.ts.map +1 -1
  306. package/dist/views/page-editor/sections/RelatedPostsSection.js +1 -1
  307. package/dist/views/page-editor/sections/RelatedPostsSection.js.map +1 -1
  308. package/dist/views/page-editor/sections/SectionRenderer.d.ts +13 -1
  309. package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -1
  310. package/dist/views/page-editor/sections/SectionRenderer.js +66 -26
  311. package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -1
  312. package/dist/views/page-editor/sections/admin-renderers.d.ts +47 -15
  313. package/dist/views/page-editor/sections/admin-renderers.d.ts.map +1 -1
  314. package/dist/views/page-editor/sections/admin-renderers.js +27 -15
  315. package/dist/views/page-editor/sections/admin-renderers.js.map +1 -1
  316. package/dist/views/page-editor/sections/parts.d.ts +0 -1
  317. package/dist/views/page-editor/sections/parts.d.ts.map +1 -1
  318. package/dist/views/page-editor/sections/parts.js +41 -16
  319. package/dist/views/page-editor/sections/parts.js.map +1 -1
  320. package/dist/views/page-editor/viewports.d.ts +7 -14
  321. package/dist/views/page-editor/viewports.d.ts.map +1 -1
  322. package/dist/views/page-editor/viewports.js +7 -12
  323. package/dist/views/page-editor/viewports.js.map +1 -1
  324. package/dist/views/post-editor/PostEditorCanvas.d.ts +2 -1
  325. package/dist/views/post-editor/PostEditorCanvas.d.ts.map +1 -1
  326. package/dist/views/post-editor/PostEditorCanvas.js +5 -2
  327. package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
  328. package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
  329. package/dist/views/post-editor/PostSectionEditor.js +2 -20
  330. package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
  331. package/dist/views/post-editor/PostTemplateEditor.d.ts.map +1 -1
  332. package/dist/views/post-editor/PostTemplateEditor.js +4 -1
  333. package/dist/views/post-editor/PostTemplateEditor.js.map +1 -1
  334. package/package.json +5 -3
  335. package/src/AdminRoot.tsx +27 -6
  336. package/src/__tests__/components/breakpoint-controls.render.test.tsx +57 -0
  337. package/src/__tests__/components/canvas-edit-tip.render.test.tsx +41 -0
  338. package/src/__tests__/components/editor-rail.render.test.tsx +192 -0
  339. package/src/__tests__/components/media-block.render.test.tsx +126 -0
  340. package/src/__tests__/components/page-audit-panel.render.test.tsx +64 -0
  341. package/src/__tests__/components/reflow-meter.render.test.tsx +144 -0
  342. package/src/__tests__/components/seo-char-count.test.tsx +26 -6
  343. package/src/__tests__/components/serp-preview.render.test.tsx +32 -0
  344. package/src/__tests__/lib/breakpoints.test.ts +84 -0
  345. package/src/__tests__/lib/canvas-edit-chrome.test.ts +56 -0
  346. package/src/__tests__/lib/media-format.test.ts +89 -0
  347. package/src/__tests__/lib/page-audit-input.test.ts +144 -0
  348. package/src/__tests__/lib/page-editor-service.test.ts +2 -0
  349. package/src/__tests__/lib/page-redirect.test.ts +29 -0
  350. package/src/__tests__/lib/rail-width.test.ts +86 -0
  351. package/src/__tests__/lib/reflow-baseline.test.ts +64 -0
  352. package/src/__tests__/lib/reflow-probe.test.ts +95 -0
  353. package/src/__tests__/lib/version-diff.test.ts +278 -0
  354. package/src/__tests__/lib/version-history.test.ts +166 -0
  355. package/src/__tests__/styles/canvas-container-queries.test.ts +103 -0
  356. package/src/__tests__/styles/packaging.test.ts +38 -0
  357. package/src/__tests__/styles/responsive-layer.test.ts +152 -0
  358. package/src/__tests__/styles/tokens-drift.test.ts +110 -0
  359. package/src/__tests__/views/admin-renderer-compat.test.ts +48 -0
  360. package/src/__tests__/views/admin-section-renderers.test.tsx +70 -4
  361. package/src/__tests__/views/canvas-review-mode.render.test.tsx +94 -0
  362. package/src/__tests__/views/editor-top-bar.render.test.tsx +43 -1
  363. package/src/__tests__/views/page-tab.render.test.tsx +226 -0
  364. package/src/__tests__/views/section-class-parity.test.ts +98 -0
  365. package/src/__tests__/views/sections-panel-design-owned.render.test.tsx +104 -0
  366. package/src/__tests__/views/version-history-panel.render.test.tsx +195 -0
  367. package/src/components/BreakpointPicker.tsx +114 -0
  368. package/src/components/CanvasEditTip.tsx +46 -0
  369. package/src/components/EditorRail.tsx +219 -0
  370. package/src/components/EditorRailTabs.tsx +117 -0
  371. package/src/components/MediaBlock.tsx +223 -0
  372. package/src/components/ReflowMeter.tsx +125 -0
  373. package/src/components/ZoomChip.tsx +39 -0
  374. package/src/components/breakpoint-icons.ts +17 -0
  375. package/src/components/page-tab/AuthorPicker.tsx +240 -0
  376. package/src/components/page-tab/DirectiveSwitch.tsx +89 -0
  377. package/src/components/seo/EditorSeoScoreBadge.tsx +9 -1
  378. package/src/components/seo/PageAuditPanel.tsx +192 -0
  379. package/src/components/seo/SeoCharCount.tsx +55 -24
  380. package/src/components/seo/SeoEditorPane.tsx +213 -31
  381. package/src/components/seo/SerpPreview.tsx +41 -0
  382. package/src/hooks/usePageAudit.ts +98 -0
  383. package/src/hooks/useReflowGuard.ts +96 -0
  384. package/src/layout/primitives/AdminShell.tsx +13 -1
  385. package/src/lib/breakpoints.ts +107 -0
  386. package/src/lib/canvas-edit-chrome.ts +82 -0
  387. package/src/lib/media-format.ts +99 -0
  388. package/src/lib/page-audit-input.ts +104 -0
  389. package/src/lib/page-editor-service.ts +18 -0
  390. package/src/lib/page-redirect.ts +60 -0
  391. package/src/lib/rail-width.ts +58 -0
  392. package/src/lib/reflow-baseline.ts +70 -0
  393. package/src/lib/reflow-probe.ts +299 -0
  394. package/src/lib/seo-service.ts +10 -0
  395. package/src/lib/version-diff.ts +376 -0
  396. package/src/lib/version-history.ts +192 -0
  397. package/src/styles/prenta-tokens.css +120 -42
  398. package/src/styles/responsive.css +281 -0
  399. package/src/styles/theme.css +138 -0
  400. package/src/views/page-editor/CanvasMetaLine.tsx +50 -0
  401. package/src/views/page-editor/EditorCanvas.tsx +140 -54
  402. package/src/views/page-editor/EditorTopBar.tsx +55 -51
  403. package/src/views/page-editor/PageSectionEditor.tsx +575 -185
  404. package/src/views/page-editor/PageTab.tsx +433 -0
  405. package/src/views/page-editor/ReviewBar.tsx +76 -0
  406. package/src/views/page-editor/SectionInspector.tsx +84 -42
  407. package/src/views/page-editor/SectionsPanel.tsx +282 -141
  408. package/src/views/page-editor/SitePreviewFrame.tsx +3 -0
  409. package/src/views/page-editor/VersionHistoryPanel.tsx +302 -0
  410. package/src/views/page-editor/reflow-context.ts +43 -0
  411. package/src/views/page-editor/sections/ByTheNumbersSection.tsx +3 -2
  412. package/src/views/page-editor/sections/CtaBannerSection.tsx +3 -1
  413. package/src/views/page-editor/sections/FeatureHighlightSection.tsx +3 -2
  414. package/src/views/page-editor/sections/GallerySection.tsx +7 -3
  415. package/src/views/page-editor/sections/HeroBannerSection.tsx +2 -2
  416. package/src/views/page-editor/sections/MissionSection.tsx +4 -2
  417. package/src/views/page-editor/sections/PullQuoteSection.tsx +3 -1
  418. package/src/views/page-editor/sections/RelatedPostsSection.tsx +2 -1
  419. package/src/views/page-editor/sections/SectionRenderer.tsx +115 -34
  420. package/src/views/page-editor/sections/admin-renderers.tsx +54 -15
  421. package/src/views/page-editor/sections/parts.tsx +46 -18
  422. package/src/views/page-editor/viewports.ts +13 -26
  423. package/src/views/post-editor/PostEditorCanvas.tsx +18 -11
  424. package/src/views/post-editor/PostSectionEditor.tsx +1 -56
  425. package/src/views/post-editor/PostTemplateEditor.tsx +3 -0
@@ -0,0 +1,152 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { describe, expect, it } from 'vitest'
4
+
5
+ /**
6
+ * The responsive + accessibility layer (`src/styles/responsive.css`), ported
7
+ * from the design bundle's `design-system/cms-responsive.css`.
8
+ *
9
+ * Every way this breaks is invisible: a stylesheet that stops being imported,
10
+ * a rule whose class hook was renamed in a component, or a token override
11
+ * placed before the token it overrides all produce a page that renders
12
+ * perfectly and simply lacks the behaviour. None of it raises an error, and
13
+ * none of it shows up in a screenshot taken at a desktop width with a mouse.
14
+ *
15
+ * Reads the source rather than a compiled bundle so the suite does not depend
16
+ * on a build step; the entry-point assertion is what ties the two together.
17
+ */
18
+
19
+ const PKG = fileURLToPath(new URL('../../../', import.meta.url))
20
+ const read = (rel: string) => readFileSync(PKG + rel, 'utf8')
21
+
22
+ const responsive = read('src/styles/responsive.css')
23
+ const theme = read('src/styles/theme.css')
24
+ const tokens = read('src/styles/prenta-tokens.css')
25
+
26
+ describe('responsive + accessibility layer', () => {
27
+ it('is imported by theme.css, which both entry points pull in', () => {
28
+ // `build-input.css` (precompiled) and `tailwind.css` (advanced) each
29
+ // import theme.css and nothing else of ours, so importing from there is
30
+ // what puts this layer in front of every consumer.
31
+ expect(theme).toContain("@import './responsive.css'")
32
+ expect(read('src/styles/build-input.css')).toContain("@import './theme.css'")
33
+ expect(read('src/styles/tailwind.css')).toContain("@import './theme.css'")
34
+ })
35
+
36
+ it('is imported AFTER the token block it overrides', () => {
37
+ // Same specificity, so source order alone decides. Imported first, the
38
+ // prefers-contrast block would parse, match, and change nothing.
39
+ expect(theme.indexOf("@import './prenta-tokens.css'")).toBeLessThan(
40
+ theme.indexOf("@import './responsive.css'"),
41
+ )
42
+ })
43
+
44
+ it('scopes every rule to the admin root', () => {
45
+ // The admin renders inside consumer applications. A bare `:focus-visible`
46
+ // or `@media print` rule here would restyle the host's own pages.
47
+ const selectors = responsive
48
+ .replace(/\/\*[\s\S]*?\*\//g, '')
49
+ .split('\n')
50
+ .map((line) => line.trim())
51
+ .filter((line) => line.endsWith('{') && !line.startsWith('@'))
52
+
53
+ expect(selectors.length).toBeGreaterThan(10)
54
+ for (const selector of selectors) {
55
+ expect(selector, `unscoped selector: ${selector}`).toMatch(/\.prenta-admin/)
56
+ }
57
+ })
58
+
59
+ it('raises contrast rather than lowering it, in both themes', () => {
60
+ // The shipped --muted is already an AA correction over the handoff's own
61
+ // value (see prenta-tokens.css). A "more contrast" preference that moved
62
+ // it the other way would be worse than not honouring the preference.
63
+ const luminance = (hex: string) => {
64
+ const n = parseInt(hex.slice(1), 16)
65
+ return ((n >> 16) & 255) * 0.299 + ((n >> 8) & 255) * 0.587 + (n & 255) * 0.114
66
+ }
67
+ const declaration = (css: string, name: string, from = 0) => {
68
+ const match = new RegExp(`${name}:\\s*(#[0-9a-f]{6})`, 'i').exec(css.slice(from))
69
+ return match?.[1] ?? null
70
+ }
71
+
72
+ const contrastBlock = responsive.slice(responsive.indexOf('@media (prefers-contrast: more)'))
73
+ const darkStart = contrastBlock.indexOf('[data-theme=')
74
+
75
+ for (const name of ['--muted', '--sub', '--bdr']) {
76
+ const base = declaration(tokens, name)!
77
+ const raised = declaration(contrastBlock.slice(0, darkStart), name)!
78
+ // Light theme: darker text and borders on a light surface.
79
+ expect(luminance(raised), `light ${name}`).toBeLessThan(luminance(base))
80
+
81
+ const darkBase = declaration(
82
+ tokens,
83
+ name,
84
+ tokens.indexOf(".prenta-admin[data-theme='dark']"),
85
+ )!
86
+ const darkRaised = declaration(contrastBlock.slice(darkStart), name)!
87
+ // Dark theme: lighter, on a dark surface.
88
+ expect(luminance(darkRaised), `dark ${name}`).toBeGreaterThan(luminance(darkBase))
89
+ }
90
+ })
91
+
92
+ it('grows touch targets only under a coarse pointer', () => {
93
+ const coarse = responsive.slice(
94
+ responsive.indexOf('@media (pointer: coarse)'),
95
+ responsive.indexOf('@media (min-width: 2000px)'),
96
+ )
97
+ expect(coarse).toContain('min-height: 44px')
98
+ expect(coarse).toContain('min-width: 44px')
99
+ // A 26px button is right with a mouse. No control floor is imposed outside
100
+ // this block — the 44px that appears in the stacked-layout rules is the
101
+ // collapsed strip's own height, not a minimum applied to a control.
102
+ const elsewhere = responsive.replace(coarse, '').replace(/\/\*[\s\S]*?\*\//g, '')
103
+ expect(elsewhere).not.toMatch(/min-(height|width):\s*44px/)
104
+ })
105
+
106
+ it('grows the switch by its hit area, never by its box', () => {
107
+ // A switch positions its thumb against the track. Stretching the track to
108
+ // 44px leaves the thumb floating in dead space — the one carve-out the
109
+ // design bundle makes, and for this reason.
110
+ const hit = responsive.slice(responsive.indexOf('.prenta-touch-hit::after'))
111
+ expect(hit).toContain('position: absolute')
112
+ expect(hit.slice(0, hit.indexOf('}'))).not.toContain('min-height')
113
+ })
114
+
115
+ it('overrides the rail width with !important, because it is set inline', () => {
116
+ // The rail is drag-resizable and its width is an inline style, which no
117
+ // class can beat. Without this the stacked layout silently keeps a
118
+ // 360px-wide rail on a 390px screen.
119
+ const stacked = responsive.slice(responsive.indexOf('@media (max-width: 900px)'))
120
+ expect(stacked).toMatch(/\.prenta-editor-rail\s*\{[^}]*width:\s*100%\s*!important/)
121
+ })
122
+ })
123
+
124
+ describe('class hooks the layer targets', () => {
125
+ // A rule whose hook was renamed in a component is dead CSS with no error.
126
+ const hooks: Array<[string, string]> = [
127
+ ['prenta-skip-link', 'src/layout/primitives/AdminShell.tsx'],
128
+ ['prenta-editor-rail', 'src/components/EditorRail.tsx'],
129
+ ['prenta-editor-rail-collapsed', 'src/components/EditorRail.tsx'],
130
+ ['prenta-rail-label', 'src/components/EditorRail.tsx'],
131
+ ['prenta-editor-body', 'src/views/page-editor/PageSectionEditor.tsx'],
132
+ ['prenta-editor-header', 'src/views/page-editor/EditorTopBar.tsx'],
133
+ ['prenta-editor-inspector', 'src/views/page-editor/SectionInspector.tsx'],
134
+ ['prenta-canvas-scroll', 'src/views/page-editor/EditorCanvas.tsx'],
135
+ ['prenta-canvas-fit', 'src/views/page-editor/EditorCanvas.tsx'],
136
+ ['prenta-touch-target', 'src/components/EditorRail.tsx'],
137
+ ['prenta-touch-hit', 'src/components/page-tab/DirectiveSwitch.tsx'],
138
+ ]
139
+
140
+ for (const [hook, file] of hooks) {
141
+ it(`${hook} is applied in ${file.split('/').pop()}`, () => {
142
+ expect(responsive, `${hook} is unused in responsive.css`).toContain(hook)
143
+ expect(read(file), `${hook} is not applied by any component`).toContain(hook)
144
+ })
145
+ }
146
+
147
+ it('points the skip link at a target that exists', () => {
148
+ const shell = read('src/layout/primitives/AdminShell.tsx')
149
+ expect(shell).toContain('href="#prenta-main"')
150
+ expect(shell).toContain('id="prenta-main"')
151
+ })
152
+ })
@@ -0,0 +1,110 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { describe, expect, it } from 'vitest'
4
+ // @ts-expect-error — plain .mjs build script, intentionally untyped
5
+ import { OUTPUTS, buildAll, readTokens } from '../../../scripts/generate-tokens.mjs'
6
+
7
+ /**
8
+ * The token files are generated from design-system/tokens.json. Before the
9
+ * generator existed, the runtime copy (src/styles/prenta-tokens.css) was a
10
+ * hand transcription of the handoff copy (design-system/tokens.css) and the
11
+ * two silently diverged — the radius scale was renamed in one and not the
12
+ * other, and the WCAG correction to `--muted` lived only downstream where a
13
+ * re-import of the handoff would have reverted it without anyone noticing.
14
+ *
15
+ * This suite fails if a generated file is edited by hand, if tokens.json
16
+ * changes without regenerating, or if the two CSS copies stop agreeing.
17
+ */
18
+
19
+ const PKG = fileURLToPath(new URL('../../../', import.meta.url))
20
+
21
+ const read = (rel: string) => readFileSync(PKG + rel, 'utf8')
22
+
23
+ /** Declarations only — comments legitimately differ between the two copies. */
24
+ function declarationMap(css: string): Map<string, string> {
25
+ const body = css.replace(/\/\*[\s\S]*?\*\//g, '')
26
+ const out = new Map<string, string>()
27
+ for (const match of body.matchAll(/(--[\w-]+)\s*:\s*([^;}]+)[;}]/g)) {
28
+ const name = match[1]!
29
+ const value = match[2]!
30
+ // Later declarations win in CSS; the dark block legitimately re-declares
31
+ // names from the light block, so keep the first (light) binding per file
32
+ // section by only recording a name once per call.
33
+ if (!out.has(name)) out.set(name, value.trim())
34
+ }
35
+ return out
36
+ }
37
+
38
+ describe('design token generation', () => {
39
+ it('every generated file matches a fresh run of the generator', async () => {
40
+ const built = await buildAll(await readTokens())
41
+
42
+ for (const rel of Object.values(OUTPUTS) as string[]) {
43
+ const committed = read(rel)
44
+ expect(built[rel], `${rel} is out of date. Run: pnpm --filter @prenta/admin generate`).toBe(
45
+ committed,
46
+ )
47
+ }
48
+ })
49
+
50
+ it('marks generated files so they are not hand-edited', () => {
51
+ for (const rel of Object.values(OUTPUTS) as string[]) {
52
+ expect(read(rel), `${rel} is missing its generated-file banner`).toContain(
53
+ 'GENERATED FILE — DO NOT EDIT',
54
+ )
55
+ }
56
+ })
57
+
58
+ it('the handoff copy and the runtime copy carry identical values', () => {
59
+ const handoff = declarationMap(read(OUTPUTS.tokensCss))
60
+ const runtime = declarationMap(read(OUTPUTS.runtimeCss))
61
+
62
+ expect(runtime.size).toBeGreaterThan(60)
63
+ expect([...handoff.keys()].sort()).toEqual([...runtime.keys()].sort())
64
+ for (const [name, value] of handoff) {
65
+ expect(runtime.get(name), `${name} differs between the two token copies`).toBe(value)
66
+ }
67
+ })
68
+
69
+ it('scopes the runtime copy to .prenta-admin and never to :root', () => {
70
+ const runtime = read(OUTPUTS.runtimeCss).replace(/\/\*[\s\S]*?\*\//g, '')
71
+ expect(runtime).toContain('.prenta-admin {')
72
+ expect(runtime).not.toMatch(/(^|\s):root\s*[,{]/)
73
+ })
74
+
75
+ it('keeps deprecated aliases resolving to their canonical token', () => {
76
+ const runtime = declarationMap(read(OUTPUTS.runtimeCss))
77
+ // `--r-control` and `--motion-*` are the pre-2.0 names. 72 call sites still
78
+ // read `--motion-fast`; the aliases are what make the rename non-breaking.
79
+ expect(runtime.get('--r-control')).toBe('var(--r-ctl)')
80
+ expect(runtime.get('--motion-fast')).toBe('var(--mo-fast)')
81
+ expect(runtime.get('--motion-base')).toBe('var(--mo-base)')
82
+ })
83
+
84
+ it('keeps --muted above the WCAG AA threshold in both themes', () => {
85
+ // The handoff specifies #B0ADBE (2.2:1 on white). The admin renders --muted
86
+ // as real text, so the generator ships a corrected value recorded in
87
+ // tokens.json as a `designValue` deviation. If someone re-imports the
88
+ // handoff verbatim, this fails rather than shipping illegible hints.
89
+ const css = read(OUTPUTS.runtimeCss)
90
+ const [light, dark] = css.split('.prenta-admin[data-theme=')
91
+ const mutedOf = (block: string) => declarationMap(block).get('--muted')
92
+
93
+ expect(contrast(mutedOf(light!)!, '#ffffff')).toBeGreaterThanOrEqual(4.5)
94
+ expect(contrast(mutedOf(dark!)!, '#1c1a2e')).toBeGreaterThanOrEqual(4.5)
95
+ })
96
+ })
97
+
98
+ /** WCAG 2.1 relative-luminance contrast ratio between two hex colors. */
99
+ function contrast(a: string, b: string): number {
100
+ const lum = (hex: string) => {
101
+ const n = hex.trim().replace('#', '')
102
+ const channels = [0, 2, 4].map((i) => parseInt(n.slice(i, i + 2), 16) / 255)
103
+ const [r, g, bl] = channels.map((c) =>
104
+ c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4,
105
+ )
106
+ return 0.2126 * r! + 0.7152 * g! + 0.0722 * bl!
107
+ }
108
+ const [hi, lo] = [lum(a), lum(b)].sort((x, y) => y - x)
109
+ return (hi! + 0.05) / (lo! + 0.05)
110
+ }
@@ -0,0 +1,48 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { DEFAULT_SECTION_RENDERERS } from '@prenta/sections-react'
3
+ import type { AdminSectionRendererMap } from '../../views/page-editor/sections/admin-renderers.js'
4
+ import { ADMIN_RENDER_OPTIONS } from '../../views/page-editor/sections/admin-renderers.js'
5
+
6
+ /**
7
+ * `AdminSectionRendererMap` is a hand-maintained structural mirror of
8
+ * `SectionRendererMap` from @prenta/sections-react — cms-admin depends on
9
+ * cms-core only, so the type is duplicated rather than imported.
10
+ *
11
+ * The mirror exists so a consumer can hand `<AdminRoot sectionRenderers>` the
12
+ * same components it registers for the public site and have the editor canvas
13
+ * preview what actually ships. That only works while the two shapes stay
14
+ * assignable, and nothing else in the build checks it — the duplication is
15
+ * invisible to the compiler until someone tries the assignment.
16
+ *
17
+ * This file IS that assignment. If the shapes drift, the type-check fails here
18
+ * rather than in a consumer's project.
19
+ */
20
+
21
+ describe('admin renderer contract', () => {
22
+ it('accepts a real @prenta/sections-react renderer map', () => {
23
+ // The assignment is the assertion — this line fails `tsc` if the mirror
24
+ // drifts. Renderers are function types, so their parameters compare
25
+ // contravariantly: widening a field on the ADMIN side is what breaks it.
26
+ const supplied: AdminSectionRendererMap = DEFAULT_SECTION_RENDERERS
27
+
28
+ expect(Object.keys(supplied)).toContain('hero')
29
+ expect(typeof supplied.hero).toBe('function')
30
+ })
31
+
32
+ it('supplies every option a public renderer resolves', () => {
33
+ // sections-react's `ResolvedRenderOptions` — a missing key throws at
34
+ // render time, not compile time, because renderers destructure it.
35
+ for (const key of ['sanitizeHtml', 'baseUrl', 'renderImage', 'lcp', 'prefetchedForms']) {
36
+ expect(ADMIN_RENDER_OPTIONS, `ADMIN_RENDER_OPTIONS is missing ${key}`).toHaveProperty(key)
37
+ }
38
+ })
39
+
40
+ it('never grants the LCP priority slot in the canvas', () => {
41
+ // On a public page exactly one image loads eagerly. The canvas has no
42
+ // such budget to protect and nothing competing for bandwidth, so every
43
+ // image should lazy-load — `claim()` always declines.
44
+ expect(ADMIN_RENDER_OPTIONS.lcp.claim()).toBe(false)
45
+ expect(ADMIN_RENDER_OPTIONS.lcp.claim()).toBe(false)
46
+ expect(ADMIN_RENDER_OPTIONS.lcp.claimed).toBe(true)
47
+ })
48
+ })
@@ -84,9 +84,20 @@ describe('pluggable admin section renderers', () => {
84
84
  })
85
85
  })
86
86
 
87
- it('does not affect built-in section types', () => {
87
+ it('overrides built-in section types too', () => {
88
+ // REVERSED from the original behaviour, which consulted the map only for
89
+ // types the built-in switch did not know.
90
+ //
91
+ // The point of `<AdminRoot sectionRenderers>` is to hand the admin the
92
+ // same components the public site registers, so the canvas previews what
93
+ // actually ships. Excluding the built-ins made that impossible for the
94
+ // twelve types that matter most: a consumer passing
95
+ // `DEFAULT_SECTION_RENDERERS` from `@prenta/sections-react` had their
96
+ // hero, text, feature, stats and cta silently dropped, and the canvas
97
+ // rendered the admin's own approximations — different font size, weight
98
+ // and breakpoints — while presenting itself as a preview.
88
99
  const renderers: AdminSectionRendererMap = {
89
- hero: () => <div data-testid="hijacked-hero" />,
100
+ hero: () => <div data-testid="supplied-hero" />,
90
101
  }
91
102
  render(
92
103
  <AdminSectionRenderersProvider value={renderers}>
@@ -95,7 +106,62 @@ describe('pluggable admin section renderers', () => {
95
106
  />
96
107
  </AdminSectionRenderersProvider>,
97
108
  )
98
- // Built-in types always use the built-in canvas component.
99
- expect(screen.queryByTestId('hijacked-hero')).toBeNull()
109
+ expect(screen.getByTestId('supplied-hero')).toBeTruthy()
110
+ })
111
+
112
+ it('falls back to the built-in when a supplied renderer for a built-in type throws', () => {
113
+ // A broken consumer renderer must degrade to the admin's own component,
114
+ // not to the "custom section" placeholder — the type IS known here.
115
+ vi.spyOn(console, 'error').mockImplementation(() => {})
116
+ const renderers: AdminSectionRendererMap = {
117
+ hero: () => {
118
+ throw new Error('consumer hero exploded')
119
+ },
120
+ }
121
+ render(
122
+ <AdminSectionRenderersProvider value={renderers}>
123
+ <SectionRenderer
124
+ section={customSection({
125
+ sectionType: 'hero',
126
+ name: 'Hero',
127
+ content: { heading: 'Built-in hero copy' },
128
+ })}
129
+ />
130
+ </AdminSectionRenderersProvider>,
131
+ )
132
+ expect(screen.getByText('Built-in hero copy')).toBeTruthy()
133
+ vi.restoreAllMocks()
134
+ })
135
+
136
+ it('supplies the render options a public renderer expects', () => {
137
+ // sections-react renderers call `options.lcp.claim()` during render (hero,
138
+ // feature, gallery). A missing controller would throw on the first image,
139
+ // so the mirror must carry the whole shape, not just renderImage.
140
+ let seen: Record<string, unknown> | null = null
141
+ const renderers: AdminSectionRendererMap = {
142
+ hero: ({ options }) => {
143
+ seen = options as unknown as Record<string, unknown>
144
+ return <div />
145
+ },
146
+ }
147
+ render(
148
+ <AdminSectionRenderersProvider value={renderers}>
149
+ <SectionRenderer section={customSection({ sectionType: 'hero', content: {} })} />
150
+ </AdminSectionRenderersProvider>,
151
+ )
152
+ const options = seen as unknown as {
153
+ lcp: { claim: () => boolean }
154
+ prefetchedForms: Record<string, unknown>
155
+ renderImage: unknown
156
+ sanitizeHtml: unknown
157
+ baseUrl: string
158
+ }
159
+ expect(typeof options.lcp.claim).toBe('function')
160
+ // The canvas never grants the priority slot — nothing competes for
161
+ // bandwidth there, so every image should lazy-load.
162
+ expect(options.lcp.claim()).toBe(false)
163
+ expect(options.prefetchedForms).toEqual({})
164
+ expect(typeof options.renderImage).toBe('function')
165
+ expect(typeof options.sanitizeHtml).toBe('function')
100
166
  })
101
167
  })
@@ -0,0 +1,94 @@
1
+ // @vitest-environment happy-dom
2
+ import { afterEach, describe, expect, it, vi } from 'vitest'
3
+ import { cleanup, fireEvent, render, screen } from '@testing-library/react'
4
+ import { SectionRenderer } from '../../views/page-editor/sections/SectionRenderer.js'
5
+ import type { PageSection } from '../../lib/page-editor-service.js'
6
+
7
+ afterEach(cleanup)
8
+
9
+ const SECTION: PageSection = {
10
+ id: 's1',
11
+ sectionType: 'hero',
12
+ name: 'Hero Banner',
13
+ visible: true,
14
+ content: { heading: 'A headline' },
15
+ settings: {},
16
+ }
17
+
18
+ function sectionEl(): HTMLElement {
19
+ const el = document.querySelector('[data-section-id="s1"]')
20
+ if (!el) throw new Error('section not rendered')
21
+ return el as HTMLElement
22
+ }
23
+
24
+ describe('SectionRenderer — review mode', () => {
25
+ it('marks a changed section and names the change in its label', () => {
26
+ render(<SectionRenderer section={SECTION} editable reviewing changed />)
27
+
28
+ expect(screen.getByText('Hero Banner — changed')).toBeTruthy()
29
+ expect(sectionEl().getAttribute('data-changed')).toBe('true')
30
+ expect(sectionEl().className).toContain('outline-dashed')
31
+ })
32
+
33
+ it('leaves an unchanged section unlabelled and unoutlined', () => {
34
+ render(<SectionRenderer section={SECTION} editable reviewing changed={false} />)
35
+
36
+ expect(screen.queryByText(/— changed/)).toBeNull()
37
+ expect(sectionEl().getAttribute('data-changed')).toBeNull()
38
+ expect(sectionEl().className).not.toContain('outline-dashed')
39
+ })
40
+
41
+ it('keeps the changed label even though review mode suppresses editing chrome', () => {
42
+ // The trap the handoff calls out by name: review mode and site-preview
43
+ // mode both drop the cursor, the hover outline and the section label, so
44
+ // it is tempting to share one suppression rule. Doing that hides the one
45
+ // label review mode exists to show.
46
+ render(<SectionRenderer section={SECTION} editable reviewing changed selected />)
47
+
48
+ expect(sectionEl().getAttribute('data-editable')).toBeNull()
49
+ expect(sectionEl().className).not.toContain('cursor-pointer')
50
+ // The selection label is gone…
51
+ expect(screen.queryByText('Hero Banner')).toBeNull()
52
+ // …and the changed label is not.
53
+ expect(screen.getByText('Hero Banner — changed')).toBeTruthy()
54
+ })
55
+
56
+ it('does not select a section while reviewing — that would edit the current page', () => {
57
+ const onSelect = vi.fn()
58
+ render(<SectionRenderer section={SECTION} editable reviewing changed onSelect={onSelect} />)
59
+
60
+ fireEvent.click(sectionEl())
61
+
62
+ expect(onSelect).not.toHaveBeenCalled()
63
+ })
64
+
65
+ it('still selects normally when not reviewing', () => {
66
+ const onSelect = vi.fn()
67
+ render(<SectionRenderer section={SECTION} editable onSelect={onSelect} />)
68
+
69
+ fireEvent.click(sectionEl())
70
+
71
+ expect(onSelect).toHaveBeenCalledWith('s1')
72
+ expect(sectionEl().getAttribute('data-editable')).toBe('true')
73
+ })
74
+
75
+ it('names the change in the accessible name, not only in colour and a dashed border', () => {
76
+ render(<SectionRenderer section={SECTION} editable reviewing changed />)
77
+ expect(sectionEl().getAttribute('aria-label')).toBe(
78
+ 'Hero Banner section — changed in this version',
79
+ )
80
+ })
81
+
82
+ it('keeps a hidden section muted in a snapshot, as it was when saved', () => {
83
+ render(
84
+ <SectionRenderer
85
+ section={{ ...SECTION, visible: false }}
86
+ editable
87
+ reviewing
88
+ changed={false}
89
+ />,
90
+ )
91
+ expect(sectionEl().className).toContain('opacity-40')
92
+ expect(screen.getByText('Hidden')).toBeTruthy()
93
+ })
94
+ })
@@ -12,7 +12,7 @@ const baseProps = {
12
12
  publishing: false,
13
13
  canEdit: true,
14
14
  canPublish: true,
15
- viewport: '1080p' as const,
15
+ viewport: 'wide' as const,
16
16
  onViewport: vi.fn(),
17
17
  onBack: vi.fn(),
18
18
  onPreview: vi.fn(),
@@ -109,3 +109,45 @@ describe('EditorTopBar view-on-site vs view-live', () => {
109
109
  expect(screen.queryByRole('button', { name: /View (live|draft)/ })).toBeNull()
110
110
  })
111
111
  })
112
+
113
+ describe('EditorTopBar history button', () => {
114
+ it('reports whether history is showing, since it takes over the rail rather than opening a dialog', () => {
115
+ const { unmount } = render(<EditorTopBar {...baseProps} onHistory={vi.fn()} />)
116
+ expect(
117
+ screen.getByRole('button', { name: 'Version history' }).getAttribute('aria-pressed'),
118
+ ).toBe('false')
119
+ unmount()
120
+
121
+ render(<EditorTopBar {...baseProps} onHistory={vi.fn()} historyOpen />)
122
+ const open = screen.getByRole('button', { name: 'Version history' })
123
+ expect(open.getAttribute('aria-pressed')).toBe('true')
124
+ expect(open.getAttribute('title')).toBe('Close version history')
125
+ })
126
+
127
+ it('gives every icon-only control a touch target', () => {
128
+ // These buttons are 32px square, which is a fine mouse target and an
129
+ // unusable thumb one. `prenta-touch-target` is what grows them under a
130
+ // coarse pointer — and it is applied per control, so a button written with
131
+ // a conditional className is easy to leave out. Exactly that happened to
132
+ // the history button, whose pressed state made it the odd one out.
133
+ render(
134
+ <EditorTopBar
135
+ {...baseProps}
136
+ onHistory={vi.fn()}
137
+ onViewOnSite={vi.fn()}
138
+ onToggleSitePreview={vi.fn()}
139
+ />,
140
+ )
141
+
142
+ for (const name of [
143
+ 'Page settings',
144
+ 'Version history',
145
+ 'Preview page',
146
+ 'Inline site preview',
147
+ 'View draft on site',
148
+ ]) {
149
+ const button = screen.getByRole('button', { name })
150
+ expect(button.className, `${name} has no touch target`).toContain('prenta-touch-target')
151
+ }
152
+ })
153
+ })