ssh git@grit.deadkey.org man

Commit b84fa9d h

Pat Ambrosio · 26-08-26 15:31

Drop the post breadcrumb

Changes: 2 files +20 -63

Changes (Side-by-Side)

share/jenny/layout/post.sh +7 -30

render_footer_nav() {
90 printf '</footer>\n' 90 printf '</footer>\n'
91 } 91 }
92 92
93 # post_crumb
94 # The post's own segment of the breadcrumb, matching what a page renders: an
95 # arrow, then the slug with dashes read as spaces.
96 #
97 # The slug rather than the title because the breadcrumb is a picture of the
98 # URL -- it says where you are, and the address is what the reader can act on.
99 # A long title would also wrap the header, which the trail is meant to survive.
100 #
101 # Marked current the way a page's last segment is, so the same rule underlines
102 # it and the two headers behave identically.
103 post_crumb() {
104 [ -z "${IS_PAGE:-}" ] || return 0
105 local url=${POST_URL:-} slug
106 [ -n "$url" ] || return 0
107 slug=${url##*/}
108 # Under URL_STYLE=html the URL carries the extension, which is part of the
109 # address but not of the post's name -- without this the crumb read
110 # "hello world.html".
111 slug=${slug%.html}
112 [ -n "$slug" ] || return 0
113 # Built from URL_PREFIX rather than POST_URL, which is site-absolute: the
114 # site title beside it is relative, and a header that mixes the two breaks
115 # the moment the site is served from a subdirectory.
116 # The href keeps the URL's own ending; only the label drops it.
117 printf ' &#x2192;&#xFE0E; <a href="%s/post/%s" class="current">%s</a>' \
118 "$URL_PREFIX" "$(html_escape "${url##*/}")" \
119 "$(html_escape "$(printf '%s' "$slug" | tr '-' ' ')")"
120 }
121
122 html_escape() { 93 html_escape() {
123 local s=$1 94 local s=$1
124 # The replacement text must escape &, because bash >= 5.2 expands a bare & 95 # The replacement text must escape &, because bash >= 5.2 expands a bare &
cat << _EOF_
607 </head> 578 </head>
608 <body> 579 <body>
609 <a href="#" class="theme-toggle" aria-label="Toggle dark mode">&#x263C;&#xFE0E;</a> 580 <a href="#" class="theme-toggle" aria-label="Toggle dark mode">&#x263C;&#xFE0E;</a>
610 <header><a href="$URL_PREFIX/"$([ -n "${IS_PAGE:-}" ] && echo ' class="current"')>$(html_escape "${BLOG_TITLE:-}")</a>$(post_crumb)</header>
581 <!-- No breadcrumb on a post. Its own segment would be the slug, which
582 usually restates the title sitting directly beneath it -- and making
583 that read well would mean choosing titles that differ from their
584 slugs, which is the layout dictating what a post may be called. A
585 page's trail earns its place because the path has segments the title
586 does not repeat. -->
587 <header><a href="$URL_PREFIX/">$(html_escape "${BLOG_TITLE:-}")</a></header>
611 <article> 588 <article>
612 <div class="heading$([ -n "${IS_PAGE:-}" ] && echo ' heading-page')"><a href="${POST_URL:-}">$([ -z "${IS_PAGE:-}" ] && printf '<span class="stamp" data-iso="%s"><span class="stamp-slide"><span class="stamp-abs">%s</span><span class="stamp-rel" aria-hidden="true"></span></span></span>' "$(html_escape "${POST_DATE_RFC822:-}")" "$("$DATE" -d "${POST_DATE_RFC822:-}" +%m/%d/%Y 2>/dev/null || printf '%s' "${POST_DATE:-}")")<h1 class="title">$(html_escape "${POST_TITLE:-}")</h1></a></div> 589 <div class="heading$([ -n "${IS_PAGE:-}" ] && echo ' heading-page')"><a href="${POST_URL:-}">$([ -z "${IS_PAGE:-}" ] && printf '<span class="stamp" data-iso="%s"><span class="stamp-slide"><span class="stamp-abs">%s</span><span class="stamp-rel" aria-hidden="true"></span></span></span>' "$(html_escape "${POST_DATE_RFC822:-}")" "$("$DATE" -d "${POST_DATE_RFC822:-}" +%m/%d/%Y 2>/dev/null || printf '%s' "${POST_DATE:-}")")<h1 class="title">$(html_escape "${POST_TITLE:-}")</h1></a></div>
613 <div class="body-row"> 590 <div class="body-row">

test/regressions.sh +13 -33

assert_file_contains "$tl_post" '.citations ol { padding-left: 1.4em; margin-top
913 it "the build is clean" 913 it "the build is clean"
914 assert_eq "$stderr_output" "" 914 assert_eq "$stderr_output" ""
915 915
916 suite "a post names itself in the breadcrumb"
916 suite "a post has no breadcrumb"
917 917
918 # A page's header shows the trail to where you are; a post's showed only the
919 # site title, so the two headers disagreed about what a header is for.
918 # A post's own segment would be its slug, which usually restates the title
919 # directly beneath it. Making that read well would mean choosing titles that
920 # differ from their slugs -- the layout dictating what a post may be called.
921 # A page's trail earns its place because the path has segments the title does
922 # not repeat.
920 923
921 bc="$TMPROOT/breadcrumb" 924 bc="$TMPROOT/breadcrumb"
922 make_blog_min "$bc" 1 10 925 make_blog_min "$bc" 1 10
926 mkdir -p "$bc/pages"
927 printf '# Plain\n\nBody.\n' > "$bc/pages/plain.md"
923 run_jenny_split "$bc" 928 run_jenny_split "$bc"
924 bc_post=$(find "$bc/.dist/post" -name '*.html' | head -1) 929 bc_post=$(find "$bc/.dist/post" -name '*.html' | head -1)
925 930
926 it "the post's own segment follows the site title"
927 assert_file_contains "$bc_post" '&#x2192;&#xFE0E; <a href='
928
929 it "the segment reads the slug with dashes as spaces"
930 # The slug rather than the title: the breadcrumb is a picture of the URL, it
931 # says where you are, and a long title would wrap the header.
932 assert_file_contains "$bc_post" 'class="current">post 01</a>'
933
934 it "the segment is marked current, like a page's last one"
935 # So the same rule underlines it and the two headers behave identically.
936 assert_file_contains "$bc_post" 'class="current">'
937
938 it "the crumb link is relative, like the site title beside it"
939 # POST_URL is site-absolute; a header mixing the two breaks the moment the
940 # site is served from a subdirectory. Scoped to the header: the heading's own
941 # link is absolute and was so before this, which is a separate question.
931 it "a post's header is the site title alone"
942 bc_header=$("$GREP" -o '<header>.*</header>' "$bc_post" | head -1) 932 bc_header=$("$GREP" -o '<header>.*</header>' "$bc_post" | head -1)
943 assert_contains "$bc_header" 'href="../post/'
944 assert_not_contains "$bc_header" 'href="/post/'
933 assert_not_contains "$bc_header" 'class="current"'
934 assert_not_contains "$bc_header" '&#x2192;&#xFE0E;'
945 935
946 it "the label drops the extension the href keeps"
947 # Under URL_STYLE=html the URL carries .html, which is part of the address but
948 # not of the post's name -- the crumb read "hello world.html".
949 printf 'URL_STYLE=html\n' >> "$bc/.blogrc"
950 run_jenny_split "$bc"
951 assert_file_contains "$bc_post" 'class="current">post 01</a>'
952 assert_file_contains "$bc_post" 'href="../post/post-01.html"'
953
954 it "a page's breadcrumb is unchanged"
955 mkdir -p "$bc/pages"
956 printf '# Plain\n\nBody.\n' > "$bc/pages/plain.md"
957 run_jenny_split "$bc"
936 it "a page still has its trail"
937 # The two headers differ on purpose, not by omission.
958 assert_file_contains "$bc/.dist/plain.html" 'class="current">plain</a>' 938 assert_file_contains "$bc/.dist/plain.html" 'class="current">plain</a>'
959 939
960 it "the build is clean" 940 it "the build is clean"

Raw

commit b84fa9d9f3753e9038ff864afc8a7fd973495478
Author: Pat Ambrosio <cp.ambrosio@gmail.com>
Date:   Wed Aug 26 15:31:31 2026 +0800

    Drop the post breadcrumb
    
    Added one commit ago and wrong. A post's own segment is its slug, which
    usually restates the title sitting directly beneath it -- the header read
    "DEAD KEY -> ON PLAIN TEXT" above a title saying "On Plain Text".
    
    Worse than the redundancy: making it read well would mean choosing titles
    that differ from their slugs, so the layout would be dictating what a post
    may be called. A theme should not constrain the writing that way.
    
    A page keeps its trail, which earns its place: the path has segments --
    study, notes/method -- that the title does not repeat, and a page has no
    date line to identify it either.
    
    The tests become the rule they replaced: a post's header is the site title
    alone, and a page's still has its trail, so the two differ on purpose
    rather than by omission. Checked by mutation.
    
    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/post.sh b/share/jenny/layout/post.sh
index f22979c..396c355 100755
--- a/share/jenny/layout/post.sh
+++ b/share/jenny/layout/post.sh
@@ -90,35 +90,6 @@ render_footer_nav() {
   printf '</footer>\n'
 }
 
-# post_crumb
-# The post's own segment of the breadcrumb, matching what a page renders: an
-# arrow, then the slug with dashes read as spaces.
-#
-# The slug rather than the title because the breadcrumb is a picture of the
-# URL -- it says where you are, and the address is what the reader can act on.
-# A long title would also wrap the header, which the trail is meant to survive.
-#
-# Marked current the way a page's last segment is, so the same rule underlines
-# it and the two headers behave identically.
-post_crumb() {
-  [ -z "${IS_PAGE:-}" ] || return 0
-  local url=${POST_URL:-} slug
-  [ -n "$url" ] || return 0
-  slug=${url##*/}
-  # Under URL_STYLE=html the URL carries the extension, which is part of the
-  # address but not of the post's name -- without this the crumb read
-  # "hello world.html".
-  slug=${slug%.html}
-  [ -n "$slug" ] || return 0
-  # Built from URL_PREFIX rather than POST_URL, which is site-absolute: the
-  # site title beside it is relative, and a header that mixes the two breaks
-  # the moment the site is served from a subdirectory.
-  # The href keeps the URL's own ending; only the label drops it.
-  printf ' &#x2192;&#xFE0E; <a href="%s/post/%s" class="current">%s</a>' \
-    "$URL_PREFIX" "$(html_escape "${url##*/}")" \
-    "$(html_escape "$(printf '%s' "$slug" | tr '-' ' ')")"
-}
-
 html_escape() {
   local s=$1
   # The replacement text must escape &, because bash >= 5.2 expands a bare &
@@ -607,7 +578,13 @@ cat << _EOF_
     </head>
     <body>
       <a href="#" class="theme-toggle" aria-label="Toggle dark mode">&#x263C;&#xFE0E;</a>
-      <header><a href="$URL_PREFIX/"$([ -n "${IS_PAGE:-}" ] && echo ' class="current"')>$(html_escape "${BLOG_TITLE:-}")</a>$(post_crumb)</header>
+      <!-- No breadcrumb on a post. Its own segment would be the slug, which
+           usually restates the title sitting directly beneath it -- and making
+           that read well would mean choosing titles that differ from their
+           slugs, which is the layout dictating what a post may be called. A
+           page's trail earns its place because the path has segments the title
+           does not repeat. -->
+      <header><a href="$URL_PREFIX/">$(html_escape "${BLOG_TITLE:-}")</a></header>
       <article>
       <div class="heading$([ -n "${IS_PAGE:-}" ] && echo ' heading-page')"><a href="${POST_URL:-}">$([ -z "${IS_PAGE:-}" ] && printf '<span class="stamp" data-iso="%s"><span class="stamp-slide"><span class="stamp-abs">%s</span><span class="stamp-rel" aria-hidden="true"></span></span></span>' "$(html_escape "${POST_DATE_RFC822:-}")" "$("$DATE" -d "${POST_DATE_RFC822:-}" +%m/%d/%Y 2>/dev/null || printf '%s' "${POST_DATE:-}")")<h1 class="title">$(html_escape "${POST_TITLE:-}")</h1></a></div>
       <div class="body-row">
diff --git a/test/regressions.sh b/test/regressions.sh
index 31d059e..98cc3ab 100755
--- a/test/regressions.sh
+++ b/test/regressions.sh
@@ -913,48 +913,28 @@ assert_file_contains "$tl_post" '.citations ol { padding-left: 1.4em; margin-top
 it "the build is clean"
 assert_eq "$stderr_output" ""
 
-suite "a post names itself in the breadcrumb"
+suite "a post has no breadcrumb"
 
-# A page's header shows the trail to where you are; a post's showed only the
-# site title, so the two headers disagreed about what a header is for.
+# A post's own segment would be its slug, which usually restates the title
+# directly beneath it. Making that read well would mean choosing titles that
+# differ from their slugs -- the layout dictating what a post may be called.
+# A page's trail earns its place because the path has segments the title does
+# not repeat.
 
 bc="$TMPROOT/breadcrumb"
 make_blog_min "$bc" 1 10
+mkdir -p "$bc/pages"
+printf '# Plain\n\nBody.\n' > "$bc/pages/plain.md"
 run_jenny_split "$bc"
 bc_post=$(find "$bc/.dist/post" -name '*.html' | head -1)
 
-it "the post's own segment follows the site title"
-assert_file_contains "$bc_post" '&#x2192;&#xFE0E; <a href='
-
-it "the segment reads the slug with dashes as spaces"
-# The slug rather than the title: the breadcrumb is a picture of the URL, it
-# says where you are, and a long title would wrap the header.
-assert_file_contains "$bc_post" 'class="current">post 01</a>'
-
-it "the segment is marked current, like a page's last one"
-# So the same rule underlines it and the two headers behave identically.
-assert_file_contains "$bc_post" 'class="current">'
-
-it "the crumb link is relative, like the site title beside it"
-# POST_URL is site-absolute; a header mixing the two breaks the moment the
-# site is served from a subdirectory. Scoped to the header: the heading's own
-# link is absolute and was so before this, which is a separate question.
+it "a post's header is the site title alone"
 bc_header=$("$GREP" -o '<header>.*</header>' "$bc_post" | head -1)
-assert_contains "$bc_header" 'href="../post/'
-assert_not_contains "$bc_header" 'href="/post/'
+assert_not_contains "$bc_header" 'class="current"'
+assert_not_contains "$bc_header" '&#x2192;&#xFE0E;'
 
-it "the label drops the extension the href keeps"
-# Under URL_STYLE=html the URL carries .html, which is part of the address but
-# not of the post's name -- the crumb read "hello world.html".
-printf 'URL_STYLE=html\n' >> "$bc/.blogrc"
-run_jenny_split "$bc"
-assert_file_contains "$bc_post" 'class="current">post 01</a>'
-assert_file_contains "$bc_post" 'href="../post/post-01.html"'
-
-it "a page's breadcrumb is unchanged"
-mkdir -p "$bc/pages"
-printf '# Plain\n\nBody.\n' > "$bc/pages/plain.md"
-run_jenny_split "$bc"
+it "a page still has its trail"
+# The two headers differ on purpose, not by omission.
 assert_file_contains "$bc/.dist/plain.html" 'class="current">plain</a>'
 
 it "the build is clean"