v0.4.0

Minor Changes

  • Markdown block elements support {.class #id key=value} attribute syntax beyond headings. Fenced code blocks accept attributes on the opening fence line. Blockquotes and tables accept them on the trailing line.

    ```go {.highlight #example}
    fmt.Println("hello")
    ```
    
    > This is important
    {.callout}

    Render hooks expose attributes via markup.attributes for all block element types. When no attributes are present, markup.attributes is an empty map. Block-level attributes activate when autoHeadingID is true (the default).

  • Add include function to the Go template engine, matching Liquid’s {% include %} for cross-file template inclusion.

    {{ include "partials/header" }}
    {{ include "partials/card" (dict "item" . "compact" true) }}

    Includes resolve from the layouts directory (.html extension, then raw name) and support nested calls.

  • Add limit array filter that returns the first N elements, giving Go templates parity with Liquid.

    {{ range limit .collections.blog 5 }}
      <h2>{{ .data.title }}</h2>
    {{ end }}
  • Liquid layout resolution falls back to bare extensions when .liquid files are missing. The engine tries .liquid first, then the bare extension (e.g., default.html, single.json) and parses the result as Liquid.

    Layout names with a recognized extension (e.g., layout: "base.html") serve as literal filenames. Bare names without an extension get the .liquid.html fallback.

  • Breaking: Remove filename-based layout matching. Alloy no longer matches content/docs/getting-started.md to layouts/getting-started.liquid. Layouts resolve via three mechanisms:

    1. Explicit layout: in front matter or _data.yaml cascade
    2. Date-based convention (post.liquid for section children, <section>.liquid for index pages)
    3. default.liquid fallback

    Add an explicit layout: to your front matter or _data.yaml if you relied on filename matching.

  • Breaking: Remove the permalinks: key from alloy.config.yaml. Set permalink patterns via _data.yaml cascade files in each section directory. Config files with a stale permalinks: key load without error.

    # content/blog/_data.yaml
    permalink: &#34;/:year/:month/:slug/&#34;
  • Render hooks receive richer context. Link hooks get markup.title from [text](url "title"). Heading hooks get markup.inner (rendered HTML), markup.text (plain text), and markup.attributes (goldmark attribute map). markup.id uses the explicit {#custom-id} attribute when present, falling back to the auto-generated slug.

    <h{{ markup.level }} id=&#34;{{ markup.id }}&#34; class=&#34;{{ markup.attributes.class }}&#34;>
      {{ markup.inner }}
    </h{{ markup.level }}>
  • Disable sitemap generation site-wide with sitemap: false in the config file. Alloy generates sitemaps by default when omitted.

    # alloy.config.yaml
    sitemap: false
  • Front matter permalinks accept full template syntax with {{ }} expressions. Alloy renders these through the configured template engine with a page.* context containing front matter fields, date, slug, summary, and collection.

    ---
    permalink: &#34;/{{ page.lang }}/{{ page.slug }}/&#34;
    ---

    Template and token syntax are separate modes. When {{ appears, token syntax (:year, :slug) does not resolve. A template permalink that renders to empty or whitespace is a fatal build error. Pagination template permalinks respect the configured engine.

  • Disable TOC generation site-wide with content.markdown.toc: false. Alloy populates page.toc for all Markdown pages by default when omitted.

    # alloy.config.yaml
    content:
      markdown:
        toc: false
  • Format layouts (JSON, XML, etc.) follow the same lookup order as HTML layouts. Alloy inserts the format name before the template extension, so a single bare layout name drives all output formats.

    layout: article
    outputs: [html, json]

    Extension-bearing layout names (e.g., article.liquid) with format outputs produce a build error. Set layout to a bare name.

Patch Changes

  • Accept "go" as an alias for "gotemplate" in the templates.engine config field. Alloy rejects unknown engine values with a clear error instead of falling through to Liquid.

  • Remove the extensionless (raw name) fallback from partial/include resolution. Both engines try recognized extensions only: Liquid checks widget.liquid then widget.html; Go templates check widget.html.

  • Fix Go template format layouts resolving to name.format.html (e.g., default.json.html) instead of the bare format extension (default.json, feed.xml).

  • Fix language-specific _data.yaml permalink patterns being ignored in multi-language builds. A permalink in content/es/blog/_data.yaml applies to pages in that directory instead of falling back to the default path-based URL.

  • Tighten {% inline %} tag validation. Paths must start with ./ or ../. Alloy accepts text-based extensions only (.svg, .html, .css, .js, .json, .xml, .yaml, .md and others). Binary types like .png produce an error pointing to <img>.

  • Fix nested _data.yaml permalink patterns being ignored. Alloy resolves permalinks from the nearest _data.yaml in the directory tree, so subdirectories override their parent’s URL pattern.

    # content/blog/_data.yaml
    permalink: &#34;/blog/:slug/&#34;
    
    # content/blog/posts/_data.yaml
    permalink: &#34;/blog/:year/:month/:slug/&#34;
  • Remove the broken fingerprint template filter. It hashed the path string instead of file contents and emitted no renamed file, so fingerprinted URLs would 404. Use cachebust for query-string cache busting.

  • absolute_url prepends the site’s configured baseURL when called without an argument. The url filter prepends the path portion of baseURL to relative paths.