feat!: migrate from Franklin to Zola - #146
Merged
Merged
Conversation
Documents the planned Franklin/Xranklin → Zola migration in `PLAN.md`, structured as five reviewable commits: preprocessor tool (`tools/preprocess/`), Zola scaffold, content migration to `src/`, CI switchover, and Franklin removal. Records accepted tradeoffs (header anchor scheme change, dropping the deployed demo packages, unused tag machinery) and risks. Based on a proof of concept reproducing all four REPL modes (`julia>`, `help?>`, `pkg>`, `shell>`) as ANSI-colored HTML via `REPL.helpmode`, `Pkg.REPLMode.pkgstr`, and `ANSIColoredPrinters`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tools/ZolaPreprocessor/ executes the Xranklin-style code fences
(>, ?, ], ;, !) of authored pages and emits Zola-ready markdown with
ANSI-colored HTML pre blocks (via ANSIColoredPrinters), replaying REPL
semantics: one sandbox module per page, per-expression echo, ans,
trailing-; and nothing suppression, REPL-style error rendering that
never fails the build, environment-aware pkg prompts, and colorized
static julia-repl fences.
Per review, no Xranklin commands are reimplemented; conventions replace
them: a Project.toml next to a page auto-activates that environment
(instead of \activate{}), fences run in a per-page scratch cwd under
_workdir/ (instead of the sitepath shim), and the TOC will come from
Zola's native page.toc in the template (instead of \toc). Leftover
\toc/\activate{} lines are dropped with a warning. PLAN.md updated
accordingly.
The CLI follows the Julia app conventions ((@main) entry point, [apps]
in Project.toml): julia -m ZolaPreprocessor on 1.12+, main.jl on 1.11.
Tests: reference-output comparison of a fixture page exercising every
mode (regenerate with test/update_references.jl), plus test/linting.jl
running Aqua, ExplicitImports, and JET
(julia +1.11 --project=tools/ZolaPreprocessor -e 'using Pkg; Pkg.test()').
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Zola 0.23.3 site skeleton next to the still-working Franklin setup; no content changes. The generated content/ and public/ stay gitignored. - zola.toml: metadata from config.md, /feed.xml feed, github-dark highlighting (giallo), data-lang on <pre> for the language chips - templates/: base/page/index/404, RSS 2.0 feed template, sidebar with per-page section links generated from page.toc (replaces both the hardcoded sidebar anchors and the in-content \toc), and Tera 2 components (tldr/advanced/vscode) replacing the Franklin commands - static/: CSS copied as-is plus new ansi.css (ANSI palette for the preprocessor's REPL blocks + code chrome), KaTeX with explicit $ delimiters, favicon/logo, CNAME matching the live gh-pages branch - Makefile: preprocess/serve/build/check/test/clean Verified with zola build && zola check on placeholder content exercising components, fences, ANSI passthrough, math gating, 404 and the feed. PLAN.md updated for the 0.23 realities (components, zola.toml, src/_index.md, Tera-templated content risk). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Move the authored pages into src/ (homepage as _index.md, 404.md dropped in
favor of the template), convert the 60 admonition commands to Tera 2
component calls, delete the \toc/\activate{} directives, rewrite the
sitepath fences to plain relative paths in the scratch cwd, and fix the one
cross-page link. The preprocessor now wraps emitted <pre> blocks in
{% raw %} so fence output can never break Tera's content templating, and
the Makefile runs Julia with --startup-file=no so a Revise-loading
startup.jl cannot poison section-environment precompilation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Re-resolve tools/ZolaPreprocessor and the section environments under Julia 1.12 (JET 0.9.20 -> 0.12.1, Cthulhu 2 -> 3, JuliaFormatter 2.4 -> 2.12). Fallout handled: - JET 0.12 removed test_package's target_defined_modules config; use target_modules = (ZolaPreprocessor,) instead (test/linting.jl). - Julia 1.12 renders the help-mode signature indent outside the .sgr36 span; reference output regenerated. - --startup-file=no is no longer needed: JET 0.12 depends on Revise itself, so the default environment and the section envs resolve identical JuliaInterpreter/Revise versions. Verified with a full preprocess run with the Revise startup.jl enabled. The root Franklin env and Deploy.yml stay on 1.11 until the CI switchover (PLAN.md Commits 4-5); main still deploys Franklin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace tlienart/xranklin-build-action with an explicit pipeline: setup-julia 1.12 + cache, instantiate tools/ZolaPreprocessor, make preprocess, Zola 0.23.3 via taiki-e/install-action, zola build, zola check, and a peaceiris/actions-gh-pages deploy on push to main only. The 11 known-broken external links are whitelisted individually in zola.toml's [link_checker] (curl-verified: 1 intentionally fake example URL, 1 bot-blocking 403, 3 dead pages, plus skip_anchor_prefixes for GitHub's user-content- id prefix and genuine anchor drift), so zola check still fails on any new breakage, external or internal. Full pipeline verified locally: build, serve, check all pass; routes, ANSI spans, KaTeX gating, admonitions, CNAME and feed.xml spot-checked. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- cpfiffer renamed their GitHub account to just-cameron (profile + bootcamp repo) - learnxinyminutes-docs renamed julia.html.markdown to julia.md - pkgdocs renamed the anchor to #Test-specific-dependencies - julialang.org moved the release anchors to /downloads/manual-downloads/ - Drop the VS Code sysimage callout: the extension removed the feature entirely in August 2024 (docs page deleted, useCustomSysimage gone) The whitelist keeps only the intentionally fake myuser URL, intel.com (403 to non-browser clients), and the github.com anchor skip (user-content- id prefix makes README anchors uncheckable). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The dev workflow now lives in the app itself: julia --project=tools/ZolaPreprocessor -m ZolaPreprocessor <command> - preprocess <srcdir> <outdir>: the previous positional CLI, now a subcommand - serve: preprocess, run zola serve, then poll src/ mtimes and re-preprocess changed pages so Zola live-reloads; pages execute in-process, so warm re-runs skip package loading and take seconds - build / check: preprocess, then the zola command, propagating its exit code - clean: remove content/, public/ and the workdir - everything after -- is passed to Zola (e.g. serve -- --port 1112 --open) CI calls the preprocess subcommand directly and keeps its separate zola build/check steps so the pages execute only once. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Remove config.md, utils.jl, _layout/, _css/, _libs/, _rss/, _assets/ (favicon.png and logo.svg were copied to static/assets/ in the Zola scaffold; the rest was unreferenced), the root Xranklin environment, and the __site/__cache gitignore entries. Drop the stale hljs comment in static/css/franklin.css that pointed at the deleted _css/custom.css. The README/CONTRIBUTING updates for Commit 5 follow separately. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces peaceiris/actions-gh-pages with actions/upload-pages-artifact and a separate deploy job using actions/deploy-pages, dropping the contents: write permission. Requires flipping Settings > Pages > Source to "GitHub Actions" once; the gh-pages branch can then be deleted. Also trims PLAN.md down to the remaining work (the README/CONTRIBUTING updates), deleting the completed Commit 1-4 sections and resolved risks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The preprocessor drops Franklin's `\toc`, so the migrated pages lost their top-of-page table of contents. The authored markdown now marks the spot with `<!-- toc -->` (the same places `\toc` held) and the `with_toc` component splices in a flat list of the level-2 headings from `page.toc` — shortcodes cannot access the TOC (getzola/zola#584), so the swap happens template-side on the rendered content. The dead `.franklin-toc` CSS selector is renamed to `.toc`, matching the emitted markup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A sidebar button (or `/`, Ctrl/Cmd+K) opens a search dialog with two result tiers: section headings collected from each page's table of contents, so results link straight to the anchor (Zola's search index is page-level only and every page here is long), and full-text page matches from the elasticlunr index, fetched lazily on first use. Arrow keys move the selection, Enter follows it, and Escape clears the query before closing the dialog. Enables `build_search_index` in `zola.toml`; Zola emits the JSON index and `elasticlunr.min.js` at the site root. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Zola's elasticlunr index is page-level, and every page here is long; Pagefind's sub-results link each full-text match to the heading anchor it sits under and ship ready-made excerpts, replacing the vendored `makeTeaser`. - `js/search.js` lazily imports `/pagefind/pagefind.js` and dedups sub-results against the headings tier - templates scope the crawl via `data-pagefind-body` (with explicit title metadata, since titles live in front matter) and exclude the TOC via `data-pagefind-ignore` - CI runs `npx pagefind --site public` after `zola build`; the preprocessor's `build` command does the same when `pagefind` is on the PATH and only warns when it is not - `zola.toml` stops emitting the elasticlunr index Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI cannot reliably reach every external site (bot blocking, rate limits, transient outages), so `external_level = "warn"` keeps flaky links from failing the build; broken internal links still error. This replaces the `skip_prefixes`/`skip_anchor_prefixes` whitelist, which is no longer needed once external links are non-fatal. Zola swallows the warnings at its default log level, so the workflow runs `zola check` with `RUST_LOG=info` and surfaces any broken external links in the job summary plus a workflow warning annotation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fence code can log an error without throwing one - Base does exactly that when a package extension fails to load - which strict mode previously let through silently. An ErrorLogRecorder logger now collects error-level messages during fence capture and records them as fence errors, so they fail the build unless the fence is marked allow-error. Surfaces the StructUtilsTablesExt extension-load error on sharing/index.md (pkgtemplates1), to be fixed separately. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each page now executes on its own persistent Malt.jl worker process whose load path is fixed at spawn: the page environment, the preprocessor's environment, the stdlib. Loaded packages, extension triggers, and global state can no longer leak between pages, and each page sees exactly the environment its Project.toml declares — the load-path leak that made the optimizing page's StructUtils extension trigger fail the sharing page's build is structurally impossible now. Workers spawn lazily, stay warm across re-preprocesses in serve, and are reaped explicitly after the build commands. FenceSyntaxError travels back as a tagged value because Malt transports exceptions as printed messages only. Full preprocess cost goes from ~39s to ~51s on a warm machine, since packages now load once per page instead of once per session. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Aqua's deps_compat check requires a compat entry for the Test stdlib, which PkgTemplates does not generate for MyAwesomePackage. A hidden fence now patches Test = "1" into the generated Project.toml — what a real package author should do anyway — so the aqua fence passes without its temporary allow-error flag. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Worker isolation made the pages independent, so process_tree now runs every page's worker at the same time and writes the outputs in sorted page order once all pages have finished. A full local build drops from ~51s to ~24s, bounded by the slowest page instead of the sum of all pages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pid matches the `[Worker <pid>]:` prefix Malt puts on forwarded worker output, so build-log lines can be correlated with their page. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
adrhill
marked this pull request as ready for review
August 15, 2026 10:57
Collaborator
Author
|
@gdalle approved at the JuliaCon 2026 hackathon. ;) |
adrhill
added a commit
that referenced
this pull request
Aug 15, 2026
Follow-up to the Zola migration (#146), which kept the old Franklin look to keep the diff small. This PR refreshes the design while trying to keep the "old 2015 blog post" look. ;) * **Dark mode**: The site now has light and dark themes, instead of the previous inconsistent theme. * **CSS refactor**: `franklin.css`, `poole_hyde.css`, `custom.css`, `ansi.css` and `search.css` (and the inline `<style>` block in `base.html`) are replaced by two files: * `css/site.css` — everything but code blocks * `css/code.css` — block chrome, language chips, ANSI colors * **Scrollspy**: the section currently being read is highlighted in the sidebar's section list. It marks the last heading above the reading line, reusing each heading's `scroll-margin-top` so the threshold matches both the desktop and mobile-topbar offsets. * **In-content TOC** is now hidden whenever the sidebar is on screen, since the sidebar already lists the same headings. It reappears in the mobile layout, where the sidebar is behind the drawer. * **Mobile layout**: The stacked mobile layout (sidebar dumped above the content) is replaced by a fixed top bar with a hamburger button, the site title, and a search button. * **Logo**: the logo got some minor tweaks and is now also used as the favicon.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Migrates MoJuWo from Franklin.jl to Zola, a single-binary static site engine. Pretty much all functionality of the current site is kept.
How the site is built now
MoJuWoPreprocessor) executes the code blocks in the authored pages undersrc/and writes the results to the gitignoredcontent/directory.content/into the static site using the Tera templates intemplates/.The preprocessor
MoJuWoPreprocessorreplays Xranklin's executable code fences (>julia,?help,]pkg,;shell,!plain) and replaces each with its echoed input and ANSI-colored HTML output, mimicking the REPL.Everything else passes through untouched.
Each page runs in a sandbox module on its own Malt.jl worker whose load path is fixed to the page's
Project.toml,and fences execute in a gitignored scratch directory so they never touch the repository.
Fence output is captured with IOCapture.jl and converted to HTML with ANSIColoredPrinters.jl,
emitting the same
.sgrNNspans as Xranklin so the existing CSS keeps working.These three packages plus the REPL, Pkg, and Logging stdlibs are its only runtime dependencies. See MoJuWoPreprocessor/README.md for details.
Highlights
julia --project=MoJuWoPreprocessor -m MoJuWoPreprocessor serve|preprocess|build|check|clean).servewatchessrc/, re-runs only the edited page, and live-reloads the browser.zola build, Pagefind indexing, link check). Pushes tomaindeploy through the GitHub Pages artifact flow instead of agh-pagesbranch. Broken internal links fail the build, broken external links only warn.