Decisions
Short answers to "why is it like this?", one paragraph each, with a link to the pull request or issue where the choice was made when there is one.
Why a tree and not a flat route list
Taxus 0.x ran on a flat list of routes: one record per file, keyed by
URL. That list could not answer "which pages belong to this section?"
without guessing from URL prefixes, and the guess was wrong: the root's
listing matched every URL that started with /, so the home page listed
the whole site (#70).
A tree answers membership by construction: a section's children are the
nodes under it, nothing more. The route list still exists, but it is now
a projection of the tree (RouteRegistry::from_tree), so the two cannot
disagree. The tree was introduced in
PR #71 and wired
through the build in PR #72.
Why derivations are free functions rather than methods
A method on SiteTree suggests the answer is a property of the tree. A
listing is not: it depends on a section's sort_by, on whether drafts
count, on which donors pages_from names. Free functions make those
inputs explicit parameters, so a reader sees at the call site what the
answer depends on. It also keeps the tree type small enough to read in
one sitting, and lets the generator add its own derivations (feed pages,
sitemap entries) in the same shape without touching the domain crate.
The rule for choosing is in Derivations.
Why the domain crate has no I/O
taxus-domain never reads a file, renders Markdown or runs a template.
Everything it does can be tested with a SiteTreeBuilder and a few
string literals, and its tests run in milliseconds with no fixtures. It
also fixes a boundary: file-name conventions such as the date prefix are
interpreted by the generator before a path enters the tree, so the model
does not depend on how files happen to be named. The decision is recorded
in PR #71 and the
boundary rules were settled in review
(commit feca3c7).
Why there are no themes
A theme is a second source of templates, styles and static files that
the generator has to merge with the site's own, with rules for which one
wins. Taxus has one templates/ directory, one styles/ directory and
one static/ directory, and taxus init copies a complete starting
set into them. Everything a site renders is in the site. There is no
lookup order to learn and no theme update that can change a page under
you. A site that wants to share a look with another copies the files.
There is no issue for this; it is the absence of a feature.
Why islands instead of a JS framework
Most pages need no script. A framework that renders the page in the
browser makes every page wait for JavaScript. Islands keep the page as
HTML and add interactivity only where a template asks for it: the
island() function renders a Yew component to HTML at build time, and
the WASM client hydrates that one element in the browser. The HTML is
readable before the WASM loads, and a page without islands ships no
component code at all. Yew was chosen because the same component
compiles to both the build-time renderer and the browser, so there is
one source for each island. See Islands. The runtime
plumbing that lets build() render islands from any calling context is
PR #63
(#37).
Why the image quality is part of the cache key
Hero image variants are named by a hash and skipped when the files
already exist. If the hash covered only the image bytes, changing
images.quality in site.toml would do nothing until someone deleted
dist/images/. Folding the effective quality into the hash means a
quality change re-encodes on the next build, and an unchanged image keeps
its file names, so cached URLs stay stable across deployments. The hash
is of the file's contents rather than its path and modification time so
that a fresh checkout produces the same names, which is what lets the
golden output test pin them.
PR #65 made quality
take effect (#34);
PR #81 moved the key
to content.
Why section listings are direct children only
Before the tree, a section listed every page whose URL started with the
section's URL. That made the home page a copy of the whole site and made
nested sections list each other's pages. Listing direct children only
matches what the directory shows, and pages_from lets an author opt in
to more. The alternative, Zola's transparent, pushes pages upward from
the child; pages_from pulls from the parent, so the section that shows
the pages is the one that declares it.
PR #76
(#70).
Why feeds carry dated pages only
A feed entry must have a publication date. The old feed put every document in, including the home page and section indexes, and stamped undated ones with the build time, which re-announced them to every subscriber on every build. PR #77 (#44).
Why dates come out of file names
2026-04-03-project-launch.md sorts by date in a file listing, which is
useful on disk. The date is not part of the page's name, so it is removed
from the slug and, when the frontmatter sets no date, used as the
default. Metadata belongs in frontmatter; the file name is a storage
convention the parser interprets.
PR #68
(#67).
Why the slug override stays inside its section
slug = "renamed-entry" on content/blog/e.md used to move the page to
/renamed-entry/. A slug is one segment, so it replaces the last
segment of the node path and nothing else: /blog/renamed-entry/. A site
that relied on the old address keeps it with aliases.
PR #79.