/bˈɒləks/

Writing for this site

Where a new page goes, what its front matter says, and every piece of markup the templates add on top of Markdown.

Document
SITE-01
Rev
A
Status
Stable
Published
Length
1,276 words
Content
42cd4e6

The site is built with Zola 0.23. Every page is a Markdown file under content/ with a block of TOML at the top. Zola runs each file through its template engine, Tera, then through Markdown, and the templates in templates/ turn the result into a numbered, dated document like this one.

Preview while you write

nix develop -c zola serve
# or with comma
, zola serve 

This serves the site at an available port and rebuilds on every save. Broken internal links and unknown code languages show up here as errors, so leave it running in a terminal while you write.

Before publishing, build once the way the deploy does:

nix flake check

It fails on the same things zola serve complains about, so if it passes, the site will build.

Where a page goes

Kinds of page
You’re writingFileNumbered
A postcontent/writing/slug.mdW-001, by date
A post with imagescontent/writing/slug/index.md, images beside itW-001, by date
A short notecontent/notes/slug.mdN-001, by date
A guidecontent/guides/category/slug.mdNIX-01, by weight
A Ukrainian versionthe same name with .uk.mdcounted separately

The file name becomes the address: content/writing/pinning-nixpkgs/index.md is published at /writing/pinning-nixpkgs/. Use lowercase Latin letters and hyphens, even for a Ukrainian page, so the address stays readable when shared.

Numbers are counted, not stored. A post dated before the newest one shifts the numbers after it; a guide with a lower weight shifts the guides after it. If a number must never change, pin it with id under [extra].

Front matter

The block between the +++ lines is TOML. Keys Zola knows go at the top; keys this site adds go under [extra]. A key in the wrong place is silently ignored, so if a setting has no effect, check which side of [extra] it’s on.

Posts

+++
title = "Pinning nixpkgs without flakes"
date = 2026-10-21
updated = 2026-10-27           # optional; shown as "Revised"
description = "One sentence under the title and in link previews."
[taxonomies]
tags = ["nix"]
[extra]
toc = true                     # contents list under the title block
revisions = [                  # optional; the last one is the current revision
  { rev = "A", date = "2026-10-21", note = "First published." },
  { rev = "B", date = "2026-10-27", note = "What changed, in one line." },
]
# numbered = false             # no section numbers on headings
# id = "W-100"                 # pin the document number
+++

When you add a revision, also change updated. The revision list fills the table at the end of the page; updated is what the register and the feed use.

Notes

A note needs only a date. Without a title, the register shows the first line of the text instead.

+++
date = 2026-10-25
[taxonomies]
tags = ["nix"]
+++

Guides

A guide takes everything a post takes, plus weight and status:

+++
title = "Garbage collection and disk space"
date = 2026-06-30
updated = 2026-10-20
weight = 20                    # position in the category; leave gaps
[taxonomies]
tags = ["nix"]
[extra]
status = "stable"              # draft, stable or outdated
# outdated_note = "Written for fish 3.6 and not yet checked against fish 4."
+++

Weights of 10, 20, 30 leave room to put a guide between two others later without renumbering every file.

draft and outdated add a banner under the title. outdated_note replaces the banner’s default text, so say what is out of date and since when.

A new guide category

A category is a folder under content/guides/ with an _index.md:

+++
title = "Nix"
weight = 1                     # order on /guides/
sort_by = "weight"
template = "category.html"
generate_feeds = true          # /guides/nix/atom.xml
[extra]
code = "NIX"                   # NIX-01, NIX-02…
+++

One line about what's in this category.

Without _index.md the folder isn’t a category, and its guides get no numbers. A category that exists only in Ukrainian needs just _index.uk.md.

Headings

Start sections at ##. The page title is the only #. ## and ### get section numbers (1, 1.1…) and appear in the contents list; go deeper than ### only for a run of short items.

Zola makes each heading’s anchor from its text, so renaming a heading breaks links to it. To keep an anchor stable, or to give a Ukrainian heading a short Latin one, set it yourself:

## Майстер-пароль {#master-pass}

Text

Smart punctuation is on: "quotes" become curly, -- becomes an en dash and --- an em dash. Ukrainian takes «ялинки» instead of straight quotes; type them directly.

<mark>text</mark> highlights the one phrase on the page that matters most. Use it once, if at all.

Definition lists work for glossaries:

Closure
: A store path and everything it refers to.

Raw HTML goes straight into the Markdown when nothing else will do.

Link to another page by its file, starting with @/:

[garbage collection](@/guides/nix/garbage-collection.md)
[паролі](@/guides/cybersecurity/passwords.uk.md#master-pass)

Zola checks these when it builds. If the target is moved or deleted, the build fails instead of publishing a dead link, and the target page lists every page that links to it under “Referenced by”. The path always starts from content/ and includes .uk for a Ukrainian file.

Links to other sites are ordinary Markdown.

Code

Name the language after the opening fence. An unknown name fails the build rather than silently showing plain text, which catches typos like bahs. Use txt for output that isn’t code.

Add the file name and highlight lines with options after a comma:

```nix,name=shell.nix,hl_lines=2-3
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
  packages = [ pkgs.zola ];
}
```

Which renders as:

{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
  packages = [ pkgs.zola ];
}

Callouts

GitHub’s alert syntax, with five kinds:

> [!WARNING]
> Running this deletes every old generation.

Note and Tip are black, Important is blue, Warning and Caution are red. Use red only for something that loses data or access.

A callout has no title line. To label it, start with a bold phrase: > **Analogy.** ….

Footnotes and sidenotes

A footnote is plain Markdown:[1]

A claim that needs a source.[^src]

[^src]: The source.

Prefer footnotes. Reach for a sidenote only when the reader on a phone should be able to open the note in place, without jumping to the end:Tap the number on a phone to open it. Markdown works inside.

A sentence that needs a note.{{ <sidenote text="The note." /> }}

Tables

An ordinary Markdown table works. To number it and give it a title, wrap it:

{% <tbl caption="Approaches compared"> %}
| Approach | Update with |
|---|---|
| npins | `npins update` |
{% </tbl> %}

The caption gets “Table 1.” in front, counted from the top of the page.

Figures and images

{{ <figure src="diagram.svg" alt="What the image shows" caption="Optional, *Markdown* allowed" /> }}

This adds “Figure 1.” to the caption; on wide screens the caption moves into the margin. Write alt for someone who can’t see the image: what it shows, not what it’s called. The caption is for everyone and can hold a link, which is where a credit goes.

Where the file lives decides how src is written:

  • Beside the page. Turn post.md into post/index.md and put the image in post/. Then src="diagram.svg". A Ukrainian version can have its own image, diagram.uk.svg.
  • In static/. static/images/x.png is served at /images/x.png, so src="/images/x.png". Use this for an image shared between pages, or for a guide that is a single file.

Prefer SVG for diagrams. Keep photos under a few hundred kilobytes.

Change bars

When a revision changes a paragraph, mark it, so returning readers can find what’s new:

{% <changed rev="B"> %}
The paragraph that changed.
{% </changed> %}

A bar appears in the margin beside it, with the revision letter on wide screens. Add the revision to revisions in the front matter as well.

Task lists

- [ ] Install a password manager.
- [ ] Change the email password.

They render as empty boxes, for a “do this today” list at the end of a guide. The boxes can’t be ticked.

Literal template braces

Tera reads the page before Markdown does, so a literal {{ or {% anywhere in the text, even inside a code block, is treated as a template tag and usually fails the build. Wrap such passages:

{% raw %}
Whatever contains {{ or {%.
{% endraw %}

The examples on this page are wrapped that way.

Ukrainian pages

A Ukrainian page is the same file name with .uk before .md, next to the English one: passwords.md and passwords.uk.md. When both exist, the language link in the header goes straight between them; otherwise it goes to the other language’s home page.

Either can exist alone. Each language has its own numbers, tags and feed, so a page that exists only in Ukrainian takes numbers only from other Ukrainian pages.

Sections need their own _index.uk.md to appear in Ukrainian at all. Interface text, the labels around the content, is in zola.toml under [languages.uk.translations].

Before publishing

  • nix flake check passes.
  • description is one sentence and says what the reader gets.
  • Every image has alt text.
  • A revised page has a new revisions entry and a new updated date.
  • A guide has weight and status.
  • If there’s a translation, it has the same file name with .uk.
  1. Like this one. On wide screens it sits in the margin; on phones it waits at the end under “Notes”. ↩