You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
π₯ multi-assumptions: variant B of 3 parallel framings of the same goal.
Sibling variant(s): #384 and #386. Please compare and tell us which assumption is load-bearing.
My goal
Bring Plate's paged view to production parity with Word/Pages: page-layout state β margins, header/footer chrome and content, page-number position, first-page-different rule, and footnote placement (footer or document end) β becomes fully user-customizable via UI; clicking into a header or footer presents a friendly chrome shell instead of raw editable text; and the paged-view PlateStatic preview renders the same plugin surface as the live editor (currently AI, comments, suggestions, drag-handle, and other chrome-only or editOnly plugins are silently dropped by getElementOnlyStaticPlugins in packages/pagination/src/react/page-frame.tsx:322-362).
To do so, do this (variant B β overlay store + scoped chrome sub-editors)
Introduce pageLayoutStore (Zustand or React context, document-keyed by metadata) holding: margins, pageSize, headerHeight, footerHeight, footnoteWell, footnotePlacement, pageNumber: { region: 'header'|'footer', align, format, startAt, hideOnFirst }, firstPageDifferent: boolean, and IDs for chrome editors.
Header and footer become separate Plate editor instances mounted by PageOverlay instead of nodes in editor.children. Same plugin set as the body editor, scoped to a one-row content. State persists in document metadata, not in editor.children.
PageFrame (packages/pagination/src/react/page-frame.tsx) consumes the store; chrome regions render the live header/footer editors (paged mode) β not PlateStatic. The static filter now only matters for the print/PDF path, not for on-screen paged preview.
Selection UX: each chrome region is its own editor, so it gets its own focus state, toolbar, and a chrome shell label ("Header", "Footer", "First-page header"). Body editor visibly defocuses while chrome is active. No selection inside the body ever lands inside chrome β no "ugly text" artifact.
"First page different" β store flag mounts a second header editor + second footer editor; PageFrame swaps which to render based on page.pageIndex === 0.
Page number β independent slot inside chrome regions, positioned by store config; rendered as a non-editable React element, not a Slate void, so users cannot accidentally move/delete it via body selection.
Margin customization β Page Setup dialog mutates the store; PageOverlay re-renders. Same dialog UX as variant A but writes to the store, not editor options.
Footnote placement β store flag drives paginate.ts which path to take; allocator stays the same.
Print/PDF (PlateStatic) path: wraps body + each chrome editor in PlateStatic with the existing static-safe filter. The filter still drops some plugins for print, but on-screen paged editing is no longer affected.
Acceptance: in dev-browser, paged mode shows live, fully-functional header/footer editors (typing in header works without "ugly text"); selecting a chrome region focuses it with a label; first page can have a different header; page-number position is store-driven; Page Setup dialog edits margins/heights/footnote placement; on-screen paged preview renders ALL live editor plugins (AI, comments, suggestions, drag-handle) inside body since it is a real editor, not PlateStatic.
Because the code is
packages/pagination/src/react/page-frame.tsx:107-158 β chrome regions today render via StaticPageValue (PlateStatic); replacing those with live sub-editors removes the static-filter surface for paged preview entirely.
packages/pagination/src/react/page-frame.tsx:322-362 β getElementOnlyStaticPlugins is the source of "PlateStatic page viewer doesn't render several plugins". Variant B sidesteps it for paged preview by not using PlateStatic there.
packages/pagination/src/lib/transforms/ensureHeader.ts / ensureFooter.ts β body-coupled chrome nodes; variant B stops persisting chrome inside editor.children, removing the need for these invariants.
packages/pagination/src/lib/types.ts β BasePaginationOptions becomes a thin pointer to pageLayoutStore for runtime; backward-compat shim keeps setMargins/setMode etc. as transforms that proxy into the store.
packages/pagination/src/react/use-pretext-measurer.ts β measurer is read-only over body content; unaffected by chrome sub-editors.
packages/pagination/src/lib/paginate.ts β needs a small change: skip body-internal header/footer when computing page allocation since they no longer live in the body.
Validate this approach β especially but not limited to
Static analysis of packages/pagination/src/react/page-frame.tsx, packages/pagination/src/react/page-overlay.tsx, packages/pagination/src/react/standard-frame.tsx, packages/pagination/src/lib/paginate.ts, packages/pagination/src/lib/transforms/, packages/pagination/src/lib/allocate-footnotes.ts.
Review of our own code in packages/pagination/src/react/, packages/core/src/static/components/PlateStatic.tsx, apps/www/src/registry/components/editor/plugins/pagination-kit.tsx.
Mounting one extra Plate editor per chrome region (potentially Γ2 with first-page-different) β what's the perf cost, undo-stack story, copy/paste behavior between body and chrome?
How do shared concerns (comments, AI suggestions, search) work across body + chrome editors? Is there a multi-editor coordination layer this assumes?
Does removing chrome from editor.children break existing DOCX export adapters that read header/footer from those nodes?
Persistence: where does pageLayoutStore actually live in user data β document JSON metadata, separate field, server-side?
@coderabbitai plan
π₯ multi-assumptions: variant B of 3 parallel framings of the same goal.
Sibling variant(s): #384 and #386. Please compare and tell us which assumption is load-bearing.
My goal
Bring Plate's paged view to production parity with Word/Pages: page-layout state β margins, header/footer chrome and content, page-number position, first-page-different rule, and footnote placement (footer or document end) β becomes fully user-customizable via UI; clicking into a header or footer presents a friendly chrome shell instead of raw editable text; and the paged-view PlateStatic preview renders the same plugin surface as the live editor (currently AI, comments, suggestions, drag-handle, and other chrome-only or
editOnlyplugins are silently dropped bygetElementOnlyStaticPluginsinpackages/pagination/src/react/page-frame.tsx:322-362).To do so, do this (variant B β overlay store + scoped chrome sub-editors)
pageLayoutStore(Zustand or React context, document-keyed by metadata) holding:margins,pageSize,headerHeight,footerHeight,footnoteWell,footnotePlacement,pageNumber: { region: 'header'|'footer', align, format, startAt, hideOnFirst },firstPageDifferent: boolean, and IDs for chrome editors.PageOverlayinstead of nodes ineditor.children. Same plugin set as the body editor, scoped to a one-row content. State persists in document metadata, not ineditor.children.PageFrame(packages/pagination/src/react/page-frame.tsx) consumes the store; chrome regions render the live header/footer editors (paged mode) β notPlateStatic. The static filter now only matters for the print/PDF path, not for on-screen paged preview.PageFrameswaps which to render based onpage.pageIndex === 0.PageOverlayre-renders. Same dialog UX as variant A but writes to the store, not editor options.paginate.tswhich path to take; allocator stays the same.PlateStaticwith the existing static-safe filter. The filter still drops some plugins for print, but on-screen paged editing is no longer affected.Acceptance: in
dev-browser, paged mode shows live, fully-functional header/footer editors (typing in header works without "ugly text"); selecting a chrome region focuses it with a label; first page can have a different header; page-number position is store-driven; Page Setup dialog edits margins/heights/footnote placement; on-screen paged preview renders ALL live editor plugins (AI, comments, suggestions, drag-handle) inside body since it is a real editor, not PlateStatic.Because the code is
packages/pagination/src/react/page-frame.tsx:107-158β chrome regions today render viaStaticPageValue(PlateStatic); replacing those with live sub-editors removes the static-filter surface for paged preview entirely.packages/pagination/src/react/page-frame.tsx:322-362βgetElementOnlyStaticPluginsis the source of "PlateStatic page viewer doesn't render several plugins". Variant B sidesteps it for paged preview by not using PlateStatic there.packages/pagination/src/lib/transforms/ensureHeader.ts/ensureFooter.tsβ body-coupled chrome nodes; variant B stops persisting chrome insideeditor.children, removing the need for these invariants.packages/pagination/src/lib/types.tsβBasePaginationOptionsbecomes a thin pointer topageLayoutStorefor runtime; backward-compat shim keepssetMargins/setModeetc. as transforms that proxy into the store.packages/pagination/src/react/use-pretext-measurer.tsβ measurer is read-only over body content; unaffected by chrome sub-editors.packages/pagination/src/lib/paginate.tsβ needs a small change: skip body-internal header/footer when computing page allocation since they no longer live in the body.Assumptions deliberately shuffled vs variant A
pageLayoutStore+ scoped chrome sub-editorseditor.options+editor.children(chrome nodes)PageOverlayfirstPageHeader/firstPageFooternode typesstaticSafeopt-in extends the static filterChromeShell+ 1 dialogAssumptions deliberately shuffled vs variant C
Section.sectPrnodessectPr,pgNumType,titlePg,footnotePr)titlePg+titlePgHdrRef/titlePgFtrRefValidate this approach β especially but not limited to
packages/pagination/src/react/page-frame.tsx,packages/pagination/src/react/page-overlay.tsx,packages/pagination/src/react/standard-frame.tsx,packages/pagination/src/lib/paginate.ts,packages/pagination/src/lib/transforms/,packages/pagination/src/lib/allocate-footnotes.ts.packages/pagination/src/react/,packages/core/src/static/components/PlateStatic.tsx,apps/www/src/registry/components/editor/plugins/pagination-kit.tsx.editor.childrenbreak existing DOCX export adapters that read header/footer from those nodes?pageLayoutStoreactually live in user data β document JSON metadata, separate field, server-side?Full plan
pageLayoutStoreshape + persistence contract (document metadata field).PageOverlay; one editor per chrome ID, plugin set inherited from body.PageFramechrome regions to render sub-editors instead ofStaticPageValue.paginate.tsto ignore body-internal header/footer and consume store.firstPageDifferentswap logic inPageOverlaybased onpage.pageIndex.editor.tf.pagination.*transforms as proxies for back-compat.PlateStatic; on-screen paged preview no longer touchesPlateStatic.dev-browserverification of all seven user-reported issues.References
packages/pagination/src/react/page-frame.tsxpackages/pagination/src/react/page-overlay.tsxpackages/pagination/src/lib/paginate.tspackages/pagination/src/lib/transforms/ensureHeader.tspackages/core/src/static/components/PlateStatic.tsxcodex/pagination-variant-a