Commit 490db3c h
Changes: 3 files +106 -27
Changes (Side-by-Side)
share/jenny/layout/page.sh +16 -7
render_footer_nav() { | |||
| 183 | back=$1 |
183 | back=$1 |
| 184 | lines=$(read_nav "${NAVS_DIR}" "site-footer" --optional) || lines="" |
184 | lines=$(read_nav "${NAVS_DIR}" "site-footer" --optional) || lines="" |
| 185 | |
185 | |
| 186 | # The row exists for the back link, which every page has; the nav is the |
||
| 187 | # optional part. Without navs/site-footer.tsv this still renders, just with |
||
| 188 | # nothing to the left of it. |
||
| 186 | # Both halves are optional and the row renders for whichever is present. |
||
| 187 | # A post passes a back link; a page passes none -- a page is not listed on |
||
| 188 | # the index, so an arrow back to it points at somewhere the reader did not |
||
| 189 | # come from, which is the same reason the heading has no arrow either. The |
||
| 190 | # breadcrumb in the header is what a page navigates by. |
||
| 191 | # Nothing to show: no links and no way back. Emitting the row anyway would |
||
| 192 | # leave an empty element and its top margin at the end of the document. |
||
| 193 | [ -n "$lines" ] || [ -n "$back" ] || return 0 |
||
| 194 | |
||
| 189 | printf '<footer class="site-footer">\n' |
195 | printf '<footer class="site-footer">\n' |
| 190 | # The way back comes first: it is the link every page has and the one a |
||
| 191 | # reader is most likely to want, so it leads the row rather than trailing it. |
||
| 192 | printf '%s\n' "$back" |
||
| 196 | # The way back comes first where there is one: it is the link a reader is |
||
| 197 | # most likely to want, so it leads the row rather than trailing it. |
||
| 198 | # An if, not "[ ] && printf": under set -e a failing test as the last |
||
| 199 | # statement of a function makes the function return non-zero, and this one is |
||
| 200 | # called from a command substitution. |
||
| 201 | if [ -n "$back" ]; then printf '%s\n' "$back"; fi |
||
| 193 | if [ -n "$lines" ]; then |
202 | if [ -n "$lines" ]; then |
| 194 | printf ' <span class="footer-links">\n' |
203 | printf ' <span class="footer-links">\n' |
| 195 | # A footer is a flat row of links, so sections and depth are read and |
204 | # A footer is a flat row of links, so sections and depth are read and |
cat << _EOF_ | |||
| 806 | <div class="contents"> |
815 | <div class="contents"> |
| 807 | $(echo "${POST_CONTENTS:-}") |
816 | $(echo "${POST_CONTENTS:-}") |
| 808 | <div class="tags">$(for i in $TAGS; do echo "<a href=\"../tag/$i/\">$(html_escape "$i")</a>"; done;)</div> |
817 | <div class="tags">$(for i in $TAGS; do echo "<a href=\"../tag/$i/\">$(html_escape "$i")</a>"; done;)</div> |
| 809 | $(render_footer_nav '<a href="'"$URL_PREFIX"'/" class="home">←︎ Index</a>') |
||
| 818 | $(render_footer_nav '') |
||
| 810 | </div> |
819 | </div> |
| 811 | </div> |
820 | </div> |
| 812 | </div> |
821 | </div> |
share/jenny/layout/post.sh +30 -1
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 ' →︎ <a href="%s/post/%s" class="current">%s</a>' \ |
||
| 118 | "$URL_PREFIX" "$(html_escape "${url##*/}")" \ |
||
| 119 | "$(html_escape "$(printf '%s' "$slug" | tr '-' ' ')")" |
||
| 120 | } |
||
| 121 | |
||
| 93 | html_escape() { |
122 | html_escape() { |
| 94 | local s=$1 |
123 | local s=$1 |
| 95 | # The replacement text must escape &, because bash >= 5.2 expands a bare & |
124 | # The replacement text must escape &, because bash >= 5.2 expands a bare & |
cat << _EOF_ | |||
| 578 | </head> |
607 | </head> |
| 579 | <body> |
608 | <body> |
| 580 | <a href="#" class="theme-toggle" aria-label="Toggle dark mode">☼︎</a> |
609 | <a href="#" class="theme-toggle" aria-label="Toggle dark mode">☼︎</a> |
| 581 | <header><a href="$URL_PREFIX/">$(html_escape "${BLOG_TITLE:-}")</a></header> |
||
| 610 | <header><a href="$URL_PREFIX/"$([ -n "${IS_PAGE:-}" ] && echo ' class="current"')>$(html_escape "${BLOG_TITLE:-}")</a>$(post_crumb)</header> |
||
| 582 | <article> |
611 | <article> |
| 583 | <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> |
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> |
| 584 | <div class="body-row"> |
613 | <div class="body-row"> |
test/regressions.sh +60 -19
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" |
||
| 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. |
||
| 920 | |
||
| 921 | bc="$TMPROOT/breadcrumb" |
||
| 922 | make_blog_min "$bc" 1 10 |
||
| 923 | run_jenny_split "$bc" |
||
| 924 | bc_post=$(find "$bc/.dist/post" -name '*.html' | head -1) |
||
| 925 | |
||
| 926 | it "the post's own segment follows the site title" |
||
| 927 | assert_file_contains "$bc_post" '→︎ <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. |
||
| 942 | 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/' |
||
| 945 | |
||
| 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" |
||
| 958 | assert_file_contains "$bc/.dist/plain.html" 'class="current">plain</a>' |
||
| 959 | |
||
| 960 | it "the build is clean" |
||
| 961 | assert_eq "$stderr_output" "" |
||
| 962 | |
||
| 916 | suite "the footer row" |
963 | suite "the footer row" |
| 917 | |
964 | |
| 918 | # The way back and the site links share one row at the end of the text. Before |
965 | # The way back and the site links share one row at the end of the text. Before |
assert_file_contains "$fr_post" '.site-footer { display: flex; align-items: base | |||
| 944 | assert_file_contains "$fr_post" 'gap: 1.5em; margin-top: 2.5em; }' |
991 | assert_file_contains "$fr_post" 'gap: 1.5em; margin-top: 2.5em; }' |
| 945 | assert_file_not_contains "$fr_post" '.home { margin-left: auto;' |
992 | assert_file_not_contains "$fr_post" '.home { margin-left: auto;' |
| 946 | |
993 | |
| 947 | it "pages get the same row" |
||
| 994 | it "pages get the site links but no way back" |
||
| 995 | # A page is not listed on the index, so an arrow back to it points somewhere |
||
| 996 | # the reader did not come from. |
||
| 948 | assert_file_contains "$fr/.dist/plain.html" 'class="footer-link"' |
997 | assert_file_contains "$fr/.dist/plain.html" 'class="footer-link"' |
| 998 | assert_file_not_contains "$fr/.dist/plain.html" 'class="home"' |
||
| 949 | |
999 | |
| 950 | it "a blog with no site nav still gets its way back" |
1000 | it "a blog with no site nav still gets its way back" |
| 951 | # The row exists for the back link; the nav is the optional part. |
1001 | # The row exists for the back link; the nav is the optional part. |
assert_file_exists "$nop/.dist/fallback.html" | |||
| 1523 | it "and does so without complaint" |
1573 | it "and does so without complaint" |
| 1524 | assert_eq "$stderr_output" "" |
1574 | assert_eq "$stderr_output" "" |
| 1525 | |
1575 | |
| 1526 | it "a page ends with the way back, like a post" |
||
| 1527 | # Held in a column before the body the arrow wrapped onto its own line once |
||
| 1528 | # the body no longer fit beside it, leaving a stray glyph between the title and |
||
| 1529 | # the first paragraph on a phone. Below the text it needs no column at any |
||
| 1530 | # width, and posts and pages end the same way. |
||
| 1531 | assert_file_contains "$pg/.dist/study/consciousness.html" 'class="home"' |
||
| 1532 | |
||
| 1533 | it "the way back comes after the body, not before it" |
||
| 1534 | tags_line=$("$GREP" -n 'class="tags"' "$pg/.dist/study/consciousness.html" | head -1 | cut -d: -f1) |
||
| 1535 | home_line=$("$GREP" -n 'class="home"' "$pg/.dist/study/consciousness.html" | head -1 | cut -d: -f1) |
||
| 1536 | [ -n "$home_line" ] && [ "$home_line" -gt "$tags_line" ] \ |
||
| 1537 | && pass "back link follows the content" \ |
||
| 1538 | || fail "back link at line $home_line must come after the body at line $tags_line" |
||
| 1576 | it "a page has no link back to the index" |
||
| 1577 | # A page is not listed on the index, so an arrow back to it points at a place |
||
| 1578 | # the reader did not come from -- the same reason the heading has no arrow. |
||
| 1579 | # The breadcrumb in the header is what a page navigates by. |
||
| 1580 | assert_file_not_contains "$pg/.dist/study/consciousness.html" 'class="home"' |
||
| 1539 | |
1581 | |
| 1540 | it "a blog with only pages and no posts still builds" |
1582 | it "a blog with only pages and no posts still builds" |
| 1541 | only="$TMPROOT/onlypages" |
1583 | only="$TMPROOT/onlypages" |
printf 'Link & Text <foo>\t/x\n' > "$tfn/navs/site-footer.tsv" | |||
| 2205 | out=$(render_footer_nav_in "$tfn/navs" "..") |
2247 | out=$(render_footer_nav_in "$tfn/navs" "..") |
| 2206 | assert_contains "$out" 'Link & Text <foo>' |
2248 | assert_contains "$out" 'Link & Text <foo>' |
| 2207 | |
2249 | |
| 2208 | it "a missing template nav file leaves the row with just the way back" |
||
| 2209 | # The row exists for the back link, which every page has; the site nav is the |
||
| 2210 | # optional part. Before the two shared a row this returned nothing at all. |
||
| 2250 | it "a row with neither links nor a way back renders nothing" |
||
| 2251 | # Both halves are optional. Emitting the row anyway would leave an empty |
||
| 2252 | # element and its top margin at the end of the document. |
||
| 2211 | rm -f "$tfn/navs/site-footer.tsv" |
2253 | rm -f "$tfn/navs/site-footer.tsv" |
| 2212 | out=$(render_footer_nav_in "$tfn/navs" ".." 2>/dev/null); st=$? |
2254 | out=$(render_footer_nav_in "$tfn/navs" ".." 2>/dev/null); st=$? |
| 2213 | assert_eq "$st" "0" |
2255 | assert_eq "$st" "0" |
| 2214 | assert_contains "$out" '<footer class="site-footer">' |
||
| 2215 | assert_not_contains "$out" 'footer-link' |
||
| 2256 | assert_eq "$out" "" |
||
| 2216 | |
2257 | |
| 2217 | suite "CSS variables completeness" |
2258 | suite "CSS variables completeness" |
| 2218 | |
2259 | |
Raw
commit 490db3c896a049e39c949620adeb428144cbc755
Author: Pat Ambrosio <cp.ambrosio@gmail.com>
Date: Wed Aug 26 15:24:23 2026 +0800
No index link on pages; a post names itself in the breadcrumb
A page is not listed on the index, so an arrow back to it points at a
place the reader did not come from -- the same reason the heading lost its
arrow earlier. Pages keep the site links in the footer row and drop the
way back; posts keep both. The row renders for whichever half is present
and, with neither, does not render at all rather than leaving an empty
element and its top margin at the end of the document.
The back link is now emitted with an "if" rather than "[ ] && printf": the
layouts run under set -e, and a failing test as the last statement of a
function makes the function return non-zero -- which this one does from
inside a command substitution.
A post's header showed only the site title while a page's showed the trail
to where you are, so the two disagreed about what a header is for. A post
now names itself: an arrow, then its slug with dashes read as spaces,
marked current the way a page's last segment is, so one rule underlines
both.
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.
Two things measurement caught. The crumb was site-absolute where the site
title beside it is relative, which breaks the moment the site is served
from a subdirectory; it is built from URL_PREFIX now. And under
URL_STYLE=html the label read "hello world.html" -- the extension is part
of the address but not of the post's name, so the href keeps it and the
label drops it.
Tests: the crumb's presence, wording, current marker, relative href, the
extension rule under both URL styles, that a page's own breadcrumb is
unchanged, and that pages get site links without a way back. Three
existing tests asserted the page back link that is now deliberately gone.
Checked by mutation -- dropping the crumb, leaving the extension in the
label, restoring the page's index link -- each 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 2bbc7fd..629ca88 100755
--- a/share/jenny/layout/page.sh
+++ b/share/jenny/layout/page.sh
@@ -183,13 +183,22 @@ render_footer_nav() {
back=$1
lines=$(read_nav "${NAVS_DIR}" "site-footer" --optional) || lines=""
- # The row exists for the back link, which every page has; the nav is the
- # optional part. Without navs/site-footer.tsv this still renders, just with
- # nothing to the left of it.
+ # Both halves are optional and the row renders for whichever is present.
+ # A post passes a back link; a page passes none -- a page is not listed on
+ # the index, so an arrow back to it points at somewhere the reader did not
+ # come from, which is the same reason the heading has no arrow either. The
+ # breadcrumb in the header is what a page navigates by.
+ # Nothing to show: no links and no way back. Emitting the row anyway would
+ # leave an empty element and its top margin at the end of the document.
+ [ -n "$lines" ] || [ -n "$back" ] || return 0
+
printf '<footer class="site-footer">\n'
- # The way back comes first: it is the link every page has and the one a
- # reader is most likely to want, so it leads the row rather than trailing it.
- printf '%s\n' "$back"
+ # The way back comes first where there is one: it is the link a reader is
+ # most likely to want, so it leads the row rather than trailing it.
+ # An if, not "[ ] && printf": under set -e a failing test as the last
+ # statement of a function makes the function return non-zero, and this one is
+ # called from a command substitution.
+ if [ -n "$back" ]; then printf '%s\n' "$back"; fi
if [ -n "$lines" ]; then
printf ' <span class="footer-links">\n'
# A footer is a flat row of links, so sections and depth are read and
@@ -806,7 +815,7 @@ cat << _EOF_
<div class="contents">
$(echo "${POST_CONTENTS:-}")
<div class="tags">$(for i in $TAGS; do echo "<a href=\"../tag/$i/\">$(html_escape "$i")</a>"; done;)</div>
- $(render_footer_nav '<a href="'"$URL_PREFIX"'/" class="home">←︎ Index</a>')
+ $(render_footer_nav '')
</div>
</div>
</div>
diff --git a/share/jenny/layout/post.sh b/share/jenny/layout/post.sh
index 121c4ab..f22979c 100755
--- a/share/jenny/layout/post.sh
+++ b/share/jenny/layout/post.sh
@@ -90,6 +90,35 @@ 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 ' →︎ <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 &
@@ -578,7 +607,7 @@ cat << _EOF_
</head>
<body>
<a href="#" class="theme-toggle" aria-label="Toggle dark mode">☼︎</a>
- <header><a href="$URL_PREFIX/">$(html_escape "${BLOG_TITLE:-}")</a></header>
+ <header><a href="$URL_PREFIX/"$([ -n "${IS_PAGE:-}" ] && echo ' class="current"')>$(html_escape "${BLOG_TITLE:-}")</a>$(post_crumb)</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 729aec1..31d059e 100755
--- a/test/regressions.sh
+++ b/test/regressions.sh
@@ -913,6 +913,53 @@ 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"
+
+# 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.
+
+bc="$TMPROOT/breadcrumb"
+make_blog_min "$bc" 1 10
+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" '→︎ <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.
+bc_header=$("$GREP" -o '<header>.*</header>' "$bc_post" | head -1)
+assert_contains "$bc_header" 'href="../post/'
+assert_not_contains "$bc_header" 'href="/post/'
+
+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"
+assert_file_contains "$bc/.dist/plain.html" 'class="current">plain</a>'
+
+it "the build is clean"
+assert_eq "$stderr_output" ""
+
suite "the footer row"
# The way back and the site links share one row at the end of the text. Before
@@ -944,8 +991,11 @@ assert_file_contains "$fr_post" '.site-footer { display: flex; align-items: base
assert_file_contains "$fr_post" 'gap: 1.5em; margin-top: 2.5em; }'
assert_file_not_contains "$fr_post" '.home { margin-left: auto;'
-it "pages get the same row"
+it "pages get the site links but no way back"
+# A page is not listed on the index, so an arrow back to it points somewhere
+# the reader did not come from.
assert_file_contains "$fr/.dist/plain.html" 'class="footer-link"'
+assert_file_not_contains "$fr/.dist/plain.html" 'class="home"'
it "a blog with no site nav still gets its way back"
# The row exists for the back link; the nav is the optional part.
@@ -1523,19 +1573,11 @@ assert_file_exists "$nop/.dist/fallback.html"
it "and does so without complaint"
assert_eq "$stderr_output" ""
-it "a page ends with the way back, like a post"
-# Held in a column before the body the arrow wrapped onto its own line once
-# the body no longer fit beside it, leaving a stray glyph between the title and
-# the first paragraph on a phone. Below the text it needs no column at any
-# width, and posts and pages end the same way.
-assert_file_contains "$pg/.dist/study/consciousness.html" 'class="home"'
-
-it "the way back comes after the body, not before it"
-tags_line=$("$GREP" -n 'class="tags"' "$pg/.dist/study/consciousness.html" | head -1 | cut -d: -f1)
-home_line=$("$GREP" -n 'class="home"' "$pg/.dist/study/consciousness.html" | head -1 | cut -d: -f1)
-[ -n "$home_line" ] && [ "$home_line" -gt "$tags_line" ] \
- && pass "back link follows the content" \
- || fail "back link at line $home_line must come after the body at line $tags_line"
+it "a page has no link back to the index"
+# A page is not listed on the index, so an arrow back to it points at a place
+# the reader did not come from -- the same reason the heading has no arrow.
+# The breadcrumb in the header is what a page navigates by.
+assert_file_not_contains "$pg/.dist/study/consciousness.html" 'class="home"'
it "a blog with only pages and no posts still builds"
only="$TMPROOT/onlypages"
@@ -2205,14 +2247,13 @@ printf 'Link & Text <foo>\t/x\n' > "$tfn/navs/site-footer.tsv"
out=$(render_footer_nav_in "$tfn/navs" "..")
assert_contains "$out" 'Link & Text <foo>'
-it "a missing template nav file leaves the row with just the way back"
-# The row exists for the back link, which every page has; the site nav is the
-# optional part. Before the two shared a row this returned nothing at all.
+it "a row with neither links nor a way back renders nothing"
+# Both halves are optional. Emitting the row anyway would leave an empty
+# element and its top margin at the end of the document.
rm -f "$tfn/navs/site-footer.tsv"
out=$(render_footer_nav_in "$tfn/navs" ".." 2>/dev/null); st=$?
assert_eq "$st" "0"
-assert_contains "$out" '<footer class="site-footer">'
-assert_not_contains "$out" 'footer-link'
+assert_eq "$out" ""
suite "CSS variables completeness"