/** * One-shot Playwright probe to verify the Toast dismiss flow emits * `data-event*` perceptual signals on the item element. * * Usage: * 1. `npm run dev` in another terminal (defaults to port 5173) * 2. `node --import tsx/esm scripts/probe-toast-dismiss.ts` * * Output: list of mutations seen during the dismiss interaction. If the * runtime + semantic engine are wired correctly, you should see at minimum: * - data-event="dismiss" added * - data-event-id="sig-N" added * - data-event-phase="active" added * - data-event-family="emerge" added * …followed shortly after by their removal as the cleanup tick runs. */ import { chromium } from 'playwright' const URL = process.env.PROBE_URL ?? 'http://localhost:5173/test/soma/toast' async function main() { const browser = await chromium.launch() const page = await browser.newPage() const consoleLines: string[] = [] page.on('console', (msg) => consoleLines.push(`[${msg.type()}] ${msg.text()}`)) await page.goto(URL, { waitUntil: 'networkidle' }) // 1. Trigger a default toast (the demo page has an "Add default" button). const triggerBtn = page.getByRole('button', { name: /^default$/i }).first() await triggerBtn.click() // 2. Wait for the item to mount. const item = page.locator('[data-toast-item]').first() await item.waitFor({ state: 'attached', timeout: 5000 }) // 3. Install a MutationObserver in the page context that watches the item. await page.evaluate(() => { const item = document.querySelector('[data-toast-item]') as HTMLElement | null if (!item) { ;(window as unknown as Record).__probeResult = { error: 'item not found' } return } const captured: Array<{ time: number; attr: string; oldValue: string | null; newValue: string | null }> = [] const start = performance.now() const observer = new MutationObserver((mutations) => { for (const m of mutations) { if (m.type !== 'attributes' || !m.attributeName) continue captured.push({ time: Math.round(performance.now() - start), attr: m.attributeName, oldValue: m.oldValue, newValue: (m.target as HTMLElement).getAttribute(m.attributeName) }) } }) observer.observe(item, { attributes: true, attributeOldValue: true }) ;(window as unknown as Record).__probeStop = () => { observer.disconnect() return captured } }) // 4. Click the close button (× inside the toast item). const closeBtn = item.locator('[data-toast-close]').first() await closeBtn.click() // 5. Wait long enough for the dismiss + cleanup to complete (~5 frames). await page.waitForTimeout(200) // 6. Pull the captured mutations out. const result = (await page.evaluate(() => { const stop = (window as unknown as { __probeStop?: () => unknown; __probeResult?: unknown }) .__probeStop const errResult = (window as unknown as { __probeResult?: unknown }).__probeResult if (errResult) return errResult return stop ? stop() : { error: 'observer not installed' } })) as Array<{ time: number; attr: string; oldValue: string | null; newValue: string | null }> console.log('\n=== Console messages ===') for (const line of consoleLines) console.log(line) console.log('\n=== Mutations on the toast item during dismiss ===') if (Array.isArray(result) && result.length === 0) { console.log('(none)') } else if (Array.isArray(result)) { for (const m of result) { console.log(`+${m.time}ms ${m.attr}: ${JSON.stringify(m.oldValue)} → ${JSON.stringify(m.newValue)}`) } const sawSignal = result.some((m) => m.attr === 'data-event' && m.newValue === 'dismiss') console.log(`\nSignal observed: ${sawSignal ? '✅ YES' : '❌ NO'}`) } else { console.log('error result:', result) } await browser.close() } main().catch((err) => { console.error(err) process.exit(1) })