A footer wanting to print your version had nowhere to get it. `poops.json` is JSON, so a value cannot compute one, and a theme rendering `site.footer` renders it verbatim — which left the number hardcoded in a second file, silently wrong from the next release on.
exec hooks can run before a stage, not only after it. A generator that has to write
before the stage reads — fetching posts the templates render, writing a data file — had
nowhere to go. Hijacking an earlier stage's hook worked in a build, where the pipeline order
put it first, and then silently stopped working in watch, because a markup-only edit never
fires the earlier stage. Every stage now also takes pre:<stage>, fired in build and watch
alike: "pre:markup": "node script/fetch-posts.mjs". pre:build runs once before the
pipeline starts, which is where clearing dist or fetching content belongs, and pre:styles
fires ahead of Sass — the pair brackets the whole style pipeline, since styles fires past
PostCSS. post:<stage> is accepted as the explicit spelling of a bare <stage>, so a config
can read pre:markup above post:markup instead of above something that looks like a typo.
markup.options.site values are filled from package.json, with the same tokens a
banner uses. "footer": "MyLib v{{ version }} — MIT licensed" now prints the version
the manifest holds, filled at build time from the one place it lives. {{ name }},
{{ homepage }}, {{ license }}, {{ author }}, {{ description }} and {{ year }}
fill the same way, at any depth — a title inside site.links gets them too — and the
pkg option still says which package.json to read.
A page can join a sidebar section without moving. The nav tree is built from urls, so
grouping pages that already sit side by side meant a subdirectory — and a subdirectory
changes their url, every link pointing at them and every search result that has been
indexed, to buy one heading in a sidebar. navGroup: "No APG pattern" in front matter
synthesizes that section beside the page's ungrouped siblings and nests the page inside it,
url untouched. The label is used as written rather than humanized, the section sorts where
its first child would, and grouping is per directory — the same name in two directories
opens a section in each, instead of one reaching across the tree. A page that is itself a
section (a directory's index page, or the homepage) is refused with a warning: its
subpages would stay behind on the ungrouped path and split the section in two.
A banner token with no matching package.json field no longer prints undefined.
{{ homepage }} in a package that declares none put that literal word at the top of
every built file, which reads as Poops being broken rather than as a field you never
filled in. The token is now left as written, naming what is missing.
A PostCSS pass no longer leaves a stale source map beside its output. Poops wrote the processed CSS and threw away the map PostCSS had composed for it, so whatever map the Sass stage left on disk stayed there describing lines the pass had since moved — a debugger pointing confidently at the wrong rule. The composed map is now written next to the output, shifted past the banner line like every other stage. An input with no map still produces none.