Local-first · text-layout engine
A virtualized
note editor —
layout derived, never stored.
Ori keeps the whole note in a Y.Doc, measures it with Pretext, and renders only the blocks you can see. Ten thousand lines stay editable while the DOM stays tiny.
MIT · 0.0.1 alpha · Y.Doc + Pretext + virtualization
On cartography
The quiet coastline maps a folded chart. A Y.Text holds the prose, marks live in its attributes, and @ada reviewed it before dawn.
•List items measure their own marker gutter.
◦Nested items wrap against the same Pretext layout.
To-do items toggle a checkbox you can click.
Every measured line wraps from Pretext, never the browser
…9,994 more blocks, idle in the Y.Doc, never touching the DOM until you scroll to them.
live demo
Try it — it's real.
A full @wingleeio/ori-react editor, running right here in the page. Type, drag to select for the formatting menu, or press / for blocks like Bullet list, Numbered list, or a To-do list. Press @ to mention.
benchmark
Stays fast as documents grow.
The same document loaded into ori and four other rich-text editors, then typed into. ori renders only the visible viewport and only re-renders the block you're editing — so load time stays low and typing stays flat as the document grows.
Loading
ori is the only virtualized editor in this rich-text set. Building its CRDT document is O(n), so load time grows to ~15ms at 5,000 blocks — still well under the full-render editors (42–133ms).
ori renders only the viewport at any size (~77 nodes). The others render every block.
Typing
Here virtualization pays off for ori: typing only re-renders the block you're in, so it stays flat at ~0.1ms/keystroke whether the note has 100 or 5,000 blocks.
Lexical, TipTap, and Quill also stay sub-millisecond in this fixture. Slate still scales with document size — ~9.8ms at 5,000 blocks, where typing starts to feel heavy. Toggle the others off to compare the fast ones.
Chrome for Testing · Apple Silicon · load: avg of two median-of-5 runs. Loading: main-thread time to mount + lay out the document (paint excluded). Typing: main-thread scripting time per keystroke (insert-text event work: beforeinput + input when emitted), typing real characters — lower is snappier. This chart compares rich-text/block editors; ori also includes built-in CRDT collaboration. Absolute ms vary by machine. Reproduce with apps/bench.
the data flow
canonical state
A block-structured CRDT. Every edit is a Yjs update — local-first, syncable, undoable.
layout & measurement
Wraps blocks into materialized lines; answers caret, hit-testing and selection geometry.
renders the window
A binary-searched height map turns scroll into the handful of blocks that become DOM.
Built to scale
Thousands of blocks, a few dozen DOM nodes. Heights come from Pretext, windowing from a height map — a 10,000-line note stays light.
Layout you can trust
Pretext measures with the same font the browser paints, and lines are materialized so the browser never re-wraps and disagrees with the caret.
Extensible, measurable nodes
Register custom block types and inline atoms — image, divider, @mention — with a measure() function and a renderer. The engine does the rest.
Real editing
Drag-select, full keyboard nav, split/merge, undo/redo, clipboard and IME — all from logical positions that survive offscreen blocks.
Three small packages
@wingleeio/ori-pretextPure layout & measurement. No DOM, React or Yjs.
@wingleeio/ori-coreY.Doc model, virtualizer, node schema, EditorController.
@wingleeio/ori-reactuseEditor + <NoteEditor>, overlays, keyboard & IME.
Long notes that stay light — and a layout engine you can read.
Get started