From ee15ffbc0f7a0bfaf398d25b8fda86b4a036f576 Mon Sep 17 00:00:00 2001
From: Justin Bunton <130115204+Bunton33@users.noreply.github.com>
Date: Sun, 18 Oct 2026 23:45:22 -0400
Subject: [PATCH] fix: pin selectolax below 1.0.1 so images render again
selectolax 1.0.1 (a dependency of mkdocs-glightbox 0.5.2) makes the
glightbox plugin drop every image from the built pages. The deploy at
03:37Z on 2026-10-19 published the wiki with empty
where every
content image should be. Pinning selectolax<1.0.1 restores them
(3158 images in the local build, 410 with 1.0.1).
check_links.py now also fails when the built site has fewer than half as
many images as the Markdown, so a plugin regression like this one
fails the PR checks instead of reaching the live site.
Co-Authored-By: Claude Opus 5.5
---
requirements.txt | 2 ++
tools/docs-checks/README.md | 2 +-
tools/docs-checks/check_links.py | 19 +++++++++++++++----
3 files changed, 18 insertions(+), 5 deletions(-)
diff --git a/requirements.txt b/requirements.txt
index ad79c024bb..e56cd9e740 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,3 +6,5 @@ markdown-include
pymdown-extensions
pillow
cairosvg
+# selectolax 1.0.1 makes mkdocs-glightbox drop every image from the built site (Oct 2026).
+selectolax<1.0.1
diff --git a/tools/docs-checks/README.md b/tools/docs-checks/README.md
index 44f5a40a2d..73bf12c69d 100644
--- a/tools/docs-checks/README.md
+++ b/tools/docs-checks/README.md
@@ -9,7 +9,7 @@ changed lines in the PR. Fix them and push again; the checks re-run.
| `check_markdown.py` | Missing front matter `title:` or `description:`, heading levels that skip (`##` to `####`), more than one `#` H1, images without alt text, snippet includes that point at a missing file or start inside the included page's front matter | Add the field, use the next heading level, describe the image inside `![...]`, or start the include after the included page's closing `---` |
| `check_secrets.py` | ESPHome API encryption keys, generated OTA passwords, passwords typed into YAML, and access tokens | Use `!secret` (or a placeholder like `your-wifi-password`). If the value was real, change it on the device or account too |
| `check_images.py` | Photos with GPS location data (phones add it by default) | `exiftool -gps:all= -overwrite_original docs/assets/` |
-| `check_links.py` | Broken links, links to a missing `#anchor`, filename case mismatches (GitHub Pages is case-sensitive), and images without alt text, checked on the built site | Fix the link target or anchor; match the file name's case exactly |
+| `check_links.py` | Broken links, links to a missing `#anchor`, filename case mismatches (GitHub Pages is case-sensitive), images without alt text, and a built site with far fewer images than the Markdown, checked on the built site | Fix the link target or anchor; match the file name's case exactly. If images went missing, look for a recently updated build package and pin it in `requirements.txt` |
Warnings (shown, but they don't fail the PR): em dashes and the filler words
the style guide bans, and images or recordings over 1.5 MB that the PR adds
diff --git a/tools/docs-checks/check_links.py b/tools/docs-checks/check_links.py
index 20198d3412..8d74f254d2 100644
--- a/tools/docs-checks/check_links.py
+++ b/tools/docs-checks/check_links.py
@@ -6,9 +6,11 @@
links (../page/, https://wiki.apolloautomation.com/...), so this checks every
link, #anchor, image, and video in page content against the built site.
Filename case must match exactly, because GitHub Pages is case-sensitive.
-Fails on any broken link, missing anchor, or image without alt text.
+Fails on any broken link, missing anchor, or image without alt text, and
+when the built site has far fewer images than the Markdown (a plugin
+dropping them, as selectolax 1.0.1 did to mkdocs-glightbox in October 2026).
"""
-import os, sys, posixpath
+import os, re, sys, posixpath
from html.parser import HTMLParser
from urllib.parse import urlsplit, unquote
@@ -136,8 +138,17 @@ def src_md(url):
print(f"::error file={a['page']}::Link to a missing #anchor: {a['ref']}")
for m in missing_alt:
print(f"::error file={m['page']}::Image without alt text: {m['src']}")
+md_imgs = 0
+for r, _, fs in os.walk(DOCS):
+ for f in fs:
+ if f.endswith(".md"):
+ md_imgs += len(re.findall(r"!\[[^\]]*\]\(|
0 and built_imgs < md_imgs // 2
+if images_dropped:
+ print(f"::error::The built site has {built_imgs} images, but the Markdown has about {md_imgs}. A build plugin is probably removing images; check recently updated packages.")
for wrong, right in sorted(set(case_mismatch)):
print(f"::error::Link case doesn't match the file (GitHub Pages is case-sensitive): {wrong} should be {right}")
-total = len(broken) + len(bad_anchor) + len(missing_alt) + len(set(case_mismatch))
-print(f"check_links: {len(pages)} pages, {len(broken)} broken, {len(bad_anchor)} bad anchors, {len(missing_alt)} missing alt, {len(set(case_mismatch))} case mismatches")
+total = len(broken) + len(bad_anchor) + len(missing_alt) + len(set(case_mismatch)) + images_dropped
+print(f"check_links: {len(pages)} pages, {built_imgs} images, {len(broken)} broken, {len(bad_anchor)} bad anchors, {len(missing_alt)} missing alt, {len(set(case_mismatch))} case mismatches")
sys.exit(1 if total else 0)