diff --git a/.agents/AGENTS.md b/.agents/AGENTS.md
index 3d1e8a06f2..c299862c87 100644
--- a/.agents/AGENTS.md
+++ b/.agents/AGENTS.md
@@ -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.
diff --git a/.changeset/pagination-automount-runtime.md b/.changeset/pagination-automount-runtime.md
new file mode 100644
index 0000000000..cff67071de
--- /dev/null
+++ b/.changeset/pagination-automount-runtime.md
@@ -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
diff --git a/.changeset/pagination-cache-key.md b/.changeset/pagination-cache-key.md
new file mode 100644
index 0000000000..3027162ee4
--- /dev/null
+++ b/.changeset/pagination-cache-key.md
@@ -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.
diff --git a/.changeset/pagination-compose-place-whole.md b/.changeset/pagination-compose-place-whole.md
new file mode 100644
index 0000000000..ab894681f4
--- /dev/null
+++ b/.changeset/pagination-compose-place-whole.md
@@ -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.
diff --git a/.changeset/pagination-continuous-breaks.md b/.changeset/pagination-continuous-breaks.md
new file mode 100644
index 0000000000..91c2b4ef98
--- /dev/null
+++ b/.changeset/pagination-continuous-breaks.md
@@ -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.
diff --git a/.changeset/pagination-enabled-option.md b/.changeset/pagination-enabled-option.md
new file mode 100644
index 0000000000..b4480e697d
--- /dev/null
+++ b/.changeset/pagination-enabled-option.md
@@ -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)`.
diff --git a/.changeset/pagination-mapping-in-output.md b/.changeset/pagination-mapping-in-output.md
new file mode 100644
index 0000000000..5c7ffe03da
--- /dev/null
+++ b/.changeset/pagination-mapping-in-output.md
@@ -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.
diff --git a/.changeset/pagination-margin-aware-packing.md b/.changeset/pagination-margin-aware-packing.md
new file mode 100644
index 0000000000..c28fd38528
--- /dev/null
+++ b/.changeset/pagination-margin-aware-packing.md
@@ -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.
diff --git a/.changeset/pagination-page-fixes.md b/.changeset/pagination-page-fixes.md
new file mode 100644
index 0000000000..e66d0d0cb5
--- /dev/null
+++ b/.changeset/pagination-page-fixes.md
@@ -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
diff --git a/.changeset/pagination-pretext-measure-block.md b/.changeset/pagination-pretext-measure-block.md
new file mode 100644
index 0000000000..dda337af26
--- /dev/null
+++ b/.changeset/pagination-pretext-measure-block.md
@@ -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.
diff --git a/.changeset/pagination-pretext-measure.md b/.changeset/pagination-pretext-measure.md
new file mode 100644
index 0000000000..fc610c5954
--- /dev/null
+++ b/.changeset/pagination-pretext-measure.md
@@ -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).
diff --git a/.changeset/pagination-react-continuous-overlay.md b/.changeset/pagination-react-continuous-overlay.md
new file mode 100644
index 0000000000..9560f971ff
--- /dev/null
+++ b/.changeset/pagination-react-continuous-overlay.md
@@ -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.
diff --git a/.changeset/pagination-scaffold.md b/.changeset/pagination-scaffold.md
new file mode 100644
index 0000000000..1c2b655bf2
--- /dev/null
+++ b/.changeset/pagination-scaffold.md
@@ -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`).
diff --git a/.changeset/pagination-scorch-mutator.md b/.changeset/pagination-scorch-mutator.md
new file mode 100644
index 0000000000..6e94e5f285
--- /dev/null
+++ b/.changeset/pagination-scorch-mutator.md
@@ -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.
diff --git a/.changeset/pagination-snapshot-text.md b/.changeset/pagination-snapshot-text.md
new file mode 100644
index 0000000000..44076b996e
--- /dev/null
+++ b/.changeset/pagination-snapshot-text.md
@@ -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.
diff --git a/.changeset/pagination-wrapped-blocks.md b/.changeset/pagination-wrapped-blocks.md
new file mode 100644
index 0000000000..2009c06a57
--- /dev/null
+++ b/.changeset/pagination-wrapped-blocks.md
@@ -0,0 +1,5 @@
+---
+"@platejs/pagination": patch
+---
+
+Fix page-break markers for editors whose block UI wraps Slate elements.
diff --git a/.changeset/utils-pagination-keys.md b/.changeset/utils-pagination-keys.md
new file mode 100644
index 0000000000..ba43c8dd18
--- /dev/null
+++ b/.changeset/utils-pagination-keys.md
@@ -0,0 +1,5 @@
+---
+'@platejs/utils': patch
+---
+
+Add `KEYS.pagination`, `KEYS.header`, `KEYS.footer`, `KEYS.pageBreak` for the pagination plugin family
diff --git a/.gitignore b/.gitignore
index 743b135db2..dfd43e2fc8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -129,4 +129,4 @@ packages/plate/docs/
*.local*
-.omx/
\ No newline at end of file
+.omx/dogfood-output/
diff --git a/AGENTS.md b/AGENTS.md
index ccf1e3f8a7..a88839593b 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -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.
diff --git a/apps/www/next-env.d.ts b/apps/www/next-env.d.ts
index 2d5420ebae..0c7fad710c 100644
--- a/apps/www/next-env.d.ts
+++ b/apps/www/next-env.d.ts
@@ -1,7 +1,7 @@
///
///
///
-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.
diff --git a/apps/www/package.json b/apps/www/package.json
index 1363600275..2d157b841d 100644
--- a/apps/www/package.json
+++ b/apps/www/package.json
@@ -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:^",
diff --git a/apps/www/src/app/dev/pagination2/page.tsx b/apps/www/src/app/dev/pagination2/page.tsx
new file mode 100644
index 0000000000..6d4eb95442
--- /dev/null
+++ b/apps/www/src/app/dev/pagination2/page.tsx
@@ -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 ;
+}
diff --git a/apps/www/src/app/dev/pagination2/pagination2-view.tsx b/apps/www/src/app/dev/pagination2/pagination2-view.tsx
new file mode 100644
index 0000000000..099440829d
--- /dev/null
+++ b/apps/www/src/app/dev/pagination2/pagination2-view.tsx
@@ -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 (
+
+ );
+}
diff --git a/apps/www/src/registry/blocks/editor-ai/components/editor/editor-kit.tsx b/apps/www/src/registry/blocks/editor-ai/components/editor/editor-kit.tsx
index 640dedbc9f..3d49f11883 100644
--- a/apps/www/src/registry/blocks/editor-ai/components/editor/editor-kit.tsx
+++ b/apps/www/src/registry/blocks/editor-ai/components/editor/editor-kit.tsx
@@ -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';
@@ -65,6 +66,9 @@ export const EditorKit = [
...AlignKit,
...LineHeightKit,
+ // Layout
+ ...PaginationKit,
+
// Collaboration
...DiscussionKit,
...CommentKit,
diff --git a/apps/www/src/registry/components/editor/editor-kit.tsx b/apps/www/src/registry/components/editor/editor-kit.tsx
index 258ac829e0..7d751d6915 100644
--- a/apps/www/src/registry/components/editor/editor-kit.tsx
+++ b/apps/www/src/registry/components/editor/editor-kit.tsx
@@ -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';
@@ -67,6 +68,9 @@ export const EditorKit = [
...AlignKit,
...LineHeightKit,
+ // Layout
+ ...PaginationKit,
+
// Collaboration
...DiscussionKit,
...CommentKit,
diff --git a/apps/www/src/registry/components/editor/plugins/pagination-kit.tsx b/apps/www/src/registry/components/editor/plugins/pagination-kit.tsx
new file mode 100644
index 0000000000..83db1c1cf1
--- /dev/null
+++ b/apps/www/src/registry/components/editor/plugins/pagination-kit.tsx
@@ -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];
diff --git a/apps/www/src/registry/registry-blocks.ts b/apps/www/src/registry/registry-blocks.ts
index 9298b02ccc..41a5933268 100644
--- a/apps/www/src/registry/registry-blocks.ts
+++ b/apps/www/src/registry/registry-blocks.ts
@@ -56,6 +56,7 @@ export const registryBlocks: Registry['items'] = [
'math-kit',
'media-kit',
'mention-kit',
+ 'pagination-kit',
'slash-kit',
'suggestion-kit',
'table-kit',
diff --git a/apps/www/src/registry/registry-kits.ts b/apps/www/src/registry/registry-kits.ts
index 01caa24791..a00abe12b4 100644
--- a/apps/www/src/registry/registry-kits.ts
+++ b/apps/www/src/registry/registry-kits.ts
@@ -662,6 +662,7 @@ export const registryKits: Registry['items'] = [
'math-kit',
'media-kit',
'mention-kit',
+ 'pagination-kit',
'slash-kit',
'suggestion-kit',
'table-kit',
@@ -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: [
diff --git a/apps/www/src/registry/registry-ui.ts b/apps/www/src/registry/registry-ui.ts
index 66d1ba19d6..eb20ff111a 100644
--- a/apps/www/src/registry/registry-ui.ts
+++ b/apps/www/src/registry/registry-ui.ts
@@ -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',
@@ -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.',
diff --git a/apps/www/src/registry/ui/fixed-toolbar-buttons.tsx b/apps/www/src/registry/ui/fixed-toolbar-buttons.tsx
index 398461db0d..568dde5da9 100644
--- a/apps/www/src/registry/ui/fixed-toolbar-buttons.tsx
+++ b/apps/www/src/registry/ui/fixed-toolbar-buttons.tsx
@@ -42,6 +42,7 @@ import { MarkToolbarButton } from './mark-toolbar-button';
import { MediaToolbarButton } from './media-toolbar-button';
import { ModeToolbarButton } from './mode-toolbar-button';
import { MoreToolbarButton } from './more-toolbar-button';
+import { PaginationToolbarButton } from './pagination-toolbar-button';
import { TableToolbarButton } from './table-toolbar-button';
import { ToggleToolbarButton } from './toggle-toolbar-button';
import { ToolbarGroup } from './toolbar';
@@ -125,6 +126,7 @@ export function FixedToolbarButtons() {
+
diff --git a/apps/www/src/registry/ui/pagination-toolbar-button.tsx b/apps/www/src/registry/ui/pagination-toolbar-button.tsx
new file mode 100644
index 0000000000..202d6a555a
--- /dev/null
+++ b/apps/www/src/registry/ui/pagination-toolbar-button.tsx
@@ -0,0 +1,26 @@
+'use client';
+
+import { PaginationPlugin } from '@platejs/pagination/react';
+import { SeparatorHorizontalIcon } from 'lucide-react';
+import { useEditorRef, usePluginOption } from 'platejs/react';
+import type * as React from 'react';
+
+import { ToolbarButton } from './toolbar';
+
+export function PaginationToolbarButton(
+ props: React.ComponentProps
+) {
+ const editor = useEditorRef();
+ const enabled = usePluginOption(PaginationPlugin, 'enabled');
+
+ return (
+ editor.setOption(PaginationPlugin, 'enabled', !enabled)}
+ pressed={enabled}
+ tooltip="Page breaks"
+ >
+
+
+ );
+}
diff --git a/bunfig.toml b/bunfig.toml
index 634b98ac0b..6c16ee2354 100644
--- a/bunfig.toml
+++ b/bunfig.toml
@@ -6,3 +6,11 @@ preload = ["./tooling/config/bunTestSetup.ts"]
tsconfig = "./tooling/config/tsconfig.test.json"
# Keep the inner loop quiet. Full pass spam is slower and useless.
onlyFailures = true
+# Exclude Playwright e2e tests - they should be run with `npx playwright test`
+root = "./packages"
+
+[install]
+# Supply-chain defense: require packages to be at least 7 days old before
+# installation. Compromised packages (stolen maintainer tokens) are almost
+# always yanked within hours, well before this window elapses.
+minimumReleaseAge = 604800
diff --git a/diary.md b/diary.md
new file mode 100644
index 0000000000..ee154b71e2
--- /dev/null
+++ b/diary.md
@@ -0,0 +1,154 @@
+# Pagination rewrite — work diary
+
+Truthful, disciplined log of the `@platejs/pagination` rewrite work. I separate
+**what I verified** from **what I believe but did not prove**, and I list the
+gaps I knowingly left. Where I made a mistake, it's recorded.
+
+- **Branch:** `codex/pagination-premirror-ideas` (off `codex/pagination-page-fixes`).
+- **PR:** #407 (base `codex/pagination-page-fixes`).
+- **Source of ideas:** `premirror` (the user's own MIT repo, cloned at
+ `../premirror`). I adapted its architecture/ideas; I wrote original Slate code,
+ did not copy premirror source.
+
+---
+
+## What the task was
+
+User mandate: complete rewrite of the Slate pagination package, borrowing
+premirror's deterministic *derived-layout* approach (page counting + presentation),
+"ensuring no detail is left behind." User explicitly chose the **full overlay**
+direction: the Slate document model never changes; pages are a render-time
+projection. Later the user chose **approach #1 (clipped clones)** for rendering
+blocks taller than a page.
+
+## Architecture I chose, and why
+
+Pipeline: `Slate value → buildSnapshot → measure (DOM) → composeLayout (pure) →
+geometry/projection → render (page chrome + spacers + split clones)`.
+
+- **Document model never mutates.** This kills the old engine's problems
+ (TrailingBlock normalization loop, `page`-node pollution, undo hazards) and is
+ yjs-friendly (no shared-doc mutation per client). I am **confident** this is
+ the right top-level call — it's also where premirror and Plate `main`'s
+ variant-A both landed.
+- **Pure `composeLayout`.** Measurement is pushed upstream (injected
+ `MeasureFn`), so the layout pass is deterministic, DOM-free, and unit-testable.
+ **Confident** — this is directly verified by tests.
+
+## What I built (modules)
+
+- `layout/types.ts` — the layout contract.
+- `layout/compose.ts` — pure page composition: fit / whole-block overflow /
+ splittable-block fragmenting / oversized overflow / manual breaks / widow-orphan
+ / keep-with-next, with a `breakReason` per boundary.
+- `layout/snapshot.ts` — Slate value → flat block snapshot, stable content ids.
+- `measure/measure.ts` — `measureSnapshot` (cache keyed by id+width; DOM read injected).
+- `react/domMeasure.ts` — pure-DOM `MeasureFn` via `[data-slate-node=element]` children.
+- `react/geometry.ts` — `getPageGeometry` / `getBlockPlacements` (page stacking).
+- `react/alignContent.ts` — page-start CSS spacers (whole-block alignment).
+- `layout/mapping.ts` — `MappingIndex` (block/line → page/fragment).
+- `layout/projection.ts` — `fragmentRects` / `blockLinePosition`.
+- `react/splitClones.ts` — `computeSplitPlan` (pure) + `renderSplitClones` (DOM):
+ clipped read-only clones for blocks taller than a page.
+
+## Key decisions (and honesty about each)
+
+1. **Block-level granularity, not line/run-level.** premirror's composer works at
+ line + run granularity (it has its own line breaker, `LineBox`/`PlacedRun`,
+ and `pmRange` on every unit). I deliberately compose at **top-level-block**
+ granularity and approximate lines as `lineCount ≈ round(heightPx /
+ lineHeightPx)`. This was a pragmatic choice to ship a working engine without
+ reimplementing text layout. **I am NOT confident this is "appropriate" — it is
+ a real fidelity reduction vs premirror**, and it's the most likely thing the
+ skeptical inspector agents (glm-5.1 + deepseek, still running at time of
+ writing) will flag as a mistranslation. The widow/orphan + split math inherits
+ the approximation error of that line estimate.
+
+2. **Spacers for whole-block alignment.** A single continuous `Editable`, with
+ `margin-top` spacers pushing page-start blocks to their page's content top.
+ Works well for normal short-block content. It **cannot** split one block
+ across pages — which led to decision #3.
+
+3. **Approach #1 (clipped clones) for split blocks.** Live `Editable` clipped to
+ the slice that fits its page; later slices rendered as read-only clipped
+ clones positioned by page geometry. The user chose this over glyph projection
+ (#2) after I gave a difficulty/CPU/yjs comparison. **Confident** it's the
+ pragmatic balance; **not** a pixel-perfect Word-class renderer, and it is
+ arguably a "hack" relative to premirror's decoration projection (the inspectors
+ may say so).
+
+4. **Verified on the playground template, not apps/www.** apps/www dev is broken
+ by a **pre-existing** `globals.css:8504` Turbopack-dev PostCSS error that 500s
+ every route there (unrelated to pagination; not my change). I confirmed my
+ code's imports were clean, then ran the demo on the template dev server (clean
+ CSS) instead. The template demo route + the vendored `./react` export are
+ **scratch** used only to run the demo; I did **not** commit them.
+
+## Bugs I introduced and then fixed (recorded, not hidden)
+
+- **130px overlap** at the live→clone junction: I first sliced clones using the
+ layout's uniform-lineHeight estimate while the live block sat in real DOM flow
+ — the two coordinate systems drifted. Fixed by slicing in **real measured
+ pixels** (live block's measured top/height + page geometry).
+- **Half-line duplication** at the clip: pixel-clipping cut a text line mid-line,
+ showing it partially on one page and fully on the next. Fixed by **snapping the
+ clip to line boundaries** via `Range.getClientRects()`.
+
+Both fixes were verified by re-screenshotting in agent-browser; the junction gap
+then measured exactly 216px (= bottom margin 96 + page gap 24 + top margin 96),
+which is the correct inter-page spacing.
+
+## What I actually verified (evidence)
+
+- **Unit tests: 161 pass** for the package (`bun test`), incl. the pure layers:
+ compose (10), snapshot (6), measure (6), geometry (2), mapping (5), projection
+ (3), splitClones plan (4). These cover the **pure** logic only.
+- **Typecheck** (`turbo typecheck --filter pagination`) and **biome lint** clean
+ after each commit.
+- **Live browser (agent-browser, template dev):** 4-page flow renders; page
+ numbers; clean page boundaries; a block ~7× page height splits across pages
+ with seamless junctions (screenshots taken).
+
+## What I did NOT do / cannot claim
+
+- **No automated test** covers `renderSplitClones`, `alignContentToLayout`, or
+ `domMeasure` — they are DOM side-effecting and verified **only** by manual
+ agent-browser screenshots. That is weaker evidence than a test.
+- **Editing inside clone regions is not implemented** — clones are read-only;
+ clicking a continuation does not place the caret. Known follow-up.
+- **Blocks *after* a split block are not correctly spaced** — the analytic spacer
+ assumes full-height flow. I sidestepped this in the demo by making the giant
+ block the **last** block. This is a real unsolved case, not a solved one.
+- **Selection/caret → page mapping (P5) is not built.** For whole-block content
+ the native Editable handles caret; across split boundaries it is unsolved.
+- **The glm-5.1 correctness bugs are NOT yet fixed:** `lineHeightPx` NaN/0 guard
+ (`compose.ts`), native-margin measurement gap (`domMeasure.ts` uses
+ `offsetHeight` only → progressive drift), measurement cache never evicted,
+ `type` dropped between snapshot stages. I reported them; I did not fix them.
+- **apps/www end-to-end is unverified** (its dev CSS is broken); only the template
+ path was exercised.
+- **Incremental invalidation** (premirror has a dirty-range seam) is **not**
+ implemented — every change does a full snapshot→measure→compose (the id-keyed
+ measure cache softens it, but it is not incremental compose).
+- The two **skeptical inspector agents** (glm-5.1, deepseek-v4-pro) I dispatched
+ to find mistranslations had **not returned** when I wrote this. Their findings
+ may contradict claims here; I have not folded them in.
+
+## Confidence summary
+
+- **High confidence:** the no-mutation overlay architecture; the pure compose
+ engine's correctness for its (block-level) model; determinism; mapping/projection.
+- **Medium confidence:** the clipped-clone renderer's visual correctness (verified
+ by eye, not tests; only common cases exercised).
+- **Low confidence / known weak:** block-level (vs line/run) granularity as a
+ faithful premirror translation; the `lineCount` approximation; everything in the
+ "did NOT do" list above.
+
+## Commits this session (rewrite arc), newest last
+
+- deterministic layout core (snapshot + compose)
+- measurement layer
+- DOM-backed block measurer
+- overlay renderer — page chrome + content alignment
+- MappingIndex + projection (P0 foundation)
+- split-block rendering via clipped clones (P0)
diff --git a/docs/plans/2026-05-15-pagination-plugin-refactor.md b/docs/plans/2026-05-15-pagination-plugin-refactor.md
new file mode 100644
index 0000000000..24b1504e34
--- /dev/null
+++ b/docs/plans/2026-05-15-pagination-plugin-refactor.md
@@ -0,0 +1,502 @@
+# `@platejs/pagination` Refactor Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: `superpowers:subagent-driven-development`. Steps use checkbox (`- [ ]`) syntax for tracking. Tasks are sequential — touch `BasePaginationPlugin.ts` / `reflowEngine.ts` repeatedly.
+
+**Goal:** Land the 18 review findings (P0–P3) from the 2026-05-15 pagination review against `north-star` + `plate-plugin-creator` + `react` skill rules.
+
+**Architecture:** Keep the current Slate-base + Plate-wrapper split. Replace editor-instance bolt-ons with a WeakMap registry, route shared keys through `KEYS`, kill all `as any`, drop manual memoization, split the composition lane (`overrideEditor` for `apply`/`normalizeNode`; `.extendTransforms` for feature methods), make Yjs a subpath export.
+
+**Tech Stack:** TypeScript, Slate 0.112+, Plate 52+, React 18+, React Compiler, Yjs (optional).
+
+**Branch:** `codex/pagination-folder-pick` (current). Commit per task; never push without explicit user ask.
+
+**Verification gates per task:**
+- `pnpm install` (only if package.json changed)
+- `pnpm turbo build --filter=./packages/pagination` (only if exports/types changed)
+- `pnpm turbo typecheck --filter=./packages/pagination`
+- `pnpm --filter @platejs/pagination test`
+- `pnpm lint:fix` (path-scoped to changed files)
+- `pnpm brl` (only on T5 or whenever public files move)
+
+---
+
+## Task 1 — Wire `KEYS.pagination` + `KEYS.p`
+
+**Files:**
+- Modify: `packages/pagination/src/BasePaginationPlugin.ts:27` (drop `PAGINATION_KEY`), `:214` (use `KEYS.pagination`), `:243` (use `KEYS.p`)
+- Verify: `packages/pagination/src/__tests__/BasePaginationPlugin.spec.ts`
+
+- [ ] **Step 1: Confirm tests pass on current branch**
+ - Run: `pnpm --filter @platejs/pagination test`
+ - Expected: full green (record baseline; do not proceed if red).
+
+- [ ] **Step 2: Add `KEYS` import + replace key constant**
+ - In `BasePaginationPlugin.ts`, add at top: `import { KEYS } from '@platejs/utils';` (use the same import path other Plate packages use — confirm by `grep -n "from '@platejs/utils'" packages/list/src/lib/*.ts`).
+ - Delete `const PAGINATION_KEY = 'pagination';` (line 27).
+ - Replace `key: PAGINATION_KEY` (line 214) with `key: KEYS.pagination`.
+ - Replace `defaultBlockType: 'p'` (line 243) with `defaultBlockType: KEYS.p`.
+ - Replace internal references `(editor as any).getType?.(PAGINATION_KEY)` / similar with `editor.getType(BasePaginationPlugin)` (after typing pass — for this task, do search/replace `PAGINATION_KEY` → `KEYS.pagination` only).
+
+- [ ] **Step 3: Add `@platejs/utils` to `dependencies` in `packages/pagination/package.json`**
+ - Confirm version matches sibling packages: `grep -A1 '"@platejs/utils"' packages/list/package.json`.
+
+- [ ] **Step 4: Verify typecheck + tests**
+ - Run: `pnpm install` (lockfile may change)
+ - Run: `pnpm turbo build --filter=./packages/pagination`
+ - Run: `pnpm turbo typecheck --filter=./packages/pagination`
+ - Run: `pnpm --filter @platejs/pagination test`
+ - Expected: all green.
+
+- [ ] **Step 5: Commit**
+ - `git add packages/pagination/src/BasePaginationPlugin.ts packages/pagination/package.json pnpm-lock.yaml`
+ - `git commit -m "refactor(pagination): route key through KEYS.pagination + KEYS.p"`
+
+---
+
+## Task 2 — WeakMap registry for runtime + mutating flag
+
+**Files:**
+- Create: `packages/pagination/src/internal/editorRegistry.ts`
+- Modify: `packages/pagination/src/BasePaginationPlugin.ts:55-303` (drop `(editor as any).__paginationRuntime`, `(editor as any).__paginationMutating`)
+- Modify: `packages/pagination/src/reflowEngine.ts:347` (consume new registry API)
+
+**Why:** `north-star`/`plate-plugin-creator` forbid bolt-on `__` properties via `as any`. Use `WeakMap` like `packages/footnote/src/lib/registry.ts`.
+
+- [ ] **Step 1: Write failing test for registry semantics**
+ - Create `packages/pagination/src/__tests__/editorRegistry.spec.ts`:
+ ```ts
+ import { createSlateEditor } from 'platejs';
+ import { BasePaginationPlugin } from '../BasePaginationPlugin';
+ import { getPaginationRuntime, isPaginationMutating, withPaginationMutations } from '../internal/editorRegistry';
+
+ test('getPaginationRuntime returns runtime once plugin is applied', () => {
+ const editor = createSlateEditor({ plugins: [BasePaginationPlugin] });
+ expect(getPaginationRuntime(editor)).toBeDefined();
+ });
+
+ test('withPaginationMutations toggles mutating flag for the editor only', () => {
+ const a = createSlateEditor({ plugins: [BasePaginationPlugin] });
+ const b = createSlateEditor({ plugins: [BasePaginationPlugin] });
+ let seen = false;
+ withPaginationMutations(a, () => {
+ seen = isPaginationMutating(a);
+ expect(isPaginationMutating(b)).toBe(false);
+ });
+ expect(seen).toBe(true);
+ expect(isPaginationMutating(a)).toBe(false);
+ });
+ ```
+ - Run: `pnpm --filter @platejs/pagination test editorRegistry.spec`
+ - Expected: FAIL (module not found).
+
+- [ ] **Step 2: Create `internal/editorRegistry.ts`**
+ ```ts
+ import type { SlateEditor } from 'platejs';
+ import type { PaginationRuntime } from '../types';
+
+ const runtimes = new WeakMap();
+ const mutating = new WeakSet();
+
+ export const setPaginationRuntime = (editor: SlateEditor, r: PaginationRuntime) => {
+ runtimes.set(editor, r);
+ };
+ export const getPaginationRuntime = (editor: SlateEditor): PaginationRuntime | undefined =>
+ runtimes.get(editor);
+ export const isPaginationMutating = (editor: SlateEditor) => mutating.has(editor);
+ export const withPaginationMutations = (editor: SlateEditor, fn: () => void) => {
+ const prev = mutating.has(editor);
+ mutating.add(editor);
+ try {
+ fn();
+ } finally {
+ if (!prev) mutating.delete(editor);
+ }
+ };
+ ```
+
+- [ ] **Step 3: Wire registry into `BasePaginationPlugin.ts`**
+ - In `withPagination`, replace lines 60-62:
+ ```ts
+ const runtime = createPaginationRuntime();
+ setPaginationRuntime(editor, runtime);
+ ```
+ - Replace every `(editor as any).__paginationMutating` read with `isPaginationMutating(editor)`.
+ - Delete the bottom helper functions `withPaginationMutations` (`:248-256`) and `getPaginationRuntime` (`:299-303`).
+ - Re-export from `internal/editorRegistry` at the top of the file for backward-compat to internal callers.
+
+- [ ] **Step 4: Update `reflowEngine.ts`**
+ - Replace `import { ..., withPaginationMutations } from './BasePaginationPlugin'` with `import { withPaginationMutations } from './internal/editorRegistry'`.
+
+- [ ] **Step 5: Run tests**
+ - Run: `pnpm --filter @platejs/pagination test`
+ - Expected: all green incl. new spec.
+
+- [ ] **Step 6: Commit**
+ - `git commit -m "refactor(pagination): move runtime+mutating flag to WeakMap registry"`
+
+---
+
+## Task 3 — Type the `overrideEditor` body (`BasePaginationPlugin.ts`)
+
+**Files:**
+- Modify: `packages/pagination/src/BasePaginationPlugin.ts` (entire `withPagination` block + helper fns)
+
+**Goal:** Zero `as any` and zero `any`-typed params in this file. Use plugin context (`editor`, `tf`, `getOptions`, `getOption`, `type`).
+
+- [ ] **Step 1: Type the override editor**
+ - The `OverrideEditor` already infers context. Destructure: `({ editor, type, getOptions, tf: { apply, normalizeNode } })`.
+ - Type `apply(op: Operation)` (already imported). Strip the `(editor as any)` cast — `editor` is typed.
+ - Type `normalizeNode(entry: NodeEntry)` using `import { type NodeEntry }` from `slate`.
+ - Replace `(node as any)?.type === type` with `Element.isElement(node) && node.type === type` (import `Element` from slate).
+
+- [ ] **Step 2: Type feature transforms**
+ - Define `type PaginationTransforms = { togglePreview: () => boolean; setPageSize: (size: 'A4' | 'Letter' | 'Legal') => void; setMargins: (m: DocumentSettings['margins']) => void; toggleHeader: () => boolean; toggleFooter: () => boolean; };`
+ - Stop using `editor.getPlugin(BasePaginationPlugin) as any` — use `getOptions()` from the destructured ctx.
+
+- [ ] **Step 3: Type helper functions**
+ - `wrapRootRange(editor: SlateEditor, type: string, start: number, end: number)` — drop `any`.
+ - `normalizeRootChildren(editor: SlateEditor, type: string): boolean` — drop `any`.
+
+- [ ] **Step 4: Verify typecheck**
+ - Run: `pnpm turbo build --filter=./packages/pagination` then `pnpm turbo typecheck --filter=./packages/pagination`
+ - Run: `pnpm --filter @platejs/pagination test`
+ - Expected: all green.
+
+- [ ] **Step 5: Grep self-check**
+ - Run: `grep -nE "\\b(as any|: any)\\b" packages/pagination/src/BasePaginationPlugin.ts`
+ - Expected: zero matches.
+
+- [ ] **Step 6: Commit**
+ - `git commit -m "refactor(pagination): remove any from BasePaginationPlugin"`
+
+---
+
+## Task 4 — Type `reflowEngine.ts` + `runtime.ts`
+
+**Files:**
+- Modify: `packages/pagination/src/reflowEngine.ts`, `packages/pagination/src/runtime.ts`
+
+- [ ] **Step 1: Type `reflowEngine.ts`**
+ - `editor: Editor` (already typed), drop `(editor as any).getType?.` — call `editor.getType(BasePaginationPlugin)` directly (this method exists on `SlateEditor`).
+ - Replace `(editor as any).getOption?.(BasePaginationPlugin, 'defaultBlockType')` with `editor.getOption(BasePaginationPlugin, 'defaultBlockType')`.
+ - Replace `(editor as any).hasEditableTarget` with a guard `'hasEditableTarget' in editor`.
+ - For `(ReactEditor as any).toDOMRange`, do not cast: import the typed method from `slate-react`, accept the type narrowing required by `ReactEditor.toDOMRange(editor, range)` (cast only the editor to `ReactEditor` once via `ReactEditor.isReactEditor(editor)` guard).
+ - Strip the `nextPageDom: PageDom | undefined` literal — `PageDom | undefined` is already what `getPageDom` returns; just import it.
+
+- [ ] **Step 2: Type `runtime.ts`**
+ - Replace `const anyOp = op as any` with destructured access on `Operation` types. Slate's `Operation` is a discriminated union — switch on `op.type` or use `'path' in op` / `'newPath' in op` narrowing.
+ - Final shape:
+ ```ts
+ export function getPageIndexFromOp(op: Operation): number | null {
+ const indices: number[] = [];
+ if ('path' in op && Array.isArray(op.path) && op.path.length > 0) indices.push(op.path[0]);
+ if ('newPath' in op && Array.isArray(op.newPath) && op.newPath.length > 0) indices.push(op.newPath[0]);
+ return indices.length ? Math.min(...indices) : null;
+ }
+ ```
+
+- [ ] **Step 3: Verify**
+ - Run: `pnpm turbo typecheck --filter=./packages/pagination`
+ - Run: `pnpm --filter @platejs/pagination test`
+ - Expected: all green.
+
+- [ ] **Step 4: Grep self-check**
+ - Run: `grep -nE "\\b(as any|: any)\\b" packages/pagination/src/reflowEngine.ts packages/pagination/src/runtime.ts`
+ - Expected: zero matches.
+
+- [ ] **Step 5: Commit**
+ - `git commit -m "refactor(pagination): remove any from reflowEngine and runtime"`
+
+---
+
+## Task 5 — Generate barrel via `pnpm brl`
+
+**Files:**
+- Modify: `packages/pagination/src/index.ts` (regenerated)
+
+- [ ] **Step 1: Run barrel generator**
+ - Run: `pnpm --filter @platejs/pagination brl`
+ - Inspect the diff; confirm exports cover `BasePaginationPlugin`, `PaginationPlugin`, `PaginationCoordinator`, `PaginationRegistryProvider`, `usePaginationRegistry`, `createAlwaysLeader`, `createAwarenessLeaderElection`, the type re-exports.
+
+- [ ] **Step 2: If brl produces unwanted exports**
+ - Move helpers under `src/internal/` so brl skips them (per `plate-plugin-creator` barrel rule).
+ - Re-run brl until output is minimal public surface.
+
+- [ ] **Step 3: Verify**
+ - Run: `pnpm turbo build --filter=./packages/pagination`
+ - Run: `pnpm --filter @platejs/pagination test`
+
+- [ ] **Step 4: Commit**
+ - `git commit -m "chore(pagination): regenerate barrel via pnpm brl"`
+
+---
+
+## Task 6 — Yjs subpath export + optional peer
+
+**Files:**
+- Modify: `packages/pagination/package.json` (`exports`, `peerDependencies`, `peerDependenciesMeta`)
+- Move: `packages/pagination/src/YjsIntegration.tsx` → still in `src/`, but no longer in main barrel
+- Remove from main barrel: `YjsPaginationBridge` re-export (`index.ts:27`)
+- Create: a separate barrel entry for `./yjs`
+
+- [ ] **Step 1: Update `package.json`**
+ - Add to `exports`:
+ ```json
+ "./yjs": "./dist/yjs.js"
+ ```
+ - Add `@platejs/yjs` to `peerDependenciesMeta` with `{ "optional": true }`.
+
+- [ ] **Step 2: Move Yjs surface**
+ - Create `packages/pagination/src/yjs.ts`:
+ ```ts
+ export { YjsPaginationBridge } from './YjsIntegration';
+ ```
+ - Confirm build emits `dist/yjs.js`. If `plate-pkg p:build` requires explicit entry registration, update the package script config — check sibling packages (`grep -l '"./yjs"' packages/*/package.json`).
+
+- [ ] **Step 3: Remove from main barrel**
+ - Re-run `pnpm brl` after `YjsIntegration.tsx` is gated. If brl still picks it up, move it under `src/internal/yjs/YjsIntegration.tsx` and re-export only from `src/yjs.ts`.
+
+- [ ] **Step 4: Verify**
+ - Run: `pnpm install`
+ - Run: `pnpm turbo build --filter=./packages/pagination`
+ - Run: `pnpm --filter @platejs/pagination test`
+ - Verify a consumer can `import { PaginationPlugin } from '@platejs/pagination'` without `@platejs/yjs` installed.
+
+- [ ] **Step 5: Commit**
+ - `git commit -m "feat(pagination): expose Yjs bridge under ./yjs subpath, mark optional"`
+
+---
+
+## Task 7 — Remove manual memoization in `PaginationCoordinator`
+
+**Files:**
+- Modify: `packages/pagination/src/PaginationCoordinator.tsx`
+
+**Why:** Repo runs React Compiler (`react-compiler-runtime` in deps). `react` skill: *NEVER use `useCallback`/`useMemo` for perf*.
+
+- [ ] **Step 1: Strip `useCallback` from `shouldProcess`, `runReflow`, `scheduleReflowFrom`**
+ - Replace each with plain function declarations.
+ - Delete the `scheduleReflowFromRef` ref-dance (lines 69–78, 148) — call `scheduleReflowFrom` directly inside `runReflow`. The compiler memoizes for you.
+
+- [ ] **Step 2: Audit other manual memos**
+ - `useRef(leader.amILeader())` (line 45) — keep as ref but re-init in a `useEffect([leader])` to cover prop change (covered fully in Task 12).
+ - Keep `useRef` for genuine mutable handles (`scheduledRef`, `resizeTimerRef`, `runningRef`, `pendingStartRef`, `isLeaderRef`).
+
+- [ ] **Step 3: Verify**
+ - Run: `pnpm --filter @platejs/pagination test`
+ - Expected: all PaginationCoordinator specs still green.
+
+- [ ] **Step 4: Commit**
+ - `git commit -m "refactor(pagination): drop manual memoization in PaginationCoordinator"`
+
+---
+
+## Task 8 — Move example files out of `src/`
+
+**Files:**
+- Delete: `packages/pagination/src/example_visualization_with_toggle/`
+
+**Why:** A 350-line lucide-react wireframe with zero Plate API doesn't belong in a publishable package's `src`. Two `.md` siblings have the same problem.
+
+- [ ] **Step 1: Confirm the dir has no inbound imports from production code**
+ - Run: `grep -rn "example_visualization_with_toggle" packages/pagination/src --include='*.ts' --include='*.tsx'`
+ - Expected: zero matches (or only matches within the directory itself).
+
+- [ ] **Step 2: Delete the directory**
+ - Run: `git rm -r packages/pagination/src/example_visualization_with_toggle`
+ - If user wants the wireframe preserved, ask first; otherwise delete.
+
+- [ ] **Step 3: Verify build**
+ - Run: `pnpm turbo build --filter=./packages/pagination`
+ - Run: `pnpm --filter @platejs/pagination test`
+
+- [ ] **Step 4: Commit**
+ - `git commit -m "chore(pagination): remove non-Plate example wireframe from src"`
+
+---
+
+## Task 9 — Split composition lane via `.extendTransforms`
+
+**Files:**
+- Modify: `packages/pagination/src/BasePaginationPlugin.ts`
+
+**Why:** `plate-plugin-creator` composition rule — feature methods go on `.extendTransforms()` (plugin-specific surface), not mixed inside `overrideEditor`.
+
+- [ ] **Step 1: Extract feature methods from `withPagination`**
+ - Move `togglePreview`, `setPageSize`, `setMargins`, `toggleHeader`, `toggleFooter` out of the `transforms.pagination` block in `withPagination`.
+ - Leave `apply` and `normalizeNode` in `withPagination`.
+
+- [ ] **Step 2: Chain `.extendTransforms` on the plugin definition**
+ ```ts
+ export const BasePaginationPlugin = createTSlatePlugin({ ... })
+ .overrideEditor(withPagination)
+ .extendTransforms(({ editor, getOptions }) => ({
+ togglePreview() { ... },
+ setPageSize(size) { ... },
+ setMargins(margins) { ... },
+ toggleHeader() { ... },
+ toggleFooter() { ... },
+ }));
+ ```
+ - Each method calls `editor.setOption(BasePaginationPlugin, key, value)` / `getOptions()` instead of the old `getPlugin().options` access.
+
+- [ ] **Step 3: Verify**
+ - Run: `pnpm turbo typecheck --filter=./packages/pagination`
+ - Run: `pnpm --filter @platejs/pagination test`
+ - Expected: all green; transforms still callable as `editor.tf.pagination.togglePreview()`.
+
+- [ ] **Step 4: Commit**
+ - `git commit -m "refactor(pagination): move feature transforms to .extendTransforms"`
+
+---
+
+## Task 10 — P2 batch A: SSR-safe scheduleIdle + debug-guarded console.error + microtask-coalesced notify
+
+**Files:**
+- Create: `packages/pagination/src/internal/scheduleIdle.ts`
+- Modify: `packages/pagination/src/PaginationCoordinator.tsx`
+- Modify: `packages/pagination/src/reflowEngine.ts:380`
+- Modify: `packages/pagination/src/types.ts` (`ReflowOptions.debug?: boolean`)
+- Modify: `packages/pagination/src/runtime.ts` (notify coalescing)
+
+- [ ] **Step 1: Test for microtask coalescing (TDD red)**
+ - Add `packages/pagination/src/__tests__/runtime.spec.ts` case:
+ ```ts
+ test('multiple markDirty in same tick produce one notification', async () => {
+ const r = createPaginationRuntime();
+ let count = 0;
+ r.subscribe(() => count++);
+ r.markDirty(0); r.markDirty(1); r.markDirty(2);
+ await Promise.resolve();
+ expect(count).toBe(1);
+ });
+ ```
+ - Run test, expect FAIL (notify fires synchronously 3×).
+
+- [ ] **Step 2: Implement microtask flag in `runtime.ts`**
+ ```ts
+ let pending = false;
+ const notify = () => {
+ if (pending) return;
+ pending = true;
+ queueMicrotask(() => {
+ pending = false;
+ subscribers.forEach((fn) => fn());
+ });
+ };
+ ```
+ - Test goes green.
+
+- [ ] **Step 3: Extract `scheduleIdle`**
+ ```ts
+ export const scheduleIdle = (cb: () => void): void => {
+ if (typeof window === 'undefined') return; // SSR no-op
+ const ric = (window as Window & typeof globalThis & { requestIdleCallback?: (cb: () => void) => number }).requestIdleCallback;
+ if (ric) ric(cb);
+ else window.setTimeout(cb, 0);
+ };
+ ```
+ - Replace the inline `ric` block in `PaginationCoordinator.tsx`.
+
+- [ ] **Step 4: Add `debug` flag**
+ - `types.ts`: `debug?: boolean` on `ReflowOptions`.
+ - `BasePaginationPlugin.ts`: default `debug: false`.
+ - `reflowEngine.ts:380`: replace `console.error('Text split failed:', e)` with `if (opts.debug) console.error('Text split failed:', e)`.
+
+- [ ] **Step 5: Verify**
+ - Run: `pnpm --filter @platejs/pagination test`
+ - Run: `pnpm turbo typecheck --filter=./packages/pagination`
+
+- [ ] **Step 6: Commit**
+ - `git commit -m "refactor(pagination): coalesce notify, extract scheduleIdle, gate debug logs"`
+
+---
+
+## Task 11 — P2 batch B: `withPaginationMutations` as a transform + monotonic-offset fallback
+
+**Files:**
+- Modify: `packages/pagination/src/BasePaginationPlugin.ts` (extend transforms)
+- Modify: `packages/pagination/src/reflowEngine.ts` (consume new transform, add linear fallback)
+- Keep: `packages/pagination/src/internal/editorRegistry.ts` (low-level still used)
+
+- [ ] **Step 1: Expose `withMutations` on plugin transforms**
+ - In the `.extendTransforms` block from Task 9, add:
+ ```ts
+ withMutations(fn: () => void) {
+ withPaginationMutations(editor, fn);
+ },
+ ```
+ - Call sites in `reflowEngine.ts` use `editor.tf.pagination.withMutations(...)` instead of importing the helper.
+
+- [ ] **Step 2: Linear-scan fallback for non-monotonic `offsetTop`**
+ - In `findOverflowSplitIndex` (`reflowEngine.ts:222`), before binary search, sample first 3 children. If `child[i+1].offsetTop < child[i].offsetTop` for any pair, switch to linear scan.
+ - Add TDD test with a stub `contentEl` whose `children[1].offsetTop < children[0].offsetTop`. Assert correct index returned.
+
+- [ ] **Step 3: Verify**
+ - Run: `pnpm --filter @platejs/pagination test`
+ - Run: `pnpm turbo typecheck --filter=./packages/pagination`
+
+- [ ] **Step 4: Commit**
+ - `git commit -m "refactor(pagination): expose mutations transform + non-monotonic split fallback"`
+
+---
+
+## Task 12 — P3 batch: leader-ref re-init + setPageSize typing + dedupe markDirty loops + hardcoded type cleanup
+
+**Files:**
+- Modify: `packages/pagination/src/PaginationCoordinator.tsx`
+- Modify: `packages/pagination/src/BasePaginationPlugin.ts`
+
+- [ ] **Step 1: Re-init leader ref on prop change**
+ - In `PaginationCoordinator.tsx`, replace `useRef(leader.amILeader())` with:
+ ```ts
+ const isLeaderRef = useRef(leader.amILeader());
+ useEffect(() => {
+ isLeaderRef.current = leader.amILeader();
+ }, [leader]);
+ ```
+
+- [ ] **Step 2: Narrow `setPageSize` parameter type**
+ - In `BasePaginationPlugin.ts`, narrow to `setPageSize(size: 'A4' | 'Letter' | 'Legal')`.
+
+- [ ] **Step 3: Collapse dedup mark-dirty loops**
+ - In `toggleHeader` / `toggleFooter`, replace the `for (let i = 0; i < children.length; i++) runtime.markDirty(i)` with `runtime.markDirty(0)` (consumeDirtyMin already cascades).
+
+- [ ] **Step 4: Remove hardcoded `'page'` literal**
+ - Audit `BasePaginationPlugin.ts`/`reflowEngine.ts` for `'page'` string literals. Replace with `editor.getType(BasePaginationPlugin)`. The `node.type: 'page'` declaration stays — that's the canonical type name.
+
+- [ ] **Step 5: Final `any`/literal grep**
+ - Run: `grep -rnE "\\b(as any|: any)\\b" packages/pagination/src/`
+ - Run: `grep -rnE "'(pagination|header|footer)'" packages/pagination/src/ --include='*.ts' --include='*.tsx'`
+ - Expected: zero `any`. Strings only where introducing new node types is intentional (`'header'`/`'footer'` block creation).
+
+- [ ] **Step 6: Verify full package**
+ - Run: `pnpm turbo build --filter=./packages/pagination`
+ - Run: `pnpm turbo typecheck --filter=./packages/pagination`
+ - Run: `pnpm --filter @platejs/pagination test`
+ - Run: `pnpm lint:fix`
+
+- [ ] **Step 7: Commit**
+ - `git commit -m "polish(pagination): leader re-init, narrowed page-size, dedup loops"`
+
+---
+
+## Final Verification (after all tasks)
+
+- [ ] Run: `pnpm turbo build --filter=./packages/pagination`
+- [ ] Run: `pnpm turbo typecheck --filter=./packages/pagination`
+- [ ] Run: `pnpm --filter @platejs/pagination test`
+- [ ] Run: `pnpm lint:fix`
+- [ ] Spot-check: `git log --oneline codex/pagination-folder-pick ^origin/codex/pagination-folder-pick` shows 12 commits.
+- [ ] Self-grep: `grep -rnE "\\b(as any|: any|__pagination)\\b" packages/pagination/src/` — zero matches.
+- [ ] Add changeset per `.agents/rules/changeset.mdc` if package version should bump.
+
+---
+
+## Notes for implementer subagents
+
+- Use the `task` superpower per CLAUDE.md.
+- TDD discipline: for behavior changes (T2 microtask coalesce, T11 fallback), write the failing test first. For pure refactors (T1, T3–T9, T12), confirm green → refactor → confirm green again.
+- Never push or open PR without explicit user ask. Commit only.
+- If a step's verification reveals pre-existing build/typecheck noise unrelated to the change, run `pnpm run reinstall` once before debugging deeper (per CLAUDE.md guidance).
+- Each task is independent of the next on file boundaries; do not skip ahead — later tasks assume earlier tasks landed (e.g., T9 assumes T2/T3 done).
diff --git a/docs/plans/2026-05-20-pagination-wiring.md b/docs/plans/2026-05-20-pagination-wiring.md
new file mode 100644
index 0000000000..4e081728dc
--- /dev/null
+++ b/docs/plans/2026-05-20-pagination-wiring.md
@@ -0,0 +1,59 @@
+# Wire `@platejs/pagination` into apps/www (+ auto-mount runtime)
+
+**Goal:** Make `PaginationPlugin` usable as a one-line plugin and wire it into the
+main `apps/www` `EditorKit` so the playground editor is paginated.
+
+**Decisions (user-confirmed 2026-05-20):**
+- Wiring location: main `EditorKit` (default playground becomes paginated).
+- Package fix: yes — React `PaginationPlugin` auto-mounts its required runtime.
+
+**north-star reaffirmed: laws** — React wrapper owns mounting its own React
+runtime (registry provider + coordinator). Leader-collab mode delegates
+coordinator ownership to the yjs bridge. No new public API shape.
+
+**Branch / PR strategy (stacked):**
+- PR1 `codex/pagination-plugin-automount` (off `codex/pagination-folder-pick`):
+ package change.
+- PR2 `codex/pagination-www-wiring` (off PR1): apps/www wiring.
+
+---
+
+## PR1 — Package: auto-mount provider + coordinator
+
+The React `PaginationPlugin` currently only wires `render.node: PageElement`.
+`PageElement` calls `usePaginationRegistry()` (warns without provider) and reflow
+only runs if a `PaginationCoordinator` is mounted. Fix: the wrapper mounts both.
+
+- `render.aboveEditable = PaginationRegistryProvider` (wraps the Editable → pages).
+- `render.afterEditable = PaginationAfterEditable` (internal wrapper that mounts
+ `PaginationCoordinator` unless `collaboration.mode === 'leader'`).
+
+### TDD cycles (packages/pagination)
+- [ ] Cycle 1 (RED→GREEN): `PaginationPlugin.render.aboveEditable === PaginationRegistryProvider`.
+- [ ] Cycle 2: `PaginationPlugin.render.afterEditable` is defined (auto coordinator).
+- [ ] Cycle 3 (behavior): `PaginationAfterEditable` renders a `PaginationCoordinator`
+ when `collaboration.mode === 'all'`, renders `null` when `mode === 'leader'`.
+
+### Verify
+- `pnpm --filter @platejs/pagination test`
+- `pnpm turbo build --filter=./packages/pagination` then `typecheck`
+- `pnpm brl` (new internal file shouldn't change barrel; confirm)
+- `pnpm lint:fix`
+- changeset (patch/minor: pagination now auto-mounts its runtime)
+
+---
+
+## PR2 — apps/www wiring
+
+- [ ] Add `@platejs/pagination` to `apps/www/package.json` deps.
+- [ ] Create `apps/www/src/registry/components/editor/plugins/pagination-kit.tsx`
+ exporting `PaginationKit = [PaginationPlugin]`.
+- [ ] Add `...PaginationKit` to `EditorKit` in `editor-kit.tsx`.
+- [ ] Add to `registry-kits.ts` if other kits are registered there.
+
+### Verify
+- `pnpm install`
+- `pnpm turbo typecheck --filter=./apps/www` (build deps first)
+- `pnpm lint:fix`
+- Browser: load the playground via dev-browser, confirm pages render + reflow,
+ no `usePaginationRegistry` warning in console.
diff --git a/docs/plans/2026-05-21-pagination-rewrite.md b/docs/plans/2026-05-21-pagination-rewrite.md
new file mode 100644
index 0000000000..4204e00197
--- /dev/null
+++ b/docs/plans/2026-05-21-pagination-rewrite.md
@@ -0,0 +1,154 @@
+# Pagination rewrite — borrow premirror ideas (Slate)
+
+**Goal:** Complete rewrite of `@platejs/pagination` (Slate), borrowing premirror's
+deterministic derived-layout ideas for **page counting/measurement** and
+**presentation**. Branch: `codex/pagination-premirror-ideas`. premirror cloned at
+`../premirror`.
+
+**Mandate:** User authorized a complete redo, "ensuring no detail is left behind."
+
+## Core architectural shift
+- **Current (model-mutating):** wrap root content into `page` nodes; a React
+ `PaginationCoordinator` measures real DOM (offsetTop/offsetHeight) and
+ `moveNodes` blocks between pages (`reflowEngine.ts`), splitting oversized
+ blocks via `ReactEditor.toDOMRange` binary search. Mutates the doc → fights
+ other plugins (TrailingBlock normalization loop), pollutes the model with
+ `page` nodes, undo-history hazards (mitigated via `withoutSaving`).
+- **premirror (derived overlay):** PM owns the doc unchanged; composer takes a
+ measured snapshot → deterministic layout (pages/frames/lines/runs) →
+ React projects fragments into absolute-positioned page viewports. Document
+ model never changes. (This is also where `main`'s `@platejs/pagination@0.0.0`
+ "variant A render-time overlay + pretext height oracle" already went.)
+
+## Current package inventory (to preserve/supersede — nothing left behind)
+- `BasePaginationPlugin.ts` (333) — key `'pagination'`, node `page`
+ (isElement/isContainer), `normalizeRootChildren` wrapping, `normalizeInitialValue`,
+ `onNodeChange` re-wrap, `overrideEditor` (apply/normalizeNode), transforms:
+ `togglePreview`, `setPageSize` (A4/Letter/Legal), `setMargins`, `toggleHeader`,
+ `toggleFooter`, `withMutations`. Options: `documentSettings` (sizes/margins),
+ `reflow` (enabled, debounceMs, maxPagesPerIdle, underflow, allowTextSplit,
+ overflow/underflowThresholdPx, debug), `collaboration` (mode all/leader),
+ `defaultBlockType`, `viewMode` (paginated/continuous).
+- `reflowEngine.ts` (418) — `reflowPageBoundary` (overflow push / underflow pull,
+ hysteresis), `findOverflowSplitIndex` (binary search + non-monotonic linear
+ fallback), `splitOversizedBlock` (text split via toDOMRange).
+- `PaginationCoordinator.tsx` (212) — dirty-page loop, idle scheduling, leader gating.
+- `registry.tsx` — `PaginationRegistryProvider` + page DOM registry.
+- `PageElement.tsx` — renders page box (paginated white A4 + shadow / continuous),
+ bottom-margin page number (just added).
+- `internal/`: `runtime.ts` (dirty set + microtask notify), `scheduleIdle.ts`
+ (SSR-safe ric), `editorRegistry.ts` (WeakMap runtime + mutating flag),
+ `PaginationAboveEditable.tsx` (provider + coordinator).
+- `leaderElection.ts` (always/awareness), `yjs/YjsIntegration.tsx`.
+- Tests: ~2621 lines across 12 files.
+
+## Recent fixes already landed (PR #405 / #406 — folder-pick lineage)
+- Literal `'pagination'` key (KEYS.pagination unreleased in published utils).
+- Single shared provider+coordinator subtree (reflow reads registry).
+- Page number in bottom margin.
+- Template: drop TrailingBlock conflict; rely on package auto-mount.
+
+## Research (in flight — 4 agents on premirror)
+1. composer engine (page flow/counting, line breaking, widow/orphan, determinism)
+2. core + measurement (page specs, pretext height oracle, snapshot, LayoutOutput)
+3. react presentation (page chrome viewports, absolute positioning, decorations)
+4. design docs (architecture/rationale, API, policies, testing strategy)
+
+## DECISION: Full premirror-style overlay (user-chosen)
+Document model NEVER changes (no `page` nodes). One Slate `Editable`; pages are a
+derived render projection. Kills the TrailingBlock normalization conflict + model
+pollution + undo hazards entirely.
+
+## Synthesized design (from 4 premirror research agents)
+Pipeline (adapted to Slate, DOM as measurement source):
+```
+Slate value → snapshot(flat blocks + stable ids + slate paths)
+ → measure (real DOM block heights, cached) → MeasuredSnapshot
+ → composeLayout(pure, deterministic) → LayoutOutput
+ → render overlay (page chrome + projected content) + mapping
+```
+
+### Layout contract (`src/layout/types.ts`) — mirror premirror, Slate-flavored
+- `PageSpec { widthPx, heightPx, preset? }`; presets A4 `794×1123`, Letter `816×1056` (96dpi).
+- `PageMargins { topPx,rightPx,bottomPx,leftPx }` (default 96 = 1in). Content frame = page − margins.
+- `LayoutPolicies { widowLinesMin:2, orphanLinesMin:2, keepWithNextEnabled:true }`.
+- `UnmeasuredSnapshot = { blocks: { id, path, type, attrs }[] }` (flat, top-level blocks).
+- `MeasuredSnapshot` = blocks + `{ heightPx, lineHeightPx, lineCount }` (DOM-measured, cached by id+content+width).
+- `LayoutOutput = { pages: PageLayout[]; mapping: MappingIndex; metrics }`.
+ - `PageLayout { index, spec, frames: FrameLayout[] }`
+ - `FrameLayout { bounds: Rect; fragments: BlockFragment[] }`
+ - `BlockFragment { blockId, fragmentIndex, slateRange{path,offsetStart,offsetEnd}, y, height, breakReason? }`
+ - `BreakReason = 'block_overflow' | 'manual_break' | 'keep_with_next' | 'widow_orphan'`
+ - `MappingIndex { pathToPage(path)→pageIndex; ... }` (Slate path ↔ page/fragment).
+- Use **measured per-line heights** (not premirror's uniform lineHeightPx grid) — we have real DOM.
+
+### compose (`src/layout/compose.ts`) — PURE, DOM-free, deterministic
+Block-level fill (Slate has no runs): accumulate `currentY` from measured block
+heights against `frame.height`; flush page when a block overflows. Port
+premirror's `linesThatFitFirstFragment` widow/orphan arithmetic at block-line
+granularity (lineCount ≈ round(height/lineHeight)); keep-with-next look-ahead;
+manual break via block attr. Emit `breakReason` per boundary. Same input →
+identical output (snapshot-tested).
+
+### measure (`src/measure/`)
+Measure each top-level block's rendered height + line height from a hidden
+measurement container (page content width), cache by `{blockId, contentHash, widthPx}`.
+Only re-measure dirty blocks (derive dirty set from Slate ops).
+
+### react overlay (`src/react/`)
+- Page-chrome surfaces: absolute white pages (paper recipe `boxShadow 0 2px 12px rgba(15,23,42,.12)`,
+ `1px solid #e5e7eb`, gray desk), `getPageGeometry()` → per-page {left,top},
+ single/spread modes, gap constant.
+- Content projection: single `Editable`; blocks visually placed per layout
+ (start with vertical spacers/translateY aligning block tops to page frames;
+ full glyph-projection is the aspirational endpoint).
+- Margin chrome: page numbers / headers / footers anchored to page box + margin insets.
+- Selection projection via mapping (later phase).
+
+### Public API (keep stable where sane)
+`PaginationPlugin` (auto-mounts overlay), options `{ page, margins, typography,
+policies, viewMode }`, transforms `setPageSize/setMargins/togglePreview/
+toggleHeader/toggleFooter`. Drop node-wrapping + reflowEngine + registry-move logic.
+
+## Phased build (TDD, stacked PRs)
+- **P1 ✅ DONE** — `layout/types.ts` (contract) + `layout/compose.ts` (pure
+ `composeLayout`): DOM-free, deterministic, breakReasons + widow/orphan +
+ keep-with-next + manual break + oversized split. 10 tests green.
+- **P2 ✅ DONE** — `layout/snapshot.ts` (`buildSnapshot`): Slate value → flat
+ block snapshot, stable content-based ids, atomic/keepWithNext/breakBefore
+ hints. 6 tests green. (typecheck + lint clean for both.)
+- **P3 (next): DOM measurement** — measure block heights/lineHeight from a
+ hidden container at content width → `MeasuredSnapshot`; cache by
+ `{id, contentHash, widthPx}`; dirty-set re-measure. Needs browser/jsdom seam.
+- **P4 ✅ DONE (overlay engine + first renderer; verified live in agent-browser)** —
+ `react/geometry.ts` (getPageGeometry/getBlockPlacements, pure + tested),
+ `react/domMeasure.ts` (pure-DOM MeasureFn via `[data-slate-node=element]`
+ children — no slate-react), `react/alignContent.ts` (page-start CSS spacers,
+ no model mutation), `react/index.ts` (clean `@platejs/pagination/react` entry
+ re-exporting the slate-react-free pipeline). Demo route renders white A4 page
+ chrome + single continuous Editable + spacer alignment + page numbers.
+ Verified: 4 pages, content flows across page boxes, clean boundaries. 149 tests.
+ NOTE: apps/www dev is unusable (pre-existing globals.css:8504 Turbopack-dev
+ PostCSS error 500s all routes) — verified via the playground template dev
+ (clean CSS) instead.
+- **P0 split-block rendering ✅ DONE (verified live)** — `layout/mapping.ts`
+ (MappingIndex), `layout/projection.ts` (fragmentRects/blockLinePosition),
+ `react/splitClones.ts` (`computeSplitPlan` pure + `renderSplitClones`). Approach
+ #1: one live Editable + read-only clipped clones per page-slice. Real-pixel
+ slicing (live block's measured top/height + page geometry) with **line-boundary
+ snapping** via `Range.getClientRects()` → seamless live→clone + clone→clone
+ junctions (no overlap, no gap, no half-line). Verified: a block 7× page height
+ splits cleanly across pages. Known follow-up: editing inside clone regions
+ (read-only) + blocks AFTER a split need measure-based spacers.
+- P5: selection/caret mapping (slatePath↔layoutPoint) + headers/footers.
+- P6: migrate template/demo, delete node-wrapping + reflowEngine, redeploy + browser verify.
+
+## Status
+Foundation (P1+P2) on branch `codex/pagination-premirror-ideas`, uncommitted.
+The deterministic core (snapshot → compose) is complete and tested headlessly.
+Remaining P3–P6 are larger; P4 overlay renderer is the research-grade frontier.
+
+## Test strategy (adopt premirror's)
+Fixture tiers smoke/core/stress with declared expected page count + break events;
+determinism gate (same input → identical LayoutOutput); semantic assertions paired
+with snapshots; pinned typography for measurement tests.
diff --git a/docs/plans/2026-05-22-pagination-rewrite-v2.md b/docs/plans/2026-05-22-pagination-rewrite-v2.md
new file mode 100644
index 0000000000..0427f95891
--- /dev/null
+++ b/docs/plans/2026-05-22-pagination-rewrite-v2.md
@@ -0,0 +1,141 @@
+# Pagination rewrite v2 — scorch the mutator, rebuild on pretext (rigid TDD)
+
+Supersedes `2026-05-21-pagination-rewrite.md`. That doc planned a block-level
+overlay; this one commits to **full pretext (line/run) measurement** and the
+**deletion of the document-mutator path**.
+
+`north-star reaffirmed: laws` (runtime boundary: document is the source of truth,
+pagination is a derived projection — no model mutation).
+
+## Locked decisions (user)
+
+1. **Measurement = full pretext everywhere.** `@chenglou/pretext` is the only
+ measurement source, live and print. Deterministic across machines + SSR,
+ true premirror parity, real run model. Satisfies the pretext gate
+ (`.agents/AGENTS.md` → "Pagination (pretext gate)") with no attestation owed.
+2. **Scorch first, host second** (path #1). PR1 is a pure deletion of the
+ mutator; the projection host is a later PR. Each PR small, green, reviewable.
+3. **Rigid TDD order** below — every phase is one stacked branch/PR, red→green,
+ one behavior at a time (no horizontal slicing).
+
+## Open decision — split-block render (pros/cons, pick before Phase 7)
+
+How to show a block taller than one page. Full pretext changes the calculus
+because we now have per-line `LineBox` with absolute coordinates and `pmRange`.
+
+| Option | Pros | Cons |
+|---|---|---|
+| **A. Overlay clipped clones** (single flowing `Editable` + read-only clones per later slice) | Native contenteditable selection/IME just works on the live block; verified in browser already; no model mutation (yjs-safe) | Clones read-only (no caret in continuation); needs `data-slate-*` strip + `aria-hidden`+`inert` (F-021); two coordinate systems to keep aligned |
+| **B. Line projection** (premirror-style: absolute-position every `LineBox` on its page; editable is a projection surface) | True premirror parity; no clones; split is free (lines just land on different pages); caret continuation works everywhere | Native selection/IME is lost — must build `useProjectedSelection`; biggest research effort; highest risk |
+| **C. Place whole, overflow** (oversized block bleeds past the page edge + dev warning) | Trivial; caret always correct; matches compose atomic-block handling | Tall tables/code visibly clipped; not Word-class |
+
+**DECIDED: C — place whole, overflow.** Oversized blocks are placed whole and
+allowed to bleed past the page edge (with a dev warning). Caret is always
+correct (no read-only clones), selection stays native, and it matches compose's
+atomic-block handling. Consequence: **no clone machinery** — `react/splitClones.ts`
++ its spec become dead code and are deleted when the "place whole" path lands in
+the compose rewrite (PR5). F-021 (clone hazards) is fully moot. B (line
+projection) remains the aspirational endpoint if Word-class split is wanted
+later, but is explicitly out of scope.
+
+## What full pretext reshapes (vs. the current block-level survivors)
+
+The current pure pipeline measures block `offsetHeight` and fakes
+`lineCount = round(h/lineHeight)`. Full pretext replaces that with a run/line model:
+
+- `snapshot.ts` must emit `StyledRun[]` per block: `{ id, text, font, marks,
+ slateRange:{path,offsetStart,offsetEnd}, atomic? }` (Slate analog of
+ premirror's `StyledRun` keyed by Slate path instead of PM pos).
+- `measure/` calls `prepareWithSegments(text, font, {whiteSpace:'pre-wrap'})`
+ then `layoutNextLine(prepared, cursor, widthPx)` to wrap → real `LineBox` +
+ `PlacedRun` with per-run `x`/`width` and `slateRange`.
+- `compose.ts` fills pages by **real lines**, widow/orphan on lines that exist,
+ atomic-run protection, `breakReason` per boundary. Kills F-003, F-024,
+ F-003-determinism, and the half-line/round bug in one move.
+- `mapping.ts` can finally implement `pmPosToLayout`/`layoutToPmPos` because
+ every line carries a `slateRange` (closes F-004).
+
+## Rigid TDD phase order (stacked PRs)
+
+Each phase: write the failing test first, confirm it fails for the right reason,
+minimal green, refactor. `bun test` per phase; `dev-browser` gate on any phase
+that changes a browser surface. `pnpm brl` whenever exports/files move.
+
+- **PR1 — Scorch the mutator (pure deletion).**
+ Delete: `BasePaginationPlugin.ts`, `internal/reflowEngine.ts`,
+ `PaginationCoordinator.tsx`, `PageElement.tsx`, `registry.tsx`,
+ `internal/runtime.ts`, `leaderElection.ts`, `yjs/YjsIntegration.tsx`,
+ `types.ts` (root), `internal/editorRegistry.ts`,
+ `internal/PaginationAboveEditable.tsx`, `PaginationPlugin.ts`,
+ `internal/scheduleIdle.ts` + their 12 specs. Drop `./yjs` export from
+ `package.json`. `.` → re-exports `./react`. Run `pnpm brl`.
+ Also delete `react/splitClones.ts` + spec (dead under option C) and the
+ clone-only `projection.ts` consumers' barrel wiring; trim now-dead deps
+ (`@lifeomic/attempt`, `@platejs/yjs`, `y-protocols`, `yjs`, `slate-history`);
+ neutralize the template (delete `pagination-kit.tsx` + `pagination-toolbar-button.tsx`,
+ strip from `editor-kit.tsx` / `fixed-toolbar-buttons.tsx` / scratch demo — waiver granted).
+ *DONE & green: 32/32 specs pass, typecheck clean, lint clean.*
+ Closes the runtime side of F-001, F-005, F-007, F-008, F-009, F-010, F-012,
+ F-013, F-014, F-015, F-016, F-018, F-021, F-022, F-026, F-028, F-030 (deleted).
+
+- **PR2 — Cache-key correctness on the survivor (F-006).**
+ RED: two widths for one block must not thrash one slot. GREEN: key the map by
+ `${id}@${widthPx}`. (F-024/F-011 deliberately deferred — obviated by pretext
+ in PR3–PR5.)
+
+- **PR3 — pretext measurement primitive.**
+ Add `@chenglou/pretext`. New `measure/pretext.ts`: `(text, font, widthPx) →
+ LineBox[]` via `prepareWithSegments`+`layoutNextLine`. Deterministic →
+ ideal unit tests (known string + font + width → known line count/widths).
+
+- **PR4 — run-level snapshot.**
+ RED: a Slate paragraph with mixed marks/inline-void → expected `StyledRun[]`
+ with correct `slateRange`s + `atomic` on voids. GREEN: rewrite `snapshot.ts`.
+
+- **PR5 — line/run compose.**
+ RED: fixtures with declared expected page count + break events; widow/orphan
+ on real lines; atomic run never mid-broken. GREEN: rewrite `compose.ts` to
+ consume measured runs → `LineBox`/`PlacedRun`/pages; oversized blocks placed
+ whole (option C). (`splitClones` already removed in PR1.) Closes F-003, F-024,
+ F-023 (partial: obstacles still later).
+
+- **PR6 — mapping in LayoutOutput (F-004, F-007, F-019).**
+ RED: `pmPosToLayout(point)` and inverse round-trip; `composeLayout` returns a
+ prebuilt `mapping` (no 3× rebuild). GREEN: build index once during compose.
+
+- **PR7 — projection host plugin (F-002, F-001 render side).**
+ The new `PaginationPlugin`: subscribes to editor changes, runs
+ snapshot→measure→compose, renders absolute page chrome + content. Oversized
+ blocks placed whole (option C) — no clones. `.` exports it. *`dev-browser` gate.*
+
+- **PR8 — selection/caret projection.**
+ `useProjectedSelection` equivalent via PR6 mapping (scroll-into-view across
+ pages, page-anchored decorations). Unblocks split-render option B if chosen.
+ *`dev-browser` gate.*
+
+- **PR9 — headers/footers/page-setup (F-026 avoided).**
+ Render-layer chrome from plugin options; `setPageSize`/`setMargins` as
+ `setOption` (O(1)), not per-page model inserts. Unify the preset enum (F-029).
+
+- **PR10 — migrate template/demo + cleanup.**
+ Replace the scratch `dev/pagination2` demo with the real plugin; delete vendored
+ scratch; redeploy; final `dev-browser` verification.
+
+## Deferred / explicitly out of v1
+- F-023 obstacles (floats/`shape-outside`) — needs the line model (now present
+ after PR5) but is additive; schedule after PR9.
+- F-025 stable-id requirement — decide during PR4 (pretext snapshot): likely
+ **require** node ids and document it, rather than content-hash.
+- F-011 width-correct offscreen measurement — moot once pretext measures at a
+ given `widthPx` directly (PR3).
+
+## Test strategy
+Fixture tiers smoke/core/stress with declared expected page count + break events;
+determinism gate (same input → identical LayoutOutput — now genuinely true with
+pretext, no DOM); semantic assertions paired with snapshots; pinned font metrics
+for measurement tests.
+
+## Status
+Caches/dist cleared. Mutator inventory mapped (13 files / 1389 LOC + 12 specs).
+Pretext confirmed: `@chenglou/pretext@^0.0.3` (`prepareWithSegments`,
+`layoutNextLine`). Awaiting split-render decision (A/B/C) before PR7.
diff --git a/docs/plans/2026-05-23-pagination-impl-plan.md b/docs/plans/2026-05-23-pagination-impl-plan.md
new file mode 100644
index 0000000000..1027ef6177
--- /dev/null
+++ b/docs/plans/2026-05-23-pagination-impl-plan.md
@@ -0,0 +1,206 @@
+# @platejs/pagination — Implementation Plan (Claude)
+
+Standalone plan. Does **not** edit `2026-05-22-pagination-unified-plan.md`
+(another agent is actively authoring that file). Grounded in the verified
+current source (read 2026-05-23) + a triage of `premirror-audit-findings.md`
+against the post-scorch tree (#408–#413).
+
+`north-star reaffirmed: laws` — derived overlay; the Slate document never
+mutates; pages are a render projection.
+
+---
+
+## 0. Audit triage — what the scorch already fixed vs what remains
+
+The audit `premirror-audit-findings.md` predates the #408–#413 rewrite. Re-scored
+against the current tree:
+
+| # | Finding | Status now | Evidence |
+|---|---------|-----------|----------|
+| P0 | Run/text fidelity lost | **Partly fixed** — snapshot extracts block `text`; pretext measures real wrapped lines. Still block-granularity (no per-leaf runs/marks). | `snapshot.ts:84` (`text: nodeText`), `pretext.ts:34` |
+| P1 | No `Point`↔`LayoutPoint` mapping | **OPEN** — mapping is block/line only. | `mapping.ts:18` |
+| P2 | Clone-based split rendering | **STALE** — `splitClones.ts` deleted in scorch. | (file gone) |
+| P3 | No dirty-range invalidation | **OPEN** — cache by `id@width`, but full pipeline each edit. | `measure.ts:55` |
+| P4 | Line-breaking estimated not measured | **Largely fixed** — `measureBlockHeight` counts real pretext lines; `linesToPlace` deleted. `measure.ts` still has a `height/lineHeight` cache fallback. | `pretext.ts:57`, `compose.ts:63` |
+| P5 | `MappingIndex` not in `LayoutOutput`, rebuilt per-call | **OPEN — cheapest win.** | `types.ts` (no `mapping`), `projection.ts:36,66` |
+| P6 | No font model | **Partly fixed** — `domMeasure` resolves font + content width. | `domMeasure.ts:32,46` |
+| P7 | No obstacle/slot (float, multi-column) | **OPEN (future).** | (none) |
+| P8 | Spacer self-referential w/ offsetHeight | **Improved** — measure is pretext line-count, not `offsetHeight`, so the circularity is broken. Spacer still depends on prior layout. | `domMeasure.ts:69`, `alignContent.ts:18` |
+| P9 | Determinism gap (clone DOM reads) | **STALE** — clones gone; compose is pure. | (file gone) |
+| P10 | No previous-layout seam | **OPEN** — no incremental compose. | `measure.ts:45` |
+
+**Conclusion:** the rendering hazards (P2/P9) are gone; the remaining debt is
+**contract/correctness + scaling** (P5, P1, P3, P10) plus the missing plugin
+host + render layers. This plan targets those in cost/value order.
+
+---
+
+## 1. Decision: compose granularity (place-whole vs line-split)
+
+Two viable directions; this is the one real fork:
+
+- **A. Keep place-whole (option C, current).** A block is atomic for layout; tall
+ blocks overflow. Mid-block page appearance is a *cosmetic* spacer. Lowest risk,
+ matches the user's earlier choice, ships fastest.
+- **B. Real line-split in `composeLayout` + widow/orphan.** Uses the already-present
+ `LayoutPolicies.widow/orphanLinesMin` (`types.ts:27`) and
+ `BlockFragment.{lineStart,lineCount,fragmentIndex}` (`types.ts:107`) — infra
+ that place-whole leaves unused. Higher fidelity (P4), but only *worth* it once
+ selection projection (P1) exists, else a split block's caret/selection breaks.
+
+**Plan choice:** ship **A first** (foundation + both render modes on place-whole),
+then **B as a fast-follow gated on P1** (real splitting is pointless without
+Point↔Layout mapping to project the caret across the split). This sequences risk
+correctly and never ships a split the selection can't follow.
+
+---
+
+## 2. Stacked PRs (TDD red→green, branch-over-branch on #413)
+
+### Foundation (shared by both render modes)
+
+- **PR1 — embed `MappingIndex` in `LayoutOutput`, build once (P5).**
+ `composeLayout` builds the mapping during composition and returns it on
+ `LayoutOutput.mapping`; `fragmentRects`/`blockLinePosition` consume
+ `layout.mapping` instead of rebuilding (`projection.ts:36,66`).
+ *Red:* `out.mapping.pageOfBlock(2) === 1`; spy asserts `buildMappingIndex`
+ **not** called inside `fragmentRects`. *Pure.*
+
+- **PR2 — `Point`↔`LayoutPoint` mapping (P1).** During compose, accumulate
+ `{path,offset}`→`{pageIndex,frameIndex,fragmentIndex,lineIndex,offsetInLine}`
+ refs (premirror `LineRef` analog). Add `pointToLayout(point)` +
+ `layoutToPoint(layoutPoint)` to the mapping. pretext line cursors
+ (`MeasuredLine.start/end`, `pretext.ts:23`) seed the offset math.
+ *Red:* caret at `{path:[3],offset:120}` resolves to the right page+line and
+ round-trips back. *Pure, canvas-stubbed.*
+
+- **PR3 — layout registry (footnote pattern).** `lib/registry.ts`:
+ `WeakMap`;
+ `installLayoutRegistry` wraps `editor.apply`, marks dirty on content ops
+ (`insert_text,remove_text,insert_node,remove_node,split_node,merge_node,move_node,set_node`),
+ **not** `set_selection`; lazy rebuild on read. Precedent
+ `footnote/registry.ts:11,95`, `slate-history/with-history.ts` op-inspection.
+ *Red:* starts dirty; dirty after `insert_text`; clean after read; selection-only
+ op stays clean. *Unit.*
+
+- **PR4 — `BasePaginationPlugin` + `PaginationPlugin` host.**
+ `createTSlatePlugin` base (options: `page/margins/policies/viewMode`) +
+ `toPlatePlugin`; `useHooks` runs snapshot→measure→compose on change,
+ rAF-batched (precedent `selection/useRequestReRender`), writes layout to the
+ registry. Supplies the pretext/DOM `MeasureFn` (`createDomMeasure`). No visible
+ render yet. *RTL:* edit → registry layout updates once per frame.
+
+- **PR5 — print-parity gate (nail FIRST conceptually; lands here once the host
+ exists).** Headless-Chrome: `serializeHtml` a fixture → render with CSS `@page`
+ → extract real page-break Ys → assert within **±1 line** of pretext `breakYs`.
+ Same `widthPx` (`@page` content rect) + same font
+ (`getComputedStyle(editable).font`). Locks the premise for both modes.
+ *dev-browser/headless.*
+
+- **PR6 — static print path (P-authority).** `static/` render of `layout.pages`:
+ `serializeHtml` + `@page` + `break-inside:avoid` on atomic blocks
+ (`core/src/static/serializeHtml.tsx`, `pluginRenderElementStatic.tsx`).
+ Authoritative PDF/print, shared by both modes.
+
+### Render modes (on the foundation; selected by `viewMode`)
+
+- **PR7 — `viewMode:'continuous'` (cheapest; prove the foundation).**
+ `render.belowRootNodes` (`PlatePlugin.ts:476`): thin dashed semi-faded
+ full-width rule + "Page N" tick at each break Y, `pointer-events:none`. No
+ boxes, no spacers, pure flow → native selection/IME/find untouched.
+ *dev-browser:* rules at correct Ys; editing fully native.
+
+- **PR8 — `viewMode:'paged'` chrome + spacers (Approach B).**
+ `render.afterEditable` → absolute A4 boxes + numbers behind the editable
+ (`getPageGeometry`); `aboveNodes` margin spacers (lift `alignContentToLayout`)
+ snap page-start blocks to the next page's content-top (`PlatePlugin.ts:447`).
+ One DOM tree → native selection intact. *dev-browser:* blocks align into boxes,
+ caret native.
+
+- **PR9 — packaging + `viewMode` API.** `.` = base/registry/queries/pure pipeline;
+ `/react` = plugin + overlays + hooks. Default `viewMode:'continuous'`.
+ `pnpm brl`; changeset (patch). *dev-browser:* register plugin alone in each mode.
+
+### Fidelity fast-follow (gated)
+
+- **PR10 — line-split compose + widow/orphan (P4, decision B).** Reinstate
+ line-granularity fragments in `composeLayout` using
+ `LayoutPolicies.widow/orphanLinesMin`; orphan guard (avail < orphanMin → push
+ whole), widow guard (pull lines back). **Gated on PR2** (selection must follow
+ the split). *Pure, canvas-stubbed:* 50-line block splits 46/4; atomic stays whole.
+- **PR11 — mid-block split spacer + cross-gap selection cosmetics.** Paged-mode
+ in-block gap at the split line; supplemental highlight rects via
+ `getRangeBoundingClientRect`/`getSelectionRects` (floating/cursor). *dev-browser.*
+- **PR12 — dirty-range incremental (P3/P10).** Inspect `editor.operations` for
+ changed block paths; measure-only-dirty; pass previous layout to compose to skip
+ unchanged pages. *Bench: O(changed) not O(pages) at 100+ pages.*
+
+---
+
+## 3. Four-axis assessment (paged=B vs continuous=two-mode)
+
+- **(a) Soundness/risk:** continuous = lowest (overlay rules, nothing reflows;
+ only risk is break-vs-print drift, killed by PR5). paged = highest fidelity;
+ risks are spacer/scroll exactness + (with PR10/11) mid-block caret. All risk is
+ in the render layer; the pipeline + registry are mode-agnostic.
+- **(b) Compute:** shared per-edit snapshot→measure(dirty)→compose ≈ O(blocks)
+ until PR12 makes it O(changed). continuous adds a few absolute rules (~free);
+ paged adds spacer reflow + chrome repaint. 100+ pages: profile spacer recalc
+ (paged); continuous scales flat.
+- **(c) Yjs:** both safe by construction — zero doc mutation; layout is per-client
+ `WeakMap` derived state, nothing crosses the wire. Caveat: paged spacers/split
+ gaps must be CSS/render-slot only, never `setNodes`.
+- **(d) UX:** continuous = maximal native fidelity (selection/IME/a11y/find), page
+ awareness via faded lines, not pixel-WYSIWYG. paged = Word-class WYSIWYG, mid-block
+ splits visible (after PR10/11), at per-edit recompute cost. Mode switch is cheap
+ (same registry; mount/unmount render layer). Ship continuous default, paged opt-in.
+
+---
+
+## 4. Sequencing summary
+
+PR1 (P5) → PR2 (P1) → PR3 (registry) → PR4 (host) → PR5 (parity gate) →
+PR6 (print) → PR7 (continuous) → PR8 (paged) → PR9 (packaging) →
+[gated] PR10 (line-split) → PR11 (split spacer+selection) → PR12 (incremental).
+
+Every PR: rigid TDD (pure layers canvas-stubbed; render/selection in dev-browser),
+stacked branch-over-branch, `check` before PR.
+
+---
+
+## 6. Salvaged external references
+
+From a DOCX→HTML paginator (Docxodus `PaginationEngine`). Its architecture is the
+**rejected** one (DOM-clone page boxes, `getBoundingClientRect` measurement, DOM
+mutation — our audit P2/P8/P9), so **no code is adopted**. Only these algorithms
+/specs are kept, each tagged with the PR it informs:
+
+1. **Margin-collapsing in the flow → near-term correctness fix (informs F2 / line-split).**
+ Adjacent block margins collapse: the gap between two stacked blocks is
+ `max(prevMarginBottom, currMarginTop)`, not their sum. Their flow uses
+ `effectiveMarginTop = max(currTop, prevBottom) − prevBottom`. Our `composeLayout`
+ stacks by raw `heightPx` and `alignContent` ignores collapse, so multi-block
+ break-Y drifts by the collapsed margin. Adopt the formula when compose starts
+ accumulating real inter-block spacing (break-Y emission). pretext gives line
+ boxes, not block margins — block margins still come from computed style.
+
+2. **Header/footer by page-position + effective content height → print path (PR6) + future H/F.**
+ Per-section header/footer with `default | first | even` variants selected by
+ `(pageInSection === 1 ? first : globalPageNumber % 2 === 0 ? even : default)`;
+ a header/footer taller than its margin *expands* and reduces content height:
+ `contentHeight −= (headerHeight − marginTop) + (footerHeight − marginBottom)`.
+ Exactly what `@page` margins + a headers/footers feature need.
+
+3. **Footnote-area reservation + split/continuation → footnotes-meet-pagination phase.**
+ Reserve bottom-of-page footnote space (cap `MAX_FOOTNOTE_AREA_RATIO ≈ 0.6` of
+ content height; keep `MIN_BODY_CONTENT_HEIGHT`); when a footnote overflows, split
+ it at child-element boundaries and carry a continuation to the next page. Pairs
+ with `@platejs/footnote`.
+
+4. **Token/offset position model → PR2 (`Point↔LayoutPoint`, audit P1).** PAWLS
+ `PawlsToken {x,y,width,height,text}` + `TextSpan {start,end}` validate the target
+ shape: char-offset spans ↔ layout coordinates. Our analog feeds from pretext line
+ cursors (`MeasuredLine.start/end`) rather than a token layout engine.
+
+Page dimensions confirm our presets (US Letter 612×792pt, A4 595×842pt; default
+margins 72pt, header/footer distance 36pt) — informational only.
diff --git a/docs/plans/2026-05-24-pagination-demo-print-regression.md b/docs/plans/2026-05-24-pagination-demo-print-regression.md
new file mode 100644
index 0000000000..401b84c429
--- /dev/null
+++ b/docs/plans/2026-05-24-pagination-demo-print-regression.md
@@ -0,0 +1,68 @@
+# Pagination Demo Print Regression
+
+## Goal
+
+Find why the demo printed a page successfully on branch
+`codex/pagination-e2e-tests` but not on `codex/playground-pagination-toggle`,
+then fix this branch.
+
+## Constraints
+
+- Pagination work must use pretext for faithful pagination.
+- Do not edit `templates/**` manually.
+- If code changes, verify in the same turn.
+- If browser surface changes, verify with `dev-browser` before handoff.
+
+## Plan
+
+- [x] Load repo skills and rules.
+- [x] Check existing pagination plans and documented learnings.
+- [ ] Reproduce the current failure.
+- [ ] Compare current branch with `codex/pagination-e2e-tests`.
+- [ ] Identify root cause before patching.
+- [ ] Implement focused fix.
+- [ ] Run tests, coverage, lint/type checks, and browser proof as applicable.
+- [ ] Evaluate whether to capture a `docs/solutions/` learning.
+
+## Findings
+
+- Current branch: `codex/playground-pagination-toggle`.
+- Known-good comparison branch exists locally: `codex/pagination-e2e-tests`.
+- `docs/solutions/patterns/critical-patterns.md` is referenced by the skill but
+ is absent in this repo.
+- Existing pagination plans emphasize derived layout, pretext-based measurement,
+ and browser verification for render surfaces.
+- The repo app route `/dev/pagination2` renders pagination labels on this branch:
+ `Page 1 of 4` through `Page 4 of 4`.
+- The template `/editor` route renders zero page markers on load because
+ `PaginationKit` configures `PaginationPlugin` with `enabled: false`.
+- Root cause: the comparison branch tests a direct demo plugin install; this
+ branch's playground integration starts the plugin disabled.
+- Second root cause: full playground editors wrap each top-level Slate element
+ in block UI chrome (`div.relative.group`), while `topLevelBlockElements`
+ only measured direct `[data-slate-node="element"]` children. That made every
+ block fall back to one line, so the full editor composed as a single page.
+
+## Progress
+
+- 2026-05-24: Started branch comparison and regression investigation.
+- 2026-05-24: Reproduced template `/editor` missing pagination markers; direct
+ `/dev/pagination2` demo remains green.
+- 2026-05-24: Fixed measurement lookup for wrapped block DOM, enabled pagination
+ by default in the playground integration, added registry wiring and E2E tests.
+- 2026-05-24: Verified template `/editor` renders `Page 1 of 2` / `Page 2 of 2`.
+
+## Verification
+
+- `pnpm install`
+- `bun test --coverage packages/pagination/src` — 56 pass; package lines are
+ 100% except `react/domMeasure.ts` at 81.48%; Bun did not emit branch coverage.
+- `pnpm turbo build --filter=./packages/pagination`
+- `pnpm turbo typecheck --filter=./packages/pagination --filter=./apps/www`
+- `pnpm lint:fix`
+- `bun typecheck` in `templates/plate-playground-template`
+- `PLAYWRIGHT_BASE_URL=http://localhost:3002 pnpm exec playwright test tooling/e2e/pagination.spec.ts --browser=chromium --workers=1`
+- Browser probe for `http://localhost:3001/editor` — 1 break line, 1 page marker,
+ labels `Page 1 of 2`, `Page 2 of 2`, no console errors.
+- `dev-browser --connect http://127.0.0.1:9222 --help` blocked: CLI not installed
+ in this environment.
diff --git a/docs/plans/premirror-audit-findings.md b/docs/plans/premirror-audit-findings.md
new file mode 100644
index 0000000000..4b4782eef7
--- /dev/null
+++ b/docs/plans/premirror-audit-findings.md
@@ -0,0 +1,278 @@
+# Premirror → Plate Pagination Translation Audit
+
+## Summary
+
+Premirror operates at **text-run granularity** (per-styled-segment text extraction, per-character width measurement, word-boundary line breaking). Our adaptation operates at **top-level block granularity** (block heights, estimated line counts). This is not a refinement — it's a fundamental modelshift that loses 6 layers of fidelity premirror's contracts require. Additionally, we lack premirror's bidirectional position mapping, dirty-range incremental invalidation, and decoration-projection rendering.
+
+---
+
+## Findings (ranked by severity)
+
+### 🔴 P0 — Run-level fidelity entirely lost (model collapse)
+
+**What premirror does:**
+- Snapshot extracts text at the `StyledRun` level — each mark-span (bold, italic, code, link) becomes its own run with `text`, `font`, `marks`, and `pmRange` (`premirror/core/src/index.ts:57-64`).
+- Each run is measured individually via Pretext, producing `widthPx` (`premirror/prosemirror-adapter/src/index.ts:355-390`).
+- The composer uses per-run widths to do real line-filling — accumulating runs until content width overflows, respecting word boundaries (`premirror/composer/src/index.ts:349-506`).
+- `LayoutOutput` preserves per-run coordinates (`PlacedRun.x`, `PlacedRun.width`) and per-line `pmRange` (`premirror/core/src/index.ts:97-112`).
+
+**What we did:**
+- `UnmeasuredBlock` has NO text content, NO runs, NO per-character measurement (`pagination/src/layout/types.ts:80-87`).
+- `MeasuredBlock` stores only `heightPx`, `lineHeightPx`, `lineCount` (`pagination/src/layout/types.ts:48-69`).
+- `snapshot.ts` builds blocks from Slate node types/paths/hints only — zero text extraction (`pagination/src/layout/snapshot.ts:59-76`).
+- `measure.ts` reads DOM `offsetHeight` → divides by line-height → calls it line count (`pagination/src/measure/measure.ts:39-43`).
+- Our `BlockFragment` has NO per-line coordinate information — just `y`, `heightPx`, `lineStart`, `lineCount` (`pagination/src/layout/types.ts:105-120`).
+
+**Why it's broken:**
+- We lost the ability to do any text-level layout: selection placement, caret positioning, hit-testing, or per-character rendering within blocks.
+- The `lineCount = Math.round(heightPx / lineHeightPx)` approximation (`pagination/src/measure/measure.ts:39-43`) is only correct for monospaced content with uniform line-height. For mixed fonts, inline elements, or any non-rectangular block, it produces wrong line counts and wrong page breaks.
+- premirror's `PlacedRun` precision enables precise cursor/selection projection (`premirror/react/src/index.tsx:265-321`). Ours can only approximate.
+
+**Correct Slate approach:**
+Slate's equivalent of "runs" are text leaves with marks. A proper adaptation would:
+1. Extract leaf-level text + marks from Slate nodes (Slate's equivalent of ProseMirror marks).
+2. Measure leaf widths via Canvas `measureText` or a hidden measurement DOM (Slate doesn't have Pretext, but Canvas API or layout-next-line equivalents exist).
+3. Store measured leaf widths analogously to `MeasuredRun`.
+4. Use those widths in line-filling (same algorithm as premirror's `breakBlockIntoLineDrafts`).
+
+---
+
+### 🔴 P1 — No bidirectional pmPos ↔ layout mapping (controller blindness)
+
+**What premirror does:**
+- `MappingIndex` has two precise functions (`premirror/core/src/index.ts:141-144`):
+ - `pmPosToLayout(pmPos) → LayoutPoint | null` — maps any document position to `{pageIndex, frameIndex, fragmentIndex, lineIndex, offsetInLine}`.
+ - `layoutToPmPos(point) → number | null` — reverse map.
+- The mapping is built during composition from `LineRef` records sorted by `pmFrom` (`premirror/composer/src/index.ts:556-598`).
+- The `LayoutOutput` includes the mapping (`premirror/core/src/index.ts:162-166`).
+- The React layer uses mapping implicitly through `collectRectsForPmRange` to project selection rectangles into page coordinates (`premirror/react/src/index.tsx:265-321`).
+
+**What we did:**
+- Our `MappingIndex` only maps `blockIndex → FragmentRef[]` (`pagination/src/layout/mapping.ts:18-32`):
+ - `fragmentsOfBlock(blockIndex)`
+ - `pageOfBlock(blockIndex)`
+ - `fragmentOfBlockLine(blockIndex, lineIndex)` — closest thing to position mapping, but works at line granularity within a single block.
+- NO `pmPosToLayout` counterpart. NO `layoutToPmPos`. NO document-level position resolution.
+- `LayoutOutput` does NOT include the mapping (`pagination/src/layout/types.ts:138-141`).
+
+**Why it's broken:**
+- Cannot answer "where is cursor position 42?" (the fundamental question for editing).
+- Cannot reverse-resolve a click on a page to a document position.
+- `useProjectedSelection` in premirror (`premirror/react/src/index.tsx:328-343`) uses layout iteration with precise per-line `pmRange` matching. Our equivalent doesn't exist — there's no way to project a Slate selection range onto page coordinates at all.
+- The pagination engine is blind to document positions; it only knows about blocks.
+
+**Correct Slate approach:**
+- During composition, accumulate `{path, offset}` → `{pageIndex, frameIndex, fragmentIndex, lineIndex, offsetInLine}` refs, analogous to premirror's `LineRef` (`premirror/composer/src/index.ts:547-554`).
+- Build a forward map from Slate `Point` to `LayoutPoint`.
+- Build a reverse map from `LayoutPoint` to Slate `Point`.
+- Include mapping in `LayoutOutput`.
+
+---
+
+### 🔴 P2 — Clone-based split rendering is a fragile hack vs premirror's decoration projection
+
+**What premirror does:**
+- Design principle: "Content fragments are expected to be positioned by ProseMirror decorations, not a duplicated text layer" (`premirror/react/src/index.tsx:151-154`).
+- `PremirrorPageViewport` stacks page surfaces (static `` with white background + border) and overlays a single `contenteditable` editor with `pointer-events: none` wrapper → `pointer-events: auto` on the editor surface (`premirror/react/src/index.tsx:239-261`).
+- The editor content is NOT duplicated. The layout is purely visual — page surface divs with transparent editor overlay. The single editor scrolls continuously with page gaps, and decorations reposition content onto pages.
+
+**What we did:**
+- `computeSplitPlan` detects blocks that span pages and plans clipped clones (`pagination/src/react/splitClones.ts:47-86`).
+- `renderSplitClones` reads live DOM with `getBoundingClientRect()`, `scrollHeight`, and `Range.getClientRects()` (`pagination/src/react/splitClones.ts:130-208`).
+- For each split block, the live block is clipped with `maxHeight` + `overflow: hidden` to show only the first page's portion, and subsequent pages get read-only DOM clones with `overflow: hidden` + `translateY` to reveal the right slice (`pagination/src/react/splitClones.ts:88-120`).
+- Clones strip `contenteditable` attributes (`pagination/src/react/splitClones.ts:107-110`).
+- `collectLineBottoms` uses `Range.getClientRects()` to find visual line boundaries (`pagination/src/react/splitClones.ts:211-232`).
+- `alignContentToLayout` applies `margin-top` spacers to push page-starting blocks down (`pagination/src/react/alignContent.ts:56-69`).
+
+**Why it's broken:**
+1. **Layout thrashing**: `renderSplitClones` reads `getBoundingClientRect()` (forces layout), then writes `maxHeight`, then reads `scrollHeight`. This is a classic forced-synchronous-layout pattern that tanks performance.
+2. **Stale measurements**: The live block may already be clipped from a previous render cycle, making `scrollHeight` potentially inaccurate if the browser hasn't reflowed yet.
+3. **Read-only clones can't be edited**: If the user clicks a cloned portion, it's inert. This creates a UX dead zone on every page after page 1 for split blocks.
+4. **Visual seams**: The clone uses `overflow: hidden` with `translateY` — pixel rounding, font metrics, and subpixel shifts can create visible 1px gaps or overlaps at fragment boundaries.
+5. **DOM duplication cost**: Each split block's clone clones the entire block DOM subtree (potentially large for complex blocks).
+6. **Fragile line detection**: `collectLineBottoms` deduplicates rect bottoms within 1px tolerance (`pagination/src/react/splitClones.ts:222-224`), which breaks at high DPIs or with mixed font sizes.
+
+**Correct Slate approach:**
+- Follow premirror's model: a single continuous editable flowing through stacked page surface divs.
+- Use CSS to visually separate pages, not DOM cloning.
+- The editor's content naturally fills pages as the user scrolls. No content duplication.
+- For split blocks, the editor's built-in overflow scrolling handles fragment visibility.
+
+---
+
+### 🟡 P3 — No dirty-range incremental invalidation
+
+**What premirror does:**
+- `PremirrorInvalidationState` tracks `{from, to}` dirty document ranges (`premirror/prosemirror-adapter/src/index.ts:49-52`).
+- A `Plugin` automatically derives invalidation from transactions via `tr.docChanged` → full range, or explicit `PREMIRROR_META_KEY` metadata (`premirror/prosemirror-adapter/src/index.ts:53-81`).
+- `getInvalidationRange(state)` exposes it to consumers (`premirror/prosemirror-adapter/src/index.ts:437`).
+- The react hook `usePremirrorEngine` receives `previousLayoutOverride` — this is the seam for passing a prior layout so the composer can skip recomposing unchanged content (`premirror/react/src/index.tsx:34-35`).
+
+**What we did:**
+- `measureSnapshot` caches by `block.id + width` key (`pagination/src/measure/measure.ts:53-58`).
+- But there is NO invalidation by document position. Every edit triggers a full `buildSnapshot` + `measureSnapshot` + `composeLayout` pipeline.
+- No Plugin or equivalent for tracking changed positions in the Slate document.
+- The cache is keyed by `block.id` which is content-hash based, so ANY text change in a block busts the entire block cache.
+
+**Why it's broken:**
+- premirror's dirty-range allows: (a) skip re-measuring blocks outside the dirty range, (b) skip re-composing pages before the dirty range. Ours re-runs the entire pipeline on every keystroke.
+- For large documents (100+ pages), this is O(n) per edit vs premirror's O(dirty_range).
+- The `id` hash-based cache key changes whenever the block's text content changes, making the cache a write-through pattern with no incremental benefit.
+
+**Correct Slate approach:**
+- Slate operations carry path information — track which top-level block paths were modified in the current operation batch.
+- Only re-measure and re-compose blocks/pages within or after the changed paths.
+- Annotate operations with invalidation metadata (similar to `PREMIRROR_META_KEY`).
+
+---
+
+### 🟡 P4 — Line-breaking is estimated, not measured
+
+**What premirror does:**
+- `breakBlockIntoLineDrafts` performs real line-filling: iterates runs, accumulates width, breaks when content width exceeded, prefers word boundaries (`premirror/composer/src/index.ts:349-506`).
+- `fixWordBoundarySplits` handles word-boundary corrections across line boundaries (`premirror/composer/src/index.ts:300-347`).
+- `splitPlacedRunAtWordBoundary` splits a run at a word boundary for reflow (`premirror/composer/src/index.ts:254-298`).
+- Each run has individually measured width via Pretext (`premirror/composer/src/index.ts:85-112`).
+- `pushPlacedSegment` creates `PlacedRun` with precise x-coordinate from accumulated width (`premirror/composer/src/index.ts:202-223`).
+
+**What we did:**
+- `linesToPlace` divides remaining space height by line height (`pagination/src/layout/compose.ts:31-47`).
+- No word-boundary awareness. No per-character width accumulation. No line-filling.
+- `placeBlock` splits by uniform `lineHeightPx` chunks (`pagination/src/layout/compose.ts:87-163`).
+- The assumption is that every line of a block has the same height and the same internal structure.
+
+**Why it's broken:**
+- False assumption: all lines within a block have uniform height. Reality: inline images, superscript, mixed font sizes, and inline code blocks produce varying line heights.
+- No orphan/widow protection at the correct granularity — premirror counts actual lines; we count estimated lines.
+- A block with 3 long text lines might actually wrap to 6 visual lines. Our `lineCount = Math.round(heightPx/lineHeightPx)` will estimate 3 (wrong).
+
+**Correct Slate approach:**
+- Use Canvas `measureText` or an off-screen DOM to measure individual leaf widths.
+- Implement line-filling with the same word-boundary preference algorithm as premirror.
+- Store per-line coordinates (not just per-fragment).
+
+---
+
+### 🟡 P5 — Missing MappingIndex in LayoutOutput (contract violation)
+
+**What premirror does:**
+- `LayoutOutput` includes `mapping: MappingIndex` as a required field. Every consumer of `LayoutOutput` gets bidirectional position resolution (`premirror/core/src/index.ts:162-166`).
+
+**What we did:**
+- `LayoutOutput` has NO mapping field (`pagination/src/layout/types.ts:138-141`).
+- `buildMappingIndex` is a separate function that consumers must call independently (`pagination/src/layout/mapping.ts:34`).
+- `projection.ts` calls `buildMappingIndex` internally and discards it — each call rebuilds the index (`pagination/src/layout/projection.ts:31-36`).
+
+**Why it's broken:**
+- Repeated index rebuilds (`fragmentRects` and `blockLinePosition` both call `buildMappingIndex(layout)`) — O(fragments) work wasted on every call.
+- Consumers can't inspect the mapping without rebuilding it.
+- Diverges from premirror's contract: layout output should be self-contained.
+
+**Correct Slate approach:**
+- Include `mapping: MappingIndex` in `LayoutOutput`.
+- Build it once during `composeLayout` and freeze it.
+
+---
+
+### 🟠 P6 — No TypographyConfig / font model
+
+**What premirror does:**
+- `TypographyConfig` specifies `defaultFont`, `defaultLineHeightPx`, `tabSize` (`premirror/core/src/index.ts:26-31`).
+- Fonts are resolved per-run: bold → weight 700, italic → style italic, code → monospace family (`premirror/prosemirror-adapter/src/index.ts:105-114`).
+- `parseFontSizePx` extracts font size from the font string (`premirror/prosemirror-adapter/src/index.ts:99-103`).
+
+**What we did:**
+- No `TypographyConfig`. No font resolution. No per-leaf font measurement.
+- `domMeasure.ts` reads `computedStyle.lineHeight` from DOM (`pagination/src/react/domMeasure.ts:19-27`).
+- No concept of what font is being used or how marks affect it.
+
+**Why it's broken:**
+- The line height used for line-count estimation comes from a single DOM element's computed style, which may not represent every leaf in a mixed-style block.
+- premirror's font resolution enables correct measurement even before DOM exists (offline/pre-render layout). Ours can only measure after DOM render.
+
+---
+
+### 🟠 P7 — No obstacle / slot selection support
+
+**What premirror does:**
+- `BandObstacle` represents items that carve into the content frame (images, floats, columns) (`premirror/core/src/index.ts:183-188`).
+- `usableSlotForBand` computes the leftmost horizontal slot in a content band, accounting for obstacles (`premirror/composer/src/index.ts:159-181`).
+- `mergeIntervals` merges overlapping obstacle intervals for slot computation (`premirror/composer/src/index.ts:138-153`).
+- `slotSelectionPolicy` in policies (`single_slot_flow` / `multi_slot_fill`) (`premirror/core/src/index.ts:32-38`).
+
+**What we did:**
+- No `BandObstacle`. No slot computation. No `slotSelectionPolicy`.
+- Content is always full-width within the content frame.
+
+**Why it's missing:**
+- Future features (float images, multi-column layout, side-notes) depend on this.
+- The contract simply doesn't exist in our adaptation.
+
+---
+
+### 🟠 P8 — Spacer alignment is self-referential and fragile
+
+**What premirror does:**
+- No spacers. Content positioning is driven by layout coordinates projected through decorations.
+- `PremirrorPageViewport` stacks page divs with absolute positioning, editor overlay on top.
+
+**What we did:**
+- `alignContentToLayout` applies CSS `margin-top` to push page-starting blocks to their page's content-frame top (`pagination/src/react/alignContent.ts:56-69`).
+- `computePageStartSpacers` computes the required gap = contentHeight - prevPageBottom + bottomMargin + pageGap + topMargin (`pagination/src/react/alignContent.ts:18-50`).
+
+**Why it's fragile:**
+- The spacer depends on the *measured heights* from the previous measurement cycle. If those heights change (CSS cascade, font loading, reflow), the spacer becomes stale.
+- `domMeasure` reads `offsetHeight` which includes margins, but the comment says "marginTop is applied as spacer, so reading margins here would double-count" (`pagination/src/react/domMeasure.ts:39-41`). This means the spacer height computation and the measurement are circular: the spacer changes the height, which changes the measurement, which changes the spacer.
+- The `previous.fragments[previous.fragments.length - 1]` approach picks the last fragment's computed y, but y is from the *layout* model, not the *actual* rendered position (`pagination/src/react/alignContent.ts:34-37`).
+
+---
+
+### 🟢 P9 — Determinism gap in clone rendering
+
+**What premirror does:**
+- `composeLayout` is pure — same inputs → same outputs (verified by test: `premirror/composer/src/index.test.ts:56-62`).
+- `usePremirrorEngine` wraps it in `useMemo` for React determinism (`premirror/react/src/index.tsx:55-74`).
+
+**What we did:**
+- `composeLayout` is pure (`pagination/src/layout/compose.ts`).
+- BUT `renderSplitClones` reads live DOM values (`getBoundingClientRect`, `scrollHeight`, `Range.getClientRects`) — these vary with browser state, fonts, zoom, OS font rendering, etc. (`pagination/src/react/splitClones.ts:158-160`, `pagination/src/react/splitClones.ts:216-226`).
+- `alignContentToLayout` writes `marginTop` to DOM elements — a mutation that can trigger reflow and affect subsequent measurements.
+
+**Why it matters:**
+- The layout engine is deterministic but the rendering layer injects non-deterministic DOM reads. Two identical editor states can produce visually different page splits due to timing-dependent DOM measurements.
+- This violates premirror's "deterministic pagination" design constraint (`premirror/docs/design-proposal.md:19`).
+
+---
+
+### 🟢 P10 — No measurement-caching seam across re-renders
+
+**What premirror does:**
+- `MeasuredDocumentSnapshot.measuredRuns` is a flat record keyed by `runId` (`premirror/core/src/index.ts:85-87`).
+- The react hook passes `previousRef` to the composer via optional `previousLayoutOverride` (`premirror/react/src/index.tsx:62-63`).
+- This seam allows the composer to reuse previously measured and computed data for unchanged parts of the document.
+
+**What we did:**
+- `measureSnapshot` accepts a `cache?: MeasureCache` (`Map
`) (`pagination/src/measure/measure.ts:28-34`).
+- But there's no mechanism to pass a previous layout to the composer for incremental re-composition.
+- `previousLayoutOverride` equivalent does not exist in our code.
+
+**Why it matters:**
+- premirror's design allows skipping entire pages of re-composition when only a later block changed. Ours re-composes the entire document from scratch.
+
+---
+
+## Summary Table
+
+| Severity | Finding | premirror file:line | Our file:line | Gap |
+|----------|---------|-------------------|---------------|-----|
+| P0 | Run/text fidelity lost | `core/src/index.ts:57-64` | `layout/types.ts:80-87` | No text extraction, no per-run measurement |
+| P1 | No bidirectional pmPos↔layout mapping | `core/src/index.ts:141-144`, `composer/src/index.ts:556-598` | `layout/mapping.ts:18-32` | Only block↔fragment; no doc-position precision |
+| P2 | Clone rendering vs decoration projection | `react/src/index.tsx:151-154` | `react/splitClones.ts:130-208` | DOM duplication + overflow clipping instead of decorations |
+| P3 | No dirty-range invalidation | `prosemirror-adapter/src/index.ts:49-81` | (none) | Full recomposition every edit |
+| P4 | Line-breaking estimated not measured | `composer/src/index.ts:349-506` | `layout/compose.ts:87-163` | height÷lineHeight instead of real line-filling |
+| P5 | MappingIndex missing from LayoutOutput | `core/src/index.ts:162-166` | `layout/types.ts:138-141` | Contract divergence |
+| P6 | No TypographyConfig / font model | `core/src/index.ts:26-31`, `prosemirror-adapter/src/index.ts:99-114` | (none) | No font resolution per leaf |
+| P7 | No obstacle / slot selection | `core/src/index.ts:183-188`, `composer/src/index.ts:159-181` | (none) | Missing contract for multi-column, floats |
+| P8 | Spacer alignment self-referential | (doesn't use spacers) | `react/alignContent.ts:56-69`, `react/domMeasure.ts:39-41` | Circular dependency between spacer and measurement |
+| P9 | Determinism gap in clone rendering | `composer/src/index.test.ts:56-62` | `react/splitClones.ts:158-160` | Live DOM reads introduce non-determinism |
+| P10 | No previous-layout seam | `react/src/index.tsx:34-35`, `react/src/index.tsx:62-63` | `measure/measure.ts:28-34` | Full recompose every time, no incremental path |
diff --git a/docs/solutions/ui-bugs/2026-05-24-pagination-must-measure-wrapped-block-elements.md b/docs/solutions/ui-bugs/2026-05-24-pagination-must-measure-wrapped-block-elements.md
new file mode 100644
index 0000000000..855ab45517
--- /dev/null
+++ b/docs/solutions/ui-bugs/2026-05-24-pagination-must-measure-wrapped-block-elements.md
@@ -0,0 +1,87 @@
+---
+module: Pagination
+date: 2026-05-24
+problem_type: ui_bug
+component: tooling
+symptoms:
+ - "The minimal pagination demo rendered page markers, but the full playground editor rendered none"
+ - "Template /editor showed zero pagination break lines and labels on load"
+ - "The full editor content was taller than one page, but pagination composed it as one page"
+root_cause: logic_error
+resolution_type: code_fix
+severity: medium
+tags:
+ - pagination
+ - pretext
+ - dom-measurement
+ - block-wrappers
+ - registry
+---
+
+# Pagination must measure wrapped block elements
+
+## Problem
+
+Pagination worked in the focused `/dev/pagination2` demo but did not render page markers in the full playground editor.
+
+The two surfaces used the same pagination pipeline. The difference was the editor DOM shape.
+
+## Root Cause
+
+`topLevelBlockElements` queried only direct editable children:
+
+```ts
+editable.querySelectorAll(':scope > [data-slate-node="element"]');
+```
+
+That matched the focused demo, where Slate elements were direct children of the editable.
+
+The full playground block UI wraps each top-level Slate element in an outer UI wrapper. In that DOM shape, the direct-child query returns no blocks. Pagination then falls back to synthetic one-line measurements and can compose the whole document as a single page.
+
+There was also an integration issue: the playground template configured `PaginationPlugin` with `enabled: false`, so even a correct layout could stay invisible on first load.
+
+## Fix
+
+Resolve one top-level Slate element per editable child:
+
+- use the child itself when it is a Slate element
+- otherwise use the first nested Slate element inside that child
+- preserve document order and avoid querying all descendants as independent blocks
+
+Enable pagination in the editor kit integration by default and register the pagination toolbar control with the registry source.
+
+## Why This Works
+
+Pagination needs the same top-level block sequence that Slate renders, not a flat list of every descendant element.
+
+Looking through each editable child preserves that top-level sequence while tolerating wrapper components added by block UI chrome.
+
+## Verification
+
+These checks passed:
+
+```bash
+bun test --coverage packages/pagination/src
+pnpm turbo build --filter=./packages/pagination
+pnpm turbo typecheck --filter=./packages/pagination
+pnpm lint:fix
+PLAYWRIGHT_BASE_URL=http://localhost:3002 pnpm exec playwright test tooling/e2e/pagination.spec.ts --browser=chromium --workers=1
+```
+
+Template `/editor` browser proof after refreshing the local pagination vendor package:
+
+```json
+{
+ "lineCount": 1,
+ "labels": ["Page 1 of 2", "Page 2 of 2"],
+ "errors": []
+}
+```
+
+## Prevention
+
+Do not use direct-child-only selectors for Plate editor block measurement when block UI wrappers can sit between the editable and Slate elements.
+
+Pagination browser checks should cover both the minimal demo route and a full editor route with block wrappers.
+
+When verifying a generated template against local package code, rebuild the package, refresh the template's vendored package copy, and reinstall the template dependency graph before testing the page.
diff --git a/package.json b/package.json
index 88b8cc24f0..7da922e890 100644
--- a/package.json
+++ b/package.json
@@ -52,6 +52,7 @@
"reinstall": "bash tooling/scripts/reinstall.sh",
"release": "pnpm build && pnpm changeset publish",
"shadcn:build": "pnpm --filter www shadcn:build",
+ "deploy:playground": "pnpm turbo build --filter=./packages/pagination && cd templates/plate-playground-template && bun run vendor:pagination && bun install && npx opennextjs-cloudflare build && npx opennextjs-cloudflare deploy",
"templates:ai": "./tooling/scripts/update-template.sh ai",
"templates:basic": "./tooling/scripts/update-template.sh basic",
"templates:check": "cd templates/plate-template && pnpm lint && pnpm typecheck && cd ../plate-playground-template && pnpm lint && pnpm typecheck",
diff --git a/packages/pagination/failures.md b/packages/pagination/failures.md
new file mode 100644
index 0000000000..275870defe
--- /dev/null
+++ b/packages/pagination/failures.md
@@ -0,0 +1,2098 @@
+# Pagination Translation — Failure Assessment
+
+This document tracks the gaps between **premirror** (the source: a deterministic
+ProseMirror-targeted snapshot→measure→compose pipeline) and the **`@platejs/pagination`**
+translation. Each entry is *appended* as the audit proceeds: it never rewrites prior
+findings. Severity is informal — read the "Pros / Cons" of each proposed fix before
+acting.
+
+Legend:
+
+- **Location** — file paths and ranges that exhibit the problem.
+- **What premirror does** — the source-of-truth behavior.
+- **What plate does** — what the translation actually ships.
+- **Why it's a failure** — the user-visible / correctness consequence.
+- **Fix options** — concrete remediation paths with pros and cons.
+
+---
+
+## F-001 — Two parallel architectures coexist (pure pipeline vs. document mutator)
+
+### Location
+
+- Pure pipeline (premirror-faithful):
+ - `src/layout/snapshot.ts`
+ - `src/layout/compose.ts`
+ - `src/layout/projection.ts`
+ - `src/layout/mapping.ts`
+ - `src/measure/measure.ts`
+ - `src/react/geometry.ts`
+ - `src/react/alignContent.ts`
+ - `src/react/splitClones.ts`
+ - `src/react/domMeasure.ts`
+- Document mutator (the runtime path actually used):
+ - `src/BasePaginationPlugin.ts` (lines 85–126, `withPagination` + `normalizeRootChildren`)
+ - `src/internal/reflowEngine.ts` (entire file)
+ - `src/PaginationCoordinator.tsx` (lines 70–114, `runReflow`)
+
+### What premirror does
+
+Quoting `src/layout/types.ts:8`: "The document model never changes — pages are a
+derived projection." Premirror's composer (`packages/composer/src/index.ts`) is
+*pure*: given `(MeasuredDocumentSnapshot, previous, LayoutInput)` it returns a
+`LayoutOutput` of pages → frames → fragments → lines. No PM transactions are
+emitted by the engine. Page chrome is rendered as a viewport overlay
+(`PremirrorPageViewport` in `packages/react/src/index.tsx`).
+
+### What plate does
+
+The runtime path **mutates the Slate value**: `normalizeRootChildren` wraps loose
+top-level blocks into synthetic `{ type: 'page', children: [...] }` elements, and
+`reflowPageBoundary` moves nodes between sibling `page` elements via `moveNodes`,
+`insertNodes`, `splitNodes`, and `removeNodes`. The pure pipeline under
+`src/layout/**` exists but is *never called* from the React plugin chain.
+
+### Why it's a failure
+
+1. The model is no longer portable: any consumer reading `editor.children`
+ sees `page` wrapper elements that don't exist in their schema.
+2. Serialization (Markdown, HTML, DOCX) must now special-case unwrapping `page`
+ or it leaks pagination state into exports.
+3. Two clients running pagination produce slightly different `page` wrapping
+ when their viewport widths differ — Yjs then replicates conflicting moves.
+4. The premirror-faithful pipeline shipped in `src/layout/**` is dead code from
+ the React surface's perspective — increasing bundle size and confusing
+ readers about which path is authoritative.
+
+### Fix options
+
+**Option A — Delete the document mutator; ship the pure pipeline.**
+- Pros:
+ - Restores premirror's invariant ("document is the source of truth").
+ - One implementation to test and maintain.
+ - Yjs replay becomes trivial: only authored ops cross the wire.
+- Cons:
+ - Requires building the viewport overlay (à la `PremirrorPageViewport`) that
+ Plate currently delegates to `PageElement` rendering pages as block nodes.
+ - Selection / caret projection must be implemented (premirror's `MappingIndex`
+ has no equivalent in plate's mapping layer — see F-004).
+ - Large breaking change for any caller already shaped around `page` elements.
+
+**Option B — Delete the pure pipeline; commit to the mutator.**
+- Pros:
+ - Smallest diff; ships what already runs in production.
+ - Avoids the overlay/decoration rewrite.
+- Cons:
+ - Abandons every premirror correctness property (determinism, no model writes,
+ no collab divergence).
+ - Keeps tracked failures F-002, F-003, F-005, F-006 indefinitely.
+ - Throws away ~800 lines of tested code.
+
+**Option C — Bridge: run the pure pipeline as the *truth*, keep the mutator as
+a fallback for non-React surfaces.**
+- Pros:
+ - Incremental migration; new features land on the pure path.
+ - Mutator remains as the "compat" exporter for legacy callers.
+- Cons:
+ - Doubles the surface area to test (every behavior across two code paths).
+ - Hard to keep the two paths in agreement; subtle drift will show up as
+ intermittent test flakes.
+ - Probably the worst long-term choice for an "editor law" package.
+
+### Recommendation seed
+
+Option A is the only outcome that matches the stated translation goal. Stage
+it behind a `viewMode: 'projected'` flag if the breaking change is too large
+to land at once.
+
+---
+
+## F-002 — Pure pipeline is never wired into the runtime
+
+### Location
+
+- `src/PaginationPlugin.ts` (whole file — 25 lines)
+- `src/internal/PaginationAboveEditable.tsx` (whole file — 27 lines)
+- `src/PaginationCoordinator.tsx:70-114` (`runReflow`)
+- `src/layout/compose.ts` (exported but unreferenced from React entrypoints)
+- `src/measure/measure.ts` (same)
+- `src/react/alignContent.ts`, `src/react/splitClones.ts` (same)
+
+### What premirror does
+
+`packages/react/src/index.tsx:51-86` exposes `usePremirrorEngine`, which on
+every `editorState` change runs `runtime.toSnapshot → measureSnapshot →
+composeLayout` synchronously inside `useMemo`, then `useLayoutEffect` caches
+the previous layout. The resulting `LayoutOutput` is the only authority used
+by `PremirrorPageViewport` and `useProjectedSelection`.
+
+### What plate does
+
+`PaginationPlugin` registers `aboveEditable: PaginationAboveEditable`, which
+mounts `` and ``. The
+coordinator's `runReflow` reads DOM heights and calls `reflowPageBoundary`,
+which mutates the document. None of `buildSnapshot`, `measureSnapshot`,
+`composeLayout`, `buildMappingIndex`, `fragmentRects`, `computePageStartSpacers`,
+`alignContentToLayout`, `getPageGeometry`, or `renderSplitClones` is referenced
+from any non-test file outside the `src/layout/` and `src/react/` islands.
+
+`rg --files-with-matches 'composeLayout\\(|buildSnapshot\\(|measureSnapshot\\('
+src` returns *only* the files where those functions are *defined* plus their
+`__tests__/*.spec.ts` neighbours.
+
+### Why it's a failure
+
+1. The translation effort has shipped two implementations and runs the wrong
+ one. Anything fixed in the pure pipeline (widow/orphan policy in
+ `compose.ts`, atomic block handling in `snapshot.ts`) has no production
+ effect.
+2. Reviewers reading `index.ts` cannot tell which APIs are "live": the barrel
+ exports `composeLayout` alongside `BasePaginationPlugin`, suggesting both
+ are part of the contract.
+3. Tests for the pure pipeline pass without exercising the runtime path, so
+ green CI does not imply working pagination.
+
+### Fix options
+
+**Option A — Replace `PaginationAboveEditable` with a pipeline-driven host.**
+Write a `PaginationViewport` that runs the pure pipeline on each editor
+content change (via `useEditorSelector` or `editor.children` subscription),
+renders page chrome over the editable, and applies `alignContentToLayout` /
+`renderSplitClones` as CSS-only side effects.
+
+- Pros:
+ - Activates the dead code immediately.
+ - No model mutation, so it composes cleanly with Yjs (F-006/F-007).
+ - Selection projection becomes possible once a `MappingIndex` is built
+ (see F-004).
+- Cons:
+ - Needs an overlay container in the React tree that isn't part of Slate's
+ editable subtree (or accept that `aboveEditable` is the host).
+ - `PageElement` becomes inert; consumers who customized it must migrate to a
+ page-chrome render prop.
+
+**Option B — Delete the pure pipeline (mirror of F-001 Option B).**
+
+- Pros: shrinks bundle and removes the "which path?" confusion.
+- Cons: throws away the only premirror-faithful code and locks the package
+ into the mutator design.
+
+**Option C — Compute the pure layout for diagnostics only and keep mutating
+the document for rendering.**
+
+- Pros: lets tooling show "this is where the composer thinks page 3 starts"
+ without changing the runtime.
+- Cons: extra CPU per edit for no behavior change; the diagnostic layout and
+ the mutator can disagree, which is worse than not having the layout at all.
+
+### Recommendation seed
+
+Option A. If shipping a viewport host is too large a change in one PR, at
+minimum re-export `composeLayout` / `buildSnapshot` from a separate
+`@platejs/pagination/layout` entry so the runtime barrel does not falsely
+imply they're the live API.
+
+---
+
+## F-003 — Line-level fidelity lost (block-granularity composer)
+
+### Location
+
+- `src/layout/compose.ts` (whole file)
+- `src/layout/types.ts:48-119` (`MeasuredBlock`, `BlockFragment`)
+- `src/measure/measure.ts` (whole file)
+
+### What premirror does
+
+`packages/composer/src/index.ts:349-506` (`breakBlockIntoLineDrafts`) walks
+every `StyledRun` inside a block, splits on `\n`, measures with
+`@chenglou/pretext` (`prepareWithSegments` / `layoutNextLine`), wraps at the
+last whitespace before overflow, fixes mid-word splits (`fixWordBoundarySplits`),
+honours atomic runs (`run.atomic`), and emits `LineBox` / `PlacedRun` records
+with per-run `x` and `width`. Selection projection in `useProjectedSelection`
+relies on those per-line PM ranges (`line.pmRange.from..to`).
+
+### What plate does
+
+`composeLayout` consumes `MeasuredBlock { heightPx, lineCount, lineHeightPx }`
+and emits `BlockFragment { lineStart, lineCount, heightPx, y }`. There is no
+run, no `PlacedRun`, no per-line text, no per-line PM range, no whitespace
+break logic, no word-boundary fixup, no atomic-run handling, and no
+typography awareness. A "line" is just `Math.round(heightPx / lineHeightPx)`.
+
+### Why it's a failure
+
+1. Premirror's `widow_orphan_protection` reasons about lines that *exist*;
+ plate's reasons about lines that the DOM happened to render at the moment
+ of measurement. A late font load changes `lineHeightPx`, which changes
+ `lineCount`, which silently shifts every page break.
+2. Caret projection is impossible: nothing in plate's mapping records the
+ per-line PM range, so `pmPosToLayout` cannot be implemented faithfully
+ (see F-004).
+3. Atomic runs (inline-void links, inline images, mentions) cannot be
+ protected from being mid-broken because the composer never sees them.
+4. The composer's "splittable" hint comes from `buildSnapshot` based only on
+ top-level block type. Inline-level "do not break" hints from premirror
+ (`atomic: true` on a `StyledRun`) have no equivalent.
+
+### Fix options
+
+**Option A — Adopt premirror's runs + line-drafts model verbatim.**
+Import `BlockSnapshot` / `StyledRun` / `PlacedRun` shapes, port
+`breakBlockIntoLineDrafts`, and integrate `pretext` (or a Slate-compatible
+text shaper) for width measurement.
+
+- Pros:
+ - True premirror parity: deterministic layout, accurate widows/orphans,
+ atomic-run protection, real caret projection.
+ - Unlocks features that require line-level data (selection rects,
+ keyboard navigation by visual line, hyphenation).
+- Cons:
+ - Requires a text-measurement dependency (`pretext` or canvas-based).
+ - Snapshot extraction from Slate's tree is non-trivial (Slate doesn't have
+ PM's `nodeSize` / `forEach` semantics).
+ - Large change; touches `snapshot.ts`, `compose.ts`, `mapping.ts`,
+ `measure.ts`, and every test under `src/layout/__tests__/`.
+
+**Option B — Keep block-level composer; emit "line stub" records that hold
+the DOM-measured line bottoms.**
+
+- Pros: small diff; reuses `collectLineBottoms` from `splitClones.ts`.
+- Cons:
+ - Layout becomes DOM-dependent and non-deterministic across machines.
+ - Yjs / SSR / headless tests can't run the composer.
+ - Still no PM ranges per line, so caret projection remains impossible.
+
+**Option C — Treat block-level as a deliberate scope reduction; document it.**
+Mark every "line" feature as out-of-scope and remove the line-related fields
+from `BlockFragment`.
+
+- Pros: zero work; honest about current capability.
+- Cons: kills the most valuable premirror properties; admits the translation
+ is a downgrade.
+
+### Recommendation seed
+
+Option A, sequenced after F-001/F-002. There is no way to deliver real caret
+projection or correct widows/orphans without line-level data.
+
+---
+
+## F-004 — No PM/Slate-position ↔ layout mapping
+
+### Location
+
+- `src/layout/mapping.ts` (whole file)
+- `src/layout/projection.ts:30-83` (`fragmentRects`, `blockLinePosition`)
+- (absent) — no equivalent of premirror's `MappingIndex.pmPosToLayout` /
+ `layoutToPmPos`.
+
+### What premirror does
+
+`packages/composer/src/index.ts:556-598` builds a sorted `LineRef[]` index and
+exposes `pmPosToLayout(pmPos): LayoutPoint | null` and the inverse. Selection
+projection (`useProjectedSelection` in `packages/react/src/index.tsx:328-344`)
+walks every `LineBox.pmRange` to emit rects for any PM range.
+
+### What plate does
+
+`MappingIndex` only exposes `fragmentsOfBlock`, `pageOfBlock`, and
+`fragmentOfBlockLine(blockIndex, lineIndex)`. There is **no** function from a
+Slate `Point` (or even a top-level path) to a `LayoutPoint`, and no inverse.
+
+### Why it's a failure
+
+1. The package cannot project the editor's selection onto page coordinates.
+ Any caller wanting "scroll caret into view across pages" must duplicate
+ the work outside the package.
+2. Plugins that draw decorations relative to the page (page-anchored
+ tooltips, footnote markers, sidebar comments aligned to caret line) have
+ no public API.
+3. `fragmentOfBlockLine(blockIndex, lineIndex)` assumes a *single* line index
+ per block — but a block can have multiple lines and the index is in
+ "line within block", which the caller must compute outside this package
+ (using DOM, not the layout) because the composer never records line PM
+ ranges (F-003).
+
+### Fix options
+
+**Option A — After F-003, add `pmPosToLayout` / `layoutToPmPos` to
+`MappingIndex`.** Requires line-PM ranges to exist.
+
+- Pros: closes the contract gap; matches premirror exactly.
+- Cons: dependent on F-003.
+
+**Option B — Approximate via Slate `Path` ↔ `BlockFragment`.**
+Expose `pathToFragment(path: Path)` and `fragmentToPath(ref)` only.
+
+- Pros: shippable today against the current block-level model.
+- Cons: cannot resolve to a *line* or a caret offset; degrades when blocks
+ span pages.
+
+**Option C — Defer entirely; advertise pagination as "visual only".**
+
+- Pros: nothing to build.
+- Cons: makes the package useless for accessibility, keyboard navigation,
+ caret-tracking comments, and most premirror-class features.
+
+### Recommendation seed
+
+Option B today (cheap, useful for split-block rendering), Option A after
+F-003 lands.
+
+---
+
+## F-005 — Reflow emits Yjs ops; collab peers diverge
+
+### Location
+
+- `src/internal/reflowEngine.ts:21-27` (`withoutSaving`)
+- `src/internal/reflowEngine.ts:104-117, 167-181, 217-225, 382-411`
+ (all `editor.tf.moveNodes`, `insertNodes`, `removeNodes`, `splitNodes`
+ calls inside reflow)
+- `src/yjs/YjsIntegration.tsx:12-56` (the bridge that pretends collaboration
+ is safe)
+- `src/leaderElection.ts` (the gate that's supposed to prevent the divergence)
+
+### What premirror does
+
+The composer mutates nothing. Pagination output is a derived value; collab
+peers see the same `editorState` and independently render the same
+`LayoutOutput`. Selection rects can differ per peer (different viewport
+widths) without producing transactions.
+
+### What plate does
+
+`reflowPageBoundary` issues Slate transforms that **are** persisted to the
+document. `withoutSaving` only wraps them with `HistoryEditor.withoutSaving`,
+which suppresses undo entries but **not** Yjs CRDT updates — the Yjs binding
+observes the resulting Slate operations and replicates them. The bridge tries
+to elect a single "leader" peer (`collabOpts.mode === 'leader'`), but:
+
+1. The leader gate is checked only in `shouldProcess()` *inside* the React
+ coordinator. `normalizeRootChildren` (called from `withPagination` and
+ `normalizeInitialValue`) runs on every peer regardless of leader status,
+ so initial wrapping conflicts on first sync.
+2. `createAwarenessLeaderElection` picks "min client id with
+ `pagination.ready === true`". Until the first peer flips
+ `awareness.setLocalStateField('pagination', { ready: true })`, *every*
+ peer thinks it is the leader (`clientId === clientId`) and proceeds.
+3. Reflow happens after a debounce + idle callback; two peers can both pass
+ the leader check, both move nodes, and produce conflicting CRDT updates.
+
+### Why it's a failure
+
+1. Two peers editing the same document produce different page counts after a
+ resize race, then replicate those page boundaries to each other, producing
+ double-wraps or empty pages that the next reflow tries to "fix" — an
+ amplification loop.
+2. Cursor position references baked into Yjs anchors can land *inside* the
+ synthetic `page` element after wrapping, and become invalid when the
+ other peer un-wraps.
+3. Server-persisted documents now carry layout-time decisions: opening the
+ same JSON in a different viewport will trigger more wrapping ops,
+ permanently growing the document.
+
+### Fix options
+
+**Option A — Make pagination purely projected (F-001 Option A).**
+- Pros: collab safety becomes definitional; no ops to replicate.
+- Cons: requires the F-001 rewrite.
+
+**Option B — Tag reflow ops with a meta flag and have the Yjs binding skip
+them.**
+- Pros: minimal change; works with the current mutator architecture.
+- Cons: requires upstream `@platejs/yjs` support; introduces an op-level
+ contract that all collab plugins must honor. Easy to miss in third-party
+ bindings.
+
+**Option C — Strict leader-only reflow, gated *before* initial normalize.**
+Hoist `isPaginationMutating` and leader checks into `normalizeInitialValue`
+and `withPagination.normalizeNode`.
+
+- Pros: keeps the mutator design; works today.
+- Cons: a follower joining mid-document still sees the leader's `page`
+ wrappers materialize as CRDT inserts, which is the same divergence in
+ slow-motion. Also: "what if the leader leaves?" — promotion mid-edit will
+ reflow against the new leader's viewport and re-wrap everything.
+
+### Recommendation seed
+
+Option A. Anything else is a guard against a problem the design created.
+
+---
+
+## F-006 — Measure cache key vs. cache map disagree on dimensionality
+
+### Location
+
+- `src/measure/measure.ts:52-79`
+
+### What premirror does
+
+`pretextWidthCache` (`packages/composer/src/index.ts:24-42`) keys by
+`${font}\n${text}`, so width depends only on the inputs to measurement.
+
+### What plate does
+
+```ts
+const cacheKey = `${block.id}@${widthPx}`;
+const cached = cache?.get(block.id); // ← keyed by id alone
+if (cached && cached.key === cacheKey) { ... }
+else {
+ metrics = measure(block);
+ if (metrics && cache) cache.set(block.id, { key: cacheKey, metrics });
+}
+```
+
+The `MeasureCache` is `Map` keyed by
+`block.id`, but the *logical* key includes `widthPx`. The cache holds **one
+slot per block**, not one per (block, width) pair. Two viewports measuring
+the same document at different widths thrash a single cache entry, each
+invalidating the other's measurement.
+
+### Why it's a failure
+
+1. Resize storms invalidate the cache even when the new width is one the
+ document was just measured at — defeats the cache's purpose.
+2. Two side-by-side editors (e.g., comparison view) trample each other's
+ measurements.
+3. The bug is silent: cache hits become rare but nothing logs the miss rate,
+ so performance regressions look like "measurement is slow" rather than
+ "cache doesn't cache".
+
+### Fix options
+
+**Option A — Key the map by the composite key.**
+```ts
+cache.set(`${block.id}@${widthPx}`, { metrics });
+const cached = cache?.get(`${block.id}@${widthPx}`);
+```
+- Pros: one-line fix; correct cache semantics.
+- Cons: cache grows unbounded with viewport-width changes; needs an LRU or
+ width-bucket eviction.
+
+**Option B — Cache per-width as nested maps.**
+`Map>`.
+- Pros: explicit; easy to evict a whole block on content change.
+- Cons: slightly more code; same unbounded-growth concern.
+
+**Option C — Quantize `widthPx` (snap to nearest 16px) before keying.**
+- Pros: bounds the cardinality of cached widths.
+- Cons: introduces fuzz; needs justification per content type. A 1px width
+ delta usually doesn't matter for block height; sometimes it does
+ (justification, RTL, long URLs).
+
+### Recommendation seed
+
+Option A first (correctness), Option C later as an optimization with a
+measured cache hit-rate baseline.
+
+---
+
+## F-007 — `getPageIndexFromOp` confuses path with page index
+
+### Location
+
+- `src/internal/runtime.ts:49-60`
+- `src/BasePaginationPlugin.ts:96-105` (consumer)
+
+### What premirror does
+
+Premirror tracks invalidation as a *PM position range*
+(`PremirrorInvalidationState = { from: number; to: number }` in
+`packages/prosemirror-adapter/src/index.ts:49-81`). The range covers the
+changed PM positions; recomposition recomputes any block touching that range.
+
+### What plate does
+
+```ts
+export function getPageIndexFromOp(op: Operation): number | null {
+ const indices: number[] = [];
+ if ('path' in op && op.path.length > 0) indices.push(op.path[0]);
+ if ('newPath' in op && op.newPath.length > 0) indices.push(op.newPath[0]);
+ return indices.length ? Math.min(...indices) : null;
+}
+```
+
+`op.path[0]` is only the page index *if the document is currently wrapped*.
+That wrapping is itself produced by reflow, so during initial normalize or
+between `normalizeRootChildren` calls, `op.path[0]` is a top-level block
+index, not a page index. Worse, `set_selection` operations don't have a
+`path` at all — they're ignored entirely.
+
+### Why it's a failure
+
+1. Before the first wrap, `markDirty(blockIndex)` marks a *block* as dirty
+ but the coordinator reads it as a *page* index, so reflow starts from
+ the wrong DOM node (or `getPageDom(blockIndex)` returns `undefined` and
+ reflow silently no-ops).
+2. After `wrapRootRange`, a `move_node` op might use `path: [3]` (moving
+ block 3 inside the wrap operation) — the runtime then marks "page 3"
+ dirty even when there are only 2 pages, polluting the dirty set with
+ nonexistent indices.
+3. Operations from `set_node` deep inside a block (`path: [0, 5, 1, 0]`)
+ correctly mark page 0 dirty, but a `set_node` on the *root selection*
+ carries no path, so caret-only changes never invalidate.
+
+### Fix options
+
+**Option A — Resolve `op.path` to a page index by walking the live tree.**
+Look up the ancestor `page` element of the given path and emit its index.
+
+- Pros: correct semantics; works regardless of wrap state.
+- Cons: requires reading `editor.children` per op (cheap, but synchronous);
+ still breaks during wrap-in-progress.
+
+**Option B — Move from "page dirty" to "block dirty" sets.**
+Track the set of block paths (or PM-equivalent positions) and let the
+coordinator compute which page to start reflow from after looking up the
+current layout.
+
+- Pros: matches premirror's invalidation model; survives re-wrapping.
+- Cons: requires F-001 / F-002 since "block dirty → page" needs the layout
+ index.
+
+**Option C — Mark "everything dirty" (`markDirty(0)`) on every op.**
+
+- Pros: trivially correct.
+- Cons: kills the entire incremental reflow optimization; pagination becomes
+ O(pages) per keystroke.
+
+### Recommendation seed
+
+Option B once the pure pipeline is live. Until then, Option A as a
+correctness patch.
+
+---
+
+## F-008 — Coordinator races: mutation guard doesn't span the async reflow
+
+### Location
+
+- `src/PaginationCoordinator.tsx:70-114` (`runReflow`)
+- `src/internal/editorRegistry.ts:21-31` (`withPaginationMutations`)
+- `src/internal/reflowEngine.ts:107-117, 178-184, 218-225` (mutation sites)
+
+### What premirror does
+
+Layout is synchronous within a React commit (`useMemo` inside
+`usePremirrorEngine`). There is no "are we currently paginating?" flag
+because there is no asynchronous mutation.
+
+### What plate does
+
+`runReflow` is `async` and awaits a `requestAnimationFrame` before mutating.
+Each call to `reflowPageBoundary` *internally* wraps its `editor.tf.moveNodes`
+in `HistoryEditor.withoutSaving`, but **not** in `withPaginationMutations`.
+The `mutating` `WeakSet` is set only inside specific transforms
+(`paginationTf.withMutations(...)` in `splitOversizedBlock`,
+`_withPaginationMutations` in `toggleHeader` / `toggleFooter`).
+
+So in the normal overflow path:
+
+1. `runReflow` schedules an idle callback.
+2. User types between rAF and the mutation.
+3. The typed op fires `apply` → `markDirty(pageIndex)` → notifies subscribers.
+4. The subscriber `consumeDirtyMin()` clears the set and reschedules.
+5. `runReflow` then calls `moveNodes`, which fires its own `apply` →
+ `getPageIndexFromOp` returns a page index → `markDirty` again →
+ another reflow scheduled.
+
+`isPaginationMutating` returns `false` for the reflow's own ops because
+nothing in `reflowPageBoundary`'s overflow branch wraps with
+`_withPaginationMutations`.
+
+### Why it's a failure
+
+1. Reflow ops re-enter the dirty queue, producing a self-sustaining reflow
+ loop on any document larger than `maxPagesPerIdle`.
+2. User keystrokes during reflow can land in a DOM node that the mutator is
+ about to move; the selection is then placed in a node that no longer
+ exists, throwing `NodeNotFoundError`.
+3. The guard exists for `splitOversizedBlock` and the toggle transforms but
+ not the hot path, so the asymmetry is silent and easy to regress.
+
+### Fix options
+
+**Option A — Wrap the entire `reflowPageBoundary` call in
+`_withPaginationMutations`.**
+Move the guard into the coordinator:
+```ts
+withPaginationMutations(editor, () => {
+ reflowPageBoundary(editor, page, ctx);
+});
+```
+
+- Pros: closes the loop; one line in the coordinator.
+- Cons: any *legitimate* user op interleaved between rAF and the mutation is
+ also marked as "pagination", suppressing its dirty notification. This is
+ usually fine (the next reflow tick covers it) but rare interleavings can
+ drop edits from the dirty set.
+
+**Option B — Make reflow synchronous and run inside `flushSync`.**
+
+- Pros: no race window.
+- Cons: blocks the main thread during layout; defeats the idle scheduling
+ that's the whole point of the coordinator.
+
+**Option C — Track op causality via op metadata (Slate's `op.tags`).**
+Tag every reflow op as `{ source: 'pagination' }` and skip those in the
+runtime.
+
+- Pros: clean separation; works even across async boundaries.
+- Cons: Slate operations don't carry metadata by default; requires wrapping
+ the editor to add it. Plus collab bindings must propagate the tag, which
+ most don't.
+
+### Recommendation seed
+
+Option A. The race is real and Option B's perf hit is unacceptable for large
+documents.
+
+---
+
+## F-009 — `findOverflowSplitIndex` linear-scan fallback returns the wrong split
+
+### Location
+
+- `src/internal/reflowEngine.ts:231-277`
+
+### Symptom
+
+```ts
+if (!monotonic) {
+ for (let i = 0; i < children.length; i++) {
+ const bottom = children[i].offsetTop + children[i].offsetHeight;
+ if (bottom > maxHeight) return i;
+ }
+ return null;
+}
+```
+
+The fallback returns the first child whose *bottom* exceeds `maxHeight`. In
+a non-monotonic layout (CSS columns, absolute positioning, `flex` with
+`order:`), the first child *in document order* whose bottom overflows is
+not necessarily the first child *visually* overflowing. The mutator then
+moves the wrong subset of children to the next page, leaving the actual
+overflowing content in place — so the same page reflows infinitely.
+
+### Why it's a failure
+
+1. Any consumer using multi-column layout, floats, or
+ `position: absolute` blocks triggers an infinite reflow loop.
+2. The "infinite loop" is partially masked by `maxPagesPerIdle: 6`, so the
+ symptom is "pagination just stops working after a resize" rather than a
+ hang.
+3. The binary-search path also makes the same assumption (children sorted by
+ `offsetTop`), so if a customization makes the layout monotonic *except*
+ for one outlier, the binary search converges to a child near the outlier
+ but not necessarily the first overflower.
+
+### Fix options
+
+**Option A — Sort children by `offsetTop + offsetHeight` first; then linear
+scan.**
+- Pros: handles any layout deterministically.
+- Cons: O(n log n) per reflow; for documents with thousands of top-level
+ blocks per page (rare) that's measurable.
+
+**Option B — Pre-flight detect "is this page layout pageable?" and refuse to
+reflow non-monotonic layouts.**
+- Pros: prevents the corruption.
+- Cons: silently disables pagination for multi-column / flex `order:` users.
+
+**Option C — Replace heuristic with a layout-aware split derived from
+`composeLayout` (after F-002 lands).**
+- Pros: deterministic, layout-agnostic; the composer already knows where
+ pages break.
+- Cons: requires the pure pipeline to be live.
+
+### Recommendation seed
+
+Option A as a stopgap, Option C as the final answer.
+
+---
+
+## F-010 — `splitOversizedBlock` is unsound under decorations and inline voids
+
+### Location
+
+- `src/internal/reflowEngine.ts:279-418`
+
+### What it does
+
+Binary-searches a text offset in a block using `editor.api.string(blockPath)`
+character offsets, converts each midpoint to a Slate `Point` via
+`pointAtOffset`, builds a Slate `Range`, and calls `ReactEditor.toDOMRange`
+to read the DOM bottom. The first midpoint with `rect.bottom <= maxBottom`
+becomes the split point.
+
+### Why it's a failure
+
+1. `editor.api.string(blockPath)` concatenates text leaves without
+ accounting for inline voids (``, ``,
+ ``). The character offset returned by `pointAtOffset` for a
+ midpoint inside a void is "inside the void's surrounding zero-width
+ text" — splitting there either errors (Slate refuses to split a void) or
+ silently splits *around* the void on one side.
+2. Slate `Range` decorations (search highlights, comments, suggestions)
+ alter the DOM range layout; the `getBoundingClientRect()` reading is
+ then unstable across keystrokes.
+3. Some decorations wrap the range in a separate DOM node with its own
+ line box, producing a `rect.bottom` reading that depends on whether the
+ decoration is currently mounted — pagination becomes non-deterministic.
+4. The fallback "proportional estimate" (`maxHeight / scrollHeight * length`)
+ ignores font kerning and is wrong by tens of percent for justified text.
+
+### Fix options
+
+**Option A — Limit `allowTextSplit` to blocks with no inline voids and no
+active decorations.**
+Inspect `editor.api.nodes` first and bail if any non-text descendant is
+present, or if `editor.api.decorate` returns non-empty for the range.
+
+- Pros: avoids the unsoundness in practice.
+- Cons: most rich content has decorations; option degenerates to "never
+ split", reintroducing the oversized-block hang.
+
+**Option B — Use the `composeLayout` result to know exactly how many lines
+fit, then split at `lineStart + lineCount` (after F-002/F-003).**
+
+- Pros: deterministic; respects voids and decorations because they're known
+ to the composer.
+- Cons: requires F-003's line-level layout to exist.
+
+**Option C — Stop splitting oversized blocks; place them whole and let them
+overflow visually (with a debug warning).**
+
+- Pros: trivial; matches the "atomic block" handling in
+ `composeLayout` lines 105-121.
+- Cons: tall content (long tables, code blocks) overflows the visible page;
+ users see a clipped paragraph and lose content visually.
+
+### Recommendation seed
+
+Option C in the short term (correctness over feature), Option B as the
+permanent fix once the pure pipeline lands.
+
+---
+
+## F-011 — `domMeasure.createDomMeasure` ignores the configured content width
+
+### Location
+
+- `src/react/domMeasure.ts:34-46`
+- `src/measure/measure.ts:30-50` (declared `widthPx` in `MeasureOptions`)
+
+### What it does
+
+```ts
+return (block) => {
+ const dom = topLevelBlockElements(editable)[block.path[0]];
+ if (!dom) return null;
+ return {
+ heightPx: dom.offsetHeight,
+ lineHeightPx: resolveLineHeight(getComputedStyle(dom)),
+ };
+};
+```
+
+The measurement reads `dom.offsetHeight` at the *current* DOM width, never
+consulting the `widthPx` that `measureSnapshot` was called with. The cache
+key (already broken — see F-006) records `widthPx` as if it were the
+measurement geometry, but the measurement was actually taken at whatever
+width the live editable happened to be at that moment.
+
+### Why it's a failure
+
+1. If the coordinator calls `measureSnapshot({ widthPx: 720 })` but the
+ editable is currently rendered at 600px (because the page chrome hasn't
+ applied the new size yet), the cache stores the wrong height under the
+ `@720` key.
+2. After the user resizes from 720 → 480, the cache returns the *720* entry
+ for the 480-width measurement, because nothing actually re-measured at
+ 480.
+3. Side-by-side editors with the same document at different widths get the
+ same heights, then look broken when the layouts diverge from reality.
+
+### Fix options
+
+**Option A — Render the block into a hidden offscreen iframe / shadow root
+sized to `widthPx`, measure there.**
+
+- Pros: width-correct; no interference with the live editable.
+- Cons: heavy; needs a parallel React tree or a serialized HTML clone;
+ consumers must ensure their styles apply to the measurement frame.
+
+**Option B — Trust the live DOM and remove `widthPx` from the measurement
+contract entirely.**
+
+- Pros: honest about what the measurer actually does.
+- Cons: cache keys become meaningless; same content at different widths
+ collides; documents with one editor only "work" because of luck.
+
+**Option C — Apply `width: ${widthPx}px` temporarily to the DOM block,
+measure, restore.**
+
+- Pros: width-correct without a separate tree.
+- Cons: causes layout thrash (synchronous reflow per block); measurable on
+ large documents; can flash content during measurement.
+
+### Recommendation seed
+
+Option B is the honest short-term move (and forces F-006 to be re-thought).
+Option A is the right long-term answer.
+
+---
+
+## F-012 — `normalizeRootChildren` wraps on *every* peer at sync time
+
+### Location
+
+- `src/BasePaginationPlugin.ts:128-158, 311-333` (`normalizeInitialValue`,
+ `normalizeRootChildren`)
+- `src/BasePaginationPlugin.ts:139-154` (`onNodeChange` re-runs the wrap)
+
+### What premirror does
+
+The "is this content paginated?" question never touches the document. Each
+peer composes its own layout from the shared document state.
+
+### What plate does
+
+`normalizeInitialValue` calls `normalizeRootChildren(editor, type)` on first
+mount, *before* leader election can resolve. Every peer that opens the
+document at roughly the same time runs the wrap, each producing an
+independent set of `insert_node` / `move_node` operations for the synthetic
+`page` element. Yjs then merges those concurrent inserts, producing
+*duplicate* `page` ancestors that the next normalize tries to unwrap (`if
+ElementApi.isElement(node) && node.type === type && path.length !== 1
+{ editor.tf.unwrapNodes ... }`), which produces *more* ops, which produces
+more conflicts.
+
+### Why it's a failure
+
+1. First-sync convergence is undefined: two peers opening simultaneously
+ may end up with N+1 nested pages, then race to unwrap.
+2. The unwrap rule `path.length !== 1` only catches strict nesting; pages
+ accidentally created at sibling positions of legitimate pages are not
+ detected as duplicates.
+3. The "rescue" `onNodeChange` handler that re-wraps non-page roots will
+ *re-create* the conflict immediately after the unwrap settles.
+4. Stored documents that *already* contain `page` wrappers (saved by a
+ previous session) are correct on load, but if anyone opens the doc in a
+ schema without `BasePaginationPlugin`, the `page` wrappers persist as
+ unknown elements — pagination becomes a serialization concern.
+
+### Fix options
+
+**Option A — Move wrapping out of normalize and into a render-time
+projection (F-001 Option A).**
+
+- Pros: deletes the entire failure class.
+- Cons: requires the full pure-pipeline migration.
+
+**Option B — Make wrapping leader-only; followers ignore the wrap.**
+
+- Pros: works inside the mutator architecture.
+- Cons: a follower opening alone is its own leader and wraps anyway. As
+ soon as a second peer joins, the duplicates appear. The fix is
+ fundamentally racy.
+
+**Option C — Store pagination metadata in `editor.meta` (a separate Yjs
+sub-document, ignored by serialization), and never wrap the main tree.**
+
+- Pros: keeps the main document portable; gives a place for layout state.
+- Cons: pages-as-nodes is what `PageElement` renders today; changing that
+ invalidates every customization. Effectively requires F-001.
+
+### Recommendation seed
+
+Option A. The mutator design cannot be patched into collab safety.
+
+---
+
+## F-013 — `PaginationAboveEditable` cannot be replaced; double-coordinator hazard
+
+### Location
+
+- `src/PaginationPlugin.ts:20-25`
+- `src/yjs/YjsIntegration.tsx:12-56`
+
+### What it does
+
+`PaginationPlugin` is `toPlatePlugin(BasePaginationPlugin, { render: {
+aboveEditable: PaginationAboveEditable } })`. `PaginationAboveEditable`
+hard-mounts both `PaginationRegistryProvider` *and*
+``. There is no way to swap one without the other.
+
+Consumers wanting the Yjs bridge mount `` separately,
+which itself renders a *second* `` with a leader
+election. Both coordinators subscribe to the same runtime and both call
+`runReflow`.
+
+### Why it's a failure
+
+1. With two coordinators, every dirty page triggers two `runReflow`
+ invocations. The "first wins" race is decided by whichever subscriber
+ `consumeDirtyMin()` first; the second sees `dirty.size === 0` and
+ no-ops. But under load both can win for *different* indices.
+2. The second coordinator misses the leader election entirely — it was
+ mounted by `PaginationAboveEditable` with `leaderElection: undefined`,
+ so it uses `createAlwaysLeader`. Meanwhile the Yjs coordinator does
+ leader-gating. Followers run reflow via the always-leader coordinator
+ even when the Yjs gate would block them.
+3. There is no `unmountCoordinator` / `disableCoordinator` option, so the
+ bug cannot be fixed by configuration.
+
+### Fix options
+
+**Option A — Add a `mountCoordinator: boolean` option (default true).**
+Allow `PaginationAboveEditable` to skip the coordinator when the consumer
+provides their own.
+
+- Pros: smallest change; fixes the Yjs bridge case.
+- Cons: another boolean knob; consumers must remember to flip it; default
+ remains lossy for Yjs users who forget.
+
+**Option B — Make the coordinator side a separate React plugin extension
+(e.g., `PaginationCoordinatorPlugin`) that consumers compose in.**
+
+- Pros: opt-in by construction; no double-mount.
+- Cons: breaks the "just register `PaginationPlugin` and it works" promise.
+
+**Option C — Detect a mounted coordinator in the runtime and short-circuit
+the second one.**
+
+- Pros: zero API change.
+- Cons: introduces a hidden global; debuggability worsens.
+
+### Recommendation seed
+
+Option B aligns with how Yjs and history plugins are composed elsewhere in
+Plate.
+
+---
+
+## F-014 — Yjs bridge waits for sync but trusts `_isConnected` / `_isSynced` internals
+
+### Location
+
+- `src/yjs/YjsIntegration.tsx:14-22`
+
+### What it does
+
+```ts
+const isConnected = usePluginOption(YjsPlugin, '_isConnected');
+const isSynced = usePluginOption(YjsPlugin, '_isSynced');
+const canProcess = Boolean(isConnected && isSynced);
+```
+
+These option keys are explicitly prefixed `_` — by convention, internal /
+unstable. The bridge depends on them being read-only signals.
+
+### Why it's a failure
+
+1. `_isConnected` / `_isSynced` are not part of `@platejs/yjs`'s public API.
+ A minor-version bump to the Yjs plugin can rename or remove them, and
+ the pagination bridge silently degrades to "never paginate" (always
+ false) or "always paginate" (undefined coerced to false in the boolean).
+2. Tests for `YjsIntegration.spec.tsx` mock these directly, so the public
+ API breakage is invisible until production.
+
+### Fix options
+
+**Option A — Subscribe to the underlying Yjs provider events
+(`y-websocket` / `y-webrtc` emit `status` / `sync`) directly.**
+
+- Pros: no dependency on unstable plugin internals.
+- Cons: requires the bridge to know about the provider type; couples to
+ specific Yjs providers.
+
+**Option B — Lobby `@platejs/yjs` to expose stable `isConnected` /
+`isSynced` selectors and use those.**
+
+- Pros: clean upstream contract.
+- Cons: requires cross-package coordination; not actionable from this
+ package alone.
+
+**Option C — Add a `canProcess?: boolean` prop on `YjsPaginationBridge` and
+let the consumer wire it up.**
+
+- Pros: pushes the policy decision to the integrator.
+- Cons: every consumer has to learn how to detect Yjs sync; defeats the
+ purpose of a bridge.
+
+### Recommendation seed
+
+Option B, with Option A as the transitional implementation.
+
+---
+
+## F-015 — `runReflow` calls `requestAnimationFrame` unconditionally in SSR
+
+### Location
+
+- `src/PaginationCoordinator.tsx:81-83`
+- `src/internal/scheduleIdle.ts:7-17` (which *does* SSR-guard)
+
+### What it does
+
+```ts
+runningRef.current = true;
+try {
+ await new Promise((r) => requestAnimationFrame(r));
+ ...
+```
+
+`requestAnimationFrame` is a `window` global; on Node `globalThis.requestAnimationFrame`
+is `undefined`. Calling it raises `ReferenceError`.
+
+### Why it's a failure
+
+1. Server-render of a Plate document with `PaginationPlugin` registered
+ throws if `runReflow` is invoked (it is, by `scheduleReflowFrom(0)` in
+ the mount effect — and effects can fire during hydration depending on
+ the React version).
+2. `scheduleIdle` is SSR-safe, but the rAF inside `runReflow` is not, so
+ the safety guarantee leaks.
+3. Static site generators (Next.js `getStaticProps`, Remix's loader) trip
+ this if any code path renders the editor.
+
+### Fix options
+
+**Option A — Guard the rAF with `typeof window !== 'undefined'`.**
+
+- Pros: trivial.
+- Cons: still relies on `window` directly; the function shape becomes
+ "promise that may never resolve in SSR".
+
+**Option B — Bail at the top of `runReflow` when `typeof window ===
+'undefined'`.**
+
+- Pros: identical SSR contract to `scheduleIdle`.
+- Cons: code paths that *want* to run a "headless" reflow (for snapshotting
+ during SSR) lose the option.
+
+**Option C — Inject the scheduler so tests can replace `rAF` with a
+synchronous shim.**
+
+- Pros: improves testability.
+- Cons: more plumbing for one line of code.
+
+### Recommendation seed
+
+Option B; the package isn't designed for headless pagination today.
+
+---
+
+## F-016 — `PageElement` registry effect re-runs on every render with `pageIndex` change
+
+### Location
+
+- `src/PageElement.tsx:22-40`
+- `src/registry.tsx:24-32`
+
+### What it does
+
+```ts
+const path = usePath(BasePaginationPlugin.key);
+const pageIndex = typeof path?.[0] === 'number' ... ? path[0] : null;
+
+useEffect(() => {
+ if (!registry || pageIndex === null || !outerRef.current || !contentRef.current)
+ return;
+ return registry.registerPage(pageIndex, {
+ outer: outerRef.current,
+ content: contentRef.current,
+ });
+}, [registry, pageIndex]);
+```
+
+`usePath` returns a fresh array reference on every render. `pageIndex` is
+a primitive so the dep array is fine, but when the mutator moves pages
+around, page 2 becomes page 1 — the effect on the *previous* page-2
+instance cleans up (deletes the entry from the registry under key `2`)
+*after* the new page-1 instance has already registered its DOM under key
+`1`. Order is non-deterministic across React 18 strict-mode double-effects.
+
+### Why it's a failure
+
+1. After every reflow that renumbers pages, the registry may briefly miss
+ the entry for the renumbered page until both effects settle. A reflow
+ scheduled during that window calls `getPageDom(pageIndex)` → `undefined`
+ → silently no-ops.
+2. The cleanup guard `if (current?.outer === dom.outer) delete` is meant
+ to prevent stale deletes, but it's racy: the next register call writes
+ the new `outer` ref *under the same `pageIndex`*, then the stale
+ cleanup deletes it because `current?.outer === dom.outer` is true (same
+ DOM node — pages share the same `` recycled by React's keyed
+ reconciliation).
+
+### Fix options
+
+**Option A — Key page DOM by a stable identity (e.g., Slate node `id`),
+not by `path[0]`.**
+
+- Pros: stable across reflow / moves.
+- Cons: requires Slate elements to carry stable ids; `PageElement` doesn't
+ set one today.
+
+**Option B — Re-register on every render via `useLayoutEffect` with no
+deps (always re-runs).**
+
+- Pros: registry is always current.
+- Cons: every commit triggers a registry write; perf cost on large docs.
+
+**Option C — Treat the registry as an inversion: pages publish their refs
+into a context that `PaginationCoordinator` reads via `forwardRef`s
+keyed by Slate path, computed at read time.**
+
+- Pros: lazy and racefree.
+- Cons: bigger refactor.
+
+### Recommendation seed
+
+Option A; in the meantime, document the race and add a "registry stale"
+log in dev.
+
+---
+
+## F-017 — Tests don't exercise the runtime path under collaboration or SSR
+
+### Location
+
+- `src/__tests__/YjsIntegration.spec.tsx` (mostly mocks)
+- `src/__tests__/PaginationCoordinator.spec.tsx`
+- `src/__tests__/reflowEngine.spec.ts` (uses `Object.defineProperty` to fake
+ `offsetHeight` / `scrollHeight`)
+- (absent) — no test asserts cross-peer page convergence; no test asserts
+ SSR-safety; no test asserts behavior with `splittable: false`; no test
+ exercises the pure pipeline against the runtime.
+
+### What premirror does
+
+The composer has unit tests against deterministic snapshots; the React
+layer is tested with real `editorState` transitions. SSR safety is implicit
+because the composer is pure.
+
+### What plate does
+
+Tests are split between:
+1. **Pure pipeline tests** (`src/layout/__tests__/*`, `src/measure/__tests__/*`,
+ `src/react/__tests__/*`) — green, exercise dead code (F-002).
+2. **Runtime mutator tests** (`src/__tests__/reflowEngine.spec.ts`,
+ `BasePaginationPlugin.spec.ts`) — exercise the live path but fake DOM
+ metrics via `Object.defineProperty`. No real layout reflow is invoked.
+3. **Coordinator tests** — mock `runReflow` rather than exercising it.
+
+### Why it's a failure
+
+1. CI cannot detect any of F-005, F-008, F-012, F-015, F-016 because the
+ relevant scenarios are mocked away.
+2. Refactors that change the runtime path can pass all "pure" tests while
+ breaking production behavior.
+3. The pure pipeline's tests are the strongest evidence in the repo, yet
+ none of that code runs.
+
+### Fix options
+
+**Option A — Add integration tests with a real jsdom that mounts the
+editor, types content, and asserts the resulting page boundaries.**
+
+- Pros: catches end-to-end regressions.
+- Cons: jsdom layout is not real CSS; some failures only show up in real
+ browsers (Playwright / Cypress).
+
+**Option B — Promote the pure pipeline tests to "the" contract; deprecate
+the mutator-specific tests after F-001.**
+
+- Pros: focuses test effort on the desired final architecture.
+- Cons: requires F-001 / F-002 to land first.
+
+**Option C — Add cross-peer convergence tests with a fake Y.Doc.**
+
+- Pros: catches F-005 / F-012.
+- Cons: setting up Yjs in unit tests is non-trivial; tests are slow.
+
+### Recommendation seed
+
+Option B is the strategic direction; Option C is the highest-value tactical
+test to add immediately.
+
+---
+
+## F-018 — `reflowEngine` is hard-coupled to `slate-react` / `slate-history`
+
+### Location
+
+- `src/internal/reflowEngine.ts:12-13`
+ ```ts
+ import { HistoryEditor } from 'slate-history';
+ import { ReactEditor } from 'slate-react';
+ ```
+- `src/internal/reflowEngine.ts:287` (`if (!('hasEditableTarget' in editor))`)
+- `src/internal/reflowEngine.ts:332` (`const toDOMRange = ReactEditor.toDOMRange`)
+
+### What premirror does
+
+The composer is framework-agnostic. ProseMirror coupling lives in the
+adapter (`packages/prosemirror-adapter`), not the layout engine. The
+composer can run in any environment that produces a `MeasuredDocumentSnapshot`.
+
+### What plate does
+
+`reflowEngine.ts` *is* the layout engine for the live path, but it
+unconditionally imports `slate-react` and `slate-history` at module top
+level. Consumers using `@platejs/core` headless (server-side rendering,
+markdown converters, AI agents that mutate a Slate value without a React
+host) cannot import the package without dragging in React.
+
+### Why it's a failure
+
+1. The package's `BasePaginationPlugin` is supposed to be headless (`Base*`
+ convention everywhere in Plate), but importing it transitively pulls
+ `slate-react`. Tree-shaking can't drop the React import because of how
+ the engine references `ReactEditor.toDOMRange`.
+2. Tests that want to verify `BasePaginationPlugin` semantics without React
+ end up loading React anyway.
+3. The "Base/React" split that the rest of `@platejs/*` enforces is
+ broken here — readers can't trust the convention.
+
+### Fix options
+
+**Option A — Inject the DOM split function as a dependency of `withPagination`.**
+Move all `ReactEditor` calls into `PaginationPlugin` (React layer), pass a
+`splitOversizedBlock?: (editor, path) => boolean` callback into the base
+plugin options.
+
+- Pros: restores the Base/React split.
+- Cons: more wiring; consumers replacing the splitter must implement a
+ non-trivial function.
+
+**Option B — Lazy-load `slate-react` inside `splitOversizedBlock` using a
+dynamic `import()`.**
+
+- Pros: minimal API change.
+- Cons: introduces async into a synchronous code path; React bundlers
+ often refuse to code-split for that pattern.
+
+**Option C — Drop `splitOversizedBlock` entirely (F-010 Option C).**
+
+- Pros: removes the React coupling along with the unsound feature.
+- Cons: loses the only path that handles oversized single blocks.
+
+### Recommendation seed
+
+Option A; Option C as a stop-gap until the React layer ships its own splitter.
+
+---
+
+## F-019 — `projection.fragmentRects` rebuilds the mapping index on every call
+
+### Location
+
+- `src/layout/projection.ts:31-57, 60-83`
+
+### What it does
+
+```ts
+export function fragmentRects(layout, geometry, blockIndex) {
+ const mapping = buildMappingIndex(layout); // ← per call
+ ...
+}
+export function blockLinePosition(layout, geometry, blockIndex, lineIndex, lineHeightPx) {
+ const mapping = buildMappingIndex(layout); // ← per call
+ ...
+}
+```
+
+Each invocation walks all pages × frames × fragments to construct the index.
+A consumer rendering N split-block clones calls `fragmentRects` N times,
+producing O(N × pages × fragments) work.
+
+### What premirror does
+
+Premirror's `buildMappingIndex` is computed once per layout
+(`composeLayout` returns `{ mapping }` already constructed in
+`packages/composer/src/index.ts:801`) and callers reuse it.
+
+### Why it's a failure
+
+1. For a 100-page document with 10 split blocks, the projection step runs
+ `buildMappingIndex` 10× — quadratic in page count for what should be
+ constant.
+2. Memoization isn't possible at the call site because `layout` is a fresh
+ object each compose; consumers can't compare references.
+
+### Fix options
+
+**Option A — Have `composeLayout` (or a one-shot `buildLayout`) return the
+mapping pre-built, identical to premirror.**
+
+- Pros: matches the source; O(1) at call sites.
+- Cons: changes `LayoutOutput` shape (breaking).
+
+**Option B — Cache the mapping inside `buildMappingIndex` via a WeakMap
+keyed by `layout`.**
+
+- Pros: no API change.
+- Cons: weak-map cache is invisible; hard to debug stale entries.
+
+**Option C — Accept the cost; document that the projection helpers are
+"hot" and should be called sparingly.**
+
+- Pros: zero work.
+- Cons: shifts the burden to every consumer.
+
+### Recommendation seed
+
+Option A; the contract should mirror premirror's.
+
+---
+
+## F-020 — `alignContentToLayout` mutates DOM `style.marginTop` outside React
+
+### Location
+
+- `src/react/alignContent.ts:56-69`
+
+### What it does
+
+```ts
+topLevelBlockElements(editable).forEach((el, index) => {
+ el.style.marginTop = spacers.has(index) ? `${spacers.get(index)}px` : '';
+});
+```
+
+It rewrites inline `margin-top` on every top-level block element directly,
+not through React. The next React render that touches `style.marginTop`
+(e.g., a plugin animating a block in) will wipe the spacer.
+
+### What premirror does
+
+Premirror's projection paints page boundaries on an overlay div; it never
+mutates the editor's content layout DOM.
+
+### Why it's a failure
+
+1. Any custom `RenderElementProps` that sets `style.marginTop` (theming,
+ draft.js-style inline spacing plugins) collides — last writer wins,
+ pagination loses on every re-render of that block.
+2. CSS animations on `margin-top` glitch because React's reconciler can't
+ tween a property it doesn't own.
+3. Snapshot testing of the editor's rendered HTML now includes pagination
+ spacers, which makes snapshots brittle to viewport changes.
+
+### Fix options
+
+**Option A — Express spacers as a `data-pagination-page-start` attribute
+and use a stylesheet `[data-pagination-page-start] { margin-top: var(...) }`.**
+
+- Pros: React-friendly; consumers can override via CSS specificity.
+- Cons: per-block dynamic value requires CSS custom properties; still need
+ inline `style.setProperty('--page-spacer', ...)`.
+
+**Option B — Wrap each block in a positioned overlay rather than
+modifying its margin.**
+
+- Pros: zero collision with React rendering.
+- Cons: layout cost; double the DOM nodes per top-level block.
+
+**Option C — Stop trying to align the editable to page chrome — render the
+page chrome as a separate scrolling layer with its own positions.**
+
+- Pros: clean separation.
+- Cons: parallel scroll layers don't synchronize perfectly across
+ browsers; complex selection rendering.
+
+### Recommendation seed
+
+Option A; smallest change with the cleanest semantics.
+
+---
+
+## F-021 — `splitClones.renderSplitClones` clones contenteditable trees, breaking selection
+
+### Location
+
+- `src/react/splitClones.ts:88-120, 130-208`
+
+### What it does
+
+For each block that spans pages, the live block is clipped to the first
+fragment's height (`maxHeight:
px; overflow: hidden`) and a deep
+DOM clone of the same element is appended to an overlay div per
+subsequent fragment. The clone has `contenteditable` stripped, but it
+still carries `data-slate-node`, `data-slate-leaf`, and `data-slate-string`
+attributes.
+
+### What premirror does
+
+Premirror renders the editor on a single absolute-positioned layer; pages
+are static page surfaces drawn behind. There are no DOM clones.
+
+### Why it's a failure
+
+1. `slate-react` looks up DOM nodes by `data-slate-*` to translate
+ selection events; the cloned nodes carry the same attributes as the
+ originals. If a user clicks the cloned slice (even with
+ `pointer-events: none`, focus / IME can still target descendants under
+ some configurations), Slate may resolve the click to a node that
+ doesn't reflect the live caret.
+2. Mutation observers inside Slate may pick up the cloned subtree as a
+ "new node" insertion and emit normalization ops.
+3. Decoration plugins that walk `data-slate-leaf` (search highlights,
+ spellcheck overlays) double-render their decorations on the clones,
+ which then drift from the live state.
+4. Accessibility: cloned content is exposed to screen readers as
+ duplicate text (the clone is `contenteditable="false"` but still
+ readable). Long documents become unreadable.
+
+### Fix options
+
+**Option A — Strip `data-slate-*` attributes from clones before inserting.**
+
+- Pros: prevents Slate's mutation observer and selection logic from
+ treating clones as live.
+- Cons: misses any other Slate-specific markers that ship in future
+ versions.
+
+**Option B — Use `aria-hidden="true"` and `inert` on the overlay container.**
+
+- Pros: protects accessibility.
+- Cons: doesn't address Slate's DOM lookup confusion.
+
+**Option C — Replace clones with screenshots (canvas drawing of the slice).**
+
+- Pros: zero collision with Slate.
+- Cons: pixel-perfect rendering is hard for complex content; selection
+ inside the slice becomes impossible (read-only by design).
+
+**Option D — Render pagination as a single non-paginated stream and let
+CSS Pagination Module / `column-count` handle the visual paging.**
+
+- Pros: no clones needed; browsers handle the split.
+- Cons: CSS Pagination is poorly supported and doesn't expose hooks for
+ custom page chrome.
+
+### Recommendation seed
+
+Option A + Option B together as the minimum; Option C only if the visual
+fidelity gap is acceptable.
+
+---
+
+## F-022 — `viewMode: 'continuous'` partially honored; reflow still runs
+
+### Location
+
+- `src/PageElement.tsx:45-94` (renders differently based on `viewMode`)
+- `src/PaginationCoordinator.tsx:32-40, 205-209` (still subscribes and
+ reflows in continuous mode)
+
+### What it does
+
+`PageElement` checks `viewMode === 'paginated'` to decide whether to
+constrain height, show page numbers, and apply page-shadow styling. In
+`continuous` mode it renders the page as a full-width unstyled flow.
+
+`PaginationCoordinator`, however, **does** still call `scheduleReflowFrom(0)`
+on `viewMode` change and continues to subscribe to runtime dirty
+notifications. `reflowPageBoundary` reads `pageDom.content.clientHeight`,
+which in continuous mode is just `auto` → typically the full content
+height, never overflowing → reflow is a no-op every time but still costs
+the debounce + idle + rAF cycle on every keystroke.
+
+### What premirror does
+
+Premirror has no analog. Pagination is purely projected; turning it off
+means not mounting the viewport. No background work happens.
+
+### Why it's a failure
+
+1. Continuous mode pays the full pagination cost (per-keystroke debounce,
+ per-resize debounce, per-rAF reflow tick) for zero benefit.
+2. Toggling between modes triggers a chain reaction:
+ `scheduleReflowFrom(0)` → `markDirty(0)` → coordinator wakes → no-op
+ → reschedules. Each toggle leaves a pending timer on the heap.
+3. Devs reading the code can't tell that "continuous" disables reflow,
+ because it doesn't — the savings are accidental, not intentional.
+
+### Fix options
+
+**Option A — Short-circuit `scheduleReflowFrom` when `viewMode !== 'paginated'`.**
+
+- Pros: one-line fix.
+- Cons: continuous-mode users who *want* page-break overlay data lose it.
+
+**Option B — Render `PaginationCoordinator` conditionally on
+`viewMode === 'paginated'`.**
+
+- Pros: cleanly unmounts everything in continuous mode.
+- Cons: state (dirty set, leader election) is rebuilt on every toggle —
+ one-time cost but visible flicker on slow machines.
+
+**Option C — Treat `viewMode` as the only switch; in `continuous` mode,
+also skip the wrap normalization so the document doesn't carry `page`
+elements.**
+
+- Pros: enables "pagination is a view, not a model" semantics partially.
+- Cons: switching back to paginated re-runs `normalizeRootChildren`,
+ generating ops again.
+
+### Recommendation seed
+
+Option B. Continuous mode should be the cheapest configuration.
+
+---
+
+## F-023 — `LayoutPolicies` lost premirror's `minSlotWidthPx` and `slotSelectionPolicy`
+
+### Location
+
+- `src/layout/types.ts:26-34` (`LayoutPolicies`)
+- `src/BasePaginationPlugin.ts:70-79` (`DEFAULT_REFLOW_OPTIONS`)
+
+### What premirror has
+
+```ts
+export type LayoutPolicyConfig = {
+ widowLinesMin?: number;
+ orphanLinesMin?: number;
+ keepWithNextEnabled?: boolean;
+ minSlotWidthPx?: number; // ← gone in plate
+ slotSelectionPolicy?: "single_slot_flow" | "multi_slot_fill"; // ← gone
+};
+```
+
+### What plate has
+
+```ts
+export type LayoutPolicies = {
+ widowLinesMin: number;
+ orphanLinesMin: number;
+ keepWithNextEnabled: boolean;
+};
+```
+
+`minSlotWidthPx` and `slotSelectionPolicy` are dropped. The composer never
+considers obstacles (the `BandObstacle` type from premirror is also missing
+from plate's `types.ts`), so wrap-around content (sidebar callouts,
+floated images, marginalia) isn't representable.
+
+### Why it's a failure
+
+1. Any document with a floated figure / callout is laid out as if the
+ float doesn't exist. Pages overflow visually but the composer thinks
+ they fit.
+2. Premirror's "leftmost usable slot" logic (`usableSlotForBand` in
+ `packages/composer/src/index.ts:159-181`) has no equivalent — there's
+ no way to express "this line shares horizontal space with an obstacle."
+3. The translation is a strict subset; users porting from premirror lose
+ the feature with no migration path.
+
+### Fix options
+
+**Option A — Add `obstacles?: BandObstacle[]` to `LayoutInput` and have
+the composer carve slots.**
+
+- Pros: restores premirror parity.
+- Cons: requires line-level layout (F-003). Block-level can't represent
+ per-line slot widths.
+
+**Option B — Document the loss as a deliberate scope reduction in the
+package README.**
+
+- Pros: zero work.
+- Cons: admits the translation is incomplete.
+
+**Option C — Expose `obstacles` as a *render-only* concept (CSS `shape-outside`
+on overlays) without changing the composer.**
+
+- Pros: gets visual parity for floats.
+- Cons: composer's height calculation still ignores the float, so
+ pagination decisions stay wrong.
+
+### Recommendation seed
+
+Option A after F-003; Option B in the meantime so users aren't surprised.
+
+---
+
+## F-024 — `MeasuredBlock.lineCount` rounds away half-lines
+
+### Location
+
+- `src/measure/measure.ts:39-43`
+ ```ts
+ function lineCountFrom(heightPx: number, lineHeightPx: number): number {
+ if (lineHeightPx <= 0) return 1;
+ return Math.max(1, Math.round(heightPx / lineHeightPx));
+ }
+ ```
+
+### What it does
+
+The line count is computed as `round(heightPx / lineHeightPx)`. A 2.4-line
+block (e.g., a paragraph with a partial third line due to font-fallback
+metrics) is reported as 2 lines.
+
+### Why it's a failure
+
+1. The composer (`compose.ts:131-159`) multiplies `fit * lineHeight` to
+ compute the placed fragment height. If `lineCount` is rounded down, the
+ trailing partial line vanishes from the layout — content overflows
+ into the next page even though the composer placed only "2 lines"
+ on the current page.
+2. Widow/orphan minimums become wrong: a 3-line paragraph reported as 2
+ never triggers orphan protection.
+3. The same content with a different font yields a different line count,
+ making pagination font-dependent without acknowledging it.
+
+### Fix options
+
+**Option A — Use `Math.ceil` instead of `Math.round`.**
+
+- Pros: never under-reports lines; safer for overflow.
+- Cons: documents with descenders inflate line counts; layouts use more
+ pages than strictly necessary.
+
+**Option B — Keep `lineHeightPx` as a float and let the composer compute
+fractional lines.**
+
+- Pros: most accurate.
+- Cons: line-based widow/orphan policies become floating-point comparisons.
+
+**Option C — Track total `heightPx` directly and stop using `lineCount`
+in the composer; replace `cap = floor(remaining / lineHeight)` with
+`fits = (b.heightPx <= remaining)`.**
+
+- Pros: removes the rounding ambiguity.
+- Cons: loses the line-level widow/orphan policy granularity entirely.
+
+### Recommendation seed
+
+Option A immediately, Option B once the line model becomes first-class
+(F-003).
+
+---
+
+## F-025 — `stableId` falls back to a content hash that collides across edits
+
+### Location
+
+- `src/layout/snapshot.ts:46-50`
+ ```ts
+ function stableId(node: SlateNode): string {
+ if (typeof node.id === 'string' && node.id.length > 0) return node.id;
+ return `${node.type ?? 'node'}#${hash(nodeText(node))}`;
+ }
+ ```
+
+### What premirror does
+
+Premirror keys runs by `${runFrom}-${runIndex}` (PM position based) for
+`MeasuredDocumentSnapshot.measuredRuns`. Each PM transaction re-derives
+runs deterministically — there is no need for a "stable" id across edits
+because the snapshot is fully rebuilt per transaction.
+
+### What plate does
+
+When a node has no `id`, `stableId` derives a djb2 hash from the
+*concatenated text* of the node. Two paragraphs with identical text
+("hello") produce identical ids. The `MeasureCache` keys by this id —
+two paragraphs with the same content share the same cache slot but live
+at different paths.
+
+### Why it's a failure
+
+1. Two `"hello"` paragraphs cache the same `heightPx`, even if one is
+ inside a deeply nested wrapper that adds margin and the other isn't.
+2. Editing one paragraph to a new value invalidates the *other*
+ paragraph's cache entry because the id changes only for the edited
+ one, but the cache lookup for the unedited duplicate now misses (the
+ slot is the same string, but the value was overwritten by the edited
+ one before).
+3. `id`-less Slate values (the default for any Plate setup that doesn't
+ enable a stable-id plugin) cascade these collisions through the entire
+ measurement layer.
+
+### Fix options
+
+**Option A — Require stable ids on every block; refuse to operate without
+them.**
+
+- Pros: deterministic; matches premirror's reliance on PM positions.
+- Cons: forces every consumer to ship a node-id plugin (`@platejs/node-id`
+ or similar).
+
+**Option B — Key by `${path.join('.')}#${nodeText(node)}` instead of
+content hash.**
+
+- Pros: ids become unique within a tree.
+- Cons: path-based ids invalidate on every reorder; cache misses skyrocket
+ when blocks move.
+
+**Option C — Keep the hash but include `path` and a structural fingerprint
+(child types) in the hash input.**
+
+- Pros: reduces collisions for visually identical content.
+- Cons: still not deterministic if paths shift; can be patched piecemeal
+ without forcing consumers to add ids.
+
+### Recommendation seed
+
+Option A. The right call is to depend on stable ids and document the
+requirement clearly.
+
+---
+
+## F-026 — `toggleHeader` / `toggleFooter` are O(pages) and dirty the entire document
+
+### Location
+
+- `src/BasePaginationPlugin.ts:199-241` (`toggleHeader`)
+- `src/BasePaginationPlugin.ts:242-286` (`toggleFooter`)
+
+### What it does
+
+Both transforms iterate every existing page and `insertNodes` /
+`removeNodes` a header/footer in each. Then they `markDirty(i)` for every
+page index, which queues the full set into the runtime dirty set.
+
+### Why it's a failure
+
+1. For a 200-page document, toggling the header inserts 200 nodes in a
+ single `withoutNormalizing` block, then schedules reflow for every
+ page index — the entire pagination model is recomputed.
+2. The dirty notification is `markDirty(0..N-1)`; the runtime collapses
+ to `consumeDirtyMin() === 0`, so the coordinator restarts reflow at
+ page 0 anyway — but `markDirty` calls 200 times still trigger 200
+ subscriber notifications (the `notify` microtask is rate-limited, but
+ each `dirty.add(i)` runs).
+3. Headers / footers are page chrome — they're *not* document content.
+ Embedding them in every page node bloats the model with repeated
+ identical structures.
+
+### What premirror does
+
+Premirror doesn't expose page chrome at the model level. Headers/footers
+would be drawn by the React layer per page placement, not inserted into
+the document.
+
+### Fix options
+
+**Option A — Store header/footer templates in plugin options and render
+them in `PageElement` per page.**
+
+- Pros: model stays clean; toggle is `setOption` (O(1)).
+- Cons: breaks the current API where consumers can author per-page
+ headers; needs per-page overrides keyed by page index instead.
+
+**Option B — Hoist header/footer to the first page only and let the React
+layer clone for display.**
+
+- Pros: O(1) toggle; preserves per-document customization.
+- Cons: multi-page documents with different headers per section can't be
+ represented.
+
+**Option C — Leave it; document the O(pages) cost.**
+
+- Pros: zero work.
+- Cons: bad UX for large documents.
+
+### Recommendation seed
+
+Option A; pagination's header/footer concern belongs in the rendering
+layer, not the document model.
+
+---
+
+## F-027 — `pageDom.content.children[0]` cast loses TypeScript type safety
+
+### Location
+
+- `src/internal/reflowEngine.ts:198-203`
+ ```ts
+ const firstChildEl = nextPageDom.content.children[0] as
+ | HTMLElement
+ | undefined;
+ if (!firstChildEl) {
+ return { changed: false, nextPageToContinue: null };
+ }
+ ```
+
+### Why it's a failure
+
+`Element.children[0]` returns `Element | undefined`. Casting to
+`HTMLElement | undefined` loses the SVG/MathML case (SVGElement is an
+Element but not HTMLElement). A document containing inline SVG or
+MathML at the top of a page reads `firstChildEl.offsetHeight` — but
+SVGElement does not have `offsetHeight`. Result: `NaN` or `undefined`
+arithmetic, false underflow detection, content yanked between pages
+incorrectly.
+
+### Fix options
+
+**Option A — Guard with `instanceof HTMLElement`.**
+
+- Pros: correctness; falls back gracefully for SVG.
+- Cons: SVG-first content has no measurement path at all.
+
+**Option B — Use `getBoundingClientRect().height` instead of `offsetHeight`.**
+
+- Pros: works for SVG and MathML.
+- Cons: includes transforms (rotations would inflate the measurement).
+
+**Option C — Define a `getBlockHeight(el)` helper that handles every
+element type.**
+
+- Pros: encapsulates the platform quirks.
+- Cons: adds a new module.
+
+### Recommendation seed
+
+Option B; `getBoundingClientRect` is the cross-element measurement.
+
+---
+
+## F-028 — `resizeTimerRef` and `scheduledRef` don't deduplicate across modes
+
+### Location
+
+- `src/PaginationCoordinator.tsx:47-52, 161-183`
+
+### What it does
+
+The coordinator keeps two independent timer refs (`scheduledRef`,
+`resizeTimerRef`). On a resize storm, both can fire reflow:
+
+1. `resizeTimerRef` waits 200ms after the last `resize` event and calls
+ `scheduleReflowFrom(0)`.
+2. `scheduleReflowFrom(0)` arms `scheduledRef` (100ms by default).
+3. Meanwhile, the resize also caused layout changes that mutated the DOM,
+ firing `onNodeChange` → `markDirty(0)` → runtime subscriber →
+ `scheduleReflowFrom(0)` → `scheduledRef` armed *again* (the existing
+ timer is preserved because of `if (scheduledRef.current !== null) return`).
+4. The `pendingStartRef` is updated with `min(..., 0)` → still 0.
+5. After 200ms the resize timer fires; 100ms later the scheduled timer
+ fires. Two reflow runs back-to-back.
+
+### Why it's a failure
+
+1. Each resize triggers ~2× the work it needs to.
+2. If the user is *also* typing during a resize, all three signal paths
+ (resize, scheduleIdle from typing, runtime dirty) fight for the timer
+ slot. Symptoms: layout jitter, "the resize handle dragging is laggy."
+3. The cleanup logic on unmount only clears the *current* `scheduledRef`
+ and `resizeTimerRef`, not any pending idle callbacks queued via
+ `scheduleIdle`. Those still fire after unmount, calling `runReflow`
+ on a torn-down editor.
+
+### Fix options
+
+**Option A — Use a single timer ref + `pendingStartRef`; merge resize into
+the same debounce.**
+
+- Pros: one execution path; fewer races.
+- Cons: resize-specific debounce duration (200ms) is lost — typing
+ becomes laggier or resize becomes thrashier depending on the chosen
+ shared value.
+
+**Option B — Use `AbortController` to cancel queued idle callbacks on
+unmount.**
+
+- Pros: clean teardown.
+- Cons: `requestIdleCallback` doesn't accept an `AbortSignal`; needs a
+ wrapper.
+
+**Option C — Replace all of this with a single derived `useMemo`
+following premirror (F-002 Option A).**
+
+- Pros: kills the entire scheduling layer.
+- Cons: requires the rewrite.
+
+### Recommendation seed
+
+Option C is the right outcome; Option B as the immediate teardown fix.
+
+---
+
+## F-029 — `PageSpec.preset` is declared but never consumed
+
+### Location
+
+- `src/layout/types.ts:11-17` (`PagePreset`, `PageSpec.preset`)
+
+### What premirror does
+
+`packages/core/src/index.ts:227-229`:
+```ts
+export function pageSpecForPreset(preset: PagePreset): PageSpec {
+ return preset === "a4" ? { ...A4_PAGE_PX } : { ...LETTER_PAGE_PX };
+}
+```
+
+The preset is a way to resolve `PageSpec` dimensions from a label.
+
+### What plate does
+
+`PageSpec` exposes `preset?: PagePreset`. There is no `pageSpecForPreset`
+helper; the only place that consumes presets is `setPageSize` in
+`BasePaginationPlugin.ts:178-189`, which takes a string `'A4' | 'Letter' | 'Legal'`
+(note: capitalized; different from `PagePreset` which is lowercase
+`'a4' | 'letter'`) and reads a hard-coded `PAGE_SIZES` map.
+
+### Why it's a failure
+
+1. `PagePreset` from `layout/types.ts` is `'a4' | 'letter'`; `setPageSize`
+ takes `'A4' | 'Letter' | 'Legal'`. The two type universes don't
+ communicate, so a caller building a `PageSpec` via the layout types
+ can't pass it through `setPageSize`.
+2. `'Legal'` exists in `PAGE_SIZES` but not in `PagePreset`, so the
+ layout layer doesn't know about it.
+3. The `preset?` field on `PageSpec` is documentation only — never read
+ by the composer or the React layer.
+
+### Fix options
+
+**Option A — Unify the two enums: a single `PagePreset = 'a4' | 'letter'
+| 'legal'`; `setPageSize` takes it directly.**
+
+- Pros: one type; consistent vocabulary.
+- Cons: API break for callers using `'A4'`.
+
+**Option B — Map between the two enums in `setPageSize`.**
+
+- Pros: no API break.
+- Cons: keeps the two-universe confusion; new presets must be added in
+ two places.
+
+**Option C — Drop `preset` from `PageSpec` entirely; force dimensions in
+all consumers.**
+
+- Pros: simplest contract.
+- Cons: loses premirror's preset helper convenience.
+
+### Recommendation seed
+
+Option A; consistency wins.
+
+---
+
+## F-030 — `paginationTf.withMutations` only works via `editor.getTransforms`
+
+### Location
+
+- `src/internal/reflowEngine.ts:379-411`
+ ```ts
+ const paginationTf =
+ editor.getTransforms(BasePaginationPlugin).pagination;
+ withoutSaving(editor, () => {
+ paginationTf.withMutations(() => { ... });
+ });
+ ```
+- `src/BasePaginationPlugin.ts:30-33` (re-exports `withPaginationMutations`)
+
+### What it does
+
+`splitOversizedBlock` calls the transform via
+`editor.getTransforms(...).pagination.withMutations`. But there's also a
+direct export `_withPaginationMutations` (from
+`internal/editorRegistry.ts`) that's used by `toggleHeader` /
+`toggleFooter`. They're the same function under the hood but exposed
+through two paths.
+
+### Why it's a failure
+
+1. Two paths to the same primitive multiply the chance of forgetting one
+ in a future refactor. If `_withPaginationMutations` gains behavior
+ (e.g., op tagging from F-008 Option C), the `editor.getTransforms`
+ path won't pick it up unless explicitly forwarded.
+2. The direct export crosses the "internal/" boundary; the file is named
+ `internal/editorRegistry.ts` but its function is re-exported from
+ the public `BasePaginationPlugin.ts`.
+3. `editor.getTransforms` is an undocumented path for accessing transforms
+ outside of `tf.*`; relying on it ties the package to current Plate
+ internals.
+
+### Fix options
+
+**Option A — Pick one entrypoint (`editor.tf.pagination.withMutations`) and
+remove the other.**
+
+- Pros: single API surface.
+- Cons: requires migrating call sites; consumers using the direct export
+ must switch.
+
+**Option B — Make `_withPaginationMutations` the canonical implementation
+and have the transform delegate to it (which it already does).**
+
+- Pros: matches the current code; documents the relationship.
+- Cons: doesn't reduce surface area.
+
+**Option C — Move `_withPaginationMutations` out of the public barrel.**
+
+- Pros: enforces the "internal" naming.
+- Cons: the React coordinator currently imports it directly via
+ `BasePaginationPlugin.ts`, so the import chain needs reshuffling.
+
+### Recommendation seed
+
+Option A; one public API per behavior.
+
+---
+
+## Triage Summary
+
+| ID | Severity | Blocks shipping a faithful translation | Cheapest mitigation |
+| ----- | ---------- | -------------------------------------- | --------------------------------------------------------------- |
+| F-001 | Critical | Yes | None below "delete the mutator" |
+| F-002 | Critical | Yes | At least re-route via Option A |
+| F-003 | Critical | Yes (selection / widows/orphans) | Documented downgrade (Option C) |
+| F-004 | High | Yes (caret API) | Path↔fragment helpers (Option B) |
+| F-005 | Critical | Yes (collab) | Strict leader gate (Option C); not a real fix |
+| F-006 | High | No (perf only) | Composite key (Option A) — 1-line fix |
+| F-007 | High | No | Resolve via tree walk (Option A) |
+| F-008 | Critical | No (correctness) | Wrap reflow in mutation guard (Option A) — 1-line fix |
+| F-009 | High | No | Sort children before scan (Option A) |
+| F-010 | High | No | Drop the splitter (Option C) |
+| F-011 | Medium | No | Remove `widthPx` from contract (Option B) |
+| F-012 | Critical | Yes (collab) | None; requires F-001 |
+| F-013 | Medium | No | Make coordinator opt-in (Option B) |
+| F-014 | Medium | No | Bridge upstream (Option B) |
+| F-015 | High | No | SSR guard at top of `runReflow` (Option B) — 1-line fix |
+| F-016 | Medium | No | Stable-id keying (Option A) |
+| F-017 | Medium | No (process) | Add cross-peer test (Option C) |
+| F-018 | High | Yes (Base/React split) | Inject splitter (Option A) |
+| F-019 | Low | No (perf only) | WeakMap cache (Option B) |
+| F-020 | Medium | No | Data-attribute spacers (Option A) |
+| F-021 | Critical | No (correctness/A11y) | Strip `data-slate-*` (Option A) |
+| F-022 | Low | No | Conditional render (Option B) |
+| F-023 | Medium | Yes (feature parity) | Document the gap (Option B); requires F-003 to fix |
+| F-024 | Medium | No (correctness) | `ceil` (Option A) — 1-line fix |
+| F-025 | High | No | Require stable ids (Option A) |
+| F-026 | Low | No (perf only) | Chrome-as-render-prop (Option A) |
+| F-027 | Low | No | `getBoundingClientRect` (Option B) |
+| F-028 | Low | No (perf only) | Single timer (Option A); fully cured by F-002 |
+| F-029 | Low | No | Unify enums (Option A) |
+| F-030 | Low | No | One entrypoint (Option A) |
+
+### Strategic recommendation
+
+Pursue **F-001 Option A** (pure projection) as the single anchor change.
+Most criticals (F-002, F-005, F-007, F-008 partially, F-010, F-012, F-018,
+F-020, F-021, F-022, F-028) dissolve once the document model stops being
+mutated. The remaining work — F-003 (line-level fidelity), F-023
+(obstacles), F-004 (PM↔layout), F-025 (stable ids) — then becomes
+incremental, additive work on a known-good foundation.
+
+The 1-line fixes (F-006, F-008 Option A, F-015 Option B, F-024 Option A)
+should land immediately regardless of whether the larger F-001 migration
+is approved: they're correctness patches with no architectural risk.
+
+
+
+
diff --git a/packages/pagination/package.json b/packages/pagination/package.json
new file mode 100644
index 0000000000..b2e509d7ee
--- /dev/null
+++ b/packages/pagination/package.json
@@ -0,0 +1,70 @@
+{
+ "name": "@platejs/pagination",
+ "version": "52.2.0",
+ "description": "Pagination plugin for Plate - page-based document layout",
+ "keywords": [
+ "pagination",
+ "pages",
+ "layout",
+ "plate",
+ "plugin",
+ "slate",
+ "editor"
+ ],
+ "homepage": "https://platejs.org",
+ "bugs": {
+ "url": "https://github.com/udecode/plate/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/udecode/plate.git",
+ "directory": "packages/pagination"
+ },
+ "license": "MIT",
+ "sideEffects": false,
+ "exports": {
+ ".": "./dist/index.js",
+ "./react": "./dist/react/index.js",
+ "./package.json": "./package.json"
+ },
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "files": [
+ "dist/**/*"
+ ],
+ "scripts": {
+ "brl": "plate-pkg p:brl",
+ "build": "plate-pkg p:build",
+ "build:watch": "plate-pkg p:build:watch",
+ "clean": "plate-pkg p:clean",
+ "lint": "plate-pkg p:lint",
+ "lint:fix": "plate-pkg p:lint:fix",
+ "test": "plate-pkg p:test",
+ "test:watch": "plate-pkg p:test:watch",
+ "typecheck": "plate-pkg p:typecheck"
+ },
+ "dependencies": {
+ "@chenglou/pretext": "^0.0.6",
+ "@udecode/react-utils": "workspace:*",
+ "react-compiler-runtime": "^1.0.0"
+ },
+ "devDependencies": {
+ "@plate/scripts": "workspace:*",
+ "@platejs/core": "workspace:^",
+ "platejs": "workspace:^",
+ "slate": ">=0.112.0",
+ "slate-react": ">=0.112.0"
+ },
+ "peerDependencies": {
+ "platejs": ">=52.0.0",
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0",
+ "slate": ">=0.112.0",
+ "slate-react": ">=0.112.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "type": "module",
+ "module": "./dist/index.js"
+}
diff --git a/packages/pagination/src/index.ts b/packages/pagination/src/index.ts
new file mode 100644
index 0000000000..5a8889e974
--- /dev/null
+++ b/packages/pagination/src/index.ts
@@ -0,0 +1,7 @@
+/**
+ * @file Automatically generated by barrelsby.
+ */
+
+export * from './layout/index';
+export * from './lib/index';
+export * from './measure/index';
diff --git a/packages/pagination/src/layout/__tests__/compose.spec.ts b/packages/pagination/src/layout/__tests__/compose.spec.ts
new file mode 100644
index 0000000000..374b283477
--- /dev/null
+++ b/packages/pagination/src/layout/__tests__/compose.spec.ts
@@ -0,0 +1,141 @@
+import { composeLayout } from '../compose';
+import type { LayoutInput, MeasuredBlock, MeasuredSnapshot } from '../types';
+
+// A4 @ 96dpi, 1in margins → content frame height = 1123 - 192 = 931px.
+const INPUT: LayoutInput = {
+ page: { widthPx: 794, heightPx: 1123, preset: 'a4' },
+ margins: { topPx: 96, rightPx: 96, bottomPx: 96, leftPx: 96 },
+ policies: { widowLinesMin: 2, orphanLinesMin: 2, keepWithNextEnabled: true },
+};
+const LH = 20;
+
+let nextId = 0;
+function block(
+ heightPx: number,
+ extra: Partial = {}
+): MeasuredBlock {
+ const id = `b${nextId++}`;
+ return {
+ id,
+ path: [nextId],
+ heightPx,
+ lineHeightPx: LH,
+ lineCount: Math.max(1, Math.round(heightPx / LH)),
+ ...extra,
+ };
+}
+function snap(...blocks: MeasuredBlock[]): MeasuredSnapshot {
+ return { blocks };
+}
+
+describe('composeLayout (place-whole / option C)', () => {
+ it('places blocks that fit on a single page, stacked by height', () => {
+ const out = composeLayout(snap(block(100), block(100), block(100)), INPUT);
+ expect(out.pages).toHaveLength(1);
+ const frags = out.pages[0].frames[0].fragments;
+ expect(frags.map((f) => f.y)).toEqual([0, 100, 200]);
+ // every block is one whole fragment.
+ expect(frags.every((f) => f.fragmentIndex === 0)).toBe(true);
+ expect(frags.every((f) => f.lineStart === 0)).toBe(true);
+ expect(out.metrics).toEqual({ pages: 1, blocks: 3 });
+ });
+
+ it('exposes the content frame bounds (page minus margins)', () => {
+ const out = composeLayout(snap(block(100)), INPUT);
+ expect(out.pages[0].frames[0].bounds).toEqual({
+ x: 96,
+ y: 96,
+ width: 794 - 192,
+ height: 931,
+ });
+ });
+
+ it('moves a block whole to the next page when it does not fit the remaining space', () => {
+ const out = composeLayout(snap(block(700), block(400)), INPUT);
+ expect(out.pages).toHaveLength(2);
+ expect(out.pages[0].frames[0].fragments).toHaveLength(1);
+ const p2first = out.pages[1].frames[0].fragments[0];
+ expect(p2first.breakReason).toBe('block_overflow');
+ expect(p2first.y).toBe(0);
+ expect(p2first.heightPx).toBe(400);
+ });
+
+ it('places a block taller than a full page on its own page, accepting overflow', () => {
+ const out = composeLayout(snap(block(2000)), INPUT);
+ expect(out.pages).toHaveLength(1);
+ const frags = out.pages[0].frames[0].fragments;
+ expect(frags).toHaveLength(1);
+ expect(frags[0].heightPx).toBe(2000);
+ expect(frags[0].fragmentIndex).toBe(0);
+ });
+
+ it('respects manual page breaks (breakBefore)', () => {
+ const out = composeLayout(
+ snap(block(100), block(100, { breakBefore: true })),
+ INPUT
+ );
+ expect(out.pages).toHaveLength(2);
+ expect(out.pages[1].frames[0].fragments[0].breakReason).toBe(
+ 'manual_break'
+ );
+ });
+
+ it('keeps a keepWithNext block with the following block', () => {
+ // filler fills most of page 1 → A(100,keepWithNext)+B(100)=200 > remaining.
+ const out = composeLayout(
+ snap(block(800), block(100, { keepWithNext: true }), block(100)),
+ INPUT
+ );
+ expect(out.pages).toHaveLength(2);
+ expect(out.pages[0].frames[0].fragments).toHaveLength(1); // only filler
+ const p2 = out.pages[1].frames[0].fragments;
+ expect(p2).toHaveLength(2); // A + B together
+ expect(p2[0].breakReason).toBe('keep_with_next');
+ });
+
+ it('is deterministic — identical input yields identical output', () => {
+ const mk = () => snap(block(700), block(700), block(2000));
+ nextId = 0;
+ const a = composeLayout(mk(), INPUT);
+ nextId = 0;
+ const b = composeLayout(mk(), INPUT);
+ expect(JSON.stringify(a)).toBe(JSON.stringify(b));
+ });
+
+ it('packs by flowHeightPx (margin-aware) when present, not just text height', () => {
+ // Text height 400 each → 800 ≤ 931 would fit one page. Flow height 600 each
+ // (DOM margins) → 1200 > 931, so the second block must overflow to page 2.
+ nextId = 0;
+ const out = composeLayout(
+ snap(
+ block(400, { flowHeightPx: 600, path: [0] }),
+ block(400, { flowHeightPx: 600, path: [1] })
+ ),
+ INPUT
+ );
+ expect(out.pages).toHaveLength(2);
+ expect(out.mapping.pageOfBlock(1)).toBe(1); // block 1 begins page 2
+ });
+
+ it('falls back to heightPx for packing when flowHeightPx is absent', () => {
+ nextId = 0;
+ const out = composeLayout(snap(block(400), block(400)), INPUT);
+ expect(out.pages).toHaveLength(1);
+ });
+
+ it('emits a single empty page for empty input', () => {
+ const out = composeLayout(snap(), INPUT);
+ expect(out.pages).toHaveLength(1);
+ expect(out.pages[0].frames[0].fragments).toHaveLength(0);
+ });
+
+ it('includes a MappingIndex in LayoutOutput, built during composition', () => {
+ const out = composeLayout(
+ snap(block(700, { path: [0] }), block(400, { path: [1] })),
+ INPUT
+ );
+ // 700 fits page 0; 400 overflows to page 1.
+ expect(out.mapping.pageOfBlock(0)).toBe(0);
+ expect(out.mapping.pageOfBlock(1)).toBe(1);
+ });
+});
diff --git a/packages/pagination/src/layout/__tests__/continuous.spec.ts b/packages/pagination/src/layout/__tests__/continuous.spec.ts
new file mode 100644
index 0000000000..bff404aa17
--- /dev/null
+++ b/packages/pagination/src/layout/__tests__/continuous.spec.ts
@@ -0,0 +1,52 @@
+import { composeLayout } from '../compose';
+import { getContinuousBreaks, getContinuousBreakYs } from '../continuous';
+import type { LayoutInput, MeasuredBlock, MeasuredSnapshot } from '../types';
+
+const INPUT: LayoutInput = {
+ margins: { bottomPx: 96, leftPx: 96, rightPx: 96, topPx: 96 },
+ page: { heightPx: 1123, preset: 'a4', widthPx: 794 },
+ policies: { keepWithNextEnabled: true, orphanLinesMin: 2, widowLinesMin: 2 },
+};
+
+function block(heightPx: number, path: number[]): MeasuredBlock {
+ return { heightPx, id: `b${path[0]}`, lineCount: 1, lineHeightPx: 20, path };
+}
+function snap(...blocks: MeasuredBlock[]): MeasuredSnapshot {
+ return { blocks };
+}
+
+describe('getContinuousBreakYs', () => {
+ it('returns the continuous-flow Y at each interior page boundary', () => {
+ // content height = 931. page0=[700]; 400 doesn't fit (1100>931) → page1=[400,400].
+ const layout = composeLayout(
+ snap(block(700, [0]), block(400, [1]), block(400, [2])),
+ INPUT
+ );
+ // one interior boundary, after the 700px block.
+ expect(getContinuousBreakYs(layout)).toEqual([700]);
+ });
+
+ it('returns no break lines for a single page', () => {
+ const layout = composeLayout(snap(block(100, [0])), INPUT);
+ expect(getContinuousBreakYs(layout)).toEqual([]);
+ });
+});
+
+describe('getContinuousBreaks', () => {
+ it('points each interior break at the block that begins the next page', () => {
+ // page0=[b0]; b1 doesn't fit (700+400 > 931) → page1=[b1,b2].
+ const layout = composeLayout(
+ snap(block(700, [0]), block(400, [1]), block(400, [2])),
+ INPUT
+ );
+ // one interior boundary: the next page begins at whole block index 1.
+ expect(getContinuousBreaks(layout)).toEqual([
+ { blockIndex: 1, lineStart: 0 },
+ ]);
+ });
+
+ it('returns no breaks for a single page', () => {
+ const layout = composeLayout(snap(block(100, [0])), INPUT);
+ expect(getContinuousBreaks(layout)).toEqual([]);
+ });
+});
diff --git a/packages/pagination/src/layout/__tests__/mapping.spec.ts b/packages/pagination/src/layout/__tests__/mapping.spec.ts
new file mode 100644
index 0000000000..0e3e51fa7c
--- /dev/null
+++ b/packages/pagination/src/layout/__tests__/mapping.spec.ts
@@ -0,0 +1,69 @@
+import { buildMappingIndex } from '../mapping';
+import type { BlockFragment, PageLayout } from '../types';
+
+const spec = { widthPx: 794, heightPx: 1123, preset: 'a4' as const };
+const bounds = { x: 96, y: 96, width: 602, height: 931 };
+
+function frag(
+ path: number[],
+ fragmentIndex: number,
+ lineStart: number,
+ lineCount: number
+): BlockFragment {
+ return {
+ blockId: `b${path[0]}`,
+ fragmentIndex,
+ heightPx: lineCount * 20,
+ lineCount,
+ lineStart,
+ path,
+ y: 0,
+ };
+}
+function page(index: number, fragments: BlockFragment[]): PageLayout {
+ return { frames: [{ bounds, fragments }], index, spec };
+}
+
+// Block 0: whole on page 0. Block 1: split across pages 0,1,2 (lines 0-9,10-19,20-24).
+const pages: PageLayout[] = [
+ page(0, [frag([0], 0, 0, 5), frag([1], 0, 0, 10)]),
+ page(1, [frag([1], 1, 10, 10)]),
+ page(2, [frag([1], 2, 20, 5)]),
+];
+
+describe('buildMappingIndex', () => {
+ it('returns all fragments of a block across pages, in order', () => {
+ const idx = buildMappingIndex(pages);
+ const refs = idx.fragmentsOfBlock(1);
+ expect(refs.map((r) => r.pageIndex)).toEqual([0, 1, 2]);
+ expect(refs.map((r) => r.fragment.lineStart)).toEqual([0, 10, 20]);
+ });
+
+ it('pageOfBlock returns the first fragment page', () => {
+ const idx = buildMappingIndex(pages);
+ expect(idx.pageOfBlock(0)).toBe(0);
+ expect(idx.pageOfBlock(1)).toBe(0);
+ expect(idx.pageOfBlock(99)).toBeNull();
+ });
+
+ it('maps a block line to its containing page (caret mapping)', () => {
+ const idx = buildMappingIndex(pages);
+ expect(idx.pageOfBlockLine(1, 3)).toBe(0); // line 3 → first fragment
+ expect(idx.pageOfBlockLine(1, 12)).toBe(1); // line 12 → second fragment
+ expect(idx.pageOfBlockLine(1, 24)).toBe(2); // last line → third fragment
+ expect(idx.pageOfBlockLine(1, 999)).toBeNull(); // out of range
+ });
+
+ it('fragmentOfBlockLine returns the fragment ref containing the line', () => {
+ const idx = buildMappingIndex(pages);
+ const ref = idx.fragmentOfBlockLine(1, 12);
+ expect(ref?.pageIndex).toBe(1);
+ expect(ref?.fragment.fragmentIndex).toBe(1);
+ });
+
+ it('isSplit reports blocks spanning multiple pages', () => {
+ const idx = buildMappingIndex(pages);
+ expect(idx.isSplit(0)).toBe(false);
+ expect(idx.isSplit(1)).toBe(true);
+ });
+});
diff --git a/packages/pagination/src/layout/__tests__/projection.spec.ts b/packages/pagination/src/layout/__tests__/projection.spec.ts
new file mode 100644
index 0000000000..9a60a68fd0
--- /dev/null
+++ b/packages/pagination/src/layout/__tests__/projection.spec.ts
@@ -0,0 +1,86 @@
+import { getPageGeometry } from '../../react/geometry';
+import { buildMappingIndex } from '../mapping';
+import { blockLinePosition, fragmentRects } from '../projection';
+import type { BlockFragment, LayoutOutput, PageLayout } from '../types';
+
+const spec = { widthPx: 794, heightPx: 1123, preset: 'a4' as const };
+const bounds = { x: 96, y: 96, width: 602, height: 931 };
+
+function frag(
+ path: number[],
+ parts: {
+ fragmentIndex: number;
+ lineStart: number;
+ lineCount: number;
+ y: number;
+ }
+): BlockFragment {
+ const { fragmentIndex, lineCount, lineStart, y } = parts;
+
+ return {
+ blockId: `b${path[0]}`,
+ fragmentIndex,
+ heightPx: lineCount * 20,
+ lineCount,
+ lineStart,
+ path,
+ y,
+ };
+}
+function page(index: number, fragments: BlockFragment[]): PageLayout {
+ return { frames: [{ bounds, fragments }], index, spec };
+}
+const layoutPages: PageLayout[] = [
+ page(0, [
+ frag([0], { fragmentIndex: 0, lineStart: 0, lineCount: 5, y: 0 }),
+ frag([1], { fragmentIndex: 0, lineStart: 0, lineCount: 40, y: 100 }),
+ ]),
+ page(1, [frag([1], { fragmentIndex: 1, lineStart: 40, lineCount: 6, y: 0 })]),
+];
+const layout: LayoutOutput = {
+ mapping: buildMappingIndex(layoutPages),
+ metrics: { blocks: 2, pages: 2 },
+ pages: layoutPages,
+};
+const geo = getPageGeometry(layout, 24);
+
+describe('fragmentRects', () => {
+ it('returns absolute stack rects for each fragment of a split block', () => {
+ const rects = fragmentRects(layout, geo, 1);
+ expect(rects).toHaveLength(2);
+ // fragment 0: page0.top(0) + frame.y(96) + frag.y(100) = 196
+ expect(rects[0]).toMatchObject({
+ pageIndex: 0,
+ top: 196,
+ left: 96,
+ width: 602,
+ });
+ // fragment 1: page1.top(1147) + 96 + 0 = 1243
+ expect(rects[1]).toMatchObject({ pageIndex: 1, top: 1147 + 96 });
+ });
+});
+
+describe('blockLinePosition', () => {
+ it('maps a block line to its absolute stack position', () => {
+ // block 1, line 2 (in fragment 0, lineStart 0): top = 196 + (2-0)*20 = 236
+ expect(
+ blockLinePosition(layout, geo, 1, { lineIndex: 2, lineHeightPx: 20 })
+ ).toMatchObject({
+ pageIndex: 0,
+ top: 236,
+ });
+ // block 1, line 42 (in fragment 1, lineStart 40): top = 1243 + (42-40)*20 = 1283
+ expect(
+ blockLinePosition(layout, geo, 1, { lineIndex: 42, lineHeightPx: 20 })
+ ).toMatchObject({
+ pageIndex: 1,
+ top: 1147 + 96 + 40,
+ });
+ });
+
+ it('returns null for an out-of-range line', () => {
+ expect(
+ blockLinePosition(layout, geo, 1, { lineIndex: 999, lineHeightPx: 20 })
+ ).toBeNull();
+ });
+});
diff --git a/packages/pagination/src/layout/__tests__/snapshot.spec.ts b/packages/pagination/src/layout/__tests__/snapshot.spec.ts
new file mode 100644
index 0000000000..7608384502
--- /dev/null
+++ b/packages/pagination/src/layout/__tests__/snapshot.spec.ts
@@ -0,0 +1,69 @@
+import { buildSnapshot } from '../snapshot';
+
+const p = (text: string, extra: Record = {}) => ({
+ children: [{ text }],
+ type: 'p',
+ ...extra,
+});
+
+describe('buildSnapshot', () => {
+ it('maps each top-level node to a block with its path', () => {
+ const snap = buildSnapshot([p('a'), p('b'), p('c')], {});
+ expect(snap.blocks.map((b) => b.path)).toEqual([[0], [1], [2]]);
+ expect(snap.blocks.map((b) => b.type)).toEqual(['p', 'p', 'p']);
+ });
+
+ it('carries the concatenated text of each block (for line measurement)', () => {
+ const snap = buildSnapshot(
+ [
+ { children: [{ text: 'Hello ' }, { text: 'world' }], type: 'p' },
+ p('second'),
+ ],
+ {}
+ );
+ expect(snap.blocks[0].text).toBe('Hello world');
+ expect(snap.blocks[1].text).toBe('second');
+ });
+
+ it('uses node.id as the stable id when present', () => {
+ const snap = buildSnapshot([p('a', { id: 'fixed-1' })], {});
+ expect(snap.blocks[0].id).toBe('fixed-1');
+ });
+
+ it('derives a stable, content-based id when node.id is absent', () => {
+ const a = buildSnapshot([p('hello')], {}).blocks[0].id;
+ const b = buildSnapshot([p('hello')], {}).blocks[0].id;
+ const c = buildSnapshot([p('different')], {}).blocks[0].id;
+ expect(a).toBe(b); // same content → same id (deterministic, cache-friendly)
+ expect(a).not.toBe(c); // different content → different id
+ });
+
+ it('marks atomic types as non-splittable', () => {
+ const snap = buildSnapshot(
+ [p('x'), { children: [{ text: '' }], type: 'img' }],
+ { atomicTypes: ['img'] }
+ );
+ expect(snap.blocks[0].splittable).toBeUndefined(); // p → splittable (default)
+ expect(snap.blocks[1].splittable).toBe(false);
+ });
+
+ it('marks keepWithNext from type or node attr', () => {
+ const snap = buildSnapshot(
+ [
+ { children: [{ text: 'Heading' }], type: 'h1' },
+ p('x', { keepWithNext: true }),
+ p('y'),
+ ],
+ { keepWithNextTypes: ['h1'] }
+ );
+ expect(snap.blocks[0].keepWithNext).toBe(true); // by type
+ expect(snap.blocks[1].keepWithNext).toBe(true); // by attr
+ expect(snap.blocks[2].keepWithNext).toBeUndefined();
+ });
+
+ it('reads breakBefore from the node attr', () => {
+ const snap = buildSnapshot([p('a'), p('b', { breakBefore: true })], {});
+ expect(snap.blocks[0].breakBefore).toBeUndefined();
+ expect(snap.blocks[1].breakBefore).toBe(true);
+ });
+});
diff --git a/packages/pagination/src/layout/compose.ts b/packages/pagination/src/layout/compose.ts
new file mode 100644
index 0000000000..4446bbfacb
--- /dev/null
+++ b/packages/pagination/src/layout/compose.ts
@@ -0,0 +1,120 @@
+// ============================================================
+// pagination/layout/compose.ts
+//
+// Pure, deterministic page composition. Given a measured snapshot (block
+// heights + line metrics) and layout input (page/margins/policies), produce a
+// LayoutOutput of pages → frames → block fragments. No DOM, no document
+// mutation — same input always yields identical output.
+//
+// Block-level, place-whole composer (option C): a top-level Slate block is the
+// atomic unit. 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 whole
+// and overflows its page (no mid-block splitting, no clones).
+// ============================================================
+
+import { buildMappingIndex } from './mapping';
+import type {
+ BlockFragment,
+ BreakReason,
+ FrameLayout,
+ LayoutInput,
+ LayoutOutput,
+ MeasuredBlock,
+ MeasuredSnapshot,
+ PageLayout,
+ Rect,
+} from './types';
+
+export function composeLayout(
+ snapshot: MeasuredSnapshot,
+ input: LayoutInput
+): LayoutOutput {
+ const { margins, page, policies } = input;
+ const bounds: Rect = {
+ x: margins.leftPx,
+ y: margins.topPx,
+ width: page.widthPx - margins.leftPx - margins.rightPx,
+ height: page.heightPx - margins.topPx - margins.bottomPx,
+ };
+ const frameHeight = bounds.height;
+
+ const pages: PageLayout[] = [];
+ let fragments: BlockFragment[] = [];
+ let pageIndex = 0;
+ let currentY = 0;
+ let pendingReason: BreakReason | undefined;
+
+ const flushPage = () => {
+ const frame: FrameLayout = { bounds, fragments };
+ pages.push({ frames: [frame], index: pageIndex, spec: page });
+ pageIndex += 1;
+ currentY = 0;
+ fragments = [];
+ };
+ const breakToNewPage = (reason: BreakReason) => {
+ flushPage();
+ pendingReason = reason;
+ };
+ const push = (frag: Omit) => {
+ const breakReason = fragments.length === 0 ? pendingReason : undefined;
+ fragments.push({ ...frag, breakReason });
+ pendingReason = undefined;
+ };
+
+ // Packing uses the rendered flow height (text + DOM box spacing) so the engine
+ // fills a page like the real DOM does. Falls back to text height when the
+ // measurer didn't supply margins. lineCount stays text-only for line mapping.
+ const flowOf = (b: MeasuredBlock) => b.flowHeightPx ?? b.heightPx;
+
+ const placeBlock = (b: MeasuredBlock) => {
+ // Place the block whole. If it doesn't fit the remaining space and we're not
+ // already at the top of a fresh page, move it whole to the next page. A
+ // block taller than a full frame is placed at the top and overflows.
+ const flow = flowOf(b);
+ if (flow > frameHeight - currentY && fragments.length > 0) {
+ breakToNewPage('block_overflow');
+ }
+
+ push({
+ blockId: b.id,
+ fragmentIndex: 0,
+ heightPx: flow,
+ lineCount: b.lineCount,
+ lineStart: 0,
+ path: b.path,
+ y: currentY,
+ });
+ currentY += flow;
+ };
+
+ const blocks = snapshot.blocks;
+ for (let i = 0; i < blocks.length; i++) {
+ const b = blocks[i];
+
+ if (b.breakBefore && fragments.length > 0) breakToNewPage('manual_break');
+
+ if (
+ policies.keepWithNextEnabled &&
+ b.keepWithNext &&
+ i + 1 < blocks.length &&
+ fragments.length > 0
+ ) {
+ const combined = flowOf(b) + flowOf(blocks[i + 1]);
+ const remaining = frameHeight - currentY;
+ if (combined > remaining && combined <= frameHeight) {
+ breakToNewPage('keep_with_next');
+ }
+ }
+
+ placeBlock(b);
+ }
+
+ // Emit the final (or only/empty) page.
+ flushPage();
+
+ return {
+ mapping: buildMappingIndex(pages),
+ metrics: { blocks: blocks.length, pages: pages.length },
+ pages,
+ };
+}
diff --git a/packages/pagination/src/layout/continuous.ts b/packages/pagination/src/layout/continuous.ts
new file mode 100644
index 0000000000..65ae503831
--- /dev/null
+++ b/packages/pagination/src/layout/continuous.ts
@@ -0,0 +1,77 @@
+// ============================================================
+// pagination/layout/continuous.ts
+//
+// Continuous-mode projection: where page boundaries fall in the single, un-spaced
+// editable flow. Used by the continuous view to draw thin advisory break-lines at
+// each page boundary without inserting any gaps (content stays in normal flow).
+// ============================================================
+
+import type { LayoutOutput } from './types';
+
+/** Total measured height of a page's fragments (continuous-flow contribution). */
+function pageHeight(page: LayoutOutput['pages'][number]): number {
+ let sum = 0;
+ for (const frame of page.frames) {
+ for (const fragment of frame.fragments) sum += fragment.heightPx;
+ }
+
+ return sum;
+}
+
+/**
+ * The continuous-flow Y (px, relative to content top) at each interior page
+ * boundary — i.e. the cumulative content height at the end of every page except
+ * the last. N pages produce N-1 break Ys; the document end is not a break.
+ *
+ * This is the pure pretext-flow position. The continuous overlay does not
+ * position against it — DOM box spacing (margins) makes pretext's text-only
+ * cumulative Y drift from the real rendered boundary. Prefer
+ * {@link getContinuousBreaks}, which names the boundary block so the overlay can
+ * anchor to that block's live DOM top.
+ */
+export function getContinuousBreakYs(layout: LayoutOutput): number[] {
+ const breakYs: number[] = [];
+ let cumulative = 0;
+
+ for (let i = 0; i < layout.pages.length - 1; i++) {
+ cumulative += pageHeight(layout.pages[i]);
+ breakYs.push(cumulative);
+ }
+
+ return breakYs;
+}
+
+/** A page boundary expressed as the block (and line) that begins the next page. */
+export type ContinuousBreak = {
+ /** Top-level block index (Slate `path[0]`) that begins the page after this break. */
+ blockIndex: number;
+ /**
+ * First line within that block where the next page starts. `0` is a clean
+ * whole-block boundary; `> 0` means the block is split across the boundary
+ * (line-split mode), and the overlay offsets by `lineStart × lineHeight`.
+ */
+ lineStart: number;
+};
+
+/**
+ * Each interior page boundary, named by the block that begins the next page.
+ *
+ * pretext owns the decision — which block (and line) starts each page comes
+ * straight from composition. The continuous overlay anchors its advisory rule
+ * to that boundary block's live DOM top, so the line always lands on a real
+ * block edge instead of a text-only pixel sum that ignores DOM margins.
+ *
+ * N pages produce N-1 breaks; the document end is not a break.
+ */
+export function getContinuousBreaks(layout: LayoutOutput): ContinuousBreak[] {
+ const breaks: ContinuousBreak[] = [];
+
+ for (let i = 1; i < layout.pages.length; i++) {
+ const first = layout.pages[i].frames[0]?.fragments[0];
+ if (!first) continue;
+
+ breaks.push({ blockIndex: first.path[0], lineStart: first.lineStart });
+ }
+
+ return breaks;
+}
diff --git a/packages/pagination/src/layout/index.ts b/packages/pagination/src/layout/index.ts
new file mode 100644
index 0000000000..8eae61c0d9
--- /dev/null
+++ b/packages/pagination/src/layout/index.ts
@@ -0,0 +1,10 @@
+/**
+ * @file Automatically generated by barrelsby.
+ */
+
+export * from './compose';
+export * from './continuous';
+export * from './mapping';
+export * from './projection';
+export * from './snapshot';
+export * from './types';
diff --git a/packages/pagination/src/layout/mapping.ts b/packages/pagination/src/layout/mapping.ts
new file mode 100644
index 0000000000..1357082aef
--- /dev/null
+++ b/packages/pagination/src/layout/mapping.ts
@@ -0,0 +1,83 @@
+// ============================================================
+// pagination/layout/mapping.ts
+//
+// Bidirectional-ish index over a LayoutOutput: locate which page/fragment a
+// top-level block (or a line within it) lands on. This is the foundation for
+// (a) rendering blocks that split across pages and (b) projecting the caret /
+// selection onto pages.
+// ============================================================
+
+import type { BlockFragment, PageLayout } from './types';
+
+export type FragmentRef = {
+ pageIndex: number;
+ frameIndex: number;
+ fragment: BlockFragment;
+};
+
+export type MappingIndex = {
+ /** All fragments of a top-level block (path[0]), in document/page order. */
+ fragmentsOfBlock: (blockIndex: number) => FragmentRef[];
+ /** Page index of a block's first fragment, or null if absent. */
+ pageOfBlock: (blockIndex: number) => number | null;
+ /** Page index containing a given (0-based) line of a block, or null. */
+ pageOfBlockLine: (blockIndex: number, lineIndex: number) => number | null;
+ /** Fragment ref containing a given line of a block, or null. */
+ fragmentOfBlockLine: (
+ blockIndex: number,
+ lineIndex: number
+ ) => FragmentRef | null;
+ /** Whether the block spans more than one page. */
+ isSplit: (blockIndex: number) => boolean;
+};
+
+export function buildMappingIndex(pages: PageLayout[]): MappingIndex {
+ const byBlock = new Map();
+
+ pages.forEach((page) => {
+ page.frames.forEach((frame, frameIndex) => {
+ for (const fragment of frame.fragments) {
+ const blockIndex = fragment.path[0];
+ const refs = byBlock.get(blockIndex);
+ const ref: FragmentRef = {
+ fragment,
+ frameIndex,
+ pageIndex: page.index,
+ };
+ if (refs) refs.push(ref);
+ else byBlock.set(blockIndex, [ref]);
+ }
+ });
+ });
+
+ const fragmentsOfBlock = (blockIndex: number): FragmentRef[] =>
+ byBlock.get(blockIndex) ?? [];
+
+ const fragmentOfBlockLine = (
+ blockIndex: number,
+ lineIndex: number
+ ): FragmentRef | null => {
+ for (const ref of fragmentsOfBlock(blockIndex)) {
+ const { lineCount, lineStart } = ref.fragment;
+ if (lineIndex >= lineStart && lineIndex < lineStart + lineCount) {
+ return ref;
+ }
+ }
+
+ return null;
+ };
+
+ return {
+ fragmentOfBlockLine,
+ fragmentsOfBlock,
+ isSplit: (blockIndex) => {
+ const refs = fragmentsOfBlock(blockIndex);
+
+ return new Set(refs.map((r) => r.pageIndex)).size > 1;
+ },
+ pageOfBlock: (blockIndex) =>
+ fragmentsOfBlock(blockIndex)[0]?.pageIndex ?? null,
+ pageOfBlockLine: (blockIndex, lineIndex) =>
+ fragmentOfBlockLine(blockIndex, lineIndex)?.pageIndex ?? null,
+ };
+}
diff --git a/packages/pagination/src/layout/projection.ts b/packages/pagination/src/layout/projection.ts
new file mode 100644
index 0000000000..40c616364c
--- /dev/null
+++ b/packages/pagination/src/layout/projection.ts
@@ -0,0 +1,80 @@
+// ============================================================
+// pagination/layout/projection.ts
+//
+// Project layout fragments / caret lines into absolute stack coordinates,
+// using the MappingIndex + page geometry. Pure. Consumed by split-block
+// rendering (fragmentRects) and caret/selection placement (blockLinePosition).
+// ============================================================
+
+import type { PageGeometry } from '../react/geometry';
+import type { LayoutOutput } from './types';
+
+export type FragmentRect = {
+ pageIndex: number;
+ fragmentIndex: number;
+ lineStart: number;
+ lineCount: number;
+ left: number;
+ top: number;
+ width: number;
+ height: number;
+};
+
+export type LinePosition = {
+ pageIndex: number;
+ left: number;
+ top: number;
+};
+
+/** Absolute stack rects for every fragment of a (possibly split) block. */
+export function fragmentRects(
+ layout: LayoutOutput,
+ geometry: PageGeometry,
+ blockIndex: number
+): FragmentRect[] {
+ const rects: FragmentRect[] = [];
+
+ for (const ref of layout.mapping.fragmentsOfBlock(blockIndex)) {
+ const placement = geometry.placements[ref.pageIndex];
+ const frame = layout.pages[ref.pageIndex]?.frames[ref.frameIndex];
+ if (!placement || !frame) continue;
+
+ rects.push({
+ fragmentIndex: ref.fragment.fragmentIndex,
+ height: ref.fragment.heightPx,
+ left: placement.left + frame.bounds.x,
+ lineCount: ref.fragment.lineCount,
+ lineStart: ref.fragment.lineStart,
+ pageIndex: ref.pageIndex,
+ top: placement.top + frame.bounds.y + ref.fragment.y,
+ width: frame.bounds.width,
+ });
+ }
+
+ return rects;
+}
+
+/** Absolute stack position of a given (0-based) line within a block. */
+export function blockLinePosition(
+ layout: LayoutOutput,
+ geometry: PageGeometry,
+ blockIndex: number,
+ line: { lineIndex: number; lineHeightPx: number }
+): LinePosition | null {
+ const ref = layout.mapping.fragmentOfBlockLine(blockIndex, line.lineIndex);
+ if (!ref) return null;
+
+ const placement = geometry.placements[ref.pageIndex];
+ const frame = layout.pages[ref.pageIndex]?.frames[ref.frameIndex];
+ if (!placement || !frame) return null;
+
+ return {
+ left: placement.left + frame.bounds.x,
+ pageIndex: ref.pageIndex,
+ top:
+ placement.top +
+ frame.bounds.y +
+ ref.fragment.y +
+ (line.lineIndex - ref.fragment.lineStart) * line.lineHeightPx,
+ };
+}
diff --git a/packages/pagination/src/layout/snapshot.ts b/packages/pagination/src/layout/snapshot.ts
new file mode 100644
index 0000000000..71bf4a45d6
--- /dev/null
+++ b/packages/pagination/src/layout/snapshot.ts
@@ -0,0 +1,78 @@
+// ============================================================
+// pagination/layout/snapshot.ts
+//
+// Build an UnmeasuredSnapshot (flat list of top-level blocks) from a Slate
+// value. Pure: no DOM, no editor instance. The measurement pass later turns
+// this into a MeasuredSnapshot for the composer.
+// ============================================================
+
+import type { UnmeasuredBlock, UnmeasuredSnapshot } from './types';
+
+export type SnapshotOptions = {
+ /** Block types that must not be split across pages (void/atomic). */
+ atomicTypes?: string[];
+ /** Block types kept on the same page as the following block (e.g. headings). */
+ keepWithNextTypes?: string[];
+};
+
+type SlateNode = {
+ type?: string;
+ id?: unknown;
+ text?: string;
+ children?: SlateNode[];
+ keepWithNext?: unknown;
+ breakBefore?: unknown;
+};
+
+/** Concatenate all text leaves under a node, depth-first. */
+function nodeText(node: SlateNode): string {
+ if (typeof node.text === 'string') return node.text;
+ if (!node.children) return '';
+
+ let out = '';
+ for (const child of node.children) out += nodeText(child);
+
+ return out;
+}
+
+/** Small deterministic string hash (djb2) for content-based ids. */
+function hash(input: string): string {
+ let h = 5381;
+ for (let i = 0; i < input.length; i++) h = (h * 33) ^ input.charCodeAt(i);
+
+ return (h >>> 0).toString(36);
+}
+
+function stableId(node: SlateNode): string {
+ if (typeof node.id === 'string' && node.id.length > 0) return node.id;
+
+ return `${node.type ?? 'node'}#${hash(nodeText(node))}`;
+}
+
+export function buildSnapshot(
+ value: SlateNode[],
+ options: SnapshotOptions
+): UnmeasuredSnapshot {
+ const atomic = new Set(options.atomicTypes ?? []);
+ const keepWithNext = new Set(options.keepWithNextTypes ?? []);
+
+ const blocks: UnmeasuredBlock[] = value.map((node, index) => {
+ const type = node.type ?? 'unknown';
+ const block: UnmeasuredBlock = {
+ id: stableId(node),
+ path: [index],
+ text: nodeText(node),
+ type,
+ };
+
+ if (keepWithNext.has(type) || node.keepWithNext === true) {
+ block.keepWithNext = true;
+ }
+ if (node.breakBefore === true) block.breakBefore = true;
+ if (atomic.has(type)) block.splittable = false;
+
+ return block;
+ });
+
+ return { blocks };
+}
diff --git a/packages/pagination/src/layout/types.ts b/packages/pagination/src/layout/types.ts
new file mode 100644
index 0000000000..4f890ecee7
--- /dev/null
+++ b/packages/pagination/src/layout/types.ts
@@ -0,0 +1,158 @@
+// ============================================================
+// pagination/layout/types.ts
+//
+// The pagination layout contract. Adapted from premirror's deterministic
+// snapshot → measure → compose → LayoutOutput pipeline, flattened to Slate's
+// block granularity (Slate has no runs; a top-level block is the atomic unit).
+//
+// The document model never changes — pages are a derived projection.
+// ============================================================
+
+import type { MappingIndex } from './mapping';
+
+export type PagePreset = 'a4' | 'letter';
+
+export type PageSpec = {
+ widthPx: number;
+ heightPx: number;
+ preset?: PagePreset;
+};
+
+export type PageMargins = {
+ topPx: number;
+ rightPx: number;
+ bottomPx: number;
+ leftPx: number;
+};
+
+/** Pagination break policies (widow/orphan/keep-with-next). */
+export type LayoutPolicies = {
+ /** Min lines kept at the top of a page for a split block. */
+ widowLinesMin: number;
+ /** Min lines kept at the bottom of a page for a split block. */
+ orphanLinesMin: number;
+ /** Keep a block (e.g. heading) with the following block. */
+ keepWithNextEnabled: boolean;
+};
+
+/** Pure inputs to {@link composeLayout}. */
+export type LayoutInput = {
+ page: PageSpec;
+ margins: PageMargins;
+ policies: LayoutPolicies;
+};
+
+/**
+ * A top-level block with its real measured geometry. Built by the measurement
+ * pass from the rendered DOM (height at the page content width), then fed to
+ * the pure composer.
+ */
+export type MeasuredBlock = {
+ /** Stable id (used for caching + fragment grouping). */
+ id: string;
+ /** Slate path of the top-level block (e.g. `[3]`). */
+ path: number[];
+ /** Measured rendered height at the content width, in CSS px. */
+ heightPx: number;
+ /**
+ * Rendered flow height = text height + the block's own vertical box spacing
+ * (margins/padding/border) the DOM adds around it. Used for page *packing*
+ * (which block fits per page) so the engine matches real DOM flow. Falls back
+ * to {@link heightPx} when absent. `heightPx`/`lineCount` stay text-only so
+ * line-level mapping is unaffected.
+ */
+ flowHeightPx?: number;
+ /** Measured line height, in CSS px (>= 1). */
+ lineHeightPx: number;
+ /** Number of text lines (>= 1), derived from height / lineHeight. */
+ lineCount: number;
+ /** Keep this block on the same page as the next block. */
+ keepWithNext?: boolean;
+ /** Force a page break before this block. */
+ breakBefore?: boolean;
+ /**
+ * Whether the block may be split across pages. Atomic blocks (tables,
+ * images, void) are placed whole and pushed to the next page if they don't
+ * fit. Defaults to true.
+ */
+ splittable?: boolean;
+};
+
+export type MeasuredSnapshot = {
+ blocks: MeasuredBlock[];
+};
+
+/**
+ * A top-level block before measurement. Built from the Slate value; carries the
+ * stable id (for measurement caching + fragment grouping), path, type, and
+ * pagination hints derived from the node/type.
+ */
+export type UnmeasuredBlock = {
+ id: string;
+ path: number[];
+ type: string;
+ /** Concatenated text of the block, used for line measurement. */
+ text: string;
+ keepWithNext?: boolean;
+ breakBefore?: boolean;
+ splittable?: boolean;
+};
+
+export type UnmeasuredSnapshot = {
+ blocks: UnmeasuredBlock[];
+};
+
+export type BreakReason =
+ | 'block_overflow'
+ | 'manual_break'
+ | 'keep_with_next'
+ | 'widow_orphan';
+
+export type Rect = { x: number; y: number; width: number; height: number };
+
+/**
+ * A placed (sub)range of a block on a single page. A block that spans pages
+ * produces multiple fragments sharing `blockId` with distinct `fragmentIndex`.
+ */
+export type BlockFragment = {
+ blockId: string;
+ path: number[];
+ fragmentIndex: number;
+ /** First line index of this fragment within the whole block. */
+ lineStart: number;
+ /** Number of lines in this fragment. */
+ lineCount: number;
+ /** Frame-relative top, in CSS px. */
+ y: number;
+ /** Fragment height, in CSS px. */
+ heightPx: number;
+ /** Set on the FIRST fragment of a page when the page break was non-trivial. */
+ breakReason?: BreakReason;
+};
+
+export type FrameLayout = {
+ /** Page-relative content rectangle (page minus margins). */
+ bounds: Rect;
+ fragments: BlockFragment[];
+};
+
+export type PageLayout = {
+ index: number;
+ spec: PageSpec;
+ frames: FrameLayout[];
+};
+
+export type ComposeMetrics = {
+ pages: number;
+ blocks: number;
+};
+
+export type LayoutOutput = {
+ pages: PageLayout[];
+ metrics: ComposeMetrics;
+ /**
+ * Position index over {@link pages}, built once during composition. Consumers
+ * (projection, selection) read this instead of rebuilding it per call.
+ */
+ mapping: MappingIndex;
+};
diff --git a/packages/pagination/src/lib/BasePaginationPlugin.ts b/packages/pagination/src/lib/BasePaginationPlugin.ts
new file mode 100644
index 0000000000..23820443c2
--- /dev/null
+++ b/packages/pagination/src/lib/BasePaginationPlugin.ts
@@ -0,0 +1,69 @@
+// ============================================================
+// pagination/lib/BasePaginationPlugin.ts
+//
+// Slate-first base plugin. Owns the pagination options (page geometry, margins,
+// break policies, view mode, atomic/keep-with-next block types) and wires the
+// editor.apply override that marks the per-editor layout registry dirty on
+// content edits. Measurement + rendering live in the React layer (toPlatePlugin).
+//
+// The document model is never mutated here — pagination is a derived overlay.
+// ============================================================
+
+import type { PluginConfig } from 'platejs';
+
+import { createTSlatePlugin } from 'platejs';
+
+import type { LayoutPolicies, PageMargins, PageSpec } from '../layout/types';
+import { invalidateLayoutRegistry, shouldInvalidateLayout } from './registry';
+
+/** How pages are presented while editing. Print authority is the static path. */
+export type PaginationViewMode = 'continuous' | 'paged';
+
+export type PaginationOptions = {
+ /**
+ * Whether pagination is active. When `false`, the React layer skips layout
+ * recompute and renders no page-break overlay, leaving the editor untouched;
+ * the document is never mutated either way. Toggle at runtime with
+ * `editor.setOption(BasePaginationPlugin, 'enabled', next)`.
+ *
+ * @default true
+ */
+ enabled: boolean;
+ page: PageSpec;
+ margins: PageMargins;
+ policies: LayoutPolicies;
+ viewMode: PaginationViewMode;
+ /** Block types placed whole, never split (tables, images, void). */
+ atomicTypes: string[];
+ /** Block types kept on the same page as the next block (e.g. headings). */
+ keepWithNextTypes: string[];
+};
+
+export type PaginationConfig = PluginConfig<'pagination', PaginationOptions>;
+
+// A4 @ 96dpi with 1in margins; widow/orphan = 2 lines; continuous by default
+// (cheapest, fully native edit surface — paged is the high-fidelity opt-in).
+const DEFAULT_OPTIONS: PaginationOptions = {
+ atomicTypes: [],
+ enabled: true,
+ keepWithNextTypes: [],
+ margins: { bottomPx: 96, leftPx: 96, rightPx: 96, topPx: 96 },
+ page: { heightPx: 1123, preset: 'a4', widthPx: 794 },
+ policies: { keepWithNextEnabled: true, orphanLinesMin: 2, widowLinesMin: 2 },
+ viewMode: 'continuous',
+};
+
+export const BasePaginationPlugin = createTSlatePlugin({
+ key: 'pagination',
+ options: DEFAULT_OPTIONS,
+}).overrideEditor(({ editor, tf: { apply } }) => ({
+ transforms: {
+ apply(operation) {
+ if (shouldInvalidateLayout(operation)) {
+ invalidateLayoutRegistry(editor);
+ }
+
+ apply(operation);
+ },
+ },
+}));
diff --git a/packages/pagination/src/lib/__tests__/BasePaginationPlugin.spec.ts b/packages/pagination/src/lib/__tests__/BasePaginationPlugin.spec.ts
new file mode 100644
index 0000000000..0b6e3eb6b4
--- /dev/null
+++ b/packages/pagination/src/lib/__tests__/BasePaginationPlugin.spec.ts
@@ -0,0 +1,60 @@
+import { createSlateEditor } from 'platejs';
+
+import { BasePaginationPlugin } from '../BasePaginationPlugin';
+import { getLayoutRegistry } from '../registry';
+
+function editorWithPagination() {
+ return createSlateEditor({
+ plugins: [BasePaginationPlugin],
+ value: [{ children: [{ text: 'hello' }], type: 'p' }],
+ });
+}
+
+describe('BasePaginationPlugin', () => {
+ it('enables pagination by default', () => {
+ const editor = editorWithPagination();
+
+ expect(editor.getOptions(BasePaginationPlugin).enabled).toBe(true);
+ });
+
+ it('can be disabled via options', () => {
+ const editor = createSlateEditor({
+ plugins: [
+ BasePaginationPlugin.configure({ options: { enabled: false } }),
+ ],
+ value: [{ children: [{ text: 'hello' }], type: 'p' }],
+ });
+
+ expect(editor.getOptions(BasePaginationPlugin).enabled).toBe(false);
+ });
+
+ it('invalidates the layout registry on a content edit', () => {
+ const editor = editorWithPagination();
+ getLayoutRegistry(editor).dirty = false; // simulate a fresh build
+
+ editor.tf.apply({
+ offset: 0,
+ path: [0, 0],
+ text: 'x',
+ type: 'insert_text',
+ });
+
+ expect(getLayoutRegistry(editor).dirty).toBe(true);
+ });
+
+ it('does not invalidate the layout registry on a selection-only change', () => {
+ const editor = editorWithPagination();
+ getLayoutRegistry(editor).dirty = false;
+
+ editor.tf.apply({
+ newProperties: {
+ anchor: { offset: 1, path: [0, 0] },
+ focus: { offset: 1, path: [0, 0] },
+ },
+ properties: null,
+ type: 'set_selection',
+ });
+
+ expect(getLayoutRegistry(editor).dirty).toBe(false);
+ });
+});
diff --git a/packages/pagination/src/lib/__tests__/registry.spec.ts b/packages/pagination/src/lib/__tests__/registry.spec.ts
new file mode 100644
index 0000000000..9168ff6e75
--- /dev/null
+++ b/packages/pagination/src/lib/__tests__/registry.spec.ts
@@ -0,0 +1,56 @@
+import { createSlateEditor } from 'platejs';
+
+import type { LayoutOutput } from '../../layout/types';
+import {
+ ensureLayout,
+ getLayoutRegistry,
+ invalidateLayoutRegistry,
+ shouldInvalidateLayout,
+} from '../registry';
+
+const FAKE_LAYOUT = {
+ mapping: {} as LayoutOutput['mapping'],
+ metrics: { blocks: 0, pages: 1 },
+ pages: [],
+} satisfies LayoutOutput;
+
+describe('layout registry', () => {
+ it('starts dirty so the first read triggers a build', () => {
+ const editor = createSlateEditor();
+ expect(getLayoutRegistry(editor).dirty).toBe(true);
+ });
+
+ it('builds once on read, serves cached, rebuilds after invalidation', () => {
+ const editor = createSlateEditor();
+ let builds = 0;
+ const compute = () => {
+ builds++;
+
+ return FAKE_LAYOUT;
+ };
+
+ ensureLayout(editor, compute); // dirty → build
+ ensureLayout(editor, compute); // clean → cached
+ expect(builds).toBe(1);
+
+ invalidateLayoutRegistry(editor);
+ ensureLayout(editor, compute); // dirty again → rebuild
+ expect(builds).toBe(2);
+ });
+
+ it('treats content operations as invalidating but selection as not', () => {
+ for (const type of [
+ 'insert_text',
+ 'remove_text',
+ 'insert_node',
+ 'remove_node',
+ 'split_node',
+ 'merge_node',
+ 'move_node',
+ 'set_node',
+ ]) {
+ expect(shouldInvalidateLayout({ type })).toBe(true);
+ }
+ expect(shouldInvalidateLayout({ type: 'set_selection' })).toBe(false);
+ });
+});
diff --git a/packages/pagination/src/lib/index.ts b/packages/pagination/src/lib/index.ts
new file mode 100644
index 0000000000..28c79d404e
--- /dev/null
+++ b/packages/pagination/src/lib/index.ts
@@ -0,0 +1,6 @@
+/**
+ * @file Automatically generated by barrelsby.
+ */
+
+export * from './BasePaginationPlugin';
+export * from './registry';
diff --git a/packages/pagination/src/lib/registry.ts b/packages/pagination/src/lib/registry.ts
new file mode 100644
index 0000000000..bf14b86473
--- /dev/null
+++ b/packages/pagination/src/lib/registry.ts
@@ -0,0 +1,86 @@
+// ============================================================
+// pagination/lib/registry.ts
+//
+// Per-editor derived-layout registry. The composed LayoutOutput is per-client
+// state (never replicated, yjs-safe), held in a WeakMap keyed by the editor and
+// rebuilt lazily: content edits mark it dirty, the next read recomputes.
+//
+// Mirrors the footnote registry pattern (packages/footnote/src/lib/registry.ts):
+// WeakMap + dirty flag + lazy rebuild. The `editor.apply` override that calls
+// invalidateLayoutRegistry on content ops lives in the plugin layer; this module
+// stays free of plugin wiring so it is unit-testable on its own.
+// ============================================================
+
+import type { SlateEditor } from 'platejs';
+
+import type { MeasureCache } from '../measure/measure';
+import type { LayoutOutput } from '../layout/types';
+
+export type LayoutRegistryEntry = {
+ /** Last composed layout, or null when never built / invalidated. */
+ output: LayoutOutput | null;
+ /** Whether {@link output} is stale and must be rebuilt on next read. */
+ dirty: boolean;
+ /** Measurement cache reused across rebuilds (keyed by block id + width). */
+ measureCache: MeasureCache;
+};
+
+const LAYOUT_REGISTRY = new WeakMap();
+
+/** Get (lazily creating) the editor's layout registry entry. Starts dirty. */
+export function getLayoutRegistry(editor: SlateEditor): LayoutRegistryEntry {
+ let entry = LAYOUT_REGISTRY.get(editor);
+
+ if (!entry) {
+ entry = { dirty: true, measureCache: new Map(), output: null };
+ LAYOUT_REGISTRY.set(editor, entry);
+ }
+
+ return entry;
+}
+
+/** Mark the editor's layout stale so the next read rebuilds it. */
+export function invalidateLayoutRegistry(editor: SlateEditor): void {
+ const entry = getLayoutRegistry(editor);
+ entry.dirty = true;
+ entry.output = null;
+}
+
+/**
+ * Return the editor's layout, rebuilding via `compute` only when dirty. The
+ * pipeline (snapshot→measure→compose) is injected so this stays pure and
+ * testable; the plugin supplies the real `compute`.
+ */
+export function ensureLayout(
+ editor: SlateEditor,
+ compute: () => LayoutOutput
+): LayoutOutput {
+ const entry = getLayoutRegistry(editor);
+
+ if (entry.dirty || !entry.output) {
+ entry.output = compute();
+ entry.dirty = false;
+ }
+
+ return entry.output;
+}
+
+/** Slate operation types that change content (and thus invalidate layout). */
+const CONTENT_OPS = new Set([
+ 'insert_node',
+ 'insert_text',
+ 'merge_node',
+ 'move_node',
+ 'remove_node',
+ 'remove_text',
+ 'set_node',
+ 'split_node',
+]);
+
+/**
+ * Whether a Slate operation changes content and so invalidates the layout.
+ * Selection-only operations (`set_selection`) do not.
+ */
+export function shouldInvalidateLayout(operation: { type: string }): boolean {
+ return CONTENT_OPS.has(operation.type);
+}
diff --git a/packages/pagination/src/measure/__tests__/measure.spec.ts b/packages/pagination/src/measure/__tests__/measure.spec.ts
new file mode 100644
index 0000000000..f5461bb055
--- /dev/null
+++ b/packages/pagination/src/measure/__tests__/measure.spec.ts
@@ -0,0 +1,107 @@
+import type { UnmeasuredBlock, UnmeasuredSnapshot } from '../../layout/types';
+import { type BlockMetrics, measureSnapshot } from '../measure';
+
+const ub = (
+ id: string,
+ extra: Partial = {}
+): UnmeasuredBlock => ({
+ id,
+ path: [0],
+ text: '',
+ type: 'p',
+ ...extra,
+});
+const snap = (...blocks: UnmeasuredBlock[]): UnmeasuredSnapshot => ({ blocks });
+
+describe('measureSnapshot', () => {
+ it('measures each block and derives lineCount', () => {
+ const measure = () => ({ heightPx: 100, lineHeightPx: 20 });
+ const out = measureSnapshot(snap(ub('a'), ub('b')), measure, {
+ widthPx: 600,
+ });
+ expect(out.blocks).toHaveLength(2);
+ expect(out.blocks[0]).toMatchObject({
+ id: 'a',
+ heightPx: 100,
+ lineHeightPx: 20,
+ lineCount: 5,
+ });
+ });
+
+ it('rounds lineCount and clamps to at least 1', () => {
+ const measure = () => ({ heightPx: 25, lineHeightPx: 20 });
+ const out = measureSnapshot(snap(ub('a')), measure, { widthPx: 600 });
+ expect(out.blocks[0].lineCount).toBe(1); // round(1.25) = 1
+
+ const measure2 = () => ({ heightPx: 8, lineHeightPx: 20 });
+ const out2 = measureSnapshot(snap(ub('a')), measure2, { widthPx: 600 });
+ expect(out2.blocks[0].lineCount).toBe(1); // clamp
+ });
+
+ it('reuses cached metrics for the same id + width (no re-measure)', () => {
+ const cache = new Map();
+ let calls = 0;
+ const measure = () => {
+ calls += 1;
+ return { heightPx: 100, lineHeightPx: 20 };
+ };
+ measureSnapshot(snap(ub('a')), measure, { cache, widthPx: 600 });
+ measureSnapshot(snap(ub('a')), measure, { cache, widthPx: 600 });
+ expect(calls).toBe(1);
+ });
+
+ it('re-measures when width changes', () => {
+ const cache = new Map();
+ let calls = 0;
+ const measure = () => {
+ calls += 1;
+ return { heightPx: 100, lineHeightPx: 20 };
+ };
+ measureSnapshot(snap(ub('a')), measure, { cache, widthPx: 600 });
+ measureSnapshot(snap(ub('a')), measure, { cache, widthPx: 500 });
+ expect(calls).toBe(2);
+ });
+
+ it('keeps each (id, width) cached when widths alternate (no thrash)', () => {
+ const cache = new Map();
+ let calls = 0;
+ const measure = () => {
+ calls += 1;
+ return { heightPx: 100, lineHeightPx: 20 };
+ };
+ measureSnapshot(snap(ub('a')), measure, { cache, widthPx: 600 });
+ measureSnapshot(snap(ub('a')), measure, { cache, widthPx: 500 });
+ // width 600 was already measured — must be a cache hit, not a re-measure.
+ measureSnapshot(snap(ub('a')), measure, { cache, widthPx: 600 });
+ expect(calls).toBe(2);
+ });
+
+ it('carries over pagination hints from the unmeasured block', () => {
+ const measure = () => ({ heightPx: 100, lineHeightPx: 20 });
+ const out = measureSnapshot(
+ snap(
+ ub('a', { keepWithNext: true, breakBefore: true, splittable: false })
+ ),
+ measure,
+ { widthPx: 600 }
+ );
+ expect(out.blocks[0]).toMatchObject({
+ keepWithNext: true,
+ breakBefore: true,
+ splittable: false,
+ });
+ });
+
+ it('falls back to a default line when measurement returns null', () => {
+ const measure = (): BlockMetrics | null => null;
+ const out = measureSnapshot(snap(ub('a')), measure, {
+ fallbackLineHeightPx: 24,
+ widthPx: 600,
+ });
+ expect(out.blocks[0]).toMatchObject({
+ heightPx: 24,
+ lineHeightPx: 24,
+ lineCount: 1,
+ });
+ });
+});
diff --git a/packages/pagination/src/measure/__tests__/pretext.spec.ts b/packages/pagination/src/measure/__tests__/pretext.spec.ts
new file mode 100644
index 0000000000..1714dee90b
--- /dev/null
+++ b/packages/pagination/src/measure/__tests__/pretext.spec.ts
@@ -0,0 +1,69 @@
+// pretext measures text via a canvas context. In the test runtime there is no
+// DOM/canvas, so we install a deterministic monospace stub (10px per char)
+// BEFORE importing the module under test — this makes line breaking exact and
+// machine-independent for the test, while production uses the real browser canvas.
+class StubOffscreenCanvas {
+ getContext() {
+ return {
+ font: '',
+ measureText: (s: string) => ({ width: s.length * 10 }),
+ };
+ }
+}
+// @ts-expect-error - test-only canvas stub
+globalThis.OffscreenCanvas = StubOffscreenCanvas;
+
+import { measureBlockHeight, measureTextLines } from '../pretext';
+
+describe('measureTextLines', () => {
+ it('keeps text that fits within the width on a single line', () => {
+ // "hi there" = 8 chars * 10px = 80px < 100px width.
+ const lines = measureTextLines('hi there', '16px monospace', 100, 20);
+ expect(lines).toHaveLength(1);
+ expect(lines[0].text).toBe('hi there');
+ });
+
+ it('wraps text wider than the line into word-broken lines', () => {
+ // 10px/char, 100px width → wraps at word boundaries.
+ const lines = measureTextLines(
+ 'alpha beta gamma delta',
+ '16px monospace',
+ 100,
+ 20
+ );
+ expect(lines.map((l) => l.text.trim())).toEqual([
+ 'alpha beta',
+ 'gamma',
+ 'delta',
+ ]);
+ });
+
+ it('exposes an advancing cursor range per line (mapping seed)', () => {
+ const lines = measureTextLines(
+ 'alpha beta gamma delta',
+ '16px monospace',
+ 100,
+ 20
+ );
+ expect(lines[0].start).toEqual({ segmentIndex: 0, graphemeIndex: 0 });
+ // each line's end is at or beyond its start; next line starts at prev end.
+ for (let i = 1; i < lines.length; i++) {
+ expect(lines[i].start.segmentIndex).toBeGreaterThanOrEqual(
+ lines[i - 1].start.segmentIndex
+ );
+ }
+ });
+});
+
+describe('measureBlockHeight', () => {
+ it('is the wrapped line count times the line height', () => {
+ // "alpha beta gamma delta" wraps to 3 lines at 100px → 3 * 20 = 60.
+ expect(
+ measureBlockHeight('alpha beta gamma delta', '16px monospace', 100, 20)
+ ).toBe(60);
+ });
+
+ it('treats empty text as a single line tall', () => {
+ expect(measureBlockHeight('', '16px monospace', 100, 20)).toBe(20);
+ });
+});
diff --git a/packages/pagination/src/measure/index.ts b/packages/pagination/src/measure/index.ts
new file mode 100644
index 0000000000..2ed68d4549
--- /dev/null
+++ b/packages/pagination/src/measure/index.ts
@@ -0,0 +1,6 @@
+/**
+ * @file Automatically generated by barrelsby.
+ */
+
+export * from './measure';
+export * from './pretext';
diff --git a/packages/pagination/src/measure/measure.ts b/packages/pagination/src/measure/measure.ts
new file mode 100644
index 0000000000..644b4418f9
--- /dev/null
+++ b/packages/pagination/src/measure/measure.ts
@@ -0,0 +1,91 @@
+// ============================================================
+// pagination/measure/measure.ts
+//
+// Turn an UnmeasuredSnapshot into a MeasuredSnapshot by measuring each block's
+// rendered height + line height. The actual DOM read is injected (`MeasureFn`)
+// so this assembly + cache layer stays pure and unit-testable; the React layer
+// supplies a DOM-backed measurer (offsetHeight + computed line-height).
+//
+// Caching is keyed by the block's stable content id + the content width, so
+// unchanged blocks are not re-measured (premirror's "measure once, cache by
+// signature" idea).
+// ============================================================
+
+import type {
+ MeasuredBlock,
+ MeasuredSnapshot,
+ UnmeasuredBlock,
+ UnmeasuredSnapshot,
+} from '../layout/types';
+
+export type BlockMetrics = {
+ heightPx: number;
+ lineHeightPx: number;
+ /**
+ * The block's own vertical box spacing (margins + padding + borders) the DOM
+ * adds around its text, in CSS px. Added to text height to form the block's
+ * flow height for page packing. Optional; defaults to 0 (no spacing).
+ */
+ boxSpacingPx?: number;
+};
+
+export type MeasureFn = (block: UnmeasuredBlock) => BlockMetrics | null;
+
+export type MeasureCache = Map;
+
+export type MeasureOptions = {
+ /** Content width the blocks are measured at (part of the cache key). */
+ widthPx: number;
+ /** Persistent cache across calls; pass the same Map to reuse measurements. */
+ cache?: MeasureCache;
+ /** Line height used when measurement is unavailable. Default 20. */
+ fallbackLineHeightPx?: number;
+};
+
+function lineCountFrom(heightPx: number, lineHeightPx: number): number {
+ if (lineHeightPx <= 0) return 1;
+
+ return Math.max(1, Math.round(heightPx / lineHeightPx));
+}
+
+export function measureSnapshot(
+ snapshot: UnmeasuredSnapshot,
+ measure: MeasureFn,
+ options: MeasureOptions
+): MeasuredSnapshot {
+ const { cache, fallbackLineHeightPx = 20, widthPx } = options;
+
+ const blocks: MeasuredBlock[] = snapshot.blocks.map((block) => {
+ // Cache slot is per (block, width): a single id measured at two widths must
+ // keep both, or alternating widths thrash one slot and defeat the cache.
+ const cacheKey = `${block.id}@${widthPx}`;
+ let metrics: BlockMetrics | null = cache?.get(cacheKey) ?? null;
+
+ if (!metrics) {
+ metrics = measure(block);
+ if (metrics && cache) cache.set(cacheKey, metrics);
+ }
+
+ const heightPx = metrics?.heightPx ?? fallbackLineHeightPx;
+ const lineHeightPx = metrics?.lineHeightPx ?? fallbackLineHeightPx;
+
+ const measured: MeasuredBlock = {
+ heightPx,
+ id: block.id,
+ lineCount: lineCountFrom(heightPx, lineHeightPx),
+ lineHeightPx,
+ path: block.path,
+ };
+ // Flow height (for packing) = text height + the block's box spacing. Only set
+ // when the measurer supplied spacing, so the composer falls back cleanly.
+ const boxSpacingPx = metrics?.boxSpacingPx ?? 0;
+ if (boxSpacingPx > 0) measured.flowHeightPx = heightPx + boxSpacingPx;
+ if (block.keepWithNext) measured.keepWithNext = true;
+ if (block.breakBefore) measured.breakBefore = true;
+ if (block.splittable === false) measured.splittable = false;
+
+ return measured;
+ });
+
+ return { blocks };
+}
diff --git a/packages/pagination/src/measure/pretext.ts b/packages/pagination/src/measure/pretext.ts
new file mode 100644
index 0000000000..1d38f658d2
--- /dev/null
+++ b/packages/pagination/src/measure/pretext.ts
@@ -0,0 +1,69 @@
+// ============================================================
+// pagination/measure/pretext.ts
+//
+// Real text line-breaking via @chenglou/pretext. Given a block's text, a CSS
+// font string, and the content width, this returns the wrapped lines with each
+// line's text, measured width, and the segment/grapheme cursor range it spans
+// (the seed for mapping a line back to a Slate offset).
+//
+// pretext measures glyph widths through a canvas 2d context (OffscreenCanvas or
+// a DOM canvas). It therefore requires a browser-like environment at runtime;
+// tests inject a deterministic canvas stub.
+// ============================================================
+
+import { layoutWithLines, prepareWithSegments } from '@chenglou/pretext';
+
+/** A position inside the prepared text (pretext cursor). */
+export type LineCursor = {
+ segmentIndex: number;
+ graphemeIndex: number;
+};
+
+/** One wrapped visual line of a block. */
+export type MeasuredLine = {
+ text: string;
+ widthPx: number;
+ start: LineCursor;
+ end: LineCursor;
+};
+
+/**
+ * Break `text` into the visual lines it wraps to at `widthPx`, measured with
+ * `font`. `lineHeightPx` is the line box height pretext stacks lines by.
+ */
+export function measureTextLines(
+ text: string,
+ font: string,
+ widthPx: number,
+ lineHeightPx: number
+): MeasuredLine[] {
+ const prepared = prepareWithSegments(text, font, { whiteSpace: 'pre-wrap' });
+ const { lines } = layoutWithLines(prepared, widthPx, lineHeightPx);
+
+ return lines.map((line) => ({
+ end: line.end,
+ start: line.start,
+ text: line.text,
+ widthPx: line.width,
+ }));
+}
+
+/**
+ * Block height = wrapped line count × line height. This is the canonical,
+ * pretext-driven block measurement: the layout no longer trusts the DOM box
+ * height, it counts the lines pretext wraps `text` to at `widthPx`. Empty text
+ * is one line tall.
+ */
+export function measureBlockHeight(
+ text: string,
+ font: string,
+ widthPx: number,
+ lineHeightPx: number
+): number {
+ const lineCount = Math.max(
+ 1,
+ measureTextLines(text, font, widthPx, lineHeightPx).length
+ );
+
+ return lineCount * lineHeightPx;
+}
diff --git a/packages/pagination/src/react/PaginationPlugin.tsx b/packages/pagination/src/react/PaginationPlugin.tsx
new file mode 100644
index 0000000000..4e7e82cddb
--- /dev/null
+++ b/packages/pagination/src/react/PaginationPlugin.tsx
@@ -0,0 +1,197 @@
+// ============================================================
+// pagination/react/PaginationPlugin.tsx
+//
+// React lift of BasePaginationPlugin. Runs the pure pipeline
+// (snapshot → pretext measure → compose) against the live editable on content
+// changes, stores the layout in the per-editor registry, and (continuous view)
+// paints thin advisory break-lines at each page boundary. The document model is
+// never mutated — pages are a derived overlay.
+//
+// pretext owns the break decision (which block begins each page). The overlay
+// anchors each advisory rule to that boundary block's live DOM top, so the line
+// always lands on a real block edge — never mid-paragraph — regardless of the
+// margins the DOM flow adds between blocks.
+// ============================================================
+
+import React, { useEffect, useLayoutEffect, useState } from 'react';
+import {
+ type EditableSiblingComponent,
+ toPlatePlugin,
+ useEditorRef,
+ usePluginOption,
+} from 'platejs/react';
+
+import { composeLayout } from '../layout/compose';
+import {
+ type ContinuousBreak,
+ getContinuousBreaks,
+} from '../layout/continuous';
+import { buildSnapshot } from '../layout/snapshot';
+import { measureSnapshot } from '../measure/measure';
+import { BasePaginationPlugin } from '../lib/BasePaginationPlugin';
+import { getLayoutRegistry, invalidateLayoutRegistry } from '../lib/registry';
+import { createDomMeasure, topLevelBlockElements } from './domMeasure';
+
+// Layout effect on the client (run before paint so lines appear with content),
+// plain effect on the server (useLayoutEffect is a no-op + warns during SSR).
+const useIsomorphicLayoutEffect =
+ typeof window === 'undefined' ? useEffect : useLayoutEffect;
+
+/** Shared "Page N of M" chip, in the LEFT margin gutter (left of the content). */
+const labelStyle: React.CSSProperties = {
+ background: 'rgb(241 245 249)',
+ border: '1px solid rgb(203 213 225)',
+ borderRadius: 4,
+ color: 'rgb(71 85 105)',
+ fontSize: 10,
+ lineHeight: '14px',
+ marginRight: 8,
+ padding: '0 5px',
+ position: 'absolute',
+ // Right edge pinned to the content's left edge → the chip sits in the left
+ // margin. The left gutter stays on-screen when a narrow viewport overflows the
+ // page width (unlike the right gutter, which scrolls off).
+ right: '100%',
+ top: -7,
+ whiteSpace: 'nowrap',
+};
+
+/**
+ * Continuous-view overlay: a thin dashed advisory rule at each page boundary,
+ * plus a "Page N of M" chip in the left margin (including a "Page 1 of M" marker
+ * at the top so the first page and the total are always shown). `pointer-events:
+ * none`, so editing/selection stay fully native. Each rule is anchored to the
+ * live DOM top of the block pretext chose to begin the next page. Renders nothing
+ * for a single-page document.
+ */
+const PaginationBreakLines: EditableSiblingComponent = () => {
+ const editor = useEditorRef();
+ const enabled = usePluginOption(PaginationPlugin, 'enabled');
+ const breaks = usePluginOption(PaginationPlugin, 'breaks');
+
+ const editable = editor.api.toDOMNode(editor);
+ if (!enabled || !editable || breaks.length === 0) return null;
+
+ const style = getComputedStyle(editable);
+ const padLeft = Number.parseFloat(style.paddingLeft) || 0;
+ const padRight = Number.parseFloat(style.paddingRight) || 0;
+ const left = editable.offsetLeft + padLeft;
+ const width = Math.max(0, editable.clientWidth - padLeft - padRight);
+
+ // The overlay shares the editable's positioned-ancestor coordinate space, so a
+ // block's top there is `editable.offsetTop + (blockTop − editableTop)`. Using
+ // rects (not the offsetParent chain) keeps this correct through any wrappers
+ // Plate renders between the editable and its blocks.
+ const editableTop = editable.getBoundingClientRect().top;
+ const blocks = topLevelBlockElements(editable);
+ const total = breaks.length + 1;
+ const topOf = (el: HTMLElement) =>
+ editable.offsetTop + (el.getBoundingClientRect().top - editableTop);
+
+ return (
+
+ {blocks[0] && (
+
+
+ {`Page 1 of ${total}`}
+
+
+ )}
+ {breaks.map((brk, i) => {
+ const el = blocks[brk.blockIndex];
+ if (!el) return null;
+
+ // lineStart > 0 (future line-split mode) offsets within the block by the
+ // pretext line count; 0 is a clean whole-block top.
+ const lineHeight =
+ Number.parseFloat(getComputedStyle(el).lineHeight) || 0;
+ const top = topOf(el) + brk.lineStart * lineHeight;
+
+ return (
+
+
+ {`Page ${i + 2} of ${total}`}
+
+
+ );
+ })}
+
+ );
+};
+
+export const PaginationPlugin = toPlatePlugin(BasePaginationPlugin, {
+ options: { breaks: [] as ContinuousBreak[] },
+ render: { afterEditable: PaginationBreakLines },
+ useHooks: ({ editor, setOption }) => {
+ const [, forceRecompute] = useState(0);
+ const enabled = usePluginOption(PaginationPlugin, 'enabled');
+
+ // Recompute when the layout registry is dirty (content edits via the base
+ // plugin's apply override; selection-only changes leave it clean). Runs in a
+ // layout effect — after the DOM commits, before paint — so the advisory lines
+ // paint together with the content the moment the editor hydrates, rather than
+ // an extra frame later. setOption re-renders the overlay via usePluginOption.
+ // (The residual delay on first load is the editor's hydration time: the SSR
+ // content is on screen before the client can measure the DOM to place lines.)
+ // Skipped entirely while disabled; toggling `enabled` re-renders here (the
+ // subscribed option above), so re-enabling recomputes from the dirty registry.
+ useIsomorphicLayoutEffect(() => {
+ if (!enabled) return;
+
+ const registry = getLayoutRegistry(editor);
+ if (!registry.dirty && registry.output) return;
+
+ const editable = editor.api.toDOMNode(editor);
+ if (!editable) return;
+
+ const { atomicTypes, keepWithNextTypes, margins, page, policies } =
+ editor.getOptions(BasePaginationPlugin);
+ const widthPx = page.widthPx - margins.leftPx - margins.rightPx;
+
+ const snapshot = buildSnapshot(editor.children, {
+ atomicTypes,
+ keepWithNextTypes,
+ });
+ const measured = measureSnapshot(snapshot, createDomMeasure(editable), {
+ cache: registry.measureCache,
+ widthPx,
+ });
+ const layout = composeLayout(measured, { margins, page, policies });
+
+ registry.output = layout;
+ registry.dirty = false;
+ setOption('breaks', getContinuousBreaks(layout));
+ });
+
+ // A width change re-wraps text and changes pagination. Always invalidate the
+ // cached layout, but only re-render immediately while the overlay is visible.
+ useEffect(() => {
+ const editable = editor.api.toDOMNode(editor);
+ if (!editable || typeof ResizeObserver === 'undefined') return;
+
+ const observer = new ResizeObserver(() => {
+ invalidateLayoutRegistry(editor);
+
+ if (enabled) {
+ forceRecompute((n) => n + 1);
+ }
+ });
+ observer.observe(editable);
+
+ return () => observer.disconnect();
+ }, [editor, enabled]);
+ },
+});
diff --git a/packages/pagination/src/react/__tests__/domMeasure.spec.ts b/packages/pagination/src/react/__tests__/domMeasure.spec.ts
new file mode 100644
index 0000000000..dfbe47fdc1
--- /dev/null
+++ b/packages/pagination/src/react/__tests__/domMeasure.spec.ts
@@ -0,0 +1,120 @@
+class StubOffscreenCanvas {
+ getContext() {
+ return {
+ font: '',
+ measureText: (s: string) => ({ width: s.length * 10 }),
+ };
+ }
+}
+// @ts-expect-error - test-only canvas stub
+globalThis.OffscreenCanvas = StubOffscreenCanvas;
+
+import { createDomMeasure, topLevelBlockElements } from '../domMeasure';
+
+const block = (index: number, text: string) => ({
+ id: `b${index}`,
+ path: [index],
+ text,
+ type: 'p',
+});
+
+const attach = (editable: HTMLElement) => {
+ document.body.appendChild(editable);
+
+ return editable;
+};
+
+describe('topLevelBlockElements', () => {
+ it('returns direct Slate element children', () => {
+ const editable = document.createElement('div');
+ editable.innerHTML = `
+ Title
+ Body
+ `;
+
+ expect(topLevelBlockElements(editable).map((el) => el.textContent)).toEqual(
+ ['Title', 'Body']
+ );
+ });
+
+ it('returns Slate elements wrapped by block UI chrome', () => {
+ const editable = document.createElement('div');
+ editable.innerHTML = `
+
+
+
drag handle
+
+ Body link
+
+
+ `;
+
+ expect(
+ topLevelBlockElements(editable).map((el) => el.textContent?.trim())
+ ).toEqual(['Title', 'Body link']);
+ });
+});
+
+describe('createDomMeasure', () => {
+ it('returns null when the block DOM cannot be resolved', () => {
+ const editable = document.createElement('div');
+
+ expect(createDomMeasure(editable)(block(0, 'Missing'))).toBeNull();
+ });
+
+ it('measures direct top-level blocks', () => {
+ const editable = document.createElement('div');
+ editable.innerHTML = 'Body text
';
+ const element = editable.firstElementChild as HTMLElement;
+ element.style.cssText = [
+ 'border-bottom: 6px solid black',
+ 'border-top: 5px solid black',
+ 'font-family: sans-serif',
+ 'font-size: 16px',
+ 'line-height: 20px',
+ 'margin-bottom: 2px',
+ 'margin-top: 1px',
+ 'padding-bottom: 4px',
+ 'padding-left: 11px',
+ 'padding-right: 7px',
+ 'padding-top: 3px',
+ ].join(';');
+ Object.defineProperty(element, 'clientWidth', {
+ configurable: true,
+ value: 200,
+ });
+
+ const metrics = createDomMeasure(attach(editable))(block(0, 'Body text'));
+
+ expect(metrics?.lineHeightPx).toBe(20);
+ expect(metrics?.boxSpacingPx).toBe(21);
+ expect(metrics?.heightPx).toBeGreaterThan(0);
+ });
+
+ it('measures blocks nested inside top-level UI wrappers', () => {
+ const editable = document.createElement('div');
+ editable.innerHTML = `
+
+
+ `;
+ attach(editable);
+ const second = topLevelBlockElements(editable)[1];
+ second.style.cssText = 'font-size: 16px; line-height: 24px';
+ Object.defineProperty(second, 'clientWidth', {
+ configurable: true,
+ value: 200,
+ });
+
+ const metrics = createDomMeasure(editable)(block(1, 'Second'));
+
+ expect(metrics?.lineHeightPx).toBe(24);
+ expect(metrics?.heightPx).toBeGreaterThan(0);
+ });
+});
diff --git a/packages/pagination/src/react/__tests__/geometry.spec.ts b/packages/pagination/src/react/__tests__/geometry.spec.ts
new file mode 100644
index 0000000000..66d6bda16e
--- /dev/null
+++ b/packages/pagination/src/react/__tests__/geometry.spec.ts
@@ -0,0 +1,86 @@
+import { buildMappingIndex } from '../../layout/mapping';
+import type { LayoutOutput, PageLayout } from '../../layout/types';
+import { getBlockPlacements, getPageGeometry } from '../geometry';
+
+const spec = { widthPx: 794, heightPx: 1123, preset: 'a4' as const };
+const bounds = { x: 96, y: 96, width: 602, height: 931 };
+
+function page(index: number, fragments: any[]): PageLayout {
+ return { frames: [{ bounds, fragments }], index, spec };
+}
+function out(pages: PageLayout[]): LayoutOutput {
+ return {
+ mapping: buildMappingIndex(pages),
+ metrics: { blocks: 0, pages: pages.length },
+ pages,
+ };
+}
+
+describe('getPageGeometry', () => {
+ it('stacks pages vertically with the gap', () => {
+ const geo = getPageGeometry(out([page(0, []), page(1, [])]), 24);
+ expect(geo.placements.map((p) => p.top)).toEqual([0, 1123 + 24]);
+ expect(geo.width).toBe(794);
+ expect(geo.height).toBe(1123 + 24 + 1123); // gap not counted after last
+ });
+});
+
+describe('getBlockPlacements', () => {
+ it('maps each block to its first fragment top in stack coords', () => {
+ const layout = out([
+ page(0, [
+ {
+ blockId: 'a',
+ path: [0],
+ fragmentIndex: 0,
+ lineStart: 0,
+ lineCount: 1,
+ y: 0,
+ heightPx: 100,
+ },
+ {
+ blockId: 'b',
+ path: [1],
+ fragmentIndex: 0,
+ lineStart: 0,
+ lineCount: 1,
+ y: 100,
+ heightPx: 100,
+ },
+ ]),
+ page(1, [
+ {
+ blockId: 'c',
+ path: [2],
+ fragmentIndex: 0,
+ lineStart: 0,
+ lineCount: 1,
+ y: 0,
+ heightPx: 100,
+ },
+ ]),
+ ]);
+ const geo = getPageGeometry(layout, 24);
+ const placements = getBlockPlacements(layout, geo);
+
+ expect(placements.map((p) => p.blockIndex)).toEqual([0, 1, 2]);
+ // block a: page0 top(0) + frame.y(96) + frag.y(0) = 96
+ expect(placements[0]).toMatchObject({
+ pageIndex: 0,
+ targetTop: 96,
+ startsPage: true,
+ });
+ // block b: 0 + 96 + 100 = 196, not page start
+ expect(placements[1]).toMatchObject({
+ pageIndex: 0,
+ targetTop: 196,
+ startsPage: false,
+ });
+ // block c: page1 top(1147) + 96 + 0 = 1243, page start
+ expect(placements[2]).toMatchObject({
+ pageIndex: 1,
+ targetTop: 1123 + 24 + 96,
+ startsPage: true,
+ });
+ });
+});
diff --git a/packages/pagination/src/react/alignContent.ts b/packages/pagination/src/react/alignContent.ts
new file mode 100644
index 0000000000..06d73d77f1
--- /dev/null
+++ b/packages/pagination/src/react/alignContent.ts
@@ -0,0 +1,69 @@
+// ============================================================
+// pagination/react/alignContent.ts
+//
+// Align a single continuous editable's content to the composed page frames by
+// applying a top-margin "spacer" to each block that starts a page. This is a
+// CSS-only side effect on the live DOM — the document model never changes.
+//
+// The spacer for the first block of page p is the empty space left at the
+// bottom of page p-1's content frame, plus the inter-page non-content space
+// (bottom margin + page gap + top margin), so the block snaps to page p's
+// content-frame top.
+// ============================================================
+
+import type { LayoutInput, LayoutOutput } from '../layout/types';
+import { topLevelBlockElements } from './domMeasure';
+import { PAGE_STACK_GAP_PX } from './geometry';
+
+export function computePageStartSpacers(
+ layout: LayoutOutput,
+ input: LayoutInput,
+ gapPx: number = PAGE_STACK_GAP_PX
+): Map {
+ const contentHeight =
+ input.page.heightPx - input.margins.topPx - input.margins.bottomPx;
+ const spacers = new Map();
+
+ for (const page of layout.pages) {
+ if (page.index === 0) continue;
+
+ const first = page.frames[0].fragments[0];
+ if (!first) continue;
+
+ const prev = layout.pages[page.index - 1].frames[0];
+ const prevBottom = prev.fragments.reduce(
+ (max, f) => Math.max(max, f.y + f.heightPx),
+ 0
+ );
+
+ spacers.set(
+ first.path[0],
+ contentHeight -
+ prevBottom +
+ input.margins.bottomPx +
+ gapPx +
+ input.margins.topPx
+ );
+ }
+
+ return spacers;
+}
+
+/**
+ * Compute + apply page-start spacers to the editable's top-level blocks (CSS
+ * `margin-top` only — no model change). Returns the spacer map.
+ */
+export function alignContentToLayout(
+ editable: HTMLElement,
+ layout: LayoutOutput,
+ input: LayoutInput,
+ gapPx: number = PAGE_STACK_GAP_PX
+): Map {
+ const spacers = computePageStartSpacers(layout, input, gapPx);
+
+ topLevelBlockElements(editable).forEach((el, index) => {
+ el.style.marginTop = spacers.has(index) ? `${spacers.get(index)}px` : '';
+ });
+
+ return spacers;
+}
diff --git a/packages/pagination/src/react/domMeasure.ts b/packages/pagination/src/react/domMeasure.ts
new file mode 100644
index 0000000000..9b64128aee
--- /dev/null
+++ b/packages/pagination/src/react/domMeasure.ts
@@ -0,0 +1,108 @@
+// ============================================================
+// pagination/react/domMeasure.ts
+//
+// DOM-backed MeasureFn for the engine: resolves each top-level block's font +
+// content width from the live editable, then derives height from the number of
+// lines pretext wraps the block text to. Pure DOM (no slate-react / editor
+// dependency) — top-level blocks are the direct `[data-slate-node="element"]`
+// children of the editable, indexed by path[0].
+// ============================================================
+
+import type { MeasureFn } from '../measure/measure';
+import { measureBlockHeight } from '../measure/pretext';
+
+/** Direct top-level block elements of an editable, in document order. */
+export function topLevelBlockElements(editable: HTMLElement): HTMLElement[] {
+ const selector = '[data-slate-node="element"]';
+
+ return Array.from(editable.children).flatMap((child) => {
+ if (child instanceof HTMLElement && child.matches(selector)) {
+ return [child];
+ }
+
+ const nested = child.querySelector(selector);
+
+ return nested instanceof HTMLElement ? [nested] : [];
+ });
+}
+
+function resolveLineHeight(style: CSSStyleDeclaration): number {
+ const lh = Number.parseFloat(style.lineHeight);
+ if (Number.isFinite(lh) && lh > 0) return lh;
+
+ const fontSize = Number.parseFloat(style.fontSize);
+ if (Number.isFinite(fontSize) && fontSize > 0) return fontSize * 1.5;
+
+ return 20;
+}
+
+/** A canvas-compatible font string from computed style (the editor's font). */
+function resolveFont(style: CSSStyleDeclaration): string {
+ if (style.font) return style.font;
+
+ const parts = [
+ style.fontStyle,
+ style.fontWeight,
+ style.fontSize,
+ style.fontFamily,
+ ].filter((p) => p && p !== 'normal');
+
+ return parts.join(' ').trim() || '16px sans-serif';
+}
+
+/** Inner content width (excludes horizontal padding) the text wraps within. */
+function contentWidth(dom: HTMLElement, style: CSSStyleDeclaration): number {
+ const padLeft = Number.parseFloat(style.paddingLeft) || 0;
+ const padRight = Number.parseFloat(style.paddingRight) || 0;
+
+ return Math.max(0, dom.clientWidth - padLeft - padRight);
+}
+
+/**
+ * The block's own vertical box spacing (margins + padding + borders), in px.
+ * pretext measures only the text height; this is the non-text spacing the DOM
+ * flow adds around the block, which the composer adds to form the flow height
+ * used for page packing. Summing top+bottom margins slightly over-counts where
+ * adjacent margins collapse (gap = max, not sum) — conservative and within
+ * advisory tolerance; exact collapse modeling is a later refinement.
+ */
+function verticalBoxSpacing(style: CSSStyleDeclaration): number {
+ const px = (v: string) => Number.parseFloat(v) || 0;
+
+ return (
+ px(style.marginTop) +
+ px(style.marginBottom) +
+ px(style.paddingTop) +
+ px(style.paddingBottom) +
+ px(style.borderTopWidth) +
+ px(style.borderBottomWidth)
+ );
+}
+
+/**
+ * Build a {@link MeasureFn} that resolves the block's font + content width from
+ * the live editable, then derives height from the number of lines pretext wraps
+ * the block text to. Pretext — not the DOM box — owns the line count, so the
+ * layout is line-accurate and the box's padding/margin don't perturb it.
+ * Re-queries on each call so it reflects edits.
+ */
+export function createDomMeasure(editable: HTMLElement): MeasureFn {
+ return (block) => {
+ const dom = topLevelBlockElements(editable)[block.path[0]];
+ if (!dom) return null;
+
+ const style = getComputedStyle(dom);
+ const lineHeightPx = resolveLineHeight(style);
+
+ return {
+ boxSpacingPx: verticalBoxSpacing(style),
+ heightPx: measureBlockHeight(
+ block.text,
+ resolveFont(style),
+ contentWidth(dom, style),
+ lineHeightPx
+ ),
+ lineHeightPx,
+ };
+ };
+}
diff --git a/packages/pagination/src/react/geometry.ts b/packages/pagination/src/react/geometry.ts
new file mode 100644
index 0000000000..d6d5547214
--- /dev/null
+++ b/packages/pagination/src/react/geometry.ts
@@ -0,0 +1,94 @@
+// ============================================================
+// pagination/react/geometry.ts
+//
+// Pure projection of a LayoutOutput into on-screen page placements + per-block
+// target positions. Used by the overlay renderer to draw page chrome and to
+// align the editable content to page frames. No DOM.
+// ============================================================
+
+import type { LayoutOutput } from '../layout/types';
+
+export const PAGE_STACK_GAP_PX = 24;
+
+export type PagePlacement = {
+ index: number;
+ left: number;
+ top: number;
+ width: number;
+ height: number;
+};
+
+export type PageGeometry = {
+ placements: PagePlacement[];
+ /** Total width of the stacked pages (max page width). */
+ width: number;
+ /** Total height of the stacked pages (incl. inter-page gaps). */
+ height: number;
+};
+
+/** Vertically stack pages with a fixed gap (single-column mode). */
+export function getPageGeometry(
+ layout: LayoutOutput,
+ gapPx: number = PAGE_STACK_GAP_PX
+): PageGeometry {
+ const placements: PagePlacement[] = [];
+ let top = 0;
+ let width = 0;
+
+ for (const page of layout.pages) {
+ placements.push({
+ height: page.spec.heightPx,
+ index: page.index,
+ left: 0,
+ top,
+ width: page.spec.widthPx,
+ });
+ top += page.spec.heightPx + gapPx;
+ width = Math.max(width, page.spec.widthPx);
+ }
+
+ return { height: Math.max(0, top - gapPx), placements, width };
+}
+
+export type BlockPlacement = {
+ /** Top-level block index (path[0]). */
+ blockIndex: number;
+ pageIndex: number;
+ /** Absolute top of the block's first fragment, in stack coordinates. */
+ targetTop: number;
+ /** Whether this block starts a page (first fragment on its page). */
+ startsPage: boolean;
+};
+
+/**
+ * For each top-level block, where its first fragment lands in stack coordinates
+ * (page top + content-frame y + fragment y). Drives spacer alignment so each
+ * page-starting block snaps to the next page's content-frame top.
+ */
+export function getBlockPlacements(
+ layout: LayoutOutput,
+ geometry: PageGeometry
+): BlockPlacement[] {
+ const byBlock = new Map();
+
+ for (const page of layout.pages) {
+ const placement = geometry.placements[page.index];
+ if (!placement) continue;
+
+ for (const frame of page.frames) {
+ frame.fragments.forEach((fragment, fragmentPos) => {
+ const blockIndex = fragment.path[0];
+ if (byBlock.has(blockIndex)) return; // keep the first fragment only
+
+ byBlock.set(blockIndex, {
+ blockIndex,
+ pageIndex: page.index,
+ startsPage: fragmentPos === 0,
+ targetTop: placement.top + frame.bounds.y + fragment.y,
+ });
+ });
+ }
+ }
+
+ return [...byBlock.values()].sort((a, b) => a.blockIndex - b.blockIndex);
+}
diff --git a/packages/pagination/src/react/index.ts b/packages/pagination/src/react/index.ts
new file mode 100644
index 0000000000..2406391648
--- /dev/null
+++ b/packages/pagination/src/react/index.ts
@@ -0,0 +1,8 @@
+/**
+ * @file Automatically generated by barrelsby.
+ */
+
+export * from './PaginationPlugin';
+export * from './alignContent';
+export * from './domMeasure';
+export * from './geometry';
diff --git a/packages/pagination/tsconfig.build.json b/packages/pagination/tsconfig.build.json
new file mode 100644
index 0000000000..5db35c04f6
--- /dev/null
+++ b/packages/pagination/tsconfig.build.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../tooling/config/tsconfig.build.json",
+ "compilerOptions": {
+ "outDir": "./dist"
+ },
+ "include": ["src"]
+}
diff --git a/packages/pagination/tsconfig.json b/packages/pagination/tsconfig.json
new file mode 100644
index 0000000000..c80a0bca42
--- /dev/null
+++ b/packages/pagination/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../../tooling/config/tsconfig.base.json",
+ "include": ["src", "../../tooling/config/global.d.ts"]
+}
diff --git a/packages/utils/src/lib/plate-keys.ts b/packages/utils/src/lib/plate-keys.ts
index 68d01a0f6a..e620a3ec9a 100644
--- a/packages/utils/src/lib/plate-keys.ts
+++ b/packages/utils/src/lib/plate-keys.ts
@@ -19,6 +19,7 @@ export const NODES = {
equation: 'equation',
excalidraw: 'excalidraw',
file: 'file',
+ footer: 'footer',
footnoteDefinition: 'footnoteDefinition',
footnoteInput: 'footnoteInput',
footnoteReference: 'footnoteReference',
@@ -28,6 +29,7 @@ export const NODES = {
h4: 'h4',
h5: 'h5',
h6: 'h6',
+ header: 'header',
highlight: 'highlight',
hr: 'hr',
img: 'img',
@@ -43,6 +45,7 @@ export const NODES = {
mentionInput: 'mention_input',
olClassic: 'ol',
p: 'p',
+ pageBreak: 'pageBreak',
searchHighlight: 'search_highlight',
slashInput: 'slash_input',
strikethrough: 'strikethrough',
@@ -105,6 +108,7 @@ export const KEYS = {
nodeId: 'nodeId',
normalizeTypes: 'normalizeTypes',
ol: 'decimal',
+ pagination: 'pagination',
placeholder: 'placeholder',
playwright: 'playwright',
removeEmptyNodes: 'removeEmptyNodes',
diff --git a/plans/2026-05-15-fix-pagination-plugin-tdd-v2.md b/plans/2026-05-15-fix-pagination-plugin-tdd-v2.md
new file mode 100644
index 0000000000..69fc78f1bc
--- /dev/null
+++ b/plans/2026-05-15-fix-pagination-plugin-tdd-v2.md
@@ -0,0 +1,345 @@
+# Fix Pagination Plugin — TDD Implementation Plan (100% Coverage)
+
+## Objective
+
+Transform the `@platejs/pagination` plugin from an untested internal engine with a broken toolbar UX into a production-ready feature with 100% test coverage, hardened reflow engine, and a fully functional toolbar transforms API. **Every implementation step follows TDD: write tests first, then implement to make them pass.**
+
+## Context (From Research)
+
+The pagination plugin has **zero tests** across all 10 source files. The toolbar button in the playground template calls 5 transforms (`togglePreview`, `setPageSize`, `setMargins`, `toggleHeader`, `toggleFooter`) that don't exist — the button falls through to a toast. The reflow engine's text-split path accesses `ReactEditor.toDOMRange` via raw cast with no fallback. Window resize lacks its own debounce layer. Empty-page edge cases are unguarded.
+
+## Test Infrastructure
+
+- **Runner**: Bun (`bun test` from `packages/pagination/`)
+- **DOM**: Happy DOM (registered via `@happy-dom/global-registrator`)
+- **Editor factory**: `createSlateEditor` from `platejs`
+- **JSX test values**: `@platejs/test-utils` (`jsxt`) for concise document structure
+- **File naming**: `*.spec.ts` for pure-logic tests, `*.spec.tsx` for React/DOM tests
+- **Existing pattern reference**: `packages/toc/src/lib/BaseTocPlugin.spec.ts` (transforms), `packages/suggestion/src/lib/transforms/acceptSuggestion.spec.tsx` (editor state)
+
+---
+
+## TDD Cycle 1: Runtime (Dirty Tracking)
+
+**File**: `packages/pagination/src/__tests__/runtime.spec.ts`
+
+### Tests to Write (before any changes)
+
+- [ ] `createPaginationRuntime` returns object with `markDirty`, `consumeDirtyMin`, `subscribe`
+- [ ] `markDirty(pageIndex)` adds page to dirty set and notifies subscribers
+- [ ] `markDirty(null)` / `markDirty(-1)` / `markDirty(NaN)` are no-ops
+- [ ] `consumeDirtyMin()` returns the minimum dirty page index
+- [ ] `consumeDirtyMin()` after multiple `markDirty` calls returns the smallest index
+- [ ] `consumeDirtyMin()` clears the dirty set after consumption
+- [ ] `consumeDirtyMin()` on empty set returns `null`
+- [ ] `subscribe(fn)` returns unsubscribe function; unsubscribed callbacks are not called
+- [ ] `getPageIndexFromOp` extracts index from `set_node` operation path
+- [ ] `getPageIndexFromOp` extracts index from `insert_node` operation path
+- [ ] `getPageIndexFromOp` extracts min index from `move_node` operation (has both `path` and `newPath`)
+- [ ] `getPageIndexFromOp` returns `null` for operation with no paths (e.g., `set_selection`)
+- [ ] `getPageIndexFromOp` handles `merge_node` (has `path`)
+- [ ] `getPageIndexFromOp` handles `split_node` (has `path`)
+
+### Implementation
+
+No changes needed — `runtime.ts` already implements all these correctly. Tests verify existing behavior.
+
+---
+
+## TDD Cycle 2: Registry (DOM Slot Management)
+
+**File**: `packages/pagination/src/__tests__/registry.spec.tsx`
+
+### Tests to Write
+
+- [ ] `PaginationRegistryProvider` renders children
+- [ ] `registerPage(index, dom)` stores DOM refs and returns cleanup function
+- [ ] `getPageDom(index)` returns stored `{ outer, content }` refs
+- [ ] `getPageDom(index)` returns `undefined` for unknown index
+- [ ] `getKnownPages()` returns sorted array of registered page indices
+- [ ] Cleanup function removes the page from registry
+- [ ] Cleanup function does NOT remove a different page that was registered at the same index later (outer ref identity check)
+- [ ] Multiple registrations at different indices are all tracked
+- [ ] `usePaginationRegistry()` returns `null` when used outside provider
+
+### Implementation
+
+No changes needed — `registry.tsx` already implements correctly. Tests verify.
+
+---
+
+## TDD Cycle 3: Leader Election
+
+**File**: `packages/pagination/src/__tests__/leaderElection.spec.ts`
+
+### Tests to Write
+
+- [ ] `createAlwaysLeader().amILeader()` returns `true`
+- [ ] `createAlwaysLeader().subscribe()` returns a no-op unsubscribe
+- [ ] `createAlwaysLeader().destroy()` is a no-op
+- [ ] `createAwarenessLeaderElection(awareness, ydoc)` with single ready client: that client is leader
+- [ ] Multiple ready clients: lowest `clientID` wins
+- [ ] Client without `pagination.ready === true` in awareness state is excluded from election
+- [ ] `subscribe(cb)` is called when awareness state changes
+- [ ] `destroy()` removes awareness listener and clears subscribers
+- [ ] After `destroy()`, subscribers are no longer called
+
+### Implementation Notes
+
+- Need to mock `Awareness` and `Y.Doc` — use minimal stubs with `on`/`off`/`getStates`/`clientID`
+- `leaderElection.ts` requires no changes; tests validate existing behavior
+
+---
+
+## TDD Cycle 4: BasePaginationPlugin — Normalization & Transforms API
+
+**Files**:
+- `packages/pagination/src/__tests__/BasePaginationPlugin.spec.ts` (normalization, onNodeChange)
+- `packages/pagination/src/__tests__/paginationTransforms.spec.ts` (toolbar transforms)
+
+### Tests to Write (Normalization — existing behavior; no code changes)
+
+- [ ] Editor with flat `children` (no pages): `normalizeInitialValue` wraps all children into one page
+- [ ] Editor with a mix of page and non-page root children: non-page children are wrapped into pages
+- [ ] Editor where all children are already pages: no change
+- [ ] Nested page (page inside page at depth > 1): inner page is unwrapped
+- [ ] `normalizeNode` with path length 0 (root): `normalizeRootChildren` is called
+- [ ] `onNodeChange` handler: when root has non-page children, wraps them and calls `markDirty(0)`
+- [ ] `onNodeChange` handler: no-op when `__paginationMutating` is true
+- [ ] `onNodeChange` handler: no-op when `meta.isNormalizing` is true
+- [ ] `onNodeChange` handler: no-op when all children are pages
+- [ ] `onNodeChange` handler: no-op when children array is empty or not an array
+- [ ] `withPaginationMutations` sets and restores `__paginationMutating` flag
+- [ ] `withPaginationMutations` restores flag on exception (try/finally)
+- [ ] `apply` override marks page dirty when operation path references a page index
+- [ ] `apply` override does not mark dirty when `__paginationMutating` is true
+- [ ] `getPaginationRuntime` returns the runtime attached to editor
+- [ ] `getPaginationRuntime` returns `undefined` when no runtime is attached
+
+### Tests to Write (Transforms API — NEW implementation needed)
+
+- [ ] `editor.tf.pagination.togglePreview()` toggles `viewMode` from `'paginated'` to `'continuous'`
+- [ ] `editor.tf.pagination.togglePreview()` toggles `viewMode` from `'continuous'` to `'paginated'`
+- [ ] `editor.tf.pagination.setPageSize('A4')` sets sizes to `{ width: 794, height: 1123 }`
+- [ ] `editor.tf.pagination.setPageSize('Letter')` sets sizes to `{ width: 816, height: 1056 }`
+- [ ] `editor.tf.pagination.setPageSize('Legal')` sets sizes to `{ width: 816, height: 1344 }`
+- [ ] `editor.tf.pagination.setMargins({ top: 48, right: 48, bottom: 48, left: 48 })` updates margins
+- [ ] `editor.tf.pagination.toggleHeader()` inserts `{ type: 'header', children: [{ type: 'p', children: [{ text: '' }] }] }` as first child of every page
+- [ ] `editor.tf.pagination.toggleHeader()` when headers exist: removes them from all pages
+- [ ] `editor.tf.pagination.toggleFooter()` inserts footer as last child of every page
+- [ ] `editor.tf.pagination.toggleFooter()` when footers exist: removes them from all pages
+- [ ] Toggle transforms use `withPaginationMutations` to prevent re-entry dirty marking
+- [ ] Toggle transforms mark pages as dirty after mutation
+- [ ] Toggle transforms return `boolean` indicating whether elements were added (`true`) or removed (`false`)
+
+### Implementation
+
+**`BasePaginationPlugin.ts`** — Extend the `withPagination` `OverrideEditor` to inject a `pagination` namespace into `editor.tf`:
+
+```ts
+// Inside the returned { transforms: { ... } } object, add:
+pagination: {
+ togglePreview: () => { ... },
+ setPageSize: (size) => { ... },
+ setMargins: (margins) => { ... },
+ toggleHeader: () => { ... },
+ toggleFooter: () => { ... },
+}
+```
+
+Implementation details:
+- `togglePreview`: read current `viewMode` via `editor.getOption(BasePaginationPlugin, 'viewMode')`, set to opposite via `editor.setOption`
+- `setPageSize`: define `PAGE_SIZES` constant map, call `editor.setOptions` with new sizes
+- `setMargins`: call `editor.setOptions` with new margins
+- `toggleHeader`/`toggleFooter`: iterate all page children, check first/last child type, insert or remove. Use `withPaginationMutations` + `Editor.withoutNormalizing`.
+
+---
+
+## TDD Cycle 5: Reflow Engine
+
+**File**: `packages/pagination/src/__tests__/reflowEngine.spec.ts`
+
+### Tests to Write (existing behavior, some require DOM)
+
+- [ ] `reflowPageBoundary` — no overflow, no underflow: returns `{ changed: false }`
+- [ ] `reflowPageBoundary` — overflow with multiple children: moves overflowing children to next page, returns `{ changed: true, nextPageToContinue: pageIndex + 1 }`
+- [ ] `reflowPageBoundary` — overflow creates next page when it doesn't exist
+- [ ] `reflowPageBoundary` — single oversized child when `allowTextSplit: true`: splits text block
+- [ ] `reflowPageBoundary` — single oversized child when `allowTextSplit: false`: returns `{ changed: false }` (no infinite loop)
+- [ ] `reflowPageBoundary` — underflow pulls first child from next page, returns `{ changed: true, nextPageToContinue: pageIndex }`
+- [ ] `reflowPageBoundary` — underflow with insufficient space (< `underflowThresholdPx`): no change
+- [ ] `reflowPageBoundary` — underflow with no next page: no change
+- [ ] `reflowPageBoundary` — underflow with candidate too large for available space: no change
+- [ ] `reflowPageBoundary` — underflow with `underflow: false` in options: no change
+- [ ] `reflowPageBoundary` — empty trailing page removal
+- [ ] `reflowPageBoundary` — overflow beyond `overflowThresholdPx` only
+- [ ] `reflowPageBoundary` — does not pollute undo history (`HistoryEditor.withoutSaving`)
+- [ ] `findOverflowSplitIndex` — binary search finds first overflowing child
+- [ ] `findOverflowSplitIndex` — all children fit: returns `null`
+- [ ] `findOverflowSplitIndex` — empty content: returns `null`
+- [ ] `splitOversizedBlock` — successfully splits text block at measured boundary
+- [ ] `splitOversizedBlock` — returns `false` when editor has no `hasEditableTarget` (no React binding)
+- [ ] `splitOversizedBlock` — returns `false` when `ReactEditor.toDOMRange` throws
+- [ ] `splitOversizedBlock` — returns `false` when text length < 2
+- [ ] `splitOversizedBlock` — returns `false` when `toDOMRange` is unavailable (non-React editor)
+
+### Implementation
+
+**`reflowEngine.ts:210-323`** — `splitOversizedBlock` hardening:
+
+- [ ] Add `await new Promise(r => requestAnimationFrame(r))` before binary search to ensure DOM settled
+- [ ] Add fallback path when `toDOMRange` is unavailable: proportional estimation using `fullText.length` mapped to `contentEl.clientHeight`, split point = `floor(charCount * maxHeight / contentEl.scrollHeight)`, find nearest word boundary
+
+**`reflowEngine.ts:129-137`** — Empty page guard:
+
+- [ ] Before removing a page, check if it's the last remaining page. If yes and it's empty, insert a default paragraph block instead of removing.
+
+---
+
+## TDD Cycle 6: PaginationCoordinator (Scheduling & React Integration)
+
+**File**: `packages/pagination/src/__tests__/PaginationCoordinator.spec.tsx`
+
+### Tests to Write
+
+- [ ] Subscribes to runtime dirty notifications; calls `scheduleReflowFrom(minDirty)` on notification
+- [ ] Debounces rapid dirty marks: multiple marks within `debounceMs` result in one reflow call with the minimum index
+- [ ] Does NOT schedule reflow when `reflow.enabled` is `false`
+- [ ] Does NOT schedule reflow when `canProcess` prop is `false`
+- [ ] In `'leader'` collaboration mode, does NOT schedule when `isLeaderRef.current` is `false`
+- [ ] In `'leader'` collaboration mode, re-rechecks leader status when election changes
+- [ ] Uses `requestIdleCallback` when available, falls back to `setTimeout(0)`
+- [ ] Reflow on window resize: triggers `scheduleReflowFrom(0)`
+- [ ] Resize handler has its own debounce (200ms) independent of reflow debounce
+- [ ] Reflow on initial mount: triggers `scheduleReflowFrom(0)`
+- [ ] Reflow on `viewMode` change: triggers `scheduleReflowFrom(0)`
+- [ ] While running, concurrent calls are re-scheduled instead of dropped
+- [ ] Processes at most `maxPagesPerIdle` pages per idle frame, re-schedules remainder
+- [ ] Stops cascade when a page has no registered DOM (`registry.getPageDom` returns undefined)
+- [ ] `runningRef` guard prevents overlapping reflow runs
+
+### Implementation
+
+**`PaginationCoordinator.tsx:163-168`** — Add resize debounce:
+
+- [ ] Add `resizeTimerRef` using the same `useRef` pattern as `scheduledRef`
+- [ ] In resize handler: clear previous timer, set new 200ms timer before calling `scheduleReflowFrom(0)`
+
+---
+
+## TDD Cycle 7: PageElement & React Components
+
+**File**: `packages/pagination/src/__tests__/PageElement.spec.tsx`
+
+### Tests to Write
+
+- [ ] `PageElement` renders children inside content div
+- [ ] In `'paginated'` mode, outer div has fixed dimensions from `documentSettings`
+- [ ] In `'continuous'` mode, outer div has `width: 100%` and `height: auto`
+- [ ] In `'paginated'` mode, content div has correct dimensions (`sizes - margins`)
+- [ ] In `'continuous'` mode, content div has `width: 100%` and `height: auto`
+- [ ] `PageElement` registers DOM refs with registry on mount
+- [ ] `PageElement` unregisters DOM refs on unmount
+- [ ] `PageElement` re-registers when `pageIndex` changes
+- [ ] `PageElement` does not register when `registry` is null (no provider)
+- [ ] `PageElement` does not register when `pageIndex` is null
+- [ ] Changing `documentSettings` via `editor.setOptions` triggers re-render with new dimensions
+- [ ] Changing `viewMode` via `editor.setOption` triggers re-render with correct styling
+
+### Implementation
+
+No changes needed — `PageElement.tsx` should already handle all above. Tests verify.
+
+---
+
+## TDD Cycle 8: PaginationPlugin (Integration)
+
+**File**: `packages/pagination/src/__tests__/PaginationPlugin.spec.tsx`
+
+### Tests to Write
+
+- [ ] `PaginationPlugin` combined with `BasePaginationPlugin` + `PageElement` renders a page node
+- [ ] `PaginationPlugin` can be configured with custom `documentSettings`
+- [ ] `PaginationPlugin` can be configured with custom `reflow` options
+- [ ] `PaginationPlugin` `afterEditable` render slot works (via `PaginationRegistryProvider` + `PaginationCoordinator`)
+- [ ] End-to-end: typing text into a page, causing overflow, triggers reflow to next page
+
+### Implementation
+
+No changes needed — `index.ts` wiring is already correct. Tests verify.
+
+---
+
+## TDD Cycle 9: YjsIntegration (Collaboration Bridge)
+
+**File**: `packages/pagination/src/__tests__/YjsIntegration.spec.tsx`
+
+### Tests to Write
+
+- [ ] `YjsPaginationBridge` renders `PaginationCoordinator` with leader election when awareness and ydoc are present
+- [ ] `YjsPaginationBridge` renders `PaginationCoordinator` without leader election when awareness/ydoc are absent
+- [ ] `YjsPaginationBridge` sets `canProcess` to `true` only when both `isConnected` and `isSynced` are true
+- [ ] `YjsPaginationBridge` sets `canProcess` to `false` when not connected or not synced
+- [ ] `YjsPaginationBridge` calls `runtime.markDirty(0)` after initial sync completes
+- [ ] `YjsPaginationBridge` sets `awareness.setLocalStateField('pagination', { ready: canProcess })` on connect
+- [ ] `YjsPaginationBridge` destroys leader election on unmount
+- [ ] `YjsPaginationBridge` handles missing runtime gracefully
+
+### Implementation
+
+No changes needed — `YjsIntegration.tsx` already implements correctly. Tests verify.
+
+---
+
+## Coverage Matrix (100% Target)
+
+| Source File | Functions Covered | Lines | Branches |
+|---|---|---|---|
+| `runtime.ts` | 2 (createRuntime, getPageIndexFromOp) | 100% | 100% |
+| `registry.tsx` | 3 (Provider, register, useRegistry) | 100% | 100% |
+| `leaderElection.ts` | 2 (createAlwaysLeader, createAwarenessLeaderElection) | 100% | 100% |
+| `types.ts` | 0 (types only, no logic) | N/A | N/A |
+| `BasePaginationPlugin.ts` | 7 (plugin, withPagination, normalizeRootChildren, wrapRootRange, withPaginationMutations, getPaginationRuntime, transforms API) | 100% | 100% |
+| `refowEngine.ts` | 4 (reflowPageBoundary, findOverflowSplitIndex, splitOversizedBlock, withoutSaving) | 100% | 100% |
+| `PaginationCoordinator.tsx` | 1 component (runReflow, scheduleReflowFrom, shouldProcess) | 100% | 100% |
+| `PageElement.tsx` | 1 component | 100% | 100% |
+| `YjsIntegration.tsx` | 1 component | 100% | 100% |
+| `index.ts` | 0 (re-exports only) | N/A | N/A |
+
+---
+
+## Verification Criteria
+
+- [ ] All 9 test files pass with `bun test` from `packages/pagination/`
+- [ ] Coverage report (`bun test --coverage`) shows 100% line coverage and 100% branch coverage across all source files with logic
+- [ ] `pnpm typecheck` passes for the pagination package (`pnpm turbo typecheck --filter=./packages/pagination`)
+- [ ] `PaginationToolbarButton` dropdown toggles page preview without toast fallback
+- [ ] Page size changes (A4/Letter/Legal) resize rendered pages
+- [ ] Margin preset changes update page padding
+- [ ] Header/footer toggles add/remove header/footer blocks
+- [ ] Rapid window resize does not cause jank (200ms debounce)
+- [ ] Text split path works with DOM measurement and with proportional fallback
+- [ ] Empty page edge case guarded (last page never removed if it's the only page)
+- [ ] All reflow mutations excluded from undo history
+
+## Potential Risks and Mitigations
+
+1. **[R] DOM-based reflow tests require Happy DOM to accurately report `offsetTop`, `offsetHeight`, `scrollHeight`, `clientHeight`**
+ Mitigation: Happy DOM supports these properties. If edge cases fail, mock `getBoundingClientRect` / `offsetTop` on individual test elements using `Object.defineProperty`.
+
+2. **[R] `splitOversizedBlock` binary search uses `ReactEditor.toDOMRange` which may not work in Happy DOM**
+ Mitigation: The fallback proportional-estimate path (Task 5) is testable without ReactEditor. The primary path can be tested by mocking `ReactEditor.toDOMRange` to return a stub `DOMRect`. The fallback path is tested by setting `toDOMRange` to `undefined`.
+
+3. **[R] `PaginationCoordinator` tests require mocking `requestIdleCallback` and timing control**
+ Mitigation: Use `mock` from `bun:test` (already global in setup). Mock `requestIdleCallback` as `setTimeout` for deterministic test runs. Use fake timers where needed.
+
+4. **[R] YjsIntegration tests require `@platejs/yjs` dependency which is optional**
+ Mitigation: Tests import `YjsPlugin` only for the spec file; dependency is already in `devDependencies`. If import fails in test, mock `YjsPlugin` and its options.
+
+## Alternative Approaches
+
+1. **[Alt] Use `editor.api.pagination.*` instead of `editor.tf.pagination.*`**: More aligned with Plate convention (api = custom, tf = Slate transforms). Trade-off: must update toolbar button imports. Evaluation: keep `tf` for now to match toolbar button expectations, consider migration to `api` in a follow-up.
+
+2. **[Alt] Skip DOM tests for reflow engine, test only logic paths**: Would miss coverage on `findOverflowSplitIndex` binary search and `splitOversizedBlock` text split. Trade-off: faster test suite but incomplete coverage. Evaluation: include DOM tests; Happy DOM handles them.
+
+3. **[Alt] Combine all spec files into one**: Simpler file layout but harder to maintain. Trade-off: single file with 80+ tests is messy. Evaluation: keep separate files by module.
diff --git a/plans/2026-05-15-fix-pagination-plugin-v1.md b/plans/2026-05-15-fix-pagination-plugin-v1.md
new file mode 100644
index 0000000000..b4e0c995ec
--- /dev/null
+++ b/plans/2026-05-15-fix-pagination-plugin-v1.md
@@ -0,0 +1,114 @@
+# Fix Pagination Plugin — Make It Usable End-to-End
+
+## Objective
+
+Transform the pagination plugin from a mostly-functional internal engine into a fully usable end-user feature. The toolbar button exists in the playground template but its five transform methods (`togglePreview`, `toggleHeader`, `toggleFooter`, `setPageSize`, `setMargins`) are unimplemented — clicking them either does nothing or falls through to a toast. Additionally, the reflow engine's text-split path is fragile, resize handling lacks its own debounce, and there are no tests.
+
+## Scope
+
+- **Package**: `packages/pagination/`
+- **Template**: `templates/plate-playground-template/`
+- **8 implementation tasks** across 3 layers: transforms API, engine hardening, and testing
+
+---
+
+## Implementation Plan
+
+### Layer 1: Transforms API — Core User-Facing Gap
+
+- [ ] **Task 1.** Implement `editor.tf.pagination` transforms namespace in `BasePaginationPlugin.ts` override editor.
+
+ **Rationale**: The toolbar button (`pagination-toolbar-button.tsx:100-102`) accesses `editor.tf.pagination` which doesn't exist. The Plate `OverrideEditor` pattern allows injecting custom transforms. Five methods needed:
+ - `togglePreview()` — toggle `viewMode` between `'paginated'` and `'continuous'` using `editor.setOption`
+ - `setPageSize(size: 'A4' | 'Letter' | 'Legal')` — update `documentSettings.sizes` via `editor.setOptions` with known presets (A4: 794x1123, Letter: 816x1056, Legal: 816x1344 at 96 DPI)
+ - `setMargins(m: Margins)` — update `documentSettings.margins` via `editor.setOptions`
+ - `toggleHeader()` — insert/remove a `{ type: 'header', children: [...] }` node as first or last child of each page
+ - `toggleFooter()` — insert/remove a `{ type: 'footer', children: [...] }` node as first or last child of each page
+
+ The header/footer toggles must: check if headers/footers already exist across pages, if yes remove them from all pages via `withPaginationMutations`, if no insert a default header/footer block into every page. Mark pages dirty after mutations.
+
+ The override editor pattern (`BasePaginationPlugin.ts:49-88`) already returns `{ transforms: { apply, normalizeNode } }` — extend this to include the `pagination` namespace.
+
+- [ ] **Task 2.** Wire `documentSettings` reactivity so `PageElement` re-renders when page size or margins change via transforms.
+
+ **Rationale**: Currently `PageElement` reads `documentSettings` from `usePluginOption` which subscribes to the options store. `editor.setOption`/`editor.setOptions` already trigger store updates. Verify this works end-to-end: changing page size via toolbar updates all rendered PageElements immediately. If the options store subscription doesn't propagate to `PageElement`, fix by ensuring the plugin options store is properly reactive.
+
+- [ ] **Task 3.** Wire `viewMode` toggle reactivity so `PaginationCoordinator` and `PageElement` respond to the toggle.
+
+ **Rationale**: `PaginationCoordinator.tsx:175-179` already has a `useEffect` watching `viewMode`. `PageElement.tsx:49-77` already switches rendering based on `isPaginated`. Both use `usePluginOption` which should react to `editor.setOption(BasePaginationPlugin, 'viewMode', ...)`. Verify the full chain: toolbar toggle -> option update -> coordinator reflow -> page element re-render.
+
+### Layer 2: Engine Hardening
+
+- [ ] **Task 4.** Add resize debounce dedicated to window resize events in `PaginationCoordinator`.
+
+ **Rationale**: `PaginationCoordinator.tsx:163-168` attaches a `resize` listener that calls `scheduleReflowFrom(0)`. While the reflow pipeline has its own 100ms debounce, rapid resize events during browser window dragging still enqueue many `setTimeout` calls. Add a separate 200ms debounce on the resize handler itself (before calling `scheduleReflowFrom`), using a `useRef` timer pattern matching the existing `scheduledRef` pattern.
+
+- [ ] **Task 5.** Harden `splitOversizedBlock` text-split path against `ReactEditor.toDOMRange` failures.
+
+ **Rationale**: `reflowEngine.ts:210-323` uses `(ReactEditor as any).toDOMRange(editor, range)` to convert a Slate range to a DOM range for binary-search measurement. This is fragile — it accesses a static method via casting, and throws if the editor isn't attached to a DOM. Add:
+ 1. A try-catch guard at the `toDOMRange` call site returning `false` on failure (already partially there at L263)
+ 2. A `requestAnimationFrame` await before the binary search to ensure DOM is settled after React renders
+ 3. A fallback: if `toDOMRange` is unavailable, fall back to an `offset-based` estimate using `fullText.length` proportionally mapped to the container height — this won't be pixel-perfect but prevents complete failure
+
+- [ ] **Task 6.** Guard against empty-page edge case in `reflowPageBoundary`.
+
+ **Rationale**: `reflowEngine.ts:129-137` removes empty trailing pages. If a document has exactly one page and it becomes empty (all children moved out), the page removal would leave the editor with zero root children — a state Slate cannot handle. Add a guard: before removing a page at index 0 or the last remaining page, insert a default empty paragraph block instead.
+
+### Layer 3: Testing & Verification
+
+- [ ] **Task 7.** Write unit tests for the transforms API.
+
+ **Rationale**: No tests currently exist for `@platejs/pagination`. Tests needed:
+ - `togglePreview()` switches `viewMode` and triggers reflow
+ - `setPageSize('A4')` updates `documentSettings.sizes` to `{ width: 794, height: 1123 }`
+ - `setPageSize('Letter')` updates to `{ width: 816, height: 1056 }`
+ - `setPageSize('Legal')` updates to `{ width: 816, height: 1344 }`
+ - `setMargins(...)` updates `documentSettings.margins`
+ - `toggleHeader()` / `toggleFooter()` insert/remove header/footer nodes from all pages
+ - Transforms respect `withPaginationMutations` guard (no infinite markDirty loops)
+
+- [ ] **Task 8.** Write integration tests for the reflow engine core paths.
+
+ **Rationale**: The reflow engine is the most complex component. Tests needed:
+ - Single page with content that fits → no change
+ - Single page with overflowing content → child moves to new page 2
+ - Two pages where page 1 underflows → child pulled from page 2
+ - Empty trailing page removal
+ - Single-page document empty behavior (guard from Task 6)
+ - Text split for oversized single-child block
+ - Reflow mutations do not appear in undo history (`HistoryEditor.withoutSaving` verification)
+
+## Verification Criteria
+
+- [ ] `PaginationToolbarButton` dropdown toggles page preview without falling to toast
+- [ ] Page size changes (A4 ↔ Letter ↔ Legal) resize rendered pages in real-time
+- [ ] Margin preset changes update page padding in real-time
+- [ ] Header/footer toggles add/remove header/footer blocks from all pages
+- [ ] Rapid window resize during page drag does not cause jank or excessive reflow runs
+- [ ] Text split path works when a single paragraph overflows a page boundary
+- [ ] Empty page edge cases handled gracefully (no Slate-invalid empty root children)
+- [ ] All reflow mutations excluded from undo history
+- [ ] `pnpm test` passes for the pagination package
+- [ ] `pnpm typecheck` passes for the pagination package
+
+## Potential Risks and Mitigations
+
+1. **[R] `editor.tf.pagination` namespace may conflict with Plate's Slate transforms merging**
+ Mitigation: Plate's `OverrideEditor` merges returned `transforms` into `editor.tf`. Test that custom namespace keys are preserved after merge. If conflict arises, use `editor.api.pagination` instead and update the toolbar button.
+
+2. **[R] Header/footer node types (`'header'`, `'footer'`) may collide with existing plugin node types**
+ Mitigation: Define header/footer as simple block containers (no special plugin). If a `HeaderPlugin` or `FooterPlugin` exists in the registry, coordinate to avoid type collision. Alternatively, prefix with `pagination-` namespace.
+
+3. **[R] `ReactEditor.toDOMRange` binary search falls back to proportional estimate which may be inaccurate for mixed font sizes**
+ Mitigation: The proportional fallback is best-effort. Accept ±1 line inaccuracy for the fallback path. The primary path (DOM measurement) handles 99% of cases.
+
+4. **[R] Header/footer toggle iterates all pages — on large documents this could be slow**
+ Mitigation: Wrap in `Editor.withoutNormalizing` and batch all mutations. For very large documents (100+ pages), consider a future optimization using a single atomic operation.
+
+## Alternative Approaches
+
+1. **[Alt] Use `editor.api.pagination` instead of `editor.tf.pagination`**: The `api` namespace is more conventional for custom plugin methods. Trade-off: requires updating the toolbar button import pattern. Better long-term architecture but more template changes.
+
+2. **[Alt] Separate HeaderPlugin/FooterPlugin as standalone plugins**: Instead of managing headers/footers in the pagination plugin, create dedicated plugins that the pagination plugin composes with. Trade-off: cleaner separation but more boilerplate, and headers/footers are inherently coupled to page layout.
+
+3. **[Alt] Pure-CSS text overflow instead of JS binary search for text split**: Use CSS `overflow: hidden` with a measured approach (render text in hidden div, measure line by line). Trade-off: simpler but slower and requires extra DOM nodes.
diff --git a/plans/2026-05-16-pagination-end-to-end-fix-v1.md b/plans/2026-05-16-pagination-end-to-end-fix-v1.md
new file mode 100644
index 0000000000..0219a148fe
--- /dev/null
+++ b/plans/2026-05-16-pagination-end-to-end-fix-v1.md
@@ -0,0 +1,148 @@
+# Pagination Plugin — End-to-End Fix & Deploy
+
+## Objective
+
+Fix the pagination plugin integration in the playground template so that:
+1. The toolbar button correctly reads/writes plugin state (fix hardcoded values, wrong header/footer detection)
+2. The WIP placeholder comment is removed
+3. Vendor copy is regenerated from the refactored source package
+4. `bun run deploy` from the repo root correctly cleans, builds, and deploys to Cloudflare
+5. The full build pipeline works: `build:pagination` → `vendor:pagination` → `deploy`
+
+## Sage Research Findings Summary
+
+The Sage agent analysis revealed 10 contradictions between the source package (`packages/pagination/`) and the template vendor copy (`templates/plate-playground-template/vendor/platejs-pagination/`). Key findings:
+
+| # | Issue | Impact |
+|---|-------|--------|
+| 1 | Vendor copy is stale (pre-refactoring snapshot) | All 12 refactor tasks from `docs/plans/2026-05-15-pagination-plugin-refactor.md` are missing from vendor |
+| 2 | `pagination-toolbar-button.tsx:90` hardcodes `pageSize = 'A4'` | Page size radio always shows A4 selected |
+| 3 | `pagination-toolbar-button.tsx:93-98` checks root children for headers/footers | Detection always returns `false` — headers live inside page children |
+| 4 | `editor-kit.tsx:74` says "placeholder until @platejs/pagination ships" | Misleading WIP comment |
+| 5 | Toolbar accesses `editor.tf as unknown as { pagination? }` | Fragile type cast |
+| 6 | Vendor Yjs export in main barrel instead of `./yjs` subpath | Wrong import path |
+| 7 | Source uses WeakMap registry; vendor uses bolt-on `editor.__paginationRuntime` | Architecture mismatch |
+| 8 | Source uses microtask-coalesced dirty notifications; vendor is synchronous | Performance mismatch |
+| 9 | Source has dedicated 200ms resize debounce; vendor has none | Jank on resize |
+| 10 | Source has linear-scan fallback for non-monotonic offsetTop; vendor binary-search only | Edge-case fragility |
+
+## PR #4830 Context (GitHub)
+
+PR #4830 (`feat(docx-io): add docXMLater adapter layer for DOCX export`) covers pagination as one of 40+ element types in the DOCX export adapter. The PR was closed without merge. Key bot feedback:
+- **changeset-bot**: "⚠️ No Changeset found" — no version bump would occur
+- **vercel**: Deployment was "Skipped" (Ignored)
+
+This is a docx-io PR, not a pagination-specific PR, but it references pagination as a covered element type.
+
+## Implementation Plan
+
+### Phase 1: Fix Template Toolbar (Source of Truth)
+
+- [ ] **Task 1.** Fix `pageSize` being hardcoded to `'A4'` in `pagination-toolbar-button.tsx:90`.
+
+ **Rationale**: Line 90 sets `const pageSize: PaginationOptions['pageSize'] = 'A4'` unconditionally. The actual page size is stored in `documentSettings.sizes` on the plugin options. Read it from `usePluginOption(BasePaginationPlugin, 'documentSettings')` and derive the page size key by comparing against `PAGE_SIZES` presets (A4: 794x1123, Letter: 816x1056, Legal: 816x1344). If no preset matches, return the raw sizes object.
+
+ **Files**: `templates/plate-playground-template/src/components/ui/pagination-toolbar-button.tsx`
+
+- [ ] **Task 2.** Fix `headerPresent`/`footerPresent` detection to look inside page children, not root children.
+
+ **Rationale**: `pagination-toolbar-button.tsx:93-98` checks `value` (document root children) for `type === 'header'` and `type === 'footer'`. But pages are root children, and headers/footers live as first/last children within each page. The correct check: iterate root children (pages), then for each page, check if its first child is type `'header'` or last child is type `'footer'`.
+
+ **Files**: `templates/plate-playground-template/src/components/ui/pagination-toolbar-button.tsx`
+
+- [ ] **Task 3.** Remove WIP placeholder comment from `editor-kit.tsx:74`.
+
+ **Rationale**: The comment `// Pagination (placeholder until @platejs/pagination ships — PRs #357/#358)` is stale. The plugin ships in the vendor copy. Replace with `// Pagination`.
+
+ **Files**: `templates/plate-playground-template/src/components/editor/editor-kit.tsx`
+
+- [ ] **Task 4.** Fix toolbar type cast from `as unknown as { pagination?: PaginationTransforms }` to use the typed transform namespace.
+
+ **Rationale**: `pagination-toolbar-button.tsx:100-102` casts `editor.tf` through `unknown` to access `.pagination`. The `BasePaginationPlugin` exports `PaginationTransforms` type via `extendTransforms`. Import and use the proper `PaginationTransforms` type directly. Use `editor.tf.pagination` (typed via `PlateEditor['tf']` merged with the plugin's transform types) or import `PaginationTransforms` and cast narrowly.
+
+ **Files**: `templates/plate-playground-template/src/components/ui/pagination-toolbar-button.tsx`
+
+### Phase 2: Vendor Copy Regeneration
+
+- [ ] **Task 5.** Build the source pagination package and regenerate the vendor copy.
+
+ **Rationale**: The vendor directory `templates/plate-playground-template/vendor/platejs-pagination/` contains a stale pre-refactoring snapshot. The source package at `packages/pagination/` has been refactored (WeakMap registry, microtask coalescing, resize debounce, linear-scan fallback, debug gating, empty page guard). Steps:
+ 1. Build the pagination package: `pnpm turbo build --filter=./packages/pagination`
+ 2. Run vendor script: `cd templates/plate-playground-template && bun run vendor:pagination`
+
+ **Verification**: After vendoring, the `vendor/platejs-pagination/dist/` should contain the refactored code (confirm WeakMap usage, microtask coalescing, resize debounce present in the output).
+
+- [ ] **Task 6.** Verify the vendor copy matches the source package structure.
+
+ **Rationale**: The source package has subpath exports (`./yjs`) and internal modules (`editorRegistry`, `reflowEngine`, `runtime`). The vendor copy must maintain the same export surface. Check:
+ - `dist/index.js` exports match `packages/pagination/src/index.ts`
+ - `dist/yjs/index.js` exists (subpath export)
+ - No `editor.__paginationRuntime` bolt-on in the output (should use WeakMap)
+
+### Phase 3: Build & Deploy Pipeline
+
+- [ ] **Task 7.** Add root-level `deploy:playground` script that cleans, builds pagination, vendors, and deploys.
+
+ **Rationale**: The user wants `bun run deploy` from root to work. Currently no such script exists. The chain is:
+ 1. Build pagination package (if not built): `pnpm turbo build --filter=./packages/pagination`
+ 2. Vendor pagination: `cd templates/plate-playground-template && bun run vendor:pagination`
+ 3. Install template deps (vendor file: link): `cd templates/plate-playground-template && bun install`
+ 4. Deploy: `cd templates/plate-playground-template && bun run deploy`
+
+ Add to root `package.json` scripts: `"deploy:playground": "pnpm turbo build --filter=./packages/pagination && cd templates/plate-playground-template && bun run vendor:pagination && bun install && bun run deploy"`
+
+ **Files**: `package.json`
+
+- [ ] **Task 8.** Verify the deployment pipeline works end-to-end.
+
+ **Rationale**: The `bun run deploy` in the template calls `opennextjs-cloudflare build && opennextjs-cloudflare deploy`. This requires:
+ - `wrangler.toml` or Cloudflare config present
+ - `@opennextjs/cloudflare` installed (confirmed in devDependencies)
+ - Vendor copy correctly linked via `file:./vendor/platejs-pagination`
+
+ Verify by running the deploy command (dry-run if possible). Check that the Next.js build picks up the vendor copy without import errors.
+
+### Phase 4: Verification
+
+- [ ] **Task 9.** Verify typecheck passes for the template.
+
+ **Rationale**: After fixing the toolbar types and regenerating vendor copy, run `cd templates/plate-playground-template && bun typecheck` to ensure no TypeScript errors.
+
+- [ ] **Task 10.** Verify lint passes for the template.
+
+ **Rationale**: Run `cd templates/plate-playground-template && bun lint:fix` to auto-fix and verify no lint errors.
+
+## Verification Criteria
+
+- [ ] `pagination-toolbar-button.tsx` reads actual `documentSettings.sizes` for page size display
+- [ ] `pagination-toolbar-button.tsx` correctly detects headers/footers inside page children
+- [ ] WIP comment removed from `editor-kit.tsx:74`
+- [ ] Toolbar type cast uses proper `PaginationTransforms` type (not `as unknown`)
+- [ ] `vendor/platejs-pagination/dist/` contains refactored code (WeakMap, microtask, resize debounce)
+- [ ] `pnpm turbo build --filter=./packages/pagination` succeeds
+- [ ] `bun run vendor:pagination` succeeds from template directory
+- [ ] `bun typecheck` passes in template directory
+- [ ] `bun lint:fix` passes in template directory
+- [ ] `bun run deploy` succeeds from template directory (deploys to Cloudflare)
+
+## Potential Risks and Mitigations
+
+1. **[R] Vendor copy generation may fail if `packages/pagination/dist` doesn't exist**
+ Mitigation: Task 7 runs `pnpm turbo build --filter=./packages/pagination` before vendoring. The vendor:pagination script does `cp -r ../../packages/pagination/dist vendor/platejs-pagination/dist`.
+
+2. **[R] Template `bun install` may break with `file:./vendor/platejs-pagination` after vendor regeneration**
+ Mitigation: Bun's file: protocol creates symlinks. If the dist structure changed, run `bun install --force` in the template directory.
+
+3. **[R] Cloudflare deployment may fail due to missing wrangler config or auth**
+ Mitigation: The template already has `@opennextjs/cloudflare` and `wrangler` in devDependencies. Cloudflare auth is handled by wrangler login/token. This is environment-specific and outside code scope.
+
+4. **[R] Toolbar header/footer detection may still show incorrect state if pages are normalized differently**
+ Mitigation: The fix checks page children (index 0 for header, last index for footer). Added after the fix, verify with `bun run dev` in the template before deploying.
+
+## Alternative Approaches
+
+1. **[Alt] Use `editor.api.pagination` instead of `editor.tf.pagination`**: More aligned with Plate convention (api = custom methods, tf = Slate transforms). Trade-off: requires updating the toolbar button import pattern and the `extendTransforms` → `extendApi` migration. Deferred to follow-up.
+
+2. **[Alt] Remove vendor copy entirely and use workspace protocol**: Link `@platejs/pagination` as `"workspace:*"` instead of `"file:./vendor/platejs-pagination"`. Trade-off: simpler dev flow but breaks standalone template deployment (template must be self-contained for `shadcn` init). Keep vendor copy for standalone deployability.
+
+3. **[Alt] Publish `@platejs/pagination` to npm and use semver dependency**: Instead of vendoring, publish the package and reference it normally. Trade-off: requires npm publish workflow, changeset, and versioning. The vendor approach is already in place and works for the template pattern.
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 06a7c62234..bca0f8e123 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -371,6 +371,9 @@ importers:
'@platejs/mention':
specifier: workspace:^
version: link:../../packages/mention
+ '@platejs/pagination':
+ specifier: workspace:^
+ version: link:../../packages/pagination
'@platejs/playwright':
specifier: workspace:^
version: link:../../packages/playwright
@@ -1482,6 +1485,34 @@ importers:
specifier: workspace:^
version: link:../plate
+ packages/pagination:
+ dependencies:
+ '@chenglou/pretext':
+ specifier: ^0.0.6
+ version: 0.0.6
+ '@udecode/react-utils':
+ specifier: workspace:*
+ version: link:../udecode/react-utils
+ react-compiler-runtime:
+ specifier: ^1.0.0
+ version: 1.0.0(react@19.2.4)
+ devDependencies:
+ '@plate/scripts':
+ specifier: workspace:*
+ version: link:../plate-scripts
+ '@platejs/core':
+ specifier: workspace:^
+ version: link:../core
+ platejs:
+ specifier: workspace:^
+ version: link:../plate
+ slate:
+ specifier: '>=0.112.0'
+ version: 0.124.1
+ slate-react:
+ specifier: '>=0.112.0'
+ version: 0.124.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(slate-dom@0.124.1(slate@0.124.1))(slate@0.124.1)
+
packages/plate:
dependencies:
'@platejs/core':
@@ -2172,6 +2203,9 @@ packages:
'@changesets/write@0.4.0':
resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==}
+ '@chenglou/pretext@0.0.6':
+ resolution: {integrity: sha512-U10s4tFeyu3oVHfXuNWwZSKqHXefhaigpcBkGj60qQFRJ+yUoQ+ez3cGJelP7BWDAB58HCgjcTSmOcg+77afBQ==}
+
'@chevrotain/cst-dts-gen@11.1.2':
resolution: {integrity: sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q==}
@@ -5308,6 +5342,7 @@ packages:
'@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
+ deprecated: Potential CWE-502 - Update to 1.3.1 or higher
'@uploadthing/mime-types@0.3.5':
resolution: {integrity: sha512-iYOmod80XXOSe4NVvaUG9FsS91YGPUaJMTBj52Nwu0G2aTzEN6Xcl0mG1rWqXJ4NUH8MzjVqg+tQND5TPkJWhg==}
@@ -5653,7 +5688,6 @@ packages:
bun@1.3.9:
resolution: {integrity: sha512-v5hkh1us7sMNjfimWE70flYbD5I1/qWQaqmJ45q2qk5H/7muQVa478LSVRSFyGTBUBog2LsPQnfIRdjyWJRY+A==}
- cpu: [arm64, x64]
os: [darwin, linux, win32]
hasBin: true
@@ -10304,6 +10338,7 @@ packages:
uuid@9.0.1:
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
+ deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
hasBin: true
uvu@0.5.6:
@@ -11082,6 +11117,8 @@ snapshots:
human-id: 4.1.3
prettier: 2.8.8
+ '@chenglou/pretext@0.0.6': {}
+
'@chevrotain/cst-dts-gen@11.1.2':
dependencies:
'@chevrotain/gast': 11.1.2
diff --git a/templates/plate-playground-template/.gitignore b/templates/plate-playground-template/.gitignore
index 74445a688e..9ef964e0ba 100644
--- a/templates/plate-playground-template/.gitignore
+++ b/templates/plate-playground-template/.gitignore
@@ -40,6 +40,12 @@ pnpm-error.log*
# vercel
.vercel
+# cloudflare / opennext
+.open-next
+.wrangler
+.dev.vars
+cloudflare-env.d.ts
+
# typescript
*.tsbuildinfo
next-env.d.ts
diff --git a/templates/plate-playground-template/bun.lock b/templates/plate-playground-template/bun.lock
index fd25846bcc..5f3ce6697c 100644
--- a/templates/plate-playground-template/bun.lock
+++ b/templates/plate-playground-template/bun.lock
@@ -8,6 +8,7 @@
"@ai-sdk/gateway": "^3.0.105",
"@ai-sdk/react": "3",
"@ariakit/react": "^0.4.26",
+ "@chenglou/pretext": "^0.0.6",
"@emoji-mart/data": "1.2.1",
"@faker-js/faker": "^10.4.0",
"@platejs/ai": "^53.0.3",
@@ -37,6 +38,7 @@
"@platejs/math": "^53.0.0",
"@platejs/media": "^53.0.1",
"@platejs/mention": "^53.0.0",
+ "@platejs/pagination": "file:./vendor/platejs-pagination",
"@platejs/resizable": "^53.0.0",
"@platejs/selection": "^53.0.0",
"@platejs/slash-command": "^53.0.0",
@@ -44,6 +46,7 @@
"@platejs/table": "^53.0.0",
"@platejs/toc": "^53.0.0",
"@platejs/toggle": "^53.0.0",
+ "@platejs/yjs": "^53.0.0",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-checkbox": "^1.3.3",
@@ -83,6 +86,7 @@
"remark-emoji": "^5.0.2",
"remark-gfm": "^4.0.1",
"remark-math": "^6.0.0",
+ "slate-history": "^0.113.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",
"tailwind-scrollbar-hide": "^4.0.0",
@@ -93,6 +97,7 @@
},
"devDependencies": {
"@biomejs/biome": "2.4.13",
+ "@opennextjs/cloudflare": "^1.19.6",
"@tailwindcss/postcss": "4.2.4",
"@types/node": "^25.6.0",
"@types/react": "19.2.14",
@@ -106,6 +111,7 @@
"tailwindcss": "4.2.4",
"typescript": "6.0.3",
"ultracite": "7.6.2",
+ "wrangler": "^4.87.0",
},
},
},
@@ -128,6 +134,122 @@
"@ariakit/react-core": ["@ariakit/react-core@0.4.26", "", { "dependencies": { "@ariakit/core": "0.4.20", "@floating-ui/dom": "^1.0.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-/Peh1KiVpjj79nCJIa6lEdzSTT9P9FZoy+CxByIFKL3YKdlXmDIIhS1E/tAqKbDq4ODVdynnqmrIDxE5wCoZYw=="],
+ "@ast-grep/napi": ["@ast-grep/napi@0.40.5", "", { "optionalDependencies": { "@ast-grep/napi-darwin-arm64": "0.40.5", "@ast-grep/napi-darwin-x64": "0.40.5", "@ast-grep/napi-linux-arm64-gnu": "0.40.5", "@ast-grep/napi-linux-arm64-musl": "0.40.5", "@ast-grep/napi-linux-x64-gnu": "0.40.5", "@ast-grep/napi-linux-x64-musl": "0.40.5", "@ast-grep/napi-win32-arm64-msvc": "0.40.5", "@ast-grep/napi-win32-ia32-msvc": "0.40.5", "@ast-grep/napi-win32-x64-msvc": "0.40.5" } }, "sha512-hJA62OeBKUQT68DD2gDyhOqJxZxycqg8wLxbqjgqSzYttCMSDL9tiAQ9abgekBYNHudbJosm9sWOEbmCDfpX2A=="],
+
+ "@ast-grep/napi-darwin-arm64": ["@ast-grep/napi-darwin-arm64@0.40.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-2F072fGN0WTq7KI3okuEnkGJVEHLbi56Bw1H6NAMf7j2mJJeQWsRyGOMcyNnUXZDeNdvoMH0OB2a5wwUegY/nQ=="],
+
+ "@ast-grep/napi-darwin-x64": ["@ast-grep/napi-darwin-x64@0.40.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-dJMidHZhhxuLBYNi6/FKI812jQ7wcFPSKkVPwviez2D+KvYagapUMAV/4dJ7FCORfguVk8Y0jpPAlYmWRT5nvA=="],
+
+ "@ast-grep/napi-linux-arm64-gnu": ["@ast-grep/napi-linux-arm64-gnu@0.40.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-nBRCbyoS87uqkaw4Oyfe5VO+SRm2B+0g0T8ME69Qry9ShMf41a2bTdpcQx9e8scZPogq+CTwDHo3THyBV71l9w=="],
+
+ "@ast-grep/napi-linux-arm64-musl": ["@ast-grep/napi-linux-arm64-musl@0.40.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-/qKsmds5FMoaEj6FdNzepbmLMtlFuBLdrAn9GIWCqOIcVcYvM1Nka8+mncfeXB/MFZKOrzQsQdPTWqrrQzXLrA=="],
+
+ "@ast-grep/napi-linux-x64-gnu": ["@ast-grep/napi-linux-x64-gnu@0.40.5", "", { "os": "linux", "cpu": "x64" }, "sha512-DP4oDbq7f/1A2hRTFLhJfDFR6aI5mRWdEfKfHzRItmlKsR9WlcEl1qDJs/zX9R2EEtIDsSKRzuJNfJllY3/W8Q=="],
+
+ "@ast-grep/napi-linux-x64-musl": ["@ast-grep/napi-linux-x64-musl@0.40.5", "", { "os": "linux", "cpu": "x64" }, "sha512-BRZUvVBPUNpWPo6Ns8chXVzxHPY+k9gpsubGTHy92Q26ecZULd/dTkWWdnvfhRqttsSQ9Pe/XQdi5+hDQ6RYcg=="],
+
+ "@ast-grep/napi-win32-arm64-msvc": ["@ast-grep/napi-win32-arm64-msvc@0.40.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-y95zSEwc7vhxmcrcH0GnK4ZHEBQrmrszRBNQovzaciF9GUqEcCACNLoBesn4V47IaOp4fYgD2/EhGRTIBFb2Ug=="],
+
+ "@ast-grep/napi-win32-ia32-msvc": ["@ast-grep/napi-win32-ia32-msvc@0.40.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-K/u8De62iUnFCzVUs7FBdTZ2Jrgc5/DLHqjpup66KxZ7GIM9/HGME/O8aSoPkpcAeCD4TiTZ11C1i5p5H98hTg=="],
+
+ "@ast-grep/napi-win32-x64-msvc": ["@ast-grep/napi-win32-x64-msvc@0.40.5", "", { "os": "win32", "cpu": "x64" }, "sha512-dqm5zg/o4Nh4VOQPEpMS23ot8HVd22gG0eg01t4CFcZeuzyuSgBlOL3N7xLbz3iH2sVkk7keuBwAzOIpTqziNQ=="],
+
+ "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="],
+
+ "@aws-crypto/crc32c": ["@aws-crypto/crc32c@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag=="],
+
+ "@aws-crypto/sha1-browser": ["@aws-crypto/sha1-browser@5.2.0", "", { "dependencies": { "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg=="],
+
+ "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="],
+
+ "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="],
+
+ "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="],
+
+ "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
+
+ "@aws-sdk/client-cloudfront": ["@aws-sdk/client-cloudfront@3.984.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.6", "@aws-sdk/credential-provider-node": "^3.972.5", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.984.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.4", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.28", "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-stream": "^4.5.10", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-couDuDLpJtoeWne/nYyJ+I+5ntBVdNgBVRTCoDaXuVV7OC3u/wz5Ps0+GogspEwMLEFoOJ8t691h3YXQtnpQTw=="],
+
+ "@aws-sdk/client-dynamodb": ["@aws-sdk/client-dynamodb@3.984.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.6", "@aws-sdk/credential-provider-node": "^3.972.5", "@aws-sdk/dynamodb-codec": "^3.972.7", "@aws-sdk/middleware-endpoint-discovery": "^3.972.3", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.984.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.4", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.28", "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-8/Oft9MWQtbG6p9f8eY5fsKC2CcO5YVDlwive8eUYS9mEbgnyQxm68OyH26WvsSTykQ9QkIbR+fOG56RsIBODw=="],
+
+ "@aws-sdk/client-lambda": ["@aws-sdk/client-lambda@3.984.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.6", "@aws-sdk/credential-provider-node": "^3.972.5", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.984.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.4", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/eventstream-serde-config-resolver": "^4.3.8", "@smithy/eventstream-serde-node": "^4.2.8", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.28", "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-stream": "^4.5.10", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-kqwNBIGNxGVhINwgN/UQfdsQkaMjbu9PFV2EhATWouV+RT60uMjK9JENgLDwbgJmEVbbnPsh9HaZ5KKwPSdiDg=="],
+
+ "@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.984.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.6", "@aws-sdk/credential-provider-node": "^3.972.5", "@aws-sdk/middleware-bucket-endpoint": "^3.972.3", "@aws-sdk/middleware-expect-continue": "^3.972.3", "@aws-sdk/middleware-flexible-checksums": "^3.972.4", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-location-constraint": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-sdk-s3": "^3.972.6", "@aws-sdk/middleware-ssec": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/signature-v4-multi-region": "3.984.0", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.984.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.4", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/eventstream-serde-config-resolver": "^4.3.8", "@smithy/eventstream-serde-node": "^4.2.8", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-blob-browser": "^4.2.9", "@smithy/hash-node": "^4.2.8", "@smithy/hash-stream-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/md5-js": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.28", "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-stream": "^4.5.10", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-7ny2Slr93Y+QniuluvcfWwyDi32zWQfznynL56Tk0vVh7bWrvS/odm8WP2nInKicRVNipcJHY2YInur6Q/9V0A=="],
+
+ "@aws-sdk/client-sqs": ["@aws-sdk/client-sqs@3.984.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.6", "@aws-sdk/credential-provider-node": "^3.972.5", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-sdk-sqs": "^3.972.5", "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.984.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.4", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/md5-js": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.28", "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-TDvHpOUWlpanc3xQ5Xw0y8L2hoojBFCCSmXQ/6rKqGOf1ScX3dMA+K9aF0Zp0iwjhSh4VvsHD42esl8XwQZDjA=="],
+
+ "@aws-sdk/core": ["@aws-sdk/core@3.974.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws-sdk/xml-builder": "^3.972.22", "@smithy/core": "^3.23.17", "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.6", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-njR2qoG6ZuB0kvAS2FyICsFZJ6gmCcf2X/7JcD14sUvGDm26wiZ5BrA6LOiUxKFEF+IVe7kdroxyE00YlkiYsw=="],
+
+ "@aws-sdk/crc64-nvme": ["@aws-sdk/crc64-nvme@3.972.7", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-QUagVVBbC8gODCF6e1aV0mE2TXWB9Opz4k8EJFdNrujUVQm5R4AjJa1mpOqzwOuROBzqJU9zawzig7M96L8Ejg=="],
+
+ "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.34", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-XT0jtf8Fw9JE6ppsQeoNnZRiG+jqRixMT1v1ZR17G60UvVdsQmTG8nbEyHuEPfMxDXEhfdARaM/XiEhca4lGHQ=="],
+
+ "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.36", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/types": "^3.973.8", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.6.1", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/util-stream": "^4.5.25", "tslib": "^2.6.2" } }, "sha512-DPoGWfy7J7RKxvbf5kOKIGQkD2ek3dbKgzKIGrnLuvZBz5myU+Im/H6pmc14QcnFbqHMqxvtWSgRDSJW3qXLQg=="],
+
+ "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.38", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/credential-provider-env": "^3.972.34", "@aws-sdk/credential-provider-http": "^3.972.36", "@aws-sdk/credential-provider-login": "^3.972.38", "@aws-sdk/credential-provider-process": "^3.972.34", "@aws-sdk/credential-provider-sso": "^3.972.38", "@aws-sdk/credential-provider-web-identity": "^3.972.38", "@aws-sdk/nested-clients": "^3.997.6", "@aws-sdk/types": "^3.973.8", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-oDzUBu2MGJFgoar05sPMCwSrhw44ASyccrHzj66vO69OZqi7I6hZZxXfuPLC8OCzW7C+sU+bI73XHij41yekgQ=="],
+
+ "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.38", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/nested-clients": "^3.997.6", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-g1NosS8qe4OF++G2UFCM5ovSkgipC7YYor5KCWatG0UoMSO5YFj9C8muePlyVmOBV/WTI16Jo3/s1NUo/o1Bww=="],
+
+ "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.39", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.34", "@aws-sdk/credential-provider-http": "^3.972.36", "@aws-sdk/credential-provider-ini": "^3.972.38", "@aws-sdk/credential-provider-process": "^3.972.34", "@aws-sdk/credential-provider-sso": "^3.972.38", "@aws-sdk/credential-provider-web-identity": "^3.972.38", "@aws-sdk/types": "^3.973.8", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-HEswDQyxUtadoZ/bJsPPENHg7R0Lzym5LuMksJeHvqhCOpP+rtkDLKI4/ZChH4w3cf5kG8n6bZuI8PzajoiqMg=="],
+
+ "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.34", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-T3IFs4EVmVi1dVN5RciFnklCANSzvrQd/VuHY9ThHSQmYkTogjcGkoJEr+oNUPQZnso52183088NqysMPji1/Q=="],
+
+ "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.38", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/nested-clients": "^3.997.6", "@aws-sdk/token-providers": "3.1041.0", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-5ZxG+t0+3Q3QPh8KEjX6syskhgNf7I0MN7oGioTf6Lm1NTjfP7sIcYGNsthXC2qR8vcD3edNZwCr2ovfSSWuRA=="],
+
+ "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.38", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/nested-clients": "^3.997.6", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-lYHFF30DGI20jZcYX8cm6Ns0V7f1dDN6g/MBDLTyD/5iw+bXs3yBr2iAiHDkx4RFU5JgsnZvCHYKiRVPRdmOgw=="],
+
+ "@aws-sdk/dynamodb-codec": ["@aws-sdk/dynamodb-codec@3.973.8", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@smithy/core": "^3.23.17", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-dYQ/cQqHZd23hcl8oEGwPphTqyGnmvf2HrVmz4J90Q5Bv89oJjlwcBcifiiTvApqsVpx7Pr0IebMpkYwWJvZlQ=="],
+
+ "@aws-sdk/endpoint-cache": ["@aws-sdk/endpoint-cache@3.972.5", "", { "dependencies": { "mnemonist": "0.38.3", "tslib": "^2.6.2" } }, "sha512-itVdge0NozgtgmtbZ25FVwWU3vGlE7x7feE/aOEJNkQfEpbkrF8Rj1QmnK+2blFfYE1xWt/iU+6/jUp/pv1+MA=="],
+
+ "@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Vbc2frZH7wXlMNd+ZZSXUEs/l1Sv8Jj4zUnIfwrYF5lwaLdXHZ9xx4U3rjUcaye3HRhFVc+E5DbBxpRAbB16BA=="],
+
+ "@aws-sdk/middleware-endpoint-discovery": ["@aws-sdk/middleware-endpoint-discovery@3.972.11", "", { "dependencies": { "@aws-sdk/endpoint-cache": "^3.972.5", "@aws-sdk/types": "^3.973.8", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-vXARCZVFQHdsd6qPPZyC/hh+5x2XsCYKqUQDCqnUlpGpChMpDojOOacQWdLJ+FFXKN8X3cmLOGrtgx/zysCKqQ=="],
+
+ "@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-2Yn0f1Qiq/DjxYR3wfI3LokXnjOhFM7Ssn4LTdFDIxRMCE6I32MAsVnhPX1cUZsuVA9tiZtwwhlSLAtFGxAZlQ=="],
+
+ "@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.974.16", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "^3.974.8", "@aws-sdk/crc64-nvme": "^3.972.7", "@aws-sdk/types": "^3.973.8", "@smithy/is-array-buffer": "^4.2.2", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-stream": "^4.5.25", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6ru8doI0/XzszqLIPXf0E/V7HhAw1Pu94010XCKYtBUfD0LxF0BuOzrUf8OQGR6j2o6wgKTHUniOmndQycHwCA=="],
+
+ "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg=="],
+
+ "@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-rI3NZvJcEvjoD0+0PI0iUAwlPw2IlSlhyvgBK/3WkKJQE/YiKFedd9dMN2lVacdNxPNhxL/jzQaKQdrGtQagjQ=="],
+
+ "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ=="],
+
+ "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ=="],
+
+ "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.37", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/core": "^3.23.17", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-stream": "^4.5.25", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Km7M+i8DrLArVzrid1gfxeGhYHBd3uxvE77g0s5a52zPSVosxzQBnJ0gwWb6NIp/DOk8gsBMhi7V+cpJG0ndTA=="],
+
+ "@aws-sdk/middleware-sdk-sqs": ["@aws-sdk/middleware-sdk-sqs@3.972.22", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-DtR3mEiOUJcnEX/QuXmvbJto6xvQzp2ftnHb29c0aQYdmmzbKf0gsu9ovx1i/yy4ZR6m0rttTucS0iiP32dlGA=="],
+
+ "@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-Gli9A0u8EVVb+5bFDGS/QbSVg28w/wpEidg1ggVcSj65BDTdGR6punsOcVjqdiu1i42WHWo51MCvARPIIz9juw=="],
+
+ "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.38", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@smithy/core": "^3.23.17", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-retry": "^4.3.6", "tslib": "^2.6.2" } }, "sha512-iz+B29TXcAZsJpwB+AwG/TTGA5l/VnmMZ2UxtiySOZjI6gCdmviXPwdgzcmuazMy16rXoPY4mYCGe7zdNKfx5A=="],
+
+ "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.6", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.8", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.38", "@aws-sdk/region-config-resolver": "^3.972.13", "@aws-sdk/signature-v4-multi-region": "^3.996.25", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.24", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", "@smithy/middleware-retry": "^4.5.7", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.6.1", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.49", "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.6", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-WBDnqatJl+kGObpfmfSxqnXeYTu3Me8wx8WCtvoxX3pfWrrTv8I4WTMSSs7PZqcRcVh8WeUKMgGFjMG+52SR1w=="],
+
+ "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.13", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/config-resolver": "^4.4.17", "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A=="],
+
+ "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.984.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.6", "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-TaWbfYCwnuOSvDSrgs7QgoaoXse49E7LzUkVOUhoezwB7bkmhp+iojADm7UepCEu4021SquD7NG1xA+WCvmldA=="],
+
+ "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1041.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/nested-clients": "^3.997.6", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-Th7kPI6YPtvJUcdznooXJMy+9rQWjmEF81LxaJssngBzuysK4a/x+l8kjm1zb7nYsUPbndnBdUnwng/3PLvtGw=="],
+
+ "@aws-sdk/types": ["@aws-sdk/types@3.973.8", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw=="],
+
+ "@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA=="],
+
+ "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.984.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-9ebjLA0hMKHeVvXEtTDCCOBtwjb0bOXiuUV06HNeVdgAjH6gj4x4Zwt4IBti83TiyTGOCl5YfZqGx4ehVsasbQ=="],
+
+ "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.5", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ=="],
+
+ "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g=="],
+
+ "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.24", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.38", "@aws-sdk/types": "^3.973.8", "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-ZWwlkjcIp7cEL8ZfTpTAPNkwx25p7xol0xlKoWVVf22+nsjwmLcHYtTPjIV1cSpmB/b6DaK4cb1fSkvCXHgRdw=="],
+
+ "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.22", "", { "dependencies": { "@nodable/entities": "2.1.0", "@smithy/types": "^4.14.1", "fast-xml-parser": "5.7.2", "tslib": "^2.6.2" } }, "sha512-PMYKKtJd70IsSG0yHrdAbxBr+ZWBKLvzFZfD3/urxgf6hXVMzuU5M+3MJ5G67RpOmLBu1fAUN65SbWuKUCOlAA=="],
+
+ "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="],
+
"@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
"@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
@@ -182,6 +304,8 @@
"@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="],
+ "@chenglou/pretext": ["@chenglou/pretext@0.0.6", "", {}, "sha512-U10s4tFeyu3oVHfXuNWwZSKqHXefhaigpcBkGj60qQFRJ+yUoQ+ez3cGJelP7BWDAB58HCgjcTSmOcg+77afBQ=="],
+
"@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@11.1.2", "", { "dependencies": { "@chevrotain/gast": "11.1.2", "@chevrotain/types": "11.1.2", "lodash-es": "4.17.23" } }, "sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q=="],
"@chevrotain/gast": ["@chevrotain/gast@11.1.2", "", { "dependencies": { "@chevrotain/types": "11.1.2", "lodash-es": "4.17.23" } }, "sha512-Z9zfXR5jNZb1Hlsd/p+4XWeUFugrHirq36bKzPWDSIacV+GPSVXdk+ahVWZTwjhNwofAWg/sZg58fyucKSQx5g=="],
@@ -196,6 +320,20 @@
"@clack/prompts": ["@clack/prompts@1.2.0", "", { "dependencies": { "@clack/core": "1.2.0", "fast-string-width": "^1.1.0", "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w=="],
+ "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.5.0", "", {}, "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg=="],
+
+ "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.16.1", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": ">1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw=="],
+
+ "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260430.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-ADohZUHf7NBvPp2PdZig2Opxx+hDkk3ve7jrTne3JRx9kDSB73zc4LzcEeEN8LKkbAcqZmvfRJfpChSlusu0lA=="],
+
+ "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260430.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-/DoYC/1wHs+YRZzzqSQg1/EHB4hiv1yV5U8FnmapRRIzVaPtnt+ApeOXeMrIdKidgKOI8TqQzgBU8xbIM7Cl4Q=="],
+
+ "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260430.1", "", { "os": "linux", "cpu": "x64" }, "sha512-koJhBWvEVZPKCVFtMLp2iMHlYr+lFCF47wGbnlKdHVlemV0zTxJEyHI8aLlrhPLhBmOmYLp46rXw09/qJkRIhQ=="],
+
+ "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260430.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-hMdapNAzNQZDXGGkg4Slydc3fRJP5FUZLJVVcZCW/+imhhJro9Z1rv5n/wfR+txKoSWhTYR8eOp8Pyi2bzLzlw=="],
+
+ "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260430.1", "", { "os": "win32", "cpu": "x64" }, "sha512-jS3ffixjb5USOwz4frw4WzCz0HrjVxkgyU3WiYb06N7hBAfN6eOrveAJ4QRef0+suK4V1vQFoB1oKdRBsXe9Dw=="],
+
"@codemirror/autocomplete": ["@codemirror/autocomplete@6.20.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A=="],
"@codemirror/commands": ["@codemirror/commands@6.10.3", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q=="],
@@ -214,14 +352,72 @@
"@codemirror/view": ["@codemirror/view@6.40.0", "", { "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-WA0zdU7xfF10+5I3HhUUq3kqOx3KjqmtQ9lqZjfK7jtYk4G72YW9rezcSywpaUMCWOMlq+6E0pO1IWg1TNIhtg=="],
+ "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
+
"@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="],
+ "@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.31.0", "", { "dependencies": { "commander": "^11.1.0", "dotenv": "^16.4.5", "eciesjs": "^0.4.10", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "picomatch": "^4.0.2", "which": "^4.0.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js", "git-dotenvx": "src/cli/dotenvx.js" } }, "sha512-GeDxvtjiRuoyWVU9nQneId879zIyNdL05bS7RKiqMkfBSKpHMWHLoRyRqjYWLaXmX/llKO1hTlqHDmatkQAjPA=="],
+
+ "@ecies/ciphers": ["@ecies/ciphers@0.2.6", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g=="],
+
"@effect/platform": ["@effect/platform@0.90.3", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.33.0", "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.17.7" } }, "sha512-XvQ37yzWQKih4Du2CYladd1i/MzqtgkTPNCaN6Ku6No4CK83hDtXIV/rP03nEoBg2R3Pqgz6gGWmE2id2G81HA=="],
"@emnapi/runtime": ["@emnapi/runtime@1.9.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw=="],
"@emoji-mart/data": ["@emoji-mart/data@1.2.1", "", {}, "sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw=="],
+ "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
+
+ "@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
+
+ "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
+
+ "@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
+
+ "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
+
+ "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
+
+ "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
+
+ "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
+
+ "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
+
+ "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
+
+ "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
+
+ "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
+
+ "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
+
+ "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
+
+ "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
+
+ "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
+
+ "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
+
+ "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
+
+ "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
+
+ "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
+
+ "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
+
+ "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
+
+ "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
+
+ "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
+
+ "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
+
+ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
+
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
@@ -258,6 +454,10 @@
"@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="],
+ "@hocuspocus/common": ["@hocuspocus/common@3.4.4", "", { "dependencies": { "lib0": "^0.2.87" } }, "sha512-RykIJ0tsHHMP4Xk+4UCbc7SO5LgGxGUSTdbh6anJEsaALAyqinf1Nn5HYuMjLPolAmsar1v++m9zufR09NLpXA=="],
+
+ "@hocuspocus/provider": ["@hocuspocus/provider@3.4.4", "", { "dependencies": { "@hocuspocus/common": "^3.4.4", "@lifeomic/attempt": "^3.0.2", "lib0": "^0.2.87", "ws": "^8.17.1" }, "peerDependencies": { "y-protocols": "^1.0.6", "yjs": "^13.6.8" } }, "sha512-KbsMAfdYcIJD8eMU/5QnpXcSOvIWAcCNI33FSRSaKCIpYBFtAwkYIwWnZJmPZ8a1BMAtqQc+uvy9+UQf7GHnGQ=="],
+
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
"@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="],
@@ -320,12 +520,16 @@
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
+ "@isaacs/cliui": ["@isaacs/cliui@9.0.0", "", {}, "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg=="],
+
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
+ "@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="],
+
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
@@ -340,6 +544,8 @@
"@lezer/lr": ["@lezer/lr@1.4.8", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA=="],
+ "@lifeomic/attempt": ["@lifeomic/attempt@3.1.0", "", {}, "sha512-QZqem4QuAnAyzfz+Gj5/+SLxqwCAw2qmt7732ZXodr6VDWGeYLG6w1i/vYLa55JQM9wRuBKLmXmiZ2P0LtE5rw=="],
+
"@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="],
"@mermaid-js/parser": ["@mermaid-js/parser@1.0.1", "", { "dependencies": { "langium": "^4.0.0" } }, "sha512-opmV19kN1JsK0T6HhhokHpcVkqKpF+x2pPDKKM2ThHtZAB5F4PROopk0amuVYK5qMrIA4erzpNm8gmPNJgMDxQ=="],
@@ -384,6 +590,20 @@
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw=="],
+ "@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="],
+
+ "@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="],
+
+ "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
+
+ "@nodable/entities": ["@nodable/entities@2.1.0", "", {}, "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA=="],
+
+ "@node-minify/core": ["@node-minify/core@8.0.6", "", { "dependencies": { "@node-minify/utils": "8.0.6", "glob": "9.3.5", "mkdirp": "1.0.4" } }, "sha512-/vxN46ieWDLU67CmgbArEvOb41zlYFOkOtr9QW9CnTrBLuTyGgkyNWC2y5+khvRw3Br58p2B5ZVSx/PxCTru6g=="],
+
+ "@node-minify/terser": ["@node-minify/terser@8.0.6", "", { "dependencies": { "@node-minify/utils": "8.0.6", "terser": "5.16.9" } }, "sha512-grQ1ipham743ch2c3++C8Isk6toJnxJSyDiwUI/IWUCh4CZFD6aYVw6UAY40IpCnjrq5aXGwiv5OZJn6Pr0hvg=="],
+
+ "@node-minify/utils": ["@node-minify/utils@8.0.6", "", { "dependencies": { "gzip-size": "6.0.0" } }, "sha512-csY4qcR7jUwiZmkreNTJhcypQfts2aY2CK+a+rXgXUImZiZiySh0FvwHjRnlqWKvg+y6ae9lHFzDRjBTmqlTIQ=="],
+
"@oozcitak/dom": ["@oozcitak/dom@1.15.10", "", { "dependencies": { "@oozcitak/infra": "1.0.8", "@oozcitak/url": "1.0.4", "@oozcitak/util": "8.3.8" } }, "sha512-0JT29/LaxVgRcGKvHmSrUTEvZ8BXvZhGl2LASRUgHqDTC1M5g1pLmVv56IYNyt3bG2CUjDkc67wnyZC14pbQrQ=="],
"@oozcitak/infra": ["@oozcitak/infra@1.0.8", "", { "dependencies": { "@oozcitak/util": "8.3.8" } }, "sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg=="],
@@ -392,6 +612,10 @@
"@oozcitak/util": ["@oozcitak/util@8.3.8", "", {}, "sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ=="],
+ "@opennextjs/aws": ["@opennextjs/aws@3.10.4", "", { "dependencies": { "@ast-grep/napi": "^0.40.5", "@aws-sdk/client-cloudfront": "3.984.0", "@aws-sdk/client-dynamodb": "3.984.0", "@aws-sdk/client-lambda": "3.984.0", "@aws-sdk/client-s3": "3.984.0", "@aws-sdk/client-sqs": "3.984.0", "@node-minify/core": "^8.0.6", "@node-minify/terser": "^8.0.6", "@tsconfig/node18": "^1.0.3", "aws4fetch": "^1.0.20", "chalk": "^5.6.2", "cookie": "^1.0.2", "esbuild": "0.25.4", "express": "^5.1.0", "path-to-regexp": "^6.3.0", "urlpattern-polyfill": "^10.1.0", "yaml": "^2.8.1" }, "peerDependencies": { "next": ">=15.5.15 <16 || >=16.2.3" }, "bin": { "open-next": "dist/index.js" } }, "sha512-xVmWHGdptJgVhQivuoeAYqsWpIgGoDEeZJC6AYMgvQYisDicGuS7gh10Z8MEmrgsJxNGdZ0arCCDieGxM88afw=="],
+
+ "@opennextjs/cloudflare": ["@opennextjs/cloudflare@1.19.6", "", { "dependencies": { "@ast-grep/napi": "^0.40.5", "@dotenvx/dotenvx": "1.31.0", "@opennextjs/aws": "3.10.4", "ci-info": "^4.2.0", "cloudflare": "^4.4.1", "comment-json": "^4.5.1", "enquirer": "^2.4.1", "glob": "^12.0.0", "ts-tqdm": "^0.8.6", "yargs": "^18.0.0" }, "peerDependencies": { "next": ">=15.5.15 <16 || >=16.2.3", "wrangler": "^4.86.0" }, "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }, "sha512-2Qd0IbcqPdsjsiaFrmACxuJHR8Pxm1JrUJIn/tFoTu+HsfFR7W921NZI50KQ5bulqg1e//Lb5TonwQlEgmSBGw=="],
+
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="],
@@ -458,6 +682,8 @@
"@platejs/mention": ["@platejs/mention@53.0.0", "", { "dependencies": { "@platejs/combobox": "53.0.0", "react-compiler-runtime": "^1.0.0" }, "peerDependencies": { "platejs": ">=53.0.0", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-x7kr9SgFlO0yoJdRcv4mIeiM47cnDEIUJBMYFAQEI42JdwcVKUBiPP4bCZwkQKpGndxmlXT9iTz8+w/UBqDJOA=="],
+ "@platejs/pagination": ["@platejs/pagination@file:vendor/platejs-pagination", { "dependencies": { "@platejs/footnote": "^53.0.0", "react-compiler-runtime": "^1.0.0" }, "peerDependencies": { "platejs": ">=53.0.0", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }],
+
"@platejs/resizable": ["@platejs/resizable@53.0.0", "", { "dependencies": { "react-compiler-runtime": "^1.0.0" }, "peerDependencies": { "platejs": ">=53.0.0", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-/Wl8DmjByOTib+wKAcEnE3S3PMcpKcrupCOJvUa+vnn/cqXV3cwWzapVZ3bojF6sFWttMsLprvIGhpS/gpl5FQ=="],
"@platejs/selection": ["@platejs/selection@53.0.0", "", { "dependencies": { "copy-to-clipboard": "^3.3.3", "react-compiler-runtime": "^1.0.0" }, "peerDependencies": { "platejs": ">=53.0.0", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-hC5boNfMixwM3ygKnGfTXXZLgMBzonjkJy3TAl+R3EEd/ki75zY1jiqYvWNkAll7gSZplE6fe+AGUNHbdhE+pA=="],
@@ -476,6 +702,14 @@
"@platejs/utils": ["@platejs/utils@53.0.3", "", { "dependencies": { "@platejs/core": "^53.0.0", "@platejs/slate": "^53.0.0", "@udecode/react-utils": "^52.3.4", "@udecode/utils": "^52.3.4", "clsx": "^2.1.1", "lodash": "^4.17.21", "react-compiler-runtime": "^1.0.0" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-lQafEO0Bc04qrtr0Ilx2AYmvEZiEYEvoyH5w4W0BWIyPonpvBSdVXu3YuUZTl9JbNk6EBkF6UAiYk+uM5v9b8g=="],
+ "@platejs/yjs": ["@platejs/yjs@53.0.0", "", { "dependencies": { "@slate-yjs/core": "^1.0.2", "react-compiler-runtime": "^1.0.0", "y-protocols": "^1.0.7", "yjs": "^13.6.29" }, "peerDependencies": { "@hocuspocus/provider": "^3.4.0", "platejs": ">=53.0.0", "react": ">=18.0.0", "react-dom": ">=18.0.0", "y-webrtc": "10.3.0" } }, "sha512-4vRa8rqnlLndxZFNSg0N6eAcJLeONG2yujh4MydN2nMw1yXiVJa1Ey2nXoJveDn8tvY8LNFv7J6wbxPkb6b83Q=="],
+
+ "@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="],
+
+ "@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="],
+
+ "@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="],
+
"@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="],
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
@@ -604,6 +838,110 @@
"@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="],
+ "@slate-yjs/core": ["@slate-yjs/core@1.0.2", "", { "dependencies": { "y-protocols": "^1.0.5" }, "peerDependencies": { "slate": ">=0.70.0", "yjs": "^13.5.29" } }, "sha512-X0hLFJbQu9c1ItWBaNuEn0pqcXYK76KCp8C4Gvy/VaTQVMo1VgAb2WiiJ0Je/AyuIYEPPSTNVOcyrGHwgA7e6Q=="],
+
+ "@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw=="],
+
+ "@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.2.3", "", { "dependencies": { "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw=="],
+
+ "@smithy/config-resolver": ["@smithy/config-resolver@4.4.17", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-TzDZcAnhTyAHbXVxWZo7/tEcrIeFq20IBk8So3OLOetWpR8EwY/yEqBMBFaJMeyEiREDq4NfEl+qO3OAUD+vbQ=="],
+
+ "@smithy/core": ["@smithy/core@3.23.17", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-stream": "^4.5.25", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-x7BlLbUFL8NWCGjMF9C+1N5cVCxcPa7g6Tv9B4A2luWx3be3oU8hQ96wIwxe/s7OhIzvoJH73HAUSg5JXVlEtQ=="],
+
+ "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.14", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg=="],
+
+ "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.14", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw=="],
+
+ "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.14", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-8IelTCtTctWRbb+0Dcy+C0aICh1qa0qWXqgjcXDmMuCvPJRnv26hiDZoAau2ILOniki65mCPKqOQs/BaWvO4CQ=="],
+
+ "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-sqHiHpYRYo3FJlaIxD1J8PhbcmJAm7IuM16mVnwSkCToD7g00IBZzKuiLNMGmftULmEUX6/UAz8/NN5uMP8bVA=="],
+
+ "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.14", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-Ht/8BuGlKfFTy0H3+8eEu0vdpwGztCnaLLXtpXNdQqiR7Hj4vFScU3T436vRAjATglOIPjJXronY+1WxxNLSiw=="],
+
+ "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.14", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-lWyt4T2XQZUZgK3tQ3Wn0w3XBvZsK/vjTuJl6bXbnGZBHH0ZUSONTYiK9TgjTTzU54xQr3DRFwpjmhp0oLm3gg=="],
+
+ "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.17", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/querystring-builder": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw=="],
+
+ "@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.15", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.2", "@smithy/chunked-blob-reader-native": "^4.2.3", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-0PJ4Al3fg2nM4qKrAIxyNcApgqHAXcBkN8FeizOz69z0rb26uZ6lMESYtxegaTlXB5Hj84JfwMPavMrwDMjucA=="],
+
+ "@smithy/hash-node": ["@smithy/hash-node@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g=="],
+
+ "@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-tw4GANWkZPb6+BdD4Fgucqzey2+r73Z/GRo9zklsCdwrnxxumUV83ZIaBDdudV4Ylazw3EPTiJZhpX42105ruQ=="],
+
+ "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw=="],
+
+ "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="],
+
+ "@smithy/md5-js": ["@smithy/md5-js@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-V2v0vx+h0iUSNG1Alt+GNBMSLGCrl9iVsdd+Ap67HPM9PN479x12V8LkuMoKImNZxn3MXeuyUjls+/7ZACZghA=="],
+
+ "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.14", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw=="],
+
+ "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.32", "", { "dependencies": { "@smithy/core": "^3.23.17", "@smithy/middleware-serde": "^4.2.20", "@smithy/node-config-provider": "^4.3.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-ZZkgyjnJppiZbIm6Qbx92pbXYi1uzenIvGhBSCDlc7NwuAkiqSgS75j1czAD25ZLs2FjMjYy1q7gyRVWG6JA0Q=="],
+
+ "@smithy/middleware-retry": ["@smithy/middleware-retry@4.5.7", "", { "dependencies": { "@smithy/core": "^3.23.17", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/service-error-classification": "^4.3.1", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.6", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-bRt6ZImqVSeTk39Nm81K20ObIiAZ3WefY7G6+iz/0tZjs4dgRRjvRX2sgsH+zi6iDCRR/aQvQofLKxxz4rPBZg=="],
+
+ "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.20", "", { "dependencies": { "@smithy/core": "^3.23.17", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-Lx9JMO9vArPtiChE3wbEZ5akMIDQpWQtlu90lhACQmNOXcGXRbaDywMHDzuDZ2OkZzP+9wQfZi3YJT9F67zTQQ=="],
+
+ "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA=="],
+
+ "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.14", "", { "dependencies": { "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg=="],
+
+ "@smithy/node-http-handler": ["@smithy/node-http-handler@4.6.1", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/querystring-builder": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-iB+orM4x3xrr57X3YaXazfKnntl0LHlZB1kcXSGzMV1Tt0+YwEjGlbjk/44qEGtBzXAz6yFDzkYTKSV6Pj2HUg=="],
+
+ "@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="],
+
+ "@smithy/protocol-http": ["@smithy/protocol-http@5.3.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ=="],
+
+ "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A=="],
+
+ "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw=="],
+
+ "@smithy/service-error-classification": ["@smithy/service-error-classification@4.3.1", "", { "dependencies": { "@smithy/types": "^4.14.1" } }, "sha512-aUQuDGh760ts/8MU+APjIZhlLPKhIIfqyzZaJikLEIMrdxFvxuLYD0WxWzaYWpmLbQlXDe9p7EWM3HsBe0K6Gw=="],
+
+ "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.9", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ=="],
+
+ "@smithy/signature-v4": ["@smithy/signature-v4@5.3.14", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA=="],
+
+ "@smithy/smithy-client": ["@smithy/smithy-client@4.12.13", "", { "dependencies": { "@smithy/core": "^3.23.17", "@smithy/middleware-endpoint": "^4.4.32", "@smithy/middleware-stack": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-stream": "^4.5.25", "tslib": "^2.6.2" } }, "sha512-y/Pcj1V9+qG98gyu1gvftHB7rDpdh+7kIBIggs55yGm3JdtBV8GT8IFF3a1qxZ79QnaJHX9GXzvBG6tAd+czJA=="],
+
+ "@smithy/types": ["@smithy/types@4.14.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg=="],
+
+ "@smithy/url-parser": ["@smithy/url-parser@4.2.14", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ=="],
+
+ "@smithy/util-base64": ["@smithy/util-base64@4.3.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="],
+
+ "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ=="],
+
+ "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g=="],
+
+ "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="],
+
+ "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ=="],
+
+ "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.49", "", { "dependencies": { "@smithy/property-provider": "^4.2.14", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-a5bNrdiONYB/qE2BuKegvUMd/+ZDwdg4vsNuuSzYE8qs2EYAdK9CynL+Rzn29PbPiUqoz/cbpRbcLzD5lEevHw=="],
+
+ "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.54", "", { "dependencies": { "@smithy/config-resolver": "^4.4.17", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-g1cvrJvOnzeJgEdf7AE4luI7gp6L8weE0y9a9wQUSGtjb8QRHDbCJYuE4Sy0SD9N8RrnNPFsPltAz/OSoBR9Zw=="],
+
+ "@smithy/util-endpoints": ["@smithy/util-endpoints@3.4.2", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-a55Tr+3OKld4TTtnT+RhKOQHyPxm3j/xL4OR83WBUhLJaKDS9dnJ7arRMOp3t31dcLhApwG9bgvrRXBHlLdIkg=="],
+
+ "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg=="],
+
+ "@smithy/util-middleware": ["@smithy/util-middleware@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw=="],
+
+ "@smithy/util-retry": ["@smithy/util-retry@4.3.8", "", { "dependencies": { "@smithy/service-error-classification": "^4.3.1", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-LUIxbTBi+OpvXpg91poGA6BdyoleMDLnfXjVDqyi2RvZmTveY5loE/FgYUBCR5LU2BThW2SoZRh8dTIIy38IPw=="],
+
+ "@smithy/util-stream": ["@smithy/util-stream@4.5.25", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.6.1", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-/PFpG4k8Ze8Ei+mMKj3oiPICYekthuzePZMgZbCqMiXIHHf4n2aZ4Ps0aSRShycFTGuj/J6XldmC0x0DwednIA=="],
+
+ "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw=="],
+
+ "@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
+
+ "@smithy/util-waiter": ["@smithy/util-waiter@4.3.0", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-JyjYmLAfS+pdxF92o4yLgEoy0zhayKTw73FU1aofLWwLcJw7iSqIY2exGmMTrl/lmZugP5p/zxdFSippJDfKWA=="],
+
+ "@smithy/uuid": ["@smithy/uuid@1.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g=="],
+
+ "@speed-highlight/core": ["@speed-highlight/core@1.2.15", "", {}, "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw=="],
+
"@standard-schema/spec": ["@standard-schema/spec@1.0.0-beta.4", "", {}, "sha512-d3IxtzLo7P1oZ8s8YNvxzBUXRXojSut8pbPrTYtzsc5sn4+53jVqbk66pQerSZbZSJZQux6LkclB/+8IDordHg=="],
"@svta/cml-608": ["@svta/cml-608@1.0.1", "", {}, "sha512-Y/Ier9VPUSOBnf0bJqdDyTlPrt4dDB+jk5mYHa1bnD2kcRl8qn7KkW3PRuj4w1aVN+BS2eHmsLxodt7P2hylUg=="],
@@ -660,6 +998,8 @@
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.4", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.4", "@tailwindcss/oxide": "4.2.4", "postcss": "^8.5.6", "tailwindcss": "4.2.4" } }, "sha512-wgAVj6nUWAolAu8YFvzT2cTBIElWHkjZwFYovF+xsqKsW2ADxM/X2opxj5NsF/qVccAOjRNe8X2IdPzMsWyHTg=="],
+ "@tsconfig/node18": ["@tsconfig/node18@1.0.3", "", {}, "sha512-RbwvSJQsuN9TB04AQbGULYfOGE/RnSFk/FLQ5b0NmDf5Kx2q/lABZbHQPKCO1vZ6Fiwkplu+yb9pGdLy1iGseQ=="],
+
"@types/d3": ["@types/d3@7.4.3", "", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="],
"@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
@@ -744,6 +1084,8 @@
"@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
+ "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
+
"@types/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
@@ -794,22 +1136,38 @@
"@xmldom/xmldom": ["@xmldom/xmldom@0.8.11", "", {}, "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw=="],
+ "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
+
+ "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
+
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
+ "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
+
"ai": ["ai@6.0.169", "", { "dependencies": { "@ai-sdk/gateway": "3.0.105", "@ai-sdk/provider": "3.0.9", "@ai-sdk/provider-utils": "4.0.24", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-eDAIc8bJJ3Ktk5qvBvWfAK7Uwj6zrOMI9VvQ6yLfF+7HdqnR0TSxgkLuIONrmowB2OujeAuVF9jalpNPJSN57g=="],
"ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
"ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
+ "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
+
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
"argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
"aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="],
+ "array-timsort": ["array-timsort@1.0.3", "", {}, "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ=="],
+
+ "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
+
+ "aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
+
"babel-plugin-macros": ["babel-plugin-macros@3.1.0", "", { "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg=="],
"babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="],
@@ -832,10 +1190,16 @@
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
+ "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="],
+
"bluebird": ["bluebird@3.4.7", "", {}, "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA=="],
+ "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
+
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
+ "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="],
+
"brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
@@ -846,6 +1210,12 @@
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
+ "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
+
+ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
+
+ "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
+
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
@@ -864,6 +1234,8 @@
"ce-la-react": ["ce-la-react@0.3.2", "", { "peerDependencies": { "react": ">=17.0.0" } }, "sha512-QJ6k4lOD/btI08xG8jBPxRCGXvCnusGGkTsiXk0u3NqUu/W+BXRnFD4PYjwtqh8AWmGa5LDbGk0fLQsqr0nSMA=="],
+ "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
+
"char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="],
"character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
@@ -884,12 +1256,18 @@
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
+ "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="],
+
"citty": ["citty@0.2.1", "", {}, "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg=="],
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
+ "cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="],
+
+ "cloudflare": ["cloudflare@4.5.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-fPcbPKx4zF45jBvQ0z7PCdgejVAPBBCZxwqk1k7krQNfpM07Cfj97/Q6wBzvYqlWXx/zt1S9+m8vnfCe06umbQ=="],
+
"cloudflare-video-element": ["cloudflare-video-element@1.3.5", "", {}, "sha512-zj9gjJa6xW8MNrfc4oKuwgGS0njRLpOlQjdifbuNxvy8k4Y3pKCyKCMG2XIsjd2iQGhgjS57b1P5VWdJlxcXBw=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
@@ -902,14 +1280,26 @@
"color-name": ["color-name@2.1.0", "", {}, "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg=="],
+ "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
+
"commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
+ "comment-json": ["comment-json@4.6.2", "", { "dependencies": { "array-timsort": "^1.0.3", "esprima": "^4.0.1" } }, "sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w=="],
+
"compute-scroll-into-view": ["compute-scroll-into-view@3.1.1", "", {}, "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="],
"confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
+ "content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="],
+
+ "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
+
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
+ "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
+
+ "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
+
"copy-to-clipboard": ["copy-to-clipboard@3.3.3", "", { "dependencies": { "toggle-selection": "^1.0.6" } }, "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA=="],
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
@@ -1030,6 +1420,10 @@
"delaunator": ["delaunator@5.0.1", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw=="],
+ "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
+
+ "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
+
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
@@ -1060,44 +1454,68 @@
"domutils": ["domutils@1.7.0", "", { "dependencies": { "dom-serializer": "0", "domelementtype": "1" } }, "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg=="],
+ "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
+
"duck": ["duck@0.1.12", "", { "dependencies": { "underscore": "^1.13.1" } }, "sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
+ "duplexer": ["duplexer@0.1.2", "", {}, "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="],
+
+ "eciesjs": ["eciesjs@0.4.18", "", { "dependencies": { "@ecies/ciphers": "^0.2.5", "@noble/ciphers": "^1.3.0", "@noble/curves": "^1.9.7", "@noble/hashes": "^1.8.0" } }, "sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ=="],
+
+ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
+
"effect": ["effect@3.17.7", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-dpt0ONUn3zzAuul6k4nC/coTTw27AL5nhkORXgTi6NfMPzqWYa1M05oKmOMTxpVSTKepqXVcW9vIwkuaaqx9zA=="],
"electron-to-chromium": ["electron-to-chromium@1.5.313", "", {}, "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA=="],
"elkjs": ["elkjs@0.9.3", "", {}, "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ=="],
+ "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
+
"emojilib": ["emojilib@2.4.0", "", {}, "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw=="],
"emoticon": ["emoticon@4.1.0", "", {}, "sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ=="],
+ "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
+
"encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="],
"enhanced-resolve": ["enhanced-resolve@5.20.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ=="],
+ "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="],
+
"ent": ["ent@2.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "punycode": "^1.4.1", "safe-regex-test": "^1.1.0" } }, "sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw=="],
"entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
+ "err-code": ["err-code@3.0.1", "", {}, "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA=="],
+
"error": ["error@4.4.0", "", { "dependencies": { "camelize": "^1.0.0", "string-template": "~0.2.0", "xtend": "~4.0.0" } }, "sha512-SNDKualLUtT4StGFP7xNfuFybL2f6iJujFtrWuvJqGbVQGaN+adE23veqzPz1hjUjTunLi2EnJ+0SJxtbJreKw=="],
"error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
+ "error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="],
+
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
+ "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
+
"es6-promise-pool": ["es6-promise-pool@2.5.0", "", {}, "sha512-VHErXfzR/6r/+yyzPKeBvO0lgjfC5cbDCQWjWwMZWSb6YU39TGIl51OUmCfWCq4ylMdJSB8zkz2vIuIeIxXApA=="],
+ "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
+
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"escape-goat": ["escape-goat@3.0.0", "", {}, "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw=="],
+ "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
+
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"eslint": ["eslint@10.2.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.5.5", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q=="],
@@ -1124,12 +1542,20 @@
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
+ "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
+
"ev-store": ["ev-store@7.0.0", "", { "dependencies": { "individual": "^3.0.0" } }, "sha512-otazchNRnGzp2YarBJ+GXKVGvhxVATB1zmaStxJBYet0Dyq7A9VhH8IUEB/gRcL6Ch52lfpgPTRJ2m49epyMsQ=="],
"eve-raphael": ["eve-raphael@0.5.0", "", {}, "sha512-jrxnPsCGqng1UZuEp9DecX/AuSyAszATSjf4oEcRxvfxa1Oux4KkIPKBAAWWnpdwfARtr+Q0o9aPYWjsROD7ug=="],
+ "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
+
"eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="],
+ "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
+
+ "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
+
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
"fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="],
@@ -1146,6 +1572,10 @@
"fast-wrap-ansi": ["fast-wrap-ansi@0.1.6", "", { "dependencies": { "fast-string-width": "^1.1.0" } }, "sha512-HlUwET7a5gqjURj70D5jl7aC3Zmy4weA1SHUfM0JFI0Ptq987NH2TwbBFLoERhfwk+E+eaq4EK3jXoT+R3yp3w=="],
+ "fast-xml-builder": ["fast-xml-builder@1.1.7", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-Yh7/7rQuMXICNr0oMYDR2yHP6oUvmQsTToFeOWj/kIDhAwQ+c4Ol/lbcwOmEM5OHYQmh6S6EQSQ1sljCKP36bQ=="],
+
+ "fast-xml-parser": ["fast-xml-parser@5.7.2", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.5", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w=="],
+
"fastest-levenshtein": ["fastest-levenshtein@1.0.16", "", {}, "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
@@ -1156,6 +1586,8 @@
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
+ "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
+
"find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="],
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
@@ -1166,8 +1598,22 @@
"flowchart.js": ["flowchart.js@1.18.0", "", { "dependencies": { "raphael": "2.3.0" } }, "sha512-1rZflSLyrj5/+ryzU+qIQr6IGysPIHT4UrgN+6IaVv7pLDSLu7CvC9e0X7FU6ocpRAEtHk5+UaFXv9NZKRoG3g=="],
+ "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
+
+ "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
+
+ "form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="],
+
+ "formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
+
+ "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
+
"fractional-indexing": ["fractional-indexing@3.2.0", "", {}, "sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ=="],
+ "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
+
+ "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
+
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
@@ -1176,13 +1622,21 @@
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
+ "get-browser-rtc": ["get-browser-rtc@1.1.0", "", {}, "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ=="],
+
+ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
+
+ "get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="],
+
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
- "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
+ "get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
+
+ "glob": ["glob@12.0.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-5Qcll1z7IKgHr5g485ePDdHcNQY0k2dtv/bjYy0iuyGxQw2qSOiiXUXJ+AYQpg3HNoUMHqAruX478Jeev7UULw=="],
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
@@ -1194,6 +1648,8 @@
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
+ "gzip-size": ["gzip-size@6.0.0", "", { "dependencies": { "duplexer": "^0.1.2" } }, "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q=="],
+
"hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="],
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
@@ -1222,7 +1678,15 @@
"htmlparser2": ["htmlparser2@3.10.1", "", { "dependencies": { "domelementtype": "^1.3.1", "domhandler": "^2.3.0", "domutils": "^1.5.1", "entities": "^1.1.1", "inherits": "^2.0.1", "readable-stream": "^3.1.1" } }, "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ=="],
- "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
+ "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
+
+ "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
+
+ "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="],
+
+ "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
+
+ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
@@ -1246,6 +1710,8 @@
"internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="],
+ "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
+
"is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="],
"is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="],
@@ -1274,11 +1740,19 @@
"is-plain-object": ["is-plain-object@5.0.0", "", {}, "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="],
+ "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
+
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
+ "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
+
"isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
- "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
+ "isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="],
+
+ "isomorphic.js": ["isomorphic.js@0.2.5", "", {}, "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw=="],
+
+ "jackspeak": ["jackspeak@4.2.3", "", { "dependencies": { "@isaacs/cliui": "^9.0.0" } }, "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg=="],
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
@@ -1350,6 +1824,8 @@
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
+ "lib0": ["lib0@0.2.117", "", { "dependencies": { "isomorphic.js": "^0.2.4" }, "bin": { "0serve": "bin/0serve.js", "0gentesthtml": "bin/gentesthtml.js", "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js" } }, "sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw=="],
+
"lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="],
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
@@ -1400,7 +1876,7 @@
"lowlight": ["lowlight@3.3.0", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.0.0", "highlight.js": "~11.11.0" } }, "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ=="],
- "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
+ "lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
"lucide-react": ["lucide-react@1.13.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-NmK8a8CD1W6VNIzPjObHwhOzBDwmNvk7hhYqKsmKJUvDZjc7yJXR0MRHGwCOzVx4pZlUkCyJs5x+aMzDI0mGIw=="],
@@ -1452,8 +1928,14 @@
"media-tracks": ["media-tracks@0.3.4", "", {}, "sha512-5SUElzGMYXA7bcyZBL1YzLTxH9Iyw1AeYNJxzByqbestrrtB0F3wfiWUr7aROpwodO4fwnxOt78Xjb3o3ONNQg=="],
+ "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
+
"mensch": ["mensch@0.3.4", "", {}, "sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g=="],
+ "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
+
+ "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
+
"mermaid": ["mermaid@11.13.0", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.2", "@mermaid-js/parser": "^1.0.1", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "cytoscape": "^3.33.1", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.19", "dompurify": "^3.3.1", "katex": "^0.16.25", "khroma": "^2.1.0", "lodash-es": "^4.17.23", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0" } }, "sha512-fEnci+Immw6lKMFI8sqzjlATTyjLkRa6axrEgLV2yHTfv8r+h1wjFbV6xeRtd4rUV1cS4EpR9rwp3Rci7TRWDw=="],
"micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="],
@@ -1534,14 +2016,22 @@
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
+ "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
+
"min-document": ["min-document@2.19.2", "", { "dependencies": { "dom-walk": "^0.1.0" } }, "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A=="],
+ "miniflare": ["miniflare@4.20260430.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.24.8", "workerd": "1.20260430.1", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-MWvMm3Siho9Yj7lbJZidLs8hbrRvIcOrif2mnsHQZdvoKfedpea+GaN8XJxbpRcq0B2WzNI1BB1ihdnqes3/ZA=="],
+
"minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
"minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
+ "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
+
"mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="],
+ "mnemonist": ["mnemonist@0.38.3", "", { "dependencies": { "obliterator": "^1.6.1" } }, "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw=="],
+
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
@@ -1564,12 +2054,18 @@
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
+ "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
+
"next": ["next@16.2.4", "", { "dependencies": { "@next/env": "16.2.4", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.4", "@next/swc-darwin-x64": "16.2.4", "@next/swc-linux-arm64-gnu": "16.2.4", "@next/swc-linux-arm64-musl": "16.2.4", "@next/swc-linux-x64-gnu": "16.2.4", "@next/swc-linux-x64-musl": "16.2.4", "@next/swc-win32-arm64-msvc": "16.2.4", "@next/swc-win32-x64-msvc": "16.2.4", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q=="],
"next-tick": ["next-tick@0.2.2", "", {}, "sha512-f7h4svPtl+QidoBv4taKXUjJ70G2asaZ8G28nS0OkqaalX8dwwrtWtyxEDPK62AC00ur/+/E0pUwBwY5EPn15Q=="],
+ "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
+
"node-emoji": ["node-emoji@2.2.0", "", { "dependencies": { "@sindresorhus/is": "^4.6.0", "char-regex": "^1.0.2", "emojilib": "^2.4.0", "skin-tone": "^2.0.0" } }, "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw=="],
+ "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
+
"node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
"node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="],
@@ -1578,12 +2074,26 @@
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
+ "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
+
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
"nypm": ["nypm@0.6.5", "", { "dependencies": { "citty": "^0.2.0", "pathe": "^2.0.3", "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
+ "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
+
+ "object-treeify": ["object-treeify@1.1.33", "", {}, "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A=="],
+
+ "obliterator": ["obliterator@1.6.1", "", {}, "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig=="],
+
+ "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
+
+ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
+
+ "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
+
"open-color": ["open-color@1.9.1", "", {}, "sha512-vCseG/EQ6/RcvxhUcGJiHViOgrtz4x0XbZepXvKik66TMGkvbmjeJrKFyBEx6daG5rNyyd14zYXhz0hZVwQFOw=="],
"optics-ts": ["optics-ts@2.4.1", "", {}, "sha512-HaYzMHvC80r7U/LqAd4hQyopDezC60PO2qF5GuIwALut2cl5rK1VWHsqTp0oqoJJWjiv6uXKqsO+Q2OO0C3MmQ=="],
@@ -1596,6 +2106,8 @@
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
+ "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
+
"package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="],
"pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
@@ -1612,12 +2124,16 @@
"parse5-parser-stream": ["parse5-parser-stream@7.1.2", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="],
+ "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
+
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
"path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="],
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
+ "path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="],
+
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
@@ -1626,6 +2142,8 @@
"path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
+ "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
+
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
@@ -1670,6 +2188,8 @@
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
+ "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
+
"proxy-compare": ["proxy-compare@2.6.0", "", {}, "sha512-8xuCeM3l8yqdmbPoYeLbrAXCBWu19XEYc5/F28f5qOaoAIMyfmBUkl5axiK+x9olUvRlcekvnm98AP9RDngOIw=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
@@ -1678,12 +2198,22 @@
"pwacompat": ["pwacompat@2.0.17", "", {}, "sha512-6Du7IZdIy7cHiv7AhtDy4X2QRM8IAD5DII69mt5qWibC2d15ZU8DmBG1WdZKekG11cChSu4zkSUGPF9sweOl6w=="],
+ "qs": ["qs@6.15.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg=="],
+
+ "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
+
"radix-ui": ["radix-ui@1.4.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-accessible-icon": "1.1.7", "@radix-ui/react-accordion": "1.2.12", "@radix-ui/react-alert-dialog": "1.1.15", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-aspect-ratio": "1.1.7", "@radix-ui/react-avatar": "1.1.10", "@radix-ui/react-checkbox": "1.3.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-context-menu": "2.2.16", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-dropdown-menu": "2.1.16", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-form": "0.1.8", "@radix-ui/react-hover-card": "1.1.15", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-menubar": "1.1.16", "@radix-ui/react-navigation-menu": "1.2.14", "@radix-ui/react-one-time-password-field": "0.1.8", "@radix-ui/react-password-toggle-field": "0.1.3", "@radix-ui/react-popover": "1.1.15", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-progress": "1.1.7", "@radix-ui/react-radio-group": "1.3.8", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-scroll-area": "1.2.10", "@radix-ui/react-select": "2.2.6", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-slider": "1.3.6", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-switch": "1.2.6", "@radix-ui/react-tabs": "1.1.13", "@radix-ui/react-toast": "1.2.15", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-toggle-group": "1.1.11", "@radix-ui/react-toolbar": "1.1.11", "@radix-ui/react-tooltip": "1.2.8", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-escape-keydown": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA=="],
"raf": ["raf@3.4.1", "", { "dependencies": { "performance-now": "^2.1.0" } }, "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA=="],
+ "randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="],
+
+ "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
+
"raphael": ["raphael@2.3.0", "", { "dependencies": { "eve-raphael": "0.5.0" } }, "sha512-w2yIenZAQnp257XUWGni4bLMVxpUpcIl7qgxEgDIXtmSypYtlNxfXWpOBxs7LBTps5sDwhRnrToJrMUrivqNTQ=="],
+ "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
+
"react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="],
"react-compiler-runtime": ["react-compiler-runtime@1.0.0", "", { "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^0.0.0-experimental" } }, "sha512-rRfjYv66HlG8896yPUDONgKzG5BxZD1nV9U6rkm+7VCuvQc903C4MjcoZR4zPw53IKSOX9wMQVpA1IAbRtzQ7w=="],
@@ -1740,6 +2270,8 @@
"roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="],
+ "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
+
"rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="],
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
@@ -1760,14 +2292,32 @@
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
+ "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
+
+ "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
+
"setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
+ "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
+
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
+ "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
+
+ "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="],
+
+ "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
+
+ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
+
+ "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
+
+ "simple-peer": ["simple-peer@9.11.1", "", { "dependencies": { "buffer": "^6.0.3", "debug": "^4.3.2", "err-code": "^3.0.1", "get-browser-rtc": "^1.1.0", "queue-microtask": "^1.2.3", "randombytes": "^2.1.0", "readable-stream": "^3.6.0" } }, "sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw=="],
+
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
"skin-tone": ["skin-tone@2.0.0", "", { "dependencies": { "unicode-emoji-modifier-base": "^1.0.0" } }, "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA=="],
@@ -1776,6 +2326,8 @@
"slate-dom": ["slate-dom@0.124.1", "", { "dependencies": { "@juggle/resize-observer": "^3.4.0", "direction": "^1.0.4", "is-hotkey": "^0.2.0", "is-plain-object": "^5.0.0", "lodash": "^4.17.21", "scroll-into-view-if-needed": "^3.1.0", "tiny-invariant": "1.3.1" }, "peerDependencies": { "slate": ">=0.121.0" } }, "sha512-D3yVibjLZM4Oj4MmXxOEXbjrlf4wJez3OvGABBNYrAP7gXb0d96tKNtWZ0hGm/5y84idw/LHjZ7W1uTYqFR9rQ=="],
+ "slate-history": ["slate-history@0.113.1", "", { "dependencies": { "is-plain-object": "^5.0.0" }, "peerDependencies": { "slate": ">=0.65.3" } }, "sha512-J9NSJ+UG2GxoW0lw5mloaKcN0JI0x2IA5M5FxyGiInpn+QEutxT1WK7S/JneZCMFJBoHs1uu7S7e6pxQjubHmQ=="],
+
"slate-hyperscript": ["slate-hyperscript@0.100.0", "", { "dependencies": { "is-plain-object": "^5.0.0" }, "peerDependencies": { "slate": ">=0.65.3" } }, "sha512-fb2KdAYg6RkrQGlqaIi4wdqz3oa0S4zKNBJlbnJbNOwa23+9FLD6oPVx9zUGqCSIpy+HIpOeqXrg0Kzwh/Ii4A=="],
"slate-react": ["slate-react@0.124.0", "", { "dependencies": { "@juggle/resize-observer": "^3.4.0", "direction": "^1.0.4", "is-hotkey": "^0.2.0", "lodash": "^4.17.21", "scroll-into-view-if-needed": "^3.1.0", "tiny-invariant": "1.3.1" }, "peerDependencies": { "react": ">=18.2.0", "react-dom": ">=18.2.0", "slate": ">=0.121.0", "slate-dom": ">=0.119.1" } }, "sha512-NLN6ME64ChOgJtiVTKwISS1sI/Y8/qN1cwmDTZM9AQJCl+jR3XNCvDsKNrW0kJU+1G3NgIGaYoVWhgIVEIL+Aw=="],
@@ -1786,20 +2338,34 @@
"sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="],
+ "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
+ "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
+
"spotify-audio-element": ["spotify-audio-element@1.0.4", "", {}, "sha512-QdKrJPkYCzaNwwz2vN2eDGyoW0KmQFmnwVprB41mpMzj4qujbqr6pegEchQeTn0b5PceKiLoVu0pp2QDpTcWnw=="],
"sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
"sqids": ["sqids@0.3.0", "", {}, "sha512-lOQK1ucVg+W6n3FhRwwSeUijxe93b51Bfz5PMRMihVf1iVkl82ePQG7V5vwrhzB11v0NtsR25PSZRGiSomJaJw=="],
+ "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
+
"string-template": ["string-template@0.2.1", "", {}, "sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw=="],
+ "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
+
"string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
+ "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
+
+ "strnum": ["strnum@2.2.3", "", {}, "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg=="],
+
"style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="],
"styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
@@ -1808,6 +2374,8 @@
"super-media-element": ["super-media-element@1.4.2", "", {}, "sha512-9pP/CVNp4NF2MNlRzLwQkjiTgKKe9WYXrLh9+8QokWmMxz+zt2mf1utkWLco26IuA3AfVcTb//qtlTIjY3VHxA=="],
+ "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
+
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
"swr": ["swr@2.4.1", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA=="],
@@ -1822,6 +2390,8 @@
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
+ "terser": ["terser@5.16.9", "", { "dependencies": { "@jridgewell/source-map": "^0.3.2", "acorn": "^8.5.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg=="],
+
"text-segmentation": ["text-segmentation@1.0.3", "", { "dependencies": { "utrie": "^1.0.2" } }, "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw=="],
"throttleit": ["throttleit@2.1.0", "", {}, "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw=="],
@@ -1838,6 +2408,10 @@
"toggle-selection": ["toggle-selection@1.0.6", "", {}, "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="],
+ "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
+
+ "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
+
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
"ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
@@ -1846,6 +2420,8 @@
"ts-essentials": ["ts-essentials@10.1.0", "", { "peerDependencies": { "typescript": ">=4.5.0" }, "optionalPeers": ["typescript"] }, "sha512-LirrVzbhIpFQ9BdGfqLnM9r7aP9rnyfeoxbP5ZEkdr531IaY21+KdebRSsbvqu28VDJtcDDn+AlGn95t0c52zQ=="],
+ "ts-tqdm": ["ts-tqdm@0.8.6", "", {}, "sha512-3X3M1PZcHtgQbnwizL+xU8CAgbYbeLHrrDwL9xxcZZrV5J+e7loJm1XrXozHjSkl44J0Zg0SgA8rXbh83kCkcQ=="],
+
"tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
"tunnel-rat": ["tunnel-rat@0.1.2", "", { "dependencies": { "zustand": "^4.3.2" } }, "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ=="],
@@ -1856,6 +2432,8 @@
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
+ "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
+
"typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
"ua-parser-js": ["ua-parser-js@1.0.41", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug=="],
@@ -1866,10 +2444,12 @@
"underscore": ["underscore@1.13.8", "", {}, "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ=="],
- "undici": ["undici@6.23.0", "", {}, "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g=="],
+ "undici": ["undici@7.24.8", "", {}, "sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ=="],
"undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
+ "unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="],
+
"unicode-emoji-modifier-base": ["unicode-emoji-modifier-base@1.0.0", "", {}, "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g=="],
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
@@ -1886,12 +2466,16 @@
"unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
+ "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
+
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
"uploadthing": ["uploadthing@7.7.4", "", { "dependencies": { "@effect/platform": "0.90.3", "@standard-schema/spec": "1.0.0-beta.4", "@uploadthing/mime-types": "0.3.6", "@uploadthing/shared": "7.1.10", "effect": "3.17.7" }, "peerDependencies": { "express": "*", "h3": "*", "tailwindcss": "^3.0.0 || ^4.0.0-beta.0" }, "optionalPeers": ["express", "h3", "tailwindcss"] }, "sha512-rlK/4JWHW5jP30syzWGBFDDXv3WJDdT8gn9OoxRJmXLoXi94hBmyyjxihGlNrKhBc81czyv8TkzMioe/OuKGfA=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
+ "urlpattern-polyfill": ["urlpattern-polyfill@10.1.0", "", {}, "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw=="],
+
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
"use-composed-ref": ["use-composed-ref@1.4.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w=="],
@@ -1922,6 +2506,8 @@
"validator": ["validator@13.15.26", "", {}, "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA=="],
+ "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
+
"vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
"vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
@@ -1950,20 +2536,36 @@
"web-resource-inliner": ["web-resource-inliner@8.0.0", "", { "dependencies": { "ansi-colors": "^4.1.1", "escape-goat": "^3.0.0", "htmlparser2": "^9.1.0", "mime": "^2.4.6", "valid-data-url": "^3.0.0" } }, "sha512-Ezr98sqXW/+OCGoUEXuOKVR+oVFlSdn1tIySEEJdiSAw4IjrW8hQkwARSSBJTSB5Us5dnytDgL0ZDliAYBhaNA=="],
+ "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="],
+
"web-worker": ["web-worker@1.5.0", "", {}, "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw=="],
+ "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
+
"webworkify": ["webworkify@1.5.0", "", {}, "sha512-AMcUeyXAhbACL8S2hqqdqOLqvJ8ylmIbNwUIqQujRSouf4+eUFaXbG6F1Rbu+srlJMmxQWsiU7mOJi0nMBfM1g=="],
"whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="],
"whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="],
- "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
+ "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
+
+ "which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
"wistia-video-element": ["wistia-video-element@1.3.6", "", { "dependencies": { "super-media-element": "~1.4.2" } }, "sha512-wPizIpXDaCs6fvDzhU3MBtEpxIqhgXlu00kSrKgmjPb5DRqZt927xZZjE1qm81Df40d445u4a/mRKX5I66zaYA=="],
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
+ "workerd": ["workerd@1.20260430.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260430.1", "@cloudflare/workerd-darwin-arm64": "1.20260430.1", "@cloudflare/workerd-linux-64": "1.20260430.1", "@cloudflare/workerd-linux-arm64": "1.20260430.1", "@cloudflare/workerd-windows-64": "1.20260430.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-KEgIWyiw3Jmn+DCd/L3ePo5fmiiYb/UcwKvDWPf/nLLOiwShDFzDSsegU5NY/JcwgvO/QsLHVi2FYrbkcXNY5Q=="],
+
+ "wrangler": ["wrangler@4.87.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.5.0", "@cloudflare/unenv-preset": "2.16.1", "blake3-wasm": "2.1.5", "esbuild": "0.27.3", "miniflare": "4.20260430.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260430.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260430.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-lfhfKwLfQlowwgV0xhlYgE9fU3n0I30d4ccGY/rTCEm/n42Mjvlr0Ng3ZPNqlsrsKBcDR531V7dsPkgELvrk/Q=="],
+
+ "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
+
+ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
+
+ "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
+
"x-is-array": ["x-is-array@0.1.0", "", {}, "sha512-goHPif61oNrr0jJgsXRfc8oqtYzvfiMJpTqwE7Z4y9uH+T3UozkGqQ4d2nX9mB9khvA8U2o/UbPOFjgC7hLWIA=="],
"x-is-string": ["x-is-string@0.1.0", "", {}, "sha512-GojqklwG8gpzOVEVki5KudKNoq7MbbjYZCbyWzEz7tyPA7eleiE0+ePwOWQQRb5fm86rD3S8Tc0tSFf3AOv50w=="],
@@ -1974,12 +2576,28 @@
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
+ "y-protocols": ["y-protocols@1.0.7", "", { "dependencies": { "lib0": "^0.2.85" }, "peerDependencies": { "yjs": "^13.0.0" } }, "sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw=="],
+
+ "y-webrtc": ["y-webrtc@10.3.0", "", { "dependencies": { "lib0": "^0.2.42", "simple-peer": "^9.11.0", "y-protocols": "^1.0.6" }, "optionalDependencies": { "ws": "^8.14.2" }, "peerDependencies": { "yjs": "^13.6.8" }, "bin": { "y-webrtc-signaling": "bin/server.js" } }, "sha512-KalJr7dCgUgyVFxoG3CQYbpS0O2qybegD0vI4bYnYHI0MOwoVbucED3RZ5f2o1a5HZb1qEssUKS0H/Upc6p1lA=="],
+
+ "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
+
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="],
+ "yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="],
+
+ "yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
+
+ "yjs": ["yjs@13.6.30", "", { "dependencies": { "lib0": "^0.2.99" } }, "sha512-vv/9h42eCMC81ZHDFswuu/MKzkl/vyq1BhaNGfHyOonwlG4CJbQF4oiBBJPvfdeCt/PlVDWh7Nov9D34YY09uQ=="],
+
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
+ "youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="],
+
+ "youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="],
+
"youtube-video-element": ["youtube-video-element@1.9.0", "", { "dependencies": { "media-played-ranges-mixin": "^0.1.0" } }, "sha512-Hh0dbQM+FVlUaYUbpYkZNUvdKxTNcSNvTGzkQKYShltnX+LRHEp2eYvC2Zm43eU8Np+CBZuoNR2i+seCYzzAyg=="],
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
@@ -1994,10 +2612,122 @@
"@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
+ "@aws-crypto/crc32/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-crypto/crc32c/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
+
+ "@aws-crypto/sha1-browser/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
+
+ "@aws-crypto/sha256-browser/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-crypto/sha256-js/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-crypto/supports-web-crypto/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
+
+ "@aws-crypto/util/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/client-cloudfront/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/client-dynamodb/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/client-lambda/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/client-s3/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/client-sqs/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/core/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/crc64-nvme/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/credential-provider-env/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/credential-provider-http/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/credential-provider-ini/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/credential-provider-login/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/credential-provider-node/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/credential-provider-process/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/credential-provider-sso/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/credential-provider-web-identity/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/dynamodb-codec/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/endpoint-cache/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/middleware-bucket-endpoint/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/middleware-endpoint-discovery/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/middleware-expect-continue/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/middleware-flexible-checksums/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/middleware-host-header/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/middleware-location-constraint/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/middleware-logger/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/middleware-recursion-detection/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/middleware-sdk-s3/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/middleware-sdk-sqs/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/middleware-ssec/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/middleware-user-agent/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-endpoints": "^3.4.2", "tslib": "^2.6.2" } }, "sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g=="],
+
+ "@aws-sdk/middleware-user-agent/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.25", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.37", "@aws-sdk/types": "^3.973.8", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-+CMIt3e1VzlklAECmG+DtP1sV8iKq25FuA0OKpnJ4KA0kxUtd7CgClY7/RU6VzJBQwbN4EJ9Ue6plvqx1qGadw=="],
+
+ "@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-endpoints": "^3.4.2", "tslib": "^2.6.2" } }, "sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g=="],
+
+ "@aws-sdk/nested-clients/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/region-config-resolver/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/signature-v4-multi-region/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/token-providers/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/types/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/util-arn-parser/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/util-endpoints/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/util-locate-window/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/util-user-agent-browser/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/util-user-agent-node/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@aws-sdk/xml-builder/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+ "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
+
"@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+ "@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
+
+ "@dotenvx/dotenvx/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
+
"@emnapi/runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
@@ -2024,12 +2754,18 @@
"@mux/mux-video/custom-media-element": ["custom-media-element@1.4.5", "", {}, "sha512-cjrsQufETwxjvwZbYbKBCJNvmQ2++G9AvT45zDi7NXL9k2PdVcs2h0jQz96J6G4TMKRCcEsoJ+QTgQD00Igtjw=="],
+ "@node-minify/core/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
+
+ "@opennextjs/aws/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="],
+
"@platejs/core/jotai": ["jotai@2.8.4", "", { "peerDependencies": { "@types/react": ">=17.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@types/react", "react"] }, "sha512-f6jwjhBJcDtpeauT2xH01gnqadKEySwwt1qNBLvAXcnojkmb76EdqRt05Ym8IamfHGAQz2qMKAwftnyjeSoHAA=="],
"@platejs/core/nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="],
"@platejs/docx-io/nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="],
+ "@poppinss/dumper/@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="],
+
"@radix-ui/react-alert-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
"@radix-ui/react-avatar/@radix-ui/react-context": ["@radix-ui/react-context@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw=="],
@@ -2054,6 +2790,104 @@
"@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
+ "@smithy/chunked-blob-reader/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/chunked-blob-reader-native/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/config-resolver/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/core/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/credential-provider-imds/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/eventstream-codec/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/eventstream-serde-browser/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/eventstream-serde-config-resolver/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/eventstream-serde-node/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/eventstream-serde-universal/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/fetch-http-handler/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/hash-blob-browser/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/hash-node/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/hash-stream-node/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/invalid-dependency/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/is-array-buffer/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/md5-js/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/middleware-content-length/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/middleware-endpoint/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/middleware-retry/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/middleware-serde/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/middleware-stack/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/node-config-provider/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/node-http-handler/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/property-provider/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/protocol-http/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/querystring-builder/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/querystring-parser/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/shared-ini-file-loader/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/signature-v4/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/smithy-client/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/types/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/url-parser/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/util-base64/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/util-body-length-browser/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/util-body-length-node/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/util-buffer-from/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/util-config-provider/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/util-defaults-mode-browser/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/util-defaults-mode-node/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/util-endpoints/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/util-hex-encoding/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/util-middleware/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/util-retry/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/util-stream/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/util-uri-escape/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/util-utf8/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/util-waiter/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@smithy/uuid/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
"@swc/helpers/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w=="],
@@ -2070,6 +2904,8 @@
"@tailwindcss/postcss/postcss": ["postcss@8.5.10", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ=="],
+ "accepts/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
+
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"aria-hidden/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
@@ -2082,6 +2918,8 @@
"cheerio/htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="],
+ "cheerio/undici": ["undici@6.23.0", "", {}, "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g=="],
+
"cheerio-select/domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
"cheerio-select/domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
@@ -2090,10 +2928,16 @@
"chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
+ "cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
+
+ "cloudflare/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
+
"cmdk/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="],
"cosmiconfig/yaml": ["yaml@1.10.3", "", {}, "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA=="],
+ "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
+
"css-select/domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
"css-select/domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
@@ -2102,6 +2946,8 @@
"d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
+ "d3-dsv/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
+
"d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="],
"d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="],
@@ -2116,10 +2962,18 @@
"effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
+ "encoding-sniffer/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
+
"ent/punycode": ["punycode@1.4.1", "", {}, "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="],
+ "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
+
+ "express/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
+
"file-selector/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+ "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
+
"hls-video-element/custom-media-element": ["custom-media-element@1.4.5", "", {}, "sha512-cjrsQufETwxjvwZbYbKBCJNvmQ2++G9AvT45zDi7NXL9k2PdVcs2h0jQz96J6G4TMKRCcEsoJ+QTgQD00Igtjw=="],
"htmlparser2/entities": ["entities@1.1.2", "", {}, "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="],
@@ -2150,8 +3004,6 @@
"parse5-htmlparser2-tree-adapter/domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
- "path-scurry/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
-
"player.style/media-chrome": ["media-chrome@4.16.1", "", { "dependencies": { "ce-la-react": "^0.3.2" } }, "sha512-qtFlsy0lNDVCyVo//ZCAfRPKwgehfOYp6rThZzDUuZ5ypv41yqUfAxK+P9TOs+XSVWXATPTT2WRV0fbW0BH4vQ=="],
"points-on-path/points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="],
@@ -2172,10 +3024,24 @@
"roughjs/points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="],
+ "router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
+
+ "send/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
+
+ "simple-peer/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
+
"slate-react/lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="],
+ "string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
+
+ "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
+
"tunnel-rat/zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="],
+ "type-is/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
+
+ "ultracite/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
+
"unist-util-remove-position/unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="],
"use-callback-ref/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
@@ -2186,8 +3052,18 @@
"web-resource-inliner/htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="],
+ "whatwg-encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
+
+ "wrap-ansi/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
+
"zustand-x/use-sync-external-store": ["use-sync-external-store@1.4.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw=="],
+ "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
+
+ "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
+
+ "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
+
"@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/primitive": ["@radix-ui/primitive@1.1.1", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="],
"@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="],
@@ -2244,6 +3120,64 @@
"@excalidraw/mermaid-to-excalidraw/mermaid/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
+ "@node-minify/core/glob/minimatch": ["minimatch@8.0.7", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg=="],
+
+ "@node-minify/core/glob/minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="],
+
+ "@node-minify/core/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.4", "", { "os": "none", "cpu": "arm64" }, "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="],
+
+ "@opennextjs/aws/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="],
+
+ "accepts/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
+
"cheerio/domhandler/domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
"cheerio/domutils/domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
@@ -2252,6 +3186,12 @@
"cheerio/htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
+ "cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
+
+ "cloudflare/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
+
"css-select/domhandler/domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
"css-select/domutils/domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
@@ -2264,12 +3204,20 @@
"domutils/dom-serializer/entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="],
+ "express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
+
"mermaid/katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
"micromark-extension-math/katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
"parse5-htmlparser2-tree-adapter/domhandler/domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
+ "send/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
+
+ "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
+
+ "type-is/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
+
"use-file-picker/file-selector/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"web-resource-inliner/htmlparser2/domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
@@ -2280,6 +3228,14 @@
"web-resource-inliner/htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
+ "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
+
+ "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
+
+ "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
+
+ "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
+
"@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-dismissable-layer/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw=="],
"@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-dismissable-layer/@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.0", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw=="],
@@ -2344,6 +3300,12 @@
"@excalidraw/mermaid-to-excalidraw/mermaid/mdast-util-from-markdown/unist-util-stringify-position": ["unist-util-stringify-position@3.0.3", "", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg=="],
+ "@node-minify/core/glob/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="],
+
+ "@node-minify/core/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
+
+ "@node-minify/core/glob/path-scurry/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
+
"@excalidraw/excalidraw/@radix-ui/react-tabs/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA=="],
"@excalidraw/excalidraw/@radix-ui/react-tabs/@radix-ui/react-roving-focus/@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-compose-refs": "1.0.0" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw=="],
@@ -2368,6 +3330,8 @@
"@excalidraw/mermaid-to-excalidraw/mermaid/mdast-util-from-markdown/micromark-util-decode-string/micromark-util-character": ["micromark-util-character@1.2.0", "", { "dependencies": { "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg=="],
+ "@node-minify/core/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
"@excalidraw/mermaid-to-excalidraw/mermaid/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-destination": ["micromark-factory-destination@1.1.0", "", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg=="],
"@excalidraw/mermaid-to-excalidraw/mermaid/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-label": ["micromark-factory-label@1.1.0", "", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0", "uvu": "^0.5.0" } }, "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w=="],
diff --git a/templates/plate-playground-template/next.config.ts b/templates/plate-playground-template/next.config.ts
index 08efee23e2..cd5be80fe7 100644
--- a/templates/plate-playground-template/next.config.ts
+++ b/templates/plate-playground-template/next.config.ts
@@ -1,3 +1,4 @@
+import { initOpenNextCloudflareForDev } from '@opennextjs/cloudflare';
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
@@ -20,4 +21,7 @@ const nextConfig: NextConfig = {
},
};
+// Wire OpenNext-Cloudflare bindings to Next.js dev server.
+initOpenNextCloudflareForDev();
+
export default nextConfig;
diff --git a/templates/plate-playground-template/open-next.config.ts b/templates/plate-playground-template/open-next.config.ts
new file mode 100644
index 0000000000..7a3d171726
--- /dev/null
+++ b/templates/plate-playground-template/open-next.config.ts
@@ -0,0 +1,3 @@
+import { defineCloudflareConfig } from '@opennextjs/cloudflare';
+
+export default defineCloudflareConfig();
diff --git a/templates/plate-playground-template/package.json b/templates/plate-playground-template/package.json
index c4e4151255..b432e4e541 100644
--- a/templates/plate-playground-template/package.json
+++ b/templates/plate-playground-template/package.json
@@ -4,12 +4,16 @@
"private": true,
"scripts": {
"build": "next build",
+ "cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts",
"depset": "bunx depset@latest @platejs --yes && bunx depset@latest platejs --yes",
+ "deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
"dev": "next dev",
- "lint": "biome check src biome.jsonc components.json tsconfig.json eslint.config.mjs next.config.ts postcss.config.mjs && eslint",
- "lint:fix": "biome check src biome.jsonc components.json tsconfig.json eslint.config.mjs next.config.ts postcss.config.mjs --fix --unsafe",
+ "vendor:pagination": "rm -rf vendor/platejs-pagination/dist && cp -r ../../packages/pagination/dist vendor/platejs-pagination/dist",
+ "lint": "biome check src biome.jsonc components.json tsconfig.json eslint.config.mjs next.config.ts postcss.config.mjs && eslint .",
+ "lint:fix": "biome check src biome.jsonc components.json tsconfig.json eslint.config.mjs next.config.ts postcss.config.mjs --fix --unsafe && eslint . --fix",
"prepare": "bun x skiller@latest apply || true",
"preview": "next build && next start",
+ "preview:cf": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
"start": "next start",
"typecheck": "tsc --noEmit"
},
@@ -46,6 +50,7 @@
"@platejs/math": "^53.0.0",
"@platejs/media": "^53.0.1",
"@platejs/mention": "^53.0.0",
+ "@platejs/pagination": "file:./vendor/platejs-pagination",
"@platejs/resizable": "^53.0.0",
"@platejs/selection": "^53.0.0",
"@platejs/slash-command": "^53.0.0",
@@ -53,6 +58,7 @@
"@platejs/table": "^53.0.0",
"@platejs/toc": "^53.0.0",
"@platejs/toggle": "^53.0.0",
+ "@platejs/yjs": "^53.0.0",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-checkbox": "^1.3.3",
@@ -92,16 +98,19 @@
"remark-emoji": "^5.0.2",
"remark-gfm": "^4.0.1",
"remark-math": "^6.0.0",
+ "slate-history": "^0.113.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",
"tailwind-scrollbar-hide": "^4.0.0",
"tw-animate-css": "^1.4.0",
"uploadthing": "7.7.4",
"use-file-picker": "2.1.2",
- "zod": "^4.3.6"
+ "zod": "^4.3.6",
+ "@chenglou/pretext": "^0.0.6"
},
"devDependencies": {
"@biomejs/biome": "2.4.13",
+ "@opennextjs/cloudflare": "^1.19.6",
"@tailwindcss/postcss": "4.2.4",
"@types/node": "^25.6.0",
"@types/react": "19.2.14",
@@ -114,7 +123,8 @@
"postcss": "^8.5.12",
"tailwindcss": "4.2.4",
"typescript": "6.0.3",
- "ultracite": "7.6.2"
+ "ultracite": "7.6.2",
+ "wrangler": "^4.87.0"
},
"packageManager": "bun@1.3.3"
}
diff --git a/templates/plate-playground-template/src/app/dev/pagination2/page.tsx b/templates/plate-playground-template/src/app/dev/pagination2/page.tsx
new file mode 100644
index 0000000000..3ad6db429d
--- /dev/null
+++ b/templates/plate-playground-template/src/app/dev/pagination2/page.tsx
@@ -0,0 +1,7 @@
+import { PaginationView } from './pagination2-view';
+
+export const dynamic = 'force-dynamic';
+
+export default function Page() {
+ return ;
+}
diff --git a/templates/plate-playground-template/src/app/dev/pagination2/pagination2-view.tsx b/templates/plate-playground-template/src/app/dev/pagination2/pagination2-view.tsx
new file mode 100644
index 0000000000..7f9eb40339
--- /dev/null
+++ b/templates/plate-playground-template/src/app/dev/pagination2/pagination2-view.tsx
@@ -0,0 +1,69 @@
+'use client';
+
+import { PaginationPlugin } from '@platejs/pagination/react';
+import type { Value } from 'platejs';
+import { Plate, PlateContent, usePlateEditor } from 'platejs/react';
+
+import { BasicNodesKit } from '@/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 (
+
+ );
+}
diff --git a/templates/plate-playground-template/src/app/editor/page.tsx b/templates/plate-playground-template/src/app/editor/page.tsx
index 1d76470b29..ec1ef351ed 100644
--- a/templates/plate-playground-template/src/app/editor/page.tsx
+++ b/templates/plate-playground-template/src/app/editor/page.tsx
@@ -2,6 +2,8 @@ import { Toaster } from 'sonner';
import { PlateEditor } from '@/components/editor/plate-editor';
+export const dynamic = 'force-dynamic';
+
export default function Page() {
return (
diff --git a/templates/plate-playground-template/src/components/editor/editor-kit.tsx b/templates/plate-playground-template/src/components/editor/editor-kit.tsx
index 66e774d445..1d7972de11 100644
--- a/templates/plate-playground-template/src/components/editor/editor-kit.tsx
+++ b/templates/plate-playground-template/src/components/editor/editor-kit.tsx
@@ -32,6 +32,7 @@ import { MarkdownKit } from '@/components/editor/plugins/markdown-kit';
import { MathKit } from '@/components/editor/plugins/math-kit';
import { MediaKit } from '@/components/editor/plugins/media-kit';
import { MentionKit } from '@/components/editor/plugins/mention-kit';
+import { PaginationKit } from '@/components/editor/plugins/pagination-kit';
import { SlashKit } from '@/components/editor/plugins/slash-kit';
import { SuggestionKit } from '@/components/editor/plugins/suggestion-kit';
import { TableKit } from '@/components/editor/plugins/table-kit';
@@ -65,6 +66,9 @@ export const EditorKit = [
...AlignKit,
...LineHeightKit,
+ // Layout
+ ...PaginationKit,
+
// Collaboration
...DiscussionKit,
...CommentKit,
diff --git a/templates/plate-playground-template/src/components/editor/plugins/pagination-kit.tsx b/templates/plate-playground-template/src/components/editor/plugins/pagination-kit.tsx
new file mode 100644
index 0000000000..83db1c1cf1
--- /dev/null
+++ b/templates/plate-playground-template/src/components/editor/plugins/pagination-kit.tsx
@@ -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];
diff --git a/templates/plate-playground-template/src/components/ui/fixed-toolbar-buttons.tsx b/templates/plate-playground-template/src/components/ui/fixed-toolbar-buttons.tsx
index 2392d47da2..371638ce2a 100644
--- a/templates/plate-playground-template/src/components/ui/fixed-toolbar-buttons.tsx
+++ b/templates/plate-playground-template/src/components/ui/fixed-toolbar-buttons.tsx
@@ -40,6 +40,7 @@ import { MarkToolbarButton } from './mark-toolbar-button';
import { MediaToolbarButton } from './media-toolbar-button';
import { ModeToolbarButton } from './mode-toolbar-button';
import { MoreToolbarButton } from './more-toolbar-button';
+import { PaginationToolbarButton } from './pagination-toolbar-button';
import { TableToolbarButton } from './table-toolbar-button';
import { ToggleToolbarButton } from './toggle-toolbar-button';
import { ToolbarGroup } from './toolbar';
@@ -123,6 +124,7 @@ export function FixedToolbarButtons() {
+
diff --git a/templates/plate-playground-template/src/components/ui/pagination-toolbar-button.tsx b/templates/plate-playground-template/src/components/ui/pagination-toolbar-button.tsx
new file mode 100644
index 0000000000..202d6a555a
--- /dev/null
+++ b/templates/plate-playground-template/src/components/ui/pagination-toolbar-button.tsx
@@ -0,0 +1,26 @@
+'use client';
+
+import { PaginationPlugin } from '@platejs/pagination/react';
+import { SeparatorHorizontalIcon } from 'lucide-react';
+import { useEditorRef, usePluginOption } from 'platejs/react';
+import type * as React from 'react';
+
+import { ToolbarButton } from './toolbar';
+
+export function PaginationToolbarButton(
+ props: React.ComponentProps
+) {
+ const editor = useEditorRef();
+ const enabled = usePluginOption(PaginationPlugin, 'enabled');
+
+ return (
+ editor.setOption(PaginationPlugin, 'enabled', !enabled)}
+ pressed={enabled}
+ tooltip="Page breaks"
+ >
+
+
+ );
+}
diff --git a/templates/plate-playground-template/vendor/platejs-pagination/package.json b/templates/plate-playground-template/vendor/platejs-pagination/package.json
new file mode 100644
index 0000000000..3f16766329
--- /dev/null
+++ b/templates/plate-playground-template/vendor/platejs-pagination/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "@platejs/pagination",
+ "version": "52.2.0",
+ "description": "Pagination plugin for Plate - page-based document layout",
+ "license": "MIT",
+ "sideEffects": false,
+ "type": "module",
+ "exports": {
+ ".": "./dist/index.js",
+ "./package.json": "./package.json",
+ "./react": "./dist/react/index.js"
+ },
+ "main": "./dist/index.js",
+ "module": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "dependencies": {
+ "@chenglou/pretext": "^0.0.6"
+ }
+}
diff --git a/templates/plate-playground-template/wrangler.jsonc b/templates/plate-playground-template/wrangler.jsonc
new file mode 100644
index 0000000000..6fc9c88372
--- /dev/null
+++ b/templates/plate-playground-template/wrangler.jsonc
@@ -0,0 +1,14 @@
+{
+ "$schema": "./node_modules/wrangler/config-schema.json",
+ "name": "plate-playground",
+ "main": ".open-next/worker.js",
+ "compatibility_date": "2026-04-23",
+ "compatibility_flags": ["nodejs_compat"],
+ "assets": {
+ "directory": ".open-next/assets",
+ "binding": "ASSETS"
+ },
+ "observability": {
+ "enabled": true
+ }
+}
diff --git a/tooling/config/tsdown.config.ts b/tooling/config/tsdown.config.ts
index fbd39f95db..7849b0b2f6 100644
--- a/tooling/config/tsdown.config.ts
+++ b/tooling/config/tsdown.config.ts
@@ -37,6 +37,15 @@ const STATIC_INPUT_FILE_PATH = fs.existsSync(STATIC_TS_INPUT_FILE_PATH)
? STATIC_TS_INPUT_FILE_PATH
: STATIC_TSX_INPUT_FILE_PATH;
+const YJS_TS_INPUT_FILE_PATH = path.join(PACKAGE_ROOT_PATH, 'src/yjs/index.ts');
+const YJS_TSX_INPUT_FILE_PATH = path.join(
+ PACKAGE_ROOT_PATH,
+ 'src/yjs/index.tsx'
+);
+const YJS_INPUT_FILE_PATH = fs.existsSync(YJS_TS_INPUT_FILE_PATH)
+ ? YJS_TS_INPUT_FILE_PATH
+ : YJS_TSX_INPUT_FILE_PATH;
+
const entry = [convertPathToPattern(INPUT_FILE)];
if (fs.existsSync(REACT_INPUT_FILE_PATH)) {
@@ -47,6 +56,10 @@ if (fs.existsSync(STATIC_INPUT_FILE_PATH)) {
entry.push(convertPathToPattern(STATIC_INPUT_FILE_PATH));
}
+if (fs.existsSync(YJS_INPUT_FILE_PATH)) {
+ entry.push(convertPathToPattern(YJS_INPUT_FILE_PATH));
+}
+
// Disable sourcemaps in CI to speed up builds
const enableSourcemaps = !process.env.CI;
diff --git a/tooling/e2e/pagination.spec.ts b/tooling/e2e/pagination.spec.ts
new file mode 100644
index 0000000000..cb576c9dde
--- /dev/null
+++ b/tooling/e2e/pagination.spec.ts
@@ -0,0 +1,128 @@
+import { expect, test } from '@playwright/test';
+
+// ============================================================
+// E2E: @platejs/pagination continuous-view overlay
+//
+// Locks in the user-visible behavior the pagination dogfood pass surfaced:
+// advisory break lines render on load, page labels include the real total, and
+// the overlay remains non-interactive so native editing is untouched.
+// ============================================================
+
+const ROUTE = process.env.PLAYWRIGHT_BASE_URL
+ ? new URL('/dev/pagination2', process.env.PLAYWRIGHT_BASE_URL).toString()
+ : '/dev/pagination2';
+const CONTENT_PER_PAGE = 931; // 1123 - 96 - 96
+
+const BREAK_LINE = '[data-slot="pagination-break-line"]';
+const PAGE_MARKER = '[data-slot="pagination-page-marker"]';
+const LABEL = '[data-slot="pagination-break-label"]';
+const CONTAINER = '[data-slot="pagination-break-lines"]';
+
+/** Read the explicit inline `top` (px) of an absolutely-positioned overlay node. */
+const topOf = (handle: {
+ evaluate: (fn: (el: HTMLElement | SVGElement) => R) => Promise;
+}) => handle.evaluate((el) => Number.parseFloat((el as HTMLElement).style.top));
+
+test.describe('pagination continuous-view overlay', () => {
+ test('advisory break lines render on load', async ({ page }) => {
+ await page.goto(ROUTE);
+
+ await expect(page.locator(BREAK_LINE).first()).toBeVisible();
+ expect(await page.locator(BREAK_LINE).count()).toBeGreaterThan(0);
+ });
+
+ test('renders a "Page 1 of N" marker and consistent "Page K of N" labels', async ({
+ page,
+ }) => {
+ await page.goto(ROUTE);
+ await expect(page.locator(BREAK_LINE).first()).toBeVisible();
+
+ const breakCount = await page.locator(BREAK_LINE).count();
+ const total = breakCount + 1;
+
+ await expect(page.locator(`${PAGE_MARKER} ${LABEL}`)).toHaveText(
+ `Page 1 of ${total}`
+ );
+
+ const labels = await page.locator(`${BREAK_LINE} ${LABEL}`).allInnerTexts();
+ expect(labels).toEqual(
+ Array.from({ length: breakCount }, (_, i) => `Page ${i + 2} of ${total}`)
+ );
+ });
+
+ test('break lines sit on the A4 boundary without accumulating drift', async ({
+ page,
+ }) => {
+ await page.goto(ROUTE);
+ await expect(page.locator(BREAK_LINE).first()).toBeVisible();
+
+ const markerTop = await topOf(page.locator(PAGE_MARKER));
+ const lines = page.locator(BREAK_LINE);
+ const count = await lines.count();
+ expect(count).toBeGreaterThan(1);
+
+ let prev = markerTop;
+ for (let i = 0; i < count; i++) {
+ const top = await topOf(lines.nth(i));
+ const fromOrigin = top - markerTop;
+ const expectedBoundary = (i + 1) * CONTENT_PER_PAGE;
+
+ expect(fromOrigin).toBeLessThanOrEqual(expectedBoundary + 30);
+
+ const gap = top - prev;
+ expect(gap).toBeLessThanOrEqual(CONTENT_PER_PAGE + 30);
+ expect(gap).toBeGreaterThanOrEqual(CONTENT_PER_PAGE - 250);
+ prev = top;
+ }
+ });
+
+ test('page labels stay on-screen on a viewport narrower than the page', async ({
+ page,
+ }) => {
+ await page.setViewportSize({ height: 900, width: 600 });
+ await page.goto(ROUTE);
+ await expect(page.locator(BREAK_LINE).first()).toBeVisible();
+
+ const labels = page.locator(LABEL);
+ const count = await labels.count();
+ expect(count).toBeGreaterThan(0);
+
+ for (let i = 0; i < count; i++) {
+ const box = await labels.nth(i).boundingBox();
+ expect(box).not.toBeNull();
+ expect(box!.x).toBeGreaterThanOrEqual(0);
+ expect(box!.x + box!.width).toBeLessThanOrEqual(600);
+ }
+ });
+
+ test('overlay never intercepts pointer events', async ({ page }) => {
+ await page.goto(ROUTE);
+ await expect(page.locator(BREAK_LINE).first()).toBeVisible();
+
+ const pointerEvents = await page
+ .locator(CONTAINER)
+ .evaluate((el) => getComputedStyle(el).pointerEvents);
+ expect(pointerEvents).toBe('none');
+
+ await expect(
+ page.locator('[contenteditable="true"]').first()
+ ).toBeVisible();
+ });
+
+ test('no console errors while the overlay computes and recomputes', async ({
+ page,
+ }) => {
+ const errors: string[] = [];
+ page.on('console', (msg) => {
+ if (msg.type() === 'error') errors.push(msg.text());
+ });
+ page.on('pageerror', (err) => errors.push(err.message));
+
+ await page.goto(ROUTE);
+ await expect(page.locator(BREAK_LINE).first()).toBeVisible();
+ await page.setViewportSize({ height: 900, width: 700 });
+ await expect(page.locator(BREAK_LINE).first()).toBeVisible();
+
+ expect(errors).toEqual([]);
+ });
+});
diff --git a/tooling/scripts/brl.sh b/tooling/scripts/brl.sh
index 23fe7115c1..4bb1ce1211 100644
--- a/tooling/scripts/brl.sh
+++ b/tooling/scripts/brl.sh
@@ -42,7 +42,7 @@ run_barrelsby() {
common_excludes='.*__tests__.*|(.*(fixture|template|spec|slow|internal).*)|(.*\.d\.ts$)'
-src_excludes="$common_excludes|(^.*\/(react|static)\/.*$)"
+src_excludes="$common_excludes|(^.*\/(react|static|yjs)\/.*$)"
# Run barrelsby on the src directory if index.tsx doesn't exist
run_barrelsby "$INIT_CWD/src" -D -l all -q -e "$src_excludes"
@@ -56,3 +56,8 @@ fi
if [ -d "$INIT_CWD/src/static" ]; then
run_barrelsby "$INIT_CWD/src/static" -D -l all -q -e "$common_excludes"
fi
+
+# Check if the src/yjs directory exists and run barrelsby if it does and if index.tsx doesn't exist
+if [ -d "$INIT_CWD/src/yjs" ]; then
+ run_barrelsby "$INIT_CWD/src/yjs" -D -l all -q -e "$common_excludes"
+fi