/** * Standalone test for the Yjs-based peer client. * * Run with the dev backend + frontend running, and preferably with * an Alice browser tab also open so you can watch the edits land * live on her screen. * * Usage: * npx tsx demo/test-yjs-client.ts * * What it does: * - connects as a Yjs peer with name "Probe" ✔ visible in Alice's byline * - adds an author "Probe Author" tied to a new affiliation * - creates a heading "Yjs probe section" + an empty slot * - types a paragraph with inline math atoms + a citation chip * - registers the citation entry in the shared citations map * - prints the resulting section structure * - disconnects */ import { addAuthorRecord, appendInline, buildEmptyParagraph, connectPersona, insertInlineNode, registerCitation, typeIntoParagraph, } from "./lib/yjs-client.js"; import * as Y from "yjs"; async function main() { console.log("connecting to ws://localhost:5678/collab ..."); const probe = await connectPersona({ url: "ws://localhost:5678/collab", docName: "default", user: { name: "Probe", color: "#ff8800" }, }); console.log("synced"); // Author push addAuthorRecord(probe, { name: "Probe Author", newAffiliation: { name: "Test Lab" }, }); console.log("author pushed, total authors:", probe.frontmatter.authors.length); // Write a probe section at the end of the doc. Elements are // created detached and attached in one transaction so the text // child can be inserted from inside the attached heading. const heading = new Y.XmlElement("heading"); heading.setAttribute("level", 2); const paragraph = buildEmptyParagraph(); probe.ydoc.transact(() => { probe.fragment.push([heading, paragraph]); const headingText = new Y.XmlText(); heading.push([headingText]); headingText.insert(0, "Yjs probe section"); }); console.log("heading + empty paragraph pushed"); await typeIntoParagraph( paragraph, "Typed via a Node-side Yjs peer: text, math ", { cps: 30 }, ); await insertInlineNode(paragraph, { type: "inlineMath", latex: "e^{i\\pi}" }, 120); await typeIntoParagraph(paragraph, ", and a citation chip ", { cps: 30 }); registerCitation(probe, "probe2026yjs", { id: "probe2026yjs", "citation-key": "probe2026yjs", type: "article-journal", title: "Probing Yjs via a Node peer", author: [{ family: "Probe", given: "Test" }], issued: { "date-parts": [[2026]] }, }); appendInline(paragraph, [ { type: "citation", key: "probe2026yjs", label: "(Probe, 2026)", }, ]); await typeIntoParagraph(paragraph, ".", { cps: 30 }); // Dump the probe section back out as plain text console.log("\nfinal paragraph text:"); const walk = (el: Y.XmlElement | Y.XmlText): string => { if (el instanceof Y.XmlText) return el.toString(); if (el.nodeName === "inlineMath") return `$${el.getAttribute("latex")}$`; if (el.nodeName === "citation") return `[${el.getAttribute("label")}]`; return el .toArray() .map((c) => walk(c as Y.XmlElement | Y.XmlText)) .join(""); }; console.log( paragraph .toArray() .map((c) => walk(c as Y.XmlElement | Y.XmlText)) .join(""), ); console.log("\nclosing..."); probe.destroy(); process.exit(0); } main().catch((err) => { console.error("probe failed:", err); process.exit(1); });