import { bridge } from "../src/collab/bridge.ts"; import "../src/docs/element.ts"; import { draftSandbox } from "../src/sandbox-name.ts"; import { connectTo } from "../src/sandbox.ts"; import { editor, join } from "./peers.ts"; const PATH = "/work/notes.md"; const SAMPLE = `# Notes Processes in the kernel read and write this file with \`node:fs\`. Their cursors show what they changed. ## Ideas - Type here while a process runs `; // A process that types a sentence into the file one word at a time, reading before each write. const TYPIST = ` const fs = require("node:fs"); const path = ${JSON.stringify(PATH)}; const words = "The typist reads the file before every word, so whatever you type meanwhile is kept.".split(" "); const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); (async () => { for (const [i, word] of words.entries()) { const text = fs.readFileSync(path, "utf8"); // The sentence so far, wherever edits around it have moved it; the first word starts a new item. const typed = "- " + words.slice(0, i).join(" "); const marker = "- Type here while a process runs"; let at, insert; if (i > 0 && text.includes(typed)) [at, insert] = [text.indexOf(typed) + typed.length, " " + word]; else if (text.includes(marker)) [at, insert] = [text.indexOf(marker) + marker.length, "\\n- " + word]; else [at, insert] = [text.length, "\\n- " + word]; fs.writeFileSync(path, text.slice(0, at) + insert + text.slice(at)); await sleep(140); } console.log("typist: done"); })(); `; // A process that reads once, thinks, then saves atomically (temp file + rename), as editors and agents do. const REWRITE = ` const fs = require("node:fs"); const path = ${JSON.stringify(PATH)}; const text = fs.readFileSync(path, "utf8"); console.log("rewrite: read " + text.length + " characters, thinking for 3s"); setTimeout(() => { const next = text.replace(/^(#+ )(.*)$/gm, (_, hashes, title) => hashes + title.toUpperCase()) + "\\n## Summary\\n\\nHeadings uppercased by rewrite.js from the version it read.\\n"; fs.writeFileSync(path + ".tmp", next); fs.renameSync(path + ".tmp", path); console.log("rewrite: saved"); }, 3000); `; // A process that watches the file, as a dev server or test runner would. const WATCH = ` const fs = require("node:fs"); const path = ${JSON.stringify(PATH)}; fs.watch(path, () => { const text = fs.readFileSync(path, "utf8"); console.log("watch: " + text.split("\\n").length + " lines, " + text.length + " characters"); }); console.log("watch: watching " + path); `; const room = join("nib-kernel:playground", SAMPLE); for (const [name, color] of [["Ada", 0], ["Grace", 1]] as const) { const { doc, presence } = room.replica(); const el = editor("nib-doc", name.toLowerCase()); el.user = { name, colorClassName: `nib-user-${color}` }; el.presence = presence; el.doc = doc; } const output = document.querySelector("#console")!; const status = document.querySelector("#kernel-status")!; const decoder = new TextDecoder(); const log = (line: string) => { output.textContent += line; output.scrollTop = output.scrollHeight; }; async function pipe(stream: ReadableStream) { for await (const chunk of stream as unknown as AsyncIterable) log(decoder.decode(chunk)); } // The kernel runs on a sandbox origin, as the editor's does (D27), never on this page's. const kernel = await connectTo(draftSandbox()); await kernel.applyFiles({ "/app/typist.js": TYPIST, "/app/rewrite.js": REWRITE, "/app/watch.js": WATCH }); // The kernel gets its own replica of the doc, like any other peer. const { doc, presence } = room.replica(); bridge(kernel, PATH, doc, { presence }); Object.assign(globalThis, { kernel, kernelDoc: doc }); function run(script: string) { const proc = kernel.exec(`/app/${script}`, { argv: ["node", `/app/${script}`] }); void pipe(proc.stdout); void pipe(proc.stderr); return proc; } // Give the bridge's first write a moment to land before anything watches the file. await new Promise((resolve) => setTimeout(resolve, 50)); run("watch.js"); for (const [id, script] of [["#run-typist", "typist.js"], ["#run-rewrite", "rewrite.js"]] as const) { const button = document.querySelector(id)!; button.disabled = false; button.addEventListener("click", () => run(script)); } status.textContent = "Kernel ready. Run a process, then type in either editor while it works.";