How It Works
The single-source measurement model behind mugen.
One description, two consumers
A row is a pure function of its item to a tree of primitives —
the function you hand to <MugenVList render={Row} />:
function (: Comment) {
return (
< ={4} ={12}>
<>{.}</>
<>{.}</>
</>
);
}The tree it returns is consumed two ways:
- The walker (
heightOf) interprets it to compute a height — threading the available width top-down, recursing through composites, summing child heights, and calling pretext at eachTextleaf. No React render, no DOM, no reflow. - React renders the same tree to the DOM for the rows in the visible window.
Because both come from one description, a row's measured height and its painted height cannot disagree.
pretext: height without the DOM
pretext measures wrapped text analytically. It splits into two calls:
const = (, , ); // expensive: canvas + segmentation
const { } = (, , ); // cheap: pure arithmeticmugen caches prepare on (font, options, text) — never on width — so resizing
the list is a pure layout() pass over cached work. This is the dominant
performance term, and it's why a 100k-row resize stays smooth.
The offset index
Per-row heights are kept in a Fenwick tree:
- the prefix sum to row i is its top offset,
- the total sum is the scrollbar height,
- a point update (one row changed) and a scroll-position lookup are both O(log n).
So when a single row's height changes, patching the scrollbar and finding the new visible slice cost a logarithm, not a re-scan.
Off-screen state is real state
A row's useMugenState lives in the list instance's slot store, not
in a mounted React component. The walker runs render(item) for every row to
measure it — on- or off-screen — so that state, and the height it produces, is
real whether or not the row was ever painted:
A
useMugenEffecton row 9,000 parses its markdown and callssetBlocks(...). mugen re-walks that one row, patches the offset index, and the scrollbar grows to the real height — even though row 9,000 was never on screen.
The measure pass
To measure a row, mugen runs render(item) inside an ambient session (so the
mugen hooks resolve their per-row slots by call order) and an inert React
dispatcher, so:
useMugenState/useMugenMemoread their slot in the instance,- cosmetic
useStatereturns its initial value (it can't affect height), useEffect/useLayoutEffectthrow — a row that needs a React effect can't be measured without mounting; useuseMugenEffect.
This is why rows must be pure item → tree, with the mugen hooks called in the same order every render.