Found while reviewing #219, which had to regenerate static/css/tailwind.css.
Makefile:53 invokes a bare tailwindcss from PATH. There is no package.json, no version pin, and no hash pin anywhere. So the committed generated artefact depends entirely on whichever tailwind binary happens to be installed on the machine that ran make css.
Consequences, in order of how much they bite:
The CSS is a COMMITTED, SERVED artefact. Nothing in Dockerfile or script/ regenerates it, so whatever is in the tree is what users get. A regeneration on a different tailwind version silently changes the shipped stylesheet.
Two contributors running make css can produce different output from identical input, and neither can tell which is correct.
This is what already went wrong: #219 added ten classes to a template and the CSS was not regenerated, so the chevron <svg> rendered at the 300x150 CSS default on every delivery row. The gate could not catch it because nothing verifies the artefact matches the templates.
The tailwind CLI is not present on this host at all, so the regeneration had to be done in an ad-hoc container. It produced correct output, but by a route the repo does not define.
It also sits against the repo policy of hash-pinning external references, which the golangci-lint image digest and the script/bootstrap release-archive sha256 pins already follow.
Definition of done:
tailwindcss is pinned to an exact version, hash-pinned consistent with how the lint image and bootstrap archives are pinned
make css runs it in a container the same way linting does, rather than depending on a host binary
a check fails when static/css/tailwind.css does not match what the pinned tailwind produces from the current templates and input.css — that is the part that would have caught #219's missing classes
regenerating on the pinned version produces no diff against the committed artefact, or the one-time reflow is committed in the same change
Found while reviewing https://git.eeqj.de/sneak/webhooker/pulls/219, which had to regenerate `static/css/tailwind.css`.
`Makefile:53` invokes a bare `tailwindcss` from `PATH`. There is no `package.json`, no version pin, and no hash pin anywhere. So the committed generated artefact depends entirely on whichever tailwind binary happens to be installed on the machine that ran `make css`.
Consequences, in order of how much they bite:
- The CSS is a COMMITTED, SERVED artefact. Nothing in `Dockerfile` or `script/` regenerates it, so whatever is in the tree is what users get. A regeneration on a different tailwind version silently changes the shipped stylesheet.
- Two contributors running `make css` can produce different output from identical input, and neither can tell which is correct.
- This is what already went wrong: https://git.eeqj.de/sneak/webhooker/pulls/219 added ten classes to a template and the CSS was not regenerated, so the chevron `<svg>` rendered at the 300x150 CSS default on every delivery row. The gate could not catch it because nothing verifies the artefact matches the templates.
- The tailwind CLI is not present on this host at all, so the regeneration had to be done in an ad-hoc container. It produced correct output, but by a route the repo does not define.
It also sits against the repo policy of hash-pinning external references, which the `golangci-lint` image digest and the `script/bootstrap` release-archive sha256 pins already follow.
Definition of done:
- `tailwindcss` is pinned to an exact version, hash-pinned consistent with how the lint image and bootstrap archives are pinned
- `make css` runs it in a container the same way linting does, rather than depending on a host binary
- a check fails when `static/css/tailwind.css` does not match what the pinned tailwind produces from the current templates and `input.css` — that is the part that would have caught https://git.eeqj.de/sneak/webhooker/pulls/219's missing classes
- regenerating on the pinned version produces no diff against the committed artefact, or the one-time reflow is committed in the same change
make css runs the Tailwind standalone CLI at one exact version, fetched in a container (as make lint runs the linter) and pinned by sha256 like the other pinned downloads, never a binary from the host's PATH.
A check inside make check (and so the image build) regenerates the stylesheet from the templates and static/css/input.css with the pinned version and fails, naming the difference, when it does not match the committed static/css/tailwind.css. This is what would have caught a class used in a template but missing from the stylesheet.
In the same change, .btn-text (unused since #447) is removed from input.css, and tailwind.css is regenerated and committed, so the check starts green; the PR says how large the one-time change to tailwind.css is.
The README says how to change styles: edit the templates or input.css, then make css.
Model: opus-5-5
Plan.
- `make css` runs the Tailwind standalone CLI at one exact version, fetched in a container (as `make lint` runs the linter) and pinned by sha256 like the other pinned downloads, never a binary from the host's `PATH`.
- A check inside `make check` (and so the image build) regenerates the stylesheet from the templates and `static/css/input.css` with the pinned version and fails, naming the difference, when it does not match the committed `static/css/tailwind.css`. This is what would have caught a class used in a template but missing from the stylesheet.
- In the same change, `.btn-text` (unused since https://git.eeqj.de/sneak/webhooker/pulls/447) is removed from `input.css`, and `tailwind.css` is regenerated and committed, so the check starts green; the PR says how large the one-time change to `tailwind.css` is.
- The README says how to change styles: edit the templates or `input.css`, then `make css`.
Model: opus-5-5
make css now runs the standalone tailwindcss CLI, v4.2.1 (the version in the committed file's header), pinned by sha256, in a stage of the Dockerfile. A css-check stage, run by make check and required by the image build, fails when static/css/tailwind.css differs from what it generates. input.css names its sources (the templates, static/js/app.js, and the Go file that picks status colour classes) instead of having the whole checkout scanned. .btn-text is removed and tailwind.css regenerated, which only removes unused rules (28,364 to 23,891 bytes). The README has a new Stylesheet section on how to change styles.
Judgement call: a new base image, debian:bookworm-slim pinned by digest, for the glibc build of tailwindcss.
Unverified: the arm64 binary is pinned but was not run here.
Deviation: the check reports where the two files first differ, not which rules differ.
Model: opus-5-5
Built in https://git.eeqj.de/sneak/webhooker/pulls/473.
`make css` now runs the standalone `tailwindcss` CLI, v4.2.1 (the version in the committed file's header), pinned by sha256, in a stage of the `Dockerfile`. A `css-check` stage, run by `make check` and required by the image build, fails when `static/css/tailwind.css` differs from what it generates. `input.css` names its sources (the templates, `static/js/app.js`, and the Go file that picks status colour classes) instead of having the whole checkout scanned. `.btn-text` is removed and `tailwind.css` regenerated, which only removes unused rules (28,364 to 23,891 bytes). The README has a new Stylesheet section on how to change styles.
- Judgement call: a new base image, `debian:bookworm-slim` pinned by digest, for the glibc build of `tailwindcss`.
- Unverified: the arm64 binary is pinned but was not run here.
- Deviation: the check reports where the two files first differ, not which rules differ.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found while reviewing #219, which had to regenerate
static/css/tailwind.css.Makefile:53invokes a baretailwindcssfromPATH. There is nopackage.json, no version pin, and no hash pin anywhere. So the committed generated artefact depends entirely on whichever tailwind binary happens to be installed on the machine that ranmake css.Consequences, in order of how much they bite:
Dockerfileorscript/regenerates it, so whatever is in the tree is what users get. A regeneration on a different tailwind version silently changes the shipped stylesheet.make csscan produce different output from identical input, and neither can tell which is correct.<svg>rendered at the 300x150 CSS default on every delivery row. The gate could not catch it because nothing verifies the artefact matches the templates.It also sits against the repo policy of hash-pinning external references, which the
golangci-lintimage digest and thescript/bootstraprelease-archive sha256 pins already follow.Definition of done:
tailwindcssis pinned to an exact version, hash-pinned consistent with how the lint image and bootstrap archives are pinnedmake cssruns it in a container the same way linting does, rather than depending on a host binarystatic/css/tailwind.cssdoes not match what the pinned tailwind produces from the current templates andinput.css— that is the part that would have caught #219's missing classesPlan.
make cssruns the Tailwind standalone CLI at one exact version, fetched in a container (asmake lintruns the linter) and pinned by sha256 like the other pinned downloads, never a binary from the host'sPATH.make check(and so the image build) regenerates the stylesheet from the templates andstatic/css/input.csswith the pinned version and fails, naming the difference, when it does not match the committedstatic/css/tailwind.css. This is what would have caught a class used in a template but missing from the stylesheet..btn-text(unused since #447) is removed frominput.css, andtailwind.cssis regenerated and committed, so the check starts green; the PR says how large the one-time change totailwind.cssis.input.css, thenmake css.Model: opus-5-5
Built in #473.
make cssnow runs the standalonetailwindcssCLI, v4.2.1 (the version in the committed file's header), pinned by sha256, in a stage of theDockerfile. Acss-checkstage, run bymake checkand required by the image build, fails whenstatic/css/tailwind.cssdiffers from what it generates.input.cssnames its sources (the templates,static/js/app.js, and the Go file that picks status colour classes) instead of having the whole checkout scanned..btn-textis removed andtailwind.cssregenerated, which only removes unused rules (28,364 to 23,891 bytes). The README has a new Stylesheet section on how to change styles.debian:bookworm-slimpinned by digest, for the glibc build oftailwindcss.Model: opus-5-5