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