src — mini-react (stages 1–7)

tiny-test.mjs   test runner (plumbing)
fake-dom.mjs    ~60-line DOM, instrumented to COUNT operations (plumbing)
spec.mjs        executable specification — run this
runtime.js      ← YOU WRITE THIS
node spec.mjs

Required exports

export function setDocument(doc)                    // spec injects the fake DOM
export function createElement(type, props, ...kids)
export function render(element, container)
export function useState(initial)
export function useEffect(fn, deps)

key is a reconciliation hint, not a DOM attribute — do not setAttribute('key', ...). React does not, and the stage-4 assertions compare rendered output.

Why the DOM is fake and counted

fake-dom.mjs counts every createElement, appendChild, insertBefore, removeChild, setAttribute and text mutation. That turns "the virtual DOM avoids DOM work" from a slogan into an assertion: stage 3's last test changes one item in a 200-item list and requires ≤ 5 DOM operations. A naive rebuild does ~200 and fails.

It also gives you node identity, which is how stage 4 tests that a keyed reorder moves nodes rather than recreating them. That is the mechanical version of "state follows identity, not position."

Order matters

Build the unkeyed reconciler first and watch stage 4 fail. Then add keys. Skipping the broken version means keys are a rule you were told rather than a fix you needed — and you will not be able to answer the review question about why the runtime cannot infer identity itself.

Stages 8–11 (batching, scheduling, interruptible work) are not here. They are fw-04, after bi-11. There is a separate spec there.