morphit/apps/web/scripts/payment-receipt-presentation-smoke.ts

162 lines
6.7 KiB
TypeScript

#!/usr/bin/env tsx
/**
* payment-receipt-presentation — cp474 (t.txt #8).
*
* Ken, on live morphit.io, listed three things wrong with the Payment Receipt
* bubble. All three are presentation living in a .svelte file, which no vitest
* can reach, so they are pinned here.
*
* (a) "get rid of the underline that appears under the magnifying glass and
* its 'Verify on block explorer' text ... only show that underline (as
* dots) when i mouseover it."
*
* (b) "where it says 'BLURT SENT|RECEIVED' change that text to say 'BLURT
* SENT|RECEIVED on 14 May, 2026 @ 05:03:22 UTC' and remove that date/time
* from everywhere else on the Payment Receipt. right now, when you
* mouseover anywhere on the payment receipt, that date/time alt text
* appears and it's annoying."
*
* (c) "the entire Payment Receipt bubble seems to be hyperlinked to nothing,
* so please remove that hyperlink from the card itself."
*
* (b) and (c) were ONE mechanism, which is why they arrived together: the
* bubble carried `title={fullTimestamp}` — a native tooltip that fires anywhere
* over the card and can't be styled or dismissed — plus `cursor-pointer` and an
* onclick, which is what made it look like a link to nowhere. The click only
* toggled a popover showing that same timestamp. A receipt is a document: the
* time belongs printed on its face, and once it is there the popover is a
* duplicate of something already on screen. So the receipt opts out of the
* affordance entirely.
*
* Ordinary message bubbles MUST keep it (cp402 [5]): their timestamp is printed
* nowhere, so tap-to-reveal is the only way to see it. Removing the popover
* wholesale would fix Ken's receipt and silently break every other bubble —
* this smoke pins both halves.
*
* Tamper tests (each must turn this smoke red):
* - Put `underline` back on the verify link's resting state → fails.
* - Restore `title={fullTimestamp || undefined}` on the bubble → fails.
* - Make `timestampRevealable` ignore isReceipt → fails.
* - Drop the timestamp from the SENT/RECEIVED line → fails.
* - Remove the tap-to-reveal popover from ordinary bubbles → fails.
*/
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { SUPPORTED_LOCALES } from '../src/lib/i18n/locales';
const HERE = dirname(fileURLToPath(import.meta.url));
const REPO = join(HERE, '..', '..', '..');
// Derived, never hand-listed — `locale-source-of-truth-smoke` caught exactly this
// in the first battery run after this file was written, and it was right to: an
// inline array silently stops covering locale #11 the day one is added, while
// still reporting green.
const LOCALES = SUPPORTED_LOCALES.map((l) => l.code);
function read(rel: string): string {
return readFileSync(join(REPO, rel), 'utf8');
}
let passed = 0;
let failed = 0;
function check(name: string, cond: boolean, detail = ''): void {
if (cond) {
console.log(`${name}`);
passed++;
} else {
console.log(`${name}${detail ? `: ${detail}` : ''}`);
failed++;
}
}
console.log('\n── payment-receipt-presentation (cp474 / t.txt #8) ────\n');
const msg = read('apps/web/src/lib/components/ChatMessage.svelte');
// ─── (a) the verify link's underline is hover/focus-only, dotted ─
const verifyLink =
/<a\s+href=\{lp\(verifyPath\)\}\s+class="([^"]*)"/.exec(msg)?.[1] ?? '';
check('the "Verify on block explorer" link was found', verifyLink.length > 0);
check(
'(a) no underline at rest',
verifyLink.includes('no-underline') && !/(^|\s)underline(\s|$)/.test(verifyLink),
`class was: ${verifyLink.slice(0, 120)}`
);
check('(a) underline appears on hover', verifyLink.includes('hover:underline'));
check('(a) …and it is DOTTED, per Ken', verifyLink.includes('decoration-dotted'));
check(
'(a) keyboard users get the same dots (no pointer to reveal them with)',
verifyLink.includes('focus-visible:underline')
);
// ─── (b)+(c) the receipt opts out of the timestamp affordance ────
check(
'(b/c) a receipt is identified for presentation purposes',
/const isReceipt = \$derived\(decoded\?\.kind === 'funds_sent'\)/.test(msg)
);
check(
'(b/c) the timestamp affordance is gated on NOT being a receipt',
/const timestampRevealable = \$derived\(fullTimestamp !== '' && !isReceipt\)/.test(msg),
'without !isReceipt the tooltip + pseudo-link come straight back'
);
check(
'(b) the bubble tooltip is suppressed on a receipt',
/title=\{timestampRevealable \? fullTimestamp : undefined\}/.test(msg),
'a bare title={fullTimestamp} fires anywhere over the card — the "annoying" mouseover'
);
check(
'(c) the pointer cursor (the "hyperlink to nothing") is gone on a receipt',
/class:cursor-pointer=\{timestampRevealable\}/.test(msg)
);
check(
'(c) the click handler no-ops on a receipt',
/function onBubbleActivate[\s\S]{0,240}?if \(!timestampRevealable\) return;/.test(msg)
);
// ─── (b) the timestamp is printed ON the SENT/RECEIVED line ──────
check(
'(b) the SENT/RECEIVED line prints the timestamp',
/chat\.funds_sent\.receipt_when[\s\S]{0,60}?values: \{ when: fullTimestamp \}/.test(msg),
'this is the "remove it from everywhere else" half — it has to be SOMEWHERE'
);
check(
'(b) the printed timestamp uses the canonical sitewide formatter',
/formatDayMonthTime/.test(msg),
'a second date format on one card is how a date standard dies'
);
check(
'(b) the uppercase pill row does not shout the month name',
/normal-case/.test(msg),
'the row is `uppercase`; without normal-case it renders "14 MAY, 2026"'
);
// ─── ordinary bubbles must KEEP the popover ──────────────────────
check(
'ordinary (non-receipt) bubbles still reveal their timestamp on tap',
/showTimestamp = !showTimestamp/.test(msg),
'their timestamp is printed nowhere — removing this would break every other bubble to fix the receipt'
);
// ─── locale parity: all ten, same turn ───────────────────────────
for (const loc of LOCALES) {
const json = JSON.parse(read(`apps/web/src/lib/i18n/locales/${loc}.json`)) as {
chat?: { funds_sent?: Record<string, string> };
};
const v = json.chat?.funds_sent?.receipt_when;
check(
`locale ${loc}: receipt_when present and carries {when}`,
typeof v === 'string' && v.includes('{when}'),
v === undefined ? 'missing' : `got ${JSON.stringify(v)}`
);
}
console.log(`\n${'─'.repeat(54)}`);
if (failed === 0) {
console.log(`✓ all ${passed} payment-receipt-presentation checks passed`);
process.exit(0);
} else {
console.log(`${failed}/${passed + failed} payment-receipt-presentation checks failed`);
process.exit(1);
}