carbon-tokenization / backend /demo /test-yjs-client.ts
tfrere's picture
tfrere HF Staff
feat(demo): full showcase polish - mouse cursor, robust selection, HF affiliations
f469961
Raw
History Blame Contribute Delete
3.42 kB
/**
* 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);
});