From d1e45a06c841854e497bbec63a44b8fe41cf74c3 Mon Sep 17 00:00:00 2001
From: transitrix <279946036+transitrix@users.noreply.github.com>
Date: Sat, 8 Aug 2026 09:56:12 +0400
Subject: [PATCH] site: declare the full icon set on every page, add alpha to
the 128px favicon
Ten pages declared only the 128px PNG icon and fell back to a stale
favicon.ico on two more, so most of the site rendered an opaque tile in the
browser tab while the home page alone showed the transparent logo. Every
page listed in sitemap.xml now declares the same set - the SVG icon, all
three PNG sizes, and apple-touch - sourced from one canonical partial at
assets/inc/favicon-links.html so the declaration has a single copy to keep
in sync, even though the site has no build step to include it at serve time.
favicon-128.png is regenerated from the SVG source with a transparent
background (RGBA) instead of the opaque RGB it shipped as. apple-touch-icon.png
is left untouched - iOS composites transparency to black, so opaque is
correct there.
A new CI check reads the partial and sitemap.xml, asserts every page
declares the identical set, and asserts every icon PNG carries an alpha
channel. It self-tests against a fixture page with no icon declarations and
an unreadable path before trusting its verdict on the real pages.
---
.github/workflows/icon-declarations.yml | 142 ++++++++++++++++++++++++
assets/img/favicon-128.png | Bin 34333 -> 4270 bytes
assets/inc/favicon-links.html | 5 +
de/dsl-examples/index.html | 3 +
de/dsm/index.html | 3 +
de/install/index.html | 3 +
de/quickstart/index.html | 5 +
de/strategy-and-behaviour/index.html | 3 +
de/ttrs/index.html | 3 +
dsl-examples/index.html | 3 +
dsm/index.html | 3 +
install/index.html | 3 +
quickstart/index.html | 5 +
strategy-and-behaviour/index.html | 3 +
ttrs/index.html | 3 +
15 files changed, 187 insertions(+)
create mode 100644 .github/workflows/icon-declarations.yml
create mode 100644 assets/inc/favicon-links.html
diff --git a/.github/workflows/icon-declarations.yml b/.github/workflows/icon-declarations.yml
new file mode 100644
index 0000000..1ad18d7
--- /dev/null
+++ b/.github/workflows/icon-declarations.yml
@@ -0,0 +1,142 @@
+name: Icon declarations
+
+# Every page listed in sitemap.xml declares the same icon set - the SVG icon,
+# the three PNG sizes and apple-touch - and every PNG referenced from a
+# rel="icon" link carries an alpha channel. assets/inc/favicon-links.html is
+# the one canonical copy of that declaration; this check is what keeps every
+# page's
identical to it, since the site has no build step to include
+# a partial at serve time.
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+permissions:
+ contents: read
+
+jobs:
+ check-icon-declarations:
+ runs-on: ubuntu-latest
+ name: Every page declares the icon set; every icon PNG has alpha
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Verify sitemap pages and icon PNGs
+ shell: bash
+ run: |
+ set -euo pipefail
+
+ PARTIAL=assets/inc/favicon-links.html
+ [ -f "$PARTIAL" ] || { echo "::error::$PARTIAL is missing - it is the canonical icon declaration"; exit 1; }
+
+ # check_page FILE - fails if FILE is unreadable, is missing any line
+ # from the canonical partial, or does not declare exactly 4 rel="icon"
+ # links and 1 apple-touch-icon link. Never reports OK on a missing file.
+ check_page() {
+ local file="$1"
+ local errs=0
+ if [ ! -f "$file" ]; then
+ echo " MISSING FILE: $file"
+ return 1
+ fi
+ while IFS= read -r line; do
+ [ -z "${line// }" ] && continue
+ if ! grep -qF -- "$line" "$file"; then
+ echo " MISSING LINE in $file: $line"
+ errs=1
+ fi
+ done < "$PARTIAL"
+ local icount acount
+ icount=$(grep -cE ']*rel="icon"' "$file" || true)
+ acount=$(grep -cE ']*rel="apple-touch-icon"' "$file" || true)
+ if [ "$icount" -ne 4 ]; then
+ echo " $file: expected 4 rel=\"icon\" links, found $icount"
+ errs=1
+ fi
+ if [ "$acount" -ne 1 ]; then
+ echo " $file: expected 1 apple-touch-icon link, found $acount"
+ errs=1
+ fi
+ return $errs
+ }
+
+ # check_png_alpha PNG - fails unless the PNG's IHDR colour type
+ # (byte offset 25) is 4 (grayscale+alpha) or 6 (RGB+alpha).
+ check_png_alpha() {
+ local png="$1"
+ if [ ! -f "$png" ]; then
+ echo " MISSING PNG: $png"
+ return 1
+ fi
+ local ct
+ ct=$(od -An -tu1 -j 25 -N 1 "$png" | tr -d ' ')
+ if [ "$ct" != "4" ] && [ "$ct" != "6" ]; then
+ echo " $png: expected colour type 4 or 6 (has alpha), found $ct"
+ return 1
+ fi
+ return 0
+ }
+
+ echo "== Self-test: the checker itself must catch what it's meant to catch =="
+ selftest_fail=0
+
+ fixture="$(mktemp --suffix=.html)"
+ cat > "$fixture" <<'EOF'
+
+ Fixture without icon declarations
+ EOF
+ if check_page "$fixture" > /tmp/selftest-fixture.log 2>&1; then
+ echo "::error::self-test regression - a page with no icon declarations should fail check_page but passed"
+ selftest_fail=1
+ else
+ echo "ok: a layout without the partial correctly fails the check"
+ fi
+ rm -f "$fixture"
+
+ if check_page "/tmp/this-file-does-not-exist.html" > /tmp/selftest-missing.log 2>&1; then
+ echo "::error::self-test regression - an unreadable page should fail check_page but passed"
+ selftest_fail=1
+ else
+ echo "ok: an unreadable page correctly fails the check rather than reporting OK"
+ fi
+
+ if [ "$selftest_fail" -ne 0 ]; then
+ echo "::error::icon-declarations self-test failed - the checker itself is broken, not trusting its verdict on real pages"
+ exit 1
+ fi
+
+ echo "== Checking every page listed in sitemap.xml =="
+ [ -f sitemap.xml ] || { echo "::error::sitemap.xml is missing"; exit 1; }
+
+ fail=0
+ while IFS= read -r loc; do
+ [ -z "$loc" ] && continue
+ path="${loc#https://transitrix.com}"
+ path="${path%/}"
+ path="${path#/}"
+ if [ -z "$path" ]; then file="index.html"; else file="$path/index.html"; fi
+ echo "-- $loc -> $file"
+ if ! check_page "$file"; then
+ echo "::error file=$file::page does not declare the full icon set from $PARTIAL"
+ fail=1
+ fi
+ done < <(grep -oE '[^<]+' sitemap.xml | sed -E 's#?loc>##g')
+
+ echo "== Checking every icon PNG referenced from the partial for an alpha channel =="
+ while IFS= read -r png; do
+ [ -z "$png" ] && continue
+ rel="${png#/}"
+ echo "-- $rel"
+ if ! check_png_alpha "$rel"; then
+ echo "::error file=$rel::icon PNG has no alpha channel"
+ fail=1
+ fi
+ done < <(grep -oE 'rel="icon"[^>]*href="[^"]+\.png"' "$PARTIAL" | grep -oE 'href="[^"]+"' | sed -E 's/^href="//; s/"$//')
+
+ if [ "$fail" -ne 0 ]; then
+ exit 1
+ fi
+ echo "icon-declarations: every sitemap page declares the full icon set, every icon PNG has alpha."
diff --git a/assets/img/favicon-128.png b/assets/img/favicon-128.png
index 10f344e714ce827b63930e781becc50c2426d4b7..f6a7fa30a2e1c6413bfcb5542dd75b6f41af87d3 100644
GIT binary patch
literal 4270
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
literal 34333
zcmV)FK)=6