✨ Highlights
Three coordinated fixes that close the follow-ups to #175 reported on v0.8.15. No breaking changes, no new config keys, no required migrations. Existing projects build and serve unchanged.
🐛 Fix: Dev server respects the production URL
v0.8.15 introduced auto-derivation of base from url’s pathname, so a GitHub Pages project site at https://user.github.io/repo automatically gets base = /repo/ and emits subpath-aware asset URLs. The dev server, however, always serves the output dir at root (/) — so every asset on localhost 404’d whenever url carried a subpath.
The reported workaround was "base": "/", which made dev work but disabled the auto-derivation, breaking search on the production deploy. Both states were broken for one environment.
The fix: dev mode now forces base = / when the value was auto-derived. The override is restricted to:
isDev: true(build path is untouched)_baseAutoDerivedis set (an explicitbasein the config is always honoured)DOCMD_PROJECT_PREFIXenv var is absent (workspace dev uses it)
Users no longer need a base entry in their config at all.
🗂 Fix: All search data moves to .docmd-search/
Keyword and semantic search now share a single output folder:
- Before:
search-index.jsonlived at the output root, semantic files under.docmd-search/. - After: both live under
.docmd-search/. Default locale:.docmd-search/search-index.json. Non-default:.docmd-search/<locale>/search-index.json.
This keeps the output root clean and unifies the contract the client fetches against, so the next fix could land cleanly.
🐛 Fix: Removed the runtime manifest.json HEAD probe
The search client probed .docmd-search/manifest.json with a HEAD request on every page load to catch the narrow case where semantic deps were installed mid-build. On every keyword-only site that probe returned 404, which was the exact error users were reporting.
The fix: semantic mode is decided solely by the build-time data-semantic flag on the search modal. The flag is computed in onConfigResolved from semanticUsable, so the build and the client agree on which mode is active. The rare mid-build edge case resolves on the next rebuild (dev mode triggers one automatically).
🐛 Fix: Keyword index always generates as fallback
Once the runtime probe was removed, a second race surfaced: when semantic indexing succeeded, the keyword search-index.json was skipped entirely. If the modal’s data-semantic flag was stale (set to false at render time because deps were not yet installed), the client fell back to keyword mode and 404’d because no keyword index existed.
The fix: the keyword index now always generates, even after a successful semantic build. The index is tiny (a few KB for most projects) and serves as a runtime safety net. TUI messages are suppressed when semantic already ran, so the log stays clean.
🐛 Fix: data-semantic flag stamped post-build
The data-semantic="true" attribute on the search modal is computed at render time from semanticUsable, which checks whether docmd-search is resolvable. On a fresh install, deps are installed in onPostBuild, after pages have already been rendered without the flag. The client then defaults to keyword mode even though the semantic index exists.
The fix: after the semantic index builds successfully, every HTML page is post-processed to ensure the modal carries data-semantic="true". This closes the race without reintroducing the runtime HEAD probe. The post-build walk skips .docmd-search/ and node_modules/ to stay fast on large sites.
🧰 Auto-install no longer pre-blocks workspace sub-projects
The pre-check that scanned for package.json in cwd before spawning the package manager has been removed. npm, pnpm, yarn, and bun all walk up the directory tree themselves to find the project root, so the pre-check was wrong in two cases it claimed to handle:
- Workspace sub-projects (e.g.
docs/docmd-search/whendocs/package.jsonlives one level up). - Any case where a plugin or template needs to install and the project root is several dirs deep.
The fix: the spawn is always attempted. The friendly “no package.json found” hint now fires only when the spawn genuinely fails and no package.json exists in any ancestor directory. Templates are covered by the same path as plugins.
Verified end-to-end with a fresh brutetest using @docmd/template-summer and plugins.search.semantic: true: both auto-install silently on the first build, summer CSS/JS land at site/assets/template/, and the semantic index writes to site/.docmd-search/.
🏗 Portable deploys via canonical <base href>
Non-offline builds with a subpath (siteRootAbs !== '/') now emit a single <base href="/<repo>/"> tag in the document head. Asset URLs in the head use simple-relative paths (assets/foo.css); the <base> tag shifts them to the correct location at runtime, no matter where the site is hosted.
Why this matters: the previous approach encoded the subpath into every asset URL. The rename-flow went like this — you rename the repo, the next build keeps emitting /old-name/assets/... until you update config.url and rebuild. With <base>, the path is computed once from the deploy location and applied uniformly. Moving a site between repos, hosts, or folder mounts no longer requires touching config.url if url is already correct for the current deploy.
Three-mode boundary to keep this clean:
| Mode | <base> |
Assets |
|---|---|---|
build (subpath) |
emitted | simple-relative 'assets/foo.css' |
build (root deploy) |
none (document default) | simple-relative 'assets/foo.css' |
dev |
none | depth-aware ./ or ../ |
build --offline |
none (file:// has no host) | depth-aware ./ or ../ |
The canonicaliser (normaliseBaseTag in packages/parser/src/utils/url-utils.ts) strips any pre-existing <base> tag emitted by older templates or third-party plugins, then injects exactly one at the canonical position right after </title>. The deploy shape is decided in one place; templates don’t need to know about it.
📂 Fix: Robust offline asset path resolution
Offline builds (--offline) could previously suffix arbitrary non-markdown asset files (e.g. PDFs, images, JSON files in subdirectories) with /index.html, breaking downloads and images.
The fix: the URL normalizer now uses a robust, non-destructive check to verify if a path carries a filename with a dot (indicating an asset file), ensuring only targeted files (.md, .html, etc.) are processed, leaving other file extensions untouched.
🧰 Improved consumer simulation workflow
Testing the documentation generator monorepo changes against consumer projects is now seamless:
- The fix: the local simulation engine (
tools/sim.mjs) has been optimized with--sourcespecification and support for the--doctormode to run simulated consumer checks. - Wired commands: added
dev:sim,build:sim, anddoctor:simcommands directly todocs/package.jsonutilizing the monorepo’ssim.mjswrapper with--regen-tarsand--skip-monorepo-buildflags.
🧹 Noisy CLI environment warnings silenced
Running npm or npx commands spawned from within a pnpm runner could leak internal pnpm configurations into the environment, causing npm to output annoying warnings about “Unknown env config”.
The fix: the spawning wrappers now sanitize the environment by stripping npm_config_npm_globalconfig, npm_config_verify_deps_before_run, and npm_config__jsr_registry from process.env prior to executing subcommands.
🎨 Favicon fallback to prevent 404 console errors
Web browsers automatically request /favicon.ico at the root of a domain. If the site had no favicon configured, this led to a 404 response in the browser console.
The fix: the page generator and 404 template now automatically fall back to the default assets/favicon.ico when config.favicon is not defined in the workspace config.
📊 Enhanced prep pipeline and TUI summaries
The monorepo development pipeline TUI has been polished:
- Terminal cursor-up rewriting: the test runner status now correctly updates the
[ WAIT ]line to[ DONE ]or[ FAIL ]in-place, eliminating duplicate output lines in standard terminal emulators. - Accurate time and verdict summary: at the very end of the prep execution, a concise 2-3 line summary reports the total elapsed time, success status, and a list of failed sections.
📦 Upgrade
npx @docmd/core@0.8.16 build
No config changes required. If you previously set "base": "/" as a workaround for v0.8.15, you can remove it — the dev server now adapts automatically.