chore(release): 0.3.0, and make the release guards able to fail (#63) - #64
Merged
Merged
Conversation
Bumps both version declarations and rolls [Unreleased] into [0.3.0]. Two latent defects in the release plumbing are fixed first, because a PyPI upload can only be yanked, never replaced. The wheel guard could not catch what it exists to catch. It asserted >= 14 JinjaX templates, cotton wrappers and cotton partials — figures from 0.1.0, when 14 components in one set was the whole package. The real counts are now 21 wrappers and 105 partials, so a packaging regression that dropped all seven primitives from all five themes would have left exactly 14 and passed. Its required-assets list had also never picked up cf_ui_primitives.json from #52. Bumping the numbers would only move the staleness to the next component, so the mechanism is replaced: the guard derives its expectation from the source tree and asserts the wheel contains every shipped template and asset. It cannot drift, and it fails on one missing file rather than only on a missing fourteen. That swap carries the same hazard in a new place — if the source layout moves, the expectation set silently becomes empty and every wheel passes. So the guard asserts its own scan found something first, with a floor far below the real figure. The version was declared twice with nothing comparing them. pyproject.toml and _version.py both carry it and the tag-match step reads only the former, so a bump missing _version.py would publish a correctly-named wheel whose cf_ui.__version__ reported the previous version, permanently, with every gate green. Now asserted in the unit suite rather than at release time, because the drift is introduced when the bump commit is written. Both new guards were mutation-tested rather than assumed: version agreement clean pass; fails with _version.py left at 0.2.0 PEP 440 shape isolated by mutating both declarations to 0.3.0.dev0, so agreement holds and only the shape check can fire — it did wheel, intact pass, 237/237 wheel, 7 daisy primitives removed fail (the old guard's blind spot) wheel, primitives.json removed fail (never in the old list) wheel, src/ emptied of templates fail, refusing to pass vacuously Gate: 2313 passed, 13 skipped · ruff check and format clean · mkdocs build --strict clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NhqNRBg83czKfr8L6FF5xf
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.
Closes #63.
Cuts 0.3.0 — and fixes two latent defects in the release plumbing first, because a PyPI upload can only be yanked, never replaced.
The release
0.2.0 shipped 14 structural components and nothing underneath them. 0.3.0 is the primitives layer: seven components (
button,badge,heading,label,icon,box,prose) across five themes in both template sets, on one shared vocabulary, plus the daisy CDN fix (#56), the grid decision (#55) and djLint across both template trees.The changelog carries one upgrade note above the section list, because it is the only behaviour change a 0.2.0 consumer can be surprised by: on the daisy theme,
cf_ui_headnow emits Tailwind's Play CDN script alongside the daisyUI stylesheet. If you already have a real Tailwind build,CF_UI_DAISY_CDN = "off"restores the previous single-tag output. Every other theme is untouched.The wheel guard could not catch what it exists to catch
release.ymlasserted>= 14JinjaX templates,>= 14cotton wrappers and>= 14cotton theme partials. Those are 0.1.0 figures, from when 14 components in one template set was the entire package. The real counts are now 21 wrappers, 105 jinja, 105 partials — so a packaging regression that dropped all seven primitives from all five themes would have left exactly 14 per set and passed. The required-assets list had also never picked upcf_ui_primitives.json, added in #52, so a wheel shipping without it published clean.Bumping the numbers would move the staleness to the next component, so the mechanism is replaced rather than patched. The guard now derives its expectation from the source tree — every
.jinja/.html/.js/.mjs/.css/.jsonundertemplates/orstatic/— and asserts the wheel contains all of them. Wheel entries are already src-relative, so the paths compare directly with no rewriting. It cannot drift, and it fails on one missing file rather than only on a missing fourteen.That swap reintroduces the same hazard in a new place, which is worth naming rather than leaving for someone to find: if the source layout ever moves, the expectation set silently becomes empty and every wheel passes. So the guard checks its own scan found something before trusting a pass, with a floor (100) far below the real figure (237) — low enough never to need editing, high enough that an empty scan is caught.
The version was declared twice with nothing comparing them
pyproject.tomlandsrc/cf_ui/_version.pyboth carry it, andrelease.yml's tag-match step reads only the former. A bump that missed_version.pywould publish a correctly-named wheel whosecf_ui.__version__reported the previous version — indefinitely, with every gate green.tests/unit/test_version.pynow asserts they agree. It lives in the unit suite rather than inrelease.ymldeliberately: the drift is introduced when the bump commit is written, and that is when it should fail, not at the tag when the commit is already on master.Both guards were mutation-tested, not assumed
Per this repo's rule that a test has to be able to fail:
_version.pyleft at0.2.00.3.0.dev0cf_ui_primitives.jsonremoved from the wheelsrc/present but emptied of templatesThe third row is why it is two tests. A first pass mutated only
_version.py, which tripped the agreement assertion before the shape check ever ran — so the PEP 440 guard was passing untested. Mutating both declarations to the same suffixed value isolates it: agreement holds, and the shape check is then the only thing that can fire. It did.The wheel cases run the guard script extracted verbatim from
release.yml, not a paraphrase, against a wheel fromuv build --wheel— the same builder CI uses.Acceptance criteria
pyproject.tomlandsrc/cf_ui/_version.py.[Unreleased]rolled into[0.3.0] — 2026-07-31.cf_ui_primitives.jsoncovered — automatically, by being instatic/, rather than by being added to a list that will go stale again.v0.3.0— after this merges. Publish still waits on thepypienvironment gate.Gate
2313 passed, 13 skipped ·
ruff check src testsclean ·ruff format --check src testsclean ·mkdocs build --strictclean · wheel builds and passes the new guard at 237/237.Note on merging
Do not tag until this is on
master—release.yml's tag-match step compares the tag againstpyproject.tomlat the tagged commit, so a tag pushed before the merge would either miss the bump or point at a commit that is not what shipped.🤖 Generated with Claude Code
https://claude.ai/code/session_01NhqNRBg83czKfr8L6FF5xf