// Screenshot tour of the editor-libraries slice: // boot → open main.cjs (microlighter highlight mirror active, textarea text // transparent) → open notes.md (micro-composer replaces the textarea stack). // Start the dev server first (node scripts/serve.mjs on :8931), then run: // node scripts/screenshot-editor.mjs // Shots land in /tmp/nib-shots-editor/*.png. import { chromium } from 'playwright'; import { mkdirSync } from 'node:fs'; const DIR = '/tmp/nib-shots-editor'; mkdirSync(DIR, { recursive: true }); const browser = await chromium.launch(); const page = await browser.newPage({ viewport: { width: 1280, height: 800 } }); page.on('pageerror', (e) => console.log('[pageerror]', e.message)); page.on('console', (m) => { if (m.type() === 'error') console.log('[console.error]', m.text()); }); // 1. Boot: calm default — node_modules collapsed, editor empty. await page.goto('http://localhost:8931/demo/harness.html'); await page.waitForSelector('#tree [data-path="/app"]', { timeout: 10000 }); await page.waitForSelector('#sb-boot:has-text("VFS ready")', { timeout: 10000 }); await page.waitForTimeout(500); await page.screenshot({ path: `${DIR}/1-boot.png` }); console.log('1-boot.png — booted: explorer tree, empty editor, OUTPUT panel ready'); // 2. Open main.cjs — the microlighter mirror paints behind the transparent // textarea and registers CSS Custom Highlights. Waits + assertions are the // proof (the PNG cannot be inspected here). await page.click('#tree [data-path="/app/main.cjs"]'); await page.waitForSelector('#tabs .tab[data-path="/app/main.cjs"]'); await page.waitForFunction(() => document.getElementById('content')?.value.includes('node:fs'), null, { timeout: 10000 }); // The mirror must be visible, the textarea text transparent, and highlights // registered after a rAF. await page.waitForFunction(() => { const wrap = document.getElementById('hl-wrap'); const body = document.getElementById('editor-body'); return !wrap.hidden && body.classList.contains('hl-on'); }, null, { timeout: 10000 }); await page.waitForFunction(() => CSS.highlights.size > 0, null, { timeout: 10000 }); const hlStats = await page.evaluate(() => ({ highlights: CSS.highlights.size, mirrorChars: document.getElementById('hl-mirror')?.textContent.length ?? 0, contentChars: document.getElementById('content')?.value.length ?? 0, mirrorLang: document.getElementById('hl-mirror')?.dataset.language ?? null, })); console.log('2-code-highlight.png — main.cjs open:', JSON.stringify(hlStats)); if (hlStats.highlights <= 0) throw new Error('no CSS Custom Highlights registered'); if (hlStats.mirrorChars !== hlStats.contentChars) throw new Error('mirror out of sync with textarea'); await page.waitForTimeout(300); await page.screenshot({ path: `${DIR}/2-code-highlight.png` }); console.log('2-code-highlight.png — main.cjs with microlighter mirror (neon tokens behind transparent text)'); // 3. Open notes.md — the composer replaces the textarea stack entirely. await page.click('#tree [data-path="/app/notes.md"]'); await page.waitForSelector('#tabs .tab[data-path="/app/notes.md"]'); await page.waitForFunction(() => { const md = document.getElementById('md-editor'); const content = document.getElementById('content'); const gutter = document.getElementById('gutter'); return md && !md.hidden && content.hidden && gutter.hidden; }, null, { timeout: 10000 }); await page.waitForSelector('#md-editor [contenteditable]', { state: 'visible', timeout: 10000 }); const mdStats = await page.evaluate(() => { const input = document.querySelector('#md-editor [contenteditable]'); return { composerVisible: !document.getElementById('md-editor').hidden, textareaHidden: document.getElementById('content').hidden, gutterHidden: document.getElementById('gutter').hidden, text: input?.textContent ?? '', dirty: document.querySelector('#tabs .tab[data-path="/app/notes.md"]')?.dataset.dirty ?? null, }; }); console.log('3-md-composer.png — notes.md open:', JSON.stringify(mdStats)); if (!mdStats.text.includes('# scratch')) throw new Error('composer did not load the seeded markdown'); await page.waitForTimeout(300); await page.screenshot({ path: `${DIR}/3-md-composer.png` }); console.log('3-md-composer.png — notes.md in the micro-composer (textarea stack hidden, composer active)'); await browser.close(); console.log(`done — 3 shots in ${DIR}`);