Changelog
All notable changes to DocsForge are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[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
packages/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 underpackages/docsforge-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