From 1d6821b732e68efffbf72bce6250bee642868a62 Mon Sep 17 00:00:00 2001 From: Vuong Hoang Date: Mon, 28 Sep 2026 10:32:26 -0700 Subject: [PATCH] =?UTF-8?q?fix(as-S4):=20reading=20measure=20=E2=80=94=20p?= =?UTF-8?q?rose=20at=2072ch,=20headings=20step=20by=20~1.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From the anti-slop run (design-dev, 2026-09-28). A rendered doc ran 110-120 characters a line and its h3 sat at 1.08x its body. Prose blocks in .markdown-body are capped at 72ch (pre and tables keep the full width, where they scroll), and h3/h2/h1 step at 1.2/1.44/1.73em. Measured in a real browser: a long paragraph now reads under 76 characters across, and every heading step is >= 1.18. Contract: as_antislop S4. Falsifiers: antislop.toml 43/43 proved (S1-S4); all 12 tables 323/323 proved on this tree. --- booth/templates/base.html | 5 +++- docs/contracts/as_antislop.contract.md | 12 ++++++-- tests/mutations/antislop.toml | 16 +++++++++++ tests/test_antislop_browser.py | 39 ++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 3 deletions(-) diff --git a/booth/templates/base.html b/booth/templates/base.html index ddf126f..e1bdb1e 100644 --- a/booth/templates/base.html +++ b/booth/templates/base.html @@ -730,7 +730,10 @@ .markdown-body{color:var(--text-body);line-height:1.65;font-size:15px;overflow-wrap:break-word} .markdown-body h1,.markdown-body h2,.markdown-body h3{color:var(--text-heading);line-height:1.25; letter-spacing:var(--tracking-h);margin:1.6em 0 .5em;font-weight:650} - .markdown-body h1{font-size:1.6em}.markdown-body h2{font-size:1.3em}.markdown-body h3{font-size:1.08em;font-weight:600} + .markdown-body h1{font-size:1.73em}.markdown-body h2{font-size:1.44em}.markdown-body h3{font-size:1.2em;font-weight:600} + /* as S4: headings step by ~1.2 each (h3 was 1.08 over its body); prose reads at a + book's measure, while pre and tables keep the full width where they scroll */ + .markdown-body > :is(p,ul,ol,blockquote,dl,h1,h2,h3,h4,h5,h6){max-width:72ch} .markdown-body h1,.markdown-body h2{border-bottom:1px solid var(--border-subtle);padding-bottom:.3em} .markdown-body :first-child{margin-top:0} .markdown-body p,.markdown-body ul,.markdown-body ol,.markdown-body blockquote{margin:.7em 0} diff --git a/docs/contracts/as_antislop.contract.md b/docs/contracts/as_antislop.contract.md index a94b072..49929ea 100644 --- a/docs/contracts/as_antislop.contract.md +++ b/docs/contracts/as_antislop.contract.md @@ -109,9 +109,17 @@ The run found that the Booth is sound on desktop and has a set of problems a vie - `.vname` already ellipsises; the detector measures the clipped inner width. - The filmstrip clips its next frame at the edge on purpose: the clipped frame is the "there is more" cue of a horizontal scroller. -## S4 to S6 +## S4 — reading measure + +**The rule:** a rendered document reads at a book's measure and says its structure with size. + +- **Measure.** Prose blocks in `.markdown-body` (paragraphs, lists, block quotes, headings, definition lists) are at most `72ch` wide. Wide blocks (`pre`, tables) keep the full width, where they scroll. + - *Falsifiable:* on the doc view at 1280 wide, a long paragraph measures at most 76 characters of its own font across: `test_doc_prose_reads_at_a_book_measure`. +- **Heading scale.** h3 : body, h2 : h3 and h1 : h2 are each at least 1.18 (h3 `1.2em`, h2 `1.44em`, h1 `1.73em`). Before this, h3 was `1.08em` over its body. + - *Falsifiable:* `test_doc_headings_step_by_size`. + +## S5 and S6 These are added as each slice is staged: -- S4, reading measure; - S5, interaction and screen readers; - S6, the operator's rulings (tagline, needs-you stripe, matte brand dot, the Wipe-now stripe on `::before`). diff --git a/tests/mutations/antislop.toml b/tests/mutations/antislop.toml index 8c57a7e..2396597 100644 --- a/tests/mutations/antislop.toml +++ b/tests/mutations/antislop.toml @@ -313,3 +313,19 @@ file = "booth/templates/base.html" test = "tests/test_antislop_browser.py::test_board_head_stays_compact_on_a_phone" old = """ .bench-head{flex-wrap:wrap}.bench-title{white-space:nowrap}.bench-note{flex:1 1 100%}}""" new = """ }""" + +# ---- S4: reading measure + +[[mutation]] +label = "S4 prose runs the full width again" +file = "booth/templates/base.html" +test = "tests/test_antislop_browser.py::test_doc_prose_reads_at_a_book_measure" +old = """ .markdown-body > :is(p,ul,ol,blockquote,dl,h1,h2,h3,h4,h5,h6){max-width:72ch}""" +new = """""" + +[[mutation]] +label = "S4 h3 flat against its body again (1.08em)" +file = "booth/templates/base.html" +test = "tests/test_antislop_browser.py::test_doc_headings_step_by_size" +old = """.markdown-body h3{font-size:1.2em;font-weight:600}""" +new = """.markdown-body h3{font-size:1.08em;font-weight:600}""" diff --git a/tests/test_antislop_browser.py b/tests/test_antislop_browser.py index f22e4d7..b496acf 100644 --- a/tests/test_antislop_browser.py +++ b/tests/test_antislop_browser.py @@ -115,3 +115,42 @@ def test_review_header_is_one_line_on_a_phone(browser, live): assert page.locator(".topbar .tagline").evaluate("e => getComputedStyle(e).display") != "none", \ "the Desk keeps its tagline (negative control)" page.close() + + +# ---- S4: reading measure -------------------------------------------------------- + +_DOC = "# The Booth\n\n## What it is\n\n### Stated once\n\n" + ("The Booth is the fleet's operator-review surface. Agents post work; the operator looks at it, judges it, and the judgment gets back to the agent. " * 4) + "\n" + + +def _doc_page(browser, live): + base, root = live + d = root / "d" + d.mkdir() + (d / "doc.md").write_text(_DOC) + page = browser.new_page(viewport={"width": 1280, "height": 900}) + page.goto(f"{base}/b/d/view?f=doc.md", wait_until="load") + return page + + +def test_doc_prose_reads_at_a_book_measure(browser, live): + page = _doc_page(browser, live) + chars = page.locator(".markdown-body p").first.evaluate("""p => { + const probe = document.createElement('span'); + probe.textContent = '0'.repeat(100); + probe.style.cssText = 'position:absolute;visibility:hidden;white-space:nowrap'; + p.appendChild(probe); + const ch = probe.getBoundingClientRect().width / 100; + probe.remove(); + return p.getBoundingClientRect().width / ch; + }""") + assert chars <= 76, f"a paragraph runs {chars:.0f} characters across" + page.close() + + +def test_doc_headings_step_by_size(browser, live): + page = _doc_page(browser, live) + size = lambda sel: page.locator(f".markdown-body {sel}").first.evaluate("e => parseFloat(getComputedStyle(e).fontSize)") + body, h3, h2, h1 = size("p"), size("h3"), size("h2"), size("h1") + for lo, hi, name in ((body, h3, "h3:body"), (h3, h2, "h2:h3"), (h2, h1, "h1:h2")): + assert hi / lo >= 1.18, f"{name} is {hi / lo:.2f} ({hi}px over {lo}px)" + page.close()