Changelog
[Unreleased]
[13.0.1] — 2026-09-17
Added
- Studio pip and install handling — installs and upgrades add
--break-system-packagesautomatically on PEP 668 externally-managed
interpreters (Debian/Ubuntu system Python),--userinstalls keep their
flag, and every flow asks which DocsForge install to use when several
interpreters have one (remembered choice pre-selected). The update flow
asks at most one confirmation per item (quick-pick choices execute
directly; release notes re-show the same dialog instead of recursing).
Fixed
- Parallel builds no longer merge navigation highlights — concurrent
page renders shared one nav tree, so templates snapshotted other pages'activeflags mid-render: sibling sections lit up together (old and new
pages merged in the sidebar) and duplicate sticky section headers
overlapped each other (46 of 50 pages on an 8-worker build).active
reads during a render now resolve per thread to exactly the rendering
page, its ancestors, and its locale counterparts — full parallelism
kept, serial output restored. Covered by a barrier-forced-overlap
regression test. - Locale output stays correct under parallelism — translated-nav
highlights are registered explicitly per render, and page templates read
the page-context locale first instead of the shared config value (which
could carry another page's locale). - Service worker stops stampeding first loads — the manifest sync pool
was unbounded (a bookkeeping bug removed the just-added task instead of
the finished one), so activation fired hundreds of concurrent no-cache
fetches; manifest refreshes fired on every navigation instead of real
reloads only. Both fixed; access-time writes no longer block responses,
and LRU tracking now records URL-string cache keys correctly. - Sidebar height clamp rescoped — the footer-overlap guard applied from
60em, clipping the full-height nav drawer on tablets; it now applies at
screen+ for both rails and tablet-landscape for the TOC rail only. - build_frontend.py refuses drifted dependencies — the script used to
build happily with stale node_modules (older icon artwork, older
mermaid/wasm) and even overwrote newer vendored sources. It now fails
fast with apnpm installhint, icon refresh is an explicit--refresh-icons(normal builds never touchsrc/), output syncs prune
deletions, and an empty twemoji fetch aborts instead of wiping the set. - Studio translation diagnostics replace the check command — missing
locale variants and orphan translations are now editor errors on the
file itself (refreshed with diagnostics, no command to run), soDocsForge: Check Translation Twinsis removed. Editable installs are
left out of update checks entirely (no engine update is ever offered for
them).DocsForge: Open Outputis removed (the Output view stays in the
sidebar). User-facing wording says translations, not twins.
[13.0.1b1] — 2026-09-17
Added
- Studio page workflows —
DocsForge: New Pagescaffolds a doc
(folder defaulting to the active file's directory, optional locale twin
stubs, textualnav:insert with section picker), andDocsForge: Check Translation Twinslists missing/orphan locale variants with one-click
stub creation or orphan deletion. - Studio completions for anchors, snippets, frontmatter —
#inside a
link completes target-document heading slugs (served by an
mtime-validated index at.docsforge/studio/headings.json),--8<-- "…"completes sibling and docs-tree files, and frontmatter
offers engine-backed keys plushide:/search:values (custom keys stay
legal and are never flagged). - Studio multi-root servers — each workspace folder gets its own
serve/build state and pidfile adoption; commands act on the active
file's project (with a picker when ambiguous), the status bar names the
folder, and Open Built Page uses the document's own server. - Studio offline update cache — successful version checks persist to
global state; offline manual checks reuse the last-seen versions labeled
cached, and the silent startup check refreshes only the sidebar badge
from cache instead of notifying.
Fixed
- Studio review fixes — New Page never overwrites an existing
translation with a stub (skipped twins are reported); rapid double
serve/build can no longer orphan a server process; build failures reject
with real errors instead of unhandled rejections;./and../link
prefixes complete; Rename Anchor rewrites same-page[text](#anchor)
links; slugs keep CJK (# 你好→你好); Format Document leaves fenced
code blocks alone;docsforge.formatOnSaveis honored via will-save;
Rename Document rejects escaping paths; ambiguous quick fixes ask instead
of guessing; mixed fresh/cached update rounds are labeled per channel;
dashed prereleases with local metadata parse; go-to-definition and hover
use the link under the cursor; Stop Server no longer hijacks to cancel
builds; pidfile PIDs must be numeric and diagnostics paths stay inside
the docs dir. Locales also match case-insensitively (pt-BR). - Second Studio review pass —
:icon:completions resolve through the
workspace interpreter's installed package (no longer always empty in user
projects), plus frontmattericon:values; cached offline versions are
display-only instead of offering doomed updates; snippet completion scans
the whole cached file list; explicit errors when no folder is open;
update checks share serve's project resolution; anchor completion is
case-insensitive with deduped titles; formatOnSave reads document-scoped
config; frontmatter skips the closing---and snippet completion is
suppressed in fenced code; scaffold folder validation is segment-wise.
Fixed
- Studio engine updates for beta/editable installs — the extension
truncated13.0.0b3to13.0.0, so a beta checkout looked current and
only the extension update was ever offered. Pre-release segments are now
preserved for comparison, editable installs (pip install -e .) are
detected viadirect_url.jsonand never silently replaced by pip (the
prompt names the checkout and offers an explicit replace), user-site
installs keep the--userflag on upgrade, and failed upgrades print the
manual retry command.
[13.0.0] — 2026-09-15
Highlights
DocsForge 13 is a correctness and performance major. Everything below
shipped across the 13.0.0b1–b3 betas (see those entries for the full
per-fix detail); since b3 only the updater messaging fix and a Studio
docs rewrite landed.
- Parallel incremental builds that converge — page rendering actually
usesconcurrencynow, snippet-include pages no longer rebuild forever,
static copies are incremental, and manifests are compare-before-write. - Chinese navigation correctness — own homepage, prev/next chains,
preservedi18n_titles, per-page descriptions no longer clobbered. - Offline-proof assets — one Google Fonts fetch per build with local
font fallback, atomic file writes, missing outputs restored, social
cards no longer deleted as "unused". - Calmer serve and CLI — interruptible rebuilds, 404s logged as info,
friendlier help/check/setup output. - Self-updating Studio extension — engine + VSIX updates from the
palette, sidebar, or a silent startup check, with pre-release opt-in.
Behavior changes
- Non-default-locale pages keep their own frontmatter/H1 titles and
descriptions; nav-label translations no longer rewrite them. - Dev-server 404 request logs moved from error to info level.
- Pre-release versions are excluded from update checks unless
docsforge.includePrereleasesis enabled.
[13.0.0b3] — 2026-09-15
Added
- Studio update check in the sidebar — the Actions view has a
Check for Updates item (plus a title-bar button) that shows the available
versions inline once a check finds something newer.
[13.0.0b2] — 2026-09-15
Added
- Studio can update itself — new
DocsForge: Check for Updatescommand
compares the installed engine (PyPI) and extension (GitHub releases)
against the latest versions, upgrades the engine with pip in the same
interpreter setup, and downloads + installs a newer VSIX with a reload
prompt. A silent startup check (45 s delay, once per version, off viadocsforge.autoCheckUpdates) notifies only when something is newer, anddocsforge.includePrereleasesopts betas into the check.
[13.0.0b1] — 2026-09-15
Changed
- Mermaid vendored asset upgraded from 11.17 to 12.0 — ELK is now the
bundled layout engine upstream,</br>is treated as a line break in
labels, and DocsForge pins the mermaid-11 appearance (layout: dagre,theme: default,look: classic) so existing diagrams keep their look;
per-diagram front matter overrides still win. The IIFE grows ~600 kB
gzipped (ELK inlined — counted in the precache manifest), and the CDN
fallback URL bumps tomermaid@12. Verified: all nine core diagram types
(flowchart, sequence, class, state, ER, gantt, pie, mindmap, journey)
render in Chromium with the pinned config, andmermaid.initialize()
accepts DocsForge's config unchanged.
Fixed
- Missing outputs are restored instead of staying gone — cached social
cards absent from the site (deleted by older optimizer revisions or by
hand) are copied back inon_post_build; they used to stay missing
forever on incremental builds because up-to-date cards are skipped and
only rebuilt pages copy. A post-optimization pass now does the same for
every other expected static output with a live source, while pages
(rebuilt when their output is missing) and optimizer-managed directories
(deliberate pruning) are never touched. - Social cards no longer deleted as "unused assets" — card PNGs are
referenced only via absolute same-originog:image/twitter:imagemeta
tags, which the reference scanner neither collected (nometahandling)
nor resolved (absolute URLs were dropped as external). The cleanup then
deleted every generated card in the same build. Meta image/video/audio
references are now collected and same-origin absolute URLs resolve
site-relative (foreign origins stay excluded); the reference cache is
keyed by origin so build/serve scans can't leak into each other. Help output tweaks — no emoji, no examples block, and command blurbs
shortened to fit instead of truncating mid-word.Dev-server 404s are info, not errors — every missing file (typo'd
links, favicon probes, drafts) used to log a redERRORline; 404s now
log at info level (other 4xx warn, 5xx stay errors). The log prefix also
no longer paints the-separator red/yellow along with the level name.Friendlier CLI output —
docsforge --helpshows examples and a
colorful command list,docsforge checkreports with green/red/yellow
status lines and hints, and the setup wizard uses color for its banner,
steps, errors, and summary.Builds survive slow or blocked networks — the social plugin retried an
unreachable Google Fonts host once per page (94 serial connect-timeout
cascades, one error per page, minute-long stalls). Timeouts are unchanged,
but each family is now fetched at most once per build: on failure cards
render with a best-effort local font (other cached families first, then
system fonts matched by style) after a single warning, and the next build
retries the fetch. Only when no local font exists at all do pages build
without cards. Card/layer pools also shut down without waiting on stuck
network workers, so a single Ctrl-C stops the build promptly instead of
needing a second one.Parallel page builds actually run in parallel —
_build_pageheld one
shared lock across template rendering and file writes, soconcurrency
was a no-op. The lock now covers only shared state (page.active,config._current_page,get_contextgit-meta writes, plugin hooks);
rendering and atomic writes run outside it.activeclearing is
sibling-aware and_current_pageis re-asserted per hook, so concurrent
builds no longer flicker each other's sidebar highlight.- Snippet-include pages converge on incremental builds — freshness checks
used plugin-only deps while the cache recorded snippet deps, so any--8<--page rebuilt forever; empty dep lists were also dropped. Both
check paths now compute snippet deps up front and share them withupdate_cache, which persists empty lists and prunes removed deps. - Incremental I/O no longer churns — static files copy with
dirty=True
and size+mtime_ns comparison, theme/extra/manifest outputs are
compare-before-write, orphan scans exclude generated outputs (assets/,
sitemap, search index, PWA files) and consult knowndest_uris, and all
file/cache writes are atomic (tmp +os.replace). - Chinese nav correctness — locale navs use their own homepage (not the
English object), get their own prev/next chain, preservei18n_titleson
translated and fallback pages (including already-materialized pages),
no longer clobber per-page descriptions or rewrite doc titles from nav
labels, recompute locale titles idempotently, and highlight fallback
entries bysrc_uri. - Social, search, tags and minify robustness — card layer dispatch is
lock-guarded (no sentinel deadlock), font downloads time out and pools are
created only when enabled, missing</head>no longer corrupts output,
search exclusion tracks element identity (nested same-tagdivs stay
excluded), tag listing IDs are regex-escaped with correct fragment
handling,?v=hashes survive serve rebuilds, and meta files sort once
with/-normalized paths. Serve rebuilds are interruptible and race-free —
KeyboardInterrupt/SystemExitre-raise instead of being swallowed, rebuild flags mutate
under lock, bind retries useerrno.EADDRINUSE, customsite_dirs are
ignored by path (not a hardcodedsite), wildcard hosts probe loopback,
privacy downloads drain until quiescent with errors surfaced, oversized or
undecodable assets are skipped safely, the config cache keys on(mtime_ns, size), shutdown joins the observer, andcheck --fixwrites
atomically.Config loading no longer stalls ~13s on the emoji index — the twemoji
index builder rebuilt the set of all known names once per vendored icon
file (O(N²) over ~10k SVGs), blocking every freshbuild/serve/check
invocation with no output. Taken names are now tracked incrementally;
cold load dropped to well under a second with identical results.Reference docs no longer swallow headings and diagrams into code blocks
— themarkdownexample fences in the diagrams, content-tabs,
admonitions, lists, creating-your-site, publishing usage, and
troubleshooting pages used triple backticks while containing triple-backtick
inner fences, so the outer block closed early and the following headings,
tables, and live diagrams rendered as code. The outer fences are now
quadruple backticks (English and Chinese twins fixed together).Translated tabs keep their icons on incremental builds — the tab-bar
icons come from each section index page's front-mattericon, but only
default-locale sources were preloaded before template rendering, so a
rebuilt Chinese page outside setup/reference lost the setup/reference/home
icons. Locale nav sources are now preloaded too, and the navigation
signature covers locale titles and icons so zh-only changes rebuild
correctly.Interrupting
serveduring the initial build no longer hangs — the
privacy download pool had no shutdown hook, so Ctrl-C left network threads
alive and the interpreter blocked joining them until every download timed
out. The pool is now cancelled on shutdown (and reaped on the nexton_config), HTTP responses are always closed, page-build executors cancel
queued work on interrupt, the cache-invalidate message prints once instead
of twice, and an unversioned partial cache reports asunversionedrather
thanv0. Long first builds also logRendering N pages.../Writing N pages...so slow progress is distinguishable from a hang.
[12.5.7] — 2026-08-22
Added
- Ruff lint gate in CI and release —
ruff checknow runs as its own job
in both the CI and release workflows, and the release is blocked on it, so a
correctness regression cannot ship. The gate covers both correctness
(pyflakes, bugbear, pylint errors) and style (pycodestyle, isort,
pep8-naming, pyupgrade, flake8-simplify and friends): DocsForge no longer
tracks upstream ProperDocs / Material for MkDocs, so the tree is held to one
consistent house style — double quotes, a 120-column limit, sorted imports,
no relative imports beyond the parent package. Every ignored rule and
per-file exemption is justified in[tool.ruff.lint]inpyproject.toml. A
newpip install -e '.[dev]'extra installs the exact pinned ruff version CI
enforces.
Fixed
The template parity gate now catches added files — CI verified the
committeddocsforge/templatestree against a freshbuild_frontend.pyrun
withgit diff --exit-code, which reports modified and deleted files but is
blind to new ones. A dependency that adds assets (such as alucide-static
bump shipping nine new icons) left them untracked and still passed, so the
committed tree silently drifted from the build output. Untracked build output
is now a CI failure that names the exact files to add.A VS Code typings bump can no longer break the release — the CI
studio
job compiled, linted and tested the extension but never packaged it, whilevsce packageenforces constraints the compiler does not — notably that@types/vscodenever exceedsengines.vscode. Because packaging only ran in
the release workflow, after the tag and the PyPI publish, such a bump would
have produced a published release with no.vsixattached. CI now packages
the extension, and@types/vscodeis pinned to theengines.vscode
compatibility floor rather than tracked by Dependabot.Dev-server path containment is now a tested boundary — the live-reload
server's directory-traversal guard was correct but inline, undocumented and
entirely untested: the one place where a request path becomes anopen()
call had no regression net. It is now_resolve_within_root(), a single
documented method that returnsNonerather than a path when a request would
escape the site root, with an explicit rejection of any..surviving
normalization as defense in depth. Eight unit tests pin the boundary against
traversal, percent-encoded traversal, root-prefix sibling directories
(/sitevs/sitezz), escaping symlinks, and absolute paths that would
otherwise hijackos.path.join. No behavior change: every path that resolved
before still resolves, and every path that was blocked is still blocked.Social cards no longer leak between builds —
SocialPlugin.manifestwas
a class attribute that instance code mutated, so every plugin instance shared
one dict and card entries accumulated across rebuilds within a singledocsforge servesession. It is now per-instance.Translated pages keep their index-page identity — a translation sibling
such asindex.zh.mdwas treated as a regular page instead of an index
page, so on multi-language sites the top navigation tabs lost their icons
in non-default languages (the icon comes from the tab's index page), and
translated section indexes were not merged into their section withnavigation.indexes. Translation siblings now share the default file's
stem identity, soindex.zh.mdbehaves exactly likeindex.md.privacyno longer crashes when a symlink cannot be created — on
filesystems without symlink support (notably Windows without developer
mode) the fallback path that writes the downloaded asset directly readres.content, which raisesRuntimeError: The content for this response was already consumedbecause the download had already been streamed to enforce
the size limit. The already-streamed bytes are now written instead.- PWA manifest and
theme-colorcarry a real color — both emitted the
Material palette name (e.g.teal) where a CSS color is required, so the
browser ignored the value and no theme color was applied. Palette names are
now resolved to their actual hex color, with literal CSS colors passed
through unchanged. - Builds work on Python 3.10 — a parallel page-render failure raised
ExceptionGroup, a 3.11+ builtin, so on 3.10 (whichrequires-python
allows) the error handler itself raisedNameErrorand masked the real
build error. A compatible fallback is used below 3.11. - TikZ compilation cannot hang the build — every LaTeX and SVG-converter
invocation now runs under a timeout (120s, overridable withDOCSFORGE_TIKZ_TIMEOUT). A malformed diagram that put TeX into an
interactive prompt previously blocked the build forever, since the
compilation runs inside a thread pool the build waits on. - TikZ compile counters are thread-safe — the compiled/skipped counters
and the content-hash map were mutated from the compile thread pool without
synchronization, so the reported counts could be wrong and hash entries
could be lost, causing unchanged diagrams to recompile on the next build. - Stale build caches are discarded on format change —
CACHE_VERSIONwas
never compared against the stored version, so a cache written by an older
format revision was read back as if current. It is now invalidated when the
format changes. cache.invalidate()clears every cache file —validation.jsonwas
missing from the list, so link/anchor validation data survived an
invalidation. All cache files now come from a single source of truth.docsforge fixbacks up your config — the auto-fix rewrotedocsforge.ymlthroughyaml.dump, silently discarding comments, blank
lines and quoting style. It now writes a.bakcopy first and says so.- Social card manifest errors no longer swallow interrupts — a bare
exceptaround the manifest read caughtKeyboardInterrupt, so Ctrl-C
during that read was ignored.
[12.5.6] — 2026-08-20
Added
- Offline mode toggle — a new
offline.modeconfig option lets you switch
the service worker off entirely. The defaultcache-firstmode is unchanged
(service worker registers, precaches every page and asset, and serves from
cache first for offline support). Settingoffline.mode: noneskips the
service worker entirely: nosw.js, nocache-manifest.json, nomanifest.json, and pages carry no registration code or manifest link — the
site is served purely from the network.
Fixed
- Theme signature is location-independent — the theme template signature
used absolute file paths, so the same templates installed at a different
path (e.g. GitHub Actions Python toolcache updates, or a reinstall into a
new environment) produced a different signature and invalidated the whole
build cache on every CI run, defeating incremental builds. The signature
now hashes template contents with paths relative to each theme directory,
so identical templates always yield the same signature regardless of where
they are installed.
[12.5.5] — 2026-08-20
Added
- File sizes in
cache-manifest.json— the manifest now ships asizes
map with the exact byte count of every built file. The service worker uses
it (plus the response'sContent-Length, measured from the body when
missing) to account quota evictions at their real size instead of the old
guessed 5 MiB per entry.
Changed
- Service worker quota eviction is exact — when the browser cache is
full, the SW evicts least-recently-used entries until the actually-freed
bytes (measured, not estimated) cover the required space plus a
proportional 10%-of-quota margin, then retries. The old flat 20 MiB free
margin is gone. Evicted entries are removed from the persisted
previous-files list, so a file that was evicted is never falsely recorded
as cached — the next sync re-fetches it instead of skipping it. - Manifest sync is budget-aware — the background sync now checks the
free space reported bystorage.estimate()before downloading: every
changed file reserves a flat 20 MiB of budget (the usage estimate lags
behind in-flight writes), and the sync stops once the budget is exhausted
instead of downloading files that would only be evicted again. Files that
don't fit are cached on demand when actually visited. A single resource
larger than the whole quota is never cached.
Fixed
- Safari (and any small-quota browser) no longer thrashes — previously
the sync downloaded the whole manifest, hit the quota wall, evicted the
just-cached files, and recorded them as cached anyway; offline coverage
silently degraded to a few tail files. Now the sync stops early and the
tracked-file list stays truthful. - TikZ diagram cache is content-based — unchanged diagrams are now
skipped by hashing the effective source (tex plus preamble) instead of
comparing file mtimes. Git checkouts and CI cache restores bump every
mtime, which previously recompiled all diagrams on every build; edits totikz_preamblenow also correctly trigger recompilation (mtime never
noticed preamble changes). - Theme signature is content-based — a full rebuild is triggered by
actual template content changes instead of stat (mtime/size) differences.
Reinstalling the docsforge package or restoring a CI cache stamps fresh
mtimes on identical theme templates; previously that invalidated the
whole build cache on every CI run, defeating incremental builds.
[12.5.4] — 2026-08-18
Added
- Twemoji SVGs vendored — the full twemoji set (4,000+ emojis, pinned
tojdecked/twemojiv17.0.3, the maintained fork of the archivedtwitter/twemoji) ships inside the package. Unicode emojis
(:smile:,:us:) are now inlined locally at build time, so no CDN
(jsdelivr/maxcdn) is referenced anymore — falling back to the CDN only
for codepoints missing from the vendored set. - License files shipped with bundled assets — Mermaid (MIT) and KaTeX
(MIT) licenses travel alongside the bundled scripts, and the lunr
stemmer modules ship their MPL-1.1 license (the license must accompany
the code in all cases). - Docker image runs as non-root — the image now creates a
docsforge
user (uid 1000) and drops privileges (USER docsforge). - TikZ default math preamble — bare diagram sources (no
\documentclass)
are now auto-wrapped in a standalone document withamsmath,amssymb,tikz,pgfplots,tikz-cdandtkz-euclidepreloaded, so a diagram is
just thetikzpicturebody. Newtikz_preambleconfig option injects extra
preamble lines.texlive-fonts-recommended(amssymb/amsfonts) added to the
Docker image — pgfplots, tikz-cd and tkz-euclide already ship intexlive-pictures. - TikZ SVGs embed fonts —
dvisvgmno longer runs with--no-fonts, so
diagram text stays selectable and searchable; it falls back to outlined
paths only when the TeX fonts are unavailable. - TikZ compilation no longer times out — the fixed 60-second per-step
limit is removed; large or slow diagrams (pgfplots surfaces, heavyforeachloops) compile for as long as they need.
Changed
- Global
concurrencysetting — new top-levelconcurrencykey
(default: CPU count − 1) sizes every parallel pool: Markdown page
rendering, page building, TikZ compilation, social card generation and
privacy downloads. The plugin-levelconcurrencyoptions ofsocialandprivacyare removed — setconcurrency:at the top level instead
(breaking; plugin-level usage now warns). - PDF export tabs are memory-capped — the base tab count comes from
--jobs, else globalconcurrency(default CPU count − 1), and is capped
by remaining memory (≈200 MiB per Chromium tab) so parallel rendering
can't OOM the build. scripts/fetch_twemoji.pymerged intobuild_frontend.py— refresh
the vendored twemoji set withpython build_frontend.py --fetch-twemoji
(pinned tag), then a normal build syncs it intodocsforge/templates/.- Migration scripts now served from the docs site —
migrate.sh,migrate.ps1andmigrate.pylive in the docs content and are copied
into the built site (docsforge copies static files fromdocs_dir), so
the one-liners are now:
curl -fsSL https://qqshi13.github.io/docsforge/migrate.sh | bash
irm https://qqshi13.github.io/docsforge/migrate.ps1 | iex
The scripts download migrate.py from the same site
(https://qqshi13.github.io/docsforge/migrate.py) instead of the raw
GitHub URL, and the getting-started migration guide now documents the
automatic one-liner (was: "no automatic migration command").
[12.5.3] — 2026-08-17
Added
- One-line migration scripts:
curl | bash(Unix) andirm | iex
(PowerShell) convert an existingmkdocs.yml/properdocs.yml/zensical.tomltodocsforge.ymlautomatically — navigation, theme,
plugins, and extensions. Warns about anything it can't migrate
(third-party plugins,INHERIT, hooks) and prints a report with contact
info (email + issues). !!python/name:tag support in config loading — mkdocs configs
commonly useslugify: !!python/name:pymdownx.slugs.uslugify; such
configs (e.g. OI-wiki's) now parse and round-trip cleanly.- SECURITY.md — private vulnerability reporting (GitHub advisory or
email), supported-version policy. - Support page (en + zh) with the migration one-liner, support channels,
and a "what to include" checklist. - Sidebar navigation icons on every docs page (Material set, en + zh).
Changed
- minify backend is now the Go minifier (
minify-go, wheels built fromtdewolff/minifyby our own cron workflow, incl. macOS arm64): the
maintained forks (min-html, min-js, csscompress) are consolidated into one
dependency. HTML pages now also minify inline<style>/<script>blocks;
SVG default-valued attributes (e.g.preserveAspectRatio="xMidYMid meet")
are stripped, non-default values and case-sensitive names (viewBox) are
preserved. Note: musllinux/Alpine is unsupported by the Go binding
(c-shared.socannot be dlopen'd on musl). paginatevendored into the package (docsforge/paginate.py) —
another dormant dependency (2017) removed from the install.- README: migration one-liner added; broken
install.sh/install.ps1
references fixed (the Studio extension ships as a release.vsix).
Fixed
- Config parsing in
docsforge check/ CLI / PDF now routes through the
docsforge YAML loader, so!!python/name:tags no longer crash config
discovery. - Migration script:
theme.name: null(local-theme setups) defaults tomaterial; external nav links are dropped with a hint (docsforge nav is
internal-only).
[12.5.2] — 2026-08-12
Added
- Blog RSS/Atom feeds: the blog plugin now generates
feed_rss_created.xml,feed_rss_updated.xml, andfeed_atom.xmlinto
the built site on every build (drafts excluded) — no more hand-maintained
feed files. Disable withplugins: [blog: {feed: false}]. - Docs reference expansion: 9 previously undocumented config keys
(exclude_docs,draft_docs,not_in_nav,extra_templates,tikz,hooks,watch, legacyremote_branch/remote_name),social+i18n
plugin option tables, a Lucide icon section, social-cards plugin docs, and
the zero-configextra.i18n_languagesform (en + zh).
Fixed
- Blog entrypoint stayed stale on incremental builds: adding, editing, or
removing a post did not re-render the blog index when the entrypoint's own
source was unchanged. Newon_page_depsplugin event + dependency-set
change detection keep blog views fresh; no-op builds still skip. - Duplicate built-in plugin loading: declaring a built-in plugin
(plugins: [blog: {...}]) loaded a second auto-loaded instance with
default config because the dedup check compared the un-namespaced name
against the namespaced instance counter. Every declared core plugin was
silently duplicated — now deduplicated correctly. - Demo content: stale "31 extensions / 7 plugins" claims corrected;
i18nadded to the plugin list in the launch post.
Changed
- Dependency floor raised to current releases: click 8.4.2, Jinja2 3.1.6,
Markdown 3.10.3, PyYAML 6.0.3, watchdog 6.0.0, pymdown-extensions 11.0.1,
backrefs 8.0, Pygments>=2.20.0(itsdefaultstyle output matches the
committedpygments.css), and all other runtime deps; extras include
playwright 1.62.0, pypdf 6.15.0, pillow 12.3.0, cairosvg 2.9.0.
[12.5.1] — 2026-08-12
Added
- Incremental PDF export cache:
docsforge build --pdfnow skips pages
whose built HTML is unchanged since the last export (per-page content
hashes in.docsforge/cache/pdf.json, the same mechanism as the site
build). A single edited page re-renders only itself; orphaned PDFs are
removed; only successfully rendered pages are recorded as cached. on_build_donehook event: runs once at the very end of a successful
build — after page rendering, link/anchor validation,on_post_build
plugins, asset optimization, PWA manifest + service worker generation, and
the cache save — so hooks can inspect or post-process the final build
output (e.g.sw.js,cache-manifest.json). Skipped when the build is
aborted in strict mode.
Changed
- E2E tests honor
PLAYWRIGHT_CHROMIUM_EXECUTABLE: browser launch sites
now use the same env var asdocsforge.pdf, so a system browser can run
the e2e suite locally without Playwright's bundled Chromium. Unset, the
suite falls back to the bundled browser (CI) and skips gracefully when
neither is available.
Fixed
- Docs corrections: plugin count (8 core auto-load + social opt-in) and
Markdown extension count (36 default + 3 built-ins) now match the engine;
TikZ claims corrected (.texfiles compiled at build, requires a LaTeX
toolchain); stale "Insiders" references removed from the migration guide
(mkdocs-material is migrating to Zensical); zh changelog backfilled for
12.2.0–12.4.0; broken zh self-anchor fixed; icon counts corrected
(16,500+ icons across five families). - Example-site integration test pointed at the pre-12.5.0
examples/site
path and silently skipped — now runs againstexamples/sites/docsforge-demo. - Demo site: three new blog posts (TikZ diagrams, suffix-mode i18n,
DocsForge Studio diagnostics); removed an orphaned page;latex-equations.md
added to the nav; TikZ figures now resolve correctly.
[12.5.0] — 2026-08-11
Added
- DocsForge Studio (VS Code extension,
studio/): renamed fromvscode-docsforge. Full editor intelligence without a language server —
diagnostics from the build'svalidation.json(broken links/anchors,
footnotes), outline, folding, definition (ctrl+click), hover with
broken-link notices, completion (:material-/:lucide-icons + doc
paths), Find All References, and "Rename Document"/"Rename Anchor" that
rewrite every inbound link (anchor-aware, translation-aware — renaming a.zhfile renames its base + all locale variants). Explorer renames are
intercepted automatically. "Fix all broken links (N)" code action, open
link target, "Open Built Page" at the serve URL, format-on-save
(opt-in), output panel inside the sidebar view. - Python environment management in Studio: detects an interpreter
(setting → remembered venv →.venv→ PATH), checks pip + docsforge,
offers venv / user / global installs. - Apache-2.0 license: switched from LGPL-3.0-or-later;
NOTICEwith
upstream attribution (ProperDocs/MkDocs BSD-2, Material MIT, icons). - Vendored KaTeX + Mermaid as manifest-tracked devDependencies
(previously frozen / hardcoded CDN):copy_katex()+copy_mermaid()in
the frontend build; Mermaid loads from the local asset with a CDN fallback. - pygments.css generated at build time from the installed Pygments
(was a frozen snapshot). - Studio CI coverage:
ci.ymlgained astudiojob (npm ci + compile +
lint + test) and absorbedfrontend.ymlas afrontendjob.
Changed
- Social plugin is opt-in: removed from the always-loaded core plugins
(it needs pillow + cairosvg); enable viaplugins: [social]. Docs and
demo configs updated accordingly. - Link/anchor validation fixes: anchor problems are now persisted to
validation.json(they were logged but never stored — the Studio
diagnostics had nothing to show); fixed a shared class-levellink_warningslist that duplicated warnings across every page. - Renamed directories:
vscode-docsforge/→studio/,docsforge-docs/→docs/,examples/site/→examples/sites/
(gitignore negation removed — the globalsite/rule covers it now). - TypeScript 6.0.3 in Studio (TS 7 is outside the typescript-eslint
peer range). - Demo deploy: wrangler-action v4 (no pinned wrangler version).
Fixed
- Social card font fetch could hang builds: Google Fonts requests had no
timeout (reverted, upstream-style). The default cache moved to.docsforge/cache/social. - Demo pipeline: fixed the unprivileged
rm -rf /var/lib/apt/lists/*
that failed every run;neural-network.texneededamssymb; added the
missingshannon-state-machine.tex.
[12.4.0] — 2026-08-09
Added
- Social cards plugin (
social): vendored port of mkdocs-material's
social plugin, flattened intodocsforge/core/social.py(OpenGraph card
generation with two-stage parallel rendering, cache + manifest, font
download). Each i18n locale page gets its own card. Needspip install "docsforge[social]"(pillow + cairosvg). - Lucide icon set: 2022 icons, usable as
:lucide-name:inline or in
theme icon configs; stroke-based rendering support for.md-icon. - Link/anchor validation on every build: link and anchor problems are now
reported on every build, not only the first — validation data is persisted
per page in the build cache and re-checked for pages that are not re-rendered. - Programmatic palette API:
window.docsforge.setPalette({scheme, primary, accent})applies and persists themes from inline scripts/embeds (e.g. the
theme playground), including combinations with no header toggle. - Examples: the demo site now lives in-repo (
examples/site/docsforge-demo),
with acustom_diroverride, a custom-filter plugin example, a hooks
example, and a CI build test; the demo deploys to Cloudflare Pages via
GitHub Actions (with TeX Live for TikZ).
Changed
- Repository hygiene: all vendored mkdocs-material MIT headers removed
(attribution consolidated in the license page); parity tooling removed;
frontend reproducibility enforced in CI and at release time. - The service worker revalidates the manifest on every real page load
(Navigation Timing API) — a redeployed site is picked up without a hard
refresh; the 5-minute update poll and the hard-refresh monitor were removed. - Sidebar height uses
dvhso the left (nav) and right (TOC) sidebars
scroll identically on iOS/iPadOS.
[12.3.0] — 2026-08-06
Changed
- Repository flattened. The monorepo
packages/level is gone: the core
package,pyproject.toml,src/,tests/, and the frontend pipeline live
at the repo root; the docs site isdocsforge-docs/and the VS Code
extension isvscode-docsforge/. All workflows, Dockerfile, dependabot
config, and docs links updated; the full package README is now the root
README. - svgo 4. The icon pipeline now uses svgo 4 (config migrated;
viewBox
preservation unchanged). Verified byte-reproducible build output. - pnpm 11 for the frontend build (the workspace file uses pnpm 10+ syntax
that pnpm 9 rejected), with theminimumReleaseAgesupply-chain gate
disabled so dependabot bumps to freshly published versions pass CI. - Frontend CI is reproducible again:
frontend.ymlfails if the build
output differs from the committeddocsforge/templates/, and the release
workflow rebuilds the frontend fromsrc/before publishing the wheel. - The transition-time parity harness and baseline snapshot were removed
(their job is done);build_frontend.pymoved to the repo root. - CI runs the full test suite, including a dedicated Playwright e2e job.
[12.2.0] — 2026-08-06
Added
- Frontend built from source. The Material theme is vendored as source
undersrc/(mkdocs-material v9.7.7) and built by an
in-repo pipeline (scripts/build_frontend.py): esbuild bundles, sass +
autoprefixer CSS, svgo icons, minified templates and service worker. Every
shipped asset is reproducible, and parity gates
(scripts/check_frontend_parity.py) verify it per area on CI. - DocsForge frontend customizations now live in source instead of
hand-patches to minified bundles: service worker (manifest-driven delta
sync, locale-aware page serving, offline fast-404, manifest-aware candidate
skipping), stateless i18n language switcher (IndexedDB-backed), per-locale
search index, per-locale 404 pages, and theX-DocsForge-Instant-Nav
header that keeps instant navigation on the preferred locale.
Changed
- Service worker ships minified with
__DOCSFORGE_BASE_URL__/__DOCSFORGE_BUILD_HASH__placeholders;docsforge buildinjects the real
base path and a deterministic hash so SW bytes change between builds. - The SW skips page candidates absent from
cache-manifest.json— a stored
preference for the (unsuffixed) default locale no longer requestsindex.en.html(404s). - Blog posts without a
title:front-matter key no longer crash the build
when the meta plugin is active. scripts/check_frontend_parity.pygains--vs-ref(output diff vs a git
ref, no build) and sentinel-marker checks.
[12.0.0] — Unreleased
Changed
- BREAKING: Locale-agnostic i18n architecture. Translated pages are now emitted as siblings (
index.htmlandindex.<locale>.htmlin the same directory) and share the same public URL. The service worker stores the user's preferred locale in IndexedDB and serves the matching sibling on every request. This removes/<locale>/sub-sites, fallback pages, per-locale asset copies, and link/asset rewriting. - i18n search is locale-aware. Each locale has its own
search/search_index.<locale>.json; the frontend loads the index for the currently displayed language instead of always searching the default locale. - i18n language switcher reloads the same URL. Selecting a language writes the preference to IndexedDB and reloads the current page, so the switcher never produces stale locale-specific URLs.
Removed
- i18n
fallback_to_defaultoption. It is now ignored because fallback pages are no longer generated.
[11.5.4] — 2026-07-07
Fixed
- i18n link rewriting now handles unquoted and single-quoted
hrefattributes. The HTML minifier emits attributes likehref=second/, which the link rewriter previously missed, causing locale pages to link back to the default-language site. It now rewrites double-quoted, single-quoted, and unquotedhrefvalues. - i18n
nav_translationsnow apply to Page nav items. Previously only Section titles were translated; Page nav items now usenav_translationstoo, with frontmatter titles used as the fallback when no explicit nav title or translation is configured.
[11.5.3] — 2026-07-07
Fixed
- i18n fallback pages now inherit titles and nav overrides. Fallback locale pages (and translated pages created before navigation runs) no longer render nav items as "None"; they use the default page title and any custom
navtitle configured for that entry. - Locale links now rewrite for all pages, not just nav pages. Internal links from a translated page to a fallback/non-nav page now correctly stay inside the locale subtree instead of climbing out to the default-language URL.
[11.5.2] — 2026-07-07
Added
- i18n asset fallback. Translated assets (e.g.
assets/diagram.zh.png) are emitted under the locale path (zh/assets/diagram.png). If a translation is missing, the default asset is copied there automatically, so locale sites never lose images, CSS, or other docs assets. - Per-locale Material UI language. Translated pages now load the matching Material UI string file, so
<html lang>, search placeholders, and the language-switcher label follow the page locale instead of staying in the default language.
Fixed
- False-positive nav warnings for translated pages.
docsforge buildanddocsforge serveno longer log "pages exist in the docs directory, but are not included in the nav" for translated.zh.mdfiles handled by the i18n plugin. docsforge checknow treatsmaterial/i18nas a built-in plugin instead of a third-party plugin.
[11.5.1] — 2026-07-06
Fixed
- i18n language switcher used server-absolute URLs. The alternate URLs included the
site_urlsubpath, so when the templateurlfilter resolved them relative to the current page on sites deployed under a path (e.g.https://qqshi13.github.io/docsforge/), the links pointed todocsforge/docsforge/.... The i18n plugin now emits page-relative URLs (page.url) so the switcher and<link rel="alternate">tags stay inside the docs root.
[11.5.0] — 2026-07-06
Added
- Built-in i18n plugin (
material/i18n). Add translated files next to your default-language files (e.g.index.zh.mdbesideindex.md) and DocsForge builds a default site at the root plus one sub-site per locale under/<locale>/. Supports fallback pages, per-language navigation/title translations, a header language switcher,<link rel="alternate" hreflang="...">tags, per-locale search indexes, and per-locale sitemaps. - New documentation page: Multi-language sites.
Fixed
- Material theme now ships a default
paletteso builds no longer fail whentheme.paletteis omitted. - Removed
properdocs_version/mkdocs_versionlegacy fields from the template context. - VS Code extension build restored by rolling TypeScript back to
^5.9.3and@types/nodeto^22.20.0withcommonjs/nodemodule resolution.
[11.3.12] — 2026-06-27
Added
- Browser E2E tests (Playwright). A 5-test Chromium suite (
tests/e2e/) covering the service-worker behavior that can't be unit-tested: SW installs and caches the visible page, offline reload serves the cached page,search_index.jsonis served, and hover-prefetch caches the destination so it loads offline. The tests skip gracefully when no browser is available (so the defaultpytestrun is unaffected); a dedicated non-blockinge2eCI job onubuntu-latestinstalls Chromium and runspytest -m e2e.
[11.3.11] — 2026-06-27
Added
- VS Code extension tests (10). Added a mocha + ts-node suite for the extension's pure helpers: config-file discovery (
findConfig/hasConfig,.ymlpreferred over.yaml) and server-URL extraction from stdout. Extracted the helpers into a vscode-freesrc/pure.ts(behavior-preserving) so they're testable without launching VS Code. CI now runsnpm testin thebuild-vsixjob;test/is excluded from the VSIX.
[11.3.10] — 2026-06-27
Added
- Serve / live-reload unit tests (15). Covers
_find_available_port(free, in-use increment, all-in-use, firewall-dropped-SYN with the short-timeout WSL fix),_serve_url/_normalize_mount_path/_try_relativize_path, and the rebuild-queueing logic that guards against the infinite reload loop (events during a build are queued via_pending_rebuild, not signaled).
Changed
- Extracted the live-reload file-watch callback to a testable
LiveReloadServer._on_file_eventmethod (behavior-preserving).
[11.3.9] — 2026-06-26
Added
- Reproducible builds via
SOURCE_DATE_EPOCH.get_build_datetime()now honors the standardSOURCE_DATE_EPOCHenvironment variable (reproducible-builds.org). When set, the build date, pageupdate_date, sitemap<lastmod>, andsitemap.xml.gzmtime are all derived from that timestamp instead of the wall clock — so two builds of the same source produce byte-identical output (verified: identical content hash across clean builds). Defaults to the current time when unset.
Changed
- Search index entries are now sorted by location before serialization, guaranteeing byte-reproducible
search_index.jsoneven if the build loop populates entries in non-deterministic order.
[11.3.8] — 2026-06-26
Fixed
- Template edits now trigger a full rebuild. The build cache only tracked source-
.mdhashes, the config hash, and the package version — so editingbase.html, a partial, or atheme.custom_dirtemplate did not rebuild unchanged pages (stale output). The build now records a stat-only signature of all.html/.xmltemplates in the theme dirs (the 14k+.icons/excluded) and forces a full rebuild when it changes. Editingbase.htmletc. now rebuilds every page.
[11.3.7] — 2026-06-26
Changed
- Parallel Markdown rendering.
_populate_page(read source +markdown.convert, the CPU-heavy part of the build) now runs across aThreadPoolExecutor(up to 32 workers). The per-threadMarkdowninstance makesrender()thread-safe, so only the plugin event calls andconfig._current_pageare serialized via a lock. Template rendering (_build_page) stays serial to avoid the sidebar-active race. Cold build of the docs site: ~10s → ~7s; output is byte-identical across builds (verified over 5 runs).
[11.3.6] — 2026-06-26
Fixed
- DocsForge upgrades now trigger a full rebuild. The build cache only tracked source-
.mdhashes and thedocsforge.ymlhash, so upgrading DocsForge (new theme templates / service worker / build logic) did not rebuild unchanged pages — the new SW and templates wouldn't reach the built site until a source file was edited. The cache now records the package version and forces a full rebuild when it changes. - Config changes now actually rebuild unchanged pages.
cache.invalidate()deleted disk files but left the planner's in-memory hashes intact, so a config/package change didn't rebuild pages whose source was unchanged (off-by-one: the change applied on the next build, not the current one).planner.invalidate()clears both in-memory and disk state. Themeta(mtime/size hash cache) is retained across a version bump so the rebuild still skips re-reading unchanged sources.
[11.3.5] — 2026-06-26
Added
- Hover/focus link prefetch. Internal links are now prefetched on
mouseover/focusin(fire-and-forget through the service worker'sserveCurrentPage), so the destination page is already cached when the user clicks — page switches become instant. Same-origin only, deduped, skips same-page/anchor links.
Fixed
- SW update message aligned with the client. The SW now posts
DOCSFORGE_UPDATE_READY(the message namebase.htmlalready listens for) instead of the unuseddocsforge-updated.
[11.3.4] — 2026-06-26
Changed
- Build: mtime+size pre-filter for file hashing.
should_rebuildandcache-manifest.jsongeneration re-read and SHA-256'd every source.mdon every build. They now consult a{path: {mtime, size, hash}}cache (meta.json) and reuse the cached hash whenstat()reports the same mtime and size — a stat instead of a full read+hash. On a no-op build this makes the hashing phase stat-only (docs site: 0.87s → 0.66s; bigger gains on large sites).
[11.3.3] — 2026-06-26
Changed
- Build: skip asset optimization when the site is unchanged.
optimize_assetsre-scanned every HTML/CSS/JS on every build. It now runs only when a page was actually rebuilt or the source set changed; on a no-op incremental build it's skipped entirely (docs site: 1.61s → 0.87s).
[11.3.2] — 2026-06-26
Changed
- Build: skip the orphan-output scan when no source files were deleted.
find_orphaned_outputswalked the entiresite_diron every build, but orphaned outputs can only appear when a source is removed. The build now records the source-URI set (sources.json) and skips thesite_dirwalk when the set is unchanged or only grew since the last build. On a cache hit (typical incremental build) this removes a full output-tree traversal.
[11.3.1] — 2026-06-26
Changed
- SW: conditional manifest fetch.
fetchManifestnow usesfetch(cache-manifest.json, { cache: 'no-cache' })instead of cache-busting with?v=Date.now(). The static host returns 304 when the manifest is unchanged, so rapid navigations no longer re-download the full manifest each time — same freshness, less bandwidth.
[11.3.0] — 2026-06-26
Changed
- Service worker redesigned around "current page first". The SW now treats
docsforge serveand a deployed site identically and prioritizes the page you're actually viewing: - Install is non-blocking — just
skipWaiting(), no more pre-cache-all that held the SW inactive until every page was fetched. serveCurrentPage(request, manifest)runs on every navigation/page-switch: it fetches the manifest once, serves the current page from cache instantly if its hash is current, otherwise fetches+caches the fresh page and displays it (falling back to the stale cache offline). This is a separate function from the background sync.syncCacheFromManifest(manifest)runs in the background (throttled ≥10 min, deduped) using the same fetched manifest, caching every other page that is missing or whose hash changed.activateprimes the visible page first (the tab the user is on), then background-syncs the rest — so "first install caches the current page first" is literally true.- Applies to page switches too: programmatic HTML fetches (Material instant navigation) are detected via
Accept: text/htmland routed throughserveCurrentPage, not just hard navigations.
Fixed
- Manifest sync no longer re-hashes every cached HTML body.
cache-manifest.jsonstores source-.mdhashes but the SW caches built HTML, so the old body-hash comparison never matched and every page was re-fetched on every sync. The SW now diffs the manifest against the previously-synced per-file hashes plus checks cache existence — a page is re-fetched only when missing or actually changed. No body hashing.
[11.2.1] — 2026-06-26
Changed
- Service worker: far more efficient cache syncing. Previously
syncCacheFromManifestran on every navigation — a network fetch ofcache-manifest.jsonplus a re-hash (SHA-256 of the full body) of every cached page on each navigation after a deploy. Now: - Throttled to at most once per 10 minutes (and deduped so concurrent navigations share one sync).
- Diff-based — the previous manifest's per-file hashes are stored, so sync only re-fetches pages whose hash actually changed. Cached bodies are no longer re-hashed on every sync (only a one-time hash check the first time a URL is seen).
- On the docs site (44 pages) this removes ~44 SHA-256 ops + a manifest fetch per navigation, replaced by a single throttled fetch that touches only changed pages.
- The SW now posts a
docsforge-updatedmessage to open tabs when content changes (forward-compatible hook; harmless if no client listener is wired).
[11.2.0] — 2026-06-26
Changed
docsforge servenow uses the exact same service-worker caching strategy as a deployed site — no localhost special-casing. The previous versions special-casedlocalhost/127.0.0.1(first a hard network-only bypass, then a network-first variant) to keep livereload fresh. That made dev behave differently from production and broke offline dev. The SW now treats localhost identically to any other host: cache-first for HTML/assets, stale-while-revalidate for the rest, with background manifest-sync. Consequence: livereload auto-reloads may serve cached content until the SW's background sync catches up — the same freshness model as the deployed site. Dev is now faithful to production, including offline support after the server stops.
[11.1.9] — 2026-06-26
Added
- Docker image now publishes version-numbered tags. Previously only
latestandsha-*were pushed (thetype=semvermetadata never fired because the build job checks out a commit SHA, not the tag). Each release now also publishesghcr.io/qqshi13/docsforge:<version>(e.g.:11.1.9) and:<major>.<minor>(e.g.:11.1) for stable releases. - Friendly, helpful GitHub release notes. Release bodies are now generated from the matching CHANGELOG entry, with install/upgrade commands, Docker pull/run examples, and a VS Code extension download hint — instead of a bare "Full Changelog" link.
Fixed
docsforge servepages now work offline after the server stops. The service worker had a hard localhost bypass: onlocalhost/127.0.0.1it fetched from the network with no caching and no fallback, so once you stopped the dev server (or went offline) every page was blank. It now uses network-first for localhost — fresh content while the server runs (so livereload stays loop-free, the original reason for the bypass) — and caches successful responses so visited pages survive after the server is closed. The stale-cache reload loop stays fixed because the SW never serves stale HTML during a reload.
[11.1.8] — 2026-06-25
Fixed
- Config-check summary now appears at the start of
build/serveoutput, not the end.check()prints to stdout viaprint(), while the build logs to stderr vialogging(unbuffered). When the two streams are merged and piped — i.e. CI,docker run, or any| grep/| tail— stdout is block-buffered and doesn't flush until process exit, so the check block landed after the build logs even though it ran first.check()now flushes stdout before returning. (Regression guard added:test_regression_config_check_appears_before_build_logs.)
[11.1.7] — 2026-06-24
Added
docsforge serve --strict— the dev server now accepts--strict(matchingdocsforge build). Rebuilds treat warnings as errors; the server stays alive and logs the abort so you can fix issues without restarting. The flag propagatesstrict=TruethroughDevServer.serve→serve_module.serve→load_config.- Docker: customizable PDF browser. Documented how to point PDF export at a different Chromium/Chrome via
PLAYWRIGHT_CHROMIUM_EXECUTABLE(override the path, fall back to Playwright's bundled browser, or mount a host binary). Seedocs/advanced/docker.md.
Changed
- The Docker guide now lists all
--strict/ PDF-browser /--jobsoptions with copy-pasteabledocker runexamples.
[11.1.6] — 2026-06-24
Added
- 75 more tests (154 total):
test_files.py(File model, dest_uri/url mapping,get_fileswalk),test_config.py(load_config, defaults, env-tag substitution, validation),test_init.py(project scaffolding),test_search.py(SearchIndex entries/tags/jieba gating),test_tags.py(Tag model),test_privacy.py(FragmentParser, mime map),test_minify.py(JS/CSS/HTML minification),test_meta.py(meta-file merge).
Fixed
load_configcrashed withNameErroron invalid YAML. Theexcept yaml.YAMLErrorhandler referenced an unimportedyaml, so a syntax error indocsforge.ymlproduced a rawNameError: name 'yaml' is not definedinstead of a friendly error. (Found bytest_config.py::test_invalid_yaml_raises.)
[11.1.5] — 2026-06-24
Added
- Test suite. DocsForge now ships a pytest suite (79 tests) covering the incremental cache, config loading, the CLI front-end, utils, end-to-end builds, and a regression file with one named guard per historical bug. Previously the project had zero tests.
Fixed
Bugs found while writing the test suite:
- Incremental cache now works for non-root pages.
BuildPlanner.find_orphaned_outputsonly checkeddocs/<name>/index.mdfor an output atsite/<name>/index.html, missing the commonuse_directory_urls=Truemappingdocs/<name>.md→site/<name>/index.html. Every non-root page was therefore deleted as "orphaned" at the start of each build and rebuilt from scratch, defeating the incremental cache for them. Second build of the docs site dropped from ~4.7s to ~0.9s. detect_environmentalways reporteddocs_dir_exists: False. It referenced_open_config_filewithout importing it (the import lived in a different function's scope); the resultingNameErrorwas swallowed by a bareexcept, sodocs_dir_exists/has_indexwere never populated._open_config_filerejectedpathlib.Patharguments. It only handledstr/IO/None; aPathfell through to the file-descriptor branch and crashed on.seek(0). Now accepts anyos.PathLike.BuildPlanner.savedid not update the in-memoryconfig_hash. After writing the config hash to disk, a subsequentshould_full_rebuildon the same planner instance still saw the stale (None) value and forced a full rebuild.- Blog plugin
on_shutdowncould crash the build.rmtree(self.temp_dir)raisedFileNotFoundErrorwhen the temp dir was already gone (repeated builds in one process); cleanup is now idempotent.
[11.1.4] — 2026-06-24
Fixed
- Incremental cache dependency tracking actually works now. The v11.1.3 implementation had two defects that made it a silent no-op:
build.pypassedpage.content(rendered HTML) toDependencyTracker.get_file_deps, but thepymdownx.snippets--8<--include markers are consumed duringmd.convert(). It now passespage.markdown(the raw source, which retains the markers).- Include paths were resolved only relative to the source file's directory, but
pymdownx.snippetsresolves relative to its configuredbase_path(docsforge doesn't set one, so the default is the current working directory / project root). Includes are now resolved againstdocs_dir, the source file's directory, and the cwd. - Failed builds are no longer cached. In strict mode,
_build_pagere-raises, but the build loop still calledplanner.update_cacheafterward — marking a broken page as up-to-date so the next run silently skipped it. The cache is now only updated for pages that built successfully.
[11.1.3] — 2026-06-23
Fixed
- Incremental cache now tracks snippet includes.
DependencyTracker.get_file_depswas a stub returning[], so editing a file included viapymdownx.snippets(--8<-- "path") did not trigger a rebuild. Includes are now resolved relative to the source file and watched for changes. A latent bug inBuildPlanner.update_cachethat never stored dependency hashes (making the dep check a no-op) was also fixed. - Removed dead
_OPTIONAL_PLUGINScode path incli_core._check_optional_deps— the empty plugin→dependency map and its unusedplugin_namesloop. The realjieba/docsforge[chinese]check is retained.
Changed
- Repo hygiene — untracked build artifacts removed from git:
- Removed 42 committed PDF build outputs under
docsforge-docs/pdf/; the directory is now gitignored. docs/blog/index.md(auto-generated by the blog plugin) untracked and properly gitignored at the package level. The previous root-leveldocs/blog/index.mdpattern was slash-anchored and never matched the real path underdocsforge-docs/.- Deleted stale
docsforge-vscode-11.0.0-beta.2.vsixfrom disk.
[11.0.0b1] — 2026-06-19
Added
- VSCode Extension: Open Preview — Sidebar button opens the dev server in VS Code's Simple Browser via
simpleBrowser.api.open. - VSCode Extension: Open Docs — Sidebar button opens the DocsForge documentation site.
- VSCode Extension: Stop Build — Sidebar button to cancel a running build.
- VSCode Extension: Managed external server — Detects
docsforge servestarted in a terminal via.docsforge/server.jsonpidfile. - Pidfile —
docsforge servewrites.docsforge/server.jsonwith PID and URL for external tools. - Content-based cache busting — Downloaded external assets include a content hash in the filename.
- Docs badge — Compact and standard DocsForge badges.
Changed
- Service worker: bypass cache for localhost — SW detects
localhost/127.0.0.1and fetches from network. Prevents stale-cache reload loop during dev. - Livereload:
_rebuildingflag — File changes during a build are queued, not acted on immediately. One final rebuild fires afterward. - Tags template layout flattened —
fragments/tags/{layout}/tag.html→fragments/tags/{layout}-tag.html,listing.html→{layout}-listing.html.
Fixed
- Asset optimizer: unquoted HTML attributes — Made regex quotes optional to match Material's unquoted output.
- Privacy plugin:
url_relative_toargument order — Path was from file to page instead of page to file. - Privacy plugin: path normalization — Regex
/.matched.icons→_icons. Fixed. - Webserver:
.well-knownroute — Chrome DevTools probes returned 404. - Infinite reload loop —
_rebuildingflag + SW localhost bypass.
VSCode Extension
- Open Preview via
simpleBrowser.api.open - Open Docs sidebar button
- Progress notification dismisses on stop
- Pidfile detection with 3s polling
- Stop external server (kill by PID)
- Stop Build button
- Sidebar state sync for server and build status
[10.9.9] — 2026-06-18
Fixed
- Favicon 404 — Asset optimizer regex made quotes optional.
- Privacy font CSS path —
url_relative_to()argument order fixed. - Privacy path normalization —
/.regex too broad.
Added
- Pidfile —
.docsforge-server.jsonfor external server detection. - VSCode Extension: Open Preview, Open Docs.
[10.9.8] — 2026-06-18
Added
- Unified release workflow —
release.ymlwith version bump, commit, tag, release, PyPI, VSIX. - DocsForge badges — Compact (110×20) and standard SVG badges.
- Docs: Render, DigitalOcean deployment guides.
Changed
- Tags template layout flattened — Subdirectory → prefix naming.
[10.9.7] — 2026-06-18
Added
- Unified release workflow — Replaces
publish.yml+bundle-extension.yml. - VSCode Extension: sidebar improvements — 10+ bug fixes.
[10.9.6] — 2026-06-18
Fixed
- Privacy: nested URLs in downloaded CSS.
- Font-display swap for Google Fonts.
Added
- Hash-based cache manifest for offline sync.
.well-known/browser probes — 200 + empty JSON.
[10.9.5] — 2026-06-17
Changed
- Simplify build — Always complete output.
Fixed
- WSL port detection — Socket timeout.
- SW only re-caches on content change.
[10.9.4] — 2026-06-17
Fixed
- Python
__version__sync with release.
[10.9.3] — 2026-06-17
Fixed
- Hot-reload duplicate builds — Race condition.
[10.9.2] — 2026-06-17
Fixed
- Export plugin directory handling.
[10.9.1] — 2026-06-11
Fixed
[10.9.0] — 2026-06-11
Added
Git revision dates — Every page now automatically shows "Last updated" and "Created" dates from git history. No configuration required — works out of the box for any docs site in a git repository. The dates are read from
git logand formatted as human-readable strings (e.g., "Jun 11, 2026"). The existingsource-file.htmltemplate already supported this — now it's actually populated. Disable withextra.git_revision_date: falsein docsforge.yml.CLI serve options — Added
--no-open,--port, and--hostflags todocsforge serve:docsforge serve --no-open # Don't auto-open browser docsforge serve --port 3000 # Serve on port 3000 docsforge serve --host 0.0.0.0 # Serve on all interfaces
Changed
- All theme features enabled by default — The material theme now enables a rich set of features out of the box, so new sites get the full experience without needing a long
features:list in docsforge.yml. New defaults include: content.action.edit,content.action.viewcontent.code.annotate,content.code.copycontent.tooltipsnavigation.footer,navigation.indexes,navigation.sections,navigation.tabs,navigation.top,navigation.tracking,navigation.instant,navigation.instant.progresssearch.highlight,search.share,search.suggesttoc.follow
[10.8.12] — 2026-06-11
Optimized
- Markdown instance reuse — Added per-thread caching of
markdown.Markdowninstances inpages.py. Previously, every page created a new Markdown instance, re-initializing all extensions (pymdownx, codehilite, etc.) from scratch. With 36 pages and 10+ extensions, this was a significant overhead. Now: - Each thread gets a cached Markdown instance keyed by
(extensions, configs) - Instances are
reset()between pages instead of recreated - Build time improvement: ~2.5s (was ~3.4s) — ~25% faster
Especially impactful for large sites with many Markdown extensions
Streamlined build.py — Multiple internal optimizations:
- Removed redundant
if _page_lock: with _page_lock:branching in_populate_page(it was always called sequentially, no lock needed) - Removed nested
_do_build()closure in_build_pagethat added function call overhead per page - Cached
files.documentation_pages()result instead of calling it 3 times per build - Moved
hashlibimport from inline (inside_inject_sw_build_hash) to module top level Simplified lock handling: always acquire lock in
_build_page, removedNonefallback pathRemoved "Cyrus" from copyright headers — Found and fixed 3 remaining files that still had "Cyrus" in the copyright string:
theme.py,preview.py,filter_config.py
[10.8.11] — 2026-06-08
Changed
- Lazy background caching — Replaced the blocking
cache.addAll()during service worker install with non-blocking incremental background caching. Previously, the SW tried to download all pages at once during install, which could block the initial page load. Now:
- The SW installs and activates immediately — the current page loads without delay.
- After activation, other pages are cached one by one in the background via
backgroundCachePages(). - The current page is already cached by the
fetchhandler when visited. - A
DOCSFORGE_CACHE_COMPLETEmessage is sent to all clients when done, so the UI can show a subtle indicator (e.g., "All pages available offline").
[10.8.10] — 2026-06-08
Fixed
- Service worker pre-cache URL resolution —
PRE_CACHE_PAGESURLs were relative to the site root (e.g.,"./","advanced/customization/"), butcache.addAll()inside the SW resolves URLs relative to the SW script location (/assets/javascripts/sw.js). This caused all pre-cache requests to fail silently, leaving the cache empty and breaking offline support. All pre-cache URLs now prefixed with../../so they correctly resolve from the SW to the site root. - Offline 404 fallback for subpath deployments —
cache.match("/404.html")was hardcoded to the domain root, which is wrong for sites deployed under a subpath (e.g.,/docsforge/). The SW now computesBASE_URLfrom its own location and usesBASE_URL + '404.html'for the fallback.
Added
- PWA update notification — When a new DocsForge version is deployed, the service worker activates and sends a
DOCSFORGE_UPDATE_READYmessage to all open tabs. The page displays a fixed banner at the top: "A new version of this documentation is available." with Refresh and Dismiss buttons. Users can click Refresh to immediately load the new content, or Dismiss to keep reading the current version. - Periodic update checks — The page checks for service worker updates every 5 minutes (
registration.update()), so users get notified of new content even on long-running sessions without needing to reload manually.
[10.8.9] — 2026-06-07
Added
- Full PWA / offline support — DocsForge now generates a
manifest.jsonand pre-caches all HTML pages during the service worker install phase, enabling full offline browsing of all documentation pages after the first visit. manifest.json— Generated automatically with site name, description, theme color (extracted from palette), and start URL. Linked via<link rel="manifest">in the<head>of every page.- Pre-caching all pages — The service worker now receives a
__PRE_CACHE_PAGES__placeholder that is replaced at build time with the complete list of all built HTML page URLs. Duringinstall, the SW caches every page so they work offline immediately without needing to visit each page first. - Cache-first strategy — The service worker now uses
cacheFirstWithNetworkFallbackfor HTML documents (wasstaleWhileRevalidatewhich caused network requests on every navigation). This means pages load instantly from cache even online, with a background update. - Offline fallback — If a page is not cached and the user is offline, the SW serves the 404 page (or a generic offline message if 404 isn't cached).
Changed
- Service worker strategy — Switched from
staleWhileRevalidatetocacheFirstWithNetworkFallbackfor HTML pages and assets. This prioritizes offline reliability and instant loads over always-fresh content. For most documentation sites, this is the desired behavior.
Fixed
- Removed "Cyrus" from all docs — Eliminated all remaining references to "Cyrus" from documentation, site footer, and copyright strings. The
license.mdanddocsforge.ymlsite author/copyright now use "QQ" only.
[10.8.8] — 2026-06-06
Changed
- Always dirty builds — Removed the
build_typeparameter fromserve()and alldirty=Falsedefaults. All builds are now incremental by default. Thedirtyflag was a legacy concept that caused confusion — full rebuilds are only triggered when the config file changes (detected by hash), andclean_directory+cache.invalidate()are skipped unless the config changes.
[10.8.7] — 2026-06-06
Fixed
docsforge servecache invalidation —DevServer.serve()was not passingbuild_type='dirty'toserve_module.serve(), causing the dev server to do full rebuilds with cache invalidation on every file change. This made live reload painfully slow. Thebuildcommand already did dirty/incremental builds by default; nowservedoes too.
[10.8.6] — 2026-06-06
Fixed
- Asset 404s on sub-pages —
base_urlcomputed byget_relative_url()returned..without trailing slash for pages at subdirectories. Without the trailing slash,_get_relative_urltreated it as a filename (stripped it), causing CSS/JS/asset paths to resolve relative to the page directory instead of the site root. Added trailing slash normalization toget_context()and_build_template().
[10.8.5] — 2026-06-06
Fixed
- Sidebar race condition — ThreadPoolExecutor parallel page builds caused multiple pages to be marked
activesimultaneously. EachPage.activesetter propagates to its parentSection, so concurrent builds leaked active state between pages. This resulted in the sidebar showing multiple sections as expanded when only the current section should be. Restructured_build_pageto hold the existingRLockfor the entire template render + file write phase, ensuring only one page is ever active at a time.
[10.8.4] — 2026-06-06
Fixed
- Search index 404 on non-root pages —
base_urlinbuild.pywas computed backwards:get_relative_url('.', page.url)returned the path from root to page instead of from page to root. The JS injected this into page config asbase, then resolvedsearch/search_index.jsonagainst it, producing a duplicated path like/docsforge/getting-started/getting-started/search/search_index.json. Fixed toget_relative_url(page.url, '.'). - Sidebar overlapping footer on desktop — Added
max-height: calc(100vh - 2.4rem)to.md-sidebar__scrollwrapatmin-width: 60emto prevent the sidebar from extending past the viewport and overlapping the footer.
[10.3.3] — 2026-05-17
Added
- Versioned service worker — Each build generates a unique hash in the SW, ensuring browsers install the new version and purge old caches
- Auto cache cleanup — Old caches automatically deleted when new SW activates
- Offline support — All same-origin files cached; HTML uses network-first, assets use cache-first
- PWA-ready — Service worker registration in every built page
Fixed
- Service worker scope set to
/(root) instead of/assets/javascripts/so it can intercept all requests
[10.3.2] — 2026-05-17
Fixed
- Service worker scope fixed to
/so it can cache blog posts and documentation pages - Added
request.mode === "navigate"check for better HTML page detection
[10.3.1] — 2026-05-17
Fixed
.icons/directory now included in PyPI wheel build- Added
artifactspattern inpyproject.tomlto ensure Material theme icons are packaged
[10.3.0] — 2026-05-17
Added
- TikZ diagram support — Write TikZ diagrams in Markdown, automatically compiled to SVG at build time
- Theme playground — Interactive palette switcher with live preview
- Blog plugin — Built-in blogging with authors, tags, archive, pagination, and RSS feeds
Fixed
- Source repo blank spot fixed (removed fixed 234px width when no stars/forks)
- Theme persistence across page navigation (uses
__md_scopeinstead of per-page URLs) - Palette toggle button highlight sync
- 404 page styling
Changed
- Cleaned up unrelated development files from repo
- All repos use
mainas default branch
[10.2.0] — 2026-05-16
Added
- Vendored mkdocs + Material — Self-contained, no external dependencies
- GitHub Pages deployment — GitHub Actions workflow for auto-deployment
- PyPI publishing — Automated releases via GitHub Actions
[10.1.0] — 2026-05-10
Added
- Zero-config Markdown — 31 extensions loaded by default (all pymdownx + python-markdown). No
markdown_extensions:config needed. - KaTeX math — Vendored KaTeX (1.5MB) renders
$$...$$inline and display math. No CDN calls for readers, no config. - Pygments highlighting — Syntax-colored code blocks at build time. No client-side JS.
- Dark mode toggle — Light/dark mode switch in header. Auto-detects system preference.
- Auto-loaded plugins — search, tags, blog, info, meta, minify, privacy all work without config.
- Self-hosted fonts — Privacy plugin downloads and caches Google Fonts locally.
Changed
- Config file renamed from
properdocs.ymltodocsforge.yml - Theme namespace changed from
mkdocs.themestodocsforge.themes - Plugin system — 6 plugins removed, 7 remain as built-in defaults
Removed
typeset— Users can write Unicode directlyoptimize— Requires externalpngquantbinarysocial— Requires Pillow + CairoSVGprojects— Niche multi-project featureoffline— Privacy plugin covers most use casesgroup— Plugin orchestrator (niche)
[0.1.0] — 2025-05-10
Added
- Initial release