Changelog
What's new in Aardvark — releases and notable changes, newest first, filterable by tag.
Everything new in Aardvark, newest first. Releases carry a version badge; use the tag cloud on the right to filter the timeline by area — pick more than one to combine them.
Filter by tag
The reader assistant says what actually went wrong — in your language
Every rejected turn used to report “This conversation is too long”, which was true of none of the four cases that produce it — including a rejected attachment, where the reader was told to start a new chat instead of to drop a file. Failures are classified by their code now, so a reader is told the thing they can act on. A turn rejected that way also restores what was typed, but only if the reader has not already begun a new question.
The panel’s remaining chrome localizes too. Its attachment, install, zoom, fullscreen and
clear-conversation controls, and the whole “Copy page” / Markdown / agent-install menu, were
never registered for translation, so a localized site rendered them in English without saying
it had fallen back. Run a UI-string re-extraction (vark build --translate) to pick up the new
strings; until then they fall back to English as before.
See Ask AI.
Windows binaries are back
0.4.0 published macOS and Linux builds but no Windows one. The release step that vendors the
islands JS toolchain launched npm under its bare name, and Windows resolves a bare command
without consulting PATHEXT — so it never found npm.cmd, and the build failed before
producing anything. npm is resolved to an absolute path once now, and every call uses it.
macOS and Linux were never affected, and 0.4.0’s binaries for them stand. If you are on Windows, 0.4.1 is the upgrade from 0.3.3.
See Installation.
`vark deploy` publishes a site from the command line
Run vark deploy in a project and it does the same full production build as vark build,
uploads the output, makes it live on Aardvark cloud managed hosting, and prints the URL. No
connected GitHub repository required.
It authenticates with your secret key in AARDVARK_SECRET_KEY — the same possession-based
CLI credential the AI features already use, never the public key that ships inside a built
site. On the first deploy of an account with no site, the site is created for you; --slug
names it. --no-build uploads an output directory you have already built.
Two behaviors worth knowing: an interrupted deploy resumes rather than starting over, and
re-uploading an unchanged file is always safe. And a site already connected to a GitHub
repository keeps deploying from that repository, so vark deploy refuses it — before
running the build, not after it has spent the time.
See the CLI reference and Managed hosting.
Aardvark ships the islands JS toolchain — nothing to install
A fresh site builds its interactive components with no npm install and nothing downloaded.
Release binaries carry a digest-pinned store of the whole dependency tree the islands need —
React, every @mantine/* package the built-ins use, the community-component packages, the
Twoslash render dependencies and esbuild with its native binaries. When a project has no
node_modules of its own, the build stages that store into .aardvark-cache/node_modules/,
extracted and checked against a recorded sha256, never executed, and entirely offline.
Node.js is still the one build-machine prerequisite — the toolchain ships with Aardvark, but something has to run it. See Installation for the version floor.
Your project always wins, wholesale. A real node_modules (or a workspace’s hoisted tree)
means the store never serves and any copy Aardvark staged earlier is removed, so it can’t
shadow yours. And a package.json asking for packages or versions the store doesn’t carry
falls back to the existing automatic npm ci/npm install rather than being quietly served
different versions. A source checkout — or a binary built without the vendoring step — keeps
that automatic-npm behavior as before.
This generalizes what the map runtime did for one component: it
ships beside the store, so {% map %} no longer asks you to install
anything either.
Island prerendering is on by default
A site with no islands block in aardvark.config.yaml now bakes each SSR-capable island’s
rendered widget HTML into its static pages at build time. Crawlers and readers without
JavaScript see real markup, the first paint doesn’t shift, and the client hydrates that markup
on load — exactly what islands: {ssr: true} always did, now without asking. Islands that are
browser-only by design are unaffected and still mount in the client: most of the community
widgets, the native map, and any component library configured without SSR. The context menu,
data grid and lightbox are the exceptions — they stay prerendered.
Set islands: {ssr: false} (or a bare islands: false) to opt back out. That keeps the
client bundle, so islands still mount on load; you lose the baked markup, not the
interactivity.
One thing this change does not alter: a build without the islands toolchain — Node and the JS dependencies behind it — is not that fallback and never was. It ships no client bundle at all, so it warns and island widgets don’t render. See Components and snippets.
The MCP Server Card is written only when MCP is on
Every build used to write /.well-known/mcp/server-card.json advertising a {baseUrl}/mcp
endpoint — even on a purely static deploy where nothing would ever answer there. The card, its
_headers content-type and CORS rule, and its homepage Link: entry now ride the same gate as
the other MCP surfaces: mcp: true in aardvark.config.yaml together with a baseUrl.
If you relied on the card, set those two after upgrading — a site that never opted in was
advertising an endpoint it did not serve, and now correctly says nothing. A server card you
ship by hand at static/.well-known/mcp/server-card.json still wins, gate or no gate, and
still gets its _headers rule.
See Agent discovery.
The repo browser reads GitLab, and keeps a page-weight budget
{% gitfolder %} and github= code fences now read public GitLab
projects. Name one with a full https://gitlab.com/… URL — a project URL for the widget, a
/-/blob/<ref>/<path> or /-/raw/<ref>/<path> file URL for a fence — and everything works as
it does for GitHub: one anonymous archive download, no API call, no token, the same cache.
Nested groups are supported. A bare owner/repo shorthand still means GitHub, since it
carries no host; set provider: gitlab in the github: config block to point the shorthand
forms at gitlab.com instead. Only gitlab.com is supported — a self-managed host would be an
arbitrary page-supplied download host, which the build’s allowlist refuses by design.
The widget also has a page-weight budget now, so one embed can’t grow without limit. It
embeds its files’ source, highlighting and rendered previews until it reaches
gitfolder.maxEmbedBytes (default 2 MiB), counted the way the page really carries them —
encoding and escaping inflate ordinary source about half again. Files are embedded in path
order until one doesn’t fit; that file and every text preview after it are listed rather
than embedded. Each keeps its row in the tree, its size, its source link and its place in
the Download ZIP, and the widget says the preview was skipped for page weight rather than
claiming the file is too large. Images ride as served files, outside the budget, and keep
displaying. The build names what it did not embed and points at both remedies: scope the
widget with folder=, or raise the budget. Raising it re-downloads nothing — the budget is
applied when the page renders, so the next build picks it up.
See gitfolder.
Ask AI highlights code without calling out
Opening the Ask AI panel used to fetch highlight.js from a CDN the first time a reader asked a question. It doesn’t any more: answers are highlighted by the lexer Aardvark already uses for your pages, so the assistant makes no third-party request, needs no extra Content-Security-Policy origin, and still works where that CDN is unreachable.
The colors are unchanged: answers were already painted from your theme’s syntax palette and still are. What has gone is the extra stylesheet that remapped highlight.js’s class names onto that palette, because the built-in lexer emits the same classes your own code blocks use. An answer still stays plain while it streams and is colored in one pass once it finishes — what no longer happens is the repaint when the CDN script arrived late and recolored an answer already on screen.
The trade is breadth. The built-in lexer covers 20 languages — JSON, Bash, Python, JavaScript, TypeScript, Go, PHP, Rust, Ruby, Java, C, C++, SQL, YAML, INI/TOML, CSS, SCSS, XML/HTML, Markdown and Aardvark’s own template syntax, plus the usual aliases. Anything outside that list renders as readable plain text where the CDN’s grammar set would have colored it.
Released in 0.3.3. See Ask AI.
Maps run on your own site, not a CDN
{% map %} used to pull MapLibre GL JS from a public CDN at runtime. It now bundles a
runtime served from your own site and publishes the map worker as a same-origin asset, so
nothing executable in a map comes from a third party. A map keeps working when a CDN is
blocked or slow, and it needs no CDN allowance in a Content-Security-Policy.
The runtime is read out of your own node_modules, so a site upgrading from an earlier
release has to install mantine-map 0.4.0 — add it to package.json and run your package
manager, since it is the installed tree that decides and a listed-but-uninstalled package is not
a copy Aardvark can build against. Until that copy is there, the build warns,
drops the interactive map and falls back to the accessible list of locations the tag always
renders for readers without JavaScript — the build still succeeds, which is what makes a
vanished map easy to overlook. Pin the version exactly: a copy that is not 0.4.0 is dropped the
same way, since that is the version the map surface is built against. Sites scaffolded by 0.3.3
already carry the pin. This install requirement is 0.3.3 only — 0.4.0 ships the runtime inside
Aardvark and stages it automatically, so a project that installs nothing gets a working map again
(a project with its own installed copy keeps using that one).
Three knobs are gone with the CDN they pointed at: map.maplibreVersion, map.maplibreJs
and map.maplibreCss no longer do anything, and are ignored without a warning — delete
them rather than waiting to be told. If you set a Content-Security-Policy, a map now needs
worker-src 'self' and img-src 'self' data: blob:; the basemap’s own style, tile, glyph
and sprite origins still belong under connect-src/img-src, since those carry map data
rather than code.
The runtime is only shipped by sites that use it: a page tree with no {% map %} emits
neither the MapLibre bundle nor the worker.
Released in 0.3.3. See Map.
Ask-AI reader assistant
An optional Ask AI assistant answers reader questions grounded in your docs, backed by a metered cloud gateway: a reader asks in natural language, and the answer comes from your published pages, citing the ones it used.
Turn it on with ai.assistant.enabled: true plus your public gateway key
(aardvark_live_…), supplied at build time as the AARDVARK_KEY environment variable —
never the secret key, which is why a build handed one aborts rather than baking it into a
static site. Every answer is metered in real dollars against the account behind that key, so
size the key’s spend cap to the exposure you accept; a :free model slug always answers at
$0 if you want to run the panel without spending anything.
Build-time accessibility contrast audit
vark build runs a WCAG color-contrast audit over the theme palette compiled from your
theme.scss — light and dark — and reports every pair that lands below the level you asked
for, so a regression shows up at build time instead of in a reader’s browser.
The audit is deliberately non-fatal: it warns and the build finishes, because a contrast
miss is a design call to make on purpose, not a reason to block a deploy. It runs by default;
a11y: false in aardvark.config.yaml silences it, and a11y: {contrastLevel: AAA} raises
the bar from AA (4.5:1) to AAA (7:1).
Read more under Accessibility.
Faster dev-loop rebuilds
vark dev keeps edit-loop rebuilds fast by skipping Open Graph card rendering — then the
build’s heaviest phase, and one that only matters when you ship. Full vark build output is
unchanged. Publish-only phases added since are skipped in dev the same way: the whole-site
PDF, from 0.1.7.
The two skips have a visible consequence while you author: the og:image meta tag still
points at /_aardvark/og/…, which only exists after a real build, so a social-preview unfurl
and the Download-PDF menu item don’t resolve against the dev server. Verify both with
vark build.
vark dev still re-renders every page on every save — Why Aardvark still re-renders every
page covers the design thinking, and what is cached.
Generate the CLI reference from `vark --help`
The CLI reference page is generated straight from vark --help, so every flag and
subcommand stays in lockstep with the code:
- No hand-maintained option tables to drift
- Each subcommand is documented from its own
--help, examples included - The page’s front matter is preserved; only its body is rewritten, so titles, nav placement and redirects survive a regeneration
The generated page is a build artifact of the release scripts rather than something to edit by hand — change the command in the CLI and the docs follow. See the CLI reference for the full command list.
Index OpenAPI descriptions for search
Titles, summaries and descriptions from your OpenAPI spec — the API blurb, operation summaries, and parameter, response, schema and security-scheme descriptions — are part of the search index, so a reader searching for an endpoint’s behavior lands on the right reference section, not just on pages that happen to mention it in prose.
That prose lives only in the reference island’s props and is rendered in the browser, so
nothing in the page’s static HTML would otherwise carry it. Operations are indexed by
operationId and METHOD /path as well, and each result deep-links to the operation’s own
anchor rather than to the top of the page.
Interactive Mantine islands
Embed any Mantine component as an interactive island straight from Markdown. Each SSR-capable island is prerendered to static HTML at build time — so crawlers, no-JS readers and the first paint all get real markup — and then hydrated in place on the client for full interactivity. The browser-only ones — most community widgets, the native map, and any component library configured without SSR — mount on the client instead; the context menu, data grid and lightbox are prerendered like the built-ins.
Prerendering became the default in 0.4.0; islands: {ssr: false} (or a bare islands: false)
opts out, which keeps the client bundle so islands still mount on load. That is not the same
as building with --no-bundle or without the islands toolchain: such a build ships no client
bundle at all, and island widgets do not render.
Multi-language sites
Serve translated content from per-language directories with an automatic language picker.
The base language builds at the site root and every other language under its code prefix, so
content-fr/ becomes /fr/… with no routing to write.
vark build --translate fills pages that are missing or changed with a model,
grounded in the site’s definitions glossary; unchanged pages are skipped via a content-hash
cache, and --retranslate-all forces a clean sweep. Two things to know before the first run:
translation goes through the metered gateway, so it needs your AARDVARK_SECRET_KEY and it
costs money, and the “unchanged” bookkeeping lives in the local build cache — on a fresh
checkout, or after deleting .aardvark-cache/, a --translate run re-translates everything.
Translations are written into your language directory as ordinary Markdown you can review,
edit and commit.
Social unfurl cards (Open Graph)
Every page can render a branded 1200×630 Open Graph card — your logo over a diagonal
gradient of the theme’s primary color, with the page’s breadcrumb, title and description —
which becomes that page’s og:image.
Cards are opt-in: set og: {generate: true} and an absolute baseUrl, since a social
scraper needs an absolute URL to fetch. They are pure build output, written fresh under
_aardvark/og/ on every vark build and never committed, and vark dev skips them entirely
so the editing loop stays fast — which also means an unfurl preview won’t resolve against the
dev server. A page can override its card with image: in front matter.
Standardized code-block Copy & Download buttons
Every code block — on the page, in the file-tree modal, and in OpenAPI request/response samples — shares one Copy and Download affordance, so the interaction is identical everywhere a reader meets code.
Two details worth knowing. The buttons are revealed on hover and on keyboard focus, and a
copy announces itself through the page’s ARIA live region rather than only flashing a
checkmark. And what lands on the clipboard is the block’s real source: a highlighted or
Twoslash-rendered block copies the code, never the type popovers and per-line markup drawn
over it. The download’s file extension follows the fence’s language, falling back to .txt
for a language it doesn’t recognize.
See the components library.