ssh git@grit.deadkey.org man

Commit 5ca8f3d h

Pat Ambrosio · 26-08-25 21:54

Fix phone type size and a page title hanging off its own text

Changes: 3 files +123 -13

Changes (Side-by-Side)

share/jenny/layout/page.sh +50 -11

cat << _EOF_
208 body { 208 body {
209 background-color: var(--bg-primary); 209 background-color: var(--bg-primary);
210 color: var(--text-primary); 210 color: var(--text-primary);
211 font-size: 14px;
211 font-size: 16px;
212 padding: 1em; 212 padding: 1em;
213 font-family: 'Roboto', sans-serif; 213 font-family: 'Roboto', sans-serif;
214 line-height: 1.5em; 214 line-height: 1.5em;
215 } 215 }
216 @media (min-width: 736px) { body { font-size: 16px } }
216 /* One size at every width, as the index has always done. This was
217 14px below 736px and 16px above, so the same prose was a size
218 smaller on a phone than the index that linked to it -- and 14px is
219 small for body copy on a phone to begin with. There is no reason
220 for a narrow screen to get smaller text: the measure is already
221 handled by the column width, and shrinking the type only buys
222 characters per line that the reader did not ask for. */
217 article { padding: 0; margin: 1em 0; max-width: 74ch; } 223 article { padding: 0; margin: 1em 0; max-width: 74ch; }
218 /* 74ch is sized for a post: the arrow's column plus a 60ch body. A 224 /* 74ch is sized for a post: the arrow's column plus a 60ch body. A
219 sidebar is an addition to that, so the article grows by exactly the 225 sidebar is an addition to that, so the article grows by exactly the
cat << _EOF_
318 title sat at the far left while its own body was indented past it. 324 title sat at the far left while its own body was indented past it.
319 The heading reserves the same column the body does, by the same 325 The heading reserves the same column the body does, by the same
320 mechanism, so the two line up whatever the width. */ 326 mechanism, so the two line up whatever the width. */
321 .heading-page { display: flex; align-items: baseline; margin-left: 0; }
322 .heading-page .title { display: block; margin: 0; flex: 1; }
323 .heading-page .home-spacer { margin-bottom: 0; flex: 0 0 auto; }
324 /* Beside a sidebar the whole cell is already offset, so the heading 327 /* Beside a sidebar the whole cell is already offset, so the heading
325 must not indent again -- the title would land a column right of its 328 must not indent again -- the title would land a column right of its
326 own body. */ 329 own body. */
cat << _EOF_
337 .heading a { text-decoration: none; } 340 .heading a { text-decoration: none; }
338 .heading .title { color: var(--text-heading); display: table-cell; margin: 0 0 1em; font-weight:bold; max-width: 500px; } 341 .heading .title { color: var(--text-heading); display: table-cell; margin: 0 0 1em; font-weight:bold; max-width: 500px; }
339 .heading .title:hover { text-decoration: underline; } 342 .heading .title:hover { text-decoration: underline; }
343 /* Block with inline-block children, not flex: flex items do not wrap,
344 so the heading spacer held its column at every width while the
345 body's spacer -- an inline-block beside an inline-block -- let the
346 contents wrap under it below about 700px. The title stayed indented
347 97px above a body that had returned to 0, which on a phone reads as
348 the title pushed off to the right of its own text. Sharing the
349 body's mechanism is what makes the two reflow at the same width.
350
351 These sit after ".heading .title" deliberately. That rule sets
352 display: table-cell at the same specificity as this one, so source
353 order decides, and a table-cell takes its own row -- the title
354 dropped below the spacer at every width. The previous version used
355 display: flex on the parent, which sidestepped the collision only
356 because flex overrides a child's display outright. */
357 .heading-page { display: block; margin-left: 0; }
358 /* Keeps the base rule's bottom margin. An earlier version zeroed the
359 whole margin so the title could sit in a flex row, which left a
360 page title touching its first paragraph -- 0px of gap where a post
361 has 16px. The side margins still go, since the column comes from
362 the spacer beside it. */
363 .heading-page .title { display: inline-block; margin: 0 0 1em;
364 vertical-align: top; max-width: 100%; }
365 .heading-page .home-spacer { margin-bottom: 0; }
340 .heading .stamp, 366 .heading .stamp,
341 .home { display: inline-block; text-align:right; margin-right: 1.5em; } 367 .home { display: inline-block; text-align:right; margin-right: 1.5em; }
342 .home { text-decoration: none; width: 5.4em; margin-bottom: 1.5em; text-align: left; color: var(--link-home); } .home:hover { color: var(--link-home-hover); } 368 .home { text-decoration: none; width: 5.4em; margin-bottom: 1.5em; text-align: left; color: var(--link-home); } .home:hover { color: var(--link-home-hover); }
cat << _EOF_
436 /* No indent at this width: 6.82rem plus a 60ch column overflows a 462 /* No indent at this width: 6.82rem plus a 60ch column overflows a
437 phone and forces horizontal scroll. */ 463 phone and forces horizontal scroll. */
438 .contents-cell { padding-left: 0; } 464 .contents-cell { padding-left: 0; }
439 /* The heading is left alone here. A post has no breakpoint at all --
440 its heading and body both collapse by wrapping, at the same width,
441 because both are driven by the same inline column. Forcing the
442 heading to block at 735px un-indented the title while the body was
443 still indented, so the two split apart for exactly one step of the
444 range. Letting it wrap on its own keeps them together. */
465 /* No spacer at this width, in the heading or the body. The indent
466 exists to reserve a post's back-arrow column; on a phone it costs
467 more than it reserves -- at 375px it takes the measure from 42
468 characters to 32, and at 320px down to 25.
469
470 The body already wraps under its spacer here, because a 60ch
471 column cannot sit beside it. The heading did not: a two-word
472 title fits beside the spacer at any width, so the title stayed
473 indented 97px above a body that had returned to the left margin.
474 That is the artifact -- a title hanging to the right of its own
475 text on a phone.
476
477 Removing both is what keeps them together. Matching the body's
478 content-driven turnover instead was tried and does not work: the
479 heading and the body hold different content, so they give out at
480 different widths, and tuning the title's column to compensate only
481 moved the disagreement (a 10px window at 614-622px became a 90px
482 one at 528-612px). */
483 .home-spacer { display: none; }
445 } 484 }
446 .contents :first-child { margin-top: 0; } 485 .contents :first-child { margin-top: 0; }
447 h1, h2, h3, h4, h5, h6 { font-size: 1em; font-weight: normal; text-transform: uppercase; margin: 2em 0 1em; } 486 h1, h2, h3, h4, h5, h6 { font-size: 1em; font-weight: normal; text-transform: uppercase; margin: 2em 0 1em; }

share/jenny/layout/post.sh +8 -2

cat << _EOF_
105 body { 105 body {
106 background-color: var(--bg-primary); 106 background-color: var(--bg-primary);
107 color: var(--text-primary); 107 color: var(--text-primary);
108 font-size: 14px;
108 font-size: 16px;
109 padding: 1em; 109 padding: 1em;
110 font-family: 'Roboto', sans-serif; 110 font-family: 'Roboto', sans-serif;
111 line-height: 1.5em; 111 line-height: 1.5em;
112 } 112 }
113 @media (min-width: 736px) { body { font-size: 16px } }
113 /* One size at every width, as the index has always done. This was
114 14px below 736px and 16px above, so the same prose was a size
115 smaller on a phone than the index that linked to it -- and 14px is
116 small for body copy on a phone to begin with. There is no reason
117 for a narrow screen to get smaller text: the measure is already
118 handled by the column width, and shrinking the type only buys
119 characters per line that the reader did not ask for. */
114 article { padding: 0; margin: 1em 0; max-width: 74ch; } 120 article { padding: 0; margin: 1em 0; max-width: 74ch; }
115 article a { color: var(--link-article); } 121 article a { color: var(--link-article); }
116 article img { max-width: 100% } 122 article img { max-width: 100% }

test/regressions.sh +65 -0

assert_contains "$narrow" 'min-width: 0'
1423 it "the build is clean for nav sidebar" 1423 it "the build is clean for nav sidebar"
1424 assert_eq "$stderr_output" "" 1424 assert_eq "$stderr_output" ""
1425 1425
1426 suite "type size and heading spacing"
1427
1428 # Reported from an iPhone: body copy was a size smaller on posts and pages
1429 # than on the index, and a page title hung to the right of its own text.
1430
1431 ts="$TMPROOT/typesize"
1432 make_blog_min "$ts" 2 10
1433 mkdir -p "$ts/pages" "$ts/navs"
1434 printf 'Item\t/item\n' > "$ts/navs/side.tsv"
1435 printf '# Plain\n\nBody copy long enough to be worth reading on a phone.\n' > "$ts/pages/plain.md"
1436 printf 'Nav: side\n\n# Sidebar\n\nBody copy.\n' > "$ts/pages/sb.md"
1437 run_jenny_split "$ts"
1438
1439 it "posts do not shrink their body text on narrow screens"
1440 # The index has always been a flat 16px. Posts and pages were 14px below
1441 # 736px, so the same prose was smaller on a phone than the index linking to
1442 # it. Nothing about a narrow screen calls for smaller type -- the measure is
1443 # handled by the column width.
1444 post_file=$(find "$ts/.dist/post" -name '*.html' | head -1)
1445 assert_file_not_contains "$post_file" '@media (min-width: 736px) { body { font-size'
1446
1447 it "pages do not shrink their body text on narrow screens"
1448 assert_file_not_contains "$ts/.dist/plain.html" '@media (min-width: 736px) { body { font-size'
1449
1450 it "every layout declares the same base type size"
1451 # Compare the declarations rather than trusting they look alike.
1452 idx_size=$("$GREP" -oE 'font-size: [0-9]+px;' "$ts/.dist/index.html" | head -1)
1453 post_size=$("$GREP" -oE 'font-size: [0-9]+px;' "$post_file" | head -1)
1454 page_size=$("$GREP" -oE 'font-size: [0-9]+px;' "$ts/.dist/plain.html" | head -1)
1455 assert_eq "$post_size" "$idx_size"
1456 assert_eq "$page_size" "$idx_size"
1457
1458 it "a page title keeps a gap above its body"
1459 # ".heading-page .title { margin: 0 }" zeroed the base rule's bottom margin so
1460 # the title could sit in a flex row; the title then touched the first
1461 # paragraph, 0px where a post has 1em.
1462 assert_file_contains "$ts/.dist/plain.html" '.heading-page .title { display: inline-block; margin: 0 0 1em;'
1463
1464 it "the arrow column is dropped on narrow screens"
1465 # The indent reserves a post's back-arrow column. On a phone it costs more
1466 # than it reserves -- at 375px it takes the measure from 42 characters to 32 --
1467 # and the body already wraps to the left margin there while the heading, whose
1468 # title always fits beside the spacer, did not. The title was left hanging
1469 # 97px right of its own text.
1470 narrow_sp=$("$SED" -n '/@media (max-width: 735px)/,/^ }/p' "$ts/.dist/plain.html")
1471 assert_contains "$narrow_sp" '.home-spacer { display: none; }'
1472
1473 it "the heading is not a flex row"
1474 # Flex items do not wrap, which is why the heading held its column at widths
1475 # where the body had given up on it.
1476 assert_file_not_contains "$ts/.dist/plain.html" '.heading-page { display: flex'
1477
1478 it "the page title rule comes after the base title rule"
1479 # ".heading .title" sets display: table-cell at equal specificity, so source
1480 # order decides. Placed earlier, the page rule lost and the title -- a
1481 # table-cell -- took its own row below the spacer at every width.
1482 base_line=$("$GREP" -n '\.heading \.title { color' "$ts/.dist/plain.html" | head -1 | cut -d: -f1)
1483 page_line=$("$GREP" -n '\.heading-page \.title {' "$ts/.dist/plain.html" | head -1 | cut -d: -f1)
1484 [ -n "$base_line" ] && [ -n "$page_line" ] && [ "$page_line" -gt "$base_line" ] \
1485 && pass "page title rule wins on source order" \
1486 || fail "page title rule at line $page_line must come after base rule at line $base_line"
1487
1488 it "the build is clean"
1489 assert_eq "$stderr_output" ""
1490
1426 suite "template-baked navs" 1491 suite "template-baked navs"
1427 1492
1428 # The default theme renders no footer, so render_footer_nav is not called from 1493 # The default theme renders no footer, so render_footer_nav is not called from

Raw

commit 5ca8f3d2fa08691bb803ce9bd5d22cfda480fbbc
Author: Pat Ambrosio <cp.ambrosio@gmail.com>
Date:   Tue Aug 25 21:54:00 2026 +0800

    Fix phone type size and a page title hanging off its own text
    
    Both reported from an iPhone.
    
    Body copy was 14px on posts and pages below 736px and 16px above, while
    the index has always been a flat 16px -- so the same prose was a size
    smaller on a phone than the index that linked to it. Nothing about a
    narrow screen calls for smaller type: the measure is set by the column
    width, and shrinking the type only buys characters per line the reader
    did not ask for. Posts and pages now match the index at every width. The
    index, tag and 404 layouts were already flat and are untouched.
    
    A page title sat 97px right of its own body on a phone. The heading was a
    flex row, and flex items do not wrap: the spacer held its column down to
    320px while the body -- a full 60ch column that cannot fit beside it --
    had long since wrapped to the left margin. The fix is to drop the spacer
    entirely below 736px, in the heading and the body together. The indent
    reserves a post's back-arrow column, and on a phone that costs more than
    it reserves: at 375px it takes the measure from 42 characters to 32, and
    at 320px to 25.
    
    Matching the body's content-driven turnover was tried first and does not
    work. The heading and the body hold different content -- a two-word title
    against a 60ch column -- so they give out at different widths, and tuning
    the title's column to compensate only moved the disagreement: a 10px
    window at 614-622px became a 90px one at 528-612px. The post itself never
    wraps its heading, so there was no mechanism to copy.
    
    Two things fell out of that work:
    
    The page title rule now sits after ".heading .title", which sets
    display: table-cell at equal specificity. Placed earlier it lost on source
    order and the title, as a table-cell, took its own row below the spacer at
    every width. The previous flex version had sidestepped the collision only
    because flex overrides a child's display outright.
    
    The title's bottom margin is restored. Zeroing the whole margin for the
    flex row left a page title touching its first paragraph -- 0px of gap
    where a post has 1em.
    
    Verified by measuring at 320-1920px: title and body share a left edge on
    every page type at all 378 sampled widths, and prose is 16px on index,
    post, page, sidebar page and tag alike.
    
    Tests: a new suite covering the type size across layouts, the heading
    spacing, the narrow-width spacer removal, and the source-order dependency
    that a specificity tie leaves decidable only by position. Checked by
    mutation -- restoring the shrinking font, re-zeroing the margin -- both
    caught.
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01WnSAstgnDYdKPwLGv1YZE5

diff --git a/share/jenny/layout/page.sh b/share/jenny/layout/page.sh
index 3316907..df3312d 100755
--- a/share/jenny/layout/page.sh
+++ b/share/jenny/layout/page.sh
@@ -208,12 +208,18 @@ cat << _EOF_
         body {
           background-color: var(--bg-primary);
           color: var(--text-primary);
-          font-size: 14px;
+          font-size: 16px;
           padding: 1em;
           font-family: 'Roboto', sans-serif;
           line-height: 1.5em;
         }
-        @media (min-width: 736px) { body { font-size: 16px } }
+        /* One size at every width, as the index has always done. This was
+           14px below 736px and 16px above, so the same prose was a size
+           smaller on a phone than the index that linked to it -- and 14px is
+           small for body copy on a phone to begin with. There is no reason
+           for a narrow screen to get smaller text: the measure is already
+           handled by the column width, and shrinking the type only buys
+           characters per line that the reader did not ask for. */
         article { padding: 0; margin: 1em 0; max-width: 74ch; }
         /* 74ch is sized for a post: the arrow's column plus a 60ch body. A
            sidebar is an addition to that, so the article grows by exactly the
@@ -318,9 +324,6 @@ cat << _EOF_
            title sat at the far left while its own body was indented past it.
            The heading reserves the same column the body does, by the same
            mechanism, so the two line up whatever the width. */
-        .heading-page { display: flex; align-items: baseline; margin-left: 0; }
-        .heading-page .title { display: block; margin: 0; flex: 1; }
-        .heading-page .home-spacer { margin-bottom: 0; flex: 0 0 auto; }
         /* Beside a sidebar the whole cell is already offset, so the heading
            must not indent again -- the title would land a column right of its
            own body. */
@@ -337,6 +340,29 @@ cat << _EOF_
         .heading a { text-decoration: none; }
         .heading .title { color: var(--text-heading); display: table-cell; margin: 0 0 1em; font-weight:bold; max-width: 500px;  }
         .heading .title:hover { text-decoration: underline; }
+        /* Block with inline-block children, not flex: flex items do not wrap,
+           so the heading spacer held its column at every width while the
+           body's spacer -- an inline-block beside an inline-block -- let the
+           contents wrap under it below about 700px. The title stayed indented
+           97px above a body that had returned to 0, which on a phone reads as
+           the title pushed off to the right of its own text. Sharing the
+           body's mechanism is what makes the two reflow at the same width.
+
+           These sit after ".heading .title" deliberately. That rule sets
+           display: table-cell at the same specificity as this one, so source
+           order decides, and a table-cell takes its own row -- the title
+           dropped below the spacer at every width. The previous version used
+           display: flex on the parent, which sidestepped the collision only
+           because flex overrides a child's display outright. */
+        .heading-page { display: block; margin-left: 0; }
+        /* Keeps the base rule's bottom margin. An earlier version zeroed the
+           whole margin so the title could sit in a flex row, which left a
+           page title touching its first paragraph -- 0px of gap where a post
+           has 16px. The side margins still go, since the column comes from
+           the spacer beside it. */
+        .heading-page .title { display: inline-block; margin: 0 0 1em;
+                               vertical-align: top; max-width: 100%; }
+        .heading-page .home-spacer { margin-bottom: 0; }
         .heading .stamp,
         .home { display: inline-block; text-align:right; margin-right: 1.5em; }
         .home { text-decoration: none; width: 5.4em; margin-bottom: 1.5em; text-align: left;  color: var(--link-home); } .home:hover { color: var(--link-home-hover); }
@@ -436,12 +462,25 @@ cat << _EOF_
           /* No indent at this width: 6.82rem plus a 60ch column overflows a
              phone and forces horizontal scroll. */
           .contents-cell { padding-left: 0; }
-          /* The heading is left alone here. A post has no breakpoint at all --
-             its heading and body both collapse by wrapping, at the same width,
-             because both are driven by the same inline column. Forcing the
-             heading to block at 735px un-indented the title while the body was
-             still indented, so the two split apart for exactly one step of the
-             range. Letting it wrap on its own keeps them together. */
+          /* No spacer at this width, in the heading or the body. The indent
+             exists to reserve a post's back-arrow column; on a phone it costs
+             more than it reserves -- at 375px it takes the measure from 42
+             characters to 32, and at 320px down to 25.
+
+             The body already wraps under its spacer here, because a 60ch
+             column cannot sit beside it. The heading did not: a two-word
+             title fits beside the spacer at any width, so the title stayed
+             indented 97px above a body that had returned to the left margin.
+             That is the artifact -- a title hanging to the right of its own
+             text on a phone.
+
+             Removing both is what keeps them together. Matching the body's
+             content-driven turnover instead was tried and does not work: the
+             heading and the body hold different content, so they give out at
+             different widths, and tuning the title's column to compensate only
+             moved the disagreement (a 10px window at 614-622px became a 90px
+             one at 528-612px). */
+          .home-spacer { display: none; }
         }
         .contents :first-child { margin-top: 0; }
         h1, h2, h3, h4, h5, h6 { font-size: 1em; font-weight: normal; text-transform: uppercase; margin: 2em 0 1em; }
diff --git a/share/jenny/layout/post.sh b/share/jenny/layout/post.sh
index 1bf8d27..8cd075b 100755
--- a/share/jenny/layout/post.sh
+++ b/share/jenny/layout/post.sh
@@ -105,12 +105,18 @@ cat << _EOF_
         body {
           background-color: var(--bg-primary);
           color: var(--text-primary);
-          font-size: 14px;
+          font-size: 16px;
           padding: 1em;
           font-family: 'Roboto', sans-serif;
           line-height: 1.5em;
         }
-        @media (min-width: 736px) { body { font-size: 16px } }
+        /* One size at every width, as the index has always done. This was
+           14px below 736px and 16px above, so the same prose was a size
+           smaller on a phone than the index that linked to it -- and 14px is
+           small for body copy on a phone to begin with. There is no reason
+           for a narrow screen to get smaller text: the measure is already
+           handled by the column width, and shrinking the type only buys
+           characters per line that the reader did not ask for. */
         article { padding: 0; margin: 1em 0; max-width: 74ch; }
         article a { color: var(--link-article); }
         article img { max-width: 100% }
diff --git a/test/regressions.sh b/test/regressions.sh
index ee2698e..49218b0 100755
--- a/test/regressions.sh
+++ b/test/regressions.sh
@@ -1423,6 +1423,71 @@ assert_contains "$narrow" 'min-width: 0'
 it "the build is clean for nav sidebar"
 assert_eq "$stderr_output" ""
 
+suite "type size and heading spacing"
+
+# Reported from an iPhone: body copy was a size smaller on posts and pages
+# than on the index, and a page title hung to the right of its own text.
+
+ts="$TMPROOT/typesize"
+make_blog_min "$ts" 2 10
+mkdir -p "$ts/pages" "$ts/navs"
+printf 'Item\t/item\n' > "$ts/navs/side.tsv"
+printf '# Plain\n\nBody copy long enough to be worth reading on a phone.\n' > "$ts/pages/plain.md"
+printf 'Nav: side\n\n# Sidebar\n\nBody copy.\n' > "$ts/pages/sb.md"
+run_jenny_split "$ts"
+
+it "posts do not shrink their body text on narrow screens"
+# The index has always been a flat 16px. Posts and pages were 14px below
+# 736px, so the same prose was smaller on a phone than the index linking to
+# it. Nothing about a narrow screen calls for smaller type -- the measure is
+# handled by the column width.
+post_file=$(find "$ts/.dist/post" -name '*.html' | head -1)
+assert_file_not_contains "$post_file" '@media (min-width: 736px) { body { font-size'
+
+it "pages do not shrink their body text on narrow screens"
+assert_file_not_contains "$ts/.dist/plain.html" '@media (min-width: 736px) { body { font-size'
+
+it "every layout declares the same base type size"
+# Compare the declarations rather than trusting they look alike.
+idx_size=$("$GREP" -oE 'font-size: [0-9]+px;' "$ts/.dist/index.html" | head -1)
+post_size=$("$GREP" -oE 'font-size: [0-9]+px;' "$post_file" | head -1)
+page_size=$("$GREP" -oE 'font-size: [0-9]+px;' "$ts/.dist/plain.html" | head -1)
+assert_eq "$post_size" "$idx_size"
+assert_eq "$page_size" "$idx_size"
+
+it "a page title keeps a gap above its body"
+# ".heading-page .title { margin: 0 }" zeroed the base rule's bottom margin so
+# the title could sit in a flex row; the title then touched the first
+# paragraph, 0px where a post has 1em.
+assert_file_contains "$ts/.dist/plain.html" '.heading-page .title { display: inline-block; margin: 0 0 1em;'
+
+it "the arrow column is dropped on narrow screens"
+# The indent reserves a post's back-arrow column. On a phone it costs more
+# than it reserves -- at 375px it takes the measure from 42 characters to 32 --
+# and the body already wraps to the left margin there while the heading, whose
+# title always fits beside the spacer, did not. The title was left hanging
+# 97px right of its own text.
+narrow_sp=$("$SED" -n '/@media (max-width: 735px)/,/^        }/p' "$ts/.dist/plain.html")
+assert_contains "$narrow_sp" '.home-spacer { display: none; }'
+
+it "the heading is not a flex row"
+# Flex items do not wrap, which is why the heading held its column at widths
+# where the body had given up on it.
+assert_file_not_contains "$ts/.dist/plain.html" '.heading-page { display: flex'
+
+it "the page title rule comes after the base title rule"
+# ".heading .title" sets display: table-cell at equal specificity, so source
+# order decides. Placed earlier, the page rule lost and the title -- a
+# table-cell -- took its own row below the spacer at every width.
+base_line=$("$GREP" -n '\.heading \.title { color' "$ts/.dist/plain.html" | head -1 | cut -d: -f1)
+page_line=$("$GREP" -n '\.heading-page \.title {' "$ts/.dist/plain.html" | head -1 | cut -d: -f1)
+[ -n "$base_line" ] && [ -n "$page_line" ] && [ "$page_line" -gt "$base_line" ] \
+  && pass "page title rule wins on source order" \
+  || fail "page title rule at line $page_line must come after base rule at line $base_line"
+
+it "the build is clean"
+assert_eq "$stderr_output" ""
+
 suite "template-baked navs"
 
 # The default theme renders no footer, so render_footer_nav is not called from