morphit/scripts/chat-inbox-instant-subject-smoke.ts

102 lines
4.2 KiB
TypeScript

#!/usr/bin/env tsx
/**
* chat-inbox-instant-subject — cp515 (Ken's t.txt).
*
* THE BUG. cp514 made the optimistic inbox card appear within ~4s of the push.
* Its SUBJECT still didn't: the "RE:" line rendered a placeholder for about a
* minute until the durable conversation row arrived. Ken: "please make the
* subject line show up immediately as well if it has an order id permlink
* attached to it (which in this case it does)."
*
* WHY it lagged: the fast push carries only (peer, orderPermlink), so the
* injected card's `order` was a STUB of empty strings — enough to link to, not
* enough to title. The fix resolves the real order (public data, owned by one
* of the two participants) and caches it per permlink.
*
* Tamper tests (each must turn this red):
* - Drop the resolver and go back to a bare empty-string stub → fails.
* - Call resolvePendingOrder from inside the $derived (impure, self-re-entering) → fails.
* - Hardcode `pending: true` again, so a resolved card still shows the placeholder → fails.
* - Mutate the map in place instead of reassigning (derived never re-runs) → fails.
*/
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const HERE = dirname(fileURLToPath(import.meta.url));
const REPO = join(HERE, '..');
const page = readFileSync(join(REPO, 'apps/web/src/routes/[lang]/chat/+page.svelte'), 'utf8');
let passed = 0;
let failed = 0;
const check = (name: string, cond: boolean, detail = ''): void => {
if (cond) {
console.log(`${name}`);
passed++;
} else {
console.log(`${name}${detail ? `: ${detail}` : ''}`);
failed++;
}
};
console.log('\n── chat-inbox-instant-subject (cp515) ────────────────\n');
check(
'the inbox caches resolved orders for optimistic cards',
/let pendingOrders = \$state<Map<string, ConversationOrderRef>>/.test(page)
);
check(
'it resolves the order from the participants (peer first, then me)',
/for \(const owner of \[peer, me\]\)/.test(page),
'the order always belongs to one of the two people in the thread'
);
check(
'it fetches the order rather than inventing a title',
/getOrdersByAccount\(owner/.test(page)
);
check(
'the resolved card uses the REAL order, falling back to the stub',
/const resolved = p\.orderPermlink \? pendingOrders\.get\(p\.orderPermlink\) : undefined;/.test(page) &&
/order: p\.orderPermlink\s*\?\s*\(resolved \?\?/.test(page)
);
check(
'`pending` means "a subject is still loading", not "this card is optimistic"',
/pending: p\.orderPermlink !== '' && resolved === undefined/.test(page),
'a resolved card must drop the placeholder; an order-LESS thread never shows one'
);
check(
'the map is REASSIGNED, not mutated (or the derived never re-runs)',
/const next = new Map\(pendingOrders\);[\s\S]{0,600}?pendingOrders = next;/.test(page)
);
check(
'an absent status renders no label rather than a fabricated "(Live)"',
/status: rec\.status \?\? ''/.test(page)
);
// The lookup must be driven by an effect. A $derived that fires fetches is
// impure and re-enters on its own result.
const derivedBody = /const sortedConversations = \$derived\.by\(\(\) => \{([\s\S]*?)\n\t\}\);/.exec(page)?.[1] ?? '';
check('the sortedConversations derived was located', derivedBody.length > 0);
check(
'the derived stays PURE — no resolver call inside it',
derivedBody.length > 0 && !/resolvePendingOrder\(/.test(derivedBody),
'firing a fetch from a derived re-enters when its own result lands'
);
check(
'an $effect drives the lookup instead',
/\$effect\(\(\) => \{[\s\S]{0,400}?resolvePendingOrder\(c\.peer, c\.order\.permlink\)/.test(page)
);
check(
'each permlink is fetched at most once (in-flight guard + cache check)',
/pendingOrders\.has\(permlink\) \|\| pendingOrderLookups\.has\(permlink\)/.test(page)
);
check(
'the in-flight guard is released even when the lookup throws',
/finally \{[\s\S]{0,120}?pendingOrderLookups\.delete\(permlink\)/.test(page)
);
console.log(
`\n${passed} passed, ${failed} failed\n${failed === 0 ? `✓ all ${passed} chat-inbox-instant-subject checks passed` : '✗ chat-inbox-instant-subject FAILED'}`
);
process.exit(failed === 0 ? 0 : 1);