fix(ui): rebuild stale Tailwind bundle — CI now fails on a stale one - #137
Merged
Merged
Conversation
The committed bundle (tailwind.625748cf.css) was last rebuilt in May, so every utility class a template or script started using afterwards never reached the browser: 46 of them. On the live site that meant a three-column footer grid without gaps, a single-column self-hosting checklist, un-numbered "How it works" steps on /compress, the PDF tools and /redact, notice boxes without their tint (a light-grey default border on the dark UI instead), and a cookie notice that only stayed at the bottom thanks to a positioning shim in style.css. The rebuild (tailwind.8be3407b.css) emits all of them; the shim is removed. The one class dropped, grid-cols-3, is used nowhere. Root cause: docs/tailwind-build-setup.md always described a CI step that rebuilds the bundle and fails on a difference, but ci.yml never had it. It is now the "Tailwind bundle freshness gate". `git status --porcelain` over app/static/css/ catches a rotated hash (tracked deletion plus untracked file) as well as any content drift; it runs on its own line so a git failure aborts the step instead of passing it. A failing run uploads the CI-built bundle as an artifact, since Tailwind also scans untracked local files CI never sees. Because CI now downloads and executes the Tailwind CLI on every run, build-tailwind.sh verifies each download against SHA-256 pins taken from the v3.4.17 release's sha256sums.txt before it replaces the binary or becomes executable. A mismatching binary in .tools/ (truncated download, old VERSION) is re-downloaded, a mismatching download is deleted and the script exits 1, and curl -f turns an HTTP error into a failure instead of a saved error page. Also: the "Compare plans" heading on /pricing used text-h-section, which does not exist (the token is text-h-sect), so it rendered as body text. The CachingStaticFiles comment in app/main.py claimed no static file is content-hashed; the Tailwind bundle is, and takes the immutable branch. Considered and rejected: a pytest that greps template classes against the bundle — heuristic and blind to classes built in JS, whereas rebuilding the real bundle is exact; caching the CLI with actions/cache — saves seconds but adds another pinned third-party action for a fast in-GitHub download. Verified: rebuild is idempotent (same hash on every run); the script's three paths (valid binary, corrupt download, missing binary) exercised offline with a curl stand-in; before/after computed-style comparison on two local servers at 1280 px and 375 px (footer 3 -> 4 columns with 24/6 px gaps, checklist 1 -> 2 columns, /formats grids 1 -> 2, "How it works" lists numbered, notice boxes tinted, cookie notice still fixed at the bottom, no horizontal overflow). Reviewed by security-auditor (pass) and code-reviewer. Full suite 1177 green (60 skipped); ruff + format + template-class gate + i18n-drift clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MrChengLen
force-pushed
the
pr-tailwind-rebuild
branch
from
September 25, 2026 13:20
da4c438 to
a3ae7bd
Compare
This was referenced Sep 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
tailwind.625748cf.css→tailwind.8be3407b.css). The committed bundle dated from May; 46 utility classes that templates and scripts use never reached the browser.Tailwind bundle freshness gateinlint-and-test: rebuilds the bundle and fails ifapp/static/css/changes. On failure it uploads the CI-built bundle as thetailwind-bundle-ci-builtartifact.scripts/build-tailwind.sh:sha256sums.txt..tools/is re-downloaded.curl -fsSL --retry 3.#cookie-noticepositioning shim fromstyle.css; its classes are now in the bundle./pricingheading class typo (text-h-section→text-h-sect).cookie_notice.html,app/main.pyCachingStaticFiles).Why
Visible on the live site until now:
/compress, the PDF tools and/redacthave no numbers./pricing: the "Compare plans" heading renders as body text.Root cause:
docs/tailwind-build-setup.mdalways described this CI gate, butci.ymlnever had it.Verification
ruff check,ruff format --check,check_template_classes.pyand i18n drift-check are clean. Full suite 1177 passed, 60 skipped..tools/), missing binary (verified install, then build)./formatsgroupslist-style: nonedecimalNo horizontal overflow at either width.
security-auditor: PASS with notes. Both Low findings are fixed: verify-before-install, andgit statuson its own line.code-reviewer: ready with nits. Suggestions applied./pricingand/enterpriseare disabled locally. They get checked on filemorph.io after deploy.Notes for the maintainer
protect-mainhas "Require branches to be up to date before merging" off. Two PRs that each rebuild the bundle merge without a conflict: git sees a delete plus an add, not a rename. That would leave two bundles onmain. Right now no other open PR touches the bundle.docker.yml:.github/workflows/docker.ymlonmainwas committed with CRLF line endings (PR ci(docker): allow a manual rebuild via workflow_dispatch #129). Every checkout therefore shows it as modified. This PR does not touch it.🤖 Generated with Claude Code