- New `mdcms serve` command: local preview server with SPA clean-URL
fallback. Unknown extension-less paths (e.g. /section-id) are served
index.html so reloads and shared clean URLs work; paths with an
extension still 404, preserving the renderer's category-variant
fallback. Serves .md as text/markdown and .yml as text/yaml.
Options: [name], --path, --port (default 8800), --bind.
- New docs/hosting.md: explains why clean URLs 404 on plain static
hosts and documents the fix per environment (mdcms serve, service
worker, GitHub Pages 404.html, Netlify/Cloudflare _redirects, nginx,
Apache, Caddy), plus a standalone Python preview script.
- Update stale boot error message in index.html to point at
`mdcms serve` instead of the removed "option 8".
- Update CLAUDE.md command table and local-preview note.
https://claude.ai/code/session_018KXUwmSNMGF2UBywTChCcS
Renderer (app/index.html):
- navigateTo now pushes a history entry for user navigations (pushState),
while the initial load, back/forward (popstate/hashchange), and category
re-renders still replaceState. The browser Back button now returns to the
previous page instead of leaving the site.
Service worker (mdcms.py generator + app/service-worker.js):
- Serve the cached index.html app shell for navigation requests. Reloading a
clean URL like /section-id previously 404'd on the static host before any
JavaScript ran; the shell fallback lets the client-side router resolve the
path. Also makes pretty-URL reloads work offline.
https://claude.ai/code/session_018KXUwmSNMGF2UBywTChCcS
Renderer (app/index.html):
- Guard the router so navigateTo and the hashchange/popstate handlers only
load relative .md paths (isSafePagePath). Blocks fetching attacker-
controlled external URLs injected via the location hash.
- Stop treating in-page heading anchors (#heading) as page files, which
previously replaced the page with a 404.
- HTML-escape meta.title, link href/title attributes, not-found/offline
messages, and the icon fallback img; block javascript:/data: hrefs via
safeUrl.
- Hydrate mdcms tags nested inside tabs/accordions/callouts.
- Configure marked once instead of on every render.
- Validate stored theme value; fix text-align center; resolve per-category
offline message after categories initialise.
CLI (mdcms.py):
- Restore CDN_DEPS, _WOFF2_URL_RE, _fetch_bunny_fonts, _patch_index_html so
fetch-deps no longer raises NameError.
- Compare site markers against SITE_FORMAT_VERSION with zero-padded version
comparison, removing the spurious "update available" warning on v0.6 sites.
- Bump CLI to 0.6.1.
https://claude.ai/code/session_018KXUwmSNMGF2UBywTChCcS
mdcms build now calls generate_site_manifest() at the end of every build,
writing mdcms.json to the site root. This file lists all deployable files
and empty directories, and is deployed alongside the site so any mdcms
user can register a copy of the site from its URL.
mdcms register now accepts a GitHub repo URL or plain HTTPS URL as PATH
or via --from. GitHub URLs try mdcms.json (raw content) first and fall
back to the Contents API tree-walk. Plain URLs require mdcms.json to be
present and fail with a clear error if it is not found.
- generate_site_manifest() added; called at end of run_build
- download_template(dest, source=None) dispatches on source type
- _parse_github_url() extracts owner/repo/branch/subpath from GitHub URLs
- _fetch_manifest() / _apply_manifest() handle the manifest protocol
- _download_tree_api() retained as GitHub Contents API fallback
- _http_get_github() carries Accept header for Contents API responses
- MANIFEST_FILENAME = "mdcms.json"; GITHUB_URL_RE added
- app/template-manifest.json replaced by app/mdcms.json
- register command: PATH accepts URL; --from option added
https://claude.ai/code/session_01Ai8xRvmrzdhuTKiRQ2fnn9
Replace the GitHub Contents API tree-walk with a flat manifest approach.
template-manifest.json lists every file and empty directory in the starter
template; download_template() fetches the manifest then pulls each file
directly as a raw URL, sidestepping git API rate limits and making the
template hostable from any HTTP source.
- GITHUB_CONTENTS_API / _github_get / _download_tree removed
- TEMPLATE_BASE_URL + TEMPLATE_MANIFEST constants added
- _http_get() replaces _github_get() (generic, no GitHub headers)
- download_template() accepts optional base_url for custom sources
- app/template-manifest.json added (v0.4, 35 files, 2 empty dirs)
- Generated files (manifest.json, service-worker.js, search.json)
excluded from manifest — they belong to mdcms build output, not
the starter template
https://claude.ai/code/session_01Ai8xRvmrzdhuTKiRQ2fnn9
Pages whose filename matches a nav section-id now get a clean pathname
URL (e.g. /timesheet) instead of the hash-based /#pages/timesheet.md.
- _initialPathname captured at IIFE start; handles ?_route= from 404.html
- basePath determined by initBasePath() after nav data loads; subpath
deployments (e.g. /mysite/) handled automatically
- navigateTo() uses replaceState to /slug for section-id pages and falls
back to #hash for everything else
- popstate listener handles browser history if a clean URL was the entry
- resolveSlugToFile() validates that slug is both a section code and has
a pages/{slug}.md entry in navData
- app/404.html added for GitHub Pages SPA routing
https://claude.ai/code/session_01Ai8xRvmrzdhuTKiRQ2fnn9
Implements four new mdcms fenced-block types:
tab-underline / tab, tab-filled,
accordion-underline / accordion, accordion-filled
Each block reads items: from a YAML body. Tab state and accordion
open/close are managed with aria-selected / aria-expanded and
data-open attributes. Markdown content inside each item is rendered
with the same pipeline as the surrounding page.
Adds computeDerivedTokens() — called on every applyTheme() — which
computes --mdcms-bar, --mdcms-filled-bg/border/fg, --mdcms-strip-border
from the active palette. Uses HSL chroma (S × (1-|2L-1|)) instead of raw
HSL S for the bold-nav heuristic, avoiding a false-positive on near-white
nav colours like the default #F8FAFC.
Adds app/pages/tabs-accordions.md as a visual test page and
docs/unreleased.md to track this change ahead of the next release.
https://claude.ai/code/session_01SFMh7PDxJjvvo5dYbCCFFs
fetchPageFile now rejects text/html responses so servers with SPA routing
(e.g. Cloudflare Pages with /* /index.html 200) no longer trick the renderer
into treating a fallback index.html as a found markdown file. Category-variant
pages (page.current.md with no plain page.md) now fall through correctly to
their variant URL.
mdcms build now writes a self-unregistering service-worker.js when pwa: no,
evicting any stale caching worker left over from a previous pwa: yes build.
manifest.json is also removed when pwa: no.
https://claude.ai/code/session_01Xs5GyREFhjWxhS1UhW2wA8
A YAML parse error in config.yml (e.g. a stray tab character) caused
read_config to swallow the exception and return {}, disabling categories
and producing a broken nav.yml with no variants fields and wrong filenames.
read_config now raises ClickException on both OSError and YAMLError.
Documented in docs/knownbugs.md and docs/unreleased.md.
https://claude.ai/code/session_01Xs5GyREFhjWxhS1UhW2wA8
mdcms build now writes the sitename from config.yml into the <title> tag
of index.html. WhatsApp, Slack, and other link-preview crawlers read the
static HTML without executing JavaScript, so the title must be correct in
the raw file. Previously it was blank (or "MD-CMS" in older templates).
Pages without a category suffix still map to the default category.
Only posts/ files without a suffix get uncategorized: true in nav.yml
and category: null in search.json.
https://claude.ai/code/session_01EzU13EL8D5Ud2ngQUKDj9e