# Accent CMS URL: / Accent CMS renders your markdown as a fast, clean website -- served live with hot reload while you write, or built to plain static files you can host anywhere. One binary, no database, no Node toolchain. --- # Byte for byte: how we test a template engine upgrade URL: /blog/testing-a-template-engine-upgrade Date: 2026-10-10 Tags: engineering, templating, testing Every Accent theme is a set of [MiniJinja](https://github.com/mitsuhiko/minijinja) templates, and every page you serve or build passes through that engine. MiniJinja 3.0 shipped on 8 October 2026. It is a breaking release that brings the engine closer to Python's Jinja2, and it changes what some templates render. For an upgrade like this, "it compiles and the tests pass" is not good enough. The engine decides what your pages say. So before deciding whether to move, we asked one question at seven levels, from the cheapest to the most expensive: *what actually changes?* ## Rule one: measure the noise first Every comparison ran against itself before it ran against the other version. - Building the same site twice with the same binary: 0 files differed. - Fetching the same page twice from the same server: only the CSP nonce differed, which is new on every response by design. - Taking the same screenshot twice: 2 pixels differed on one page. - Running the same benchmark twice: results drifted by up to 2.8 percent. Without that floor, a difference between two versions is a rumour. With it, it is a finding. You will see below why the floor also has to include a process restart. ![The seven steps of the evaluation, cheapest first: compile, 67 errors, all mechanical; probe the changelog, 3 behaviour changes the compiler missed; contract tests, 11 of 100 failed as designed; full test suite, 30 failures and 12 left after the fixes; diff the built site, 0 of 3,174 files differ; diff what serve renders, 0 changed pixels beyond the noise floor; benchmark A/B/A, 7.5 to 24.7 percent faster.](/media/blog/minijinja-3/method.svg) ## 1. Let the compiler talk We changed one line, the MiniJinja version, on a throwaway branch and compiled. 67 errors came back from 47 call sites, in two patterns: a removed conversion function, and a macro that now takes ownership of its values. The fix was about 100 changed lines, all mechanical. The compiler is the cheapest reviewer, and it was the least informative. Everything it found was easy. The interesting changes were the ones it could not see. ## 2. Probe what compiles but changes A changelog is a list of hypotheses. For each entry that could touch Accent, we wrote a few lines that ran the same input on both versions. Three of them changed behaviour without a compile error: - **Building a map from key-value pairs.** On 2.23 it produced a map; on 3.0 it produces a list of pairs. Accent did this when it handed nested frontmatter to your templates, so `{{ page.custom.hero.title }}` would have rendered as an empty string, with no error anywhere. - **Keyword arguments to filters that do not take them.** `{{ site.current_language | default(value=site.language) }}` is how [Tera](https://keats.github.io/tera/), and therefore Zola, writes a fallback. MiniJinja 2.23 accepted it quietly, and printed the literal `{"value": "en"}` whenever the fallback actually fired. 3.0 rejects it. Two of our own development themes contained exactly this line, and 42 of that site's 44 pages failed to render. - **Key order.** Objects that Accent converts for templates now list their fields in declaration order instead of alphabetically, which matters only to a template that loops over an object's keys. Probes tell you what *could* break. The next levels tell you whether it does. ## 3. Contract tests: tests that are supposed to fail Accent keeps 100 tests that pin the exact output of MiniJinja's built-in filters, tests, and functions. They exist for this moment. On 3.0, 11 of them failed, all for the same reason: printed lists and maps now use Python's quoting, and pairs are tuples. | Template | 2.23 | 3.0 | |----------|------|-----| | `{{ "a,b,c"\|split(",")\|list }}` | `["a", "b", "c"]` | `['a', 'b', 'c']` | | `{{ {"a": 1}\|items\|list }}` | `[["a", 1]]` | `[('a', 1)]` | | `{{ dict(a=1, b=2) }}` | `{"a": 1, "b": 2}` | `{'a': 1, 'b': 2}` | That is the contract tests doing their job: they turn a one-line release note into an exact list of what your templates would print differently. ## 4. The full test suite The whole suite, 6,562 tests, failed 30 on the first run: 18 from the map change the probes predicted, the 11 contract tests, and 1 test that pins the alphabetical key order. With the map fix in, 12 remained, and every one of them is either an expected new output or a decision we still have to make. ## 5. Render everything and diff the bytes Tests check what someone thought to test. A build diff checks everything. We built the documentation site, 729 pages and 3,174 files, with both versions and compared the output byte for byte: no file differed. The starter site was identical too, and so was the development site once its `default(value=...)` line was fixed. ## 6. What a build cannot see The admin UI is rendered only by `accent serve`, never by a build. It is also where most of the upgrade's code changes landed. So we ran both versions side by side, signed in to each, and captured 15 pages: every admin screen and a sample of public pages, as raw HTML and as full-page screenshots. ![The Accent page editor rendered with MiniJinja 2.23 at the top and 3.0 at the bottom. The two screenshots are identical.](/media/blog/minijinja-3/editor-side-by-side.webp) The HTML was identical once the per-response CSP nonce was set aside. Of the 16 screenshots, 29.8 million pixels in all, 14 were identical, and one page differed by 2 pixels, the same 2 it differs by from itself. Then one page differed for real. ![Two pixel-diff heatmaps. Top: the page editor, all grey, 0 pixels differ. Bottom: the schema browser, where the cells of two table rows are red because the rows swapped places.](/media/blog/minijinja-3/pixel-diffs.webp) The schema browser listed its two content models in a different order on the two servers: 2,366 pixels of difference. It would be easy to blame the new engine. Instead, we restarted each version ten times and looked again. Both versions listed the models in both orders. The cause was ours: the models live in a hash map whose order changes with every process, and the list was never sorted. That is a small, unrelated bug, and exactly the kind of difference a single before-and-after comparison pins on the upgrade. The noise floor has to include a restart, not only a repeated request. ## 7. Speed, with a drift check A benchmark run once on each version shows a difference, not its cause. We ran Accent's template benchmark three times in sequence: 2.23, then 3.0, then 2.23 again. The rerun tells you how much the machine drifts on its own. ![Bar chart of template render time saved on MiniJinja 3.0: leaf page 24.7 percent, listing of 10 pages 23.5 percent, listing of 100 pages 14.2 percent, listing of 1,000 pages 11.3 percent, docs sidebar with 210 nodes 7.5 percent. A grey band marks the 2.8 percent drift of the 2.23 rerun.](/media/blog/minijinja-3/template-bench.svg) | Case | 2.23 | 2.23 rerun | 3.0 | Change | |------|-----:|-----------:|----:|-------:| | Leaf page | 6.55 µs | 6.56 µs | 4.90 µs | -24.7% | | Listing, 10 pages | 8.40 µs | 8.34 µs | 6.42 µs | -23.5% | | Listing, 100 pages | 32.6 µs | 32.2 µs | 28.0 µs | -14.2% | | Listing, 1,000 pages | 277 µs | 270 µs | 246 µs | -11.3% | | Docs sidebar, 210 nodes | 153 µs | 153 µs | 141 µs | -7.5% | 3.0 renders every case 7.5 to 24.7 percent faster, and the rerun moved by at most 2.8 percent, so the gain is real. The smallest pages gain the most, which suggests the cheaper part is turning Accent's page data into template values rather than rendering itself. ## What this means for your themes Accent's own sites and admin UI render the same on both versions. Your theme might not, because Accent cannot test templates it has never seen. Check yours for these: | If your template uses | On 2.23 | On 3.0 | |-----------------------|---------|--------| | `"don't stop"\|title` | `Don'T Stop` | `Don't Stop` | | `2.5\|round` | `3.0` | `2.0` (rounds half to even) | | `7 // -2` and `7 % -2` | `-3` and `1` | `-4` and `-1` | | `1 / 0` | `inf` | An error | | `true is number` | `False` | `True` | | A printed list or map, `{{ items }}` | Double quotes | Single quotes, tuples for pairs | | `default(value=x)` | Works by accident | An error; write `default(x)` | | `-page.weight` | An error | Works | Most themes use none of these. If yours does, the fix is usually one line, and the `default(value=...)` one is worth making today: it is wrong on the current version too. The upgrade is in review. When it ships, the release notes will carry this list, and part of the work is better error messages in development: instead of `undefined value`, a template error will name what was missing, as in ``undefined value: `page.hero` is undefined (from line 2)``. ## Use the method on your own upgrades None of this is specific to MiniJinja. For any dependency whose output you ship: 1. **Measure the noise floor first**, including a process restart. 2. **Read the changelog as hypotheses** and probe each one with the same input on both versions. 3. **Keep contract tests** that pin the dependency's output, so a release note becomes an exact list of changes. 4. **Diff the built output byte for byte**, not a sample. 5. **Diff what the build cannot see**: server-rendered pages and screenshots. 6. **Benchmark A/B/A**, so you know the drift before you believe the delta. The compiler found 67 problems, and every one was easy to fix. The probes, the diffs, and a restart found the ones that would have reached your pages. --- # Spec-driven development with AI agents URL: /blog/spec-driven-development-with-ai-agents Date: 2026-08-01 Tags: ai-workflows, spec-driven-development The interesting problem with AI-assisted development is not getting code written. Models are good at that now. The problems are that an agent starts every session knowing nothing about the decisions you made last week, and that when it does not know something it will frequently produce a confident, well-formed, entirely wrong answer instead of stopping. Both problems have the same shape: there is no durable, checkable record of what is true about the project. ## Where this sits That diagnosis is no longer contrarian. Through 2026 it became the mainstream reaction to vibe coding, under the name **spec-driven development**. GitHub's [Spec Kit](https://github.com/github/spec-kit) is an open-source toolkit that runs an agent through four phases -- Specify, Plan, Tasks, Implement -- and its [announcement](https://github.blog/ai-and-ml/generative-ai/spec-driven-development-with-ai-get-started-with-a-new-open-source-toolkit/) puts the thesis plainly: "We're moving from 'code is the source of truth' to 'intent is the source of truth.'" AWS shipped [Kiro](https://kiro.dev/), an agentic IDE built on the same premise, where the spec is the source of truth and code is a build artifact. [Tessl](https://tessl.io/blog/tessl-launches-spec-driven-framework-and-registry/) ships a framework plus a registry of more than ten thousand pre-built library specs, explicitly aimed at stopping agents from hallucinating APIs. So the general idea is settled. What is still open is how *rigorous* the artifact has to be, and the honest answer in most write-ups is "a requirements document and some acceptance criteria." This post is about a project that goes considerably further, because the difference turns out to matter. ## The project `yqr` is a small Rust CLI we develop alongside Accent -- a jq-style query and edit tool for YAML, [open source](https://github.com/zoosky/yqr) and [on crates.io](https://crates.io/crates/yqr) at 0.5.0, so `cargo install yqr` works. It is not a large program. It carries **27 specification documents**, and those are not a side artifact: they are the issue tracker, the design record, and the standard the work is held to. The whole set is [published as part of the project's own website](https://zoosky.github.io/yqr/specs/), which is itself built with Accent. ## Five kinds of document, each with one job There is no issue tracker. No Jira, no scratch to-do files, no in-chat task lists. Every unit of work is a markdown file in one of five directories, each carrying a `Status:` field -- Draft, In Progress, Done, Resolved, Superseded, Historical. - **Research** asks a question and answers it with evidence. One evaluation of a candidate YAML library opens with a `Question` section and then a `Method` section beginning: "Three independent, cross-checked lines of evidence." - **Architecture** records a decision, what it forecloses, and what it explicitly is not. - **Features** run Problem, Prior art, Design, Acceptance criteria. - **Bugs** run Summary, Impact, Reproduction, Observed behavior, Root cause, Remediation options, Acceptance criteria. - **Implementation** holds cross-cutting conventions and is the declared source of truth for them. The separation matters more than the exact taxonomy. A research note that concludes nothing is fine; an architecture document that concludes nothing is a failure. Giving each kind of thinking its own container prevents the fuzzy document that is half investigation, half decision, and binding as neither. ## The part that does the real work: a falsifiable invariant The architecture document makes one product promise -- the tool never rewrites bytes it did not change, so comments, key order, quoting, and blank lines survive an edit. On its own that sentence would be worth very little. What makes it useful is the next section, which reduces the promise to a single property you can run: ``` yqr '.' input.yaml == cat input.yaml # byte-for-byte identical ``` and then the line that gives it teeth: > If `yqr .` does not reproduce its input byte-for-byte, the build is broken. That is the methodology in miniature. The prose promise is not the contract; the executable property is the contract, and the prose exists to explain it. Everything downstream inherits it. The bug that opened the work measured the tool against that property across a 14-file corpus and found **13 of 14 failed**. The features that followed existed to move that number. The `validate` subcommand shipped later re-asserts the same invariant at runtime. One invariant, stated once, doing work in four documents and in CI. This is where the project departs from most of the tooling. Spec Kit, Kiro, and Tessl all give an agent structured requirements and acceptance criteria; what they do not generally produce is a *single load-bearing property that fails the build*. The distinction is between criteria an agent can argue it satisfied, and a command whose output either matches or does not. That difference is the whole reason to prefer the harder version. A model asked to "preserve formatting" produces something plausible and you end up reviewing prose. A model asked to satisfy `yqr '.' f` equals `cat f` over a corpus has a test it cannot talk its way past, and so do you. ## Claims carry citations The bug report that started the fidelity work is the most instructive document in the set. It argues that the library then in use could not possibly meet the guarantee -- not that it did not, but that it *could not*, because the information is destroyed before the emitter ever runs. An argument like that is exactly where a fluent model confabulates. This one closes with a 24-row citation index mapping every claim to a specific `file:line` in the dependency's source, and a note on method: > Method: root cause was produced by reading the source across the > load/compose/emit layers; every claim above was independently re-verified > against the source (0 of 64 claims refuted). Sixty-four claims, each re-checked, with the refutation count reported -- including the number that would have been embarrassing had it been non-zero. This turns out to be more than good manners. A cross-model study published in June 2026, [*Citation Discipline in Spec-Driven Development*](https://arxiv.org/abs/2606.30689) (Subham Panda), tested a framework requiring per-line requirement citations against an uncited control. Because a cited requirement that does not exist in the specification is mechanically detectable, the cited condition caught hallucinated requirements at an 86.4 percent true-detection rate on Claude Sonnet 4.6 and 88.0 percent on GLM-5-turbo, with no false positives. The uncited condition detected **zero**. The paper also reports the trade-off honestly: uncited generation was more deterministic; citations bought verifiability, not consistency. Which is the point. A claim without a reference is not checkable by anything, human or machine. A claim with one is a hypothesis you can refute in seconds. The habit shows up inline too. Assertions are annotated where they were verified -- that a position index is a byte offset rather than a character offset, for instance, is marked as verified against the function that advances it. And when a later investigation contradicted the architecture document, it did not quietly diverge: it added a corrections section listing four specifics the source review had disproved, flagged to be folded back. Specs correcting other specs, in writing, is a property worth engineering for. It is the mechanism by which a body of documents converges on being true rather than drifting into being decorative. ## The false pass One detail is worth the whole corpus. The test set included a file with a UTF-8 byte-order mark, and it was the only one of the fourteen whose output was byte-identical to its input. By the headline metric, it passed. It was corrupt. There was no BOM handling in the library at all, so the mark was being absorbed into the first key -- the key became something other than `a`, and `.a` returned null. The bytes round-tripped because the file had been misparsed in a way that happened to be stable for the identity filter. The spec records this under a heading that says so plainly: the case is corruption, not preservation. An agent optimising for a green metric ships that. So does a tired human. It was caught because the document format demanded an explanation of each result rather than a tally, and "this one passes for a reason I cannot state" does not survive that format. If you take one habit from this post: **make the artifact require a mechanism, not just an outcome.** A count of passing checks is not evidence. It is a number that happens to be green. ## Superseded, never deleted Of twelve feature specs, three are marked Superseded. Two were made obsolete by consolidating on a single YAML engine; one was replaced when an opt-in flag became default behavior. None were deleted. They sit in the tracker with their status and a pointer to what replaced them, and the epic summary narrates the sequence -- byte fidelity first shipped behind a flag, then became the default while the old pipeline moved behind a different flag, then the engine-selection flag was removed entirely. That narration is the part an agent cannot reconstruct and will otherwise guess at. A fresh session reading only current code sees one engine and no flags, with no way to tell whether that is a considered endpoint or an accident. Reading the tracker, it knows the shape of the road. The same instinct appears in the architecture document, about a deliberate breaking change: > Breaking the v0.1.1 contract is intended. Current `yqr .` reformats; under > this decision it must round-trip. This is a deliberate, pre-1.0 correction, > captured here so the change is a decision and not a regression. "Captured here so the change is a decision and not a regression" is the entire argument for writing specs, in one clause. ## Prior art as a required section Feature specs carry a `Prior art` section before the design. The one for the validation subcommand surveys how jq handles it (no such command; the idiom is `jq empty`), that one YAML tool shipped a validate command and later dropped it, what the dedicated linter in the space catches that parsers do not, and the long-standing precedent of `xmllint --noout`. Only then does it decide, and the decision explicitly names what it replaces. Models are excellent at this section and reliably skip it unless asked. They have read the ecosystem. Requiring the survey converts latent knowledge into a written comparison you can disagree with, and heads off the characteristic failure of agent-designed features: a locally sensible interface that ignores the convention every neighbouring tool already established. ## Why this shape suits agents in particular Most of the above is good engineering discipline that would pay off with an all-human team. Four parts pay off specifically because of how models fail. **Specs are memory.** An agent's context does not survive the session; a file does. This is not a hypothetical weakness -- [*Spec Kit Agents*](https://arxiv.org/abs/2604.05278) (Taghavi and Bhavani, April 2026) identifies agents going "context blind" in large, evolving repositories, producing hallucinated APIs and architectural violations, and proposes hooks that ground each phase in actual repository evidence. Written specs are the low-tech version of the same idea: grounding that persists. The rule that a spec's status must be updated in the same change that advances it is what keeps that memory honest, and it is enforced before a pull request rather than during review. **Falsifiability beats fluency.** The characteristic model failure is not bad code, it is a convincing account of code. Byte-equality over a corpus does not care how convincing anything is. **Navigability is a design constraint.** The project caps source files at around 500 lines, splitting them into directory modules past that, with the stated reason that it "keeps files agent-navigable." Whatever you make of the number, treating "can a reader with a limited window find its way" as an architectural requirement is genuinely new, and it is downstream of the tooling. **Admitting is a rule.** Two standing rules say, in effect: if a URL or a file cannot be fetched or read, stop and say so, and never reconstruct its contents from inference -- never fabricate a version number, an API shape, or a changelog entry. This is written down because it is the most damaging thing a capable model does, and because it does it while sounding completely reliable. A visible blocker is worth more than a plausible guess. ## What it costs The honest accounting: this is heavy. Twenty-seven documents for a pre-1.0 CLI is a lot of prose, and a real fraction of the effort goes into writing and maintaining it rather than shipping features. The bug report discussed above is longer than the module it indicts. It earns that on work with three properties. The problem must be subtle enough that the wrong solution looks right -- byte fidelity qualifies, because a semantic round-trip looks fine until you diff it. The work must span more sessions than fit in one context. And correctness must be checkable by machine, because a spec whose acceptance criteria cannot be run is just a longer opinion. Where those do not hold -- exploratory work, a spike, anything you intend to throw away -- this apparatus is friction with no return. The failure mode of spec-driven development is spec-driven procrastination, and it is real. ## The smallest version worth trying If you want the benefit without adopting the whole structure, three habits carry most of it: 1. **Write the falsifiable property first.** Before the design, before the code: what command, run against what input, proves this works? If you cannot answer, you do not yet understand the feature, and neither will an agent. 2. **Make claims cite their evidence.** A `file:line`, a command with its output, a fetched URL. Anything asserted without one is a hypothesis and should be labelled as such -- and, per the research above, it is the only version a machine can check for you. 3. **Never delete a decision.** Mark it superseded and point at what replaced it. The cost is a stale file; the benefit is that nobody re-litigates a settled question at 2am, including the agent. None of this requires particular tooling. It is markdown in a directory, next to the code, in the same commits. If you would rather read a working example than a description of one, the specs are [public](https://zoosky.github.io/yqr/specs/). ## Next: the specs as a site There is a second half to this, and it is the reason those specs are readable in a browser at all rather than being 27 files you have to clone a repository to see. A spec corpus is only durable memory if it can be *found*. Twenty-seven documents that cross-reference each other, supersede each other, and carry status fields are a small hypertext, and a directory listing is a poor way to navigate one. What that corpus wants is what any documentation wants: an index, working cross-links, full-text search, and a build that fails when a reference goes stale -- which is exactly the check that keeps a superseded pointer from rotting into a dead end. The `yqr` site is built with Accent for that reason. The specs on it are not a copy or an export: they are the same markdown files the repository holds, mounted as a section of the site and published on every push, so the record an agent reads and the record a human browses cannot drift apart. The next post in this series works through how that is set up -- content mounted from a directory the project already maintains, cross-references that break the build when they dangle, search over the corpus, and deployment from CI -- using `yqr` as the worked example throughout. ## Sources - GitHub, [*Spec-driven development with AI: Get started with a new open source toolkit*](https://github.blog/ai-and-ml/generative-ai/spec-driven-development-with-ai-get-started-with-a-new-open-source-toolkit/), and the [Spec Kit toolkit](https://github.com/github/spec-kit). - AWS, [Kiro](https://kiro.dev/) -- agentic IDE built on spec-driven development. - Tessl, [*Tessl launches spec-driven development tools for reliable AI coding agents*](https://tessl.io/blog/tessl-launches-spec-driven-framework-and-registry/). - Subham Panda, [*Citation Discipline in Spec-Driven Development: A Cross-Model Empirical Study of Output Determinism and Automated Hallucination Detection in LLM-Generated Code*](https://arxiv.org/abs/2606.30689), June 2026. - Pardis Taghavi and Santosh Bhavani, [*Spec Kit Agents: Context-Grounded Agentic Workflows*](https://arxiv.org/abs/2604.05278), April 2026. - The [`yqr` specification corpus](https://zoosky.github.io/yqr/specs/), [repository](https://github.com/zoosky/yqr), and [crate](https://crates.io/crates/yqr). --- # Accent as a static site generator URL: /blog/accent-static-site-generator Date: 2026-07-31 Tags: static-site-generator, getting-started Most of what you read about Accent CMS talks about *serving* -- the live server, hot reload, the admin UI. But the same binary is also a static site generator, and for a lot of sites that is all you need. This post is an introduction to that side of Accent: what it does, why it exists, and how to go from a directory of Markdown to a deployable site with a single command. ## What "static" means here A static site generator takes your content -- Markdown files, some templates, a few images -- and renders it *ahead of time* into plain HTML, CSS, and assets. There is no application running when a visitor arrives; their browser just downloads files. That is why static sites are fast, cheap to host, and hard to break: there is no server to crash, no database to migrate, and no runtime to patch. Accent produces exactly this kind of output. You run: ```bash accent build --output ./public ``` and Accent walks your content directory, renders every page through your theme, processes your images, generates a sitemap and RSS feed, builds a client-side search index, and writes the whole thing to `./public`. Upload that folder to any static host -- object storage, a CDN, GitHub Pages, Netlify, your own nginx -- and you are live. ## One binary, no toolchain The thing that sets Accent apart from most generators is that it is a **single binary written in Rust**. There is no Node install, no Ruby, no Python virtualenv, no plugin ecosystem to `npm install` before your first build. You download one executable and you have the whole system: the Markdown renderer, the Jinja template engine, image processing, syntax highlighting, diagram rendering, and search are all compiled in. That means a build is reproducible and self-contained. The binary you run locally is the binary your CI runs, and it has no hidden dependencies that drift between machines. ## The same content, served or built Accent's build and serve modes render through the **same code path**, so what you preview locally with `accent serve` is what `accent build` writes to disk. You develop against a live server with hot reload: ```bash accent serve --config config.yaml ``` Save a Markdown file or edit a template, and the browser refreshes itself. When the site looks right, you build it. There is no separate "production renderer" to surprise you -- serve-vs-build parity is a first-class concern in Accent, tested explicitly. ## What you get in the output A build is more than raw HTML. From the same content, `accent build` emits: - **Rendered pages** with your Jinja theme applied, breadcrumbs and navigation resolved from the content hierarchy. - **Processed, resized images** in modern formats, generated once at build time and cached. - **A sitemap and RSS feed** so search engines and readers can follow along. - **A client-side search index** -- search works on a purely static host, with no search server behind it. - **Diagrams and syntax-highlighted code**, rendered at build time so no JavaScript is required to display them. This very website is built with `accent build` and deployed as static files. ## Deploying under a path prefix If your site does not live at the root of a domain -- a GitHub Pages project site at `user.github.io/repo`, say, or a reverse-proxy mount -- Accent can rewrite every internal URL to sit under a prefix: ```bash accent build --base-path /repo --output ./public ``` Page links, theme assets, media, pagination, search, and feeds all move under the prefix together, so nothing 404s once deployed. ## Catching broken links before you ship Static sites fail silently: a mistyped link becomes a 404 that no one sees until a visitor does. Accent can fail the build instead: ```bash accent build --strict-links --output ./public ``` With `--strict-links`, Accent resolves every internal link in the build output and stops the build if one is broken. A bad link becomes a red CI check, not a shipped 404. ## Getting the binary There is one download per platform, and it contains the full feature set. Your license key decides which tier is unlocked at runtime, and neither building a site nor running the development server needs a key at all -- so everything in this post works with an unlicensed binary. On Linux and macOS the install script picks the right archive for your platform, checks the SHA-256 checksum (and the GPG signature when `gpg` is present), and installs `accent` into `~/.local/bin` -- no root, no package manager: ```bash curl -fsSL https://raw.githubusercontent.com/AccentCMS/accent/main/install.sh | sh ``` On Windows, the PowerShell equivalent: ```powershell irm https://raw.githubusercontent.com/AccentCMS/accent/main/install.ps1 | iex ``` To pin a version rather than take the latest, pass it through to the script. Note the `-s --`: a flag placed after the URL is eaten by `curl`, which does not understand it, and never reaches `sh`. ```bash curl -fsSL https://raw.githubusercontent.com/AccentCMS/accent/main/install.sh | sh -s -- --version v0.23.1 ``` ### Downloading from the releases page If you would rather not pipe a script into a shell -- a reasonable instinct, and often the policy on a build server -- take the archive yourself from the [releases page](https://github.com/AccentCMS/accent/releases/latest): | Platform | Archive | |---|---| | Linux x86_64 | `accent--x86_64-unknown-linux-gnu.tar.gz` | | Linux ARM64 | `accent--aarch64-unknown-linux-gnu.tar.gz` | | macOS Intel | `accent--x86_64-apple-darwin.tar.gz` | | macOS Apple Silicon | `accent--aarch64-apple-darwin.tar.gz` | | Windows x86_64 | `accent--x86_64-pc-windows-msvc.zip` | | Windows ARM64 | `accent--aarch64-pc-windows-msvc.zip` | Unpack it and put `accent` somewhere on your `PATH`: ```bash VERSION=v0.23.1 BASE=https://github.com/AccentCMS/accent/releases/download/$VERSION curl -fsSLO $BASE/accent-$VERSION-x86_64-unknown-linux-gnu.tar.gz tar xzf accent-$VERSION-x86_64-unknown-linux-gnu.tar.gz install -m755 accent ~/.local/bin/accent accent --version ``` Every release also ships `checksums-.txt` and a detached signature `checksums-.txt.asc` made with the Accent CMS release key, so you can verify the archive before you run it: ```bash curl -fsSLO https://raw.githubusercontent.com/AccentCMS/accent/main/release-signing-key.asc gpg --import release-signing-key.asc curl -fsSLO $BASE/checksums-$VERSION.txt curl -fsSLO $BASE/checksums-$VERSION.txt.asc gpg --verify checksums-$VERSION.txt.asc checksums-$VERSION.txt sha256sum -c --ignore-missing checksums-$VERSION.txt ``` ## Starting from scratch with `accent init` You do not need to assemble a project by hand. `accent init` scaffolds a complete, buildable site -- content, theme, and `config.yaml` -- so your first `accent build` produces real output: ```bash # Scaffold a starter site into ./my-site accent init my-site cd my-site # Preview it live, with hot reload accent serve # Build the static output accent build --output ./public ``` `accent init` ships more than one starting point. List what is available, then pick one: ```bash # See the bundled templates accent init --list # Scaffold the full documentation site instead of the simple starter accent init my-docs --docs ``` The simple starter is a good base for a blog or a small marketing site; the `--docs` template gives you the full documentation layout with navigation and search wired up. Either way you get a directory that builds on the first try -- no missing theme, no empty `config.yaml` to fill in before anything renders. ## Deploying to GitHub Pages GitHub Pages is the shortest path from a repository to a live static site, and Accent is built to deploy there cleanly -- including the awkward case that trips up most generators. GitHub Pages serves in one of two shapes, and the difference decides how you build: - **User or organization sites** (`username.github.io`) serve at the domain root. Nothing special is needed -- build as you would for any domain. - **Project sites** (`username.github.io/repo`) serve under a `/repo` sub-path. Every page link, theme asset, media file, and search fetch has to carry that prefix, or the deployed site 404s on its own assets. Accent handles the project-site case for you. The path component of `--base-url` sets the site's base path, so every internal URL Accent emits is prefixed to match -- no post-build rewriting step: ```bash accent build --output ./public --base-url https://username.github.io/repo ``` Before you push, preview the prefixed build exactly as it will be served. `accent serve-static` reads the resolved base path from the build, so no extra flags are needed: ```bash accent serve-static --dir ./public # Visit http://127.0.0.1:4403/repo ``` To automate the deploy, commit a GitHub Actions workflow that installs Accent, builds, and publishes the output: ```yaml # .github/workflows/pages.yml name: Deploy to GitHub Pages on: push: branches: [main] permissions: contents: read pages: write id-token: write # Let a running deploy finish rather than cancelling it half-published. concurrency: group: pages cancel-in-progress: false jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - name: Install Accent CMS run: | curl -fsSL https://raw.githubusercontent.com/AccentCMS/accent/main/install.sh | sh -s -- --version v0.23.1 echo "$HOME/.local/bin" >> "$GITHUB_PATH" - name: Build site run: accent build --output ./public --base-url https://username.github.io/repo --strict-links - name: Upload artifact uses: actions/upload-pages-artifact@v5 with: path: ./public deploy: needs: build runs-on: ubuntu-latest environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v5 ``` Pinning the Accent version with `--version` is deliberate: a CI job that silently follows the latest release is a build that can change under you without a commit. Bump it when you choose to. Then enable Pages in the repository under **Settings > Pages** and select **GitHub Actions** as the source. On the next push to `main`, the workflow builds the site and publishes it. Two Accent touches make this pipeline safe. The `--strict-links` flag above fails the build if any internal link is broken, so a bad link stops the deploy instead of shipping a 404. And whenever a base path is in effect -- as it is for a project site -- the build finishes with a conformance check over its own output: if any emitted page, the search index, or `llms.txt` still carries an unprefixed root-absolute URL, the build fails and names the offending files. You find out at build time, not when a visitor hits a broken asset on the sub-path. (Both guards are no-ops for a root deployment, where there is no prefix to get wrong.) One limitation worth knowing: GitHub Pages cannot set custom response headers, so the security headers Accent's server would send (CSP, frame options) are absent on Pages. For a content site that is usually fine; if you need those headers, host somewhere that supports them. ## Where to go next The whole loop -- scaffold, preview, build, deploy -- is **free, with no license key**. You pay to serve in production, not to build, so you can take a project from an empty directory all the way to a live GitHub Pages site without one: ```bash accent init my-site cd my-site accent build --output ./public --base-url https://username.github.io/repo --strict-links ``` We will go deeper on templates, content modeling, and other deployment targets in future posts. For now, the [documentation](https://accentcms.dev/docs) covers the full command set, and the [news page](https://accentcms.dev/news) tracks every release. --- # Blog URL: /blog Everything we write, in one place: how-to guides and deep dives alongside every release announcement. Subscribe via the [RSS feed](https://accentcms.dev/feed.xml), or follow the [GitHub releases](https://github.com/AccentCMS/accent/releases) for version news only. --- # Pricing: free to build, pay to serve URL: /pricing ## How licensing works **Free to build, pay to serve.** The full feature set -- plugins, media processing, client-side search, styling, every diagram language -- is free in `accent build` and the development server. You pay only to serve in production, to mount the admin UI, and for the Pro integrations. **One binary for everyone.** Every download contains everything. A license key -- an offline-verified, Ed25519-signed token -- unlocks your tier at runtime. No phone-home, no telemetry, no network calls for licensing. **Yearly means ownership.** A yearly license covers every release up to its version line, forever. When it lapses, everything you licensed keeps running -- you only renew to get newer releases. Monthly is a rental: full capability while active, falling back to the free tier if not renewed. **Evaluate for free.** Building sites and local development never need a license -- download and try everything. To evaluate production serving, a monthly license is the low-commitment path: full capability while active, no obligation beyond the month. Questions, team licenses, or anything else: [hello@accentcms.dev](mailto:hello@accentcms.dev). --- # About URL: /about Accent CMS is a high-performance markdown CMS built in Rust. It exists for people who want their content to be plain files under version control -- and still want a fast site, a comfortable authoring UI, and a modern publishing pipeline without assembling one from a dozen services. ## What it is One binary that does the whole job: a live server with hot reload for writing, a static site generator for publishing, an admin authoring UI, a WASM plugin runtime, on-demand image processing, client-side search, text-diagram rendering, and Jinja-style theming. Your content is markdown with YAML frontmatter; your site is a folder you can `git clone`. This website and the [documentation site](https://docs.accentcms.dev) are built and published with Accent CMS itself. ## Principles 1. **Files first.** Content is plain text you own, diff, and back up. No database, no export lock-in. 2. **One binary.** No runtime dependencies, no Node toolchain, no containers required. Download, run. 3. **Free to build, pay to serve.** The full feature set is free for building sites and local development. Licenses -- verified offline, never phoning home -- unlock production serving and the paid tiers. 4. **Sovereign by default.** Developed and operated from Switzerland, with infrastructure choices that keep the control plane under Swiss law. No telemetry, no tracking, no third-party calls from your site unless you add them. ## Technology Rust end to end: Tokio and Axum for the server, MiniJinja for templating, pulldown-cmark for markdown, wasmtime for the Component-Model plugin runtime. Static builds are byte-for-byte parity-tested against the live server's rendering. ## Company Accent CMS is made by AccentX GmbH, Switzerland. - Product and licensing: [hello@accentcms.dev](mailto:hello@accentcms.dev) - Security reports: [security@accentcms.dev](mailto:security@accentcms.dev) - Community: [GitHub Discussions](https://github.com/AccentCMS/accent/discussions) - Plugins, themes, and starter templates: [the Hub](https://hub.accentcms.dev) --- # Download URL: /download One binary per platform. Every download contains the full feature set; your [license key](/pricing) decides which tier is unlocked at runtime. Building a site and running the development server are free and need no key at all. ## Install **Linux and macOS** ```bash curl -fsSL https://accentcms.dev/install.sh | sh ``` **Windows (PowerShell)** ```powershell irm https://raw.githubusercontent.com/AccentCMS/accent/main/install.ps1 | iex ``` The script detects your platform, downloads the [latest release](https://github.com/AccentCMS/accent/releases/latest), verifies the checksum (and the GPG signature when `gpg` is available), and installs `accent` to your user directory -- no root required. ### System requirements The Linux glibc binaries are built against glibc 2.28 and run on any distribution that ships it or newer: Debian 10, Ubuntu 20.04, RHEL 8 (and AlmaLinux and Rocky Linux 8), Amazon Linux 2023, and everything after them. Alpine and other musl-based distributions are served by fully static musl binaries with no runtime requirements at all (v0.26.0 and later); the installer detects your libc and picks the right build. It runs the binary once before installing it and stops, naming the versions involved, if your system cannot run it. The macOS and Windows binaries have no additional runtime requirements. ## Manual download Grab the archive for your platform from the [releases page](https://github.com/AccentCMS/accent/releases/latest): | Platform | Archive | |---|---| | Linux x86_64 (glibc) | `accent--x86_64-unknown-linux-gnu.tar.gz` | | Linux ARM64 (glibc) | `accent--aarch64-unknown-linux-gnu.tar.gz` | | Linux x86_64 (musl, static -- Alpine) | `accent--x86_64-unknown-linux-musl.tar.gz` | | Linux ARM64 (musl, static -- Alpine) | `accent--aarch64-unknown-linux-musl.tar.gz` | | macOS Intel | `accent--x86_64-apple-darwin.tar.gz` | | macOS Apple Silicon | `accent--aarch64-apple-darwin.tar.gz` | | Windows x86_64 | `accent--x86_64-pc-windows-msvc.zip` | | Windows ARM64 | `accent--aarch64-pc-windows-msvc.zip` | ## Verify your download Every release ships a SHA-256 checksums file signed with the Accent CMS release key. Verification is three commands: ```bash # One-time: import the release signing key curl -fsSLO https://raw.githubusercontent.com/AccentCMS/accent/main/release-signing-key.asc gpg --import release-signing-key.asc # Verify the signature, then the archive gpg --verify checksums-.txt.asc checksums-.txt sha256sum -c --ignore-missing checksums-.txt ``` The full guide, including the embedded SBOM audit and publication attestation, is in the [documentation](https://docs.accentcms.dev/docs/v0.21/deployment/verifying-downloads). ## First steps ```bash accent init my-site cd my-site accent serve # http://127.0.0.1:4400, hot reload included accent build # static output in ./output ``` From here, the [Getting Started guide](https://docs.accentcms.dev) walks you through themes, templates, and publishing. --- # Accent CMS 0.27.5 — safer diagrams and licence command fixes URL: /news/2026/10/accent-cms-0-27-5 Date: 2026-10-24 Accent CMS 0.27.5 is available. It is a security and bug-fix release for the 0.27 line, and it does not contain the 0.28 development work. A licence with a 0.27 ceiling covers it. You do not have to change your site or theme. Diagrams that were cached before the upgrade are rendered again once, so the first build or page view after it can take a little longer. ## Security **Diagrams cannot carry script into a page.** A Mermaid `style`, `classDef` or `linkStyle` value with a quote in it could add an event handler to the rendered diagram, and a `click` link could use any scheme, `javascript:` included. If you publish pages from a remote mount, its raw HTML is escaped, but diagrams render after that step, so a third party could publish script through a `mermaid` block. The Mermaid renderer now refuses such values and links. Accent also filters every server-rendered diagram, from Mermaid, Svgbob or a plugin, before it reaches the page: only SVG drawing elements are kept, and event handlers, unsafe links and stylesheets that load from other hosts are removed. If you write a diagram plugin, emit plain SVG with `` labels: HTML inside the SVG, `` included, is removed. **Webhook signatures are compared in constant time again.** A dependency could turn the comparison behind git and CDN webhook signatures and preview tokens into code whose timing depends on the data. That timing is what an attempt to forge a signature measures. Accent now uses the fixed version. ## Fixes - **`accent license activate` fails when it refuses a key.** An empty or invalid key printed an error but exited with status 0, so a CI job that installs a key could not tell it failed. It now exits with status 1. `buy` and `renew` do the same when the key they receive is refused or never arrives. - **`accent license deactivate` removes any key in `config.yaml`.** An invalid or expired key was reported as "no licence key configured" and stayed in the file. When `ACCENTCMS_LICENSE` is set, the command now says the server keeps using that key. - **`accent license renew` and `refund` work outside a site directory**, with the key in `ACCENTCMS_LICENSE`, as `status` already did. - **`accent license refund` opens the right customer portal.** It opened an address that does not exist. - **The `accent license buy` edition prompt no longer chooses for you.** An empty or unknown answer selected Standard; the prompt now asks again, and a run without a terminal must pass `--edition`. - **Docs pages are described as technical articles.** Pages under `/docs/` with a date and an author were marked up as blog posts for search engines; they are now `TechArticle`. ## Install or upgrade ```bash curl -fsSL https://accentcms.dev/install.sh | sh ``` Binaries for all platforms, checksums and signatures are on the [GitHub release page](https://github.com/AccentCMS/accent/releases/tag/v0.27.5). --- # Accent CMS 0.27.4 — security fixes for plugins and TLS URL: /news/2026/10/accent-cms-0-27-4 Date: 2026-10-20 Accent CMS 0.27.4 is available. It is a security release for the 0.27 line, and it does not contain the 0.28 development work. A licence with a 0.27 ceiling covers it. You do not have to change your site, theme or plugins. The fixes start to operate when you install the new binary. ## Security **Plugins compile correctly on 64-bit ARM.** On Apple silicon, Linux on ARM and Windows on ARM, Accent could compile a plugin incorrectly. A plugin with a very large `br_table` instruction could jump to the wrong code. Compilers can make this instruction from a `match` or `switch` with many cases. wasmtime 48.0.7 fixes this. It also removes a way to make the server panic with a component-model stream. Only plugins run through wasmtime, so a site without plugins is not affected. If your site runs plugins that you did not write, upgrade now. **Outbound connections do not trust compromised certificates.** The private keys of some emSign intermediate certificates are now public. With these keys, an attacker can make certificates that impersonate other servers. Accent could accept these certificates on its outbound connections: when a plugin sends an HTTP request, when you install a plugin from the hub, and when Accent sends email. Now Accent refuses them. The HTTPS server in Accent is not affected. ## Install or upgrade ```bash curl -fsSL https://accentcms.dev/install.sh | sh ``` Binaries for all platforms, checksums and signatures are on the [GitHub release page](https://github.com/AccentCMS/accent/releases/tag/v0.27.4). --- # Accent CMS 0.27.3 — search, index and validation fixes URL: /news/2026/10/accent-cms-0-27-3 Date: 2026-10-19 Accent CMS 0.27.3 is available. It is a bug-fix release for the 0.27 line, and it does not contain the 0.28 development work. A licence with a 0.27 ceiling covers it. ## Do these steps after you upgrade Most fixes start to operate when you install the new binary. Four changes can need an action from you. **Search island.** The search island now contains the same form as `search_form()`. In your theme, put this line in ``: ```jinja {{ search_form("Search...", 5, script=false) }} ``` The island props `endpoint`, `min_chars` and `debounce` are removed. A form that you wrote yourself in the island does not search now. The results use the `.acms-search-*` classes. If your theme styles `.island-search-results` or `.search-hit`, change these styles to the new classes. **Tag templates.** If a configured taxonomy has no templates of its own, Accent uses the tag templates. A site that you made with `accent init` before 0.27.3 keeps its old tag templates. To show the correct terms, change `tags.html.jinja` and `tag.html.jinja`: - List `taxonomy.terms`, not `taxonomy.tags`. - Make the links with `term_url(taxonomy.name, term)`. - Compare terms with `taxonomy.current_term`. **Taxonomy names.** These names for a configured taxonomy now cause a configuration error: `name`, `singular`, `terms`, `pages`, `current_term`, `current_label` and `current_tag`. The singular `label` also causes the error. Rename the taxonomy, or its `singular`. **Diagrams turned off.** With `diagrams.enabled: false`, a `{% diagram %}` tag and a `diagram()` call now show the diagram source as a code block, as a fenced block does. To show diagrams, set `diagrams.enabled: true`. ## Fixes **Search shows results in the language of the page.** On a site with two or more languages, search showed pages of all languages. Now a search form and the search island show only the pages in the language of the current page. To translate the text for no results and the name of the results list, give `search_form()` the arguments `no_results` and `results_label`. The `{% search %}` tag takes attributes with the same names. To search all languages, use `all_languages=true`. **Redirect pages are not in the indexes.** A page with `redirect:` was in `sitemap.xml`, the search index, `llms.txt`, `llms-full.txt`, the feeds and the term pages. A translation with a redirect was also an `hreflang` alternate and an item in `page.translations`. Now Accent removes redirect pages from all these outputs. **Each taxonomy shows its own terms.** A configured taxonomy without its own templates showed the tags, for example on `/categories`. Its term pages linked to `/tags`. Now each taxonomy page shows the terms of its taxonomy. The new keys `taxonomy.name`, `taxonomy.singular`, `taxonomy.terms` and `taxonomy.current_term` give this data to your templates. **`accent validate` reports the line in the file.** The line number of a component error started after the frontmatter. Now it counts from the first line of the file. This is also true in a `---section:` region and in the admin validation report. **The dev server watches `static/`.** When you add or remove a file in `static/`, `accent serve` now checks the links again. **`accent serve` sends static files under a versioned root.** On a site with versions under `/docs`, the server redirected `/docs/a.pdf` to `/docs/v1/a.pdf`. Now it sends `static/docs/a.pdf`, as the build does. **`accent serve` sends the same script policy as your deployed site.** The server allowed WebAssembly when search was off, and the Mermaid CDN when diagrams were off. Now it allows each one only when you use the feature. ## Install or upgrade ```bash curl -fsSL https://accentcms.dev/install.sh | sh ``` Binaries for all platforms, checksums and signatures are on the [GitHub release page](https://github.com/AccentCMS/accent/releases/tag/v0.27.3). --- # Accent CMS 0.27.2 — markdown-only sites and six fixes URL: /news/2026/10/accent-cms-0-27-2 Date: 2026-10-18 Accent CMS 0.27.2 is available. It is a maintenance release for the 0.27 line, and it does not contain the 0.28 development work. A licence with a 0.27 ceiling covers it. A patch release usually contains only fixes. This release also contains two features. Read the changes below before you upgrade. ## Do these steps after you upgrade Most changes start to operate when you install the new binary. Three changes can need an action from you. **Tabs on a site from `accent init`.** From 0.26.0, the starter theme did not load its scripts. Because of this, `{% tabs %}` showed only the first panel. A new site from `accent init` is correct. A site that you made before 0.27.2 keeps its old template. To repair it: 1. Open `themes/simple/templates/base.html.jinja`. 2. Add these lines before ``: ```jinja {% for js in theme.assets.js %} {% endfor %} ``` **Diagrams in a section.** Accent now renders diagrams in a `---section:` region. If you set `diagrams.on_error: fail`, a diagram error in a section now stops `accent build`, as an error in the page body does. **Values for `--base-url`.** The value of `accent build --base-url` must be an `http://` or `https://` URL without a query or a fragment. Its path is the base path of the build, unless `--base-path` or `site.base_path` sets a path. If the URL has no path, the build is for the root. ## Fixes **`accent.exe` starts on all Windows computers.** The Windows binaries from 0.22.0 to 0.27.1 needed `VCRUNTIME140.dll`. This file is part of the Visual C++ Redistributable, not of Windows. On a computer without it, `accent` did not start. The binary now contains the C runtime. **Components link correctly under a base path.** In 0.27.0 and 0.27.1, the `button`, `card`, `image` and `tag` components of all shipped themes wrote the deployment prefix two times, for example `/site/site/blog`. Your own components that use `url()`, `media()`, `page.url` or `all_pages` had the same problem. Accent now adds the prefix one time. **`--base-url` sets all absolute URLs.** Before this release, the flag changed only the sitemap and `robots.txt`. Canonical links, `og:url`, `absolute_url()`, JSON-LD and the feeds kept the URL from the config. Now the flag sets `site.url` for the build, and all these URLs use it. **A `---section:` region renders like the page body.** Before this release, a section had no heading anchors, no `page.toc` entries, no highlighted code, no admonitions and no diagrams. Now Accent renders a section with all the markdown stages of the body. In `page.toc`, the section headings come after the body headings. **Markdown-only sites build without `--lax`.** The link check of a markdown-only build tried to find HTML files. Because of this, it stopped the build on its own redirects, and it did not find broken links in pages. Now the link check reads the `.md` file of each page. A broken link stops the build. Links in code are not checked. ## New: every page of a markdown-only site is markdown With `content.markdown_view.default_format: markdown`, all pages of the site are now markdown. This includes tag pages, category pages, the 404 page and the next pages of a listing, for example `blog/page/2.md`. Accent sends them as `text/markdown; charset=utf-8` in `accent serve`, `accent build` and `accent serve-static`. Each of these pages uses the `.md.jinja` version of its HTML template, for example `tags.md.jinja` or `error/404.md.jinja`. The default theme contains these templates. If your theme does not have them, Accent uses a built-in markdown list or message. If you set `build.header_artifacts`, Accent writes an `.htaccess` file and the Netlify `_redirects` and `_headers` files for this output. These files tell the host to do these steps: - Send each page URL to its `.md` file, for example `/docs/guide` to `docs/guide.md`. - Send `/` to `index.md`. - Send a missing page to `404.md`, with status 404. - Send `.md` files as `text/markdown; charset=utf-8`. - Send the redirects of the build with an HTTP redirect status. If you use a different host, for example Caddy or nginx, configure it to do the same steps. ## New: taxonomy terms keep their spelling Each term in `taxonomy.tags`, and in each configured taxonomy, now has a `label`. The label is the term as an author writes it, for example `Beratung aare` for the name `beratung aare`. Use `label` to show a term. Use `name` or `slug` to compare terms. On a term page, `taxonomy.current_label` contains the label. The page title uses it, so the title changes from `Tag: beratung aare` to `Tag: Beratung aare`. The shipped tag and category templates show the label. `page.taxonomies` gives the terms of the current page as a list for each configured taxonomy. `categories: praxis` and `categories: [praxis]` both give `page.taxonomies.categories`. If a page can have no terms in a taxonomy, use `page.taxonomies.categories | default([])`. ## Install or upgrade ```bash curl -fsSL https://accentcms.dev/install.sh | sh ``` Binaries for all platforms, checksums and signatures are on the [GitHub release page](https://github.com/AccentCMS/accent/releases/tag/v0.27.2). --- # Accent CMS 0.27.1 — validation fixes and security updates URL: /news/2026/10/accent-cms-0-27-1 Date: 2026-10-16 Accent CMS 0.27.1 is out: a bug-fix and security release for the 0.27 line, cut from the `v0.27.0` tag with none of the 0.28 development work. Any licence with a 0.27 ceiling covers it. Most of the fixes came from moving a real site to 0.27. ## Check your theme and plugins `accent init` and `accent plugin new` copy their templates into your project, so a site or plugin you created before 0.27.1 keeps the old copy. Three things need a small edit there. Everything else takes effect when you upgrade the binary. **Social image tags.** The upgrade fixes your canonical link and `og:url`. The `og:image` and `twitter:image` tags print `page.social_image` directly, so they still escape its slashes. In your theme's `head_meta.html.jinja` partial, replace `{{ page.social_image }}` with `{{ absolute_url(page.social_image) }}` in both tags. **JavaScript plugins.** A plugin you made with `accent plugin new --lang js` in 0.27.0 builds a component that Accent refuses to load. In its `package.json`: - Set the `build` script to `jco componentize plugin.js --wit wit --world-name content-plugin --disable http fetch-event -o plugin.wasm`. The flags must follow the input file. - Set `@bytecodealliance/jco` to `1.36.1` and `@bytecodealliance/componentize-js` to `0.23.0`. Then run `npm install` and rebuild the plugin. **Math in a docs site.** A site from `accent init --docs` loads KaTeX 0.16.21. In `templates/base.html.jinja`, change `katex@0.16.21` to `katex@0.19.0` in the stylesheet and the script URL. ## Fixes **`accent validate` accepts what the build publishes.** It reported every `{% partial %}` as not found, and every link to a file in the `static/` directory, such as a PDF, as broken. A site that used either failed validation, although it built and served correctly. Both now resolve as they do in the build. A partial that exists nowhere, and a link to a file the build does not publish, are still reported. **Links in a `---section:` region reach their pages.** A section's wikilinks and relative `.md` links were written into the page as you typed them, so they led nowhere, and `accent validate` did not check them. Sections now resolve links as the page's main content does, and `accent validate` reports a broken one with the name of its section. **Canonical and social URLs keep their slashes.** Every page built with a shipped theme wrote its canonical link and `og:url` with each slash escaped as `/`. `absolute_url()` now returns the URL with literal slashes. A theme that added `| safe` to work around this keeps working. **Component templates see the page's modules.** In a component, `page.modules` was an empty list, so you could not place a module's content with a component. It now holds each module's name, title, rendered content and fields, in `accent serve` and `accent build` alike. **JavaScript plugins load.** The template from `accent plugin new --lang js` built a component that asked for an HTTP interface Accent does not provide, so Accent skipped the plugin at startup. New plugins build correctly. A plugin still makes HTTP requests through the `outbound-http` capability. **`accent license status` works in any directory.** It failed where there was no `config.yaml`, although the key usually comes from `ACCENTCMS_LICENSE`. It now reads `config.yaml` only when there is one. **The admin schema browser lists models by name**, instead of in an order that changed every time the server restarted. ## Security **Plugin runtime: wasmtime 48.0.6 fixes twelve advisories.** One let a plugin with filesystem access escape its directory by renaming a symbolic link written with a trailing slash. Most of the others could let a plugin make the server panic through component-model async calls, streams and futures. Only plugins run through wasmtime, so a site that runs no plugins never reaches this code. If your site runs plugins you did not write, upgrade now. **TLS: rustls 0.23.46 fixes four advisories** in the library behind Accent's HTTPS server and its outbound connections: - The debug output of a client session could expose TLS secrets. - A rejected Encrypted Client Hello could send buffered data to the outer server. - Certificate decompression could allocate up to 1 GiB before the peer was authenticated. - An unauthenticated ServerHello could downgrade later connections to classical key exchange. **Dev-mode diagram preview: Mermaid 11.17.2 fixes five advisories.** A diagram could hang the browser tab that previews it, pollute JavaScript object prototypes, or restyle the page around it. Mermaid loads only in `accent serve` without `--production`. Built sites use server-rendered SVG. If you set `diagrams.renderers.mermaid.cdn_url` yourself, move it to 11.17.2. **KaTeX 0.19.0 in the docs theme** no longer treats a setting inherited from a polluted `Object.prototype`, such as `trust`, as set. **The JavaScript plugin template** takes jco 1.36.1 and componentize-js 0.23.0. The old toolchain installed a `decompress` release whose archive extraction could write outside its target directory during `npm install`. **YAML parser: noyalib 0.0.58** is a security release. Its fixes are in code Accent does not reach, so pages, config and build output are unchanged. One difference is visible: an unknown key in a model file's `body:` section is now reported with its line, column and path. Install or upgrade: ```bash curl -fsSL https://accentcms.dev/install.sh | sh ``` Binaries for all platforms, checksums, and signatures are on the [GitHub release page](https://github.com/AccentCMS/accent/releases/tag/v0.27.1). --- # Accent CMS 0.27: your own taxonomies, Atom and JSON feeds, more data in templates URL: /news/2026/10/accent-cms-0-27 Date: 2026-10-10 Accent CMS 0.27.0 is out. Five changes can need action when you upgrade, so they come first. ## Before you upgrade **Tag URLs use slugs.** A tag page moves from `/tags/javascript runtime` to `/tags/javascript-runtime`. Static builds and `accent serve` redirect the old URLs. If your theme builds tag links by hand, such as `/tags/{{ tag.name }}`, use `{{ tag_url(tag) }}` instead. The function adds the slug, the deployment prefix and the language prefix. **`pages` lists only the current language.** On a site with `site.languages`, the `pages` list that a menu loops over now holds the pages of the page's own language. To link the other languages, add a language switcher over `page.translations`. The starter theme shows how. **A broken component manifest fails `accent build`.** Before, the build dropped the component with a warning and passed. Use `--lax` to get the old behavior. **The build manifest moved out of the output.** `accent build` wrote `.build-manifest.json` into the output directory, so a deploy published it, with the URL and status of every page, drafts included. It now lives in `.accent/build/` next to `config.yaml`, and the first build after the upgrade moves it for you. If your deploy never deletes remote files, delete `/.build-manifest.json` from your host. In CI, cache `.accent/build/` together with the output to keep incremental builds. **YAML and dates are read more strictly.** - A hex or octal number with an uppercase prefix, such as `0X1F`, is now a string. `0x1F` is still the number 31. - A control character in a YAML comment is an error. - A `publish_date` that is neither a date nor an ISO 8601 timestamp keeps the page a draft. Before, the page was published. Timestamps such as `2026-12-01T09:00:00Z` now work. Run `accent validate` after the upgrade. It reports each of these. ## Your own taxonomies Tags are no longer the only way to group pages. List more taxonomies in `config.yaml`: ```yaml taxonomies: - name: categories singular: category path: /themen # default: /categories ``` Each taxonomy gets what tags have: an index page, one page per term with a slug URL, pages per language, and `taxonomy.categories` in every template. A page opts in with `categories: praxis` or `categories: [praxis]`. A theme can add `categories.html.jinja` and `category.html.jinja`; without them, Accent uses the tag templates. Link a term with `term_url("categories", term)`. ## More feeds Accent now writes Atom and JSON Feed as well as RSS, and it can write several feeds at once: ```yaml feed: outputs: - { format: atom, path: /feed.xml } - { format: json, path: /feed.json } include: ["/posts/"] # only posts, while they stay in the sitemap ``` - `feed.include` and `feed.exclude` limit a feed to one section of the site. - A page whose `content:` block sets `feed: true` gets a feed of its collection at `/feed.xml`. - A taxonomy with `feed: true` gets one feed per term, such as `/themen//feed.xml`. - `feed_outputs()` lists the feeds, so a theme can write one `` for each. The default theme does. - `accent serve` answers every feed at the same URL as the build. Outside dev mode, it caches the feed bodies. A config with only `feed.path` writes the same RSS feed as before. ## More data in templates **`site.extra` holds your own site-wide values:** ```yaml site: extra: newsbar: enabled: true message: "No free slots at the moment." ``` A template reads them as `site.extra.newsbar.message`. `site.extra` is always there, so `{% if site.extra.x %}` is safe. **Your frontmatter in listings.** Every entry in `all_pages`, `pages`, `page.children` and the other listings has a `custom` map with your own frontmatter keys. A collection's `filter` also takes a `custom:` map, so `custom: { category: praxis }` lists only those pages. **Components see the page.** A component template now gets `site`, `page`, `pages`, `all_pages`, `taxonomy`, `theme`, `dev` and `accent`, like a page template. A component can list the latest posts of a tag without an attribute for each value. **Dates in the page's language.** The `date` filter writes month and weekday names in the page's language, so a German page shows `März`. `date("long")` and `date("short")` are presets for common formats, and `locale="fr"` picks another language. ## Content and builds - **A `static/` directory.** Files under `./static` are served and copied to the site root, for files such as `favicon.ico`, a verification file or `humans.txt`. Incremental builds track them. - **`{% include-file %}`** shows a file of your site as a highlighted code block. It reads the file on every render, so the code on the page always matches the file. `lines` and `region` show part of a file. - **Marks in code.** A code block that opts in with `{% process=true %}` can wrap parts of its code in the `mark` component, for example to highlight one part of a command. - **Files in mounts.** A directory mount now publishes the non-markdown files next to its pages, as the content directory does. - **Links to files.** A relative link to a file next to the page, such as `[plan](benchmark.yml)`, is rewritten to the file's URL. The same link works on GitHub and on the site. - **Netlify `_redirects`.** With `build.header_artifacts: ["netlify"]`, the build also writes `_redirects` from your redirects and page moves. Cloudflare Pages reads the same file. - **Component manifests** can declare `body: none` and `placement: block | inline | both`. ## Security fixes - Remote content, such as a remote mount or `accent serve `, can no longer pull a file of your site into a page with `include-file` or `partial`. - `include-file`, `partial` and `diagram src=` refuse the site's config file, so they cannot publish a license key or credentials. - Theme assets no longer serve or copy dotfiles such as `.env`, and no longer follow a link out of the theme. - With the config file inside the content directory, another spelling of its name (`/Config.yaml` on macOS and Windows) or a link to it no longer serves it. - The theme asset route and the admin static route no longer read files outside their directories. - The build manifest no longer ends up in the deployed site (see above). ## Fixes for multi-language and versioned sites - Tag pages exist in each language, under the language prefix. - A translated home page has one URL and lists its pages. - `nav_tree()`, menus and `accent query tree` follow the page's language. - On versioned docs, `accent query` sees inherited pages, a `.md` link on an inherited page reaches its target, and `accent validate` reports a finding on an inherited page once. ## Other fixes - A new release reaches returning visitors within an hour. CSS, JavaScript and font files whose URL has no content hash are no longer cached for a year. - `/readyz` answers 503 while a theme template does not compile. - DocFind search finds the body words of German pages, and Simple Search keeps words with non-ASCII letters whole. - A build without `--clean` follows frontmatter edits and renders again the pages that show a changed page. - Headings keep their `{ .class #id }` attributes, and a raw HTML block stays where it was written. - A diagram's alt text reaches screen readers. ## Under the hood The YAML parser moves to noyalib 0.0.57, and the other dependencies are updated. `cargo audit` reports no known vulnerabilities. The GitHub release has the full list of changes. Install or upgrade: ```bash curl -fsSL https://accentcms.dev/install.sh | sh ``` Binaries for all platforms, checksums, and signatures are on the GitHub release page. --- # Accent CMS 0.26.1 — security fixes for the plugin runtime URL: /news/2026/10/accent-cms-0-26-1 Date: 2026-10-04 Accent CMS 0.26.1 is out: a security release for the 0.26 line, cut from the `v0.26.0` tag with none of the 0.27 development work. Any licence with a 0.26 ceiling covers it. **Plugins run on a patched runtime.** Accent runs plugins in wasmtime, a WebAssembly sandbox. RustSec published ten advisories against the wasmtime 48.0.2 that 0.26.0 ships, and 0.26.1 moves to wasmtime 48.0.5, which fixes all of them. The most severe, RUSTSEC-2026-0327, is rated critical: a native stack buffer overflow from a value the runtime did not validate. The others would have let a plugin: - make the server panic with an out-of-range file timestamp, - corrupt the runtime's garbage-collected memory, - run past the CPU budget Accent gives each plugin call, - make the server allocate excessive memory, or - read leftover bytes from a directory listing. **Who should upgrade.** Only plugins run through wasmtime, so a site that runs no plugins never reaches the affected code. If your site runs plugins, and especially plugins you did not write, upgrade now. **Nothing else changes.** Configuration, templates, and content need no changes. Installed plugins keep working without a rebuild, because the plugin interface is the same. Install or upgrade: ```bash curl -fsSL https://accentcms.dev/install.sh | sh ``` Binaries for all platforms, checksums, and signatures are on the [GitHub release page](https://github.com/AccentCMS/accent/releases/tag/v0.26.1). --- # Accent CMS 0.26: script policy, portable components, directory previews URL: /news/2026/09/accent-cms-0-26 Date: 2026-09-24 Accent CMS 0.26.0 is out. Four changes need action when you upgrade, so they come first. ## Before you upgrade **Scripts need a nonce.** Served pages now send a `Content-Security-Policy` with `script-src 'self' 'nonce-...'` and no `'unsafe-inline'`. A script runs only if it loads from your own site or carries `nonce="{{ nonce }}"`. All bundled themes already do this. If your theme has inline scripts, add the attribute to each `