mini-browser — the spine
This is not a project you sit down and build. It is a spine threaded through the browser strand: each milestone is built immediately before the Blink reading it motivates, so that the reading answers a question you already have.
Built as one block at the end, it teaches nothing — every "why is Blink like this?" question has already been answered by prose. That is specification §45 applied at module granularity.
| M | Milestone | Built in | Paired reading |
|---|---|---|---|
| 1 | hard-coded boxes | bi-03 | — |
| 2 | HTML tokenizer + tree builder | bi-03 | core/html/parser/ |
| 3 | DOM | bi-04 | core/dom/ |
| 4 | CSS parser | bi-07 | core/css/ |
| 5 | selector matching | bi-07 | SelectorChecker |
| 6 | cascade, inheritance, computed style | bi-07 | StyleForLayoutObject |
| 7 | layout tree | bi-08 | core/layout/ |
| 8 | block layout | bi-08 | block layout algorithm |
| 9 | inline text | bi-08 | core/layout/inline/ |
| 10 | display list | bi-09 | core/paint/ |
| 11 | raster | bi-09 | paint → raster boundary |
| 12 | events | bi-11 | event dispatch |
| 13 | incremental invalidation | bi-11 | style + paint invalidation |
| 14 | minimal JS integration | bi-05 | bindings |
| 15 | profiling / optimisation | bi-12 | tracing |
At every milestone record the §47 comparison: ours vs production requirements vs Blink.
The rule that makes this work
For milestones 6, 8, 11 and 13, build the naive version first and measure it, then optimise. The cost curve you measure is the argument for the production design. Skipping the naive version means the optimisation is a fact you were told rather than a conclusion you reached.
src/foster.js from the parsing lab is the seed of M2.