Skip to content
PACKAGES

Reactive Layout vs Pretext

Legacy TypeScript website content. Shared public website, blog, protocol, guide, and language-neutral docs ownership now lives in ~/src/graphrefly under D563. This page is retained here only as migration/reference material while the TS API generator still lives in website/.

Pretext solved text measurement without DOM thrash — canvas-based width cache and a cursor-driven line walker. Reactive Layout ports that class of measurement into a GraphReFly graph: statederived pipelines, heterogeneous blocks, multi-column flow around obstacles, and first-class observability.

This page is the consideration-stage comparison. For setup, APIs, and composition with the rest of GraphReFly, see Reactive Layout.

PretextReactive Layout
RoleStandalone text measurement + layoutSame measurement subset, embedded in a reactive DAG
Bundle~15 KB min+gz, zero-depGraphReFly core + reactive-layout (tree-shakeable)
ReactivityBring your own (store, observers, manual)Native — only dependent nodes recompute
Beyond single-column textYou compose by handreactiveBlockLayout, reactiveFlowLayout, exported slot primitives
Headless / SSRDOM-oriented measurement pathcellTextMeasurements, precomputedTextMeasurements, etc.
ObservabilityNone built-ingraph.describe() → mermaid; meta companions; snapshots
Full i18n (bidi, tab stops, emoji width)StrongSubset — see below

Pretext is a focused library: measure and break lines with minimal surface area. Reactive Layout is measurement + orchestration: the same kind of pipeline (analyze → segments → line breaks → height / positions) lives inside GraphReFly’s protocol, so layout participates in streaming, batching, resilience, and tooling like everything else in your graph.

ProblemPretextReactive Layout
Measure text width without DOM reflowprepare(text, font) + layout(prepared, maxWidth, lineHeight)text/font -> measurements -> reactiveLayout({ measurements }) — measurement facts in a reactive graph
Cursor-based single-line layout (multi-slot, multi-column)layoutNextLine(prepared, cursor, width)layoutNextLine(segments, cursor, slotWidth) — same shape, same cursor semantics
Carve blocked intervals from a columnHand-rolled per call sitecarveTextLineSlots(base, blocked, minSlotWidth) — exported primitive
Stacked heterogeneous blocks (text + image + SVG)Not in coreblockMeasurementProvider(...) -> reactiveBlockLayout({ measurements })
Multi-column flow around shape obstaclesHand-rolled over layoutNextLinetextMeasurements -> reactiveFlowLayout({ measurements, container, columns, obstacles }) + Obstacle = Circle | Rect + built-in slot carving
ObservabilityNonegraph.describe() → mermaid; .meta companions; snapshot serialization
Composable with other reactive workBring your ownNative — same protocol as harness, orchestration, messaging, resilience
  • Your layout has changing inputs. Slider-driven font size, user-typed text, animated obstacles, window resize, streaming LLM output. Change maxWidth alone and segments can stay cached across the change — only dependent derived nodes recompute.

  • You need more than single-column text. reactiveBlockLayout for mixed content; reactiveFlowLayout for columns with obstacles.

  • You want inspectability. Meta companions (cache-hit-rate, layout-time-ns, line-count, …) and one-call graph snapshots for tests and debugging.

  • You’re already composing GraphReFly. Streaming harness output into the upstream text node, resilience around sources, virtualization with exact heights — layout stays on the same protocol.

  • Size-sensitive embedding. A widget script tag or a landing-page hero where every kilobyte counts.

  • Text-only, one-shot rendering. Static blog, CMS preview, SSR article: measure once, render. You do not benefit from reactive recomputation.

  • Full-fidelity internationalization. Pretext includes bidi, keep-all word-break, emoji-width correction, tab-stop handling, and rich segment metadata. Reactive Layout ports a clean subset (CJK per-grapheme breaking, soft-hyphen, break-word, kinsoku, left-sticky punctuation, …) but not the full i18n stack. For RTL-heavy markets or serious emoji + CJK mixing, pretext is more battle-tested.

  • You want to own the reactive layer. Redux, MobX, Signals — pretext plugs into whatever you already use without a second abstraction.

What Pretext Offers That Reactive Layout Does Not Yet

Section titled “What Pretext Offers That Reactive Layout Does Not Yet”
  • Broader i18n: bidi, tab stops, emoji-width correction, keep-all word-break, and richer segment metadata.

  • Smaller, self-contained package for measurement-only use cases.

  • Battle-tested edge cases in complex multilingual text.

  • Incremental, cached recomputation in a dependency graph — no hand-rolled observer lists for the layout pipeline.

  • Higher-level layout primitives (reactiveBlockLayout, reactiveFlowLayout, carveTextLineSlots) beyond single-column body text.

  • Runtime inspection via graph.describe() and meta companions — no separate devtools extension.

  • Same-protocol composition with harnesses, operators, circuit breakers, lists, and CQRS-style document graphs.

Roughly 15 lines subscribe to lineBreaks and drive inputs on reactiveLayout. To get equivalent reactive recompute behavior on top of pretext alone, you typically add observer plumbing (a store, change detection, subscribers, re-layout triggers) — on the order of tens of lines, depending on your stack. That is not a knock on pretext; it is the tradeoff for staying minimal and framework-agnostic.


Reactive Layout is deeply indebted to Cheng Lou’s pretext — the algorithms, the naming, and the idea that text measurement can be pure arithmetic over cached widths. GraphReFly adds the reactive composition layer on top.