Skip to content

Paged-view production parity: margins, chrome UX, page numbers, first-page, footnotes, PlateStatic plugin coverage (variant C β€” OOXML section model)Β #386

Description

@arthrod

@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 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)

  1. Adopt OOXML's section-properties (sectPr) model as the source of truth. Introduce a new top-level Section node type carrying sectPr:
    • pgMar β€” margins (top/right/bottom/left/header/footer/gutter)
    • pgSz β€” page size + orientation
    • headerReference[] / footerReference[] β€” IDs of chrome editors (per default/first/even)
    • pgNumType β€” { start, fmt, position } (format and position of page number)
    • footnotePr β€” { pos: 'pageBottom'|'beneathText'|'sectEnd'|'docEnd', numFmt, ... }
    • titlePg β€” first-page-different flag
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. First-page-different β†’ sectPr.titlePg = true; PageFrame uses titlePgHdrRef / titlePgFtrRef for page index 0 of the section. Symmetric for first-page footer.
  7. 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".
  8. 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.
  9. Page Setup dialog operates on the active section's sectPr directly (per-section, not document-level). Multi-section docs get a section selector.
  10. 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.
  11. DOCX import/export becomes a near-direct mapping (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 β€” 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.

Assumptions deliberately shuffled vs variant A

Axis Variant C Variant A
Granularity per-section document-level
Data shape OOXML-shaped (sectPr, pgNumType, titlePg, footnotePr) flat options + chrome nodes in body
First-page rule titlePg flag + titlePgHdrRef/titlePgFtrRef dedicated firstPageHeader/firstPageFooter node types
Page number structured pgNumType inside sectPr flat pageNumber option in BasePaginationOptions
DOCX round-trip near-direct (matches OOXML) adapter mapping per option
Implementation cost high (new section abstraction across import/export, paginate, paint) low (extend existing types)
Future-proofing multi-section, per-section margins/headers, restart numbering locked to single-section docs
Reversibility medium (sections persist; can hide multi-section behind a flag) hard (schema migration if first-page node types ship)

Assumptions deliberately shuffled vs variant B

Axis Variant C Variant B
Where chrome lives ID-keyed chrome registry referenced from Section.sectPr nodes external pageLayoutStore + scoped chrome sub-editors
Granularity per-section document-level
Data model OOXML-shaped bespoke store shape
First-page rule titlePg + titlePgHdrRef store flag
DOCX round-trip near-direct adapter on store
Section model first-class multi-section none β€” single section assumed
Cost upfront high medium

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.
  • Comparative analysis vs sibling Paged-view production parity: margins, chrome UX, page numbers, first-page, footnotes, PlateStatic plugin coverage (variant A β€” schema-first)Β #384 and Paged-view production parity: margins, chrome UX, page numbers, first-page, footnotes, PlateStatic plugin coverage (variant B β€” overlay store + sub-editors)Β #385 β€” name the load-bearing assumption as a single sentence in the form "If matters more than , variant <A|B|C> wins; otherwise the other." Do not pick a winner yet β€” name the axis. Specifically: is the load-bearing question "do we need to support multi-section documents and lossless DOCX round-trip" (β†’ C) vs "is single-section + clean round-trip via adapter enough" (β†’ A or B), and within single-section, "is body-editor cleanliness more important than schema-additive simplicity" (β†’ B vs A)?
  • 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

  1. Define Section node type + sectPr shape in packages/pagination/src/lib/types.ts (OOXML-aligned).
  2. Define chromeRegistry persistence shape (keyed by header/footer ID).
  3. Migration: auto-wrap body in a single Section node; auto-move existing header/footer body nodes into chromeRegistry.
  4. Refactor paginate.ts to walk per-section.
  5. Refactor allocate-footnotes.ts to handle pageBottom | beneathText | sectEnd | docEnd.
  6. Refactor PageFrame to look up per-section sectPr, mount referenced chrome editor(s), consume pgNumType for page-number paint.
  7. Implement titlePg swap on page index 0 of each section.
  8. Build Page Setup dialog operating on active section's sectPr; add section selector for multi-section docs.
  9. Wire chrome shell (label includes section number + role).
  10. DOCX adapter: map sectPr ↔ sectPr, chromeRegistry ↔ headerN.xml/footerN.xml.
  11. Print/PDF path: wraps body editor + each chrome editor in PlateStatic with existing static-safe filter; on-screen paged preview uses live chrome editors.
  12. dev-browser verification of all seven user-reported issues + a multi-section round-trip test.

References

  • 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
  • branch codex/pagination-variant-a
  • ECMA-376 Β§17.6 (OOXML sectPr), Β§17.10 (footnotes), Β§17.11 (numbering)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions