/** * demo/solo.ts * * Single-persona demo: opens the collab-editor as "Alice", types the title * and one paragraph with human-like rhythm. Use this to visually validate * that the typing feels natural before scaling to multiple personas. * * Prereqs: * - Frontend dev server running on http://localhost:5678 * - Backend dev server running on http://localhost:8080 * * Run: * cd backend && npx tsx demo/solo.ts * * Tuning: * - Pass `--speed 1.5` to type faster, `--speed 0.7` to slow it down. * - Pass `--url http://localhost:5678` to override the target. */ import { chromium } from "playwright"; import { humanType, humanTypeInto, pause } from "./human-typing.js"; import { PERSONAS, FALLBACK_USER_KEY } from "./personas.js"; interface CliArgs { url: string; speed: number; headless: boolean; } function parseArgs(argv: string[]): CliArgs { const args: CliArgs = { url: "http://localhost:5678", speed: 1, headless: false, }; for (let i = 0; i < argv.length; i++) { const arg = argv[i]; if (arg === "--url") args.url = argv[++i]; else if (arg === "--speed") args.speed = Number(argv[++i]); else if (arg === "--headless") args.headless = true; } return args; } async function run() { const args = parseArgs(process.argv.slice(2)); const persona = PERSONAS.alice; console.log(`[demo/solo] launching as ${persona.name} (${persona.color})`); console.log(`[demo/solo] target: ${args.url} speed: ${args.speed}x`); const browser = await chromium.launch({ headless: args.headless, args: ["--window-size=1280,800"], }); const context = await browser.newContext({ viewport: { width: 1280, height: 800 }, deviceScaleFactor: 2, }); await context.addInitScript( ({ key, value }) => { try { localStorage.setItem(key, value); } catch { /* ignore */ } }, { key: FALLBACK_USER_KEY, value: JSON.stringify({ name: persona.name, color: persona.color }), } ); const page = await context.newPage(); console.log(`[demo/solo] goto ${args.url}`); try { await page.goto(args.url, { waitUntil: "domcontentloaded", timeout: 15_000 }); } catch (err) { console.error( `[demo/solo] cannot reach ${args.url}. Is the frontend dev server running?` ); await browser.close(); process.exit(1); } await page.waitForSelector("[aria-label='Undo']", { timeout: 20_000 }); console.log(`[demo/solo] editor ready, starting scenario`); await pause(1200, 2000); const title = page.getByPlaceholder("Article title"); await humanTypeInto(page, title, "Climate signals in early 2026", { speed: args.speed, typoRate: 0.03, }); await pause(900, 1600); const body = page.locator(".ProseMirror").first(); await body.click(); await pause(600, 1200); await humanType( page, "Over the last six months, a handful of monitoring stations picked up anomalies that deserve a closer look. ", { speed: args.speed, typoRate: 0.035, thinkRate: 0.22 } ); await pause(800, 1400); await humanType( page, "We cross-checked three independent datasets before drawing any conclusions.", { speed: args.speed, typoRate: 0.025 } ); await pause(1500, 2500); console.log(`[demo/solo] scenario complete. Leaving browser open for inspection.`); console.log(`[demo/solo] press Ctrl+C to quit.`); await new Promise(() => {}); } run().catch((err) => { console.error("[demo/solo] fatal:", err); process.exit(1); });