Problem Statement
Two unrelated UX/workflow issues in the footer and wallet popup:
- Version string in the footer is decorative, not load-bearing. The footer shows
v1.1.0, the first-visit wallet popup shows v1.0, package.json says 0.0.0, and the latest git tag is v0.9.6. Four sources, all out of sync. There is no workflow that keeps the footer accurate, so it tells the user nothing trustworthy.
- The wallet popup button reads
[ Connect ], which is misleading. The flow accepts any ETH address, stores it in localStorage, and uses it as a key for cloud-sync and a lookup for chain-sync. There is no signature, no provider handshake, no proof of ownership. Calling it "Connect" implies a wallet connection that does not happen.
Solution
- Make the footer version reflect the deployed git tag automatically.
build.py injects git describe --tags --always --dirty into a {{VERSION}} placeholder used in both the footer and the wallet popup header. Tags are created automatically on every merge to main by a GitHub Action, so the footer is always current without any manual edit. The footer version links to the matching GitHub Release page.
- Rename the wallet popup button from
[ Connect ] to [ Track Wallet ]. "Track" is the precise word for read-only address observation and matches existing crypto-product vocabulary (Etherscan watchlist, DeBank follow, Zapper track).
User Stories
- As the maintainer, I want the footer version to update automatically on every release, so that I never have to remember to bump a string.
- As the maintainer, I want a
release:skip PR label, so that doc-only or infra-only merges do not cut a tag.
- As the maintainer, I want a
release:minor and release:major PR label, so that I can choose the bump level for notable changes without leaving the GitHub UI.
- As the maintainer, I want builds with uncommitted changes to be marked
-dirty in the footer, so that I can immediately tell when a deployed build is not from a clean tag.
- As the maintainer, I want auto-generated release notes on every tag, so that the GitHub Releases page is a usable changelog without hand-curation.
- As a user, I want to see what version of HyperWheel I am running, so that I can report bugs against a specific build.
- As a user, I want the footer version to be a link to the GitHub Release notes, so that I can see what changed in the version I am running.
- As a user opening the app for the first time, I want the wallet popup button to use accurate language, so that I am not misled into thinking I am connecting a wallet via a provider.
- As a user who wants to track someone else's wallet (read-only), I want the button copy to make it clear this is supported and not a sign-in flow, so that I feel confident entering an address I do not own.
Implementation Decisions
- Source of truth for version is the git tag.
git describe --tags --always --dirty resolves to the current version string. package.json version is not used and stays at 0.0.0.
- Single placeholder.
build.py substitutes {{VERSION}} in the assembled HTML. Both the footer and the wallet popup header use the same placeholder so they stay in lockstep.
- Footer version is a link to
https://github.com/heyitsStylez/hyperwheel/releases/tag/<version>. The -dirty suffix is stripped from the link target but preserved in the displayed text.
- Auto-release workflow runs on push to
main. It inspects the labels on the merged PR:
release:skip — exit without tagging.
release:major — bump major, reset minor/patch.
release:minor — bump minor, reset patch.
- default — bump patch.
- The workflow then
git tags, pushes, and runs gh release create --generate-notes.
- Wallet popup button copy changes from
[ Connect ] to [ Track Wallet ]. No other strings change — Enter Wallet Address, awaiting input..., and the header [ WALLET ] button are already accurate.
- Domain term
Version added to CONTEXT.md defining the Version contract.
- ADR 0001 captures the auto-release decision and the alternatives rejected (manual script,
workflow_dispatch, release-please/semantic-release).
- Deep module:
resolve_version() in build.py — a small pure helper that wraps git describe and degrades gracefully when there are no tags or git is unavailable. Stable interface, single responsibility, testable in isolation.
Testing Decisions
- A good test exercises the external behavior of
resolve_version() — given a git state, it returns the expected string. It does not assert on internal command construction.
- Module under test:
resolve_version() in build.py.
- Cases:
- Repo with a tag at HEAD → returns the tag (e.g.
v0.9.6).
- Repo with commits past the latest tag → returns
<tag>-<n>-g<sha>.
- Repo with uncommitted changes → returns a string ending
-dirty.
- Repo with no tags → falls back to short SHA.
git not available / not a repo → returns a sensible default (e.g. unknown) without crashing the build.
- Prior art: the project's existing tests are JS (
node --test over test/unit/*.test.js). There is no existing Python test infrastructure. Use Python's stdlib unittest and put the test at test/build/test_resolve_version.py. The test creates a temporary git repo via subprocess so it does not depend on the host repo's tag state.
- The GitHub Actions workflow YAML, the HTML token substitution, and the wallet button copy are not unit-tested — they are either configuration or trivial string swaps.
Out of Scope
- Real wallet-provider connection (WalletConnect, EIP-1193, signature challenge). The app remains a read-only address tracker.
- A hand-curated
CHANGELOG.md. GitHub auto-generated release notes are sufficient.
- Bumping
package.json version. It stays at 0.0.0; git tags are the source of truth.
- A theme/preferences UI or any other unrelated cleanup of the footer.
- Multi-wallet tracking. The app continues to track exactly one address at a time.
- Migrating the existing JS test runner to also cover Python. The Python test runs separately.
Further Notes
- The
release:skip escape hatch is the only behavior that requires the maintainer to remember anything. If forgotten, the cost is one extra patch tag — low harm.
- Tag noise is expected and accepted: the footer's job is "what am I running," not "what was the last milestone." See ADR 0001.
- The wallet popup change is one line in
src/html/body.html. It is bundled here because it is small and shares a release with the version work; splitting would be churn.
Problem Statement
Two unrelated UX/workflow issues in the footer and wallet popup:
v1.1.0, the first-visit wallet popup showsv1.0,package.jsonsays0.0.0, and the latest git tag isv0.9.6. Four sources, all out of sync. There is no workflow that keeps the footer accurate, so it tells the user nothing trustworthy.[ Connect ], which is misleading. The flow accepts any ETH address, stores it inlocalStorage, and uses it as a key for cloud-sync and a lookup for chain-sync. There is no signature, no provider handshake, no proof of ownership. Calling it "Connect" implies a wallet connection that does not happen.Solution
build.pyinjectsgit describe --tags --always --dirtyinto a{{VERSION}}placeholder used in both the footer and the wallet popup header. Tags are created automatically on every merge tomainby a GitHub Action, so the footer is always current without any manual edit. The footer version links to the matching GitHub Release page.[ Connect ]to[ Track Wallet ]. "Track" is the precise word for read-only address observation and matches existing crypto-product vocabulary (Etherscan watchlist, DeBank follow, Zapper track).User Stories
release:skipPR label, so that doc-only or infra-only merges do not cut a tag.release:minorandrelease:majorPR label, so that I can choose the bump level for notable changes without leaving the GitHub UI.-dirtyin the footer, so that I can immediately tell when a deployed build is not from a clean tag.Implementation Decisions
git describe --tags --always --dirtyresolves to the current version string.package.jsonversion is not used and stays at0.0.0.build.pysubstitutes{{VERSION}}in the assembled HTML. Both the footer and the wallet popup header use the same placeholder so they stay in lockstep.https://github.com/heyitsStylez/hyperwheel/releases/tag/<version>. The-dirtysuffix is stripped from the link target but preserved in the displayed text.main. It inspects the labels on the merged PR:release:skip— exit without tagging.release:major— bump major, reset minor/patch.release:minor— bump minor, reset patch.git tags, pushes, and runsgh release create --generate-notes.[ Connect ]to[ Track Wallet ]. No other strings change —Enter Wallet Address,awaiting input..., and the header[ WALLET ]button are already accurate.Versionadded toCONTEXT.mddefining the Version contract.workflow_dispatch,release-please/semantic-release).resolve_version()inbuild.py— a small pure helper that wrapsgit describeand degrades gracefully when there are no tags or git is unavailable. Stable interface, single responsibility, testable in isolation.Testing Decisions
resolve_version()— given a git state, it returns the expected string. It does not assert on internal command construction.resolve_version()inbuild.py.v0.9.6).<tag>-<n>-g<sha>.-dirty.gitnot available / not a repo → returns a sensible default (e.g.unknown) without crashing the build.node --testovertest/unit/*.test.js). There is no existing Python test infrastructure. Use Python's stdlibunittestand put the test attest/build/test_resolve_version.py. The test creates a temporary git repo viasubprocessso it does not depend on the host repo's tag state.Out of Scope
CHANGELOG.md. GitHub auto-generated release notes are sufficient.package.jsonversion. It stays at0.0.0; git tags are the source of truth.Further Notes
release:skipescape hatch is the only behavior that requires the maintainer to remember anything. If forgotten, the cost is one extra patch tag — low harm.src/html/body.html. It is bundled here because it is small and shares a release with the version work; splitting would be churn.