chore(release): 0.6.0 stable, trusted publishing, and a package fit to hand to someone (#47, #49) - #48
Merged
Conversation
Promotes 0.6.0b0 to a final release with no code changes, and adds the workflow that publishes it. cf-ui's wheel declares `component-framework>=0.4` — the `[tool.uv.sources]` git pin does not travel into metadata — so `pip install cf-ui` cannot resolve until this package is on PyPI. This repo goes first. Publishing the beta would have worked only by accident: a specifier with no pre-release marker resolves to a pre-release just when no final release exists, so the resolution would have changed silently the first time any stable version appeared, and a stable cf-ui would have depended on a beta. Publishing uses trusted publishing (OIDC) rather than a stored API token. Two guards before upload, because a bad upload can be yanked but never replaced: `twine check` on both artifacts, and an assertion that the tag matches the version in pyproject.toml. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NhqNRBg83czKfr8L6FF5xf
This was referenced Jul 31, 2026
Closed
Merged
Built the wheel, installed it into a clean venv, and checked every documented
import against the installed package rather than the source tree. The package
was installable; it was not yet fit to hand to someone.
The README described an install nobody could perform. Every instruction was
`pip install -e ".[extra]"` — an editable install from a checkout — and the
section opened with "Not on PyPI yet", which stops being true the moment
0.6.0 uploads. The README *is* the PyPI landing page, so the one line a
visitor arriving there needs was the one that was missing. It now leads with
`pip install "component-framework[fastapi]"`, notes the quoting (bare
brackets are glob syntax in zsh), shows the missing-extra ImportError a
reader will actually meet, and demotes the editable install to a contributor
note.
Six samples did not work:
* README composition — imported `SlotComponent` and `CompositeComponent`
from `core.composition`, which exports neither. The whole example was
invented, down to a `components = {...}` attribute nothing reads. The real
API is a `Component` with a `slots` ClassVar, assembled with `compose()`.
* README testing — `mount_component`, `dispatch_event`, and `assert_state`
with a positional component argument; none exist. It also omitted the
required `component_class`.
* `docs/LOCKED_FIELDS.md` — imported `Component`/`registry` from the
top-level package, which exports only `CorruptStateError`/`StateSigner`.
* `docs/CBV_GUIDE.md` — imported `RateLimitMixin` from `adapters.django_views`;
it lives in `adapters.django_ratelimit`, as the README said all along.
* Two `docs/examples/ecommerce.md` blocks did not parse — a bare `...` in a
list literal, and a method whose body was only a comment.
Packaging:
* `py.typed`. The codebase is type-checked in CI and ships
`component-client.d.ts` for the JS, but with no PEP 561 marker every Python
consumer running mypy or pyright saw the package as untyped. The types
existed; they were not advertised.
* A `testing` extra declaring pytest. `component_framework.testing` imports
pytest at module scope, but pytest was only reachable through `dev-base`,
so following the README's testing sample after
`pip install component-framework[fastapi]` raised ModuleNotFoundError.
* Classifiers for the license (what PyPI's sidebar reads), the frameworks the
adapters target, and `Typing :: Typed`. Seven became fifteen.
* The release workflow now refuses to hand off a wheel missing the client
assets or `py.typed` — both ship inside the package with no explicit
include, so a regression there yields a wheel that installs and imports
cleanly and then serves no interactivity. Verified it can fail: a wheel
rebuilt without them exits 1 naming both.
Why all six shipped: nothing here read the documentation.
`tests/test_docs_samples.py` now parses every fenced python block in the
README, CONTRIBUTING and docs/, and resolves every `from component_framework…
import …` against the real package. Run against the previous text it goes red
on all six, plus the "Not on PyPI" claim — 8 failures, of which 3 were ones
the manual audit had missed. Ported from cf-ui's guard of the same name,
which exists because `ComponentCatalog` and `<CfCard>` sat in that README for
two releases.
Verified: 659 tests pass, ruff clean, and a clean install now resolves 34
documented imports where it resolved 15.
Closes #49
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NhqNRBg83czKfr8L6FF5xf
Conflict was the Installation section, which this branch rewrote wholesale and master only re-linked. Kept this branch's version and re-ran the absolutiser over it — the new section had introduced fresh relative links of its own (CHANGELOG.md, CONTRIBUTING.md). Merging surfaced a hole in the guard. The README claimed twice that the package was not on PyPI, in two different wordings, and `test_the_readme_documents_installing_from_pypi` pinned the exact string "Not on PyPI" — so it sailed straight past "Not yet published to PyPI" in line 3 of the same file. The pattern is now a regex over both phrasings, and a companion test pins it against the two real sentences it replaced. Two link guards added while the rule is fresh: * every README link must be absolute — relative ones are dead on the PyPI page, and the docs site cannot cover for them because it is a pdoc API reference that does not publish the markdown guides at all; * every absolute link into this repo must point at a path `git ls-files` knows. An absolute link to a moved file 404s silently, which is worse than the relative link it replaced. Checked against git rather than the filesystem: a path that exists locally but is untracked still 404s on github.com. All three go red on their exact defect (reintroduced one at a time) and green when restored. 662 tests pass. 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 #47. Closes #49.
Promotes
0.6.0b0to a final release, adds the workflow that publishes it, and fixes the things that made the package installable but not yet fit to hand to someone.Why stable, and why this repo first
cf-ui's wheel declares
Requires-Dist: component-framework>=0.4— the[tool.uv.sources]git pin does not travel into wheel metadata — sopip install cf-uicannot resolve until this package is on PyPI. Ordering is forced.Publishing the beta would have worked only by accident. A specifier with no pre-release marker resolves to a pre-release only when no final release exists, so
>=0.4→0.6.0b0today, but that resolution would flip silently the first time any stable version appeared — and a stable cf-ui 0.2.0 would have shipped depending on a beta.Publishing (#47)
Trusted publishing (OIDC), not a stored API token. The
publishjob requestsid-token: write, mints a short-lived token, and PyPI exchanges it for upload rights. Gated behind apypiGitHub environment, so you can add a required-reviewer rule if you want a human ack on every release.Three guards run before anything is uploaded, because a bad upload can be yanked but never replaced:
twine checkon both the wheel and the sdistpyproject.toml's version, so av0.6.0tag cannot publish a wheel built from a different numberpy.typed. Those ship inside the package with no explicit hatchling include, so a packaging regression produces a wheel that installs and imports cleanly and then serves no interactivity — nothing in the test suite would notice, because every test runs against the source tree. Verified it can fail: a wheel rebuilt withoutpy.typedandcomponent-client.jsexits 1 naming both.What the audit found (#49)
I built the wheel, installed it into a clean venv, and checked every documented import against the installed package rather than the source tree. Six samples did not work, and two pieces of metadata were missing.
The README described an install nobody could perform
Every instruction was
pip install -e ".[extra]"— an editable install from a checkout — and the section opened with "Not on PyPI yet", which stops being true the moment this uploads. The README is the PyPI landing page, so the one line a visitor arriving there needs was the one that was missing.It now leads with
pip install "component-framework[fastapi]", notes the quoting (bare brackets are glob syntax in zsh), shows the missing-extraImportErrora reader will actually meet, and demotes the editable install to a contributor note.Six samples that do not run
SlotComponent,CompositeComponentfromcore.compositioncomponents = {...}attribute nothing reads. Real API: aComponentwith aslotsClassVar, assembled withcompose()mount_component,dispatch_event,assert_state(component, …)component_classdocs/LOCKED_FIELDS.mdfrom component_framework import Component, registryCorruptStateError,StateSignerdocs/CBV_GUIDE.mdRateLimitMixinfromadapters.django_viewsadapters.django_ratelimit— as the README said all alongdocs/examples/ecommerce.md×2...in a list literal, and a method whose body was only a commentPackaging
py.typed. The codebase is type-checked in CI and shipscomponent-client.d.tsso TypeScript consumers get types for the client JS — but with no PEP 561 marker, every Python consumer running mypy or pyright saw the whole package as untyped. The types existed; they just were not advertised.testingextra declaring pytest.component_framework.testingimports pytest at module scope — it ships fixtures and a pytest-style base class — but pytest was only reachable throughdev-base, so following the README's testing sample afterpip install component-framework[fastapi]raisedModuleNotFoundError.Typing :: Typed.Why all six shipped
Nothing here read the documentation.
tests/test_docs_samples.pynow parses every fencedpythonblock in the README, CONTRIBUTING anddocs/, and resolves everyfrom component_framework… import …against the real package.Run against the previous text it produces 8 failures — all six samples, plus the "Not on PyPI" claim. Three of those eight were ones my own manual audit had missed (
RateLimitMixinand both ecommerce parse errors), which is the argument for the guard rather than a careful read.Ported from cf-ui's
test_docs_samples.py, which exists for exactly the same reason:ComponentCatalogand<CfCard>sat in that README for two releases.Verified
License-File: LICENSE, 15 classifiers,py.typedpresent,twine checkPASSED on wheel and sdistcomponent-frameworkis free on PyPIManual step before this can publish
Trusted publishing needs a pending publisher registered before the first upload — the workflow will fail without it. On pypi.org → Your projects → Publishing → add a pending publisher:
component-frameworkfsecada01component-frameworkrelease.ymlpypiThen merge this and push a tag:
git tag v0.6.0 && git push origin v0.6.0.Not in this PR
justfile,specs/,.claude/) is untouched — none of it is staged here.Documentationproject URL points at the live docs site, so a PyPI visitor is one click from working links.typrek hook exits 1 locally onmasteras well as on this branch, while CI'slintjob passes. CI installs thety==0.0.25pin from.[dev]; a locally-installedtyon PATH (0.0.64 here) shadows it and fails on the intentionally warn-level diagnostics. Worth a separate ticket — it means localprekis not the CI parity check the config comment claims it is.🤖 Generated with Claude Code
https://claude.ai/code/session_01NhqNRBg83czKfr8L6FF5xf