Notable changes to Sarde, grouped by release. Bug fixes, new features, and breaking changes are listed separately within each release.
Unreleased
1.6.0 - 2026-10-18
Added
sarde i18n strings: lists the translation strings of every layer (built-in, theme, and project) as JSON, with the layer each value comes from and any file that fails to parse. Seei18n strings.sarde deploy --build: builds the site, then deploys it, in one run and under one output-directory lock. With--format json, stdout keeps only the deploy events, and a failed build ends with abuild_failedenvelope. The build sees a--provideroverride, so the redirect files match the host. Seedeploy.- The link check covers frontmatter links:
image,prev.linkandnext.link, andprevornextgiven as a slug, are checked with the body's links insarde build,sarde check-linksandsarde dev. An image must exist underpublic/or beside a page bundle'sindex.md, and a link must name a page, a heading or a static file. Findings show the field instead of a line, and the JSON report addsfield. See Frontmatter links. requiresin an externalplugin.yaml: lists the ids of plugins the plugin needs enabled, like the built-in plugins'requires. It is informational:sarde plugins --format jsonandsarde plugin infoshow it, and an id that matches no plugin is a build warning. See External plugin authoring.
Changed
sarde validate --format jsonprints its result: only a fatal error used to be JSON; a successful run printed the human summary, so tooling had to parse text. Stdout now carries one object withok, the page and collection counts, the duration, and every warning with its file, field, message, level and line.--strictwith warnings setsoktofalseand exits 1. Seevalidate.- Date-prefixed file names lose the date from their URL:
2026-03-15-hello-world.mdwas served at/blog/03-15-hello-world/, because2026was read as a sidebar order, while its slug washello-world. The URL is now/blog/hello-world/, matching the slug, and the page takes its date from the name (the file's modification time used to win). The old URL redirects to the new one, so published links keep working; a page or a redirect of your own at that path takes priority. The link check warns about links in your pages to the old URL (unverified internal), so update them. In a collection with apermalinkpattern,:year,:monthand:dayalso come from the name now, and the old URL gets no redirect. Two posts in one folder that differ only by the date now share a URL; the build warns and keeps the first. See Writing Content. - The
academicpreset sets serif headings only: it set the Merriweather stack asfont-sans, which also styles the sidebar, navigation and forms, so the whole interface turned serif. The stack now goes tofont-heading: headings stay in Merriweather, while text and the interface use the default sans stack (Inter).theme.web_fontsstill loads Merriweather. To keep the old look, settheme.font_family: "'Merriweather', 'Georgia', serif". See Themes and Styling.
Fixed
- Bundle images honor
build.base_path: the responsive variants and the fallback<img>of a page-bundle image were written as root-absolute URLs, so a site served under a base path requested them from the wrong place. Both now carry the base path, in production builds and insarde dev. See Images and Assets. - Labs landing pages show their title: the labs collection root and, in a course > lab > step layout, each course page now render the page title, description, and body above the card grid, so the page has a heading like the course catalog. Each card shows a tile with the index page's
sidebar.iconor the initials of its title, and itssidebar.badge. Counts are pluralized ("1 lab", not "1 labs") and come from the newlabs.lab_countandlabs.step_counttranslation strings. A lab's step count includes the Overview, so it matches the progress bar inside the lab, and a single-page lab shows "1 step" instead of no count. Lab cards also honorbuild.base_path. See Labs. - Netlify answers redirects with a 301: Netlify serves a file that exists at a path instead of a plain
_redirectsrule for it, and the build writes an HTML redirect page at every old path, so the 301 never fired. When the site targets Netlify (deploy.provider: netlifyordeploy.redirect_format: netlify), each rule is now forced with301!. Other hosts keep the plain301. See Redirects. sarde deploytakes the output lock: a deploy could uploaddist/whilesarde devor a build was rewriting it, andsarde devwrites drafts into it. The deploy now holds the same lock assarde builduntil the upload ends, and fails while another process holds it. Seedeploy.- Config errors suggest the closest valid value: an invalid choice, such as a misspelled plugin name in
plugins.enabled, listed the valid values in a different order on every run, sometimes twice when a project plugin shared a built-in's name. The list is now sorted without duplicates, and a value within two edits of a valid one addsdid you mean "search"?. JSON error details carry it assuggestion. See JSON error envelope. - Non-Latin file names get a slug: a page named only in characters outside the Latin alphabet, such as
日本語.md, had an empty slug, so a/blog/:year/:slug/pattern gave every such post of a year the same URL. Its slug is now the same 8-character hash its default URL already used, and:titlefalls back the same way. See Writing Content. - Tag links find every term: a non-Latin tag such as
日本語linked to/tags//, and a tag with a custompermalinkindata/tags.ymllinked to a page that was never generated and lost its label, color and icon.termURLandlookupTermnow find both.sarde devalso refreshes the term page when such a tag is removed from a post. See Term metadata. sarde devstops serving deleted output: the dev server never pruned its output directory, so a deleted page's pagination, a file removed frompublic/, or a scheduled page from an earlier--futurerun stayed served until asarde build. Every full build in the dev server now removes what it no longer writes, and keeps the social cards a production build left. See Dev Server.sarde devreloads translation strings: an edit under the project'si18n/folder did nothing until a restart, because the folder was not watched. It now rebuilds like a template change. The announcements and Telescope plugins also kept the first build's strings on a reused builder; they now use the latest. See Dev Server.sarde devwatchespublic/: everypublic/path was ignored, so the documented CSS hot-swap never fired and a new or changed static file needed a restart. CSS files underpublic/now hot-swap, other files rebuild, and a deleted CSS file leaves the output. See Dev Server.sarde pluginslists the feeds'collectionsoption: the RSS and Atom plugins readcollections, but their catalog entries declared onlylimit, so tools that render plugin settings fromsarde plugins --format jsoncould not offer it. See Feeds.sarde pluginsandsarde schemaare documented: the CLI reference had no section for either command. Seepluginsandschema.sarde i18n statusandi18n scaffoldread content as the build does: on a site whose default language sits atcontent/, the other language folders counted as collections, sosarde i18n scaffold . dewrotecontent/de/fr/_index.md. Status counted files without pairing them, so a French page with no English source counted as translated, andcontent.dirwas ignored. Both commands now skip language folders, honorcontent.dir, and status pairs each translation with its source by path. Acontent/<default>/folder, which the build reads as a collection, now gets a warning instead of being treated as the default language's root. Seei18n status.- Aliases on multilingual sites point at the page: on a site with more than one language, an untranslated page's
aliasesredirected to its fallback copy in another language (for example/fr/blog/hello/), because the copies inherit the aliases and the last one written won. Both the HTML redirect pages and the_redirectsandvercel.jsonrules now point at the page itself. See Redirects.
1.5.1 - 2026-10-07
Added
rootURLtemplate function: resolves a path against the site's base path without the page's language prefix, for files that exist once at the site root, such as/sitemap.xmlor theme fonts. See Template Functions.
Fixed
- Translated pages linked a missing sitemap and font: a page in a non-default language linked
/<lang>/sitemap.xmland preloaded/<lang>/assets/fonts/InterVariable.woff2. Neither file exists, so the browser fetched two 404s on every translated page. Both links now point at the site root. Inter Tightno longer preloads Inter: the theme preloaded the bundled Inter font whenever a font stack contained the text "Inter", so a stack such as'Inter Tight', sans-seriforInterstatedownloaded a font the page never used. Font names now match in full.- The sitemap switches agree: with
sitemapinplugins.disabled, the build summary still counted a sitemap and every page still linkedsitemap.xml.build.sitemap: falsehad no effect, although the docs say it turns the sitemap off. Either switch now removes the file, the<link rel="sitemap">tag, the robots.txtSitemap:line, and the count. - Link check failures name the cause once: a build stopped by link errors reported
build failed: build failed: link validation errors found. The prefix now appears once.
1.5.0 - 2026-10-07
Added
- Catalog page for tabbed collections: when the root
_index.mdof a tabbed collection has a body, the collection root (for example/courses/) renders as a full-width catalog: the root's title, description, and body, then one card per tab with its tile, description, and sidebar badge. A root_index.mdwith only frontmatter still redirects to the first tab. Override the markup withlayouts/<collection>/catalog.html. See Tabbed Navigation. - Course site template:
sarde new site my-college --template course(or-t course) creates a course site with two sample courses (lessons, assignments, and per-course announcements and schedule pages), hands-on labs grouped by course, and a site-wide announcements page. The site includes a GitHub Actions workflow that publishes it to GitHub Pages. Scroll to top, reading progress, and focus mode are enabled for long lessons and labs. Templates are downloaded from the getsarde/sarde-templates repository, pinned to the release'svX.Ytag with a fallback tomain, and cached under~/.sarde/templatesso later runs work offline.--template owner/repo[/path][#ref]or agithub.comURL starts from a folder of any public repository. An unknown template name or a failed download fails before anything is written. See CLI Commands. initialstemplate function: returns up to two uppercase letters from a string, for monogram tiles.initials "Go Essentials"givesGE. See Template Functions.- Netlify, Cloudflare Pages and Vercel deploys:
sarde deploynow publishes to all three through their APIs, with no provider CLI needed. Tokens come fromNETLIFY_AUTH_TOKEN,CLOUDFLARE_API_TOKENandVERCEL_TOKEN, never fromsarde.yaml. Only files the provider does not already have are uploaded, uploads retry on rate limits, and the command prints the live URL. Newdeploy.account_id(Cloudflare) anddeploy.team_id(Vercel) settings;CLOUDFLARE_ACCOUNT_IDandVERCEL_ORG_IDoverride them. See Deploying. sarde deploy --checkverifies the token and access to the configured site or project without uploading anything.sarde deploy --format jsonstreams newline-delimited progress events (steps, upload progress, the result with the live URL and deploy ID) for tools such as Sarde Studio. Failures end with the shared error envelope, which now carries an optionalcode(auth,not_found,rate_limited,limit,network,canceled,config,provider).- GitHub Pages custom domain:
deploy.cnamewrites theCNAMEfile on every deploy, so a force-push no longer drops a custom domain. - Heading font:
theme.font_heading(or the newfont-headingtoken) sets the font forh1toh6. Headings followtheme.font_familyuntil it is set. See Theme Tokens. - Font size scale:
theme.font_scale(or the newtext-scaletoken) multiplies the wholetext-xstotext-5xlscale, so body text and headings grow or shrink together. Accepts0.5to2; a value outside that range is a config error. - Web fonts:
theme.web_fonts: googleorbunnyloads the theme's fonts that Sarde does not bundle from Google Fonts or Bunny Fonts, so thecleanandacademicpresets, and any Google Fonts family set infont_family,font_heading, orfont_mono, look the same for every visitor. Sarde requests only families in the Google Fonts library, in one stylesheet after a preconnect. Off by default; the service receives each visitor's IP address. See Web fonts. - Plugin requirements in the catalog:
sarde plugins --format jsongives each plugin arequireslist of plugin ids it needs enabled.social_cardsrequiresseoandsearch_highlighterrequiressearch. The field is omitted when empty, and the catalog version is unchanged. sarde schema <collection>prints a collection's resolved frontmatter schema: every catalog field that applies to its layout and inferred type, merged with the collection's customfrontmatter_schema, each labeledcatalogorcustom.--format jsonprints the same data for tools such as Sarde Studio.- Icon lists in timelines and details: a list inside a timeline entry or a details panel whose items each open with an
:icon[...]uses the icons as markers instead of bullets. See Timeline. - Plugin translation capability:
BeforeRenderContext.T(lang, key)resolves UI strings through the site's i18n layers for any plugin's per-page hook.
Changed
- Named closing fences close nested blocks:
:::/nameis the documented way to close a block that contains other blocks. It closes the nearest open block with that name, closes any inner block left open, and matches case-insensitively.:::/asidecloses an aside of any type and:::/filetreeis accepted for:::file-tree. Bare:::and longer fences (::::) work as before. Fence lines inside fenced code blocks no longer count toward nesting. See Nesting. sarde check-syntaxfollows the renderer: a:::/namefence that skips an unclosed inner block reports that block as closed implicitly, a closer that matches no open block no longer removes the block from the stack,:::/ nameand:::/name extraare reported as malformed, code fences are matched by character and length, and the:::/asideand:::/filetreealiases are understood.- Build output:
sarde buildprints its header first, shows the phase in progress on one line in an interactive terminal, and ends with a line that counts every warning, such asBuilt 143 pages in 1501 ms, 3 warnings (1 in the log above). Warnings are listed before that line, sorted by file, each with apath:line:collocation relative to the current directory, and identical warnings appear once with a count. Plugin messages such asGenerated sitemap.xmlprint only with--verbose, which also prints each phase's duration as it finishes and the five slowest pages. The link check summary counts findings by policy (1 error, 2 warnings, orno issues) and prints one line per finding with its location. See Build output. - Build JSON additions:
sarde build --format jsonaddsLinks(link check counts),SlowestPages, andLineandColon each warning. Link check warnings carryField: "link", warnings are sorted by file and line, andPhaseTimingsgainsInitializing,Checking links, andBundling assets, soDiscovering contentandTemplate setupno longer include that work. Existing fields keep their names and types. - Announcement banners appear above the site header: banners from the announcements plugin now render in a full-width, one-line strip above the navigation bar on every layout (two lines on phones), instead of inside the page content, and stay with the header while scrolling until dismissed. The strip shows one banner at a time, so
stackmode behaves likefirst, and the newbanner-heighttheme token (3rem) sets its height. Banners that are dismissed, outside their date window, or targeted at other pages no longer flash on load. A base layout ejected from an earlier version keeps the banner where it calls{{ announcementBanner }}; wrap that call and the Header component in<div class="sarde-masthead">to get the new placement. See Announcements. - Tabbed sidebars lead with an Overview entry: the sidebar inside a docs tab no longer wraps every page in a group named after the tab, which the tab switcher already shows. The tab's pages start at the top level, and its
_index.mdbecomes an Overview entry at the top. Rename it withsidebar.labelor hide it withsidebar.hiddenin the tab's_index.md. Tabs with their ownnav.yamlare unchanged. See Tabbed Navigation. - Docs tab switcher redesign: the tab dropdown at the top of the sidebar is now a tinted card with a tile showing the tab's icon (or its initials), the collection title as a small label, and up/down chevrons. Menu entries use the same tiles, and the sidebar collapse button sits further out so it no longer covers the switcher. Sites that override
DocsTabSwitcherkeep their own markup. - Deploy dependency:
github.com/zeebo/blake3(and its dependencygithub.com/klauspost/cpuid/v2) for the Cloudflare Pages asset hash. - Inferred descriptions no longer appear under the page title: a page without a
descriptionin frontmatter used to show its first paragraph under the title and again as the start of the body. The header now shows only descriptions you write; the inferred one is still used for meta tags, search, social cards, and listings. To show text under the title, setdescriptionin frontmatter. Templates can check the new.Page.DescriptionInferredfield. - Lab progress bar moved to the top: the "Step X of Y" bar on lab pages now sits between the lab badge and the page title instead of below the content, and is no longer repeated at the bottom.
- Single font names get quotes and a fallback: a font field that holds one family name, such as
font_family: Plus Jakarta Sans, is now written as'Plus Jakarta Sans', system-ui, sans-serif. A name with a digit word, such asSource Sans 3, was invalid CSS unquoted, and with no fallback a missing font fell back to the browser default. The fallback follows the family's Google Fonts category. Values that are already stacks are unchanged. See Shortcut fields.
Fixed
- Annotation tooltips are no longer cut off inside details and accordion blocks: both containers clipped everything that extended past their edge, so a tooltip on a term near the edge of a panel was partly hidden. The containers keep their rounded corners and let popovers show in full.
:::/asidedid not close:::note: the docs said it did; the parser compared the closer against the type name only.- A named closer over an unclosed inner block was swallowed:
:::/tabsafter an unclosed:::noteinside the tabs left the tabs block open to the end of the page. It now closes the tabs block and the note. - A nested block on the first line after an opener was invisible to its parent: the parent's fence tracking never saw that line, so a bare
:::later in the block could close the wrong fence. Every nested fence is now tracked. - Directive fences inside code blocks broke the enclosing block: a Markdown example showing
:::noteinside a tabs panel or details block shifted which:::closed the container. :::Terminalcould not be closed with:::/Terminal: the opener was case-insensitive and the closer was not.- Docs: the details page claimed nested details require
::::; they never did. - Readable build logs in CI: the Markdown progress counter wrote carriage returns and terminal control codes into CI logs, redirected output, and Sarde Studio's log panel, where every update piled onto one line. The progress line now appears only in an interactive terminal.
sarde build --quietprints only warnings and errors: the flag hid the summary and the warnings list but not the link check line, the progress counter, or other log lines.- Link warnings print once: a link finding with the
warnpolicy appeared in the link check report and again in the warnings list. It now appears only in the report, with its line and column. - Social card problems count as warnings: an unreadable font, an SVG or unreadable logo or background image, or a
bg_gradientwith more than two colors was reported as a plugin log line. Each is now a build warning onplugins.config.social_cards. - Ambiguous links count as errors in the link summary: the pretty summary counted an ambiguous link as a warning even under the default
errorpolicy. The summary now counts findings by their policy. - Telescope finds section pages: the index now includes
_index.mdpages such as course overviews, lab introductions, and single-page labs, which it skipped before. Sections withrender: falseand a tabbed collection root that only redirects stay out. - Redirect-only tabbed roots stay out of the sitemap and search: a tabbed collection root that only redirects to its first tab is no longer listed in
sitemap.xmlor the search index. - Tabbed collection roots behave the same in every language: the root of a tabbed collection redirected only in the default language; translated roots rendered as a list page with the first tab's sidebar. Every language now follows the same rule.
- List items ending in an icon or key stay aligned: in a list that holds paragraphs or other blocks, a list item whose last element was inline, such as an
:icon[...], a::kbd[...]key, or a highlight, got the spacing meant for blocks. That pushed the element above its line of text, as in the course template's schedule items. Only block elements now get that spacing. - Site-wide
head.tagsare rendered: tags listed underhead.tagsinsarde.yamlwere parsed and never written to any page, andheadtags set through a section'scascadewere dropped the same way. Both now render on every page they apply to, site tags before a page's own. AHeadcomponent ejected from an earlier version needs{{ siteHeadTags . }}before itsrenderHeadTagsline. See Head tags. - Head tag scripts and styles keep their quotes: the content of a
script,style, ornoscripthead tag was HTML-escaped, so quotes and&&became entities and broke the CSS or JavaScript. That content is now written as given, with any closing tag inside it neutralized. - Clear error outside a site:
sarde build,dev,check-links, andvalidaterun in a folder with nosarde.yamland nocontent/folder now stop withnot a Sarde siteand the commands to fix it, includingcdinto a subfolder that holds a site. Previouslysarde devprinted adiscovering contenterror and kept serving 404 pages. See Troubleshooting. sarde new sitesays tocdfirst: creating a site in a subfolder now ends withRun 'cd <path>' then 'sarde dev'instead of onlyRun 'sarde dev', which failed from the parent folder.- Links in timelines and cards are underlined: plain Markdown links inside a timeline entry or a card were told apart from the text by color alone, because both extensions opt out of prose styles. They now get the same subtle underline as links in body text, tabs, and steps.
- Lab pages show their tags: tags set in a lab page's frontmatter now appear under the title and link to their tag pages, as they already did on docs pages. The labs layout never rendered them, so they only appeared on the tag pages themselves.
- Code blocks follow dark mode: sites created with
sarde new sitesetdarkMode.selectorto.darkinkazari.config.yaml, a class Sarde never sets, so code blocks stayed light after switching to dark mode. Dark mode for code blocks now always comes frommarkdown.codeblocks.dark_mode_selectorinsarde.yaml, which matches the theme toggle by default, so existing sites are fixed on their next build. AdarkModeentry inkazari.config.yamlis ignored and the build warns about it; delete it. New sites no longer write one. - Section badges show in the sidebar: a
sidebar.badgeset in a section's_index.mdnever appeared on the section's group row, because only page entries copied the badge. Groups now show it, and asidebar.yamlbadge override still takes precedence. Sidebar badges also use a compact, normal-case style and wrap under a long label instead of overlapping it; badges in page content are unchanged. - The labs Overview entry is translatable: the first sidebar entry of a lab was hardcoded English. It now uses the
labs.overviewUI string. Templates can read the newNavNode.LabelKeyfield for generated entries like this one. - Deploys no longer publish
.sarde.lock: the GitHub Pages deployer copied the build's lock file into the published branch. Every deployer now skips it. - Custom deploy commands on Windows run as typed: a command that quoted a path, such as
echo done > "C:\My Site\log.txt", failed with "The filename, directory name, or volume label syntax is incorrect", because the quotes were escaped for a program other thancmd.exe. The command now reachescmd.exeunchanged, and a site path in Windows'\\?\form no longer makes the command run inC:\Windows. - GitHub Pages deploys read the remote from the site root: the deployer looked up the
originremote in the process working directory, sosarde deploy path/to/siterun from elsewhere pushed to the wrong repository or failed. Git and custom-command output no longer write straight to stdout. - GitHub Pages deploys keep files that start with an underscore: the branch GitHub Pages serves had no
.nojekyllfile, so Jekyll ran over the published site and dropped any file or directory whose name starts with_. Thegithubdeployer now adds an empty.nojekyllfile on every deploy. robots.txtno longer points at a missing sitemap: the robots plugin wrote aSitemap:line even when the sitemap plugin was off. The line now appears only when the sitemap plugin runs.search.enabled: falsenow disables search: the documented switch was never read: the index was still built, the runtime script still injected, and the header button still rendered. It now unregisters the search plugin and hides the button and modal, without warning about an unusedplugins.config.searchblock. The undocumentedsearch.provider: "disabled"value the template used to honor is gone;provideraccepts onlyorama.header.search: false, also previously inert, now hides the header button while keeping the index and modal.- Search results reach the highlighter: result links never carried the
?q=parameter that thesearch_highlighterplugin reads, so highlighting never triggered from search. Links now append?q=<query>before any#anchorwhenever the highlighter plugin is enabled, and stay clean otherwise. - Search modal strings are translatable: twenty-one runtime labels ("Recent", "Type to start searching", the result count, the full-search preview labels, and others) were hard-coded English and two of them overwrote labels the template had already translated. They now resolve through new
search.*i18n keys with_one/_otherplural forms and{count},{term},{min}placeholders, injected per page language, with English fallbacks in the script. The loading spinner gained an accessible label. - Client plugin config no longer clobbers other plugins: the inline
window.__SARDE__.pluginConfigscript assigned the object wholesale; it now merges, and every injected plugin gets an entry even with an empty config, sopluginConfig[slug]reliably signals that a plugin is active on the page. - Mobile sidebar drawer recovers from the back/forward cache: a docs or lab page cached with the drawer open came back with the header and content still inert. The drawer now resets to a closed, interactive state on restore, and backdrop cleanup no longer leaves a stray
transitionendlistener after each close.
Docs
- Added a Course Template page under Teaching: creating a course site from the template, what it contains, courses, schedules, announcements, labs, replacing the sample content, and publishing on GitHub Pages.
- Added a Template API page that maps what a template receives, the base shell contract, and where components, partials, and functions are documented, and a Route Data reference listing every field available as the dot context with the conditions under which each is populated. A test in
internal/enginefails when a struct field is added without a docs entry.
1.4.0 - 2026-08-28
Added
sarde pluginscommand prints the plugin catalog: every plugin accepted inplugins.enabled, covering built-in server and client plugins plus external plugins found under the project'splugins/directory, with descriptions, default state, and configurable fields.--format jsonemits the catalog for tooling. Metadata only:plugins.disabledand premium license checks are not applied, since builds enforce both.- Selective
sarde theme ejectaccepts paths to copy only those files or directories (sarde theme eject layouts/_blog/single.html css/blog.css); everything else keeps falling back to the embedded theme, including fixes in later releases.--name <slug>writes intothemes/<slug>/and sets the theme'sslug, and--listprints every ejectable path.theme.yamlis copied whenever the destination has none. - JSON error envelope for
build,dev, andvalidatewith--format json: a fatal error is emitted to stdout as a single JSON document carrying akind, amessage, and per-fielddetailsfor configuration validation failures, including the allowed values for enumeration checks. Human-readable error text still goes to stderr in both formats. - Ambiguous link detection reports a bare
name.mdlink, which never resolves by design, as anambiguous_linkfinding with fix advice instead of a generic broken target. - Link source positions are now included in link validation findings: the 1-based line and column of the link in its source file, in both the pretty and JSON reports.
- Typed plugin field blueprints publish plugin configuration fields with their type, label, hint, default, numeric range, and select options, so catalogs and settings interfaces can render them without re-parsing the YAML.
Changed
- Theme stylesheets fall back per file instead of all-or-nothing. Each of the 24 stylesheets Sarde looks for is read from the theme's
css/directory when present and from the embedded theme otherwise. A partialcss/directory no longer drops the stylesheets it omits, so a theme can ship only the ones it changes. - Server plugin defaults moved to per-plugin blueprint files, so
sarde pluginsand the build resolve the same field list. - Page cache schema version bumped to carry link positions. The first build after upgrading re-renders every page.
Fixed
sarde theme ejectnow places every template directory underlayouts/: the_taxonomy,_labs,_presentation,_slides, andshortcodesdirectories were written at the theme root, where the template and shortcode lookups never read them, so the ejected copies had no effect. Eject also no longer leaves empty_default/,_docs/,_blog/,components/, andpartials/directories at the theme root.- Install script authenticates GitHub API calls, so installs no longer fail against the unauthenticated rate limit.
Docs
- Added a Customization section covering layouts and templates, using themes, creating a preset, and creating a theme, including the blog-layout template lookup chain and template naming conventions.
- Completed the theme token reference: the text-safe hue variants, the aside code mix ratios, inline code text, and the
accent-textderivation were previously undocumented.
1.3.0 - 2026-08-06
Added
- Theme CSS hot-swap in dev mode: editing a theme stylesheet (under
themes/<name>/css/or the--theme-devsource tree) now reassembles the CSS bundle in place instead of running a full site rebuild. Typical refresh time is 2-7ms instead of ~1.3s. The browser restyles via the existing CSS swap mechanism without a page reload. - Draft banner: pages with
draft: truenow display a visual banner in dev mode so draft status is immediately visible in the browser. - Prose link underline in extensions: links inside tabs, details/accordion, steps, and columns now receive the same underline and hover styling as regular prose links. Previously the
not-contentwrapper on these extensions excluded them. - Aside link underline: links inside galaxy-style asides now show a visible underline at rest and shift to high-contrast text on hover, matching Starlight's aside link treatment.
- Hero CTA hover effects: the primary call-to-action button gains an accent glow, a brightness shift that works in both light and dark themes, and a press state on click.
Changed
- Kazari updated to v1.2.0.
- Active TOC link border thickened from 1px to 2px for better visibility.
- Active TOC link text in dark mode is now brighter so it stands out from the muted neighbors.
Fixed
color-scheme: lightoverride added solight-dark()CSS functions resolve correctly when the OS prefers dark mode.- Hardcoded dark-mode OKLCH values in banners and TOC links replaced with theme tokens so custom accent palettes apply everywhere.
- Telescope collection badge moved after the description for better scannability.
Docs
- Added a dedicated Updating Sarde page covering
sarde update, signed releases, package manager detection, and passive update notices. - Rewrote the Getting Started opening, dev server, build, and scaffold sections with warmer lead-ins for the educator audience.
- Documented the theme CSS fast path in the dev server reference page.
1.2.0 - 2026-08-04
Breaking
scroll_to_topthreshold is now pixels instead of a percentage: the default changed from30(meaning 30% of total scroll) to300(meaning 300 pixels from the top). Sites with a customthresholdvalue will see different behavior. A newpositionoption (left,center,right) controls button placement, defaulting to center.- Plugin slugs and config keys standardized to snake_case: all 11 client plugin slugs changed from kebab-case to snake_case (e.g.
scroll-to-toptoscroll_to_top). Legacy kebab-case spellings inplugins.enabled,plugins.disabled, andplugins.configare accepted as deprecated aliases with a build-time warning. Thescroll_to_topplugin's config field keys also moved from camelCase to snake_case (e.g.showTooltiptoshow_tooltip); old spellings are similarly aliased. Update yoursarde.yamlto the new names to silence the warnings.
Added
- Telescope command-palette plugin for fuzzy page navigation. Opens with a keyboard shortcut and lets readers search and jump to any page instantly. Enable it with
telescopeinplugins.enabled. - Custom
:::directives: sites and themes can define new Markdown:::block directives with a YAML schema, an HTML template, and an optional CSS sidecar placed in adirectives/directory.sarde new directive <name>scaffolds the starter files.sarde directives --checkvalidates definitions. The dev server live-reloads directive changes. - Plugin-shipped directives: external plugins can now include
:::directives by placing definitions inplugins/<slug>/directives/. - Social card redesign: cards now use a bottom-anchored editorial layout with a background gradient, corner logo mark, and optional watermark. New config options:
logo,watermark,watermark_opacity,accent_color_2,bg_image,bg_gradient,logo_size, and customfonts. Per-pageog_cardfrontmatter overrides colors and toggles. A disk cache under.cache/social_cards/makes repeat builds near-instant. - Site logo in the header:
site.logonow renders an image before the site title, with separate light and dark variants.replaces_title: truehides the text and shows only the logo. Raster logos getwidth/heightattributes to prevent layout shift. - Signed releases: release archives are now signed with ed25519.
sarde updateverifies the signature chain before applying an update. - Passive update notices:
sarde buildandsarde devprint a one-line notice when a newer release is available. Suppressed in CI, with--quiet, and when stderr is not a terminal. SetSARDE_NO_UPDATE_CHECK=1to disable. sarde updateimprovements: the update command now detects package-manager installs (Homebrew, Scoop, Chocolatey, winget) and prints the matching upgrade command instead of self-replacing. Release notes are shown before the confirmation prompt. A--yesflag supports non-interactive use. Permission errors print actionable advice.- Benchmark harness: new
sarde-benchtool measures build performance with median wall time, pages/sec, and per-phase breakdowns.sarde build --format jsonemits machine-readable build results. - Hero CTA icons: hero call-to-action buttons accept an optional
iconfield that renders a Lucide icon after the label. - Keyboard navigation enhancements: side navigation arrows are now mdBook-sized with reserved page margin so they never overlap content. A styled tooltip shows the target page title and keyboard shortcut on hover and focus. Below 1280px, the arrows restyle as compact floating buttons. Buttons auto-hide after inactivity and reappear on scroll. New options:
side_nav_size,show_tooltip,show_compact_nav,auto_hide,hide_delay,scroll_threshold. - Galaxy aside style: set
markdown.asides.style: galaxyfor a rounded-card look with accent ring, gradient glow, and softer hover. Thecautionandimportantaside variants now render with proper accent colors (amber and purple) instead of being unstyled. - Inline code chips: inline code now renders with an accent-tinted text color and a subtle border. Inside asides, the chip tints to match the aside accent.
fontUsedtemplate function: conditionally preloads a font file only when the current page layout actually uses it.- Blog list card restyle: blog list pages use updated card styling with a wider layout for sidebar pages.
- Sidebar hover accent: sidebar links change to the accent text color on hover.
- Docs preset accent: the docs theme preset now uses Starlight's default accent palette, with a dark-mode variant.
Changed
- Sidebar groups now use an accessible button with
aria-expandedinstead of adetails/summaryelement. - Prose links in body text are now underlined so they are not identified by color alone.
- The docs sidebar drawer uses
inertinstead ofaria-hiddenwhen closed.
Fixed
- SEO: JSON-LD is now rendered as raw script content instead of being double-encoded. Added a description fallback for pages without one, self-referencing hreflang links, and pagination indexing hints.
- Accessibility: the image lightbox and text highlighter are now operable by keyboard. ARIA roles are corrected and expanded state stays in sync across interactive components. Missing focus indicators are restored. Copy confirmations are announced to screen readers. The reading-position toast is pausable.
prefers-reduced-motionis respected in JavaScript-driven scrolling. - Theme contrast: all accent-colored text, subtle text, aside titles, and code block line numbers now meet WCAG AA contrast ratios. The
<meta charset>tag is emitted as the first element in<head>. Accent-fill foregrounds stay white in dark mode instead of flipping to black. Inline icons flow with text and honor explicit sizes. Long ToC headings stay inside the column on narrow desktop widths. List card titles are promoted toh2to avoid skipped heading levels. Missing labs tokens are defined so progress and badge styles render. Logical properties are used for lightbox, image-compare, and theme-toggle positioning. Mobile and icon-only controls meet the minimum touch target size. - Search: the
pagefind: falsefrontmatter opt-out is now honored. Documented field boosts (title 5x, tags 2.5x, description 2x) are applied. Per-language stemming and stopwords are active for 14 languages. Mermaid diagrams, KaTeX math, and code-block line numbers are excluded from the search index. - Summaries: raw
:::directive syntax no longer leaks into auto-generated descriptions, social card text, SEO meta tags, or RSS/Atom feeds. Pages built entirely from directives fall back to rendered-text extraction. - Font tokens: removed a self-referencing
var()cycle in font token fallbacks that caused the browser to ignore the declaration. - Sidebar collapse toggle enlarged and its hover state made visible in both light and dark themes.
- Search trigger uses the accent hover color and applies correctly in dark mode.
Improved
- Body font is preloaded with a metrics-matched fallback to reduce layout shift.
- The first image on each page is loaded eagerly; subsequent images honor the lazy-loading setting.
- The
scroll_to_topplugin usesrequestAnimationFrame-coalesced scroll updates and hides automatically near the page footer.
1.1.0 - 2026-07-29
Breaking
- The
last-updatedclient plugin has been removed: if yoursarde.yamllistslast-updatedunderplugins.enabled, delete that line or the build will fail withconfig validation failed: plugins.enabled[N]. The date is now rendered by the theme itself on docs, labs, blog, and default layouts, with no plugin and no JavaScript required. The plugin'sdate_formatoption lives on astheme.date_format. Relative time ("3 days ago") is no longer available; the date is always absolute.
Added
- Docs, labs, blog, and default layouts now render a "Last updated" date server-side, so it works without JavaScript and causes no layout shift.
- New
theme.date_formatsetting controls how that date is displayed. Acceptsshort,long,iso, or any Go layout string.
Changed
build.last_updatednow defaults togitinstead ofmtime. Page timestamps come from each file's last commit rather than its filesystem modification time, which in CI is the checkout time and made every page report the same moment on every deploy. Outside a git repository the behavior is unchanged, sincegitfalls back tomtimeautomatically. Setbuild.last_updated: mtimeto keep the old behavior.show_updated: falsenow hides only the "last updated" badge. The timestamp is still resolved, so sitemaplastmod, SEOdateModified, and feed timestamps for that page remain correct. Previously it suppressed the date entirely, stripping that metadata as a side effect.
Fixed
- "Edit this page" links now render on docs pages.
site.edit_urlwas previously honored only by blog and default layouts, so a docs site could configure it correctly and see links on blog posts alone. Sites that do not setsite.edit_urlare unaffected. show_updated: falsewas ignored on pages that setupdated:explicitly in frontmatter.- Sarde now warns once when
build.last_updated: gitcannot be used (git missing, not a repository, or a shallow clone) instead of silently falling back to file modification times. - The "Made with Sarde" footer credit linked to a domain that does not resolve. It now points to the documentation site. Because the footer template is compiled into the binary, sites built with 1.0.0 or 1.0.1 keep the old link until you upgrade and rebuild.
1.0.1 - 2026-07-25
Fixed
- Added
linux/arm64binaries, which were missing from the 1.0.0 release. ARM Linux servers and CI runners can now install with the standard script.
1.0.0 - 2026-07-24
The first stable release. Everything below has accumulated since the 0.1.x previews.
Breaking
- The
static/project directory is renamed topublic/. Files placed inpublic/are copied as-is to the output directory, exactly asstatic/worked before. Rename your project'sstatic/directory topublic/before your next build.
Fixed
- Frontmatter date fields (
date,updated,publish_date,expiry_date) may now be left empty. An empty value means "not set" instead of aborting the build, which is what an editor writes when a date field is cleared. - Frontmatter parse errors now name the file they came from, instead of reporting a bare parse failure with no location.
- Page cache now re-validates link targets on every build, even for cached pages. Warm builds report the same link coverage as cold builds, and renaming a linked page is detected without editing the source file.
- Custom heading IDs (
## Heading {#custom-id}) are now preserved. Previously, all heading IDs were overwritten with auto-generated slugs, causing links to custom anchors to be falsely reported as broken. - The
site.heading_linksconfig option is now functional. It controls whether clickable anchor links appear next to headings. Heading IDs are always assigned (required by the TOC, search, and link validation), but the visible anchor element is now toggled by this setting. - Content lint rules no longer trigger false positives inside fenced code blocks or inline code spans. Example syntax shown in documentation (e.g.,
in a code block) is skipped. - Content lint line numbers now match the source file on disk. Previously, line numbers were offset by the frontmatter block's height.
- The
same_site_policylink validation option works correctly on incremental rebuilds.
Improved
- Multi-language sites reuse taxonomy structures on body-only incremental rebuilds, skipping redundant taxonomy and data file processing per language.
- WebSocket hub uses ping/pong keepalive (30-second interval) to detect stale connections.
- Unchanged headings are reused during incremental rebuilds, and link validation is scoped to changed pages only.
Added
- External plugin system: install declarative
plugin.yamlpackages intoplugins/<slug>/withsarde plugin install, supporting conditional CSS/JS injection, template contributions (partials, components, shortcodes), and asset copying to the build output. No plugin code executes at build time. - New
plugins.disabledconfig list that turns off any plugin (built-in, client-side, or external) without replacing theplugins.enabledlist. - Offline license verification for premium external plugins, managed with
sarde license installandsarde license list. A missing or invalid license deactivates the plugin with a warning; the build never fails. - Announcement banners plugin with three display modes (stack, first, rotate), date scheduling, page targeting via glob patterns, and i18n message resolution.
- Social card auto-generation (1200x630 PNG/JPEG) with theme-aware colors, Inter font embedding, and title auto-sizing.
llms.txtgeneration for LLM discoverability, with optional blog content exclusion.- RTL CSS layout support.
- Language switcher and fallback notice components for multi-language sites.
- Tab state persistence across page navigation via
localStorage. - Collapsible sidebar groups with
collapsed_by_defaultconfiguration. - Mobile sidebar drawer.
- 11 client-side plugins: scroll to top, copy section link, external links, image lightbox, focus mode, keyboard nav, reading progress, reading preferences, reading position memory, search highlighter, and text highlighter.
- 25 Markdown extensions: aside, accordion, badges, cards, columns, details, figure, file tree, gallery, image compare, link buttons, link card, math, mermaid, steps, tabs, terminal, timeline, video, annotation, copy text, highlight, icon, kbd, and spoiler.