Do not store editor state as the document
This is a document model for editing content as Notion-style blocks. React and ProseMirror are kept outside the core; the package contains only the document structure and editing rules in TypeScript. sdui-document-react owns the UI, while sdui-template owns SDUI rendering.
When editor internals become persisted data, server validation and UI replacement become difficult. I started with one rule: the document should outlive the editor.
Markdown ↔ block document → apply patches → render with SDUI or React

A Storybook catalog rendering the same block document with the read-only Notion theme.
Use block anchors instead of array indexes
Insert, update, move, split, and merge operations share one patch format. Array indexes drift after an earlier edit, so placement is expressed with block IDs and before or after anchors. An invalid anchor throws instead of silently placing content somewhere else.
Applying one patch
import {
applyDocumentPatch,
createDocumentBlock,
} from "@lodado/sdui-document";
const next = applyDocumentPatch(content, {
type: "block.insert",
parentId: "root",
after: "problem",
block: createDocumentBlock({
id: "decision",
type: "document.callout",
state: { text: "The document should outlive the editor." },
}),
});
// content stays untouched; next contains the new document.
applyDocumentPatch returns a new document without mutating the original. The same rules run in the browser and on the server, while autosave and undo/redo use the same patch contract.
Design philosophy
A document is not editor-internal state
The public model contains no ProseMirror transactions, DOM selections, or React state. The document structure and persisted data should remain stable even when the editing UI changes. This package defines what a document means.
Every edit is a patch
Insert, update, delete, and move operations are recorded rather than applied as direct mutations. The same patch can be reused for optimistic UI, autosave, undo/redo, and conflict handling. Treating a change as data also makes the save flow easier to test.
Solve block-level problems first
The first concerns are document structure, block movement, permissions, and save state. Character-level CRDT and advanced inline editing are deferred until a real requirement appears. The package deliberately does not try to own every rich-text problem.
Keep side effects behind adapters
Patch application, permission decisions, and autosave state are pure functions and reducers. The package defines interfaces for databases, file storage, search, and networking, but does not implement them. The same rules can therefore run in a browser, server, or worker.
Client permissions are not security
Client-side permission checks are for hiding controls and rendering a read-only UI. A server adapter must check the same permission again before accepting a write.
What is intentionally missing
Database and search implementations remain outside the adapters, and real-time collaboration does not yet include a CRDT. Block-level patches stay simple until those requirements become concrete.
The /about résumé uses this document model and its React editor for authoring and rendering.