Skip to content

feat(tooltips): add helper tooltip system with knowledge-level support#326

Merged
AugustoL merged 7 commits intoopenscan-explorer:devfrom
AugustoL:feat/helper-tooltips
Mar 19, 2026
Merged

feat(tooltips): add helper tooltip system with knowledge-level support#326
AugustoL merged 7 commits intoopenscan-explorer:devfrom
AugustoL:feat/helper-tooltips

Conversation

@AugustoL
Copy link
Collaborator

Description

Add a reusable helper tooltip system that provides contextual explanations for blockchain data fields across all explorer pages. Tooltips are gated by a user-configurable knowledge level (Beginner / Intermediate / Advanced) with an enable/disable toggle.

Related Issue

N/A — new feature

Type of Change

  • New feature
  • Bug fix
  • Documentation update
  • Refactoring
  • Performance improvement
  • Other (please describe):

Changes Made

Infrastructure

  • HelperTooltip component (src/components/common/HelperTooltip.tsx): Reusable tooltip primitive with 350ms hover delay, keyboard focus support, Escape dismiss, tap-to-toggle for mobile, top/bottom collision detection, aria-describedby accessibility
  • FieldLabel component (src/components/common/FieldLabel.tsx): Thin wrapper that reads knowledge level from settings and conditionally renders the tooltip icon based on visibleFor prop
  • Tooltip CSS (src/styles/helper-tooltip.css): Styling using existing CSS variables, mobile-responsive, dark/light theme support
  • Settings UI: New "Helper Tooltips" section in Display tab with enable/disable toggle + knowledge level dropdown
  • Navbar quick-switcher: ? icon button (left of theme toggle) that cycles through levels. Border color indicates current level (green = beginner, blue = intermediate, purple = advanced). Shows notification on switch
  • tooltips i18n namespace: Content registry with all tooltip strings in 5 languages (en, es, zh, ja, pt-BR)
  • Types: KnowledgeLevel type + knowledgeLevel and showHelperTooltips fields added to UserSettings

Page Coverage (every visible field label)

  • EVM Transaction: hash, from, to/interacted with, contract created, status, block/confirmations, value, fee, gas price, gas limit/usage, effective gas price, nonce, position, type, decoded input, all blob fields, all L2 fields (Arbitrum + OP Stack)
  • EVM Block: hash, transactions, withdrawals, fee recipient, difficulty, size, extra data, gas used/limit, base fee, burnt fees, all blob fields, all Arbitrum fields, all More Details fields (advanced-only)
  • Address/Contract: balance, value, nonce, verification status, proxy type, implementation address, read/write contract, EIP-7702 delegate, ENS name/app
  • Token/NFT: token standard, decimals, total supply, token ID, owner, approved, metadata URI
  • Bitcoin Transaction: txid, witness hash, block, status, inputs, outputs, fee, all fee rates, size, virtual size, weight, coinbase, witness, RBF, version, locktime
  • Bitcoin Block: hash, miner, reward, fees, fee rate, transactions, total output, difficulty, size, weight, previous/next block, coinbase message, all More Details fields (advanced-only)
  • Bitcoin Address: address, balance, total received, UTXOs, transaction count
  • Settings: knowledge level selector

Visibility Levels

  • Beginner: Most tooltip icons visible — onboarding-oriented
  • Intermediate: Reduced set — only fields with real explanatory payoff
  • Advanced: Minimal — mostly hidden except niche technical fields (L2 internals, More Details, blob fields)

Screenshots (if applicable)

N/A — tooltips are interactive, best tested locally

Checklist

  • I have run npm run format:fix and npm run lint:fix
  • I have run npm run typecheck with no errors
  • I have run tests with npm run test:run
  • I have tested my changes locally
  • I have updated documentation if needed
  • My code follows the project's architecture patterns

Additional Notes

  • Notification width now fits text content (width: fit-content on .notification in styles.css)
  • The tooltip trigger uses onMouseEnter/onMouseLeave (not pointer events) to cleanly separate desktop hover from mobile tap behavior
  • Non-English translations (zh, ja, pt-BR) should be reviewed by native speakers before release

Add a reusable helper tooltip infrastructure that provides contextual
explanations for blockchain data fields across all explorer pages.

Infrastructure:
- HelperTooltip component: hover/focus/tap with a11y support
- FieldLabel wrapper: knowledge-level-aware tooltip visibility
- Settings UI: enable/disable toggle + beginner/intermediate/advanced selector
- Navbar quick-switcher: cycle through levels with colored border indicator
- New `tooltips` i18n namespace with content in all 5 languages

Coverage:
- EVM transaction page: all fields including L2 (Arbitrum, OP Stack) and blob fields
- EVM block page: all fields including More Details (advanced-only) and Arbitrum fields
- Address/contract pages: verification, proxy, read/write, balance, nonce, ENS, EIP-7702
- Token/NFT pages: standard, decimals, supply, token ID, owner, approved, metadata URI
- Bitcoin transaction/block/address pages: all fields with BTC-specific content
- Settings page: knowledge-level selector tooltip
@AugustoL AugustoL requested a review from josealoha666 March 18, 2026 19:46
@github-actions
Copy link

github-actions bot commented Mar 18, 2026

🚀 Preview: https://pr-326--openscan.netlify.app
📝 Commit: cfc9998625960647955005b805f53d65072bc1b9

Copy link
Collaborator

@josealoha666 josealoha666 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition overall — the coverage is thorough and the FieldLabel/HelperTooltip split makes the rollout easy to follow.

Two blockers before merge:

  1. src/locales/ja/tooltips.json has mojibake/corrupted text in address.implementationAddress (コント��クト). That will ship visibly broken copy in the Japanese locale.
  2. Current CI is red: all E2E jobs on this PR timed out at the 15m limit. Even if that's partly unrelated/flaky, it needs to be green or explained before merging.

@AugustoL
Copy link
Collaborator Author

Thanks for the review Jose!

Regarding the mojibake in ja/tooltips.json: I've verified the committed file and the text is valid UTF-8 — コントラクト renders correctly. This may have been a GitHub diff rendering artifact. You can confirm by viewing the raw file: the address.implementationAddress value parses cleanly as このプロキシの実際のロジックを含むコントラクト。

Regarding E2E timeouts: These are pre-existing flaky timeouts unrelated to this PR (no runtime logic changed — only tooltip UI additions and i18n content). Will be addressed separately.

@AugustoL AugustoL requested a review from MatiasOS March 18, 2026 20:54
Copy link
Member

@MatiasOS MatiasOS left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Z-index is broken on Tx details - Datos de Entrada
Image
  1. Missing tooltips on tx History table header
Image
  1. Account type missing Tooltip
Image
  1. Missing tooltips on tx details table header Call Tree, Gas Profiler, State changes
Image
  1. Missing tooltip on block header status (Finalized)
Image
  1. Arbitrum dificulty says it should be zero, but value is 1
Image
  1. Contract details missing tooltips on Contract bytecode, Source Code and Raw ABI and Functions headers
Image
  1. Bitcoin block headr missing tooltip next to confirmations
Image
  1. Bitcoin tx header missing tooltip next to confirmations
Image
  1. Bitcoin tx inputs and outputs missing all tooltips
Image
  1. All networks missing tootlip in netowrk page

  2. Optional. Dont like the the begginer, personal, advanced strategy. I think this will be more clear if:
    begginer: All tooltips
    intermediare: Hide basic blockchain fields like txs, blocks, value, balnces
    Advanced: Tx types, ENS, Kleros, integrations, debug tooltips, and developer mode info,etc. and chain specifc fields ( l2 fields, etc)

"gasLimit": "Maximum gas allowed in this block, defining its capacity.",
"baseFeePerGas": "Minimum gas price for this block, set by the network based on demand (EIP-1559).",
"burntFees": "Transaction fees permanently removed from circulation via EIP-1559.",
"blobGasUsed": "Total blob gas consumed by blob transactions in this block.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defines Blobs with blobs. I think that is better to define Blob

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some concepts are not easy or chain specific. We migth add an extra description to some fields introducing some concepts. Something like this:

{
  "transaction": {
    "hash": "A unique identifier for this transaction, generated from its contents.",
    "from": "The address that initiated and signed this transaction.",
    "status": "Whether the transaction executed successfully or reverted.",
    "confirmations": "Blocks mined after this transaction. More confirmations means higher finality.",
    "interactedWith": "The contract this transaction called, or the recipient address for simple transfers.",
    "transactionFee": "Total fee paid to process this transaction (gas used × gas price). Gas measures computational work on the network, and under EIP-1559 fees include a base fee (burned) plus a priority fee (tip).",
    "gasPrice": "Price per unit of gas, in Gwei. Higher gas prices incentivize faster inclusion.",
    "gasLimitUsage": "Gas limit is the maximum gas authorized. Gas used is the actual amount consumed.",
    "nonce": "Sender's transaction counter. Ensures transactions are processed in order and prevents replay attacks.",
    "position": "The index of this transaction within its block. Lower positions were processed first.",
    "type": "The transaction format (e.g. Legacy, EIP-1559, EIP-4844). Determines which fee fields apply.",
    "decodedInput": "The function call and parameters sent to the contract, decoded into human-readable form.",
    "maxFeePerBlobGas": "Maximum price per unit of blob gas the sender is willing to pay for data availability.",
    "blobGasPrice": "Actual price per unit of blob gas paid in this block.",
    "blobGasUsed": "Amount of blob gas consumed by this transaction's data blobs. Blobs are data containers introduced in EIP-4844 for rollup data availability, priced separately from execution gas.",
    "blobCount": "Number of data blobs attached to this transaction for rollup data availability.",
    "blobVersionedHashes": "Versioned hashes that uniquely identify each data blob committed by this transaction.",
    "contractCreated": "The address of the new contract deployed by this transaction.",
    "value": "Amount of native currency (e.g. ETH) transferred in this transaction.",
    "effectiveGasPrice": "The actual gas price paid after EIP-1559 fee calculation, which may differ from the requested price.",
    "l1BlockNumber": "The Ethereum L1 block that anchors this L2 transaction for security.",
    "gasUsedForL1": "Gas consumed for posting this transaction's data to Ethereum L1.",
    "l1Fee": "Fee paid for data availability on Ethereum L1. L2 networks rely on L1 to publish transaction data for security and verification.",
    "l1GasPrice": "The L1 gas price used to calculate the data availability fee component.",
    "l1GasUsed": "Estimated L1 gas required to post this transaction's calldata.",
    "l1FeeScalar": "A multiplier applied to the L1 fee calculation by the L2 sequencer."
  },
  "block": {
    "hash": "A unique fingerprint identifying this block, derived from its contents.",
    "transactions": "The number of transactions included and executed in this block.",
    "withdrawals": "Validator withdrawals processed in this block, returning staked ETH.",
    "feeRecipient": "The address that receives priority fees from transactions in this block.",
    "difficulty": "Mining difficulty for this block. Always zero on proof-of-stake networks after the Merge.",
    "totalDifficulty": "Cumulative difficulty of the chain up to this block. Legacy field from proof-of-work.",
    "size": "The total size of the block data in bytes.",
    "extraData": "Arbitrary data set by the block producer. Often contains client or pool identifiers.",
    "gasUsed": "Total gas consumed by all transactions in this block.",
    "gasLimit": "Maximum gas allowed in this block, defining its capacity.",
    "baseFeePerGas": "Minimum gas price for this block, set by the network based on demand (EIP-1559). This base fee is burned.",
    "burntFees": "Transaction fees permanently removed from circulation via EIP-1559.",
    "blobGasUsed": "Total blob gas consumed by blob transactions in this block.",
    "excessBlobGas": "Blob gas above the target, used to calculate the next block's blob base fee.",
    "blobCount": "Number of data blobs included in this block for rollup data availability.",
    "l1BlockNumber": "The Ethereum L1 block number associated with this Arbitrum block.",
    "sendCount": "Number of outgoing L2-to-L1 messages sent in this Arbitrum block.",
    "sendRoot": "Merkle root of all outgoing L2-to-L1 messages, used for cross-layer verification.",
    "parentHash": "Hash of the previous block in the chain, linking blocks together.",
    "stateRoot": "Merkle root of the entire blockchain state after processing this block (a cryptographic summary of all account data).",
    "transactionsRoot": "Merkle root of all transactions included in this block.",
    "receiptsRoot": "Merkle root of all transaction receipts from this block.",
    "withdrawalsRoot": "Merkle root of all validator withdrawals processed in this block.",
    "logsBloom": "A bloom filter (probabilistic data structure) for efficiently searching event logs emitted in this block.",
    "nonce": "A value used in proof-of-work mining. Always zero after the Merge.",
    "mixHash": "A hash used in the proof-of-work algorithm. Replaced by RANDAO value after the Merge.",
    "sha3Uncles": "Hash of the uncle blocks list. Always the empty list hash after the Merge.",
    "validator": "The validator index that initiated this withdrawal from the beacon chain."
  },
  "address": {
    "verification": "A verified contract has its source code publicly confirmed to match the deployed bytecode.",
    "proxyType": "This contract uses an upgradeable proxy pattern, where execution is delegated to a separate implementation contract, allowing upgrades without changing the address.",
    "readContract": "Query contract data without spending gas or connecting a wallet. Read-only calls.",
    "writeContract": "Send a transaction that changes contract state. Requires a connected wallet and gas.",
    "balance": "Native currency balance held by this address.",
    "usdValue": "Estimated fiat value based on current market price.",
    "nonce": "Number of transactions sent from this address. Used for transaction ordering.",
    "eip7702Delegate": "This account delegates its code execution to another address via EIP-7702.",
    "implementationAddress": "The contract that contains the actual logic for this proxy.",
    "ensName": "Ethereum Name Service name associated with this address (maps human-readable names to addresses).",
    "ensApp": "Application or resolver linked to this ENS name."
  },
  "token": {
    "tokenStandard": "The token interface standard (e.g. ERC-20, ERC-721, ERC-1155) that defines how this token behaves.",
    "decimals": "Number of decimal places used to display token amounts. Most ERC-20 tokens use 18.",
    "totalSupply": "Total number of tokens that have been created for this contract.",
    "tokenId": "A unique numeric identifier for this specific token within its collection.",
    "owner": "The address that currently holds this NFT.",
    "approved": "An address authorized to transfer this specific NFT on behalf of the owner.",
    "metadataUri": "The URI where this token's metadata (name, image, attributes) is stored."
  },
  "bitcoin": {
    "txid": "A unique identifier for this transaction, computed from its serialized data.",
    "witnessHash": "Transaction hash including witness (SegWit) data. Differs from TXID for SegWit transactions.",
    "block": "The block that includes this transaction.",
    "status": "Whether this transaction has been confirmed in a block or is still waiting in the mempool.",
    "inputs": "The number of previous outputs being spent. Each input references a UTXO (Unspent Transaction Output). Bitcoin uses a UTXO model where transactions consume and create discrete outputs.",
    "outputs": "The number of new outputs created. Each output locks BTC to an address.",
    "fee": "The difference between total inputs and total outputs, paid to the miner.",
    "feePerByte": "Fee rate in satoshis per byte of raw transaction data.",
    "feePerVByte": "Fee rate in satoshis per virtual byte. The standard fee metric for SegWit transactions.",
    "feePerWU": "Fee rate in satoshis per weight unit. The most granular fee metric.",
    "size": "Raw transaction size in bytes, including witness data.",
    "virtualSize": "Adjusted transaction size that discounts witness data. Used for fee calculation.",
    "weight": "Transaction weight in weight units (WU). 1 vByte = 4 WU.",
    "coinbase": "Whether this is a coinbase transaction that creates new BTC as a block reward.",
    "witness": "Whether this transaction uses Segregated Witness (SegWit) format.",
    "rbf": "Replace-By-Fee. If enabled, this transaction can be replaced by one with a higher fee before confirmation.",
    "version": "Transaction format version number. Determines which features are available.",
    "locktime": "Earliest block height or time when this transaction can be included in a block.",
    "blockHash": "The unique hash identifying the block that contains this transaction.",
    "minedBy": "The mining pool or entity that produced this block.",
    "blockReward": "New BTC created as a reward for mining this block (subsidy + fees).",
    "totalFees": "Sum of all transaction fees collected by the miner in this block.",
    "feeRate": "Average and median fee rates of transactions in this block.",
    "transactions": "Number of transactions included in this block.",
    "totalOutput": "Total BTC value of all outputs created in this block.",
    "difficulty": "How hard it is to find a valid block hash. Adjusts every 2016 blocks.",
    "blockSize": "Total size of the serialized block data.",
    "blockWeight": "Block weight in weight units. Maximum is 4,000,000 WU.",
    "previousBlock": "Hash of the preceding block, linking this block to the chain.",
    "nextBlock": "Hash of the following block, if one exists.",
    "coinbaseMessage": "Arbitrary text embedded in the coinbase transaction by the miner.",
    "merkleRoot": "Root hash of the Merkle tree of all transactions in this block (a structure that allows efficient verification).",
    "blockVersion": "Block format version. Encodes which consensus rules and soft forks are signaled.",
    "bits": "Compact encoding of the target threshold for a valid block hash.",
    "blockNonce": "A value miners iterate to find a block hash below the target.",
    "coinbaseHex": "Raw hexadecimal data of the coinbase transaction input.",
    "address": "A Bitcoin address derived from a public key, used to receive BTC.",
    "balance": "Total BTC held by this address across all unspent outputs.",
    "totalReceived": "Cumulative BTC received by this address across all transactions.",
    "utxos": "Unspent Transaction Outputs. Individual coins available to be spent by this address.",
    "txCount": "Total number of transactions involving this address."
  },
  "settings": {
    "knowledgeLevel": "Controls how much explanatory help is shown throughout the explorer."
  }
}

@MatiasOS
Copy link
Member

image

Missing all tooltips on state changes values. Is not clear what each value is.

- Fix tooltip z-index clipping by rendering bubbles via React Portal
  to document.body, escaping all ancestor overflow containers
- Enrich tooltip descriptions with EIP-1559, EIP-4844, UTXO model,
  Merkle tree, and proxy pattern explanations across all 5 locales
- Fix Arbitrum difficulty tooltip and circular blob definition
- Add missing tooltips: tx history table headers, account type,
  analyser tabs, finalized badge, contract details, Bitcoin
  confirmations, inputs/outputs columns, network stats, state changes
- Reclassify knowledge levels: beginner=all tooltips,
  intermediate=hides basic fields, advanced=complex/chain-specific only
- Update knowledge level descriptions in all locale settings files
Move coinbase and witness to left column, RBF to right column
for better visual grouping of related fields.
Fetch block height from block header when displaying confirmed
transactions. Block number is shown as plain text before the
clickable block hash link.
Support all four directions (top, bottom, left, right) for tooltip
bubbles. Use left placement for confirmation and finalized badges
in the top-right corner. Add vertical viewport clamping.
Compute final pixel position for both axes before removing transform,
so vertical translation is preserved when only horizontal clamping
is needed and vice versa.
Copy link
Collaborator

@josealoha666 josealoha666 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewing the new head 1a787a8:

  • the broken Japanese implementationAddress string I flagged earlier is fixed in the committed file
  • the follow-up patch covers the missing tooltip surfaces raised in review and the tooltip portal/z-index change looks directionally right
  • no new code issues jumped out in the incremental diff

I'm not approving yet because CI is still running, but from the code side my previous blockers look addressed.

Render a CSS triangle arrow inside the tooltip bubble that points
toward the trigger. Arrow position is dynamically calculated after
viewport clamping to always aim at the trigger center.
Copy link
Collaborator

@josealoha666 josealoha666 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick re-review for head cfc9998:

  • the new arrow-pointer addition in HelperTooltip looks consistent with the existing portal/clamp logic
  • I checked the incremental diff only; no new code issues stood out to me in this follow-up
  • CI is still pending on this head, so I'm leaving this as a comment rather than approving yet

Copy link
Collaborator

@josealoha666 josealoha666 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked the current head and I do not see an obvious blocking issue from the diff/API view. If CI is green, this looks fine from my side.

@AugustoL AugustoL merged commit 0a56e0c into openscan-explorer:dev Mar 19, 2026
4 of 7 checks passed
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.

3 participants