From 2b0971d2f23f694a9ac7a5e8dafa4efeda14723b Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Wed, 11 Feb 2026 01:15:09 -0600 Subject: [PATCH 1/2] Add copy-output plugin to dev script for root file synchronization - Introduced a new plugin in the dev script to copy the built extension files (JavaScript and CSS) from the dist directory to the project root. This allows for easier development and testing by ensuring that the root files are always up-to-date with the latest builds. --- .gitignore | 1 + scripts/dev.ts | 31 +++++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index b7022dd..0b5a2cd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ dist out .env extension.js +extension.css extension.js.LICENSE.txt diff --git a/scripts/dev.ts b/scripts/dev.ts index 3b5e789..e2a21a9 100644 --- a/scripts/dev.ts +++ b/scripts/dev.ts @@ -1,5 +1,7 @@ import esbuild from "esbuild"; import dotenv from "dotenv"; +import fs from "fs"; +import path from "path"; import { compile, args } from "./compile"; dotenv.config(); @@ -9,8 +11,33 @@ const dev = () => { return new Promise((resolve) => { compile({ opts: args, - builder: (opts: esbuild.BuildOptions) => - esbuild.context(opts).then((esb) => esb.watch()), + builder: (opts: esbuild.BuildOptions) => { + const outFile = path.join(process.cwd(), "dist", "extension.js"); + const rootFile = path.join(process.cwd(), "extension.js"); + const outCssFile = path.join(process.cwd(), "dist", "extension.css"); + const rootCssFile = path.join(process.cwd(), "extension.css"); + // Keep a root extension.js so Roam dev loading can target tldraw/ instead of tldraw/dist/. + const copyOutputPlugin: esbuild.Plugin = { + name: "copy-output-to-root", + setup(build) { + build.onEnd((result) => { + if (result.errors.length) return; + if (fs.existsSync(outFile)) { + fs.cpSync(outFile, rootFile); + } + if (fs.existsSync(outCssFile)) { + fs.cpSync(outCssFile, rootCssFile); + } + }); + }, + }; + return esbuild + .context({ + ...opts, + plugins: [...(opts.plugins || []), copyOutputPlugin], + }) + .then((esb) => esb.watch()); + }, }); process.on("exit", resolve); }); From c48ed4dd5985e2e298f0528411981a6bdba6ec6c Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Wed, 11 Feb 2026 01:21:57 -0600 Subject: [PATCH 2/2] Enhance Tldraw component to handle multiple canvas instances and display warning toast - Added logic to detect existing canvas instances by class and ID, preventing conflicts. - Implemented a warning toast notification when multiple canvases are detected, advising users to disable conflicting extensions for optimal performance. --- src/components/canvas/Tldraw.tsx | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/components/canvas/Tldraw.tsx b/src/components/canvas/Tldraw.tsx index 56b587c..c72d7bd 100644 --- a/src/components/canvas/Tldraw.tsx +++ b/src/components/canvas/Tldraw.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useMemo, useState, useRef } from "react"; import { OnloadArgs } from "roamjs-components/types"; import renderWithUnmount from "roamjs-components/util/renderWithUnmount"; +import renderToast from "roamjs-components/components/Toast"; import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle"; import getUids from "roamjs-components/dom/getUids"; import { @@ -662,8 +663,32 @@ const renderTldrawCanvasHelper = ({ if (!childFromRoot?.parentElement) return () => {}; const parentEl = childFromRoot.parentElement; - if (parentEl.querySelector(".roamjs-tldraw-canvas-container")) - return () => {}; + const hasExistingCanvasByClass = !!parentEl.querySelector( + ".roamjs-tldraw-canvas-container", + ); + // Query Builder uses this id for the canvas mount. + const hasExistingCanvasById = !!parentEl.querySelector( + "#roamjs-tldraw-canvas-container", + ); + const hasExistingCanvas = hasExistingCanvasByClass || hasExistingCanvasById; + // Query Builder tags rm-block-children with this attribute when it owns the canvas mount. + const hasQueryBuilderCanvasOwner = childFromRoot.hasAttribute( + "data-roamjs-discourse-playground", + ); + if ( + hasQueryBuilderCanvasOwner && + !parentEl.hasAttribute("data-roamjs-tldraw-qb-warning-shown") + ) { + parentEl.setAttribute("data-roamjs-tldraw-qb-warning-shown", "true"); + renderToast({ + id: "tldraw-query-builder-canvas-conflict", + intent: "warning", + content: `Multiple canvases have been detected on this page. This may cause unexpected behavior. + +Either disable the tldraw extension or disable the Query Builder Discourse Graph flag.`, + }); + } + if (hasExistingCanvas || hasQueryBuilderCanvasOwner) return () => {}; const canvasWrapperEl = document.createElement("div"); parentEl.appendChild(canvasWrapperEl);