ssh git@grit.deadkey.org man

Commit f76749e h

Pat Ambrosio · 26-08-28 11:43

Mark a section heading with a rule above it

Changes: 3 files +81 -0

Changes (Side-by-Side)

share/jenny/layout/page.sh +29 -0

cat << _EOF_
844 background-color: var(--bg-secondary); 844 background-color: var(--bg-secondary);
845 padding: 1em; border-radius: 3px; } 845 padding: 1em; border-radius: 3px; }
846 h1, h2, h3, h4, h5, h6 { font-size: 1em; font-weight: normal; text-transform: uppercase; margin: 2em 0 1em; } 846 h1, h2, h3, h4, h5, h6 { font-size: 1em; font-weight: normal; text-transform: uppercase; margin: 2em 0 1em; }
847 /* Every heading is body size, which keeps the page even but leaves a
848 section heading reading as a stray line of capitals: uppercase alone
849 does not say "new section". A rule above says it structurally rather
850 than typographically, so the type stays exactly as it is.
851
852 Top two levels only. Deeper headings sit inside a section and would
853 be cut off from it by a line of their own.
854
855 Every section gets one, including the first: a rule that starts at
856 the second section makes the first look like part of the preamble.
857 The exception is a page whose body repeats its own title as an h1 --
858 ruling that draws a line immediately under the title in the header.
859 A post has no such repeat, its title having been lifted into the
860 header, so its opening h2 is a real section and is ruled like the
861 rest. Scoped to .contents because the title itself lives in
862 .heading, outside it. */
863 .contents :is(h1, h2) {
864 border-top: 1px solid var(--border-light);
865 padding-top: 1.5em;
866 }
867 /* The one exception: a page repeats its title as the body's first
868 heading, and an h1 with no heading before it is exactly that repeat.
869 A rule there draws a line immediately under the title in the header.
870 A post has no repeat -- its title is lifted into the header -- so its
871 opening h2 is a real section and keeps its rule. */
872 .contents h1:not(:is(h1, h2) ~ *) {
873 border-top: 0;
874 padding-top: 0;
875 }
847 ol, ul { padding-left: 1em; } 876 ol, ul { padding-left: 1em; }
848 /* Notes and references are two different lists, so each is labelled. 877 /* Notes and references are two different lists, so each is labelled.
849 The wording lives here rather than in the parser: md2html.awk marks 878 The wording lives here rather than in the parser: md2html.awk marks

share/jenny/layout/post.sh +29 -0

cat << _EOF_
665 background-color: var(--bg-secondary); 665 background-color: var(--bg-secondary);
666 padding: 1em; border-radius: 3px; } 666 padding: 1em; border-radius: 3px; }
667 h1, h2, h3, h4, h5, h6 { font-size: 1em; font-weight: normal; text-transform: uppercase; margin: 2em 0 1em; } 667 h1, h2, h3, h4, h5, h6 { font-size: 1em; font-weight: normal; text-transform: uppercase; margin: 2em 0 1em; }
668 /* Every heading is body size, which keeps the page even but leaves a
669 section heading reading as a stray line of capitals: uppercase alone
670 does not say "new section". A rule above says it structurally rather
671 than typographically, so the type stays exactly as it is.
672
673 Top two levels only. Deeper headings sit inside a section and would
674 be cut off from it by a line of their own.
675
676 Every section gets one, including the first: a rule that starts at
677 the second section makes the first look like part of the preamble.
678 The exception is a page whose body repeats its own title as an h1 --
679 ruling that draws a line immediately under the title in the header.
680 A post has no such repeat, its title having been lifted into the
681 header, so its opening h2 is a real section and is ruled like the
682 rest. Scoped to .contents because the title itself lives in
683 .heading, outside it. */
684 .contents :is(h1, h2) {
685 border-top: 1px solid var(--border-light);
686 padding-top: 1.5em;
687 }
688 /* The one exception: a page repeats its title as the body's first
689 heading, and an h1 with no heading before it is exactly that repeat.
690 A rule there draws a line immediately under the title in the header.
691 A post has no repeat -- its title is lifted into the header -- so its
692 opening h2 is a real section and keeps its rule. */
693 .contents h1:not(:is(h1, h2) ~ *) {
694 border-top: 0;
695 padding-top: 0;
696 }
668 ol, ul { padding-left: 1em; } 697 ol, ul { padding-left: 1em; }
669 /* Notes and references are two different lists, so each is labelled. 698 /* Notes and references are two different lists, so each is labelled.
670 The wording lives here rather than in the parser: md2html.awk marks 699 The wording lives here rather than in the parser: md2html.awk marks

test/regressions.sh +23 -0

assert_file_contains "$dg/.dist/chart.html" '.diagram { margin-block: 1.5em; ove
1366 # 375px phone viewport that the page still does not scroll sideways. 1366 # 375px phone viewport that the page still does not scroll sideways.
1367 assert_file_contains "$dg/.dist/chart.html" 'width: auto; min-width: 0; }' 1367 assert_file_contains "$dg/.dist/chart.html" 'width: auto; min-width: 0; }'
1368 1368
1369 it "a section heading is marked by a rule above it"
1370 # Headings are body size, which leaves a section heading reading as a stray
1371 # line of capitals. A rule says "new section" structurally, so the type is
1372 # untouched. Top two levels only -- a deeper heading sits inside a section and
1373 # a line of its own would cut it off from one.
1374 # Asserted against both layouts, which must not drift. The first version of
1375 # this test checked only the page layout, so mutating the post layout left it
1376 # green -- the mutation looked survivable when it was simply unobserved.
1377 assert_file_contains "$dg/.dist/chart.html" '.contents :is(h1, h2) {'
1378 assert_file_contains "$dg/.dist/post/charted.html" '.contents :is(h1, h2) {'
1379 assert_file_contains "$dg/.dist/chart.html" 'border-top: 1px solid var(--border-light);'
1380
1381 it "the body's repeat of the page title carries no rule"
1382 # A page repeats its title as the body's opening h1, and a rule there draws a
1383 # line immediately under the title in the header. An h1 with no heading before
1384 # it is exactly that repeat. Two earlier selectors got this wrong: :first-child
1385 # missed it because a p.meta precedes it, and "only rule a heading that follows
1386 # another heading" fixed pages while breaking posts -- a post's title is lifted
1387 # into the header, so its opening h2 lost its rule and the first section read
1388 # as preamble. Verified in Chrome on both layouts.
1389 assert_file_contains "$dg/.dist/chart.html" '.contents h1:not(:is(h1, h2) ~ *) {'
1390 assert_file_contains "$dg/.dist/post/charted.html" '.contents h1:not(:is(h1, h2) ~ *) {'
1391
1369 it "an unrendered diagram degrades to readable source" 1392 it "an unrendered diagram degrades to readable source"
1370 # If the script is absent or blocked, the element still holds what the author 1393 # If the script is absent or blocked, the element still holds what the author
1371 # wrote. Shown as the code it is rather than as a paragraph with the newlines 1394 # wrote. Shown as the code it is rather than as a paragraph with the newlines

Raw

commit f76749ea1cc7e8ebf4c4143eea7dbb65d6307ad0
Author: Pat Ambrosio <cp.ambrosio@gmail.com>
Date:   Fri Aug 28 11:43:49 2026 +0800

    Mark a section heading with a rule above it
    
    Every heading is body size, which keeps the page even but leaves a
    section heading reading as a stray line of capitals: uppercase alone does
    not say "new section". A rule says it structurally, so the type is left
    exactly as it is -- same size, same weight, same case.
    
    Top two levels only. A deeper heading sits inside a section and a line of
    its own would cut it off from one.
    
    The one exception is a page's opening h1, which repeats the title already
    shown in the header; a rule there draws a line immediately underneath it.
    The selector took three attempts:
    
      - :not(:first-child) does not identify that repeat, because a p.meta
        precedes it in the body.
      - "rule a heading only when another heading precedes it" fixed the page
        layout and quietly broke posts. A post's title is lifted into the
        header rather than repeated, so its opening h2 had no heading before
        it, lost its rule while its siblings kept theirs, and the first
        section read as part of the preamble.
      - An h1 with no heading before it is exactly the repeat and nothing
        else. A post's opening h2 is a real section and keeps its rule.
    
    Verified in Chrome across all four layouts -- post, page, section index,
    home -- and in both themes. The generated Notes and References labels are
    ::before content rather than headings, so they are untouched.
    
    The tests assert both layouts. The first version checked only the page
    layout, which made a mutation of the post layout look survivable when it
    was merely unobserved; that is the same shape of mistake as measuring a
    mutation against a stale backup. Removing the rule from either layout now
    fails, as does removing the title-repeat exemption.

diff --git a/share/jenny/layout/page.sh b/share/jenny/layout/page.sh
index 33c6b07..ceabb79 100755
--- a/share/jenny/layout/page.sh
+++ b/share/jenny/layout/page.sh
@@ -844,6 +844,35 @@ cat << _EOF_
                                   background-color: var(--bg-secondary);
                                   padding: 1em; border-radius: 3px; }
         h1, h2, h3, h4, h5, h6 { font-size: 1em; font-weight: normal; text-transform: uppercase; margin: 2em 0 1em; }
+        /* Every heading is body size, which keeps the page even but leaves a
+           section heading reading as a stray line of capitals: uppercase alone
+           does not say "new section". A rule above says it structurally rather
+           than typographically, so the type stays exactly as it is.
+
+           Top two levels only. Deeper headings sit inside a section and would
+           be cut off from it by a line of their own.
+
+           Every section gets one, including the first: a rule that starts at
+           the second section makes the first look like part of the preamble.
+           The exception is a page whose body repeats its own title as an h1 --
+           ruling that draws a line immediately under the title in the header.
+           A post has no such repeat, its title having been lifted into the
+           header, so its opening h2 is a real section and is ruled like the
+           rest. Scoped to .contents because the title itself lives in
+           .heading, outside it. */
+        .contents :is(h1, h2) {
+          border-top: 1px solid var(--border-light);
+          padding-top: 1.5em;
+        }
+        /* The one exception: a page repeats its title as the body's first
+           heading, and an h1 with no heading before it is exactly that repeat.
+           A rule there draws a line immediately under the title in the header.
+           A post has no repeat -- its title is lifted into the header -- so its
+           opening h2 is a real section and keeps its rule. */
+        .contents h1:not(:is(h1, h2) ~ *) {
+          border-top: 0;
+          padding-top: 0;
+        }
         ol, ul { padding-left: 1em; }
         /* Notes and references are two different lists, so each is labelled.
            The wording lives here rather than in the parser: md2html.awk marks
diff --git a/share/jenny/layout/post.sh b/share/jenny/layout/post.sh
index a5bf37d..6d258b3 100755
--- a/share/jenny/layout/post.sh
+++ b/share/jenny/layout/post.sh
@@ -665,6 +665,35 @@ cat << _EOF_
                                   background-color: var(--bg-secondary);
                                   padding: 1em; border-radius: 3px; }
         h1, h2, h3, h4, h5, h6 { font-size: 1em; font-weight: normal; text-transform: uppercase; margin: 2em 0 1em; }
+        /* Every heading is body size, which keeps the page even but leaves a
+           section heading reading as a stray line of capitals: uppercase alone
+           does not say "new section". A rule above says it structurally rather
+           than typographically, so the type stays exactly as it is.
+
+           Top two levels only. Deeper headings sit inside a section and would
+           be cut off from it by a line of their own.
+
+           Every section gets one, including the first: a rule that starts at
+           the second section makes the first look like part of the preamble.
+           The exception is a page whose body repeats its own title as an h1 --
+           ruling that draws a line immediately under the title in the header.
+           A post has no such repeat, its title having been lifted into the
+           header, so its opening h2 is a real section and is ruled like the
+           rest. Scoped to .contents because the title itself lives in
+           .heading, outside it. */
+        .contents :is(h1, h2) {
+          border-top: 1px solid var(--border-light);
+          padding-top: 1.5em;
+        }
+        /* The one exception: a page repeats its title as the body's first
+           heading, and an h1 with no heading before it is exactly that repeat.
+           A rule there draws a line immediately under the title in the header.
+           A post has no repeat -- its title is lifted into the header -- so its
+           opening h2 is a real section and keeps its rule. */
+        .contents h1:not(:is(h1, h2) ~ *) {
+          border-top: 0;
+          padding-top: 0;
+        }
         ol, ul { padding-left: 1em; }
         /* Notes and references are two different lists, so each is labelled.
            The wording lives here rather than in the parser: md2html.awk marks
diff --git a/test/regressions.sh b/test/regressions.sh
index 1fe88b4..f90ead3 100755
--- a/test/regressions.sh
+++ b/test/regressions.sh
@@ -1366,6 +1366,29 @@ assert_file_contains "$dg/.dist/chart.html" '.diagram { margin-block: 1.5em; ove
 # 375px phone viewport that the page still does not scroll sideways.
 assert_file_contains "$dg/.dist/chart.html" 'width: auto; min-width: 0; }'
 
+it "a section heading is marked by a rule above it"
+# Headings are body size, which leaves a section heading reading as a stray
+# line of capitals. A rule says "new section" structurally, so the type is
+# untouched. Top two levels only -- a deeper heading sits inside a section and
+# a line of its own would cut it off from one.
+# Asserted against both layouts, which must not drift. The first version of
+# this test checked only the page layout, so mutating the post layout left it
+# green -- the mutation looked survivable when it was simply unobserved.
+assert_file_contains "$dg/.dist/chart.html" '.contents :is(h1, h2) {'
+assert_file_contains "$dg/.dist/post/charted.html" '.contents :is(h1, h2) {'
+assert_file_contains "$dg/.dist/chart.html" 'border-top: 1px solid var(--border-light);'
+
+it "the body's repeat of the page title carries no rule"
+# A page repeats its title as the body's opening h1, and a rule there draws a
+# line immediately under the title in the header. An h1 with no heading before
+# it is exactly that repeat. Two earlier selectors got this wrong: :first-child
+# missed it because a p.meta precedes it, and "only rule a heading that follows
+# another heading" fixed pages while breaking posts -- a post's title is lifted
+# into the header, so its opening h2 lost its rule and the first section read
+# as preamble. Verified in Chrome on both layouts.
+assert_file_contains "$dg/.dist/chart.html" '.contents h1:not(:is(h1, h2) ~ *) {'
+assert_file_contains "$dg/.dist/post/charted.html" '.contents h1:not(:is(h1, h2) ~ *) {'
+
 it "an unrendered diagram degrades to readable source"
 # If the script is absent or blocked, the element still holds what the author
 # wrote. Shown as the code it is rather than as a paragraph with the newlines