system map pretext vibescript freerange

A small stack for UI code that can be proven,
shipped not just hoped.

Three layers that stack. pretext measures text without touching the DOM. vibescript runs one render loop with one state object. freerange proves layout facts from source, no browser. Together: UI code that is deterministic, inspectable, and statically defensible — the floor AI-generated interfaces have been missing.

Layers
3
Render loops
1
Browsers in proof path
0
Demos planned
6/ one per fragile thing
01section

Three layers. Each one removes a tax the web has quietly been paying.

measurement · architecture · proof
/ 01
Layer · measurement

pretext

Turn any paragraph into pure numbers — height, line count, widest line — without a single DOM read.

Uses the browser's font engine as ground truth once, then all further answers are arithmetic over cached widths. No getBoundingClientRect, no reflow, no surprises at paint time.

Answers
height of this paragraph at width W widest line — shrink‑wrap line ranges for virtualized text ahead‑of‑time overflow checks
/ 02
Layer · architecture

vibescript

One render loop. One state object. Reads batched, writes batched, events stored as transient state.

Game‑engine discipline for UI: inputs → layout → animation → commit → DOM writes. Animations have closed‑form solutions where possible. Depth has a single Z.ts. First paint uses the same data flow as every other frame.

Answers
when does what run who owns this piece of state what is the one source of truth how do two gestures compose
/ 03
Layer · proof

freerange

Layout facts stated as @fit comments and proven from source. No browser, no fixtures, no sampled cases.

Preserved length, non‑negative sizes, monotone row tops, bounded column counts. The boring facts that catch real agent mistakes — stated once above the helper, earned once from the source, consumed at every call site.

Answers
does rows.length == items.length always can column count ever reach 8 is width ever negative here will this caption overflow at 320px
02section

How a single frame moves through the stack.

one frame · inputs → layout → commit

Inputs · once per frame in

transientst.events.click
transientst.events.pointerDown
readwindowWidth · scrollY
persistentst.selectedId · st.springs

Layout · pure, provable fn

pretextprepare(text, font) · layout(p, w, lh)
@fitgridLayout(items, w) → cols: int 1..7
@fitstackLayout(sizes, gap) → nondecreasing
@fithitBoxes(rows) → one per row
animationspringStep · closed‑form sample

Commit · one place each out

stateObject.assign(st, next)
resetst.events.click = null
DOM writestransforms · cursor · scrollTo
side effectspushState · focus · storage
scheduleif stillAnimating: scheduleRender()

Reads and writes are segregated. Every pure helper in the middle column has a contract the source earned — so whether the frame is frame 0 or frame 10,000, the shape of the answer is the thing the contract promised. No DOM reads during layout. No DOM writes before commit. Events are raw state, interpreted centrally.

03section

One helper. All three layers visible at once.

caption.ts · 22 lines
layout/caption.ts22 LOC

A caption sized by its own content. Pretext measures it. Freerange proves the result fits. Vibescript consumes the numbers during the layout phase of its render loop.

The contract is small. The source earns it. The call sites stop guessing.

Any call site — grid, focused view, overlay, print — knows by construction that the caption fits its width, and that lines.length == lineCount. No caller has to rediscover it with a console log.

// layout/caption.ts
import { prepareWithSegments, layoutWithLines } from '@chenglou/pretext'

/** @fit
 * given maxWidth: 120..800
 * given lineHeight: 16..40
 * result.height >= 0
 * result.height <= maxWidth * 10
 * result.widestLine <= maxWidth
 * result.lines[].width <= maxWidth
 * result.lines.length == result.lineCount
 */
export function measureCaption(
  text: string,
  font: string,
  maxWidth: number,
  lineHeight: number,
) {
  const prepared = prepareWithSegments(text, font)
  const { lines } = layoutWithLines(prepared, maxWidth, lineHeight)

  let widest = 0
  for (const line of lines)
    if (line.width > widest) widest = line.width

  return {
    height: lines.length * lineHeight,
    lineCount: lines.length,
    widestLine: widest,
    lines,
  }
}
04section

Seven laws. Softly enforced — by making the right thing the easy thing.

rules · 01‑07
01

Model geometry is render geometry.

If pretext says the caption fits in one line at width W, the painted DOM renders at width W too. "Measure against A, render at B" is a bug smell — assume so until proven otherwise.

02

Base geometry first, animated projection second.

Stable x/y/width/height is the source of truth. Animation is a visual residual on top. Hit testing, occlusion, scroll anchoring all read the base box — never the animated one.

03

DOM writes live in one phase.

Transforms, cursor, visibility, scrollTo, focus — all in the commit phase, at the end of render. Touching body.style.cursor during layout is still a DOM write.

04

Events are transient state, not logic.

Callbacks store the raw event and schedule a render. Nothing else. The render frame composes click + key + pointer together, so conflicting gestures resolve in one place.

05

Put facts on pure helpers, not pages.

Layout math belongs in @fit contracts on small helpers. Browser runs are for browser‑owned behavior: native selection, scroll physics, caret placement. Don't conflate them.

06

Assert the fragile interaction directly.

Not "the happy path works". Name the bug: clicking a caption selects the full prompt and stays in grid mode. Snapshot the thing a user would notice — ordered ids, visible row range, line count.

07

The check earns its size.

Small fixtures + one broader pass compressed away + one sensitivity check. "Tests pass" is too weak a stopping condition. "The check notices a meaningful regression" is the bar.

05section

Demos chosen by fragile interaction — not feature parity.

one demo · one named bug
#
Demo
Fragile thing
The check that earns it
01
Photo gallery
grid ↔ line mode
Child caption rect during the 2D→1D handoff — the classic "base is right, child box teleported" regression.
@FITPRETEXTFRAME
grid cols: int 1..7
transition‑frame snapshot at t=16ms
02
Comment thread
pretext‑owned text
Height drifts when a last word wraps that "shouldn't". The measure‑vs‑render mismatch caught red‑handed.
PRETEXT@FIT
widestRenderedLine ≤ commentInnerWidth
03
Virtualized feed
variable‑height rows
The anchor row's viewport y after resize — starts correct, ends correct, dives in the middle.
TRAJECTORY
sample t=0 · 16ms · 64ms · settled
04
Draggable cards
reorder + settle
The released card stays above the stack until settle — otherwise it pops through siblings mid‑spring.
SNAPSHOT@FIT
orderedIds + reorder‑threshold test
05
Typeahead
hit geometry per row
One hit box per visible row — exactly. Not one extra, not one missing, even mid‑virtualization.
@FIT
hitBoxes.length == visibleRows.length
06
Responsive article
320..2000 px
Line count never increases when width increases. The monotonicity law of text layout.
PRETEXT@FIT
sweep w=320..2000 step=20