Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
daff834
chore: add CodeRabbit config
arthrod May 4, 2026
8a90361
changes
arthrod May 5, 2026
604ca5d
feat(pagination): scaffold @platejs/pagination variant A (render-over…
arthrod May 6, 2026
e89b5c7
feat(pagination): pick pagination folder from pagination branch
arthrod May 15, 2026
cc3fdfc
baseline
arthrod May 15, 2026
152285c
baseline
arthrod May 15, 2026
0dd73d9
refactor(pagination): route key through KEYS.pagination + KEYS.p
arthrod May 15, 2026
0e46641
refactor(pagination): move runtime+mutating flag to WeakMap registry
arthrod May 15, 2026
b453ce8
refactor(pagination): remove any from BasePaginationPlugin
arthrod May 15, 2026
92a717d
refactor(pagination): remove any from reflowEngine and runtime
arthrod May 15, 2026
70e839e
chore(pagination): regenerate barrel via pnpm brl
arthrod May 15, 2026
115156b
feat(pagination): expose Yjs bridge under ./yjs subpath, mark optional
arthrod May 15, 2026
0daf893
refactor(pagination): drop manual memoization in PaginationCoordinator
arthrod May 15, 2026
86cc031
chore(pagination): remove example_visualization_with_toggle
arthrod May 15, 2026
916c8b7
refactor(pagination): move feature transforms to .extendTransforms
arthrod May 15, 2026
29974bd
refactor(pagination): coalesce notify, extract scheduleIdle, gate deb…
arthrod May 15, 2026
c67a853
refactor(pagination): expose mutations transform + non-monotonic spli…
arthrod May 15, 2026
f3fd117
baseline
arthrod May 16, 2026
d7b435f
long ago
arthrod May 18, 2026
08aa7b2
chore: add bunfig.toml with minimumReleaseAge=7d
arthrod May 19, 2026
90d1af6
long ago
arthrod May 19, 2026
5a498d6
fix(pagination): auto-mount registry provider + coordinator
arthrod May 20, 2026
9da7c0c
fix(pagination): work with published platejs + show page numbers
arthrod May 21, 2026
1645277
feat(pagination): deterministic layout core (snapshot + compose)
arthrod May 21, 2026
f73ae83
feat(pagination): measurement layer (snapshot → MeasuredSnapshot)
arthrod May 21, 2026
233dc17
feat(pagination): DOM-backed block measurer (createDomMeasure)
arthrod May 21, 2026
3f3e212
feat(pagination): overlay renderer — page chrome + content alignment
arthrod May 21, 2026
bf6e694
feat(pagination): MappingIndex + projection (P0 foundation)
arthrod May 21, 2026
77307c5
feat(pagination): split-block rendering via clipped clones (P0)
arthrod May 21, 2026
e92121a
long ago
arthrod May 22, 2026
3018354
refactor(pagination)!: remove document-mutating engine; pagination is…
arthrod May 22, 2026
54de7f7
fix(pagination): key measure cache by (id, width) to stop thrash
arthrod May 22, 2026
8971d68
feat(pagination): add pretext line-breaking primitive (measureTextLines)
arthrod May 22, 2026
7432f0a
feat(pagination): carry block text on the snapshot for line measurement
arthrod May 22, 2026
a4874d2
feat(pagination): pretext-driven block height (measureBlockHeight + D…
arthrod May 22, 2026
42cce98
refactor(pagination): compose places blocks whole (option C)
arthrod May 22, 2026
b36b69b
feat(pagination): build MappingIndex once in composeLayout, expose on…
arthrod May 23, 2026
3997ca3
feat(pagination): add per-editor layout registry (WeakMap, dirty-on-a…
arthrod May 23, 2026
75ed16c
feat(pagination): add BasePaginationPlugin (options + apply→registry …
arthrod May 23, 2026
ccc35a7
feat(pagination): add getContinuousBreakYs + regenerate foundation ba…
arthrod May 23, 2026
62de755
feat(pagination): add getContinuousBreaks (boundary block per interio…
arthrod May 23, 2026
de96756
feat(pagination): React PaginationPlugin host + continuous break-line…
arthrod May 23, 2026
6cd84ab
feat(pagination): DOM-anchored continuous overlay + wire demos
arthrod May 23, 2026
e7be784
fix(pagination): margin-aware packing + overlay polish (dogfood ISSUE…
arthrod May 23, 2026
c8e5bf2
feat(pagination): add enabled option to toggle pagination at runtime
arthrod May 24, 2026
14686c6
feat(playground): wire pagination into the editor behind a toolbar to…
arthrod May 24, 2026
419cd46
looks like its working :)
arthrod May 24, 2026
6926cd6
chore: enhance PR template + add title validation workflow
claude May 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .agents/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
- Answer in English by default. Switch languages only when the user explicitly asks for another language.
- Prefer the best long-term architecture fix over the nearest local patch. If the real fix is an API or abstraction change, do that.

## Pagination (pretext gate)

- **pretext is mandatory for any pagination plugin.** Faithful pagination (page counting, widow/orphan, split points) requires real text measurement/shaping via pretext; block-level DOM `offsetHeight` estimates are a downgrade, not a substitute.
- If a pagination plugin does NOT use pretext, or uses it incompletely, you MUST NOT proceed silently. First make a **solemn attestation** stating plainly that pretext is not useful here and the concrete technical reason why.
- Then, in the same response, repeat the approval request **10 times** (numbered 1–10), each asking the user to explicitly approve pretext's absence or incomplete use. Do not continue with the pagination work until the user approves.

## Git

- **Git:** Never git add, commit, push, or create PR unless the user explicitly asks, or the active command/skill explicitly requires it.
Expand Down
5 changes: 5 additions & 0 deletions .changeset/pagination-automount-runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@platejs/pagination": patch
---

Mount the registry provider and reflow coordinator automatically from `PaginationPlugin`, so registering the plugin is all that is needed for pages to render and reflow
5 changes: 5 additions & 0 deletions .changeset/pagination-cache-key.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@platejs/pagination": patch
---

Fix `measureSnapshot` cache thrashing when the same block is measured at multiple widths. The cache now keys each entry by `(block id, width)` instead of block id alone, so alternating widths (resize, side-by-side editors) stay cached instead of overwriting one slot.
5 changes: 5 additions & 0 deletions .changeset/pagination-compose-place-whole.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@platejs/pagination": patch
---

`composeLayout` places blocks whole: a block that fits the remaining space is placed, otherwise it moves whole to the next page; a block taller than a full page is placed and overflows. No mid-block splitting.
5 changes: 5 additions & 0 deletions .changeset/pagination-continuous-breaks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@platejs/pagination": patch
---

Add `getContinuousBreaks(layout)`: each interior page boundary named by the block (and line) that begins the next page. The continuous overlay anchors its advisory rule to that boundary block's live DOM top, so the line lands on a real block edge instead of a text-only pixel sum that ignores DOM margins.
5 changes: 5 additions & 0 deletions .changeset/pagination-enabled-option.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@platejs/pagination": minor
---

Add an `enabled` option (default `true`) to toggle pagination at runtime. When `false`, the React layer skips layout recompute and renders no page-break overlay; the document is never affected either way. Toggle with `editor.setOption(BasePaginationPlugin, 'enabled', next)`.
5 changes: 5 additions & 0 deletions .changeset/pagination-mapping-in-output.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@platejs/pagination": patch
---

Build the layout `MappingIndex` once during `composeLayout` and expose it on `LayoutOutput.mapping`; projection reads it instead of rebuilding the index on every call.
10 changes: 10 additions & 0 deletions .changeset/pagination-margin-aware-packing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@platejs/pagination": patch
---

Margin-aware page packing + continuous-overlay polish:

- Compose now packs pages by a block's **flow height** (text height + the DOM box spacing — margins/padding/borders — supplied by the measurer as `flowHeightPx`), falling back to text height when absent. The page count and break placement now match real DOM flow instead of under-counting per-page capacity. `heightPx`/`lineCount` stay text-only so line-level mapping is unaffected.
- Overlay labels show `Page N of M` and add a `Page 1 of M` marker, so the first page and total are always visible.
- Labels moved to the left margin gutter, so they stay on-screen when a narrow viewport overflows the page width.
- The recompute runs in a layout effect (before paint) instead of a post-paint `requestAnimationFrame`, so the advisory lines appear with the content as soon as the editor hydrates.
5 changes: 5 additions & 0 deletions .changeset/pagination-page-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@platejs/pagination": patch
---

Fix pagination not working for consumers on published `platejs`: use a literal `'pagination'` key instead of `KEYS.pagination` (unreleased in `@platejs/utils`), mount the registry provider and reflow coordinator in one shared subtree so reflow can read registered pages, and render the page number in each page's bottom margin
5 changes: 5 additions & 0 deletions .changeset/pagination-pretext-measure-block.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@platejs/pagination": minor
---

Make block measurement pretext-driven. `createDomMeasure` now resolves each block's font and content width from the live editable, then derives height from the line count pretext wraps the text to (new `measureBlockHeight`) — the line count, not the DOM box, owns layout height, so padding/margins no longer perturb pagination.
5 changes: 5 additions & 0 deletions .changeset/pagination-pretext-measure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@platejs/pagination": minor
---

Add `measureTextLines`: real text line-breaking via `@chenglou/pretext`. Given text, a CSS font string, and a content width it returns the wrapped visual lines — each with its text, measured width, and the segment/grapheme cursor range it spans — the foundation for line-accurate pagination (widow/orphan, split points, caret mapping).
5 changes: 5 additions & 0 deletions .changeset/pagination-react-continuous-overlay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@platejs/pagination": patch
---

Continuous-view React host: `PaginationPlugin` runs the pretext pipeline (snapshot → measure → compose) against the live editable on content edits and width changes, then paints advisory page-break rules as an `afterEditable` overlay. Each rule anchors to the live DOM top of the block pretext chose to begin the next page (`breaks` option), so it lands on a real block edge; the `Page N` label sits in the right margin gutter. `pointer-events: none` keeps editing and selection fully native; the document is never mutated.
5 changes: 5 additions & 0 deletions .changeset/pagination-scaffold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@platejs/pagination': minor
---

Add `@platejs/pagination` package — render-time overlay pagination (variant A). Pages are derived from `editor.children` and painted as an `afterEditable` overlay; the document is never mutated. Includes header / footer / page-break element plugins, footnote sub-plugin bundling, a DOM-backed measurer with bounded LRU cache keyed by `(node.id, marks-fingerprint, font, width)`, and editor API (`getPages`, `getPageOf`, `getFootnotes`) plus transforms (`insertPageBreak`, `setHeader`, `setFooter`).
7 changes: 7 additions & 0 deletions .changeset/pagination-scorch-mutator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@platejs/pagination": major
---

Remove the document-mutating pagination engine. Pagination is now a derived projection: the document model is never wrapped in `page` nodes or reflowed between pages.

Removes `BasePaginationPlugin`, `PaginationPlugin`, `PaginationCoordinator`, `PageElement`, the `registry`/`leaderElection` exports, and the `@platejs/pagination/yjs` entry. The package now exports only the pure layout pipeline: `buildSnapshot`, `measureSnapshot`, `composeLayout`, `getPageGeometry`, `alignContentToLayout`, and the mapping/projection helpers.
5 changes: 5 additions & 0 deletions .changeset/pagination-snapshot-text.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@platejs/pagination": patch
---

`buildSnapshot` now records each block's concatenated `text` on the snapshot, so the measurement pass can line-break it.
5 changes: 5 additions & 0 deletions .changeset/pagination-wrapped-blocks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@platejs/pagination": patch
---

Fix page-break markers for editors whose block UI wraps Slate elements.
5 changes: 5 additions & 0 deletions .changeset/utils-pagination-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@platejs/utils': patch
---

Add `KEYS.pagination`, `KEYS.header`, `KEYS.footer`, `KEYS.pageBreak` for the pagination plugin family
17 changes: 17 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
## Summary

<!-- Brief description of what this PR does and why. -->

## Type of Change

- [ ] Bug fix
- [ ] New feature / enhancement
- [ ] Breaking change
- [ ] Documentation / chore

## Related Issues

<!-- Closes #issue or N/A -->

---

**Checklist**

- [x] `pnpm typecheck`
Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/pr-validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Validate PR

on:
pull_request:
types:
- opened
- edited
- synchronize
- reopened

permissions:
pull-requests: read

jobs:
title:
name: PR title
runs-on: ubuntu-latest
steps:
- name: Check title
env:
TITLE: ${{ github.event.pull_request.title }}
run: |
# Must be at least 10 chars
if [ ${#TITLE} -lt 10 ]; then
echo "::error::PR title too short (min 10 chars): \"$TITLE\""
exit 1
fi

# Reject auto-generated Codex-style titles (e.g. "Codex/foo-bar")
if echo "$TITLE" | grep -qiE '^[Cc]odex/'; then
echo "::error::Auto-generated title detected. Replace with a descriptive title: \"$TITLE\""
exit 1
fi

echo "PR title OK: \"$TITLE\""
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,4 @@ packages/plate/docs/

*.local*

.omx/
.omx/dogfood-output/
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
- Answer in English by default. Switch languages only when the user explicitly asks for another language.
- Prefer the best long-term architecture fix over the nearest local patch. If the real fix is an API or abstraction change, do that.

## Pagination (pretext gate)

- **pretext is mandatory for any pagination plugin.** Faithful pagination (page counting, widow/orphan, split points) requires real text measurement/shaping via pretext; block-level DOM `offsetHeight` estimates are a downgrade, not a substitute.
- If a pagination plugin does NOT use pretext, or uses it incompletely, you MUST NOT proceed silently. First make a **solemn attestation** stating plainly that pretext is not useful here and the concrete technical reason why.
- Then, in the same response, repeat the approval request **10 times** (numbered 1–10), each asking the user to explicitly approve pretext's absence or incomplete use. Do not continue with the pagination work until the user approves.

## Git

- **Git:** Never git add, commit, push, or create PR unless the user explicitly asks, or the active command/skill explicitly requires it.
Expand Down
2 changes: 1 addition & 1 deletion apps/www/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
import "./.next/types/routes.d.ts";
import "./.next/dev/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
1 change: 1 addition & 0 deletions apps/www/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"@platejs/math": "workspace:^",
"@platejs/media": "workspace:^",
"@platejs/mention": "workspace:^",
"@platejs/pagination": "workspace:^",
"@platejs/playwright": "workspace:^",
"@platejs/resizable": "workspace:^",
"@platejs/selection": "workspace:^",
Expand Down
8 changes: 8 additions & 0 deletions apps/www/src/app/dev/pagination2/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { PaginationView } from './pagination2-view';

// Browser-only: the layout engine measures real DOM, so don't prerender.
export const dynamic = 'force-dynamic';

export default function Page() {
return <PaginationView />;
}
71 changes: 71 additions & 0 deletions apps/www/src/app/dev/pagination2/pagination2-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use client';

import * as React from 'react';

import { PaginationPlugin } from '@platejs/pagination/react';
import type { Value } from 'platejs';
import { Plate, PlateContent, usePlateEditor } from 'platejs/react';

import { BasicNodesKit } from '@/registry/components/editor/plugins/basic-nodes-kit';

const PAGE_W = 794; // A4 @ 96dpi
const MARGIN = 96; // 1in

function makeValue(): Value {
const out: Value = [];
for (let i = 0; i < 40; i++) {
if (i % 8 === 0) {
out.push({ children: [{ text: `Section ${i / 8 + 1}` }], type: 'h2' });
} else {
out.push({
children: [
{
text: `Paragraph ${i}. This is a reasonably long paragraph of placeholder text so that the content reliably wraps onto multiple lines and flows across several A4 pages, exercising the pagination plugin end to end.`,
},
],
type: 'p',
});
}
}

return out;
}

/**
* Continuous-view demo for the pagination plugin: a single A4-width editable in
* normal flow; the plugin paints advisory page-break lines at each boundary.
*/
export function PaginationView() {
const editor = usePlateEditor({
plugins: [...BasicNodesKit, PaginationPlugin],
value: makeValue(),
});

return (
<div
data-testid="pagination-desk"
style={{
background: 'linear-gradient(#f3f4f6, #e5e7eb)',
minHeight: '100vh',
overflow: 'auto',
padding: 24,
}}
>
<div
data-testid="pagination-stack"
style={{
background: '#fff',
boxShadow: '0 2px 12px rgba(15,23,42,0.12)',
margin: '0 auto',
padding: MARGIN,
position: 'relative',
width: PAGE_W,
}}
>
<Plate editor={editor}>
<PlateContent style={{ outline: 'none' }} />
</Plate>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { MarkdownKit } from '@/registry/components/editor/plugins/markdown-kit';
import { MathKit } from '@/registry/components/editor/plugins/math-kit';
import { MediaKit } from '@/registry/components/editor/plugins/media-kit';
import { MentionKit } from '@/registry/components/editor/plugins/mention-kit';
import { PaginationKit } from '@/registry/components/editor/plugins/pagination-kit';
import { SlashKit } from '@/registry/components/editor/plugins/slash-kit';
import { SuggestionKit } from '@/registry/components/editor/plugins/suggestion-kit';
import { TableKit } from '@/registry/components/editor/plugins/table-kit';
Expand Down Expand Up @@ -65,6 +66,9 @@ export const EditorKit = [
...AlignKit,
...LineHeightKit,

// Layout
...PaginationKit,

// Collaboration
...DiscussionKit,
...CommentKit,
Expand Down
4 changes: 4 additions & 0 deletions apps/www/src/registry/components/editor/editor-kit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { MarkdownKit } from './plugins/markdown-kit';
import { MathKit } from './plugins/math-kit';
import { MediaKit } from './plugins/media-kit';
import { MentionKit } from './plugins/mention-kit';
import { PaginationKit } from './plugins/pagination-kit';
import { SlashKit } from './plugins/slash-kit';
import { SuggestionKit } from './plugins/suggestion-kit';
import { TableKit } from './plugins/table-kit';
Expand Down Expand Up @@ -67,6 +68,9 @@ export const EditorKit = [
...AlignKit,
...LineHeightKit,

// Layout
...PaginationKit,

// Collaboration
...DiscussionKit,
...CommentKit,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client';

import { PaginationPlugin } from '@platejs/pagination/react';

// Continuous-view page-break overlay. Enabled by default so demos show page
// markers immediately; the toolbar button toggles it at runtime.
export const PaginationKit = [PaginationPlugin];
1 change: 1 addition & 0 deletions apps/www/src/registry/registry-blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const registryBlocks: Registry['items'] = [
'math-kit',
'media-kit',
'mention-kit',
'pagination-kit',
'slash-kit',
'suggestion-kit',
'table-kit',
Expand Down
12 changes: 12 additions & 0 deletions apps/www/src/registry/registry-kits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,7 @@ export const registryKits: Registry['items'] = [
'math-kit',
'media-kit',
'mention-kit',
'pagination-kit',
'slash-kit',
'suggestion-kit',
'table-kit',
Expand All @@ -670,6 +671,17 @@ export const registryKits: Registry['items'] = [
],
type: 'registry:component',
},
{
dependencies: ['@platejs/pagination'],
files: [
{
path: 'components/editor/plugins/pagination-kit.tsx',
type: 'registry:component',
},
],
name: 'pagination-kit',
type: 'registry:component',
},
{
dependencies: ['@platejs/emoji', '@emoji-mart/data@1.2.1'],
files: [
Expand Down
10 changes: 10 additions & 0 deletions apps/www/src/registry/registry-ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ export const uiComponents: Registry['items'] = [
'media-toolbar-button',
'mode-toolbar-button',
'more-toolbar-button',
'pagination-toolbar-button',
'table-toolbar-button',
'toggle-toolbar-button',
'turn-into-toolbar-button',
Expand Down Expand Up @@ -796,6 +797,15 @@ export const uiComponents: Registry['items'] = [
title: 'More Toolbar Button',
type: 'registry:ui',
},
{
dependencies: ['@platejs/pagination'],
description: 'A toolbar button for page break markers.',
files: [{ path: 'ui/pagination-toolbar-button.tsx', type: 'registry:ui' }],
name: 'pagination-toolbar-button',
registryDependencies: ['toolbar'],
title: 'Pagination Toolbar Button',
type: 'registry:ui',
},
{
dependencies: ['@platejs/resizable'],
description: 'A resizable wrapper with resize handles.',
Expand Down
Loading