Skip to content

feat(explorer): rebuild the transaction and address asset panels - #135

Open
bitcoinuniverseadmin wants to merge 5 commits into
developfrom
impl/asset-summary-ui-20260919
Open

bitcoinuniverseadmin wants to merge 5 commits into
developfrom
impl/asset-summary-ui-20260919

Conversation

@bitcoinuniverseadmin

@bitcoinuniverseadmin bitcoinuniverseadmin commented Sep 19, 2026

Copy link
Copy Markdown

Implements WP01 to WP06 of the asset summary UI plan pinned at prep/asset-summary-ui-20260919. All eight frontend source markers are resolved and removed. The backend repairs these depend on are in bitcoinuniverseio/backend-apis#204.

What changed

A shared asset-summary directory holds what the transaction and address panels must not render two different ways: the quantity presenter, the layout mixins, and the helper that makes their real templates renderable under the unit runner. The presenter never constructs a Number from a quantity, keeps an exact decimal, true digits of an unknown scale, and an unknown quantity as three separate cases, and marks a shortened headline as approximate while copy always yields the exact string. A very small nonzero amount becomes an exponent rather than zero.

The decoder validates the coverage enum instead of casting it, rejects duplicate protocol coverage and coverage from another context, requires the asset and coverage arrays rather than reading an absent one as an empty inventory, and refuses a stated total that conclusive coverage does not support or that disagrees with the identities listed. Effect evidence and the reading's checkpoint are preserved, so "not accepted" can be told from "not yet ruled on", and an absent checkpoint stays absent.

The transaction component builds one display model per response instead of mapping inside template bindings, seeds the optional protocol registry so combineLatest cannot hold a proven amount behind a display name, states each coverage gap with its own state and reason, offers a retry only where one could change the answer, honours the payload's cooldown, and keys a failed logo by its artwork revision.

Both templates keep a real table with a row header and put flex inside the header cell rather than on it, bound their numeric columns through an explicit colgroup, and stack by the panel's own width through a container query rather than by the viewport. Each row has a 44px disclosure carrying the full identity, the exact values and its own protocol effects.

The address panel keys holdings by the whole identity, carries divisibility and ruleset, resolves a divisibility conflict to unknown, separates a known quantity from the scope it was read over, states a checked and a total denominator, distinguishes a proven empty address from one that could not be fully checked, and routes its outpoint links through the active network. A bare /outpoint route resolves against the root network, so on signet those links pointed at mainnet.

Visual QA harness

The gate reported fourteen console errors on exactly these two routes, saying only ERROR yt. The gate now resolves console argument handles, which showed the cause: the app registers a service worker, and a request a service worker answers never reaches context.route, so the worker served its cached app shell for API requests and the page parsed index.html as JSON. Every fixture the run declares was being bypassed. capture.mjs has always passed serviceWorkers: 'block'; this run did not.

Three fixtures were also missing and fell through to the static server: the asset summary endpoint, which the router's prefix fallback answered with the detailed flow payload, so every transaction and address screenshot reviewed the panel's error state rather than the panel; the mining pools windows other than 1w; and the historical price the address page reads. The outpoint batch answered every request with an empty list.

Verification

  • npx vitest run: 216 files, 2491 tests pass, including 12 rendered-template cases for the transaction panel and 12 for the address panel.
  • npm run lint: 0 errors.
  • npm run build:universe: succeeds, which compiles both templates ahead of time.
  • node scripts/universe/check-text.mjs: passes.
  • npm run checkscreenshots:changed: GO. 24 screenshots, 0 automatic failures, 0 accessibility failures, 0 console errors, 0 unmatched fixtures.

Live functional acceptance on a first-party test network and the public mainnet release are separate operational steps and are not claimed here.

🤖 Generated with Claude Code

WP01 to WP06 of the asset summary UI plan, whose eight source markers are
resolved and removed. The backend repairs these depend on land alongside.

A shared asset-summary directory holds what the two panels must not render two
different ways: the quantity presenter, the layout mixins, and the helper that
makes their real templates renderable under the unit runner. The presenter never
constructs a Number from a quantity, keeps an exact decimal, true digits of an
unknown scale and an unknown quantity as three separate cases, and gives a long
value an explicitly marked short form while copy always yields the exact string.
A very small nonzero amount becomes an exponent rather than zero.

The decoder validates the coverage enum instead of casting it, rejects duplicate
protocol coverage and coverage from another context, requires the asset and
coverage arrays rather than reading an absent one as an empty inventory, and
refuses a stated total that conclusive coverage does not support or that
disagrees with the identities listed. Effect evidence and the reading's
checkpoint are preserved, so "not accepted" can be told from "not yet ruled on"
and an absent checkpoint stays absent.

The transaction component builds one display model per response instead of
mapping inside template bindings, seeds the optional protocol registry so
combineLatest cannot hold a proven amount behind a display name, states each
coverage gap with its own state and reason instead of counting things being
waited on, offers a retry only where one could change the answer, honours the
payload's cooldown, and keys a failed logo by its artwork revision.

Both templates keep a real table with a row header and put flex inside the
header cell rather than on it, bound their numeric columns through an explicit
colgroup, and stack by the panel's own width through a container query rather
than by the viewport. Each row has a 44px disclosure carrying the full identity,
the exact values and its own protocol effects, and every copy control reports
what actually happened.

The address panel keys holdings by the whole identity, carries divisibility and
ruleset, resolves a divisibility conflict to unknown, separates a known quantity
from the scope it was read over, states a checked and a total denominator,
distinguishes a proven empty address from one that could not be fully checked,
and routes its outpoint links through the active network. A bare /outpoint route
resolves against the root network, so on signet those links pointed at mainnet.

Two visual-QA fixtures were missing rather than wrong. The router falls back to a
prefix match, so the summary endpoint was served the detailed flow payload and
every transaction and address screenshot reviewed the panel's error state instead
of the panel; the outpoint batch answered every request with an empty list.

Frontend: 2491 unit tests pass, including twelve rendered-template cases for the
transaction panel and twelve for the address panel. Lint reports no errors, the
production build compiles both templates, and the text gate passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… fixture

The screenshot gate reported fourteen console errors on the transaction and
address routes, and the errors said only "ERROR yt": a production build's
minified constructor name, logged because msg.text() abbreviates a logged object
rather than printing it. The gate now resolves the argument handles in the page,
so a recorded error carries its real message.

With that visible, the cause was not the pages. The application registers a
service worker, and a request a service worker answers never reaches
context.route, so the worker returned its cached app shell for API requests and
the page parsed index.html as JSON. Every fixture this run declares was being
bypassed for any request the worker answered, which means these screenshots were
not driven by the fixtures they claim to assert. capture.mjs has always passed
serviceWorkers: 'block'; this run did not.

Three fixtures were also missing outright and fell through to the static server:
the asset summary endpoint, which the router's prefix fallback answered with the
detailed flow payload, so every transaction and address screenshot reviewed the
asset panel's error state rather than the panel; the mining pools windows other
than 1w; and the historical price the address page reads. The outpoint batch
answered every request with an empty list, so the address holdings panel was only
ever reviewed with nothing to show.

The gate now reports GO: 24 screenshots, no automatic failures, no accessibility
failures, no console errors, no unmatched fixtures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… returned

`live-evidence/mainnet-summary-20260919.json` is the verbatim body of
GET /api/v1/universe/transactions/{txid}/assets from the production explorer on
2026-09-19, for a confirmed mainnet transaction. It is read-only public data,
pinned because a fixture written by the same hand as the decoder proves less than
the real thing.

It pins two things. The deployed producer does not send the per-asset evidence or
effect evidence the rebuilt decoder reads, so the decoder has to accept their
absence or the first deploy breaks the live site; that is now asserted. And the
deployed producer states a checkpoint height of zero beside the real tip block
hash, and reports all thirty one roster protocols as having no reader at all
including the six that two configured adapters do read. Both are the defects the
backend branch repairs, and both are asserted here so that shipping the repaired
producer fails this test loudly instead of passing on stale expectations.

The panel rendering that payload says Partial and "0 assets found so far", never
that there are none, states every unread protocol as a limit rather than as
something being waited on, and offers no retry for a gap no retry could clear.

Also adds a fixture-parity spec: the visual-QA summary fixture must decode under
the same decoder the application uses, and the detailed flow payload must keep
failing to, since that is what the router's prefix fallback was serving.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Dogecoin and Zcash transaction routes read the asset summary and had no
fixture for it, so the request failed closed. It went unnoticed because other
missing fixtures on the same pages raised JSON parse failures first and the pages
never rendered far enough to ask. Repairing those, in the previous commit, is what
surfaced this.

Both are deliberately inconclusive: one protocol answered and the rest of the
chain's roster has no transaction reader, so a total would contradict its own
coverage and the decoder rejects that. The Zcash one states no divisibility, so
the panel renders the true digits labelled as smallest units rather than a scaled
figure, which is the case that was previously impossible to review.

The fixture-parity spec now covers them, and asserts that neither carries the
fabricated zero-height checkpoint the deployed producer sends.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant