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 C of 3 parallel framings of the same goal.
Sibling variant(s): #384 and #385. 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 C β OOXML section model, DOCX-native shape)
Adopt OOXML's section-properties (sectPr) model as the source of truth. Introduce a new top-level Section node type carrying sectPr:
Headers and footers stored as separate ID-keyed Plate values in a chromeRegistry: Record<HeaderFooterId, TElement[]> (mirroring DOCX's header1.xml / footer1.xml). Persisted alongside the document β not inside the body editor's editor.children.
Body wrapped in one or more Section nodes; each section's sectPr references chrome by ID. Single-section docs are the trivial case; multi-section emerges naturally.
PageFrame (packages/pagination/src/react/page-frame.tsx) reads sectPr for the section that owns the current page; renders chrome by mounting the referenced chrome editor(s) with the same plugin set as the body.
Chrome shell UX: clicking a chrome region focuses the dedicated chrome editor for that ID, with a label like Section 1 β header (default) / Section 1 β header (first page). Body editor defocuses.
First-page-different β sectPr.titlePg = true; PageFrame uses titlePgHdrRef / titlePgFtrRef for page index 0 of the section. Symmetric for first-page footer.
Page number β consume sectPr.pgNumType.position and pgNumType.fmt; render as a non-editable structured slot inside the chrome region. pgNumType.start per section enables the Word-style "restart numbering at chapter".
Footnote placement β consume sectPr.footnotePr.pos; allocator (packages/pagination/src/lib/allocate-footnotes.ts) routes definitions per section: pageBottom β per-page footer well; sectEnd β end of the section's last page; docEnd β end of the entire document.
Page Setup dialog operates on the active section's sectPr directly (per-section, not document-level). Multi-section docs get a section selector.
PlateStatic gap: each chrome region mounts its own chrome editor (real editor) for paged preview, so the static filter is not in the paged paint path. Print/PDF wraps the body editor + each chrome editor in PlateStatic instances, applying the existing static-safe filter only there.
Acceptance: in dev-browser, a multi-section document shows independent margins / chrome / page-number formats per section; first-page-different works per section; footnote placement can be set to footer / section-end / document-end; chrome shells are labeled per section; round-tripping a Word DOCX with two sections, a title-page header, and bottom-of-page footnotes preserves all of it (loaded β exported β diff is clean).
Because the code is
packages/pagination/src/lib/types.ts β current BasePaginationOptions is document-level only (one margins, one mode, one pageSize). Cannot represent per-section variation. Variant C replaces this with sectPr per section.
packages/pagination/src/lib/transforms/ensureHeader.ts / ensureFooter.ts β assume single doc-level chrome nodes inside editor.children. Replaced by chromeRegistry lookup.
packages/pagination/src/lib/paginate.ts β currently allocates pages over the whole body uniformly. Needs to walk section-by-section with per-section margins / page-size / chrome.
packages/pagination/src/lib/allocate-footnotes.ts β currently knows 'footer' | 'documentEnd'; OOXML extends to pageBottom | beneathText | sectEnd | docEnd. Variant C aligns the type to OOXML.
packages/pagination/src/react/page-frame.tsx:107-158, 156, 322-362 β chrome rendered through StaticPageValue (the source of the dropped-plugins issue) and page-number is hardcoded. Variant C replaces both with per-section sectPr consumption + live chrome editors.
packages/docx-io (DOCX I/O package, if present in the workspace) β variant C lets the importer/exporter map sectPr / headerN.xml / footerN.xml directly without re-flattening into a single doc-level header/footer.
Validate this approach β especially but not limited to
Static analysis of packages/pagination/src/lib/types.ts, packages/pagination/src/lib/paginate.ts, packages/pagination/src/lib/allocate-footnotes.ts, packages/pagination/src/react/page-frame.tsx, packages/pagination/src/lib/transforms/.
Review of our own code in packages/pagination/src/, any packages/docx-io* if present, apps/www/src/registry/components/editor/plugins/pagination-kit.tsx.
Migration cost: existing single-header / single-footer documents must auto-wrap into a single-section Section node. What's the migration story for already-saved Plate values?
Does the current pagination plugin contract (BasePaginationApi.pagination.getPages() etc.) survive a per-section model, or do consumers need to migrate to getSections().flatMap(s => s.getPages())?
Multi-section perf: paginating each section independently β is the measurer cache key still adequate, or do we need to namespace it per section?
Is the OOXML shape too OOXML-coupled? Will a future PDF / Markdown / HTML importer struggle to fit non-Word documents into sectPr?
Are there Plate registry consumers (landing-page docs, README-style content) for whom Section is overkill?
Full plan
Define Section node type + sectPr shape in packages/pagination/src/lib/types.ts (OOXML-aligned).
Define chromeRegistry persistence shape (keyed by header/footer ID).
Migration: auto-wrap body in a single Section node; auto-move existing header/footer body nodes into chromeRegistry.
Print/PDF path: wraps body editor + each chrome editor in PlateStatic with existing static-safe filter; on-screen paged preview uses live chrome editors.
dev-browser verification of all seven user-reported issues + a multi-section round-trip test.
@coderabbitai plan
π₯ multi-assumptions: variant C of 3 parallel framings of the same goal.
Sibling variant(s): #384 and #385. 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 C β OOXML section model, DOCX-native shape)
sectPr) model as the source of truth. Introduce a new top-levelSectionnode type carryingsectPr:pgMarβ margins (top/right/bottom/left/header/footer/gutter)pgSzβ page size + orientationheaderReference[]/footerReference[]β IDs of chrome editors (perdefault/first/even)pgNumTypeβ{ start, fmt, position }(format and position of page number)footnotePrβ{ pos: 'pageBottom'|'beneathText'|'sectEnd'|'docEnd', numFmt, ... }titlePgβ first-page-different flagchromeRegistry: Record<HeaderFooterId, TElement[]>(mirroring DOCX's header1.xml / footer1.xml). Persisted alongside the document β not inside the body editor'seditor.children.Sectionnodes; each section'ssectPrreferences chrome by ID. Single-section docs are the trivial case; multi-section emerges naturally.PageFrame(packages/pagination/src/react/page-frame.tsx) readssectPrfor the section that owns the current page; renders chrome by mounting the referenced chrome editor(s) with the same plugin set as the body.Section 1 β header (default)/Section 1 β header (first page). Body editor defocuses.sectPr.titlePg = true; PageFrame usestitlePgHdrRef/titlePgFtrReffor page index 0 of the section. Symmetric for first-page footer.sectPr.pgNumType.positionandpgNumType.fmt; render as a non-editable structured slot inside the chrome region.pgNumType.startper section enables the Word-style "restart numbering at chapter".sectPr.footnotePr.pos; allocator (packages/pagination/src/lib/allocate-footnotes.ts) routes definitions per section:pageBottomβ per-page footer well;sectEndβ end of the section's last page;docEndβ end of the entire document.sectPrdirectly (per-section, not document-level). Multi-section docs get a section selector.PlateStaticinstances, applying the existing static-safe filter only there.sectPr β sectPr,chromeRegistry β headerN.xml/footerN.xml).Acceptance: in
dev-browser, a multi-section document shows independent margins / chrome / page-number formats per section; first-page-different works per section; footnote placement can be set to footer / section-end / document-end; chrome shells are labeled per section; round-tripping a Word DOCX with two sections, a title-page header, and bottom-of-page footnotes preserves all of it (loaded β exported β diff is clean).Because the code is
packages/pagination/src/lib/types.tsβ currentBasePaginationOptionsis document-level only (onemargins, onemode, onepageSize). Cannot represent per-section variation. Variant C replaces this withsectPrper section.packages/pagination/src/lib/transforms/ensureHeader.ts/ensureFooter.tsβ assume single doc-level chrome nodes insideeditor.children. Replaced bychromeRegistrylookup.packages/pagination/src/lib/paginate.tsβ currently allocates pages over the whole body uniformly. Needs to walk section-by-section with per-section margins / page-size / chrome.packages/pagination/src/lib/allocate-footnotes.tsβ currently knows'footer' | 'documentEnd'; OOXML extends topageBottom | beneathText | sectEnd | docEnd. Variant C aligns the type to OOXML.packages/pagination/src/react/page-frame.tsx:107-158, 156, 322-362β chrome rendered throughStaticPageValue(the source of the dropped-plugins issue) and page-number is hardcoded. Variant C replaces both with per-sectionsectPrconsumption + live chrome editors.packages/docx-io(DOCX I/O package, if present in the workspace) β variant C lets the importer/exporter mapsectPr/headerN.xml/footerN.xmldirectly without re-flattening into a single doc-level header/footer.Assumptions deliberately shuffled vs variant A
sectPr,pgNumType,titlePg,footnotePr)titlePgflag +titlePgHdrRef/titlePgFtrReffirstPageHeader/firstPageFooternode typespgNumTypeinsidesectPrpageNumberoption inBasePaginationOptionsAssumptions deliberately shuffled vs variant B
Section.sectPrnodespageLayoutStore+ scoped chrome sub-editorstitlePg+titlePgHdrRefValidate this approach β especially but not limited to
packages/pagination/src/lib/types.ts,packages/pagination/src/lib/paginate.ts,packages/pagination/src/lib/allocate-footnotes.ts,packages/pagination/src/react/page-frame.tsx,packages/pagination/src/lib/transforms/.packages/pagination/src/, anypackages/docx-io*if present,apps/www/src/registry/components/editor/plugins/pagination-kit.tsx.Sectionnode. What's the migration story for already-saved Plate values?BasePaginationApi.pagination.getPages()etc.) survive a per-section model, or do consumers need to migrate togetSections().flatMap(s => s.getPages())?sectPr?Sectionis overkill?Full plan
Sectionnode type +sectPrshape inpackages/pagination/src/lib/types.ts(OOXML-aligned).chromeRegistrypersistence shape (keyed by header/footer ID).Sectionnode; auto-move existing header/footer body nodes intochromeRegistry.paginate.tsto walk per-section.allocate-footnotes.tsto handlepageBottom | beneathText | sectEnd | docEnd.PageFrameto look up per-sectionsectPr, mount referenced chrome editor(s), consumepgNumTypefor page-number paint.titlePgswap on page index 0 of each section.sectPr; add section selector for multi-section docs.sectPr β sectPr,chromeRegistry β headerN.xml/footerN.xml.PlateStaticwith existing static-safe filter; on-screen paged preview uses live chrome editors.dev-browserverification of all seven user-reported issues + a multi-section round-trip test.References
packages/pagination/src/lib/types.tspackages/pagination/src/lib/paginate.tspackages/pagination/src/lib/allocate-footnotes.tspackages/pagination/src/react/page-frame.tsxcodex/pagination-variant-a