Added
-
A documentation site built with zensical (the new
zensical.tomlformat) and Material for MkDocs: a landing page, a Quickstart,
a "Rendering & scanning" guide, an auto-generated CLI reference
(mkdocs-typer2) and API reference (mkdocstrings), and the existing
cookbook/standards pages, wired into adocsextra
(uv sync --extra docs). Every public symbol's docstring now carries a
usage example. Closes #10, #11, #12, #13. -
A
DocsCI workflow (.github/workflows/docs.yml) that builds the
documentation site withzensical build --strictas a gate on every pull
request tomain, and deploys it to GitHub Pages on push tomain. The
README gains a documentation badge and a link to the published site at
https://ivananishchuk.github.io/cuere/. Closes #15. -
An animated terminal demo in the README, recorded with
vhs from a checked-in
examples/demo.tapeshowing a basic render,--invert, a wallet URI with
--optimize-uri, and--mode ansi. Ascripts/render_demo.pyhelper
re-records it locally (uv run python scripts/render_demo.py). Closes #16. -
Terminal color customization for
ansimode.render/show(and
cuere.rich.QRCode, plus the CLI--dark/--lightflags) now takedark
andlightcolors for the dark modules and the light ground. AColoris a
name (one of the 16 standard ANSI colors), a 256-palette index (anintor its
string form), or a truecolor value (#rrggbb/#rgbhex, an(r, g, b)
tuple, or its"r,g,b"string); the defaults stay the theme-proof
black-on-white (palette16on231). Colors areansi-only — passing one to
half/blockraises the newColorError(aCuereError) — andshow
drops them along with itsNO_COLOR/ non-tty fallback. Adds the exported
Colortype alias. Documented in a newdocs/colors.md, including the
scanner-contrast caveats. Closes #26. -
A pytest-benchmark performance suite (
benchmarks/) and an informational
BenchmarkCI job that records encode and render timings as a JSON artifact.
It coversQRMatrix.encodeacross payload sizes, each render mode
(half/block/ansi),render_svg,render_bytes(PNG), and the
high-levelrender()/render_bytes()paths. The suite is kept out of the
test run (not intestpaths, so coverage is unaffected) and runs only via the
benchmark job oruv run pytest benchmarks/ --benchmark-only. There is no
regression threshold yet — it is the foundation for one later. Closes #22. -
File and bytes export beyond the terminal.
render_bytes()returns the
encodedbytesfor a namedOutputFormat, andsave()writes them to a path
or binary stream — inferring the format from a.txt/.svg/.pngsuffix
when one is not given. Three formats ship:text(the terminal glyph
rendering, honoringmode/invert),svg(a standalone vector document,
also exposed directly asrender_svg()), andpng(a raster image that needs
the optionalcuere[image]extra — Pillow, imported lazily soimport cuere
never pulls it in).scalesets pixels-per-module forsvg/png. Unknown
formats raiseUnknownFormatError; a missing optional dependency raises
MissingDependencyError(bothCuereErrors). The CLI gains
--output FORMAT[:PATH](PATH-or omitted means stdout; the default stays
terminal output) and--scale. Also adds theSupportsWriteBytessink
protocol. Documented in a newdocs/output-formats.mdand the cookbook.
Closes #23, #24, #25. -
lightning_uri()— a typedlightning:URI builder for bech32 Lightning
payloads (BOLT11 invoiceslnbc…, LNURLlnurl1…, BOLT12 offerslno1…).
The payload is validated structurally (a single-case run of ASCII
alphanumerics beginningln) without a checksum verify, mirroring
bitcoin_uri; invalid input raisesWalletURIError. A lowercase result
composes withoptimize_urifor a smaller QR. Documented in
docs/cookbook/wallet-uris.mdand a newdocs/lightning-uri.md. -
SchemeCaseandscheme_case()— a public, typed classifier for how
optimize_uritreats a URI's scheme:INSENSITIVE(bech32 —bitcoin:/
lightning:— may be uppercased),SIGNIFICANT(case carries meaning —
ethereum:EIP-55,wc:WalletConnect — never folded), orUNKNOWN
(unrecognized scheme, also never folded).optimize_urinow gates on this, so
wc:is recognized explicitly as case-significant rather than incidentally
passed through. -
ethereum_uri()anderc20_transfer_uri()— typed
EIP-681ethereum:request builders.
ethereum_uri(address, *, value=None, chain_id=None, gas_limit=None, gas_price=None)builds a native payment (valuein wei, matching the
spec);erc20_transfer_uri(token, *, to, amount, ...)builds the ERC-20
transfer(address,uint256)form (amountin the token's base units).
Addresses are validated structurally as0x+ 40 hex digits with their
EIP-55 checksum case preserved (so these URIs must never go through
optimize_uri); numeric arguments are positiveuint256integers. Invalid
input raisesWalletURIError. Documented indocs/cookbook/wallet-uris.md. -
docs/bip-21.mdanddocs/eip-681.md— condensed summaries of the wallet-URI
standards cuere implements (BIP-21/BIP-173 forbitcoin_uri; EIP-681/EIP-55/
EIP-155 and ERC-20transferfor the ethereum builders). -
bitcoin_uri()— a typed BIP-21bitcoin:payment-request builder
(bitcoin_uri(address, *, amount=None, label=None, message=None)). It
validates the address alphabet, rendersamountas a plain BTC decimal
(Decimal/int/str; positive, finite, satoshi precision — neverfloat),
and percent-encodeslabel/message. Returns a plainstrthat composes
withoptimize_uriandrender/show. Invalid input raises the new
WalletURIError(aCuereError). Documented in a newdocs/cookbook/wallet-uris.md. -
Mutation testing with
mutmut(dev dependency, run locally — not in CI),
configured inpyproject.tomland documented indocs/mutation-testing.md,
with the set of accepted equivalent mutants recorded there. Includes a mypy
type-check pass (mutmut-mypy.ini) that catches type-error mutants without a
runtime test, and a no-shrink hypothesis profile for deterministic runs. -
Behavioral regression tests closing the gaps mutation testing surfaced:
exact error/warning messages (WidthError, invalid render mode), encode-option
pass-through inrender/fits/rich.QRCode, block-mode width/height in
fits/show/QRCode.__rich_measure__, theshowexact-fit boundary,
per-row Rich segments, and first-colon URI scheme parsing inoptimize_uri. -
tests/test_types.py:assert_typeregression tests pinning the inferred
types of the public API. -
zizmorGitHub Actions security audit: dev dependency, a local pre-commit
hook (offline), and a CI workflow running online audits with SARIF upload to
code scanning. -
.github/settings.yml(repository-settings app):security_and_analysis
(secret scanning + push protection + Dependabot security updates), branch
protection onmain, and the PyPI/TestPyPI environments — as code. -
.github/CODEOWNERS. -
gitleaks now uploads its SARIF to code scanning.
-
.coderabbit.yaml— the CodeRabbit review configuration, pinned in-repo
rather than left to dashboard defaults: assertive profile, auto-review on pull
requests targetingmain, path filters that skip generated files (uv.lock,
build output, golden fixtures), and house-style review instructions.
CodeRabbit's bundledruffandgitleaksare disabled there since CI already
enforces both. CodeRabbit remains a best-effort second opinion;/code-review
and CI are the gate. -
.well-known/security.txt(RFC 9116) — machine-readable security contact. -
Contributor governance:
CONTRIBUTING.md,CODE_OF_CONDUCT.md, issue
templates (bug / feature / question) and a pull-request template. -
SupportsWrite— a narrow text-sink protocol (any object withwrite(str)),
exported fromcuere. It is whatshow(out=...)now accepts. -
Test-hardening pass: edge-case and property coverage for empty/whitespace
payloads, near-maximal payloads, all four EC levels (encoding + monotonic
version growth), the reachable Micro QR variants (M2–M4, plus the M1-skip and
the rejectedH/oversized cases), extreme borders, that purerender()
keeps ANSI color regardless ofNO_COLOR, and round-trip strategies that
exercise the renderers against real encoder output (not only synthetic bit
patterns).
Changed
- The cookbook moved into a
docs/cookbook/directory: the payment-request
recipes now live indocs/cookbook/wallet-uris.mdand the file/bytes export
recipes indocs/cookbook/exporting.md(the flatdocs/cookbook.mdis gone).
The wallet page now embeds the rendered terminal QR for every recipe, adds a
standalone WalletConnect (wc:) rendering recipe, and explains why cuere
encodes at error-correction level L. Ascripts/render_docs_qr.pyscript
regenerates that embedded art (run with--checkin CI to catch drift). optimize_uri()is refactored onto the newscheme_case(); its behavior is
unchanged (case-insensitive, lowercase, no-query URIs are uppercased;
everything else is returned verbatim), butethereum:andwc:are now
deliberately classified as case-significant rather than relying on the
unknown-scheme default.- Dropped
from __future__ import annotationsand theif TYPE_CHECKING:
gating of cheap stdlib/intra-package imports — those are now plain runtime
imports. Removed theflake8-type-checking(TCH) ruff ruleset, which had
been mechanically forcing both patterns plus quotedtyping.casttypes. The
lazy import ofrich/typeris unaffected (still enforced bycuere/__init__
not importing them, guarded by a test). - DRY'd the encode options across the high-level API:
render/fits/show/
rich.QRCode/coercenow take**options: Unpack[EncodeOptions](one
EncodeOptionsTypedDict) plus a sharedEncodabletype alias, so the option
set and its defaults live once inQRMatrix.encode. Calls like
render(data, border=2, error="H")are unchanged. Also centralized the ANSI
color values shared byrenderandrich. show(out=...)is typed as the narrowSupportsWriteprotocol instead of
typing.IO[str], matching what it actually needs (a singlewrite;isatty
is probed defensively). Backward compatible — everyIO[str]already
satisfies it — and it removes the double-castworkaround the tests needed to
pass a write-only stream.- mypy: made
strict_equalityandwarn_redundant_castsexplicit in the
config (both already implied bystrict = true).
Fixed
- The README's animated demo GIF now uses an absolute, tag-pinned URL so it
renders on the PyPI project page (PyPI serves the long description off-repo, so
relative image paths 404 there). - Embedded half-block QR art now scans from the rendered documentation site.
The Material/zensical theme styled code blocks withline-height: 1.4, which
injected a horizontal sliver between every glyph row and split the codes
vertically, and the syntax highlighter stripped the art's blank quiet-zone
rows. A scopeddocs/stylesheets/qr.css(wired viaextra_css) forces
line-height: 1, restores the top/bottom quiet zone, and pins black-on-white
so the codes read in both the light and dark themes; art blocks opt in with a
{ .qr }fence info string. The homepage hero QR — previously corrupted (no
quiet zone, rows truncated by the trailing-whitespace hook) — is re-rendered
with a full quiet zone and excluded from that hook. The QR-art generator is
generalized from the cookbook to all docs pages and renamed
scripts/render_docs_qr.py, so its--checkdrift guard now also covers the
homepage. The CSS also squares the module aspect ratio (scaleX), which HTML's
monospace metrics otherwise stretch ~20% wider than tall. Closes #68. - The CLI now reports a clean
error: …(exit 1) instead of an uncaught
traceback when--inputor stdin holds non-UTF-8 bytes (UnicodeDecodeError
is aValueError, so it previously slipped past the(CuereError, OSError)
handler). - Corrected mismatched action version comments (e.g.
setup-uvwas labelled
v4.2.0but pinned tov8.2.0; CodeQLv3.27.5→v4.36.2; gitleaks
v2.3.6→v3.0.0). The pinned SHAs are unchanged; the comments now match.
Security
- The release workflow now asserts the built package version matches the
release tag before publishing, so a forgotten version bump cannot publish a
mislabeled artifact. SECURITY.mdnow documents how to verify a release's sigstore signature and
SLSA provenance, not only the GitHub build-provenance attestation.- Hardened the release build against cache poisoning (
enable-cache: false
on the publish build'ssetup-uv).
What's Changed
- ci(security): zizmor + settings-as-code + action version fixes by @IvanAnishchuk in #36
- docs(security): add .well-known/security.txt (RFC 9116) by @IvanAnishchuk in #48
- docs: contributor governance (CONTRIBUTING, CoC, issue/PR templates) by @IvanAnishchuk in #49
- test: add mutation testing (mutmut) with mypy type-check pass by @IvanAnishchuk in #50
- docs: track CLAUDE.md with typing/imports conventions + mutmut workflow by @IvanAnishchuk in #51
- fix: clean error on non-UTF-8 CLI input; DRY encode options & ANSI colors by @IvanAnishchuk in #52
- chore(deps): Bump https://github.com/astral-sh/uv-pre-commit from 0.11.19 to 0.11.21 by @dependabot[bot] in #2
- chore(ci): Bump sigstore/gh-action-sigstore-python from 3.3.0 to 3.4.0 in the actions group across 1 directory by @dependabot[bot] in #3
- chore(deps): manual upgrade — pytest 9.1.0, filelock, virtualenv by @IvanAnishchuk in #53
- refactor: type show(out=) as a narrow SupportsWrite protocol (#20) by @IvanAnishchuk in #54
- docs: document solo-pm + /code-review workflow in CLAUDE.md by @IvanAnishchuk in #55
- docs: document src-layout and module map in CLAUDE.md by @IvanAnishchuk in #56
- test: harden edge-case and round-trip coverage (closes #21) by @IvanAnishchuk in #57
- feat: add BIP-21 bitcoin_uri builder (closes #27) by @IvanAnishchuk in #58
- feat: EIP-681 ethereum_uri & erc20_transfer_uri (closes #28) by @IvanAnishchuk in #59
- feat: typed lightning_uri builder + scheme_case classifier (closes #29) by @IvanAnishchuk in #60
- feat: --output dispatcher with SVG and PNG export by @IvanAnishchuk in #61
- chore(ci): pin CodeRabbit review config in .coderabbit.yaml by @IvanAnishchuk in #64
- chore(deps-dev): Bump basedpyright from 1.39.7 to 1.39.8 in the dev-dependencies group by @dependabot[bot] in #63
- docs(cookbook): wallet-URI cookbook in docs/cookbook/ (#14) by @IvanAnishchuk in #65
- ci: add pytest-benchmark performance suite and benchmark job by @IvanAnishchuk in #66
- feat: terminal color customization for ANSI mode (#26) by @IvanAnishchuk in #67
- docs: zensical documentation site with CLI + API reference (#10–#13) by @IvanAnishchuk in #69
- ci: build the docs site and deploy to GitHub Pages (closes #15) by @IvanAnishchuk in #70
- docs: add animated README demo GIF and regen script by @IvanAnishchuk in #72
- docs: make embedded half-block QR art scannable on the docs site by @IvanAnishchuk in #73
- docs: square up the embedded QR aspect ratio by @IvanAnishchuk in #74
- docs: fix CodeRabbit rate-limit wording, document docs-art exclusion sync by @IvanAnishchuk in #75
- chore: v0.2.0 pre-release polish by @IvanAnishchuk in #76
- chore: release v0.2.0 by @IvanAnishchuk in #77
New Contributors
- @dependabot[bot] made their first contribution in #2
Full Changelog: v0.1.0...v0.2.0