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

untitled.note● 24 / 9,998 rendered

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.

untitled.notelive

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.

tap to toggle · x-axis: blocks in the note

Loading

Main-thread render time (ms)
0501001501005001k2k5k

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

DOM nodes rendered
010k20k30k1005001k2k5k

ori renders only the viewport at any size (~77 nodes). The others render every block.

Typing

Scripting time per keystroke (ms)
02.557.5101005001k2k5k

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

Y.Doc→

canonical state

A block-structured CRDT. Every edit is a Yjs update — local-first, syncable, undoable.

Pretext→

layout & measurement

Wraps blocks into materialized lines; answers caret, hit-testing and selection geometry.

Virtualizer■

renders the window

A binary-searched height map turns scroll into the handful of blocks that become DOM.

01

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.

02

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.

03

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.

04

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-pretext

Pure layout & measurement. No DOM, React or Yjs.

@wingleeio/ori-core

Y.Doc model, virtualizer, node schema, EditorController.

@wingleeio/ori-react

useEditor + <NoteEditor>, overlays, keyboard & IME.

Long notes that stay light — and a layout engine you can read.

Get started
ori — MITan experimental local-first editor