@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.
- package/CHANGELOG.md +663 -0
- package/design-system/tokens.css +173 -71
- package/design-system/tokens.json +244 -63
- package/design-system/tokens.scss +203 -98
- package/design-system/tokens.tailwind.cjs +93 -38
- package/dist/AdminRoot.d.ts +27 -6
- package/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/components/breakpoint-controls.render.test.d.ts +2 -0
- package/dist/__tests__/components/breakpoint-controls.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/breakpoint-controls.render.test.js +51 -0
- package/dist/__tests__/components/breakpoint-controls.render.test.js.map +1 -0
- package/dist/__tests__/components/canvas-edit-tip.render.test.d.ts +2 -0
- package/dist/__tests__/components/canvas-edit-tip.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/canvas-edit-tip.render.test.js +37 -0
- package/dist/__tests__/components/canvas-edit-tip.render.test.js.map +1 -0
- package/dist/__tests__/components/editor-rail.render.test.d.ts +2 -0
- package/dist/__tests__/components/editor-rail.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/editor-rail.render.test.js +138 -0
- package/dist/__tests__/components/editor-rail.render.test.js.map +1 -0
- package/dist/__tests__/components/media-block.render.test.d.ts +2 -0
- package/dist/__tests__/components/media-block.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/media-block.render.test.js +108 -0
- package/dist/__tests__/components/media-block.render.test.js.map +1 -0
- package/dist/__tests__/components/page-audit-panel.render.test.d.ts +2 -0
- package/dist/__tests__/components/page-audit-panel.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/page-audit-panel.render.test.js +59 -0
- package/dist/__tests__/components/page-audit-panel.render.test.js.map +1 -0
- package/dist/__tests__/components/reflow-meter.render.test.d.ts +2 -0
- package/dist/__tests__/components/reflow-meter.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/reflow-meter.render.test.js +94 -0
- package/dist/__tests__/components/reflow-meter.render.test.js.map +1 -0
- package/dist/__tests__/components/seo-char-count.test.js +24 -6
- package/dist/__tests__/components/seo-char-count.test.js.map +1 -1
- package/dist/__tests__/components/serp-preview.render.test.d.ts +2 -0
- package/dist/__tests__/components/serp-preview.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/serp-preview.render.test.js +25 -0
- package/dist/__tests__/components/serp-preview.render.test.js.map +1 -0
- package/dist/__tests__/lib/breakpoints.test.d.ts +2 -0
- package/dist/__tests__/lib/breakpoints.test.d.ts.map +1 -0
- package/dist/__tests__/lib/breakpoints.test.js +67 -0
- package/dist/__tests__/lib/breakpoints.test.js.map +1 -0
- package/dist/__tests__/lib/canvas-edit-chrome.test.d.ts +2 -0
- package/dist/__tests__/lib/canvas-edit-chrome.test.d.ts.map +1 -0
- package/dist/__tests__/lib/canvas-edit-chrome.test.js +44 -0
- package/dist/__tests__/lib/canvas-edit-chrome.test.js.map +1 -0
- package/dist/__tests__/lib/media-format.test.d.ts +2 -0
- package/dist/__tests__/lib/media-format.test.d.ts.map +1 -0
- package/dist/__tests__/lib/media-format.test.js +70 -0
- package/dist/__tests__/lib/media-format.test.js.map +1 -0
- package/dist/__tests__/lib/page-audit-input.test.d.ts +2 -0
- package/dist/__tests__/lib/page-audit-input.test.d.ts.map +1 -0
- package/dist/__tests__/lib/page-audit-input.test.js +122 -0
- package/dist/__tests__/lib/page-audit-input.test.js.map +1 -0
- package/dist/__tests__/lib/page-editor-service.test.js +2 -0
- package/dist/__tests__/lib/page-editor-service.test.js.map +1 -1
- package/dist/__tests__/lib/page-redirect.test.d.ts +2 -0
- package/dist/__tests__/lib/page-redirect.test.d.ts.map +1 -0
- package/dist/__tests__/lib/page-redirect.test.js +26 -0
- package/dist/__tests__/lib/page-redirect.test.js.map +1 -0
- package/dist/__tests__/lib/rail-width.test.d.ts +2 -0
- package/dist/__tests__/lib/rail-width.test.d.ts.map +1 -0
- package/dist/__tests__/lib/rail-width.test.js +69 -0
- package/dist/__tests__/lib/rail-width.test.js.map +1 -0
- package/dist/__tests__/lib/reflow-baseline.test.d.ts +2 -0
- package/dist/__tests__/lib/reflow-baseline.test.d.ts.map +1 -0
- package/dist/__tests__/lib/reflow-baseline.test.js +56 -0
- package/dist/__tests__/lib/reflow-baseline.test.js.map +1 -0
- package/dist/__tests__/lib/reflow-probe.test.d.ts +2 -0
- package/dist/__tests__/lib/reflow-probe.test.d.ts.map +1 -0
- package/dist/__tests__/lib/reflow-probe.test.js +73 -0
- package/dist/__tests__/lib/reflow-probe.test.js.map +1 -0
- package/dist/__tests__/lib/version-diff.test.d.ts +2 -0
- package/dist/__tests__/lib/version-diff.test.d.ts.map +1 -0
- package/dist/__tests__/lib/version-diff.test.js +230 -0
- package/dist/__tests__/lib/version-diff.test.js.map +1 -0
- package/dist/__tests__/lib/version-history.test.d.ts +2 -0
- package/dist/__tests__/lib/version-history.test.d.ts.map +1 -0
- package/dist/__tests__/lib/version-history.test.js +125 -0
- package/dist/__tests__/lib/version-history.test.js.map +1 -0
- package/dist/__tests__/styles/canvas-container-queries.test.d.ts +2 -0
- package/dist/__tests__/styles/canvas-container-queries.test.d.ts.map +1 -0
- package/dist/__tests__/styles/canvas-container-queries.test.js +87 -0
- package/dist/__tests__/styles/canvas-container-queries.test.js.map +1 -0
- package/dist/__tests__/styles/packaging.test.d.ts +2 -0
- package/dist/__tests__/styles/packaging.test.d.ts.map +1 -0
- package/dist/__tests__/styles/packaging.test.js +31 -0
- package/dist/__tests__/styles/packaging.test.js.map +1 -0
- package/dist/__tests__/styles/responsive-layer.test.d.ts +2 -0
- package/dist/__tests__/styles/responsive-layer.test.d.ts.map +1 -0
- package/dist/__tests__/styles/responsive-layer.test.js +127 -0
- package/dist/__tests__/styles/responsive-layer.test.js.map +1 -0
- package/dist/__tests__/styles/tokens-drift.test.d.ts +2 -0
- package/dist/__tests__/styles/tokens-drift.test.d.ts.map +1 -0
- package/dist/__tests__/styles/tokens-drift.test.js +92 -0
- package/dist/__tests__/styles/tokens-drift.test.js.map +1 -0
- package/dist/__tests__/views/admin-renderer-compat.test.d.ts +2 -0
- package/dist/__tests__/views/admin-renderer-compat.test.d.ts.map +1 -0
- package/dist/__tests__/views/admin-renderer-compat.test.js +43 -0
- package/dist/__tests__/views/admin-renderer-compat.test.js.map +1 -0
- package/dist/__tests__/views/admin-section-renderers.test.js +52 -4
- package/dist/__tests__/views/admin-section-renderers.test.js.map +1 -1
- package/dist/__tests__/views/canvas-review-mode.render.test.d.ts +2 -0
- package/dist/__tests__/views/canvas-review-mode.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/canvas-review-mode.render.test.js +70 -0
- package/dist/__tests__/views/canvas-review-mode.render.test.js.map +1 -0
- package/dist/__tests__/views/editor-top-bar.render.test.js +30 -1
- package/dist/__tests__/views/editor-top-bar.render.test.js.map +1 -1
- package/dist/__tests__/views/page-tab.render.test.d.ts +2 -0
- package/dist/__tests__/views/page-tab.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/page-tab.render.test.js +151 -0
- package/dist/__tests__/views/page-tab.render.test.js.map +1 -0
- package/dist/__tests__/views/section-class-parity.test.d.ts +2 -0
- package/dist/__tests__/views/section-class-parity.test.d.ts.map +1 -0
- package/dist/__tests__/views/section-class-parity.test.js +81 -0
- package/dist/__tests__/views/section-class-parity.test.js.map +1 -0
- package/dist/__tests__/views/sections-panel-design-owned.render.test.d.ts +2 -0
- package/dist/__tests__/views/sections-panel-design-owned.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/sections-panel-design-owned.render.test.js +86 -0
- package/dist/__tests__/views/sections-panel-design-owned.render.test.js.map +1 -0
- package/dist/__tests__/views/version-history-panel.render.test.d.ts +2 -0
- package/dist/__tests__/views/version-history-panel.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/version-history-panel.render.test.js +153 -0
- package/dist/__tests__/views/version-history-panel.render.test.js.map +1 -0
- package/dist/components/BreakpointPicker.d.ts +18 -0
- package/dist/components/BreakpointPicker.d.ts.map +1 -0
- package/dist/components/BreakpointPicker.js +31 -0
- package/dist/components/BreakpointPicker.js.map +1 -0
- package/dist/components/CanvasEditTip.d.ts +6 -0
- package/dist/components/CanvasEditTip.d.ts.map +1 -0
- package/dist/components/CanvasEditTip.js +22 -0
- package/dist/components/CanvasEditTip.js.map +1 -0
- package/dist/components/EditorRail.d.ts +36 -0
- package/dist/components/EditorRail.d.ts.map +1 -0
- package/dist/components/EditorRail.js +104 -0
- package/dist/components/EditorRail.js.map +1 -0
- package/dist/components/EditorRailTabs.d.ts +32 -0
- package/dist/components/EditorRailTabs.d.ts.map +1 -0
- package/dist/components/EditorRailTabs.js +45 -0
- package/dist/components/EditorRailTabs.js.map +1 -0
- package/dist/components/MediaBlock.d.ts +49 -0
- package/dist/components/MediaBlock.d.ts.map +1 -0
- package/dist/components/MediaBlock.js +50 -0
- package/dist/components/MediaBlock.js.map +1 -0
- package/dist/components/ReflowMeter.d.ts +28 -0
- package/dist/components/ReflowMeter.d.ts.map +1 -0
- package/dist/components/ReflowMeter.js +42 -0
- package/dist/components/ReflowMeter.js.map +1 -0
- package/dist/components/ZoomChip.d.ts +17 -0
- package/dist/components/ZoomChip.d.ts.map +1 -0
- package/dist/components/ZoomChip.js +17 -0
- package/dist/components/ZoomChip.js.map +1 -0
- package/dist/components/breakpoint-icons.d.ts +3 -0
- package/dist/components/breakpoint-icons.d.ts.map +1 -0
- package/dist/components/breakpoint-icons.js +16 -0
- package/dist/components/breakpoint-icons.js.map +1 -0
- package/dist/components/page-tab/AuthorPicker.d.ts +31 -0
- package/dist/components/page-tab/AuthorPicker.d.ts.map +1 -0
- package/dist/components/page-tab/AuthorPicker.js +95 -0
- package/dist/components/page-tab/AuthorPicker.js.map +1 -0
- package/dist/components/page-tab/DirectiveSwitch.d.ts +38 -0
- package/dist/components/page-tab/DirectiveSwitch.d.ts.map +1 -0
- package/dist/components/page-tab/DirectiveSwitch.js +33 -0
- package/dist/components/page-tab/DirectiveSwitch.js.map +1 -0
- package/dist/components/seo/EditorSeoScoreBadge.d.ts +8 -1
- package/dist/components/seo/EditorSeoScoreBadge.d.ts.map +1 -1
- package/dist/components/seo/EditorSeoScoreBadge.js +2 -2
- package/dist/components/seo/EditorSeoScoreBadge.js.map +1 -1
- package/dist/components/seo/PageAuditPanel.d.ts +14 -0
- package/dist/components/seo/PageAuditPanel.d.ts.map +1 -0
- package/dist/components/seo/PageAuditPanel.js +59 -0
- package/dist/components/seo/PageAuditPanel.js.map +1 -0
- package/dist/components/seo/SeoCharCount.d.ts +19 -7
- package/dist/components/seo/SeoCharCount.d.ts.map +1 -1
- package/dist/components/seo/SeoCharCount.js +30 -16
- package/dist/components/seo/SeoCharCount.js.map +1 -1
- package/dist/components/seo/SeoEditorPane.d.ts +15 -1
- package/dist/components/seo/SeoEditorPane.d.ts.map +1 -1
- package/dist/components/seo/SeoEditorPane.js +118 -21
- package/dist/components/seo/SeoEditorPane.js.map +1 -1
- package/dist/components/seo/SerpPreview.d.ts +20 -0
- package/dist/components/seo/SerpPreview.d.ts.map +1 -0
- package/dist/components/seo/SerpPreview.js +6 -0
- package/dist/components/seo/SerpPreview.js.map +1 -0
- package/dist/hooks/usePageAudit.d.ts +41 -0
- package/dist/hooks/usePageAudit.d.ts.map +1 -0
- package/dist/hooks/usePageAudit.js +68 -0
- package/dist/hooks/usePageAudit.js.map +1 -0
- package/dist/hooks/useReflowGuard.d.ts +47 -0
- package/dist/hooks/useReflowGuard.d.ts.map +1 -0
- package/dist/hooks/useReflowGuard.js +52 -0
- package/dist/hooks/useReflowGuard.js.map +1 -0
- package/dist/layout/primitives/AdminShell.d.ts.map +1 -1
- package/dist/layout/primitives/AdminShell.js +2 -2
- package/dist/layout/primitives/AdminShell.js.map +1 -1
- package/dist/lib/breakpoints.d.ts +51 -0
- package/dist/lib/breakpoints.d.ts.map +1 -0
- package/dist/lib/breakpoints.js +84 -0
- package/dist/lib/breakpoints.js.map +1 -0
- package/dist/lib/canvas-edit-chrome.d.ts +22 -0
- package/dist/lib/canvas-edit-chrome.d.ts.map +1 -0
- package/dist/lib/canvas-edit-chrome.js +81 -0
- package/dist/lib/canvas-edit-chrome.js.map +1 -0
- package/dist/lib/media-format.d.ts +55 -0
- package/dist/lib/media-format.d.ts.map +1 -0
- package/dist/lib/media-format.js +93 -0
- package/dist/lib/media-format.js.map +1 -0
- package/dist/lib/page-audit-input.d.ts +45 -0
- package/dist/lib/page-audit-input.d.ts.map +1 -0
- package/dist/lib/page-audit-input.js +53 -0
- package/dist/lib/page-audit-input.js.map +1 -0
- package/dist/lib/page-editor-service.d.ts +12 -0
- package/dist/lib/page-editor-service.d.ts.map +1 -1
- package/dist/lib/page-editor-service.js +6 -0
- package/dist/lib/page-editor-service.js.map +1 -1
- package/dist/lib/page-redirect.d.ts +39 -0
- package/dist/lib/page-redirect.d.ts.map +1 -0
- package/dist/lib/page-redirect.js +38 -0
- package/dist/lib/page-redirect.js.map +1 -0
- package/dist/lib/rail-width.d.ts +23 -0
- package/dist/lib/rail-width.d.ts.map +1 -0
- package/dist/lib/rail-width.js +59 -0
- package/dist/lib/rail-width.js.map +1 -0
- package/dist/lib/reflow-baseline.d.ts +45 -0
- package/dist/lib/reflow-baseline.d.ts.map +1 -0
- package/dist/lib/reflow-baseline.js +38 -0
- package/dist/lib/reflow-baseline.js.map +1 -0
- package/dist/lib/reflow-probe.d.ts +159 -0
- package/dist/lib/reflow-probe.d.ts.map +1 -0
- package/dist/lib/reflow-probe.js +228 -0
- package/dist/lib/reflow-probe.js.map +1 -0
- package/dist/lib/seo-service.d.ts +10 -0
- package/dist/lib/seo-service.d.ts.map +1 -1
- package/dist/lib/seo-service.js.map +1 -1
- package/dist/lib/version-diff.d.ts +50 -0
- package/dist/lib/version-diff.d.ts.map +1 -0
- package/dist/lib/version-diff.js +294 -0
- package/dist/lib/version-diff.js.map +1 -0
- package/dist/lib/version-history.d.ts +73 -0
- package/dist/lib/version-history.d.ts.map +1 -0
- package/dist/lib/version-history.js +126 -0
- package/dist/lib/version-history.js.map +1 -0
- package/dist/prenta-admin.css +1 -1
- package/dist/views/page-editor/CanvasMetaLine.d.ts +19 -0
- package/dist/views/page-editor/CanvasMetaLine.d.ts.map +1 -0
- package/dist/views/page-editor/CanvasMetaLine.js +23 -0
- package/dist/views/page-editor/CanvasMetaLine.js.map +1 -0
- package/dist/views/page-editor/EditorCanvas.d.ts +47 -8
- package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -1
- package/dist/views/page-editor/EditorCanvas.js +68 -34
- package/dist/views/page-editor/EditorCanvas.js.map +1 -1
- package/dist/views/page-editor/EditorTopBar.d.ts +21 -2
- package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
- package/dist/views/page-editor/EditorTopBar.js +13 -20
- package/dist/views/page-editor/EditorTopBar.js.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.js +383 -78
- package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
- package/dist/views/page-editor/PageTab.d.ts +36 -0
- package/dist/views/page-editor/PageTab.d.ts.map +1 -0
- package/dist/views/page-editor/PageTab.js +65 -0
- package/dist/views/page-editor/PageTab.js.map +1 -0
- package/dist/views/page-editor/ReviewBar.d.ts +21 -0
- package/dist/views/page-editor/ReviewBar.d.ts.map +1 -0
- package/dist/views/page-editor/ReviewBar.js +22 -0
- package/dist/views/page-editor/ReviewBar.js.map +1 -0
- package/dist/views/page-editor/SectionInspector.d.ts.map +1 -1
- package/dist/views/page-editor/SectionInspector.js +64 -20
- package/dist/views/page-editor/SectionInspector.js.map +1 -1
- package/dist/views/page-editor/SectionsPanel.d.ts +30 -5
- package/dist/views/page-editor/SectionsPanel.d.ts.map +1 -1
- package/dist/views/page-editor/SectionsPanel.js +38 -7
- package/dist/views/page-editor/SectionsPanel.js.map +1 -1
- package/dist/views/page-editor/SitePreviewFrame.d.ts.map +1 -1
- package/dist/views/page-editor/SitePreviewFrame.js +3 -0
- package/dist/views/page-editor/SitePreviewFrame.js.map +1 -1
- package/dist/views/page-editor/VersionHistoryPanel.d.ts +29 -0
- package/dist/views/page-editor/VersionHistoryPanel.d.ts.map +1 -0
- package/dist/views/page-editor/VersionHistoryPanel.js +81 -0
- package/dist/views/page-editor/VersionHistoryPanel.js.map +1 -0
- package/dist/views/page-editor/reflow-context.d.ts +28 -0
- package/dist/views/page-editor/reflow-context.d.ts.map +1 -0
- package/dist/views/page-editor/reflow-context.js +14 -0
- package/dist/views/page-editor/reflow-context.js.map +1 -0
- package/dist/views/page-editor/sections/ByTheNumbersSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/ByTheNumbersSection.js +1 -1
- package/dist/views/page-editor/sections/ByTheNumbersSection.js.map +1 -1
- package/dist/views/page-editor/sections/CtaBannerSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/CtaBannerSection.js +1 -1
- package/dist/views/page-editor/sections/CtaBannerSection.js.map +1 -1
- package/dist/views/page-editor/sections/FeatureHighlightSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/FeatureHighlightSection.js +2 -2
- package/dist/views/page-editor/sections/FeatureHighlightSection.js.map +1 -1
- package/dist/views/page-editor/sections/GallerySection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/GallerySection.js +7 -3
- package/dist/views/page-editor/sections/GallerySection.js.map +1 -1
- package/dist/views/page-editor/sections/HeroBannerSection.js +1 -1
- package/dist/views/page-editor/sections/HeroBannerSection.js.map +1 -1
- package/dist/views/page-editor/sections/MissionSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/MissionSection.js +1 -1
- package/dist/views/page-editor/sections/MissionSection.js.map +1 -1
- package/dist/views/page-editor/sections/PullQuoteSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/PullQuoteSection.js +1 -1
- package/dist/views/page-editor/sections/PullQuoteSection.js.map +1 -1
- package/dist/views/page-editor/sections/RelatedPostsSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/RelatedPostsSection.js +1 -1
- package/dist/views/page-editor/sections/RelatedPostsSection.js.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.d.ts +13 -1
- package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.js +66 -26
- package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -1
- package/dist/views/page-editor/sections/admin-renderers.d.ts +47 -15
- package/dist/views/page-editor/sections/admin-renderers.d.ts.map +1 -1
- package/dist/views/page-editor/sections/admin-renderers.js +27 -15
- package/dist/views/page-editor/sections/admin-renderers.js.map +1 -1
- package/dist/views/page-editor/sections/parts.d.ts +0 -1
- package/dist/views/page-editor/sections/parts.d.ts.map +1 -1
- package/dist/views/page-editor/sections/parts.js +41 -16
- package/dist/views/page-editor/sections/parts.js.map +1 -1
- package/dist/views/page-editor/viewports.d.ts +7 -14
- package/dist/views/page-editor/viewports.d.ts.map +1 -1
- package/dist/views/page-editor/viewports.js +7 -12
- package/dist/views/page-editor/viewports.js.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.d.ts +2 -1
- package/dist/views/post-editor/PostEditorCanvas.d.ts.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.js +5 -2
- package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.js +2 -20
- package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
- package/dist/views/post-editor/PostTemplateEditor.d.ts.map +1 -1
- package/dist/views/post-editor/PostTemplateEditor.js +4 -1
- package/dist/views/post-editor/PostTemplateEditor.js.map +1 -1
- package/package.json +5 -3
- package/src/AdminRoot.tsx +27 -6
- package/src/__tests__/components/breakpoint-controls.render.test.tsx +57 -0
- package/src/__tests__/components/canvas-edit-tip.render.test.tsx +41 -0
- package/src/__tests__/components/editor-rail.render.test.tsx +192 -0
- package/src/__tests__/components/media-block.render.test.tsx +126 -0
- package/src/__tests__/components/page-audit-panel.render.test.tsx +64 -0
- package/src/__tests__/components/reflow-meter.render.test.tsx +144 -0
- package/src/__tests__/components/seo-char-count.test.tsx +26 -6
- package/src/__tests__/components/serp-preview.render.test.tsx +32 -0
- package/src/__tests__/lib/breakpoints.test.ts +84 -0
- package/src/__tests__/lib/canvas-edit-chrome.test.ts +56 -0
- package/src/__tests__/lib/media-format.test.ts +89 -0
- package/src/__tests__/lib/page-audit-input.test.ts +144 -0
- package/src/__tests__/lib/page-editor-service.test.ts +2 -0
- package/src/__tests__/lib/page-redirect.test.ts +29 -0
- package/src/__tests__/lib/rail-width.test.ts +86 -0
- package/src/__tests__/lib/reflow-baseline.test.ts +64 -0
- package/src/__tests__/lib/reflow-probe.test.ts +95 -0
- package/src/__tests__/lib/version-diff.test.ts +278 -0
- package/src/__tests__/lib/version-history.test.ts +166 -0
- package/src/__tests__/styles/canvas-container-queries.test.ts +103 -0
- package/src/__tests__/styles/packaging.test.ts +38 -0
- package/src/__tests__/styles/responsive-layer.test.ts +152 -0
- package/src/__tests__/styles/tokens-drift.test.ts +110 -0
- package/src/__tests__/views/admin-renderer-compat.test.ts +48 -0
- package/src/__tests__/views/admin-section-renderers.test.tsx +70 -4
- package/src/__tests__/views/canvas-review-mode.render.test.tsx +94 -0
- package/src/__tests__/views/editor-top-bar.render.test.tsx +43 -1
- package/src/__tests__/views/page-tab.render.test.tsx +226 -0
- package/src/__tests__/views/section-class-parity.test.ts +98 -0
- package/src/__tests__/views/sections-panel-design-owned.render.test.tsx +104 -0
- package/src/__tests__/views/version-history-panel.render.test.tsx +195 -0
- package/src/components/BreakpointPicker.tsx +114 -0
- package/src/components/CanvasEditTip.tsx +46 -0
- package/src/components/EditorRail.tsx +219 -0
- package/src/components/EditorRailTabs.tsx +117 -0
- package/src/components/MediaBlock.tsx +223 -0
- package/src/components/ReflowMeter.tsx +125 -0
- package/src/components/ZoomChip.tsx +39 -0
- package/src/components/breakpoint-icons.ts +17 -0
- package/src/components/page-tab/AuthorPicker.tsx +240 -0
- package/src/components/page-tab/DirectiveSwitch.tsx +89 -0
- package/src/components/seo/EditorSeoScoreBadge.tsx +9 -1
- package/src/components/seo/PageAuditPanel.tsx +192 -0
- package/src/components/seo/SeoCharCount.tsx +55 -24
- package/src/components/seo/SeoEditorPane.tsx +213 -31
- package/src/components/seo/SerpPreview.tsx +41 -0
- package/src/hooks/usePageAudit.ts +98 -0
- package/src/hooks/useReflowGuard.ts +96 -0
- package/src/layout/primitives/AdminShell.tsx +13 -1
- package/src/lib/breakpoints.ts +107 -0
- package/src/lib/canvas-edit-chrome.ts +82 -0
- package/src/lib/media-format.ts +99 -0
- package/src/lib/page-audit-input.ts +104 -0
- package/src/lib/page-editor-service.ts +18 -0
- package/src/lib/page-redirect.ts +60 -0
- package/src/lib/rail-width.ts +58 -0
- package/src/lib/reflow-baseline.ts +70 -0
- package/src/lib/reflow-probe.ts +299 -0
- package/src/lib/seo-service.ts +10 -0
- package/src/lib/version-diff.ts +376 -0
- package/src/lib/version-history.ts +192 -0
- package/src/styles/prenta-tokens.css +120 -42
- package/src/styles/responsive.css +281 -0
- package/src/styles/theme.css +138 -0
- package/src/views/page-editor/CanvasMetaLine.tsx +50 -0
- package/src/views/page-editor/EditorCanvas.tsx +140 -54
- package/src/views/page-editor/EditorTopBar.tsx +55 -51
- package/src/views/page-editor/PageSectionEditor.tsx +575 -185
- package/src/views/page-editor/PageTab.tsx +433 -0
- package/src/views/page-editor/ReviewBar.tsx +76 -0
- package/src/views/page-editor/SectionInspector.tsx +84 -42
- package/src/views/page-editor/SectionsPanel.tsx +282 -141
- package/src/views/page-editor/SitePreviewFrame.tsx +3 -0
- package/src/views/page-editor/VersionHistoryPanel.tsx +302 -0
- package/src/views/page-editor/reflow-context.ts +43 -0
- package/src/views/page-editor/sections/ByTheNumbersSection.tsx +3 -2
- package/src/views/page-editor/sections/CtaBannerSection.tsx +3 -1
- package/src/views/page-editor/sections/FeatureHighlightSection.tsx +3 -2
- package/src/views/page-editor/sections/GallerySection.tsx +7 -3
- package/src/views/page-editor/sections/HeroBannerSection.tsx +2 -2
- package/src/views/page-editor/sections/MissionSection.tsx +4 -2
- package/src/views/page-editor/sections/PullQuoteSection.tsx +3 -1
- package/src/views/page-editor/sections/RelatedPostsSection.tsx +2 -1
- package/src/views/page-editor/sections/SectionRenderer.tsx +115 -34
- package/src/views/page-editor/sections/admin-renderers.tsx +54 -15
- package/src/views/page-editor/sections/parts.tsx +46 -18
- package/src/views/page-editor/viewports.ts +13 -26
- package/src/views/post-editor/PostEditorCanvas.tsx +18 -11
- package/src/views/post-editor/PostSectionEditor.tsx +1 -56
- 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
|