Product rules
The rules the owner set for the whole-page output, stated as the engine's contract, each with the case that made it a rule.
The owner's brief, as docs/SESSION-18-WHOLE-PAGE.md restates it: the product is the
full-page markdown / text. Nothing a reader sees may be lost — "not even a single
character" — nothing the browser hides may be added, the order is the page's, the structure
stays, and the engine must never present a false output. The filtered content_markdown is
secondary and must not regress. Each rule below has the case that made it one; every case is
in CHANGELOG.md under its PR number.
1. Never a false output
A page the engine cannot read is a refusal with the reason, not a page of text.
- A one-sided wall is left out and named (PR #64). Cloudflare let a plain fetch of
columbia.edu/~fdc/sample.htmlthrough and answered the browser with "Performing security verification … Ray ID"; merged, the wall's sentences became content. Each side is now judged alone; the wall side is dropped and quoted inrender_error. - A login redirect is a wall (PRs #67, #73).
old.reddit.comsends a thread's reader to/login/?dest=…— four words and an empty logo. A fetch that lands on a login URL holding a password field or under 150 words is refused asredirected to a login page (…). - A browser answered with a 503 is not a page (PR #89). On
flipkart.com/mobilesthe plain fetch returned the listing (200, 560 KB) and Chromium, seconds later, "No server is available to handle this request" — which, having navigated and measured, reportedokand was merged in. A 5xx render is now a failed side; both sides 5xx is a refusal. The refusal types and theirkindare in Concepts.
2. Nothing hidden is added
What the browser hid is not on the page, and the union must not put it back from the static copy.
- Text pushed off the page is not on the page (PR #88).
vtu.ac.incarries about sixty injected gambling links on every page, each in<div style="position:absolute; left:-20914565266523px">. The browser reports a box for them — twenty trillion pixels to the left — and "has a box" had been read as "visible", so the Markdown opened with sixty lines of spam before "About VTU". A box entirely at negative coordinates is now hidden likedisplay: none;/about-vtuwent from 202 to 148 blocks. - What the render hid stays hidden through the union (PR #65). php.net's manual TOC
(
nav#trick, about 100 links,display: none) was dropped by the render and put straight back from the static copy: 330 → 233 blocks oncehidden_matterwas consulted.
The exception is deliberate and opt-in: include_hidden_text keeps screen-reader-only
labels for a caller who wants every string in the DOM.
3. The page's order is kept
Reading order is measured from the rendered layout where there is one, and source order — labelled as such — where there is not.
- Measuring against Chromium's own text found split paragraphs reversed and, when an
article sat inside one
<span>, every heading ahead of every paragraph (AppleInsider, PR #63). - A block's XPath is the geometry map's key and must be computed before the tree is edited
(PR #90). On
allbirds.com/collections/mens, removing 641display: noneelements first left 161 of 217 blocks without a rectangle and the page in source order; recall 0.685 → 0.887 once paths were stamped before editing.
4. Real repeats are kept
A page that shows two identical headings has two. Deduplication exists for hidden twins — the mobile grid beside the desktop grid — not for content a page really repeats.
- On a rendered page, repeats are kept through their rectangles: two boxes, two blocks.
- On a page the browser could not measure, a table or code block that repeats stays
(PR #69):
columbia.edu/~fdc/sample.htmlshows one demo table four times and came out with one; recall 0.979 → 0.995 with all four.
Repeated prose on an unmeasured page is still deduplicated, and that is a measured trade-off rather than an oversight. Keeping it by the same neighbour rule cost WCXB −0.0017 and Zyte −0.003; businessinsider.com carries its article three times interleaved with different furniture and fell from 0.998 to 0.471 (SESSION-18 §3). On a page nobody measured, repeated prose is a hidden copy far more often than a deliberate repeat.
5. Structure is kept
Tables stay tables, code stays code, lists nest, and the separators a reader sees survive.
<hr>is a block (BlockKind.RULE, rendered---); it was dropped outright on 6 of the 14 census sites whose markup was inspected.<dl>renders as a definition list instead of alternating paragraphs (the Cambridge Unicode FAQ, every php.net parameter list). Merged-cell tables keep their own markup because a pipe table cannot say that a cell spans three columns (PR #75).<li><p>…</p></li>lists are lists (PR #66; catb.org's eight lists, tldp.org's HOWTO index).- A browser-side code editor (CodeMirror, Monaco, Ace) is one
codeblock, gutter and cursor layers left out (PR #73; MDN's interactive examples had come out as one paragraph per line number).
6. Filtered content must not regress
content_markdown is secondary, and a change to the whole page may not lower it on the
public boards: at 928cf84 (21 September 2026, production path) WCXB dev 0.861,
WCXB test 0.878, Zyte 0.931, WCEB 0.885 joined / 0.860, WebMainBench 0.650 column mean,
DAnIEL 0.783. Every whole-page PR in the session table carries its board deltas.
The cautionary case: PR #68 rendered [text](href) into every table cell regardless of
include_links, and WebMainBench — scored with links off — fell from table_edit 0.390 to
0.338. Five PRs of "WCXB =, Zyte =" said nothing, because those corpora do not score links
in cells; PR #78 fixed it six PRs later. CONTRIBUTING.md now says: anything that touches
table, code or Markdown rendering re-runs WebMainBench before merge. See
Benchmarks for the runners.