Skip to content

Migration to Zensical - #527

Merged
jurgelenas merged 24 commits into
masterfrom
zensical
Oct 12, 2026
Merged

jurgelenas merged 24 commits into
masterfrom
zensical

Conversation

@jurgelenas

Copy link
Copy Markdown
Member

ExpressLRS.org migration to https://github.com/zensical/zensical

# Conflicts:
#	.github/workflows/publish.yml
#	Dockerfile
#	Makefile
#	mkdocs.yml
Bring the post-merge state in line with what changed on master:

- edit_uri now points at edit/master, navigation.path replaces
  navigation.indexes
- Stub out the product finder page: it is rendered by an mkdocs hook
  and a custom override template that Zensical does not run yet, so
  the page links to Hardware Selection until it is ported
- Drop the duplicate docker-compose.yml (master renamed it to
  compose.yml; the branch had recreated the old name)
- Publish workflow installs Zensical via setup-uv and the pinned
  uv.lock instead of make/pip
The mkdocs implementation needed no build-time templating: the override
template was a static HTML skeleton and the product_catalog.py hook just
pre-generates assets/data/products.json, which product-finder.js loads
client-side. So instead of waiting for Zensical's module system:

- Inline the template's HTML skeleton into docs/product-finder.md as raw
  HTML (Zensical has no per-page template frontmatter yet); sidebars are
  hidden via the existing hide frontmatter
- Make product_catalog.py runnable as a standalone script while keeping
  the on_pre_build hook entry point for mkdocs on master
- Generate the catalog in CI before the build and add a make catalog
  wrapper for local refreshes
- Restore the Product Finder nav entry and stylesheet in zensical.toml
Zensical 0.0.50 renders our Material template overrides, so enable
custom_dir and drop the placeholder homepage:

- docs/index.md and docs/product-finder.md are restored to master's
  versions; home.html, main.html (announcement banner) and
  product-finder.html all render via per-page template frontmatter
- Guard page.parent lookups in the footer partial: Zensical's template
  engine errors on attribute access of undefined values where Jinja2
  tolerated it (the guard behaves identically under mkdocs)
- Set the expresslrs/expresslrs-dark palette names so colors.css header
  and accent colors apply again

The homepage Latest News section stays hidden until blog_posts.py has a
Zensical equivalent - the template skips it when recent_posts is absent.
Zensical defaults to its new "modern" design, which restyles the header
with a neutral blurred background instead of the primary palette color.
The classic variant matches Material for MkDocs exactly, so the header
picks up the ExpressLRS blue from colors.css again. Also restore the
material weather-night/weather-sunny palette toggle icons from master.
Google Analytics, the cookie consent banner, and the page feedback
widget turn out to work natively: Zensical passes project.extra through
to the classic Material templates, so master's config is ported 1:1.
The pymdownx extensions were also verified to render out of the box
(admonitions, tabs, keys, emoji, arithmatex, critic/mark, mermaid).

The remaining hook-dependent features follow the product_catalog.py
pattern - each mkdocs hook doubles as a standalone script, run by CI
and make site around the build:

- blog_posts.py pre-renders the top three posts into a gitignored
  partial that home.html includes when no hook context exists, putting
  the Latest News section back on the homepage
- llms_txt.py reads site metadata and nav from zensical.toml (same
  shape as the mkdocs config) and writes site/llms.txt post-build
- redirects.py, new, replays redirects.yml as meta-refresh stubs -
  the same output mkdocs-redirects produces (257 redirects)

Still upstream-blocked (planned in Zensical's backlog): blog index/
archive pages, tags, social cards, minified HTML, git dates/committers.
The TODO block in zensical.toml now reflects all of this.
The workflow still called make install-python-packages, which the
Zensical Makefile no longer has. Pin pyspelling in a uv dependency
group instead and invoke it directly, matching the publish workflow's
uv setup. The make spellcheck convenience target now uses the same
command.
astral-sh/setup-uv publishes no floating v8 major tag, so @v8 fails to
resolve on the runner.
@jurgelenas

jurgelenas commented Jul 10, 2026 •

Copy link
Copy Markdown
Member Author

Currently we are waiting for zensical/backlog#30 to land

Bump Zensical 0.0.50 -> 0.0.67, which ships replacements for the
Material plugins we were missing. The blog now gets its index,
archive, category and author views with the same dated post URLs as
master.

The redirect map moves from redirects.yml into zensical.toml (INHERIT
is YAML-only), replacing the standalone redirects.py. The native stubs
land at the same 257 paths and keep #anchor fragments.

llms_txt.py stays: the native llmstxt plugin drops front-matter
descriptions and nav order.
The stray slash in hmes900.md/# made the link point at a page that
doesn't exist.
Publish /feed.xml and /feed.json, covering blog posts only, with
pages advertising them via <link rel="alternate"> and a footer RSS
icon.

Dates come from post front matter because CI's shallow checkout would
give every post the latest commit's date. The feeds sit at the site
root because the generated rss.xsl only resolves from there.
Keep master's daily schedule, manual trigger and concurrency group in
publish.yml on top of the Pages deploy. Its env block goes: it only fed
the mkdocs git-committers plugin, and product_catalog.py needs no token.
Nothing on the page uses it and the anchor doesn't exist there.
Without a markdown_extensions table Zensical used its own defaults,
which dropped master's tab slugs, critic and magiclink shorthand. Tabs
went back to __tabbed_N ids, breaking links like
firmware-version.md#via-lua-script, and critic marks and #1234 /
@user shorthand rendered as raw text.

With those restored the build is warning-free, so strict mode now
keeps it that way.
@jurgelenas
jurgelenas marked this pull request as ready for review October 3, 2026 20:04
jurgelenas and others added 6 commits October 3, 2026 23:06
The header override copied from Material looked for the
material/search plugin, which Zensical registers as plain search, so
the button never rendered even though the index was built.
Zensical has no plugin API yet, so a Markdown extension fills in the page
meta fields the MkDocs git-revision-date-localized and git-committers
plugins used to set.
Restore git revision dates and committers on Zensical
Making the docs a hatchling package only served to put overrides/hooks
on sys.path. PYTHONPATH does that with no build backend, matching the
Dockerfile, so the Makefile and publish workflow now set it too.

The Makefile also switches from pip to uv, as CI already uses.
The zensical image ships without git, so git_info skipped every page
and the dev server showed no dates or committers. The token comes from
an optional .env and lifts committer lookups off the 60 per hour
unauthenticated limit.
Zensical copies heading markup into the table of contents, so the
custom-heading spans also appear in the sidebar. The script removed
the parent of every such span and blanked those entries. It now only
matches spans directly inside a heading.

@deadbytefpv deadbytefpv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've not seen anything amiss per my testing locally.

@jurgelenas
jurgelenas merged commit 6d9d53b into master Oct 12, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants