fix(potidaea): welcome overlay hides properly + compact classified errors
Two bugs one commit:
1) Welcome overlay left the swap layout at the bottom of the page
.welcome-overlay { display: flex; min-height: calc(100vh - 140px) }
is an author rule (specificity 0,1,0) that outranks the user-agent
[hidden] { display: none } (same specificity, but user-agent). So
overlay.hidden = true had no visual effect: the empty container kept
eating a full viewport and shoved .od-layout below the fold. Reported
by user with a screenshot showing panels at the very bottom after
wallet import.
Fix: explicit .welcome-overlay[hidden] { display: none !important; }
in the author sheet (specificity 0,2,0 wins). Verified live: overlay
collapses to display:none / height 0 after import; .od-layout sits
at y=181 (just under the topbar) as expected.
2) Swap-sheet error was a wall of text with no way to grab it
setSheetError previously dumped the raw message into a single div
with textContent. Broadcast / covenant errors can be multi-KB dumps
(tx hex, cashscript stack, Bitauth trace) and blew up the review
sheet vertically. Reported by user attempting MBA→BCH redeem.
Fix:
- classifySwapError maps common patterns to short code labels:
E-INSUFFICIENT / E-DUST / E-CHANGE-DUST / E-COVENANT-STATE /
E-STALE-UTXO / E-FEE-LOW / E-NET / E-COVENANT / E-LEFTOVER-DUST /
E-REDEEM-SMALL / E-OTHER
- Compact display: mark + code chip + one-line headline + copy
button + expand toggle
- Full text lives in a collapsible block (max-height 240px, scroll)
that stays hidden until the user clicks the details arrow
- Copy button writes "[E-CODE] <full text>" to clipboard so a
support paste starts with the error code
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
acdfceaf40
commit
440416f522
2 changed files with 118 additions and 2 deletions
|
|
@ -731,6 +731,44 @@
|
|||
.ma-ico { width: 32px; height: 32px; }
|
||||
}
|
||||
|
||||
/* ============ Swap-sheet error chip ============ */
|
||||
/* Compact error surface on the review sheet: red mark + short code chip +
|
||||
* one-line headline + copy / details toggle. Full text lives in a
|
||||
* collapsible block below so raw broadcast/covenant dumps don't blow up
|
||||
* the sheet vertically. */
|
||||
.wc-sheet-err {
|
||||
font-family: var(--font-mono); font-size: 11px;
|
||||
color: #ff7a8a;
|
||||
background: rgba(255,122,138,.06);
|
||||
border: 1px solid rgba(255,122,138,.28);
|
||||
border-radius: 10px;
|
||||
padding: 10px 12px; margin: 10px 0;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.wc-sheet-err .wc-err-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
||||
.wc-sheet-err .wc-err-mark { color: #ff7a8a; font-size: 13px; }
|
||||
.wc-sheet-err .wc-err-code {
|
||||
font-family: var(--font-mono); font-size: 9px; letter-spacing: .08em;
|
||||
padding: 2px 6px; border-radius: 4px;
|
||||
color: #ff7a8a; border: 1px solid rgba(255,122,138,.32);
|
||||
background: rgba(255,122,138,.08);
|
||||
white-space: nowrap; flex: none;
|
||||
}
|
||||
.wc-sheet-err .wc-err-head { flex: 1; min-width: 0; color: var(--ink-bright); font-size: 12px; }
|
||||
.wc-sheet-err .wc-err-btn {
|
||||
background: transparent; border: 1px solid rgba(255,122,138,.22);
|
||||
color: rgba(255,122,138,.7); border-radius: 5px;
|
||||
padding: 2px 6px; font-family: var(--font-mono); font-size: 11px; line-height: 1;
|
||||
cursor: pointer; transition: color .15s, border-color .15s;
|
||||
flex: none;
|
||||
}
|
||||
.wc-sheet-err .wc-err-btn:hover { color: #ff7a8a; border-color: rgba(255,122,138,.5); }
|
||||
.wc-sheet-err .wc-err-full {
|
||||
margin-top: 8px; padding-top: 8px; border-top: 1px dashed rgba(255,122,138,.22);
|
||||
font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-soft);
|
||||
max-height: 240px; overflow: auto; word-break: break-word; white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/* ============ Tx-receipt mini-buttons (copy / view on explorer) ============ */
|
||||
/* Small square action buttons that sit next to a rendered txid across
|
||||
* the swap done sheet, wizard genesis done, and publish overlay. */
|
||||
|
|
@ -752,6 +790,12 @@
|
|||
* card, not a half-empty grid behind it. */
|
||||
#v-swap.v-swap-welcome-mode .od-layout { display: none; }
|
||||
.welcome-overlay { display: flex; align-items: center; justify-content: center; min-height: calc(100vh - 140px); padding: 40px 20px; }
|
||||
/* Explicit [hidden] override — an author `display: flex` rule wins over
|
||||
* the user-agent stylesheet's `[hidden] { display: none }`, so we have
|
||||
* to spell it out or the container keeps eating a full viewport of
|
||||
* vertical space (and shoves the real swap layout to the bottom of the
|
||||
* page) even when overlay.hidden = true. */
|
||||
.welcome-overlay[hidden] { display: none !important; }
|
||||
.welcome-card {
|
||||
max-width: 640px; width: 100%;
|
||||
background: var(--surface-1); border: 1px solid var(--border);
|
||||
|
|
|
|||
|
|
@ -788,15 +788,87 @@ function applyOptimisticSwapDelta(kind: 'claim' | 'redeem', amountUnits: bigint,
|
|||
try { paintOdAll(); } catch { /* noop */ }
|
||||
}
|
||||
|
||||
/** Classify a raw error message into a short code + one-line headline the
|
||||
* user can scan without reading a wall of text. Broadcast errors from
|
||||
* Bitauth / cashscript often dump the failing tx as a multi-KB hex blob;
|
||||
* the review sheet is not the place for that. Full text stays in the
|
||||
* details section for copy-and-paste debugging. */
|
||||
function classifySwapError(raw: string): { code: string; headline: string; full: string } {
|
||||
const full = raw.trim();
|
||||
// Pattern → { code, headline extractor }
|
||||
const patterns: Array<{ re: RegExp; code: string; head?: (m: RegExpMatchArray) => string }> = [
|
||||
{ re: /^insufficient\s+([a-z0-9]+):.*need\s+(\d+).*have\s+(\d+)/i, code: 'E-INSUFFICIENT',
|
||||
head: (m) => `Not enough ${m[1].toUpperCase()} — need ${m[2]}, have ${m[3]}` },
|
||||
{ re: /amount too small|below dust|feeSats\s*>=?/i, code: 'E-DUST',
|
||||
head: () => 'Amount below the covenant’s dust floor' },
|
||||
{ re: /change below dust/i, code: 'E-CHANGE-DUST',
|
||||
head: () => 'BCH change would fall below the dust threshold' },
|
||||
{ re: /covenant may be draining|no state utxo/i, code: 'E-COVENANT-STATE',
|
||||
head: () => 'Registry covenant state UTXO missing' },
|
||||
{ re: /64: bad-txns-inputs-missingorspent|missing inputs/i, code: 'E-STALE-UTXO',
|
||||
head: () => 'Chosen UTXOs already spent — endpoint index is stale' },
|
||||
{ re: /min relay fee|not enough fee/i, code: 'E-FEE-LOW',
|
||||
head: () => 'Miner fee too low to relay' },
|
||||
{ re: /timeout|connect failed|not connected|electrum:/i, code: 'E-NET',
|
||||
head: () => 'Electrum endpoint unreachable' },
|
||||
{ re: /require\s+statement\s+failed|failed\s+at\s+input/i, code: 'E-COVENANT',
|
||||
head: (m) => {
|
||||
const m2 = full.match(/DeviantOpenMintFT\.cash:(\d+)/i);
|
||||
return m2 ? `Covenant require() failed at line ${m2[1]}` : 'Covenant require() failed';
|
||||
} },
|
||||
{ re: /leftover dust padding/i, code: 'E-LEFTOVER-DUST',
|
||||
head: () => 'Leftover after redeem would fall below dust' },
|
||||
{ re: /^redeem too small/i, code: 'E-REDEEM-SMALL',
|
||||
head: () => 'Redeem amount too small to cover miner fee' },
|
||||
];
|
||||
for (const p of patterns) {
|
||||
const m = full.match(p.re);
|
||||
if (m) return { code: p.code, headline: p.head ? p.head(m) : m[0], full };
|
||||
}
|
||||
// Fallback — take the first sentence-ish chunk (up to first period, dash,
|
||||
// or 90 chars) as the headline; everything else lives in details.
|
||||
const first = full.split(/[.\n]|—/, 1)[0].trim().slice(0, 90);
|
||||
return { code: 'E-OTHER', headline: first || 'Swap failed', full };
|
||||
}
|
||||
|
||||
function setSheetError(msg: string): void {
|
||||
let el = document.getElementById('wc-sheet-err') as HTMLElement | null;
|
||||
if (!el) {
|
||||
el = document.createElement('div'); el.id = 'wc-sheet-err';
|
||||
el.style.cssText = 'font-family:var(--font-mono);font-size:11px;color:#ff7a8a;background:rgba(255,122,138,.08);border:1px solid rgba(255,122,138,.28);border-radius:8px;padding:8px 10px;margin:10px 0;';
|
||||
const rev = document.getElementById('reviewSheet');
|
||||
rev?.insertBefore(el, rev.firstChild);
|
||||
}
|
||||
el.textContent = '✗ ' + msg;
|
||||
el.className = 'wc-sheet-err';
|
||||
const { code, headline, full } = classifySwapError(msg);
|
||||
const detailsCollapsed = full.length > 120;
|
||||
el.innerHTML =
|
||||
'<div class="wc-err-row">' +
|
||||
'<span class="wc-err-mark">✗</span>' +
|
||||
'<span class="wc-err-code" title="Error reference">' + escHtml(code) + '</span>' +
|
||||
'<span class="wc-err-head">' + escHtml(headline) + '</span>' +
|
||||
'<button class="wc-err-btn" data-err-copy="1" title="Copy full error text">⧉</button>' +
|
||||
(detailsCollapsed
|
||||
? '<button class="wc-err-btn" data-err-toggle="1" title="Show / hide full text">⌄</button>'
|
||||
: '') +
|
||||
'</div>' +
|
||||
(detailsCollapsed
|
||||
? '<div class="wc-err-full" hidden>' + escHtml(full) + '</div>'
|
||||
: (full !== headline ? '<div class="wc-err-full">' + escHtml(full) + '</div>' : ''));
|
||||
el.querySelector<HTMLButtonElement>('[data-err-copy]')?.addEventListener('click', async (ev) => {
|
||||
ev.stopPropagation();
|
||||
try {
|
||||
await navigator.clipboard.writeText('[' + code + '] ' + full);
|
||||
const btn = ev.currentTarget as HTMLButtonElement;
|
||||
const orig = btn.textContent;
|
||||
btn.textContent = '✓';
|
||||
setTimeout(() => { btn.textContent = orig; }, 1000);
|
||||
} catch { /* clipboard blocked — silent */ }
|
||||
});
|
||||
el.querySelector<HTMLButtonElement>('[data-err-toggle]')?.addEventListener('click', (ev) => {
|
||||
ev.stopPropagation();
|
||||
const details = el!.querySelector<HTMLElement>('.wc-err-full');
|
||||
if (details) details.hidden = !details.hidden;
|
||||
});
|
||||
}
|
||||
|
||||
function wireSwapConfirmInterceptor(): void {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue