Compare commits

...

3 Commits

Author SHA1 Message Date
clawbot
93565c7196 fix: match etherscan link styling to other views (remove extra border/hover/padding)
All checks were successful
check / check (push) Successful in 21s
The etherscanLinkHtml helper had added border, px-1, hover:bg-fg,
hover:text-bg, and cursor-pointer classes that no other view uses.
All other views (addressDetail, addressToken, confirmTx, send, receive,
home, txStatus) use only 'inline-flex items-center' on etherscan links.
Removed the extra classes for consistency.
2026-02-28 11:04:37 -08:00
clawbot
71ef08fe85 fix: address all 3 violations from #59
1. Protocol name now has Etherscan link (was appearing clickable but wasn't)
2. Token contract addresses: symbol shown separately, then color dot +
   full address + Etherscan link (symbol no longer interposed)
3. Raw data section moved after transaction hash (no longer pushes
   useful info off screen)
2026-02-28 11:03:20 -08:00
clawbot
8d230aceb6 fix: enforce UI policies on transaction detail view
- Add clickable affordance (border + hover state) to all Etherscan external
  links on addresses and transaction hash per clickable affordance policy
- Fix address display when ENS name is present: show color dot and Etherscan
  link on the full address line (previously only shown on ENS name line)
- Extract etherscanLinkHtml helper for consistent link styling

Closes #59
2026-02-28 11:03:20 -08:00
2 changed files with 45 additions and 21 deletions

View File

@@ -999,18 +999,18 @@
class="text-xs" class="text-xs"
></div> ></div>
</div> </div>
<div id="tx-detail-rawdata-section" class="hidden">
<div class="text-xs text-muted mb-1">Raw data</div>
<div
id="tx-detail-rawdata"
class="text-xs break-all font-mono border border-border border-dashed p-2"
></div>
</div>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<div class="text-xs text-muted mb-1">Transaction hash</div> <div class="text-xs text-muted mb-1">Transaction hash</div>
<div id="tx-detail-hash" class="text-xs break-all"></div> <div id="tx-detail-hash" class="text-xs break-all"></div>
</div> </div>
<div id="tx-detail-rawdata-section" class="mb-4 hidden">
<div class="text-xs text-muted mb-1">Raw data</div>
<div
id="tx-detail-rawdata"
class="text-xs break-all font-mono border border-border border-dashed p-2"
></div>
</div>
</div> </div>
<!-- ============ TRANSACTION APPROVAL ============ --> <!-- ============ TRANSACTION APPROVAL ============ -->

View File

@@ -37,11 +37,19 @@ function blockieHtml(address) {
return `<img src="${src}" width="48" height="48" style="image-rendering:pixelated;border-radius:50%;display:inline-block">`; return `<img src="${src}" width="48" height="48" style="image-rendering:pixelated;border-radius:50%;display:inline-block">`;
} }
function etherscanLinkHtml(url) {
return (
`<a href="${url}" target="_blank" rel="noopener" ` +
`class="inline-flex items-center"` +
`>${EXT_ICON}</a>`
);
}
function txAddressHtml(address, ensName, title) { function txAddressHtml(address, ensName, title) {
const blockie = blockieHtml(address); const blockie = blockieHtml(address);
const dot = addressDotHtml(address); const dot = addressDotHtml(address);
const link = `https://etherscan.io/address/${address}`; const link = `https://etherscan.io/address/${address}`;
const extLink = `<a href="${link}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`; const extLink = etherscanLinkHtml(link);
let html = `<div class="mb-1">${blockie}</div>`; let html = `<div class="mb-1">${blockie}</div>`;
if (title) { if (title) {
html += `<div class="font-bold">${escapeHtml(title)}</div>`; html += `<div class="font-bold">${escapeHtml(title)}</div>`;
@@ -50,10 +58,10 @@ function txAddressHtml(address, ensName, title) {
html += html +=
`<div class="flex items-center">${dot}` + `<div class="flex items-center">${dot}` +
copyableHtml(ensName, "") + copyableHtml(ensName, "") +
extLink +
`</div>` + `</div>` +
`<div class="break-all">` + `<div class="flex items-center">${dot}` +
copyableHtml(address, "break-all") + copyableHtml(address, "break-all") +
extLink +
`</div>`; `</div>`;
} else { } else {
html += html +=
@@ -67,7 +75,7 @@ function txAddressHtml(address, ensName, title) {
function txHashHtml(hash) { function txHashHtml(hash) {
const link = `https://etherscan.io/tx/${hash}`; const link = `https://etherscan.io/tx/${hash}`;
const extLink = `<a href="${link}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`; const extLink = etherscanLinkHtml(link);
return copyableHtml(hash, "break-all") + extLink; return copyableHtml(hash, "break-all") + extLink;
} }
@@ -141,9 +149,11 @@ function render() {
if (headingEl) headingEl.textContent = "Transaction"; if (headingEl) headingEl.textContent = "Transaction";
} }
// Hide calldata section; re-fetch if this is a contract call // Hide calldata and raw data sections; re-fetch if this is a contract call
const calldataSection = $("tx-detail-calldata-section"); const calldataSection = $("tx-detail-calldata-section");
if (calldataSection) calldataSection.classList.add("hidden"); if (calldataSection) calldataSection.classList.add("hidden");
const rawDataSection = $("tx-detail-rawdata-section");
if (rawDataSection) rawDataSection.classList.add("hidden");
if (tx.isContractCall || tx.direction === "contract") { if (tx.isContractCall || tx.direction === "contract") {
loadCalldata(tx.hash, tx.to); loadCalldata(tx.hash, tx.to);
@@ -194,9 +204,20 @@ async function loadCalldata(txHash, toAddress) {
for (const d of decoded.details || []) { for (const d of decoded.details || []) {
detailsHtml += `<div class="mb-2">`; detailsHtml += `<div class="mb-2">`;
detailsHtml += `<div class="text-muted">${escapeHtml(d.label)}</div>`; detailsHtml += `<div class="text-muted">${escapeHtml(d.label)}</div>`;
if (d.address) { if (d.address && d.isToken) {
// Token entry: show symbol on its own line, then dot + address + Etherscan link
const dot = addressDotHtml(d.address); const dot = addressDotHtml(d.address);
detailsHtml += `<div>${dot}${copyableHtml(d.value, "break-all")}</div>`; const tokenSymbol = d.value.match(/^(\S+)\s*\(/)?.[1];
if (tokenSymbol) {
detailsHtml += `<div class="font-bold">${escapeHtml(tokenSymbol)}</div>`;
}
const etherscanUrl = `https://etherscan.io/token/${d.address}`;
detailsHtml += `<div class="flex items-center">${dot}${copyableHtml(d.address, "break-all")}${etherscanLinkHtml(etherscanUrl)}</div>`;
} else if (d.address) {
// Protocol/contract entry: show name + Etherscan link
const dot = addressDotHtml(d.address);
const etherscanUrl = `https://etherscan.io/address/${d.address}`;
detailsHtml += `<div class="flex items-center">${dot}${copyableHtml(d.value, "break-all")}${etherscanLinkHtml(etherscanUrl)}</div>`;
} else { } else {
detailsHtml += `<div class="font-bold">${escapeHtml(d.value)}</div>`; detailsHtml += `<div class="font-bold">${escapeHtml(d.value)}</div>`;
} }
@@ -220,13 +241,16 @@ async function loadCalldata(txHash, toAddress) {
section.classList.remove("hidden"); section.classList.remove("hidden");
// Bind copy handlers for new elements // Bind copy handlers for new elements (including raw data now outside section)
section.querySelectorAll("[data-copy]").forEach((el) => { const copyTargets = [section, rawSection].filter(Boolean);
el.onclick = () => { for (const container of copyTargets) {
navigator.clipboard.writeText(el.dataset.copy); container.querySelectorAll("[data-copy]").forEach((el) => {
showFlash("Copied!"); el.onclick = () => {
}; navigator.clipboard.writeText(el.dataset.copy);
}); showFlash("Copied!");
};
});
}
} catch (e) { } catch (e) {
log.errorf("loadCalldata failed:", e.message); log.errorf("loadCalldata failed:", e.message);
} }