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 A of 3 parallel framings of the same goal.
Sibling variant(s): #385 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 A β schema-first, options-driven)
Extend BasePaginationOptions in packages/pagination/src/lib/types.ts with: pageNumber: { side: 'header'|'footer', align: 'left'|'center'|'right', format: '1'|'1/N'|'Page 1 of N', startAt: number, hideOnFirst: boolean }, firstPageDifferent: boolean, and a per-side margin preset list.
Add optional top-level node types firstPageHeader and firstPageFooter (parallel to existing header/footer); paginate.ts selects them on page index 0 when firstPageDifferent is true.
Replace the hardcoded page-number span at packages/pagination/src/react/page-frame.tsx:156 with a <PageNumber/> block that consumes BasePaginationOptions.pageNumber. Page-number slot positions are computed inside the existing header / footer chrome bands β no new chrome region.
Wrap header/footer node renderers in a ChromeShell component (new file packages/pagination/src/react/chrome-shell.tsx) that, when the selection is inside the chrome node, shows a label (Header, Footer, First-page header), a dotted focus border, and an "Exit chrome" affordance. Plain reading mode is unchanged.
Promote MarginsDialog (already at packages/pagination/src/react/margins-dialog.tsx) to a real Page Setup dialog: per-axis numeric inputs, cm/in/mm/px unit toggle, page-size presets, header/footer height, footnote placement toggle, first-page-different toggle, page-number config β all calling existing editor.tf.pagination.setMargins/setMode/setPageSize/setFootnotePlacement plus new transforms setPageNumber and setFirstPageDifferent.
Footnote "end of document" path: keep existing footnotePlacement: 'documentEnd' semantics (packages/pagination/src/lib/allocate-footnotes.ts); ensure end-of-doc allocation renders inside the last page's content box, not below the page sheet.
Plug-the-PlateStatic-hole: introduce a per-plugin opt-in staticSafe?: boolean on the plugin descriptor. Update getElementOnlyStaticPlugins in page-frame.tsx to include any plugin where staticSafe === true even if editOnly or missing node.type. Migrate AI-comment / suggestion / drag-handle plugins to flip the flag and provide a static-render fallback. Plugins that do not opt in keep being dropped (status quo).
Acceptance: in dev-browser, paged mode shows configurable page numbers (top-right vs bottom-center), first page chrome can differ from rest, Page Setup dialog edits all four margins with cm units, footnote placement toggles between footer and end-of-document without losing definitions, header selection shows a labeled chrome shell, and at least three previously-dropped plugins (e.g. comments, suggestions, drag-handle) render in the paged-view PlateStatic preview after opting into staticSafe.
Because the code is
packages/pagination/src/lib/types.ts β BasePaginationOptions already owns margins, mode, footnotePlacement, headerHeight, footerHeight, footnoteWell, pageBorder, pageSize. No pageNumber, no firstPageDifferent. Extending here is additive.
packages/pagination/src/react/page-frame.tsx:156 β <span style={{ float: 'right' }}>{pageIndex + 1}</span> is the entire page-number implementation. Hardcoded.
packages/pagination/src/react/page-frame.tsx:322-362 β getElementOnlyStaticPlugins drops plugins with no node.type, editOnly: true, or missing node.component, AND wipes every render slot. This is the explicit reason "PlateStatic page viewer doesn't render several plugins."
packages/pagination/src/react/margins-dialog.tsx β minimal v1 native <dialog>; only edits the four margin sides. No page-size, no chrome heights, no first-page, no page-number.
packages/pagination/src/lib/transforms/ensureHeader.ts / ensureFooter.ts / toggleHeader.ts / toggleFooter.ts β single doc-level header/footer node only. No first-page concept.
packages/pagination/src/lib/allocate-footnotes.ts β already supports documentEnd placement; the gap is UI surfacing.
packages/pagination/src/lib/base-pagination-plugin.ts β defines transforms; adding setPageNumber and setFirstPageDifferent here keeps the schema-first contract.
OOXML-shaped Section nodes with sectPr + ID-keyed chrome registry
First-page rule
dedicated node types
titlePg flag in sectPr plus titlePgHdrRef/titlePgFtrRef
Page number
flat pageNumber option
structured pgNumType inside sectPr
DOCX round-trip
adapter mapping per option
near-direct (matches OOXML)
Implementation cost
low (extend existing types)
high (new section abstraction across import/export)
Future-proofing
locked to single-section docs
multi-section, per-section everything
Validate this approach β especially but not limited to
Static analysis of packages/pagination/src/lib/types.ts, packages/pagination/src/react/page-frame.tsx, packages/pagination/src/react/margins-dialog.tsx, packages/pagination/src/lib/transforms/*.ts, packages/pagination/src/lib/allocate-footnotes.ts.
Review of our own code in packages/pagination/src/react/, packages/pagination/src/lib/, packages/pagination/src/static/, apps/www/src/registry/components/editor/plugins/pagination-kit.tsx.
Will a per-plugin staticSafe opt-in actually fix AI / comments / suggestions / drag-handle in PlateStatic, or do those plugins fundamentally require live-editor state that PlateStatic cannot provide? (If the latter, this whole rendering hole survives variant A.)
Does adding firstPageHeader/firstPageFooter node types violate the existing single-header/footer invariant enforced by enforceHeaderFooterInvariants.ts? Spell out the migration path.
Is pageNumber better as a structured option or as a void inline node inserted into chrome content (so users could also reorder/style it via existing inline tooling)?
Full plan
Extend BasePaginationOptions (types.ts) with pageNumber, firstPageDifferent, and presets.
Add setPageNumber/setFirstPageDifferent transforms; add firstPageHeader/firstPageFooter node types behind the flag.
Update paginate.ts to swap chrome on page index 0 when flag is true.
Refactor PageFrame to consume pageNumber config (drop hardcoded span).
Build ChromeShell wrapper for header/footer node renderers.
Promote MarginsDialog to full Page Setup dialog wired to all transforms.
Add staticSafe plugin descriptor field + extend getElementOnlyStaticPlugins.
Migrate AI / comments / suggestions / drag-handle to staticSafe: true with static-render fallbacks.
End-of-doc footnote rendering inside last page's content box.
dev-browser verification of all seven user-reported issues.
@coderabbitai plan
π₯ multi-assumptions: variant A of 3 parallel framings of the same goal.
Sibling variant(s): #385 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 A β schema-first, options-driven)
BasePaginationOptionsinpackages/pagination/src/lib/types.tswith:pageNumber: { side: 'header'|'footer', align: 'left'|'center'|'right', format: '1'|'1/N'|'Page 1 of N', startAt: number, hideOnFirst: boolean },firstPageDifferent: boolean, and a per-side margin preset list.firstPageHeaderandfirstPageFooter(parallel to existingheader/footer);paginate.tsselects them on page index 0 whenfirstPageDifferentis true.packages/pagination/src/react/page-frame.tsx:156with a<PageNumber/>block that consumesBasePaginationOptions.pageNumber. Page-number slot positions are computed inside the existing header / footer chrome bands β no new chrome region.ChromeShellcomponent (new filepackages/pagination/src/react/chrome-shell.tsx) that, when the selection is inside the chrome node, shows a label (Header,Footer,First-page header), a dotted focus border, and an "Exit chrome" affordance. Plain reading mode is unchanged.MarginsDialog(already atpackages/pagination/src/react/margins-dialog.tsx) to a real Page Setup dialog: per-axis numeric inputs, cm/in/mm/px unit toggle, page-size presets, header/footer height, footnote placement toggle, first-page-different toggle, page-number config β all calling existingeditor.tf.pagination.setMargins/setMode/setPageSize/setFootnotePlacementplus new transformssetPageNumberandsetFirstPageDifferent.footnotePlacement: 'documentEnd'semantics (packages/pagination/src/lib/allocate-footnotes.ts); ensure end-of-doc allocation renders inside the last page's content box, not below the page sheet.staticSafe?: booleanon the plugin descriptor. UpdategetElementOnlyStaticPluginsinpage-frame.tsxto include any plugin wherestaticSafe === trueeven ifeditOnlyor missingnode.type. Migrate AI-comment / suggestion / drag-handle plugins to flip the flag and provide a static-render fallback. Plugins that do not opt in keep being dropped (status quo).Acceptance: in
dev-browser, paged mode shows configurable page numbers (top-right vs bottom-center), first page chrome can differ from rest, Page Setup dialog edits all four margins with cm units, footnote placement toggles between footer and end-of-document without losing definitions, header selection shows a labeled chrome shell, and at least three previously-dropped plugins (e.g. comments, suggestions, drag-handle) render in the paged-view PlateStatic preview after opting intostaticSafe.Because the code is
packages/pagination/src/lib/types.tsβBasePaginationOptionsalready ownsmargins,mode,footnotePlacement,headerHeight,footerHeight,footnoteWell,pageBorder,pageSize. NopageNumber, nofirstPageDifferent. Extending here is additive.packages/pagination/src/react/page-frame.tsx:156β<span style={{ float: 'right' }}>{pageIndex + 1}</span>is the entire page-number implementation. Hardcoded.packages/pagination/src/react/page-frame.tsx:322-362βgetElementOnlyStaticPluginsdrops plugins with nonode.type,editOnly: true, or missingnode.component, AND wipes every render slot. This is the explicit reason "PlateStatic page viewer doesn't render several plugins."packages/pagination/src/react/margins-dialog.tsxβ minimal v1 native<dialog>; only edits the four margin sides. No page-size, no chrome heights, no first-page, no page-number.packages/pagination/src/lib/transforms/ensureHeader.ts/ensureFooter.ts/toggleHeader.ts/toggleFooter.tsβ single doc-level header/footer node only. No first-page concept.packages/pagination/src/lib/allocate-footnotes.tsβ already supportsdocumentEndplacement; the gap is UI surfacing.packages/pagination/src/lib/base-pagination-plugin.tsβ defines transforms; addingsetPageNumberandsetFirstPageDifferenthere keeps the schema-first contract.Assumptions deliberately shuffled vs variant B
editor.options+editor.children(chrome nodes)pageLayoutStore+ scoped chrome sub-editorsPageOverlayfirstPageHeader/firstPageFooternode typesBasePaginationOptions, painted inside header/footer chromestaticSafeopt-in extends the static filterChromeShell+ 1 dialogAssumptions deliberately shuffled vs variant C
Sectionnodes withsectPr+ ID-keyed chrome registrytitlePgflag insectPrplustitlePgHdrRef/titlePgFtrRefpageNumberoptionpgNumTypeinsidesectPrValidate this approach β especially but not limited to
packages/pagination/src/lib/types.ts,packages/pagination/src/react/page-frame.tsx,packages/pagination/src/react/margins-dialog.tsx,packages/pagination/src/lib/transforms/*.ts,packages/pagination/src/lib/allocate-footnotes.ts.packages/pagination/src/react/,packages/pagination/src/lib/,packages/pagination/src/static/,apps/www/src/registry/components/editor/plugins/pagination-kit.tsx.staticSafeopt-in actually fix AI / comments / suggestions / drag-handle in PlateStatic, or do those plugins fundamentally require live-editor state that PlateStatic cannot provide? (If the latter, this whole rendering hole survives variant A.)firstPageHeader/firstPageFooternode types violate the existing single-header/footer invariant enforced byenforceHeaderFooterInvariants.ts? Spell out the migration path.pageNumberbetter as a structured option or as a void inline node inserted into chrome content (so users could also reorder/style it via existing inline tooling)?Full plan
BasePaginationOptions(types.ts) withpageNumber,firstPageDifferent, and presets.setPageNumber/setFirstPageDifferenttransforms; addfirstPageHeader/firstPageFooternode types behind the flag.paginate.tsto swap chrome on page index 0 when flag is true.PageFrameto consumepageNumberconfig (drop hardcoded span).ChromeShellwrapper for header/footer node renderers.MarginsDialogto full Page Setup dialog wired to all transforms.staticSafeplugin descriptor field + extendgetElementOnlyStaticPlugins.staticSafe: truewith static-render fallbacks.dev-browserverification of all seven user-reported issues.References
packages/pagination/src/lib/types.tspackages/pagination/src/react/page-frame.tsxpackages/pagination/src/react/margins-dialog.tsxpackages/pagination/src/lib/transforms/packages/pagination/src/lib/allocate-footnotes.tscodex/pagination-variant-a(commits e56000a, aec21fb, 1e54d38, 500e5d8)