Skip to content

feat: explorer link component - #74

Open
gabitoesmiapodo wants to merge 14 commits into
mainfrom
feat/explorer-link
Open

feat: explorer link component#74
gabitoesmiapodo wants to merge 14 commits into
mainfrom
feat/explorer-link

Conversation

@gabitoesmiapodo

@gabitoesmiapodo gabitoesmiapodo commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #9

dApps hand-roll explorer URLs for every party, contract and transaction they render, and Canton has no canonical explorer: the base URL is per-environment and per-SV.

This adds the missing pair. getExplorerLink / useExplorerLink turn an identifier into a URL, inferring party, contract or update from its shape; ExplorerLink renders the icon-only external link with target and rel pinned out of a consumer's reach. Identifier's link slot delegates to it, so the two stay one contract instead of two copies of the same anchor.

The vesting dApp consumes both: counterparty ids on grant cards link out, resolved from one build-time config. That URL is validated when the app builds, so a bad value fails the build rather than the page load and the client ships no validator.

Changes

  • ExplorerLink: icon-only external link, target and rel not overridable.
  • useExplorerLink / getExplorerLink: identifier to URL, entity inferred from shape.
  • Identifier's link slot now renders ExplorerLink.
  • Theme gains .cnc-explorer-link, split out of the identifier link rules.
  • Grant cards link the counterparty party id to the explorer.
  • Counterparty id markup extracted into one shared component.
  • VITE_EXPLORER_URL resolved, defaulted and validated at build time.
  • Public barrel exports the component, the hook and its types.

Outside the issue's scope

  • Build-time env validation for the dApp: a57a3df, e7eeaeb.
  • Identifier / truncate JSDoc and prop ordering: d1f2051.
  • Root CLAUDE.md Node engine row corrected: 0d0fb93.

Acceptance criteria

  • Accepts an entity type (party/contract/tx) and id, builds the right URL.
  • Renders as a link that opens in a new tab with rel="noopener".
  • Explorer base URL is configurable via prop/config.

Diverged from the issue:

  • The entity is optional; it is inferred from the id shape and the prop only overrides.
  • tx is named update, matching what Splice Scan calls a ledger transaction.
  • URL building lives in the hook, not the component; ExplorerLink composes no URLs.
  • rel is noopener noreferrer, and neither it nor target can be overridden.

Test plan

Automated tests

  • pnpm -C canton-dappbooster test — 18 new cases across the component and the hook.
  • pnpm -C dapp/frontend test — 8 new cases on env parsing, including javascript: rejection.
  • Full CI gate green locally: pnpm lint, typecheck, build, test, knip.

Manual verification

  1. pnpm run app:dev, open http://localhost:3012.
  2. Pick any party on the landing screen.
  3. Dashboard: each grant card's counterparty id shows an external-link icon.
  4. Click it: a new tab opens at scan.localhost:4000/party/<id> (scan currently not working in the mock app)
  5. Proposals: the counterparty id has no link icon.
  6. Toggle light/dark: the icon follows the theme, and focus shows a visible ring.

Breaking changes

None.

Checklist

  • Self-reviewed my own diff
  • Tests added or updated
  • Docs updated (if applicable)
  • No unrelated changes bundled in

Screenshots

image

Renders an explorer URL as a safe external link: new tab, rel noopener
noreferrer, an aria-hidden icon for its body. Composes no URLs; the caller
supplies the href.
Builds explorer URLs for party, contract and update ids from a per-app
ExplorerConfig. Canton has no chain registry and no canonical explorer, so the
base URL comes from the app. Entity is inferred from the id shape, overridable.
The target/rel/name contract now lives in one place. Identifier's test asserts
delegation; ExplorerLink's test pins the link's own behaviour.
parseEnv takes the source rather than reading import.meta.env directly so it
stays testable. An unset var reaches Vite as an empty string, so the schema
rejects that as well as a missing key.
Cards render the counterparty through Identifier instead of raw truncated text.
config.ts parses the environment once at import so a misconfigured build fails
on load rather than at first use.
Kit:
- ExplorerLink requires `aria-label`; an icon-only link had no accessible name
- pin `rel`/`target` behind the prop spread and drop them from the prop type
- widen the prop base to AnchorHTMLAttributes and accept a ref
- match `00`-prefixed contract ids down to their suffixless 66-character form
- give `.cnc-explorer-link` a focus ring and a display matching its Identifier slot
- restore the prop docs an earlier refactor dropped from IdentifierProps

dApp:
- default VITE_EXPLORER_URL to the local scan so a fresh clone still boots
- restrict it to http(s): the value lands in an href
- restore the from/to prefix and name the copy control per direction
- pass `announce={false}` where the Toaster already announces
- link the proposal counterparty to the explorer like the grant one
Kit:
- drop the unused per-entity path override; deployments differ by host, not by
  route, so the base url is the only knob and the memo collapses with it
- read the party-id shape off truncate.ts instead of restating the separator
- give each theme property one home: the explorer link owns its appearance, the
  identifier link only its fit in the row

dApp:
- validate the environment in vite.config.ts and define the result back, so the
  client ships neither zod nor a re-check of a value Vite already inlined
  (-14.5 kB gzip, 12% of the bundle)
- fold the repeated counterparty markup into one CounterpartyId
- report env failures with zod's own formatter
`{id}` was the placeholder the removed per-entity override substituted into.
With fixed routes the table only needs the path segment.

Also restores the ProposalCard `direction` comment dropped along the way.
- root CLAUDE.md: the node floor is 24.15.0, pinned at 24.18.1
- canton-dappbooster/CLAUDE.md: ExplorerLink/ exists now
- dapp/frontend/architecture.md: correct what src/lib holds, route counterparty
  ids through CounterpartyId, and record the build-time env seam
The href is optional per call site. Only the grant card links its counterparty,
so CounterpartyId takes the href rather than building one.
One variable against one rule does not need a schema library. `new URL` plus a
protocol check covers it, and the tests are unchanged.
Per-property comments on ExplorerLinkProps, IdentifierProps, TruncateOptions and
CounterpartyIdProps are gone, the fenced code blocks in the explorer-link JSDoc
are proper @example tags, and the entity-detection note is one comment instead
of three.
@gabitoesmiapodo gabitoesmiapodo self-assigned this Aug 6, 2026
@gabitoesmiapodo gabitoesmiapodo changed the title feat: link canton identifiers to a block explorer feat: ExplorerLink component Aug 6, 2026
@gabitoesmiapodo gabitoesmiapodo changed the title feat: ExplorerLink component feat: explorer link component Aug 6, 2026
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.

Explorer link component

1 participant