// Screenshot tour of the VS Code-restyled harness: // boot → expand node_modules → open two tabs with a dirty edit → run a guest // from the OUTPUT panel → open the tree context menu. // Start the dev server first (node scripts/serve.mjs on :8931), then run: // node scripts/screenshot-vscode.mjs // Shots land in /tmp/nib-shots-vc/*.png. import { chromium } from 'playwright'; import { mkdirSync } from 'node:fs'; const DIR = '/tmp/nib-shots-vc'; mkdirSync(DIR, { recursive: true }); const browser = await chromium.launch(); const page = await browser.newPage({ viewport: { width: 1280, height: 800 } }); // 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. Expand node_modules via its twisty. await page.click('#tree [data-path="/app/node_modules"] .twisty'); await page.waitForSelector('#tree [data-path="/app/node_modules/leftpad"]'); await page.waitForTimeout(300); await page.screenshot({ path: `${DIR}/2-expanded.png` }); console.log('2-expanded.png — node_modules expanded via its twisty (leftpad + shouty)'); // 3. Open two tabs, dirty one of them. 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 }); await page.click('#tree [data-path="/app/notes.md"]'); await page.waitForSelector('#tabs .tab[data-path="/app/notes.md"]'); await page.waitForFunction(() => document.getElementById('content')?.value === '# scratch\n', null, { timeout: 10000 }); await page.fill('#content', '# scratch\nedited for the screenshot\n'); await page.waitForSelector('#tabs .tab[data-path="/app/notes.md"][data-dirty="true"]'); await page.waitForTimeout(300); await page.screenshot({ path: `${DIR}/3-tabs-dirty.png` }); console.log('3-tabs-dirty.png — two tabs; notes.md dirty (● in tab), gutter line numbers visible'); // 4. Run a guest from the OUTPUT panel; its writeFileSync lands in the tree. await page.fill('#entry', '/app/writer.cjs'); await page.click('#btn-run'); await page.waitForSelector('#tree [data-path="/app/scratch/from-guest.txt"]', { timeout: 10000 }); await page.waitForFunction(() => document.getElementById('exit')?.textContent === '0', null, { timeout: 10000 }); await page.waitForTimeout(300); await page.screenshot({ path: `${DIR}/4-output-run.png` }); console.log('4-output-run.png — guest ran in OUTPUT panel (stdout + exit badge 0), from-guest.txt in tree'); // 5. Context menu open on a tree row. await page.click('#tree [data-path="/app/notes.md"]', { button: 'right' }); await page.waitForSelector('#context-menu', { state: 'visible' }); await page.waitForTimeout(200); await page.screenshot({ path: `${DIR}/5-context-menu.png` }); console.log('5-context-menu.png — tree context menu at the cursor (New File/New Folder/Rename/Delete/Copy Path)'); await browser.close(); console.log(`done — 5 shots in ${DIR}`);