Skip to content

Paged-view production parity: margins, chrome UX, page numbers, first-page, footnotes, PlateStatic plugin coverage (variant A β€” schema-first)Β #384

Description

@arthrod

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.

Assumptions deliberately shuffled vs variant B

Axis Variant A Variant B
Where layout state lives editor.options + editor.children (chrome nodes) external pageLayoutStore + scoped chrome sub-editors
Header/footer kind top-level Slate nodes inside the body editor separate Plate editors mounted by PageOverlay
Trigger for layout changes edit-time / option-set-time on the body editor render-time over the body editor
First-page-different optional firstPageHeader/firstPageFooter node types store flag swaps which chrome editor mounts on page 1
Page number structured option in BasePaginationOptions, painted inside header/footer chrome independent slot in store, painted in dedicated chrome region
PlateStatic plugin coverage per-plugin staticSafe opt-in extends the static filter sub-editors render natively in paged mode; static filter only matters in print/PDF path
DOCX round-trip adapter on options + chrome nodes, stays single-section adapter on store + chrome editors, stays single-section
Reversibility hard β€” schema migration if first-page nodes ship and are later removed easy β€” store can be retired; chrome editors collapse back to body nodes
Implementation surface extends existing types + adds ChromeShell + 1 dialog adds new sub-editor mounts, store, and overlay coordination

Assumptions deliberately shuffled vs variant C

Axis Variant A Variant C
Granularity document-level (one set of chrome, one margin box) per-section (multi-section docs, per-section margins/chrome)
Data shape flat options + chrome nodes in body 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.
  • Comparative analysis vs sibling Paged-view production parity: margins, chrome UX, page numbers, first-page, footnotes, PlateStatic plugin coverage (variant B β€” overlay store + sub-editors)Β #385 and Paged-view production parity: margins, chrome UX, page numbers, first-page, footnotes, PlateStatic plugin coverage (variant C β€” OOXML section model)Β #386 β€” 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 multi-section / Word DOCX-fidelity" (β†’ C) vs "do we need to keep the body editor schema clean" (β†’ B) vs "do we need the smallest patch to existing types" (β†’ A)?
  • 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

  1. Extend BasePaginationOptions (types.ts) with pageNumber, firstPageDifferent, and presets.
  2. Add setPageNumber/setFirstPageDifferent transforms; add firstPageHeader/firstPageFooter node types behind the flag.
  3. Update paginate.ts to swap chrome on page index 0 when flag is true.
  4. Refactor PageFrame to consume pageNumber config (drop hardcoded span).
  5. Build ChromeShell wrapper for header/footer node renderers.
  6. Promote MarginsDialog to full Page Setup dialog wired to all transforms.
  7. Add staticSafe plugin descriptor field + extend getElementOnlyStaticPlugins.
  8. Migrate AI / comments / suggestions / drag-handle to staticSafe: true with static-render fallbacks.
  9. End-of-doc footnote rendering inside last page's content box.
  10. dev-browser verification of all seven user-reported issues.

References

  • 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/
  • packages/pagination/src/lib/allocate-footnotes.ts
  • branch codex/pagination-variant-a (commits e56000a, aec21fb, 1e54d38, 500e5d8)

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