@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,668 @@
1
1
  # @actuate-media/cms-admin
2
2
 
3
+ ## 0.94.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 385dd10: Responsive + accessibility layer
8
+
9
+ Ports `design-system/cms-responsive.css` from the page-builder design bundle,
10
+ which the prototype now links and which post-dates the handoff copy in
11
+ `design_handoff_page_builder/` (followup 7: the bundle in hand had one `:focus`
12
+ rule, no `:focus-visible`, no reduced-motion, no contrast preference). None of
13
+ it existed in the admin.
14
+
15
+ The prototype's selectors (`.ni`, `.sec-panel`, `.icbtn`) do not exist here —
16
+ this admin is Tailwind over the same tokens — so the **rules** are ported and
17
+ re-expressed against the real structure, in one stylesheet scoped throughout to
18
+ `.prenta-admin`. The admin renders inside consumer applications; an unscoped
19
+ `@media print` or `:focus-visible` rule would restyle the host's own pages, and
20
+ a test asserts every selector in the file carries the scope.
21
+ - **Visible focus everywhere**, as a floor. Base-layer, so any component with
22
+ its own `focus-visible:ring` still wins and this only fills the gaps.
23
+ - **A skip link** — the first focusable thing in the admin, so a keyboard user's
24
+ first Tab offers a jump past ~20 sidebar items.
25
+ - **`prefers-reduced-motion`** honoured as a media query, not only through the
26
+ Appearance setting and the `motion-safe:` utilities that were written with it.
27
+ - **`prefers-contrast: more`** raises the three tertiary tokens, in both themes,
28
+ and a test asserts it raises rather than lowers — the shipped `--muted` is
29
+ already an AA correction over the handoff's value, and a "more contrast"
30
+ preference that moved it the other way would be worse than ignoring it.
31
+ - **44px touch targets under a coarse pointer**, opt-in per control rather than
32
+ a blanket `button` rule. The admin is full of dense tables where growing every
33
+ button reflows the layout around it; the design bundle carries a curated list
34
+ for the same reason. The switch is the carve-out both hit: its thumb is
35
+ positioned against the track, so the **hit area** grows and the control paints
36
+ at its designed 34×19.
37
+ - **The editor stacks below 900px** — rail above canvas, both full width, drag
38
+ handle gone. The width override needs `!important`: the rail's width is an
39
+ inline style because it is drag-resizable and persisted.
40
+ - **Print** drops the chrome and lets the canvas print at natural size instead
41
+ of as a clipped, transformed thumbnail.
42
+
43
+ Two rules deliberately did not come across. The prototype turns the sidebar into
44
+ a fixed bottom bar under 768px, where `AdminShell` already has a working
45
+ slide-in overlay driven by `matchMedia` — replacing a working navigation pattern
46
+ was not part of finishing the page builder. And the blanket `min-height` list is
47
+ the per-control class described above.
48
+
49
+ Every way this breaks is invisible: a stylesheet that stops being imported, a
50
+ class hook renamed in a component, or a token override placed before the token
51
+ it overrides all render perfectly and simply lack the behaviour. Unit tests pin
52
+ the import order and assert every hook is applied by some component; e2e tests
53
+ measure the result in a browser, because "the CSS says 44px" and "the button is
54
+ 44px" are different claims. The second caught a real gap — the history button's
55
+ conditional className had been missed by the edit that added the class to its
56
+ four neighbours.
57
+
58
+ - 385dd10: Version history: a mode, not a popup
59
+
60
+ The page editor's history was a right-hand dialog with a flat list and a
61
+ Restore button. The handoff's stage 6 asks for something different in kind:
62
+ history is about comparing **renders**, so selecting a version puts it on the
63
+ canvas and marks what changed. A modal covers the thing it exists to let you
64
+ look at; a drawer resizes it. This overlays the rail instead, so the canvas
65
+ does not move a pixel and the only thing that differs between two versions is
66
+ the page itself. An e2e test measures exactly that.
67
+
68
+ **The diff is computed, not stored.** Field-level, from the full snapshots the
69
+ versions API already returns, and the same result drives three surfaces: the
70
+ per-field list in the panel, the dashed outlines on the canvas, and the review
71
+ bar's count. One computation, so they cannot disagree. Field names come from
72
+ the section registry — the same definitions the inspector renders from — so a
73
+ diff reads in the words the editor typed under, and a custom section type
74
+ falls back to its raw key rather than dropping the row.
75
+
76
+ Whether a field changed is decided **structurally**, and the display summary is
77
+ used only to render it. Comparing the summaries is the obvious shortcut and it
78
+ loses real edits: the summary collapses an array to "N items", so retyping a
79
+ stats row while keeping the row count formats identically on both sides and the
80
+ field reports no change — no panel row and no canvas outline, on exactly the
81
+ edit a reviewer opened history to find. Repeaters are descended into row by
82
+ row, labelled through the registry's own nouns ("Stats · Stat 2 · Value"), so a
83
+ collapsed summary never has to stand in for a change it cannot express.
84
+
85
+ Visibility is diffed alongside content: hiding a section is the one structural
86
+ change a design-owned CMS gives an editor, and a history that omitted it would
87
+ be missing the only structural edit there is.
88
+
89
+ **Review mode is its own mode, deliberately.** It and site-preview mode both
90
+ drop the cursor, the hover outline and the section label, which makes one
91
+ shared suppression rule very tempting — and that rule hides the changed-section
92
+ label review mode exists to show. They are separate flags, with a test that
93
+ holds them apart. Editing is frozen throughout: every control that writes reads
94
+ one flag, so no surface can mutate the current page while the canvas shows a
95
+ different one.
96
+
97
+ **Two things found by testing this against a real browser rather than a mock:**
98
+ - Restore was nested inside the diff block, so a version with no computed
99
+ changes — the oldest snapshot held, or a metadata-only save — was reviewable
100
+ and not restorable. That is backwards: the oldest version is the one a
101
+ rollback most often wants. The prototype nests it the same way.
102
+ - Selection and disclosure were separate state, so the review bar's "Back to
103
+ current" could leave the panel describing one version while the canvas
104
+ rendered another, with Restore stranded on a row nobody could see. They are
105
+ the same act and are now one piece of state.
106
+
107
+ **And one the review bots found.** The inline site preview is a second editing
108
+ surface — an iframe of the real site with click-to-edit wired into the same
109
+ section state — and its handlers checked the user's role and nothing else. The
110
+ review lock, which every control in the rail honours, stopped at the iframe
111
+ boundary; so did the read-only lock on Page-Builder-owned pages. Both now read
112
+ the same single gate. A lock that covers only the rail is not a lock.
113
+
114
+ ## 0.93.0
115
+
116
+ ### Minor Changes
117
+
118
+ - 6f2eb6d: Add dismissible canvas editing tip, richer empty-canvas copy, lite section hover outlines, and Site preview field chrome for `[data-cms-field]`.
119
+ - 6f2eb6d: Make the page and post editors design-owned: no add, reorder, duplicate, or delete.
120
+
121
+ A document's section list now comes from its template in code. Editors change copy,
122
+ CTAs, and imagery — and can still show or hide a section, which is the one structural
123
+ property the content model gives them. See
124
+ [docs/design-owned-sections.md](https://github.com/actuate-media/prenta/blob/main/docs/design-owned-sections.md).
125
+
126
+ Structural editing is not gone, it moved to the surfaces that author a template. The
127
+ **Post Template editor** keeps it by passing `SectionsPanel`'s new `structural` prop;
128
+ the visual **Page Builder** is unaffected because it uses its own
129
+ `BuilderSectionsPanel`. `structural` defaults to `false`, so a consumer that forgets it
130
+ gets the safe behaviour rather than an editor that can quietly restructure a
131
+ design-owned page.
132
+
133
+ Existing documents are untouched — a page with a hand-built section list keeps every
134
+ section, keeps rendering, and stays fully editable.
135
+
136
+ **Deprecated, and deliberately soft — nothing is enforced.** The three endpoints below
137
+ keep working exactly as before: same status codes, same response bodies, no rate
138
+ limiting, no warning in the response payload. An integration that calls them will not
139
+ notice unless it inspects response headers, and a script that adds sections on a
140
+ schedule will keep doing so until the next major removes the routes.
141
+
142
+ If you drive page structure from outside the admin, treat this release as the window to
143
+ migrate — see [docs/design-owned-sections.md](https://github.com/actuate-media/prenta/blob/main/docs/design-owned-sections.md)
144
+ for the three common cases and what replaces each. Auditing your own callers is the only
145
+ way to find them; the deprecation will not surface itself.
146
+
147
+ Three REST endpoints now return
148
+ RFC 8594 `Deprecation` / `Link` / `X-Prenta-Deprecation` headers. The response bodies
149
+ are unchanged, so a client that ignores the headers sees no difference:
150
+ - `POST /page-sections/:collection/:id` (add)
151
+ - `POST /page-sections/:collection/:id/reorder`
152
+ - `DELETE /page-sections/:collection/:id/:sectionId`
153
+
154
+ The matching MCP tools — `add_page_section`, `reorder_page_sections`,
155
+ `remove_page_section` — carry the same notice in their descriptions so an agent reads
156
+ it before choosing a tool.
157
+
158
+ `PATCH /page-sections/:collection/:id/:sectionId` is **not** deprecated: it is how a
159
+ design-owned page is edited, including `visible` toggles. `GET` and the discovery /
160
+ validation routes are unchanged.
161
+
162
+ - 6f2eb6d: Generate design tokens from a single source, and render the editor canvas at real breakpoint widths.
163
+
164
+ **Tokens.** `design-system/tokens.json` is now the source of truth for every token
165
+ artifact. `pnpm --filter @prenta/admin generate` emits `tokens.css`, `tokens.scss`,
166
+ `tokens.tailwind.cjs`, and the `.prenta-admin`-scoped runtime copy
167
+ (`src/styles/prenta-tokens.css`) from it; a test fails the build if any generated file
168
+ is hand-edited or falls out of date. Previously the runtime copy was a hand
169
+ transcription of the handoff copy and the two had drifted — the radius scale was
170
+ renamed in one and not the other, and the WCAG correction to `--muted` existed only
171
+ downstream, where re-importing the handoff would have silently reverted it.
172
+
173
+ New tokens: the `--sp-*` spacing scale, `--h-chip`/`--h-field`/`--h-ctl` control
174
+ heights, `--bw`/`--bw-ctl`/`--bw-focus` border widths, `--ring`, `--r-field`,
175
+ `--r-fig`, `--mo-fast`/`--mo-base`/`--mo-slow` + `--ease`, the `--z-*` layer scale,
176
+ and `--fw-*` weights. `--r-control` and `--motion-fast`/`--motion-base` remain as
177
+ deprecated aliases resolving to their new names, so existing rules are unaffected.
178
+
179
+ **Breakpoints.** Presets moved to `src/lib/breakpoints.ts` — shared by the page and
180
+ post editors rather than owned by the page editor — and now carry the real CSS width
181
+ each device reports. `4K / Retina` is 2560px (previously mislabelled at 1920px),
182
+ `Wide Screen` 1920, `Laptop` 1440, plus `Tablet Landscape` 1024, `Tablet Portrait`
183
+ 768, and `Mobile` 390. The retired `1080p` and `tablet` ids coerce onto the presets
184
+ with the same width. `views/page-editor/viewports.ts` re-exports under the old names
185
+ and is deprecated.
186
+
187
+ **Canvas reflow.** `.prenta-canvas` now establishes an inline-size query container, and
188
+ the section renderers reflow on the new `@pv-sm|md|lg|xl|2xl` container steps (460 /
189
+ 700 / 900 / 1100 / 2000px) instead of Tailwind's `sm:`/`md:`/`lg:` viewport
190
+ breakpoints. Previously the previewed page reflowed against the _browser window_, so
191
+ selecting "Mobile" on a wide monitor rendered a 390px canvas still laid out for
192
+ desktop — the preview showed a width, not a layout. Containment lives on the shared
193
+ `.prenta-canvas` class rather than each host, because four surfaces render these
194
+ sections and a host that forgot it would silently render every breakpoint at the
195
+ narrowest layout. A test enforces both halves of the contract.
196
+
197
+ **Header controls.** The breakpoint control is now a grouped dropdown
198
+ (`BreakpointPicker`) rather than a segmented row — six presets with names like "Tablet
199
+ Landscape" no longer fit a header strip — plus a `ZoomChip` that reports the measured
200
+ scale and toggles fit ↔ actual size.
201
+
202
+ **Canvas.** `EditorCanvas` measures available width from a zero-height sentinel
203
+ instead of `clientWidth - padding`, which disagreed with the canvas's own metrics
204
+ once a scrollbar appeared or the host scaled the preview. It re-measures on every
205
+ render — page height changes on text edits and image swaps that resize nothing a
206
+ ResizeObserver watches — and the canvas surface now declares
207
+ `container-type: inline-size`, so sections reflow against the simulated width rather
208
+ than the browser viewport. A meta line reports breakpoint, page dimensions, and
209
+ scale.
210
+
211
+ - 6f2eb6d: Replace the page editor's fixed-width sections panel with a resizable, collapsible rail.
212
+
213
+ The rail is 300–620px, defaults to 360px, and persists its width to
214
+ `localStorage['pb.panelW']`. The resize handle is a real `separator` — arrow keys
215
+ resize it (Shift for a coarse step), `Home` resets, double-click resets — so the panel
216
+ is operable without a pointer. Collapsing **unmounts** the rail and swaps in a 38px
217
+ strip rather than animating the width to zero: a zero-width panel leaves a border
218
+ sliver and keeps its contents in the accessibility tree, where a screen-reader user
219
+ would land inside a panel nobody can see.
220
+
221
+ A tab strip across the top carries the panel's status so the rail reads at a glance
222
+ while collapsed — a section count on **Sections**, a band-coloured score dot on
223
+ **SEO & GEO** (with the score in the tab's accessible name, since colour alone is not
224
+ a signal). Arrow keys move between tabs.
225
+
226
+ **SEO moved from a right-hand aside into the rail.** `SeoEditorPane` now renders
227
+ directly inside the rail, which supplies the resizable container `ResizableAside` used
228
+ to provide. The header's SEO chip switches to that tab instead of opening a second
229
+ panel, so the editor has one settings surface rather than two competing ones.
230
+ `EditorSeoAside` is no longer used by the page editor.
231
+
232
+ The **Page** tab is not in this release — its content (address, publishing, search
233
+ directives, layout lock, danger zone) lands with the page-settings work. Page settings
234
+ remain reachable from the header, unchanged.
235
+
236
+ Also restores the stock Tailwind type scale inside `.prenta-canvas`. The admin remaps
237
+ six size keys onto its own tokens (`text-base` = 13px), which is right for admin chrome
238
+ and wrong for the canvas — that renders the consumer's website, where `text-base` must
239
+ be the 16px a visitor reads. The override lives in `@layer utilities` because layer
240
+ order beats specificity: in `@layer base` it would lose to the very utility it corrects.
241
+
242
+ - 6f2eb6d: The media block: every image slot is now the design's component, not a URL field
243
+
244
+ Section image slots were a URL text input, a Browse button that opened a modal,
245
+ and a thumbnail strip. The handoff specifies something quite different, and this
246
+ implements it.
247
+ - **The frame shows the crop the TEMPLATE renders** — 16:10 for a section image,
248
+ 1.91:1 for a social card — not the asset's own shape. Framing the asset would
249
+ tell an editor their portrait photo looks fine and then crop its subject out
250
+ on the page. What the asset actually is gets reported in the toolbar instead,
251
+ as `2400 × 1600 · 3:2` beside its filename, so both facts are visible and
252
+ neither is implied.
253
+ - **The toolbar is always visible**, never hover-only. Replace and Remove are
254
+ what an editor comes here to do; behind a hover they are undiscoverable on
255
+ touch and invisible to anyone scanning the panel.
256
+ - **The picker opens inside the shell**, not as a dialog. Choosing an image is a
257
+ comparison against the slot it is going into, and a modal covering that slot
258
+ removes the only context that matters. It lists only images, and shows each
259
+ asset's dimensions and ratio so a crop can be predicted before it happens.
260
+ - **The empty state uses the same frame**, so a slot the template renders is
261
+ visible as a slot before anything is in it — which is also what makes the
262
+ alt-text field beside it make sense.
263
+
264
+ Ratio labels snap to a name within a tolerance: 2401×1600 reads `3:2`, not
265
+ `1.50:1`. Assets with no stored dimensions say nothing rather than inventing
266
+ `1.00:1`. The format badge falls back to the MIME subtype, so a signed CDN link
267
+ with no file extension still shows `AVIF` instead of a blank chip.
268
+
269
+ The library is only requested when the picker is opened.
270
+
271
+ Clearing a slot now clears its stored width and height too. Leaving them behind
272
+ sized the next image by the previous one's box.
273
+
274
+ - 6f2eb6d: Page tab: address, publishing, search directives, layout, danger zone
275
+
276
+ The page editor's rail gains its third tab — everything about a page that is
277
+ neither its content nor its search copy, in five collapsible groups.
278
+
279
+ **Address** — the slug, the resolved full URL with a copy button, and a link to
280
+ the live page. When a page that is _already live_ changes path, the group says
281
+ so and offers to create the 301 on publish, naming both paths. A draft changing
282
+ its slug breaks nothing and says nothing: warning there would train editors to
283
+ dismiss the warning that matters. The redirect defaults to on, because an
284
+ unwanted rule is one an editor can delete while a missing one is 404s found by
285
+ visitors — and if creating it fails, the toast names both paths rather than
286
+ letting the failure pass as success.
287
+
288
+ **Publishing** — timestamps, schedule, and a byline picker (CMS user, brand, or
289
+ a one-off guest). The byline reaches the page's structured data. It does not
290
+ reassign the document's `createdById`: that records who created the record, and
291
+ trading an audit trail for a displayed name is not a swap worth making
292
+ silently. Status itself stays in the toolbar, so there is one way to publish
293
+ rather than two that can disagree.
294
+
295
+ **Search directives** — the five robots switches, an image-preview size, and the
296
+ literal `<meta name="robots">` line that will ship, composed by the same
297
+ function the renderer calls. Snippet and archive are disabled under `noindex`,
298
+ where they have no effect; an enabled control that changes nothing is a lie
299
+ about what it does.
300
+
301
+ These switches are deliberately not the admin's usual purple toggle. Purple
302
+ means "on", and "on" is the wrong axis — these are permissive vs restrictive,
303
+ and the state worth noticing is a page no longer allowed to be found. Green for
304
+ permitted, red for restricted, with the literal token (`index` / `noindex`)
305
+ beside each, because the failure to prevent is an editor flipping something
306
+ whose consequence they could not name.
307
+
308
+ **Layout** — template and schema, read-only, with the design-owned note.
309
+
310
+ **Danger zone** — duplicate and move-to-trash, with the trash confirming first
311
+ and saying when the page is live.
312
+
313
+ - 6f2eb6d: Reflow guard: per-field line counts, and a warning when an edit changes wrapping
314
+
315
+ Every guarded text field in the section inspector now carries a readout: the
316
+ character count, how many lines the copy runs to on the canvas you are looking
317
+ at, and one dot per breakpoint. When an edit makes the copy wrap to more lines
318
+ than the published version, the field says so in words — which breakpoints, how
319
+ many lines it was, how many it is now — and offers a button that takes the
320
+ canvas there.
321
+
322
+ This is the "it fits on my screen" failure: copy that reads fine on a laptop
323
+ pushes a hero button below the fold on a phone, and nothing in the editor said
324
+ so. Every dot is a button, because a warning about a width you are not looking
325
+ at is worth little if you then have to hunt for the breakpoint switcher.
326
+
327
+ **What "published" means here.** The baseline is what is live on the site, not
328
+ what was last saved. This CMS has no draft/published split — saving a PUBLISHED
329
+ page updates the live content in place — so the baseline is captured whenever
330
+ the editor takes delivery of a published page, on load and after each save and
331
+ publish. A page that has never been published has no baseline, and the guard
332
+ shows line counts with no deltas: there is no live layout to break.
333
+
334
+ **Measurement notes.** Line counts come from counting line boxes over the
335
+ element's contents, not from dividing its height by its line-height. The latter
336
+ counts padding as text — a button reading "Get Started" measured two lines
337
+ because its vertical padding came to about another line's worth of height, so
338
+ every CTA and boxed heading was over-reported.
339
+
340
+ Rich text is deliberately not guarded: the whole body is one marker, so a line
341
+ count across several paragraphs answers no question an editor is asking.
342
+
343
+ Shrinking copy is never flagged. Copy that got shorter cannot break a layout
344
+ that already held the longer version, and warning about it would put a warning
345
+ on exactly the edit an editor was asked to make.
346
+
347
+ Colour never carries the finding alone — each dot names its breakpoint, its line
348
+ count and its published line count, and the alert repeats all of it in text.
349
+
350
+ - 6f2eb6d: Reflow guard foundation: measure real components instead of a class chain
351
+
352
+ The editor can now tell you when an edit adds a line of wrapped text at a
353
+ breakpoint you are not looking at — the "it fits on my screen" failure, where
354
+ copy that reads fine on a laptop pushes a hero button below the fold on a
355
+ phone.
356
+
357
+ **How it measures.** The guard renders the actual section component off-screen
358
+ and reads the laid-out element, finding fields by the same `data-cms-field`
359
+ marker the click-to-edit bridge uses. The alternative — rebuilding each probe
360
+ from a hardcoded list of wrapper classes — is a second source of truth that has
361
+ to track the renderers by hand, and it cannot describe a consumer's own
362
+ registered renderer at all. Nothing here needs to stay in sync, and a renderer
363
+ refactor cannot silently invalidate it.
364
+
365
+ **What guards the guard.** A width cap that applies to the rendered element but
366
+ not to the probe makes the guard lie at exactly the widths where the cap binds
367
+ — so it looks correct on mobile and reports the wrong line count on laptop and
368
+ above. `e2e/reflow-probe.spec.ts` asserts probe width equals rendered width for
369
+ every guarded field on a real page, in a real browser. It has to run in
370
+ Playwright: `happy-dom` and `jsdom` do no layout, so the same assertion there
371
+ would pass while measuring nothing.
372
+
373
+ That test has already caught two real defects — a probe mounted outside
374
+ `.prenta-admin` inheriting the document's default font (a button measured 127px
375
+ rendered and 139px probed), and field paths being unique only within a section
376
+ rather than across a page.
377
+
378
+ **New in `@prenta/sections-react`:** `paddingClass`, `backgroundClass` and
379
+ `isDark` are now exported. The editor canvas wraps sections itself rather than
380
+ using `<Sections>`, so its padding has to match the site's exactly — a section
381
+ padded differently in the preview than on the site makes every line count taken
382
+ from it wrong. A parity test asserts the two agree.
383
+
384
+ - 6f2eb6d: Per-page `nosnippet`, `noarchive` and `max-image-preview`
385
+
386
+ Pages could set `noindex` and `nofollow` and nothing else. Three directives that
387
+ matter for how a page appears in results — and, for `nosnippet`, whether it can
388
+ be cited by an answer engine at all — had no representation anywhere in the
389
+ product.
390
+
391
+ The GEO audit made this visible: it already gated on whether snippets were
392
+ allowed, reading a value that was hardcoded `true` because nothing could set it.
393
+ A check that cannot fail and a control that does not exist are the same defect
394
+ seen from two ends.
395
+
396
+ **What ships.** `nosnippet`, `noarchive` and `max-image-preview` are now stored
397
+ per page, accepted by `PUT /seo/content/:type/:id`, returned by the matching
398
+ GET, and composed into the rendered `<meta name="robots">`. The GEO gate reads
399
+ the real value.
400
+
401
+ **One composer.** `@prenta/core/seo/robots-directives` owns composition, and
402
+ both the rendered head and the editor's preview call it. A preview computed by
403
+ different code from the one that renders is a preview that eventually lies —
404
+ the first version of this differed from the real output by a self-closing slash,
405
+ which is now asserted against the actual renderer.
406
+
407
+ The composer also encodes two rules worth stating. Nothing is emitted when every
408
+ directive is permissive: `index, follow` tells a crawler what it already
409
+ assumed, and a head full of no-op tags makes the meaningful ones harder to find.
410
+ And `noindex` suppresses the snippet and archive tokens, because a page that
411
+ cannot appear in results cannot show a snippet from them.
412
+
413
+ **Storage is restrictive-form** (`nosnippet: true` means blocked), matching the
414
+ existing `noindex`/`nofollow` keys, so every document written before this reads
415
+ as fully permissive rather than fully blocked.
416
+
417
+ No migration: these live in the document's existing content JSON.
418
+
419
+ - 6f2eb6d: Show the SEO/GEO audit in the editor rail, and fix the two scores that disagreed.
420
+
421
+ The rail's SEO tab now opens with twin score rings over the SEO and GEO check lists.
422
+ Each check states its outcome in text — the status square is decorative — and each one
423
+ that fails says what to do about it. A check that cannot apply is greyed **with the
424
+ reason and the enabling action**, so a score that moves because the applicable set
425
+ changed is explainable rather than mysterious.
426
+
427
+ The two scores stay separate rather than averaging into one headline: classic search
428
+ and AI answer visibility fail for different reasons and are fixed by different edits, so
429
+ an average would hide whichever half is broken.
430
+
431
+ **Fixes a live inconsistency.** The rail's tab dot called `computeLiveSeoScore` without
432
+ a focus keyword while the panel inside that tab analysed _with_ one, so the same page
433
+ showed two different scores an inch apart. The dot now reads the audit the panel
434
+ reports — the weaker of the two groups, so it reflects the half that needs work.
435
+
436
+ A gated group reads "not rated", not zero. A page held back from search has no search
437
+ performance to rate, and shouting zero at someone who noindexed a thank-you page on
438
+ purpose is noise.
439
+
440
+ Two inputs are passed as _unknown_ rather than absent, so their checks report
441
+ not-applicable instead of failing an editor for a gap in the plumbing: the OG image is
442
+ stored as a URL with no dimensions, which the check needs, and no author is surfaced
443
+ until the Page tab's author picker exists.
444
+
445
+ - 6f2eb6d: Make the editor canvas render the same section components the public site does.
446
+
447
+ `<AdminRoot sectionRenderers>` has always been documented as the way to "hand the
448
+ admin the exact same components it registers for the public site," but the canvas
449
+ consulted that map **only for section types it didn't already know**. Every built-in
450
+ type — hero, text, feature, stats, cta and the rest — short-circuited to the admin's
451
+ own approximation first, so a consumer passing `DEFAULT_SECTION_RENDERERS` from
452
+ `@prenta/sections-react` had most of their map silently ignored.
453
+
454
+ The two implementations had drifted accordingly. For a hero heading the public
455
+ component renders `text-4xl` at weight 700 stepping to `text-5xl`; the admin's
456
+ rendered `text-3xl` at weight 600. Body copy was 16px against the site's 18px. The
457
+ canvas was presenting itself as a preview of a page it was not rendering — copy that
458
+ fit on two lines there could wrap to three on the live site.
459
+
460
+ **Supplied renderers now take precedence for every type**, built-ins included. The
461
+ admin's own components remain as the fallback for consumers who pass nothing, and a
462
+ supplied renderer that throws falls back to the built-in for that type rather than to
463
+ the "custom section" placeholder.
464
+
465
+ Two things had to change for supplied renderers to actually run:
466
+ - `ADMIN_RENDER_OPTIONS` now carries `lcp` and `prefetchedForms`. Public renderers
467
+ call `options.lcp.claim()` during render, so hero, feature and gallery would have
468
+ thrown on their first image. The canvas controller always declines the priority
469
+ slot — nothing competes for bandwidth there, so every image lazy-loads.
470
+ - `AdminSectionRendererProps['options'].prefetchedForms` is typed loosely enough for
471
+ the real `SectionRendererMap` to remain assignable. Renderers are function types, so
472
+ `strictFunctionTypes` compares their parameters contravariantly and a narrower type
473
+ on the admin side made the whole map unassignable — defeating the point of the
474
+ mirror. `admin-renderer-compat.test.ts` now performs that assignment so the shapes
475
+ cannot drift apart silently again.
476
+
477
+ **Scaffolded projects get parity by default.** `create-prenta` wires
478
+ `DEFAULT_SECTION_RENDERERS` into `<AdminRoot>` for every template, merging any custom
479
+ section map over the top, and the generated admin route imports the site stylesheet so
480
+ those components arrive with their styles. Admin CSS still loads last and wins — it is
481
+ namespaced under `.prenta-admin` with a doubled-class specificity defence that exists
482
+ precisely because consumer sites import their stylesheet there.
483
+
484
+ **Consumers who wire the admin by hand** should pass `DEFAULT_SECTION_RENDERERS` (or
485
+ their own registered map) to `<AdminRoot sectionRenderers>` and ensure the admin
486
+ route's CSS covers `@prenta/sections-react`. Without both, the canvas keeps using the
487
+ admin's approximations — the previous behaviour, so nothing breaks by omission.
488
+
489
+ ### Patch Changes
490
+
491
+ - 6f2eb6d: Make every score in the page editor come from one model, banded by one scale.
492
+
493
+ Three surfaces showed a score and no two agreed. The rail's tab dot banded with
494
+ `gradeFromScore` (80/60) while the rings it summarised banded with the audit's
495
+ `scoreBand` (80/55), so a page scoring 57 read "fair" in the panel and "poor" on the
496
+ tab above it. The header chip showed `computeLiveSeoScore` — a third number from the
497
+ content analyser — so the header read 53 beside rings reading 0 and 57.
498
+
499
+ Now the tab dot and the header chip both read the audit and band with `scoreBand`.
500
+ `EditorSeoScoreBadge` takes the band from its caller rather than deriving one, because
501
+ the two scales legitimately differ: the audit bands at 80/55, `analyzeContent` at
502
+ 80/60/1, and colouring one model's number with another's thresholds is what caused
503
+ this.
504
+
505
+ **Removes a fallback that quietly reinstated the second scorer.** When both audit
506
+ groups were gated off, the tab fell back to `computeLiveSeoScore` — putting a number
507
+ from a different model, with different bands, on a tab summarising the audit. The tab
508
+ score is now three-valued: a number bands normally, `null` (deliberately unrated, e.g.
509
+ a noindexed page) renders neutral and announces "not rated", and `undefined` (not known
510
+ yet) renders no dot at all. No dot beats a dot computed by the wrong model.
511
+
512
+ One visible consequence: the header chip shows no number until the SEO tab has been
513
+ opened, because the audit is computed where the SEO record is fetched. That is a
514
+ deliberate trade — see `docs/page-builder-handoff-followups.md` for the fix that
515
+ removes it.
516
+
517
+ Also pins `@prenta/admin`'s packaging: a test asserts `sideEffects` is never `false`,
518
+ which would let bundlers drop the precompiled CSS import and render the admin unstyled
519
+ with no error anywhere. `@prenta/sections-react` hit exactly that and had to be
520
+ corrected before publish.
521
+
522
+ - 6f2eb6d: Editor canvas paints section backgrounds in the site's palette, not the admin theme's
523
+
524
+ The canvas used admin theme tokens (`bg-brand`, `bg-card`, `bg-muted`) for
525
+ section backgrounds. Those follow the editor's light/dark preference; the live
526
+ site does not. So an editor working in dark mode previewed a hero in `#9D71F0`
527
+ sitting on dark body sections, while the published page rendered `violet-600`
528
+ on white — a page no visitor would ever see.
529
+
530
+ In light mode this changes nothing visible: `--brand` resolves to `#7C3AED`,
531
+ which is `violet-600`, and `--card` is white. The entire discrepancy lived in
532
+ dark mode, which is where it was least likely to be spotted and most likely to
533
+ mislead. Six dark-mode baselines are updated; no light-mode capture moved.
534
+
535
+ It also resolves an inconsistency inside the admin's own implementation, where
536
+ a _gradient_ purple background used the site's raw palette while a _solid_
537
+ purple used a theme token — the two halves of an unfinished migration
538
+ disagreeing with each other.
539
+
540
+ **New in `@prenta/core/page-sections`:** `backgroundPreset`, `isDarkBackground`,
541
+ `BACKGROUND_PRESETS` and the `BackgroundPreset` type.
542
+
543
+ The two renderers still hold their own class strings, and have to. Tailwind
544
+ generates utilities by scanning source text, and each package's stylesheet
545
+ scans only its own `src/` tree — a literal shared from a third package is
546
+ generated by nobody, so the section renders with correct markup and no
547
+ background at all. (That failure mode is why `@prenta/sections-react` ships a
548
+ precompiled stylesheet in the first place.)
549
+
550
+ So core owns the _decision_ and each renderer maps it to its own strings
551
+ through a `Record<BackgroundPreset, string>`. Adding a preset now fails both
552
+ builds until each has handled it — verified by adding one and watching both
553
+ packages refuse to compile — and a parity test asserts the strings still match.
554
+
555
+ - 6f2eb6d: Let a saved draft's SEO be edited.
556
+
557
+ `GET /seo/content/:entityType/:entityId` resolved the record through
558
+ `gatherAuditEntities()`, which filters `status: 'PUBLISHED'`. A saved-but-unpublished
559
+ page therefore had no record, the GET 404'd, and the SEO pane rendered an error where
560
+ its form belongs — so title, description, focus keyword, schema, social preview and
561
+ length meters were all unreachable. The affected state is the normal one: saved, being
562
+ worked on, not yet published, which is exactly when metadata gets written.
563
+
564
+ It was an asymmetry rather than a policy — the matching `PUT` has no status filter, so
565
+ SEO could be written for a draft and never read back.
566
+
567
+ `GatherAuditOptions` gains an opt-in `documentIds`. When supplied the gather drops the
568
+ status filter: asking for a named document is a different question from auditing live
569
+ content. Every other caller — audits, redirect sweeps, navigation, the SEO list — is
570
+ unchanged and stays published-only. The per-document GET now scopes to the single id it
571
+ was asked for, which also avoids building every record to find one.
572
+
573
+ `SeoEditorPane` degrades instead of erroring: when no record loads it seeds the form
574
+ from the document's own meta and saving creates the record. That matters on its own,
575
+ because `@prenta/admin` peer-depends on `@prenta/core >=0.1.0` and can run against a
576
+ core without the route fix.
577
+
578
+ One visible consequence on unpublished pages: the GEO score rises, because the schema
579
+ type is now readable where it previously could not be loaded at all.
580
+
581
+ - 6f2eb6d: Give every page a score, from first paint, from one computation.
582
+
583
+ The header chip and the rail's tab dot now read an audit computed in the page
584
+ editor itself, so a score is present as soon as the page loads rather than only
585
+ after someone opens the SEO tab. While that tab is open it reports a live audit
586
+ built from unsaved form state, which supersedes the saved one — the score still
587
+ moves as you type.
588
+
589
+ Both paths build their input through `lib/page-audit-input.ts`, so they can differ
590
+ only in saved-versus-unsaved metadata — the difference an editor expects to see — and
591
+ never in which field feeds which check. A test asserts the two produce identical scores
592
+ for identical data. Three hand-written copies of that mapping is how the editor ended up
593
+ showing three different numbers.
594
+
595
+ **Also scores pages that have no SEO record yet.** A page that has never had SEO edited
596
+ had nothing to audit against, so the chip stayed blank on exactly the pages most in need
597
+ of a nudge. It now falls back to the document's own title and description, treating the
598
+ record-only inputs (focus keyword, schema, robots, OG image) as unset. That is honest —
599
+ none of them have been chosen — and the score improves the moment SEO is filled in.
600
+
601
+ - 6f2eb6d: Fix Page tab slug/path sync, publisher redirects, and stale audit after directives.
602
+
603
+ Changing the URL slug on the Page tab now keeps `path` in lockstep when it was
604
+ derived from the previous slug — the displayed full URL and the 301-on-publish
605
+ detector both read `path`, so a slug-only edit silently skipped the redirect
606
+ warning. Custom paths (e.g. `/team/about`) are left alone.
607
+
608
+ `POST /redirects` accepts the same roles that can publish a page (OWNER, ADMIN,
609
+ EDITOR), so an editor who moves a live slug can create the 301 the UI offers.
610
+ Users who cannot create redirects still see the URL-changed warning, without a
611
+ checkbox that would promise a redirect the API would reject.
612
+
613
+ Saving or publishing robots directives reloads the page audit so the header chip
614
+ and rail score update immediately after noindex/nosnippet flips.
615
+
616
+ - 6f2eb6d: Add a real search-result preview and length meters to the SEO editor.
617
+
618
+ `SerpPreview` renders the URL, title and description the way a searcher sees them.
619
+ The previous inline card painted the title in the brand accent, which made it a preview
620
+ of the admin rather than of a search result — the link blue is deliberately a literal
621
+ (`#1a0dab`, `#8ab4f8` in dark) rather than a token, because a brand accent repainting it
622
+ would quietly stop it being a preview.
623
+
624
+ `SeoCharCount` gains a meter bar and now names _which way_ a field is wrong: "too short"
625
+ means engines have less to match on, "will be truncated" means the tail is written but
626
+ nobody will read it. The old wording collapsed the short case into "aim for 30–60",
627
+ which states the target without saying which direction to edit. The bar stays full past
628
+ the maximum — one that kept growing would imply more is better.
629
+
630
+ **Removes a duplicate.** The pane carried its own `CharCount` (count only, no minimum)
631
+ while the fuller `SeoCharCount` existed but was referenced by nothing except its own
632
+ test. Both meta fields now use the one component, `min` is optional for cap-only fields,
633
+ and its range constants are the same ones the audit's title and description checks score
634
+ against — so the meter cannot say "good" while the check fails.
635
+
636
+ Note: these render only for pages that have a SEO content record, which today means
637
+ published pages. See `docs/page-builder-handoff-followups.md` #16 — the per-document GET
638
+ resolves through a published-only pipeline, so a saved draft cannot have its SEO edited
639
+ at all. That is a pre-existing gap, unchanged here.
640
+
641
+ - 6f2eb6d: Make the share image settable from the SEO editor.
642
+
643
+ The social card showed `record.ogImage` and nothing else — there was no way to choose,
644
+ replace or clear a share image from the SEO editor at all, so the one field with the
645
+ largest effect on how a link looks when shared could only be set elsewhere.
646
+
647
+ The card is now the control: an empty state that opens the media picker, a persistent
648
+ toolbar with Choose/Replace and Remove, and the filename alongside. The toolbar is
649
+ always visible rather than hover-only — a control an editor has to discover by hovering
650
+ is one most never find. Domain, title and description stay between the image and the
651
+ toolbar, so the card reads as the shared post rather than as a form with a thumbnail on
652
+ top.
653
+
654
+ The audit reacts immediately: `hasOgImage` reads the live form value, so picking or
655
+ clearing an image moves the score without a save round-trip. An empty string is treated
656
+ as a deliberate removal (the check fails) and is distinct from "an image exists but its
657
+ dimensions are unknown here" (not-applicable) — a test pins both.
658
+
659
+ The accordion carries an `Image set` / `No image` badge, so the state is readable while
660
+ collapsed. A shared link with no image is the single biggest drop in engagement, which
661
+ is worth stating without expanding the group.
662
+
663
+ Card type (`summary_large_image` vs `summary`) is not included: `PUT /seo/content`
664
+ accepts no `twitterCard` field, so it would need an API change rather than a UI one.
665
+
3
666
  ## 0.92.0
4
667
 
5
668
  ### Minor Changes