Repository navigation
Accent CMS v0.27.0
Sites can classify pages with their own taxonomies, the way tags work, and
syndicate more of themselves: Atom and JSON Feed output, feeds scoped to a
section, and a feed for each collection and taxonomy term. Templates reach
more of the site. site.extra holds site-wide values, listing entries carry
custom frontmatter, component templates see the page context, and the date
filter writes month and weekday names in the page's language. A static/
directory publishes files as is, {% include-file %} embeds a file as
highlighted code, code blocks can mark spans, and the Netlify artifact writes
_redirects.
Most fixes are for multi-language and versioned sites: tag pages in each
language, menus and navigation that follow the page's language, and inherited
pages that link, query, and validate correctly. Five security fixes stop
routes and builds from reading or publishing files they should not. YAML
parsing follows YAML 1.2.2 more closely.
Four changes can need action when you upgrade:
- Tag URLs move to slugs.
/tags/javascript runtimeis now
/tags/javascript-runtime. Static builds andaccent serveredirect the
old URLs, but a theme that builds tag links as/tags/{{ tag.name }}
should calltag_url(tag)instead. pagesholds the page's own language. A multi-language theme that
reached the other languages through its menu needs a language switcher
overpage.translations.- A broken component manifest fails
accent build. Pass--laxto keep
the old warning. - Stricter YAML. A hex or octal number with an uppercase prefix (
0X1F)
now reads as a string, and a control character in a comment is an error.
Added
-
A
static/passthrough directory, tracked across builds. Every
servable file understatic.directory(default./static) is served at
the same path from the site root and copied to the output root byaccent build, for the files a site must keep at a fixed root path: a
favicon.ico, a verification file,humans.txt, a legacy/docs/*.pdf.
Content pages and generated files win a path collision, with a warning.
The build manifest records what the copy wrote, so a build without
--cleanrefreshes a static file you edited and removes one you deleted,
and never touches a file at the same path that another pass owns.
--dry-runlists the removals. -
Span marks in code. A fenced code block that opts in with
{% process=true %}on its info string may wrap any part of its code in
the newmarkcomponent -- inline or around whole lines -- and the theme
styles the role:{% mark role="filter" %}'.items[]'{% /mark %}renders a
classed span inside an unhighlighted<pre>. A literal fence (the default)
renders byte-identically to before, so pages quoting Markdoc need no
escaping. The fence's text -- with no mark syntax and no span markup --
is what reaches the search index and llms-full.txt.accent validate
locates a malformed tag inside a processed fence, and a manifest that
enumeratesrolevalues checks them like any attribute. -
body: nonein component manifests. A self-closing component's
manifest can now state that it takes no body, instead of leaving the field
absent or declaring a body kind the template ignores. A body passed to such
a component is reported with the self-closing spelling to write instead,
andaccent query componentsgains abodycolumn (block,inline, or
none, the admin page's words). The shippededitionandimage
manifests declare it. -
placement:in component manifests.placement: block | inline | both
besidebody:declares where a component may be written. A component that
wraps one line of text and stands between paragraphs -- an install command
in a card -- declaresbothand renders warning-free in both places; a call
in a placement the manifest excludes is reported naming the declaration.
Absent, the placement followsinline:exactly as before. The shipped
buttonmanifests declareboth. -
Configurable taxonomies beyond
tags. Ataxonomies:list in
config.yaml(name,singular, optionalpath) gives each entry what
tags have: an index route and per-term routes at the configured path with
slugged URLs and per-language pages,taxonomy.<name>and
taxonomy.current_<singular>in every template,<name>.html.jinja/
<singular>.html.jinjatemplates with the tag templates as the fallback,
term_url(taxonomy, term?)besidetag_url(), normalized per-page term
lists on listing entries (taxonomies.<name>), and static build output
recorded in the build manifest. A frontmatter scalar is read as a
one-element list, socategories: praxisandcategories: [praxis]are
the same page, andaccent validatewarns when a taxonomy key holds
anything but a string or list of strings. A site without ataxonomies:
key is unchanged. -
A directory mount publishes its non-markdown files as is. The files
beside a mounted page are served under/content-media/and at the bare
page URL, and copied byaccent build, the way the content directory's
page-local media always was; before, a mount's files were indexed but
nothing answered their URLs. What a.gitignoreunder the mount source
hides stays out, as donode_modulesandtarget. Set
page_local_media: falseon a mount whose files must stay out of the
site. -
{% include-file %}embeds a file of the site as a highlighted code
block, read each time the page renders, so the code on the page never
drifts from the file.srcis relative to the page's directory, or a site
path starting with/that goes through a mount into its source, so any file
in the content directory or a mount can be included.lines(12-40,12-,
1-3,10-12) andregion(the lines betweenANCHOR: nameand
ANCHOR_END: namemarkers, mdBook's convention) show part of a file; the
shown lines are dedented by default,titleadds a caption, andlang
overrides the language the extension implies. Paths outside the site,
symbolic links leaving it, dotfiles, hidden files, files over 256 KiB, and
non-UTF-8 files are refused. A tag that cannot be rendered is an error in
accent validateand failsaccent buildunless you pass--lax, the way
a broken link does. Editing the included file re-renders the page in
accent serve, and the build manifest records the file, so the next
accent buildrenders the page again without--clean. -
Atom and JSON Feed output, feeds scoped to a section, and feeds for a
collection or a taxonomy term.feed.formatacceptsrss,atomor
json, andfeed.outputswrites several at once, each as{ format, path }; a config with onlyfeed.pathwrites the same RSS as before.
feed.includeandfeed.excludetake URL prefixes, so a feed lists one
section while a dated page outside it stays in the sitemap. A page whose
content:block setsfeed: truegets a feed at<its url>/feed.xml
listing its collection, and ataxonomies:entry withfeed: truegets
one per term at<path>/<slug>/feed.xml.accent serveanswers every
output and every collection and term feed at the URLs the build writes,
and the newfeed_outputs()template function lists the outputs so a
theme emits one<link rel="alternate">for each; the default theme does. -
site.extragives templates free-form site-wide values. Put any YAML
undersite.extrainconfig.yaml-- an announcement bar, a social
handle, a flag -- and read it assite.extra.*in every template. It is
an empty mapping when the key is absent, so{% if site.extra.x %}is
safe, and it reloads with the config on SIGHUP or a save in dev. For a
per-language value, nest under the language code and index by
site.current_language. -
Listing entries carry your own frontmatter keys, and collections filter
on them. Each entry inall_pages,pages,page.children,
page.siblings,page.collection,taxonomy.pagesand
recent_documents()has acustommapping, so{% for p in all_pages if p.custom.category == "praxis" %}works. A collection'sfiltertakes
custom:with one value per key; a page matches when its value is equal,
or, for a list, when any element is. The match is by value and type, so
featured: truedoes not matchfeatured: "true". -
The
datefilter writes month and weekday names in the page's
language.%B,%b,%Aand%ause the page'slanguage, then
site.language, then English, so a German page printsMärz. Pass
locale="fr"to override the language.date("long")anddate("short")
are presets for the common shapes:15. August 2026and15.08.2026in
de,August 15, 2026and08/15/2026inen. Numeric specifiers do not
change, so an English site renders as before. A language with no data
falls back to English and logs a warning once. -
The
netlifyheader artifact also writes_redirects. With
build.header_artifacts: ["netlify"], the build writes the file beside
_headersfrombuild.routing: theredirectslist asfrom to status
lines, then the moves the build knows about (a frontmatterredirect:
with its code, a permalink or.mdmove as a 301, each home page's
authoring URL), thenerror_documentas/* /404.html 404. The
meta-refresh stubs stay for hosts that do not read the file. Setting
build.routingwith thenetlifytarget no longer fails the build;
canonical_hoststill does, naming the key, because_redirectscannot
express it. A_redirectsin the output that the build did not write
fails the build, as a hand-written_headersdoes. Cloudflare Pages reads
the same format. -
Component templates read
site,pageand the listing variables. A
template undershortcodes/now seessite,page(the page the
component sits on, without its body),pages,all_pages,taxonomy,
theme,devandaccent, the values a page template sees. A component
can list the newest posts of a tag or link back to its page without an
attribute for each. The component's ownattrs,childrenandbody
win a name clash, and a page whose component lists others renders again
when any page is added, removed or changes what a listing shows.
Changed
-
accent servecaches rendered feed bodies outside dev mode. The site
feed, collection feeds, and per-term feeds are rendered once per
content-index build and served from the page cache on repeated requests,
so a reader that ignoresCache-Controlno longer triggers a full
re-render per poll. A content change, hot reload, or config reload
invalidates the cached bodies, and they roll over at the UTC date change
so scheduled publish and unpublish dates land in feeds on time; dev
serve keeps rendering fresh. -
A diagram plugin never receives a caption. The host puts a diagram's
caption around the picture, as it has since 0.26, so the
render-options.captiona diagram plugin is handed is always none, and
the caption is no longer part of a diagram's cache key. The field stays
in the plugin contract, so existing plugins load unchanged. The
pass-through renderer's own figure, which no page reached, is gone, so
no renderer can add a second caption. -
A relative link or image that names a file beside the page is rewritten
to the file's/content-media/URL when the page renders, the way a
.mdlink is rewritten to its page, so a README's[plan](benchmark.yml)
works on GitHub and on the site alike. A file the site does not publish,
and every file link whenmedia.page_localis off, stays as authored.
This applies to every page, in the content directory and in a mount.
accent validateresolves such links the same way. -
pageslists the menu pages of the rendered page's language. On a
site withsite.languages, thepageslist a theme's menu iterates held
the menu pages of every language; it now holds those of the page's own
language, the waypage.childrenandpage.siblingsfollow the page's
language. A theme that reached the other languages through its menu links
them with a language switcher overpage.translationsinstead, as the
starter theme now does; the internationalization guide has the example. -
Tag pages live at the tag's slug, in each language. A tag's page
is now/tags/javascript-runtimeforJavascript Runtime, where it was
the lowercase spelling with its spaces (/tags/javascript runtime); a
static build redirects each old lowercase path to the slug, and
accent serveredirects any other spelling of a tag's URL there. On a site with
site.languages, each language other than the default has its own tag
pages under its prefix (/en/tags/...), listing that language's pages
only, and a tag that only another language carries redirects from
/tags/{slug}to that language's page. Link tags with the new
tag_url()template function, which gives the slug, the deployment
prefix and the language prefix.TagInfohas aslug. Spellings that
differ only in case, spacing or punctuation are one tag; a+or#
after a letter is spelled out, soC++is/tags/cplusplusandC#is
/tags/csharp.taxonomy.tagslists the tags of the pages the tag pages
list, so drafts and archived pages no longer count, and the API's
?tag=filter andaccent query list --tagmatch the same spellings. -
YAML parsing follows YAML 1.2.2 more closely. noyalib 0.0.57 (from
0.0.51) reads frontmatter,config.yaml, theme, and model files. A plain
hex or octal number with an uppercase prefix (0X1F) or a sign after the
prefix (0x-1) is now a string;0x1Fis still 31. A raw control
character in a comment is an error, as it already was in a value. A
folded block scalar keeps a whitespace-only line indented past its
content, an empty line after an escaped line break in a double-quoted
string is a line feed, and an anchor or tag on an empty list item no
longer swallows the next item. An implicit key that is a flow collection
longer than 1024 characters is refused. Typed reads now apply the same
size and alias budgets as untyped ones.accent validatereports the
same results as before on the documentation, development, and specs
sites. -
Dependencies refreshed: every semver-compatible update, plus dirs 7,
imagesize 0.15, rand 0.10, getrandom 0.4, and base64 0.23.
wasm-encoder,wasmparser, andwit-parsermove to the 0.254 line
that wasmtime 48 uses, so the tree carries two fewer copies of
wasmparser. faster-hex 0.10.1, pulled in through gix, fixes the
RUSTSEC-2026-0306 unsoundness warning.cargo auditreports no
vulnerabilities. The Sass compiler moves to accent-sass 0.17.0, with one
output change: throughmeta.load-css, an@extendinside the loaded
file no longer reaches the including stylesheet. The shipped themes
compile byte for byte as before.
Fixed
-
accent build --dry-runlists every file a real build removes. It
listed the output of pages that are gone or no longer visible, but not
the redirect stubs a build removes once no page needs them, nor the pages
of a tag that is gone, so the real build changed more than the plan said.
The dry run now lists both, and still writes nothing. -
The orphan hint for a translated page names a file that clears it. On
a versioned site with fallbacks, a translated page in a version that
inherits its section index got a hint naming the translation file in the
version the index came from, which creates a page there and leaves the
finding. The hint now names the file in the page's own version, or the
section's URL when that version has no index of its own to place it by. -
Page-local media stays out of other routes' URLs and of the site's
configuration. A content directory namedtags/,content-media/or
theme/, or after a configured taxonomy, had its files copied by
accent buildto a bare URL thataccent serveanswers from another
route. Those files are now published under/content-media/only. With
content.directory: ., the theme, models, data and plugin directories
were page-local media, served and published like any other; neither
accent servenoraccent buildtreats them as page media now. -
accent querysees the pages a version inherits. On a versioned site
with fallbacks,accent queryindexed only the pages each version has
itself, soquery validatereported a relative link to an inherited page
as broken andquery pagedid not find that page. It now expands
fallbacks asaccent serve,accent buildandaccent validatedo. -
A finding on an inherited page is reported once. On a versioned site
with fallbacks,accent validatelisted a problem in a page that later
versions inherit once for every version, and its "across N pages" counted
each inherited copy. The finding is now reported against the page whose
source has it, a finding that only a copy has stays, and the counts in
the report and in theaccent buildandaccent servelog line count a
copy as its original. -
A page in a folder without an index file has breadcrumbs again. A
page such aspages/about.mdwas linked to its folder, which is no page,
so it rendered with nopage.parentand no breadcrumbs, not even Home.
It now hangs off the nearest page above the folder, as the navigation
tree already placed it. Its siblings andpage.prev/page.nextstay the
other pages in the folder, and no listing above it changes. -
A version whose name starts another's no longer claims its pages. On
a versioned site with versions such asv1andv10, fallback expansion
read/docs/v10/introas a page ofv1at0/intro, so a version
falling back tov1gained pages at URLs no version owns, such as
/docs/v20/intro, inaccent buildandaccent servealike. A version
now matches only as a whole URL segment. -
A diagram's alt text reaches screen readers on every path, once. Alt
text was injected as the SVG<title>, which the client-render path has
no SVG to put it in, and a plugin's SVG could end up announcing it twice.
The host now owns it, as it owns the caption:alt=becomes the wrapping
figure'saria-labelon the server, client and plugin paths alike,
nothing is injected into the SVG, and a plugin receives no alt text, so
no renderer can write a second label. Blank alt text counts as none. -
A component manifest the theme gets wrong fails the build. A manifest
that failed to parse or validate demoted its component to undeclared at
warning level, so every page using it shipped with a component-shaped
hole andaccent buildexited 0. It now fails the build the way a
broken link does, before anything is rendered;accent build --lax
reports it as a warning and keeps the old demotion, and
accent validatereports the same problems as errors, as before. -
A
.mdlink on a page a later version inherits reaches its target.
On a versioned site where one version falls back to another, a page the
later version inherits is the same file at a URL in each version, and
the link resolver took that for two pages sharing a file and left every
relative.mdlink to it as written:href="intro.md", a 404 or the
markdown twin, in every version, andaccent validatereported each
one. Such a link now names its target in the linking page's version: on
/docs/v2/guide, inherited from v1,[Intro](intro.md)is
/docs/v2/intro, whether v2 inherits that page too or has its own. A
link that names another version explicitly, such as../v1/intro.md,
still goes there. -
accent buildrenders diagrams in a plugin's language. A plugin that
declares diagram languages rendered them underaccent serveonly: the
build composed its diagram registry from the built-in renderers alone,
so a fenced block,{% diagram %}tag ordiagram()call in a plugin's
language became a warning box, or failed the build under
diagrams.on_error: fail. The build now registers the same plugin
languages as serve, under the samediagrams.plugins.enabledswitch. -
The pikchr example plugin builds from a cold checkout again. The
example's uncommitted lockfile newly resolved pest 2.9.3, which pins a
psm release that does not link onwasm32-wasip1; the manifest caps
pest below 2.9.3 until a release links on that target again. -
A
publish_datewritten as a timestamp keeps an embargoed page
hidden.publish_dateandunpublish_datewere read asYYYY-MM-DD
only, so a timestamp such as2026-12-01T09:00:00Z, the shape other
generators export, failed to parse and the page was published on the
next build. Both now read dates and ISO 8601 timestamps. The schedule
stays by the UTC day, so a timestamp is rounded to keep its promise: a
page is never shown before itspublish_dateor after its
unpublish_date. Changed: apublish_datethat is neither a date
nor a timestamp now keeps the page a draft instead of publishing it, and
accent validatereports it, and an unreadableunpublish_date, as an
error. A page'sdatereads timestamps too, on the day it falls on in
its own zone: such a page now sorts by its date in collections,
llms.txt, the pages APIs andrecent_documents(), two posts on one
day by their moment; a feed dates it with that moment and orders items
by it, so the order never contradicts the feed's ownpubDates; and a
dated permalink moves it, with a redirect from its old URL. A model's
datefield accepts the same timestamps.accent validatealso
reports a schedule whose window never shows the page as an error, and
warns on a month-first spelling such as01/03/2026, which not every
locale writes. -
The
datefilter reads timestamps with a zone, an offset or fractional
seconds.{{ site.built_at | date("%d.%m.%Y") }}printed the raw
2026-10-07T17:44:20Z, because the filter read2026-10-07T17:44:20but
not the same value withZ, an offset, fractional seconds, a space for
theTor no seconds, and the same went for a frontmatter date another
generator exported (2026-08-15T15:45:00.000Z). It reads all of them now
and prints them in their own zone, UTC when they have none, so%H:%M
and%zwork too;accent.build_time's trailingUTCand an offset
with no minutes read too.now()now prints as its date and time instead
of{}, and formats. A string it still cannot read, or a pattern chrono
cannot write, prints the value unchanged and is logged once for each
template: a value once, a pattern once. Sorting, permalinks and publishing read dates as before. -
accent docs template-filterslists only what the engine has. It
listed acontainingtest andurlencodeandwordcountfilters that
are not registered, so a template written from it failed to render.
Those rows are gone; theinrow says which side is the sequence, and thedate
row shows the positional format, theformat=andlocale=keywords
and the input shapes. A test renders every listed name through the
engine, so a listed name the engine lacks fails the build of Accent itself. -
A page whose name holds a space or another encoded character is found
on a static host. A directory namedMy Notesis the page
/My%20Notes, and the build wrote it toMy%20Notes/index.html; a static
host decodes the request toMy Notes/first and answered 404, and so did
accent serve-static. The build writes it where the host looks now, as it
writes the page's markdown twin, its media, its paginated pages, its PDF
thumbnails and its static API file, andaccent serveanswers the same
API URL. After upgrading, build once with--cleanif the site has
such pages: the first build after the upgrade is a full one, but an
output an earlier version filled keeps the copies it wrote under the
encoded names, a draft's included, until a clean build. A name whose
decoded form the build machine cannot write as a file name stays encoded
and is logged, since a static host cannot serve it: one with/,\or
NUL anywhere, and on Windows also: * ? " < > |, a control character, a
trailing dot or space, or a device name such asCON. A frontmatter
url:that names another page's file once decoded, such as/my notes
beside amy notes/directory, keeps its original URL with a warning, as
one that names the other page's URL does; so does aurl:onto another
page's own URL when that page moves too, so no page is displaced. On a
file system that ignores case, as macOS and Windows do by default, the
later of two pages whose URLs differ only in case is left out with a
warning, since they share a file. A page's collection feed moves beside
the page. -
A diagram plugin is told the page's URL, not where its file is. The
plugin contract promisespage-pathis the URL of the page a diagram is
on and that the sandbox cannot learn the host's directory layout, but the
host passed the page's absolute source file path, such as
/Users/me/site/content/docs/a.md. A fenced block and a{% diagram %}
tag now pass the page's URL,/docs/a, without the deployment prefix. A
diagram()call in a layout or partial renders on every page, so it
passes none, and one render serves every page. A plugin diagram's cache
key uses the URL too, so two checkouts of a site at different paths share
their rendered diagrams. -
A heading keeps its
{ .class #id }attributes. The pass that adds
a heading's anchor link rebuilt the heading with the generated id and no
classes, so## Title {.big-text #custom}rendered without its class and
with a slug for its id. The classes and attributes stay now, and an id
you give, with#customorid=custom, is the heading's, which the
anchor link andpage.tocuse; a generated id never takes it. A remote
page's headings drop their classes and attributes, since a third party
writes that markdown. -
A variable written in text renders its value.
Welcome to {% $site.name %}., in a component's body or in plain content, reached
the page as written: the tag pass edits tags, and the parser makes a
variable a text node. Its value is written now, as text that never
becomes markup, or as it is inside a fence that processes tags or a
diagram's source.accent validatenames an unknown$sitekey or
namespace; a$frontmatterkey stays optional. -
A page in a grouping folder is no orphan.
accent validatereported
every page in a directory with no index file as orphaned, one info line
per page on every flat-file site, although the file-page model makes
such a directory a folder that groups pages. The check now walks up from
the page past such folders and reports it only when the first section
that is not a page has an index in another form: unpublished, only in
another language, or only in another version of a versioned root. A home
page in01.home/counts as the root, on a site with no home page each
page whose walk reaches the root is reported, andaccent validate
expands a version's fallback pages first, as build and serve do, so it
checks the site that is served. -
A media link names its file when the name holds
%,?,#or\.
page.mediaURLs joined a file's name into the path as it was, so
My%20Doc.pngwas linked as a request forMy Doc.png, andwhat?.png
aswhatwith a query. Those characters are percent-encoded in the URL
now; a space or a letter outside ASCII is written as before. A link in
a page's body is read as a URL, as on GitHub, so
[photo](My%20Photo.png)is rewritten to the fileMy Photo.png. -
A
---section:region renders the way the page's body does. Each
section was rendered from its markdown directly, so a component, a
partial or another tag in it stayed literal text, with smart punctuation
rewriting its quotes; its links missed the site'sbase_path; and
accent validatenever read it. A section now passes through the tag
stage, the base path and, on remote content, the HTML escaping, as the
body does; validation checks its tags and names the section; and serve
and the build follow the partials it uses. A---section:marker inside
a fenced code block is now part of the example rather than a section
break. -
Security: remote content reads no file of the site. A remote
mount's markdown, or the documentaccent serve <url>serves, is written
by a third party, and aninclude-fileor apartialin it could
publish a file from the site's content directory, or from the directory
the command ran in. Both are refused on such a page now, andaccent validateand the build report them as warnings, which fail no build. -
Security: theme assets publish no dotfile and follow no link out of the
theme. A theme that shipped.env,.npmrcor a.git/directory
underassets/served it at/theme/assets/, andaccent buildcopied
it into the output; a symbolic link underassets/published whatever it
led to. The route and the build now leave out a name that starts with
., follow a link only to a file insideassets/, Sass sources
included, and on Windows refuse a reserved device name such as
COM1.scss, which would open the device. -
A warm build renders again the pages that show a page that changed.
A build without--cleanrenders a page again when its own inputs
change, but menus, listings, tag clouds and prev/next links show other
pages too. They kept an old title, linked a removed page, and kept a gone
tag's page and the links to it, until a full build. The build now
records what listings show of each page, and when that changes, or a
page comes or goes, it renders every page. An edit to a page's body
alone still renders that page alone. A page you remove takes its output
with it, so a link to it fails the build's link check, as it does on a
clean build. -
No builtin puts the site's config file on a page. With the config
file in the content directory,include-file,partialor
diagram src=could publish the configuration, license key and
credentials included. They refuse it now, by identity, so a case alias,
hard link or symbolic link is refused too. Apartialalso refuses a
dotfile, a file under a dot directory, and a symbolic link that leads
outside its directories, asinclude-filedoes. -
Ignore rules follow git's case rules. With
auto_indexon, a
.gitignorethat saysSecrets/hid nothing for a directory stored as
secrets/, which git ignores on macOS and Windows, and on a file system
that ignores case, a hidden file was reachable under another spelling.
.gitignorerules now match in any case where the repository's
core.ignorecasesays so, and a request is judged by the names the
directories store. Theignorelist still matches names as written. -
Page-local media under a directory with a numeric sort prefix is served
at its clean URL./content-media/guide/pic.pngand the bare
/guide/pic.pngnow answer for a file in01.guide/, asaccent build
has always published them: serve maps the clean URL to the file through
the content index instead of joining the path literally. The build also
emits the bare copy for such a directory, so the two agree. -
A file whose name holds a
%is served by that name. The media and
theme asset routes decoded a path twice, so a file namedMy%20Doc.png
was looked up asMy Doc.pngand one named%FF.pngnot at all:
/content-media/answered 404 for a file its page URL served. Every file
route, the plugin asset route included, now decodes a path once. The
page URL alias also checks a page'smedia.expose: falseagainst the
decoded path, so a differently encoded spelling of the page's URL no
longer serves its files. -
A build without
--cleanfollows an edit to a page's frontmatter. The
build decided whether a page changed from its body text alone. So a new
title, tags, template or other frontmatter field, an edit to a named
---section---block, or an edit to one of the page's module files left
the page's old output in place. It now compares the whole text of the
page's file and its modules' files, and for a plugin's page, the
frontmatter and markdown the plugin returns. The first build after
upgrading renders every page once. -
A warm build rewrites the tag redirects. The build manifest did not
record the tag pages or the redirects from a tag's old spellings. So a
build without--cleankept a redirect for a spelling no page used any
more, which failed the prefix check after--base-pathchanged. It also
kept a tag's old page where a redirect to the language that still has the
tag belonged. The redirects now share the record of the alias redirects,
and the tag pages have their own. A warm build keeps a tag page after its
tag goes, as it keeps a removed page's output, and a full build removes
it. A build also writes the tag pages again when they were removed from
the output. -
A translated home in a
home/directory has one URL. With
01.home/default.mdand01.home/default.de.md, the German home answered
at both/deand/de/home; at/deits children included itself and it
had no translations, and a build wrote node/index.html. It now follows
the default home: it is the page at/de/home, served at/de, which
/de/homeredirects to and every published URL uses, and a build copies
it tode/index.html. This holds while no page has/of its own. In
the same change:page.translationsnames each home at its root, so the language
switcher links/and/de, not/homeand/de/home.accent validateresolves a link to/de.- A root
index.de.mdis no longer shadowed by the home directory's
default.de.mdat/de. - A home that becomes a draft loses its copy at
/or/dein a warm
build; the copy at/stayed before. - A home in a flat
home.md, not ahome/directory, is served at/;
/homeredirected to a/that answered 404 (b365). Serve answers a
home's root from the index, as the build does. - The
.htaccessheader artifact redirects/homeand/de/homewith
RedirectMatchon the whole path. TheRedirectit wrote for/home
matched a prefix and sent a page under the home directory,/home/x,
to/x.
-
A refused path is a 404, and
..inside a file name is allowed. The
static resolvers refused any path whose text contained..with a server
error, so a page miss such as/notes..txtor/blog/wait...what, a
traversal probe, or an encoded absolute path answered 500, and a file
nameda..b.pngcould never be served. A refused path is now a miss,
answered with 404 and logged at debug level. One rule refuses a path on
every asset route: an absolute path, a..segment, a name that starts
with.., and a name that ends in a dot or a space, which Windows opens as
another name; two dots inside a name are part of it. -
Security: the site's config file is no longer served under another
spelling of its name. With the config file inside the content directory
(content.directory: .), a file system that ignores case, as macOS's and
Windows' do, openedconfig.yamlfor/Config.yaml, so the
configuration, a license key included, was served. A hard or symbolic
link to the config file served it on any system, and the static build
published it; the same gaps reached dotfiles, markdown sources and files
under a directory the auto-index ignore list or a.gitignorehides. A
served or published file is now judged by what it is on disk. -
A component without a template renders raw HTML in its body. A
component declared only by its manifest (render: aside, no template)
was rendered whole by the Markdoc engine, which reads a raw-HTML block as
literal text, so<div>a</div>in its body appeared on the page as
escaped markup. Such a block is now written as HTML, as in a templated
component's body; the engine still renders the rest of the body. -
Security: the theme asset route no longer reads stylesheets outside the
theme. A stylesheet request went through the style pipeline before its
path was checked, so a crafted path could read a.scssor.sassfile
outside the theme that the server process could read, in every mode, and a
.cssfile when assets were served in production form (accent serve --production, ordev.browser_reload: false) or a Sass file of the same
name sat beside it. The path is now checked first, and the pipeline
refuses a path outside the theme itself. That also
stopsaccent buildfrom writing generated utility CSS outside the output
directory when a theme setsstyling.utilities.outputto such a path. -
Security: the admin static route no longer reads files outside its
directory. Withadmin.dev_assets_pathset,/_admin/static/accepted
an encoded absolute path and returned any file the server process could
read, without a session, and followed a symlink out of the dev tree. And
a second spelling of a vendored file's URL, such as
vendor//alpine.min.js, skipped a hotfix overlay and served the copy the
overlay replaces. The route now accepts one spelling of a relative path
only, and does not follow a symlink out of the dev tree. -
nav_tree()andaccent query treefollow the page's language.
On a site with several languages,nav_tree()built one tree from every
language's pages, so a theme's navigation hung the English pages under the
German home. It now builds the rendered page's language's tree, rooted at
that language's home; link the home withnav_tree().url. And
accent query tree, the MCP content tree, the admin page tree and the
REST/treestarted at/, where another language's pages are no
children, so they never showed them. From the site root they now list
each other language's root too, with or without a home page there. -
A raw-HTML block stays where it was written. The Markdoc parser,
accent-proust, held a block of raw HTML back and attached it to
whichever node opened next: the following tag, the parent after a closing
tag, the other branch after{% else /%}, the next tab. Inaccent build
andaccent servethe block was dropped, moved to the wrong branch or
sibling, or rendered twice. Accent now usesaccent-proust0.12.2, which
keeps the block under the tag that contains it. -
A URL with broken percent-encoding is a 404, not a 500.
accent serveansweredGET /a/%FFwith500 Internal Server Errorand logged
nothing: the static-file lookup the page handler tries first treated an
encoding that is not UTF-8 as a server failure. Such a path names no file
or page, so it now gets the site's 404 page. -
The REST API's tags and schema leave out drafts.
/api/v1/tags,
/api/v1/schemaand their built files counted every page, drafts and
pages in review included, so a tag only a draft carried was listed and a
tag's count could exceed its detail's pages. The tag list now counts the
pages the page list shows, and the schema describes the pages the API
serves a detail for, archived and error pages included. A tag no listed
page carries has no detail:/api/v1/tags/{tag}is a 404, and a build
removes every tag detail file an earlier build wrote that it does not. -
A translated home page has one URL, and lists its pages. The English
home of a German site was indexed at/en/whileaccent serveanswered
at/en, so the built home listed no children and an empty
@self.children, the served home listed itself among its children and
had no translations (a language switcher there rendered nothing), and the
home showed among its children's siblings. It is now/eneverywhere and
the root of its language's tree, and a relative link or wikilink in it
to a page, such as[About](about), resolves to the English page. -
A menu on a multi-language site no longer links the other language.
The German blog's menu linked the English posts and the English menu the
German ones, against the internationalization guide's promise that each
language has its own navigation tree. Collections,page.childrenand
page.siblingswere already scoped by language; the menu now is too, in
accent serve,accent buildand the MCP page preview. -
A capitalised or multi-word tag link is no longer broken. The
starter theme linked a tag as written (/tags/Javascript%20Runtime)
while the build wrote its page in lowercase (/tags/javascript runtime),
so every such link was a broken link in the build log and a 404 on a
case-sensitive host. The shipped themes linktag_url(tag), the page the
build writes. -
accent validate --model <name>reports that model's findings. It
kept a finding only when its message carried a[model:name]marker,
which constraint, rule and file-name findings do not, so the documented
way to check one model reported a clean site while--modelslisted
them. Each model finding now carries its model, and the filter reads that.
--modelalone keeps every finding on that model's pages, as its help
says; add--modelsfor model checks only. A name no model has is an
error. -
Model rules can read the core fields. A rule's expression saw only
custom frontmatter fields, sotitle is definedfailed on every page and
a rule could not relate a custom field totitleordate. An
expression now reads every core field,title,date,tags,
status,publish_dateandurlamong them, as the field constraints
read it. -
accent queryandaccent contenttake--configafter the
subcommand.accent query page /about --config site/config.yamlwas
rejected withunexpected argument, whileserve,build,modeland
the other command groups accept the flag in either position. The
site-location flags (--site-dir,--content-dir,--theme-dir,
--theme) follow it, andaccent docs agent-readmeandaccent docs claude-mdname the flag.--site-dironqueryandcontentnow reads
<site>/contentand<site>/themes, as onserveandbuild, where it
read<site>/site/content/main. -
accent validateaccepts a child component written on one line. The
declared-components reference and the 0.26 notes show
{% tab label="macOS" %}Install with Homebrew.{% /tab %}, and validate
reported each such line as a misplaced tag, failing the CI gate the
reference tells you to run. A block component written alone on its line
inside its parent is now a block placement whether or not its body shares
the line. One on a line with other text or another tag, in a list item, or
at the top level of a page is still reported. -
A site whose content root holds the shared
media/directory builds
without a warning per media file. Withcontent.directory: ".", the
page-local pass found each shared media file again at its bare URL,
/media/<file>, where the shared-media pass had already put it, and
warned that it was skipping it. The page-local pass no longer copies
anything to a URL under/media/, whichaccent serveanswers from the
shared media directory; it still copies the directory to
content-media/, as serve answers it there. -
accent queryreads each page's document model. The query command
built its index without the models, soquery list --modelmatched
nothing,--show-modelprinted nothing, andquery validatemissed every
model finding thataccent validate --modelsreported. It now resolves
the model by the same routes validation uses (themodel:field, the
template name, a_model.yamlfile) and runs the site's model
validation forquery validate.query pagereports the model, and
query listprints it with--show-model. -
accent content update-frontmatterchecks the page's model. It
reports the model findings an edit introduces, such as an enum value the
model does not allow, in the result'smodel_findings. Under
validation.mode: strictit refuses a write that introduces a model
error, asbuildandservefail on one. An edit that would leave
frontmatter that does not parse, which made the page vanish from the
site, is refused in every mode. -
accent validateno longer reports every translated page as an
orphan. On a site withsite.languages, the home page of a language
other than the default was published at/en/while its pages sat under
/en, so validate reportedOrphaned page: parent "/en" has no index pageonce per English page: its tree starts at/en, which the
orphan check treated as a missing parent. A page directly under its
language root is now a top-level page of that language, with or without
a home page at/en. A translated page whose section really has no
index in its language is still reported, naming the file to add, such as
about/default.en.md, the translation of the section's index. -
accent validatecounts the pages it validated. The summary said
"across 2 pages" on a 30-page site, because it counted the entries that
carried a finding, a theme file among them, while a log line above it
counted something else. It now prints one summary, with the number of
pages validated (with--model <name>, the pages that model governs).
With--modelsor--model, the report shows model findings and whatever
fails the run, without the theme's informational notes. -
The first DocFind search no longer warns in the console.
search.js
initialised the DocFind module with a bare URL, a form wasm-bindgen has
deprecated and logs a warning for; it now passes
{ module_or_path: ... }. -
Commands whose output is data log to stderr. Every command wrote its
log lines to stdout, soaccent query components --format jsonwas
preceded by an INFO line and did not parse, a plain listing's first line
was a timestamp, andaccent mcpmixed log lines into its JSON-RPC
stream.query,content,model,docsandmcpnow log to stderr,
coloured only on a terminal and not underNO_COLOR. The servers and the
build still log to stdout. -
accent docs template-contextdocumentsnonce. The one variable
0.26 asks every custom theme to write on its scripts was missing from the
generated reference, because the engine injects it outside the context
types the reference is built from. A new Request and build section lists
it, with what it does underaccent serveandaccent build, from the
same declaration the engine registers it by;accent docs template-filterslists it under Globals; and the script examples on the
theme portability, environment variables and templating guide pages now
carry it. -
The Apache artifact no longer needs
AllowOverride Options. The
generated.htaccesscarriedOptions -Indexes, which Apache honours only
where the host'sAllowOverrideincludesOptions; on hosts that leave it
out, Apache refused the file and every page answered 500. The directive is
now opt-in withbuild.routing.directory_listing: deny, and the
static-build page explains how to check a host first. Upgrade note: a
site whose host allowed the directive loses it on the next build; set
directory_listing: denyto keep directory listings off. The artifact
still needsAllowOverride FileInfo Indexes, which the requirements now
name. -
Simple Search keeps words with letters outside ASCII whole. The index
and the query split words at every letter outside ASCII, so
Hartnäckiger was indexed ashartnandckiger, and a search for
Erstgespräch matched every page with a "ch" in it and linked to a
fragment of the word. Both now split only where a Unicode letter, mark or
digit ends, compose a decomposed umlaut first, ignore invisible layout
hints such as the soft hyphen, and fold case with Unicode case mapping, so
a query tokenizes exactly as the index does. The index also counts
search.content_lengthin characters, as documented, not bytes, so a page
in Japanese or Cyrillic keeps the whole length; native search snippets in
the API, MCP and CLI results do the same. -
accent docs serveaccepts--no-tlsand-p.accent serveand
accent serve-statictake--no-tlssince TLS became their default, and
a script passing it todocs servegot an error.docs servealways uses
plain HTTP, so it accepts the flag and changes nothing, and its help says
so;accent serve --docsserves the same site with TLS in development. -
A component's
mediaattribute means one thing. The shippedcard
andimagecomponents passed it tourl(), and the landing-page
tutorial tomedia(), so a value written for one rendered a broken image
under the other. Amediavalue is now a path under the shared media
directory (/landing/hero.webp) everywhere: the shipped components pass it
tomedia(), which also accepts the older/media/landing/hero.webp, with
or without the sub-path prefix, and remote files. A page-relative value or
a remote URL still goes throughurl().accent validatewarns about a
value the shared media route would not serve, and one written with the
/media/prefix. -
The 0.24.0 and 0.25.0 notes list the
page.leadchanges as
theme-visible. Both were mentioned only under Fixed, so an upgrader whose
theme already printed the field saw its first paragraph twice, or the
description in its place, without a warning. Each release's Changed
section now carries its change and what to do. -
DocFind search finds the body words of German pages. DocFind split a
page's text into keyword candidates at English stop words only, so a German
body came back as one long candidate per sentence and nothing but title
words matched. The engine now splits at punctuation and reads each page
with its own language's stop words, a page's lead and tags are indexed
too, and every phrase feeds its words to the keyword budget, so a word
inside a longer run is searchable on its own. DocFind still indexes
keywords within a per-page budget, not every word, which the search
reference now says. -
A component after a block of raw HTML no longer crashes the build or the
server. A page where a block-level tag followed raw HTML, such as
<div class="cards">before{% card %}, madeaccent buildabort with a
stack overflow and took a runningaccent servedown on the first request
for the page. The component now renders after the HTML. Tags nested more
than 64 deep are reported (accent::tag-nesting-too-deep) and left as
written instead of exhausting the stack. -
A release reaches returning visitors. CSS, JavaScript, WebAssembly, and
font files were sent withCache-Control: public, max-age=31536000, immutablewhether or not their URL changes with their content, so a
browser that had fetched/_search/search.jsor a theme stylesheet once
kept that copy for a year after the site upgraded. Now only a fingerprinted
URL (style.<hash>.css) keeps that policy; every other such file gets the
configured policy withoutimmutable, and with amax-ageors-maxage
above a day capped at an hour, so a release reaches a returning visitor
within the hour. This applies toaccent serve --production,accent serve-static, and the.htaccessand_headersfiles
build.header_artifactsgenerates. The deployment guide's nginx examples
no longer pin theme assets for a year either. -
A deploy no longer publishes the build manifest.
accent buildwrote
.build-manifest.jsoninto the output directory, so a deploy that uploaded
the directory published it, and with it the address, template, and status
of every page, drafts included. The manifest now lives in the project,
under.accent/build/besideconfig.yaml, and the output carries only an
.accent-buildfile with a digest of it, so a deleted, emptied, or
replaced output is built in full. The first build after the upgrade reads
the old file, stays incremental, and deletes it locally; delete
/.build-manifest.jsonfrom your host too if your deploy never removes
remote files. Cache.accent/build/with the output in CI to keep
incremental builds, and passaccent serve-static --configthe config the
site was built with. -
/readyzanswers 503 while a theme template does not compile. The
probe reportedtemplates: okfor a theme whose templates were broken,
because MiniJinja compiles a template on first use, so a pod with a broken
theme passed readiness and then answered 500 on every page. The server
now compiles every.jinjaand.j2template in the theme at start and
on each reload, logs each failure with the template's name, and reports
"templates": "error"with HTTP 503 until a reload fixes it. A
--productionserver refuses a reload that would replace working
templates with broken ones, and keeps serving; the response names no
template, the log does.
Verifying your download
curl -fsSLO https://raw.githubusercontent.com/AccentCMS/accent/main/release-signing-key.asc
gpg --import release-signing-key.asc
gpg --verify checksums-v0.27.0.txt.asc checksums-v0.27.0.txt
sha256sum -c --ignore-missing checksums-v0.27.0.txt