@coderabbitai plan
π₯ Plate @platejs/pagination β consolidated plan (variant A, pretext-backed)
This supersedes #353 / #354 plan churn. New baseline pinned by interactive design review with the maintainer; CR is asked to plan from scratch against this brief, not to extend the older plans.
Goal
Ship a Plate plugin (@platejs/pagination) that delivers print-grade pagination β fixed-size pages (A4 default) with per-page headers, footers, footnotes β using chenglou/pretext (height oracle, MIT, ESM) for line-break + height math, with editor-faithful Word semantics (<w:sectPr> style metadata, separate header*.xml style H/F parts).
Architecture (locked)
| # |
Topic |
Decision |
| 1 |
Standard-mode footer |
Hybrid: anchored to end of content; pinned to viewport bottom when doc is shorter than viewport |
| 2 |
Mode + size |
Single toolbar dropdown: Standard / A4 / Letter / Legal / Customβ¦. Custom opens dialog. |
| 3 |
Margins |
Word-style preset submenu + Custom dialog (4 inputs, unit selector in/cm/mm/px). Paged view also exposes drag handles on margin lines. |
| 4 |
H/F variation |
Word 6-slot model. Two toggles: Different first page, Different odd & even. Editable slots: header / header-first / header-even / footer / footer-first / footer-even. |
| 5 |
Dynamic fields |
Plate inline-void nodes: pageNumber, pageCount, date. Inserted via "Insert field" menu in H/F editor. Resolved at paint time per page. |
| 6 |
Footnotes (standard) |
End-of-document endnotes (renders before the hybrid footer). |
| 6b |
Footnotes (paged) |
Per-page footer well via portal (existing variant-A spec). |
| 7 |
Persistence |
Sibling node. Single section-properties void at end of editor.children carrying { pageSize, margins, differentFirstPage, differentOddEven }. H/F content as separate top-level sibling nodes. Body = everything else; paginate() filters non-body siblings. Word-faithful: <w:sectPr> end-of-body + separate header*.xml. |
| 8 |
Font for pretext |
Per-block DOM scrape: editor.api.toDOMNode(block) β getComputedStyle(el).font. system-ui β Inter snap client-side. Cache key (node.id, marksFingerprint, font, width), rAF-coalesced. |
| 9 |
Manual page break |
Cmd/Ctrl+Enter + /page break slash menu. Inserts { type: 'page-break' } void. Labeled rule in standard mode, hard split in paged. |
| 10 |
UI distribution |
Plugin = base + react in @platejs/pagination. Toolbar dropdown + dialogs ship as a registry kit (Plate convention), not built into the package. |
| 11 |
Print path |
Paged mode renders real <section class="page"> DOM (the <PrintPagination> component) for both on-screen-paged and @media print. No overlay path on screen. One render for both. |
Word's data model β what we're matching
<w:body>
<w:p>β¦paragraphβ¦</w:p> β body content
β¦
<w:sectPr> β section properties (NOT a page node)
<w:pgSz w:w=β¦ w:h=β¦/> β page size
<w:pgMar β¦/> β margins
<w:hdrRef w:type="default|first|even" r:id=β¦/>
<w:ftrRef w:type="default|first|even" r:id=β¦/>
<w:titlePg/> β differentFirstPage
</w:sectPr>
</w:body>
+ separate header1.xml, footer1.xml, β¦ parts (referenced by id)
Pages are never in DOCX XML β they're a layout-engine output. Variant A's "no page nodes ever in editor.children" matches Word exactly. section-properties and H/F siblings are metadata + separate parts, not pages.
Editor.children shape
editor.children = [
// body content (any normal Plate blocks):
{ type: 'p', children: [...] },
{ type: 'h1', children: [...] },
{ type: 'page-break' }, // manual hard break (void)
...
// β siblings, filtered out of paginate() body input β
{ type: 'header', children: [...] }, // default
{ type: 'header-first', children: [...] }, // optional
{ type: 'header-even', children: [...] }, // optional
{ type: 'footer', children: [...] },
{ type: 'footer-first', children: [...] },
{ type: 'footer-even', children: [...] },
{ type: 'footnoteDefinition', children: [...] }, ... // existing plugin
{ type: 'section-properties', // void
pageSize: 'A4', margins: { top, right, bottom, left },
differentFirstPage: true, differentOddEven: false },
]
What changes on codex/pagination-variant-a (it claims pretext, ships none)
Critical fixes:
- Add
pretext to packages/pagination/package.json dependencies.
- Rewrite
packages/pagination/src/react/use-pretext-measurer.ts β replace canvas+char-width estimator with real pretext.prepare() per (node.id, marksFingerprint, font, width) and pretext.layout(width) per cycle. Use prepareRichInline for blocks with mixed marks.
packages/pagination/src/react/internal/use-page-layout.ts:47 β replace ctx: { font: '', marksFingerprint: '', width } with per-block scraped values; build marksFingerprint per block via existing internal/marks-fingerprint.ts.
- Wrap recompute in a
requestAnimationFrame coalescer.
system-ui β Inter snap inside the per-block scrape.
New surface (not yet on the branch):
6. BaseSectionPropertiesPlugin β void node, end of editor.children, carries pageSize/margins/toggles. paginate() skips it.
7. BaseHeaderPlugin / BaseFooterPlugin β variant: 'default' | 'first' | 'even'. Six possible top-level sibling nodes.
8. BasePageNumberPlugin, BasePageCountPlugin, BaseDatePlugin β inline-void plugins, render value from page context at paint time.
9. BasePageBreakPlugin β Cmd/Ctrl+Enter keybinding + slash registration.
10. <PrintPagination> (paged on-screen + print) β real <section class="page"> per page + @media print { .page { page-break-after: always } }. Single render path.
11. <StandardLayout> (standard mode) β IntersectionObserver-driven hybrid footer; renders endnotes between last block and the hybrid footer.
12. <MarginDragHandles> β overlay in paged view, updates section-properties options.
13. <PaginationDropdown> β toolbar component owning mode + size + margins UX (presets, Custom dialog, two H/F toggles). Ships in registry kit, not the npm package.
Deferred to v2
- Per-section variation (multiple
<w:sectPr> in one doc).
- Mirrored margins / gutter / binding offset.
- DOCX import/export adapter (data shapes are designed to round-trip; adapter is a separate ticket).
- Yjs deterministic acceptance test (sibling-node persistence makes Yjs work for free; just needs an explicit test).
Acceptance
- 500-block doc edits at β₯55fps with pagination on (rAF-coalesced pretext layout).
- Resizing the viewport repaginates within one frame.
- Print preview matches paged-on-screen page boundaries within Β±1 line.
- Yjs co-edit produces identical pagination across clients (sibling-node persistence β deterministic, unlike the original variant-A "per-viewport" framing β see the architecture-locked persistence row).
References
@coderabbitai please plan from scratch against this brief (don't extend #353 / #354 plans). Implementation is starting in parallel β we're not waiting on the plan, but a fresh plan is welcome for cross-check.
@coderabbitai plan
π₯ Plate
@platejs/paginationβ consolidated plan (variant A, pretext-backed)This supersedes #353 / #354 plan churn. New baseline pinned by interactive design review with the maintainer; CR is asked to plan from scratch against this brief, not to extend the older plans.
Goal
Ship a Plate plugin (
@platejs/pagination) that delivers print-grade pagination β fixed-size pages (A4 default) with per-page headers, footers, footnotes β usingchenglou/pretext(height oracle, MIT, ESM) for line-break + height math, with editor-faithful Word semantics (<w:sectPr>style metadata, separateheader*.xmlstyle H/F parts).Architecture (locked)
Standard / A4 / Letter / Legal / Customβ¦. Custom opens dialog.header / header-first / header-even / footer / footer-first / footer-even.pageNumber,pageCount,date. Inserted via "Insert field" menu in H/F editor. Resolved at paint time per page.section-propertiesvoid at end ofeditor.childrencarrying{ pageSize, margins, differentFirstPage, differentOddEven }. H/F content as separate top-level sibling nodes. Body = everything else;paginate()filters non-body siblings. Word-faithful:<w:sectPr>end-of-body + separateheader*.xml.editor.api.toDOMNode(block)βgetComputedStyle(el).font. system-ui β Inter snap client-side. Cache key(node.id, marksFingerprint, font, width), rAF-coalesced.Cmd/Ctrl+Enter+/page breakslash menu. Inserts{ type: 'page-break' }void. Labeled rule in standard mode, hard split in paged.@platejs/pagination. Toolbar dropdown + dialogs ship as a registry kit (Plate convention), not built into the package.<section class="page">DOM (the<PrintPagination>component) for both on-screen-paged and@media print. No overlay path on screen. One render for both.Word's data model β what we're matching
Pages are never in DOCX XML β they're a layout-engine output. Variant A's "no
pagenodes ever ineditor.children" matches Word exactly.section-propertiesand H/F siblings are metadata + separate parts, not pages.Editor.children shape
What changes on
codex/pagination-variant-a(it claims pretext, ships none)Critical fixes:
pretexttopackages/pagination/package.jsondependencies.packages/pagination/src/react/use-pretext-measurer.tsβ replace canvas+char-width estimator with realpretext.prepare()per(node.id, marksFingerprint, font, width)andpretext.layout(width)per cycle. UseprepareRichInlinefor blocks with mixed marks.packages/pagination/src/react/internal/use-page-layout.ts:47β replacectx: { font: '', marksFingerprint: '', width }with per-block scraped values; buildmarksFingerprintper block via existinginternal/marks-fingerprint.ts.requestAnimationFramecoalescer.system-ui β Intersnap inside the per-block scrape.New surface (not yet on the branch):
6.
BaseSectionPropertiesPluginβ void node, end ofeditor.children, carries pageSize/margins/toggles.paginate()skips it.7.
BaseHeaderPlugin/BaseFooterPluginβvariant: 'default' | 'first' | 'even'. Six possible top-level sibling nodes.8.
BasePageNumberPlugin,BasePageCountPlugin,BaseDatePluginβ inline-void plugins, render value from page context at paint time.9.
BasePageBreakPluginβCmd/Ctrl+Enterkeybinding + slash registration.10.
<PrintPagination>(paged on-screen + print) β real<section class="page">per page +@media print { .page { page-break-after: always } }. Single render path.11.
<StandardLayout>(standard mode) β IntersectionObserver-driven hybrid footer; renders endnotes between last block and the hybrid footer.12.
<MarginDragHandles>β overlay in paged view, updatessection-propertiesoptions.13.
<PaginationDropdown>β toolbar component owning mode + size + margins UX (presets, Custom dialog, two H/F toggles). Ships in registry kit, not the npm package.Deferred to v2
<w:sectPr>in one doc).Acceptance
References
bb224e0β height oracle only, doesn't paginate.packages/core/src/lib/plugin/{createSlatePlugin,SlatePlugin,BasePlugin}.tspackages/footnote/src/lib/BaseFootnote*Plugin.ts.claude/skills/plate-plugin-creator/SKILL.md@coderabbitai please plan from scratch against this brief (don't extend #353 / #354 plans). Implementation is starting in parallel β we're not waiting on the plan, but a fresh plan is welcome for cross-check.