Skip to content

Conversation

@trangdoan982
Copy link
Collaborator

@trangdoan982 trangdoan982 commented Sep 1, 2025

Summary by CodeRabbit

  • New Features

    • Interactive Canvas view powered by tldraw: create/connect Discourse Nodes and Relations, search/add existing nodes, relation overlay/panel, tool panel, autosave, and toast notifications.
    • New commands to create a canvas and switch between Markdown and Canvas views.
    • “View as canvas” action and auto-open for eligible notes.
  • Enhancements

    • Relation types now support colors with UI controls.
    • New settings for default Canvas and attachments folders.
    • Improved node search and SearchBar UX.
  • Documentation

    • Added Canvas documentation and navigation link.
  • Chores

    • Added dependencies: date-fns, tldraw.
  • Style

    • Minor button styling adjustments within the editor.

@supabase
Copy link

supabase bot commented Sep 1, 2025

This pull request has been ignored for the connected project zytfjzqyijgagqxrzbmz because there are no changes detected in packages/database/supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@trangdoan982 trangdoan982 marked this pull request as ready for review September 25, 2025 16:23
@trangdoan982
Copy link
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

📝 Walkthrough

Walkthrough

Adds a full Tldraw-based canvas feature to the Obsidian app: custom shapes (nodes/relations), tools, overlays, relation bindings, asset store, persistence and a new preview view. Introduces search and creation flows, frontmatter updates, settings and constants, utility modules, UI components, documentation, and minor updates across related areas.

Changes

Cohort / File(s) Summary
Dependencies & config
apps/obsidian/package.json, apps/obsidian/tsconfig.json, apps/obsidian/styles.css
Added dependencies (date-fns, tldraw). Updated TS libs and includes to support TSX. Added CSS override for tldraw editor buttons.
Core constants & types
apps/obsidian/src/constants.ts, apps/obsidian/src/types.ts
Added canvas-related constants, view types, version, assets, save delay, and frontmatter delimiters. Extended settings with canvas folder paths. Added color to relation types and default relation colors.
General utilities
apps/obsidian/src/utils/utils.ts, apps/obsidian/src/utils/file.ts, apps/obsidian/src/utils/createNode.ts, apps/obsidian/src/utils/registerCommands.ts
Added getNodeTypeById/getRelationTypeById. Introduced folder creation and unique filepath helpers. Minor import cleanup. Registered commands for switching views and creating a canvas.
Query service
apps/obsidian/src/services/QueryEngine.ts
Typed Datacore integration, added searchDiscourseNodesByTitle, refactored searchCompatibleNodeByTitle to object params with stronger guards and filtering.
Obsidian plugin wiring
apps/obsidian/src/index.ts
Registered new Tldraw preview view, added dynamic action to switch to canvas when frontmatter key present, and auto-switch on file open. Managed action lifecycle.
Settings & non-canvas UI
apps/obsidian/src/components/GeneralSettings.tsx, .../RelationshipTypeSettings.tsx, .../RelationshipSettings.tsx, .../RelationshipSection.tsx, .../DiscourseContextView.tsx, .../BulkIdentifyDiscourseNodesModal.tsx, .../SearchBar.tsx
Added canvas folder settings. Added relation type color field and input/validation. Replaced local node-type lookups with getNodeTypeById. Updated relation search call signature and item rendering. Enhanced SearchBar API and behavior (className prop, GenericSuggest wiring).
Canvas view & host
apps/obsidian/src/components/canvas/TldrawView.tsx, .../TldrawViewComponent.tsx, .../ToastListener.tsx
Introduced a TextFileView-backed Tldraw preview and a React component managing store, autosave, and custom shapes/tools. Added global toast event bridge.
Canvas tools & panels
apps/obsidian/src/components/canvas/DiscourseNodeTool.ts, .../DiscourseRelationTool.ts, .../DiscourseToolPanel.tsx, .../ExistingNodeSearch.tsx
Added custom tools (node/relation) with context setters. Added tool panel for node/relation creation and an existing-node search that creates shapes and links.
Canvas overlays
apps/obsidian/src/components/canvas/overlays/RelationOverlay.tsx, .../overlays/RelationPanel.tsx
Added overlay to open relations panel for a selected node. Added panel to view/create/delete relations between nodes/files.
Canvas shapes & bindings
apps/obsidian/src/components/canvas/shapes/DiscourseNodeShape.tsx, .../shapes/DiscourseRelationShape.tsx, .../shapes/DiscourseRelationBinding.tsx, .../shapes/discourseNodeShapeUtils.ts
Implemented discourse-node shape and relation arrow shape with rendering, handles, validation, reification. Added binding utilities and frontmatter helpers for node metadata.
Canvas persistence & utils
apps/obsidian/src/components/canvas/stores/assetStore.ts, .../utils/tldraw.ts, .../utils/nodeCreationFlow.ts, .../utils/relationUtils.tsx, .../utils/frontmatterUtils.ts, .../utils/toastUtils.ts
Added Obsidian TL asset store using wikilink block refs. Added TL data processing, markdown integration, canvas creation, autosave templating. Added node creation flow, relation math/rendering/bindings, frontmatter relation updates, and toast helper.
Website docs
apps/website/app/(docs)/docs/obsidian/navigation.ts, apps/website/app/(docs)/docs/obsidian/pages/canvas.md
Added Canvas docs page and navigation entry.
Roam app small updates
apps/roam/src/components/DiscourseNodeSearchMenu.tsx, .../canvas/DiscourseToolPanel.tsx, .../components/settings/Settings.tsx
Prefixed async calls with void. Centralized icon URLs in constants. Minor className tweak for settings tab.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant TP as DiscourseToolPanel
  participant DT as DiscourseNodeTool
  participant NE as openCreateDiscourseNodeAt
  participant AS as AssetStore
  participant ED as Tldraw Editor
  participant FS as File System

  U->>TP: Select Node Type / drag on canvas
  TP->>DT: setDiscourseNodeToolContext(plugin, file, nodeTypeId)
  U->>ED: Pointer down (discourse-node tool)
  ED->>DT: onPointerDown
  DT->>NE: openCreateDiscourseNodeAt({ plugin, canvasFile, editor, position, initialNodeType })
  NE->>FS: createDiscourseNode()
  NE->>AS: addWikilinkBlockrefForFile(canvasFile, createdFile)
  AS-->>NE: src (blockref)
  NE->>ED: create shape(type: discourse-node, { src, title, nodeTypeId })
  ED-->>U: Node appears and is selected
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant TP as DiscourseToolPanel
  participant RT as DiscourseRelationTool
  participant ED as Tldraw Editor
  participant RB as RelationBindingUtil
  participant FM as Frontmatter Utils

  U->>TP: Select Relation Type
  TP->>RT: setDiscourseRelationToolContext(plugin, relationTypeId, file)
  U->>ED: Drag from node A to node B
  ED->>RT: create arrow(shape: discourse-relation)
  RT->>RB: bind start/end to nodes (validate compatibility)
  ED-->>U: Arrow with label shows between nodes
  U->>ED: Confirm/complete relation
  ED->>RB: finalize bindings
  ED->>FM: addRelationToFrontmatter(sourceFile, targetFile, relationTypeId)
  FM-->>ED: Frontmatter updated
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Possibly related PRs

Pre-merge checks

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title “Tldraw obsidian” is too terse and does not clearly convey the primary change, which is the integration of a Tldraw-based canvas feature into the Obsidian plugin; it reads more like two keywords than a descriptive summary. Consider renaming the pull request to something like “Add Tldraw-based canvas integration to Obsidian plugin” to clearly summarize the main feature being introduced.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 27

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/obsidian/src/components/RelationshipSection.tsx (1)

197-215: Unify relationship writes via addRelationToFrontmatter; avoid duplicates and basename/extension mismatches.

This block can create duplicate entries and ambiguous/incorrect links (e.g., [[Foo]] vs [[Foo.md]]). Reuse the new frontmatter util so linktext is canonical and de-duped.

Apply this diff:

-    try {
-      const appendLinkToFrontmatter = async (file: TFile, link: string) => {
-        await plugin.app.fileManager.processFrontMatter(file, (fm) => {
-          const existingLinks = Array.isArray(fm[relationType.id])
-            ? fm[relationType.id]
-            : [];
-          fm[relationType.id] = [...existingLinks, link];
-        });
-      };
-
-      await appendLinkToFrontmatter(
-        activeFile,
-        `"[[${selectedNode.name}]]"`.replace(/^['"]+|['"]+$/g, ""),
-      );
-      await appendLinkToFrontmatter(
-        selectedNode,
-        `"[[${activeFile.name}]]"`.replace(/^['"]+|['"]+$/g, ""),
-      );
+    try {
+      await addRelationToFrontmatter({
+        app: plugin.app,
+        plugin,
+        sourceFile: activeFile,
+        targetFile: selectedNode,
+        relationTypeId: selectedRelationType,
+      });

Add missing import at top of file:

+import { addRelationToFrontmatter } from "./canvas/utils/frontmatterUtils";
🧹 Nitpick comments (44)
apps/obsidian/tsconfig.json (1)

14-16: Use appropriate lib targets (include DOM.Iterable) or rely on base config.

"ES5","ES6","ES7" is unusual; React + tldraw typically need DOM.Iterable and a modern ES lib. Consider deferring to the base config or set a single modern target.

Apply:

-    "lib": ["DOM", "ES5", "ES6", "ES7"]
+    "lib": ["DOM", "DOM.Iterable", "ES2020"]

If the base config already sets libs correctly, remove this override instead.

apps/roam/src/components/DiscourseNodeSearchMenu.tsx (1)

197-214: Add error handling to silenced Promises.

Using void suppresses unhandled promise warnings but also swallows errors. Add .catch for observability.

Apply:

-      void waitForBlock(blockUid, textarea.value).then(() => {
+      void waitForBlock(blockUid, textarea.value).then(() => {
         onClose();
@@
-          void updateBlock({ uid: blockUid, text: newText }).then(() => {
+          void updateBlock({ uid: blockUid, text: newText })
+            .then(() => {
               const newCursorPosition = triggerPosition + pageRef.length;
@@
-            if (window.roamAlphaAPI.ui.setBlockFocusAndSelection) {
-              void window.roamAlphaAPI.ui.setBlockFocusAndSelection({
+            if (window.roamAlphaAPI.ui.setBlockFocusAndSelection) {
+              void window.roamAlphaAPI.ui.setBlockFocusAndSelection({
                 location: {
                   "block-uid": blockUid,
                   "window-id": windowId,
                 },
                 selection: { start: newCursorPosition },
-              });
+              }).catch(console.error);
             } else {
               setTimeout(() => {
                 const textareaElements = document.querySelectorAll("textarea");
                 for (const el of textareaElements) {
                   if (
                     getUids(el as HTMLTextAreaElement).blockUid === blockUid
                   ) {
                     (el as HTMLTextAreaElement).focus();
                     (el as HTMLTextAreaElement).setSelectionRange(
                       newCursorPosition,
                       newCursorPosition,
                     );
                     break;
                   }
                 }
               }, 50);
             }
-          });
+            })
+            .catch(console.error);
           posthog.capture("Discourse Node: Selected from Search Menu", {
             id: item.id,
             text: item.text,
           });
         }, 10);
-      });
+      }).catch(console.error);
apps/obsidian/src/utils/file.ts (2)

3-12: Make folder creation resilience-safe under races.

Between check and create, another process can create the folder. Swallow EEXIST by rechecking after failure.

Apply:

 export const checkAndCreateFolder = async (folderpath: string, vault: Vault) => {
   if (!folderpath) return;
 
   const abstractItem = vault.getAbstractFileByPath(folderpath);
   if (abstractItem instanceof TFolder) return;
   if (abstractItem instanceof TAbstractFile) {
     throw new Error(`${folderpath} exists as a file`);
   }
-  await vault.createFolder(folderpath);
+  try {
+    await vault.createFolder(folderpath);
+  } catch (e) {
+    // If it now exists as a folder, ignore race; otherwise rethrow.
+    const now = vault.getAbstractFileByPath(folderpath);
+    if (!(now instanceof TFolder)) throw e;
+  }
 };

23-24: Handle empty folderpath when constructing fname.

Avoid leading slashes at repo root by omitting the folder segment when empty.

Apply:

-  let fname = normalizePath(`${folderpath}/${filename}`);
+  let fname = normalizePath(folderpath ? `${folderpath}/${filename}` : filename);
apps/obsidian/src/components/canvas/DiscourseRelationTool.ts (2)

167-172: Unreachable branch after earlier validation.

After returning when !target || target.type !== "discourse-node", target is always truthy here; the if (!target) block never runs.

Apply:

-    if (!target) {
-      this.createArrowShape();
-    } else {
-      this.editor.setHintingShapes([target.id]);
-    }
+    this.editor.setHintingShapes([target.id]);

19-27: Global mutable tool context can clash across canvases.

Consider scoping context per editor/canvas instance to avoid cross‑file interference.

apps/obsidian/package.json (1)

33-38: Pin tldraw to exact version
Replace in package.json:

- "tldraw": "^3.14.2"
+ "tldraw": "3.14.2"

React 19 support was added in v3.8.0 with no known incompatibilities in 3.14.x; you may still see peer-dependency warnings from ecosystem libs (e.g. Radix). Optionally pin date-fns to "4.1.0" for reproducibility.

apps/obsidian/src/components/canvas/utils/toastUtils.ts (1)

13-20: Avoid potential toast ID collisions.

tldraw toasts dedupe by id; two same-severity toasts fired in the same millisecond will share the current id and the later one wins. Please salt the id with a random suffix (or use crypto.randomUUID) to keep rapid-fire toasts distinct.

-    id: `${severity}-${Date.now()}`,
+    id: `${severity}-${Date.now()}-${Math.random().toString(36).slice(2)}`,
apps/obsidian/src/components/canvas/utils/frontmatterUtils.ts (2)

32-42: Type the frontmatter arg correctly for processFrontMatter.

FrontMatterCache is a read-cache type; processFrontMatter receives a mutable record. The diff above switches to Record<string, unknown>. Also remove the now-unused FrontMatterCache import.

Additional change outside this hunk:

-import type { App, FrontMatterCache, TFile } from "obsidian";
+import type { App, TFile } from "obsidian";

25-28: Prefer user-facing feedback over silent console error.

Returning early on missing relation type is fine, but consider a Notice/toast in UI contexts.

apps/obsidian/src/components/canvas/utils/nodeCreationFlow.ts (1)

43-56: Center the created shape at the target position.

Current x/y place the shape’s top-left at the position. Centering improves UX and matches typical TLDraw behavior.

Apply this diff:

-        const shapeId = createShapeId();
+        const shapeId = createShapeId();
+        const width = 200;
+        const height = 100;
         tldrawEditor.createShape({
           id: shapeId,
           type: "discourse-node",
-          x: position.x,
-          y: position.y,
+          x: position.x - width / 2,
+          y: position.y - height / 2,
           props: {
-            w: 200,
-            h: 100,
+            w: width,
+            h: height,
             src: src ?? "",
             title: createdFile.basename,
             nodeTypeId: selectedNodeType.id,
           },
         });
apps/obsidian/src/components/RelationshipSection.tsx (3)

96-99: Fallback when complement is missing.

relationType.complement may be empty. Prefer complement ?? label.

Apply this diff:

-          label: isSource ? relationType.label : relationType.complement,
+          label: isSource ? relationType.label : (relationType.complement ?? relationType.label),

192-233: Consider notifying the user on missing relation type.

Short-circuiting is fine; a Notice would improve UX.


1-12: Standardize linktext across frontmatter assignments

  • apps/obsidian/src/utils/templates.ts (line 138)
  • apps/obsidian/src/utils/createNode.ts (lines 64, 167)
  • apps/obsidian/src/components/RelationshipSection.tsx (line 199)
  • apps/obsidian/src/components/canvas/utils/frontmatterUtils.ts (line 32)
  • apps/obsidian/src/components/BulkIdentifyDiscourseNodesModal.tsx (line 128)

Replace these direct fm[relationType.id] writes with the canonical linktext util and run a one-time migration to consolidate legacy '[[name]]' entries into the new '[[linktext]]' format.

apps/obsidian/src/components/SearchBar.tsx (2)

63-68: Clear pending debounce on close to avoid stray timers.

Override close() to clear debounce timeout.

Apply this diff:

   selectSuggestion(item: T, evt: MouseEvent | KeyboardEvent): void {
     this.textInputEl.value = this.getItemTextFn(item);
     this.onSelectCallback(item);
     this.close();
   }
+
+  override close(): void {
+    if (this.debounceTimeout) {
+      clearTimeout(this.debounceTimeout);
+      this.debounceTimeout = null;
+    }
+    super.close();
+  }
 }

97-121: Minor: effect can avoid re-instantiating GenericSuggest on asyncSearch changes.

You already keep asyncSearchRef current. Removing asyncSearch from deps avoids churn.

apps/obsidian/src/components/canvas/DiscourseToolPanel.tsx (4)

132-142: Normalize data- attribute naming for dataset access.*

Use kebab-case data attribute and camelCase dataset access for clarity and cross-browser consistency.

-      const nodeTypeId = target.dataset.dg_node_type_id!;
+      const nodeTypeId = target.dataset.dgNodeTypeId!;
-      data-dg_node_type_id={nodeType.id}
+      data-dg-node-type-id={nodeType.id}

Also applies to: 319-325


198-203: Parity check for focusedRelationTypeId.

Mirror the node-type existence guard for relation types to avoid stale focus if a relation type is removed.

   useEffect(() => {
     if (!focusedNodeTypeId) return;
     const exists = !!getNodeTypeById(plugin, focusedNodeTypeId);
     if (!exists) setFocusedNodeTypeId(undefined);
   }, [focusedNodeTypeId, plugin]);
+
+  useEffect(() => {
+    if (!focusedRelationTypeId) return;
+    const exists = !!relationTypes.find((r) => r.id === focusedRelationTypeId);
+    if (!exists) setFocusedRelationTypeId(undefined);
+  }, [focusedRelationTypeId, relationTypes]);

50-146: Clean up listeners on unmount.

If the component unmounts mid-drag, the document keydown listener can leak. Return a cleanup that calls your removeEventListeners (expose it via handlers or a ref).


327-333: Optional: add WebKit mask for Safari.

For the masked color icon, add WebkitMask for Safari compatibility.

         style={{
-          mask: `url("https://cdn.tldraw.com/2.3.0/icons/icon/color.svg") center 100% / 100% no-repeat`,
+          mask: `url("https://cdn.tldraw.com/2.3.0/icons/icon/color.svg") center 100% / 100% no-repeat`,
+          WebkitMask: `url("https://cdn.tldraw.com/2.3.0/icons/icon/color.svg") center 100% / 100% no-repeat`,
           backgroundColor: nodeType.color || "black",
         }}
apps/obsidian/src/components/canvas/shapes/discourseNodeShapeUtils.ts (1)

20-25: Type relations once the schema is known.

Replace unknown[] with a typed structure when you define the schema to avoid unsafe casts downstream.

apps/obsidian/src/components/canvas/TldrawView.tsx (4)

1-5: Import constants for Markdown view and TL data delimiters.

Preps for the changes below.

-import { VIEW_TYPE_TLDRAW_DG_PREVIEW } from "~/constants";
+import {
+  VIEW_TYPE_TLDRAW_DG_PREVIEW,
+  VIEW_TYPE_MARKDOWN,
+  TLDATA_DELIMITER_START,
+  TLDATA_DELIMITER_END,
+} from "~/constants";

52-57: Use the exported Markdown view constant.

Removes a magic string.

-      this.leaf.setViewState({
-        type: "markdown",
-        state: this.leaf.view.getState(),
-      }),
+      this.leaf.setViewState({
+        type: VIEW_TYPE_MARKDOWN,
+        state: this.leaf.view.getState(),
+      }),

70-71: Avoid double-reading the file.

Use this.data populated by super.onLoadFile(file).

-    const fileData = await this.app.vault.read(file);
+    const fileData = this.data || (await this.app.vault.read(file));

145-148: Remove unreachable check.

The second assetStore check can’t run after the earlier throws.

-    if (!this.assetStore) {
-      console.warn("Asset store is not set");
-      return;
-    }
apps/obsidian/src/components/canvas/shapes/DiscourseNodeShape.tsx (1)

11-16: Also import getNodeTypeIdFromFrontmatter.

Needed to derive nodeTypeId from frontmatter.

 import {
   getFrontmatterForFile,
   FrontmatterRecord,
+  getNodeTypeIdFromFrontmatter,
 } from "./discourseNodeShapeUtils";
apps/obsidian/src/components/canvas/TldrawViewComponent.tsx (2)

174-195: Unsubscribe editor event listener to prevent leaks on unmount.

Store the unsubscribe and clean it up when the component unmounts.

Apply these changes:

   const handleMount = (editor: Editor) => {
     editorRef.current = editor;
-
-    editor.on("event", (event) => {
+    const off = editor.on("event", (event) => {
       const e = event as TLPointerEventInfo;
       if (e.type === "pointer" && e.name === "pointer_down") {
-        const currentTool = editor.getCurrentTool();
-        const currentToolId = currentTool.id;
+        const currentToolId =
+          // prefer the API that returns tool id; fallback if not available
+          // @ts-expect-error narrow at runtime
+          typeof (editor as any).getCurrentToolId === "function"
+            ? (editor as any).getCurrentToolId()
+            : (editor as any).getCurrentTool?.()?.id;
 
         if (currentToolId === "discourse-relation") {
           isCreatingRelationRef.current = true;
         }
       }
 
       if (e.type === "pointer" && e.name === "pointer_up") {
         if (isCreatingRelationRef.current) {
           BaseRelationBindingUtil.checkAndReifyRelation(editor);
           isCreatingRelationRef.current = false;
         }
       }
     });
+    // Ensure we clean up when Tldraw unmounts this editor instance
+    editor.on("dispose", () => {
+      off?.();
+    });
   };

If Editor does not emit "dispose", capture off in a ref and clean it up via a React effect on unmount.


180-183: Use editor.getCurrentToolId() directly
Replace the two‐step call to getCurrentTool() with a single call:

- const currentTool = editor.getCurrentTool();
- const currentToolId = currentTool.id;
+ const currentToolId = editor.getCurrentToolId();

at apps/obsidian/src/components/canvas/TldrawViewComponent.tsx:180-183.

apps/obsidian/src/services/QueryEngine.ts (2)

61-66: Normalize DC API call to support both sync and async implementations.

Some Datacore builds return arrays, others may return Promises. Normalize with Promise.resolve.

-      const potentialNodes = await this.dc.query(dcQuery);
+      const potentialNodes = await Promise.resolve(this.dc.query(dcQuery));

110-112: Same normalization for DC query here.

-      const potentialNodes = this.dc.query(dcQuery);
+      const potentialNodes = await Promise.resolve(this.dc.query(dcQuery));
apps/obsidian/src/components/canvas/utils/tldraw.ts (1)

195-205: Escape delimiters when building the regex.

Even though these are constants, escaping prevents surprises and addresses ReDoS lint warning.

 export const getUpdatedMdContent = (
   currentContent: string,
   stringifiedData: string,
 ) => {
-  const regex = new RegExp(
-    `${TLDATA_DELIMITER_START}([\\s\\S]*?)${TLDATA_DELIMITER_END}`,
-  );
+  const escape = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+  const regex = new RegExp(
+    `${escape(TLDATA_DELIMITER_START)}([\\s\\S]*?)${escape(TLDATA_DELIMITER_END)}`
+  );
   return currentContent.replace(
     regex,
     `${TLDATA_DELIMITER_START}\n${stringifiedData}\n${TLDATA_DELIMITER_END}`,
   );
 };
apps/obsidian/src/components/canvas/stores/assetStore.ts (1)

41-53: Frontmatter insertion can duplicate leading newlines.

Current splice always inserts \n${content}\n even when no frontmatter exists (offsets 0). Consider normalizing to avoid unnecessary blank lines.

apps/obsidian/src/components/canvas/shapes/DiscourseRelationShape.tsx (1)

65-77: Avoid duplicate handle enums.

You import ARROW_HANDLES and also define ArrowHandles. Prefer a single source to reduce drift.

apps/obsidian/src/components/canvas/utils/relationUtils.tsx (2)

1385-1390: Reject the promise when 2D context is unavailable.

Currently it neither resolves nor rejects, leaving a hanging promise.

Apply this diff:

-    const ctx = canvasEl.getContext("2d");
-    if (!ctx) return;
+    const ctx = canvasEl.getContext("2d");
+    if (!ctx) {
+      reject(new Error("2D canvas context not available"));
+      return;
+    }

854-862: Remove duplicated guard.

The second identical check is dead code and adds noise.

Apply this diff:

-  if (!intersectionPoints || intersectionPoints.length !== 2) {
-    return { start: 0.5, end: 0.5 };
-  }
-
-  // there should be two intersection points - one near the start, and one near the end
-  // after the existing guard:
-  if (!intersectionPoints || intersectionPoints.length !== 2)
-    return { start: 0.5, end: 0.5 };
+  if (!intersectionPoints || intersectionPoints.length !== 2) {
+    return { start: 0.5, end: 0.5 };
+  }
apps/obsidian/src/utils/registerCommands.ts (2)

83-99: Avoid passing stale view state across view types

Using leaf.view.getState() when switching to markdown may carry incompatible state. Prefer setting only the required state (file) and mode.

Apply this diff:

-      if (!checking) {
-        leaf.setViewState({
-          type: VIEW_TYPE_MARKDOWN,
-          state: leaf.view.getState(),
-        });
-      }
+      if (!checking) {
+        const file = (leaf as any)?.view?.file?.path ?? plugin.app.workspace.getActiveFile()?.path;
+        leaf.setViewState({
+          type: VIEW_TYPE_MARKDOWN,
+          state: file ? { file } : {},
+        });
+      }

100-115: Ensure correct state shape when switching to Canvas view

Same concern as above; avoid reusing the prior view's state blob.

Apply this diff:

-      if (!checking) {
-        leaf.setViewState({
-          type: VIEW_TYPE_TLDRAW_DG_PREVIEW,
-          state: leaf.view.getState(),
-        });
-      }
+      if (!checking) {
+        const file = (leaf as any)?.view?.file?.path ?? plugin.app.workspace.getActiveFile()?.path;
+        leaf.setViewState({
+          type: VIEW_TYPE_TLDRAW_DG_PREVIEW,
+          state: file ? { file } : {},
+        });
+      }
apps/obsidian/src/components/canvas/ToastListener.tsx (1)

4-8: Namespace the custom event to avoid collisions

Use a namespaced event name to reduce risk of clashing with other listeners.

-  document.dispatchEvent(
-    new CustomEvent<TLUiToast>("show-toast", { detail: toast }),
-  );
+  document.dispatchEvent(
+    new CustomEvent<TLUiToast>("dg:show-toast", { detail: toast }),
+  );

Remember to update the listener below accordingly.

apps/obsidian/src/utils/utils.ts (1)

11-18: Defensive access and potential caching

Mirror the defensive pattern above. If this runs on hot paths, consider Map caching keyed by id to avoid repeated linear scans.

-  return plugin.settings.relationTypes.find(
-    (relation) => relation.id === relationTypeId,
-  );
+  return (plugin.settings.relationTypes ?? []).find(
+    (relation) => relation.id === relationTypeId,
+  );
apps/obsidian/src/components/canvas/overlays/RelationOverlay.tsx (2)

66-79: Improve a11y and stacking of the floating button

Add aria-label and ensure it stacks above TLDraw UI.

-        <button
+        <button
+          aria-label="Open relations panel"
           onClick={handleOpen}
           style={{
             left: `${buttonPosition.left}px`,
             top: `${buttonPosition.top}px`,
             transform: "translate(-50%, -100%)",
             pointerEvents: "all",
+            zIndex: 100,
           }}
-          className="absolute z-10 rounded px-3 py-1 text-xs text-white"
+          className="absolute rounded px-3 py-1 text-xs text-white"
         >

81-103: Panel position UX

Panel is fixed at top-left (12,12). Consider anchoring near the selected node (like the button) for spatial continuity, or provide a setting.

apps/obsidian/src/components/canvas/DiscourseNodeTool.ts (1)

28-51: Await creation flow and clear context on exit/cancel paths

If openCreateDiscourseNodeAt is async, not awaiting may cause race conditions when switching tools immediately after. Also ensure context is cleared if creation throws.

-  override onPointerDown = (_info?: TLPointerEventInfo) => {
+  override onPointerDown = async (_info?: TLPointerEventInfo) => {
@@
-    openCreateDiscourseNodeAt({
+    try {
+      await openCreateDiscourseNodeAt({
         plugin,
         canvasFile,
         tldrawEditor: this.editor,
         position: currentPagePoint,
         initialNodeType,
-    });
-
-    toolContext = null;
-    this.editor.setCurrentTool("select");
+      });
+    } finally {
+      toolContext = null;
+      this.editor.setCurrentTool("select");
+    }

Also add:

override onExit = () => { toolContext = null; };
apps/obsidian/src/components/RelationshipTypeSettings.tsx (2)

15-29: Initialize missing color field for existing settings

Existing relationTypes may lack color, leading to undefined value in the color input. Initialize defaults on first access.

You can normalize when mutating a missing index (as done), and also normalize initial state:

// when initializing state (outside selected lines)
// const [relationTypes, setRelationTypes] = useState(...).map(rt => ({ color: "#000000", ...rt }));

135-143: Prevent uncontrolled color input when color is missing

Fallback to a default color to avoid React warnings and ensure a valid value.

-              <input
+              <input
                 type="color"
-                value={relationType.color}
+                value={relationType.color ?? "#000000"}
                 onChange={(e) =>
                   handleRelationTypeChange(index, "color", e.target.value)
                 }
                 className="w-12 h-8 rounded border"
                 title="Relation color"
               />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 94b1da7 and 011ed76.

⛔ Files ignored due to path filters (13)
  • apps/website/public/apps/assets/node-color-icon.svg is excluded by !**/*.svg
  • apps/website/public/apps/assets/tool-arrow-icon.svg is excluded by !**/*.svg
  • apps/website/public/docs/obsidian/adding-existing-relations.gif is excluded by !**/*.gif
  • apps/website/public/docs/obsidian/canvas-icon-button.png is excluded by !**/*.png
  • apps/website/public/docs/obsidian/create-canvas-command.png is excluded by !**/*.png
  • apps/website/public/docs/obsidian/create-discourse-node.gif is excluded by !**/*.gif
  • apps/website/public/docs/obsidian/create-relations.gif is excluded by !**/*.gif
  • apps/website/public/docs/obsidian/export-options.png is excluded by !**/*.png
  • apps/website/public/docs/obsidian/node-search.gif is excluded by !**/*.gif
  • apps/website/public/docs/obsidian/open-canvas-command.png is excluded by !**/*.png
  • apps/website/public/docs/obsidian/relation-error.png is excluded by !**/*.png
  • apps/website/public/docs/obsidian/search-filtering.gif is excluded by !**/*.gif
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (42)
  • apps/obsidian/package.json (1 hunks)
  • apps/obsidian/src/components/BulkIdentifyDiscourseNodesModal.tsx (2 hunks)
  • apps/obsidian/src/components/DiscourseContextView.tsx (2 hunks)
  • apps/obsidian/src/components/GeneralSettings.tsx (3 hunks)
  • apps/obsidian/src/components/RelationshipSection.tsx (3 hunks)
  • apps/obsidian/src/components/RelationshipSettings.tsx (4 hunks)
  • apps/obsidian/src/components/RelationshipTypeSettings.tsx (5 hunks)
  • apps/obsidian/src/components/SearchBar.tsx (2 hunks)
  • apps/obsidian/src/components/canvas/DiscourseNodeTool.ts (1 hunks)
  • apps/obsidian/src/components/canvas/DiscourseRelationTool.ts (1 hunks)
  • apps/obsidian/src/components/canvas/DiscourseToolPanel.tsx (1 hunks)
  • apps/obsidian/src/components/canvas/ExistingNodeSearch.tsx (1 hunks)
  • apps/obsidian/src/components/canvas/TldrawView.tsx (1 hunks)
  • apps/obsidian/src/components/canvas/TldrawViewComponent.tsx (1 hunks)
  • apps/obsidian/src/components/canvas/ToastListener.tsx (1 hunks)
  • apps/obsidian/src/components/canvas/overlays/RelationOverlay.tsx (1 hunks)
  • apps/obsidian/src/components/canvas/overlays/RelationPanel.tsx (1 hunks)
  • apps/obsidian/src/components/canvas/shapes/DiscourseNodeShape.tsx (1 hunks)
  • apps/obsidian/src/components/canvas/shapes/DiscourseRelationBinding.tsx (1 hunks)
  • apps/obsidian/src/components/canvas/shapes/DiscourseRelationShape.tsx (1 hunks)
  • apps/obsidian/src/components/canvas/shapes/discourseNodeShapeUtils.ts (1 hunks)
  • apps/obsidian/src/components/canvas/stores/assetStore.ts (1 hunks)
  • apps/obsidian/src/components/canvas/utils/frontmatterUtils.ts (1 hunks)
  • apps/obsidian/src/components/canvas/utils/nodeCreationFlow.ts (1 hunks)
  • apps/obsidian/src/components/canvas/utils/relationUtils.tsx (1 hunks)
  • apps/obsidian/src/components/canvas/utils/tldraw.ts (1 hunks)
  • apps/obsidian/src/components/canvas/utils/toastUtils.ts (1 hunks)
  • apps/obsidian/src/constants.ts (2 hunks)
  • apps/obsidian/src/index.ts (3 hunks)
  • apps/obsidian/src/services/QueryEngine.ts (4 hunks)
  • apps/obsidian/src/types.ts (2 hunks)
  • apps/obsidian/src/utils/createNode.ts (2 hunks)
  • apps/obsidian/src/utils/file.ts (1 hunks)
  • apps/obsidian/src/utils/registerCommands.ts (2 hunks)
  • apps/obsidian/src/utils/utils.ts (1 hunks)
  • apps/obsidian/styles.css (1 hunks)
  • apps/obsidian/tsconfig.json (1 hunks)
  • apps/roam/src/components/DiscourseNodeSearchMenu.tsx (2 hunks)
  • apps/roam/src/components/canvas/DiscourseToolPanel.tsx (4 hunks)
  • apps/roam/src/components/settings/Settings.tsx (1 hunks)
  • apps/website/app/(docs)/docs/obsidian/navigation.ts (1 hunks)
  • apps/website/app/(docs)/docs/obsidian/pages/canvas.md (1 hunks)
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-06-17T23:37:45.289Z
Learnt from: maparent
PR: DiscourseGraphs/discourse-graph#220
File: apps/roam/src/utils/conceptConversion.ts:42-56
Timestamp: 2025-06-17T23:37:45.289Z
Learning: In the DiscourseNode interface from apps/roam/src/utils/getDiscourseNodes.ts, the field `type` serves as the unique identifier field, not a type classification field. The interface has no `uid` or `id` field, making `node.type` the correct field to use for UID-related operations.

Applied to files:

  • apps/obsidian/src/utils/utils.ts
  • apps/obsidian/src/components/DiscourseContextView.tsx
  • apps/obsidian/src/components/RelationshipSection.tsx
📚 Learning: 2025-06-22T10:40:52.752Z
Learnt from: sid597
PR: DiscourseGraphs/discourse-graph#232
File: apps/roam/src/utils/getAllDiscourseNodesSince.ts:18-31
Timestamp: 2025-06-22T10:40:52.752Z
Learning: In apps/roam/src/utils/getAllDiscourseNodesSince.ts, the user confirmed that querying for `?title` with `:node/title` and mapping it to the `text` field in the DiscourseGraphContent type is the correct implementation for retrieving discourse node content from Roam Research, despite it appearing to query page titles rather than block text content.

Applied to files:

  • apps/roam/src/components/DiscourseNodeSearchMenu.tsx
  • apps/obsidian/src/services/QueryEngine.ts
📚 Learning: 2025-08-25T15:53:21.799Z
Learnt from: sid597
PR: DiscourseGraphs/discourse-graph#372
File: apps/roam/src/components/DiscourseNodeMenu.tsx:116-116
Timestamp: 2025-08-25T15:53:21.799Z
Learning: In apps/roam/src/components/DiscourseNodeMenu.tsx, when handling tag insertion, multiple leading hashtags (like ##foo) should be preserved as they represent user intent, not normalized to a single hashtag. The current regex /^#/ is correct as it only removes one leading # before adding one back, maintaining any additional hashtags the user intended.

Applied to files:

  • apps/roam/src/components/DiscourseNodeSearchMenu.tsx
📚 Learning: 2025-06-17T23:37:45.289Z
Learnt from: maparent
PR: DiscourseGraphs/discourse-graph#220
File: apps/roam/src/utils/conceptConversion.ts:42-56
Timestamp: 2025-06-17T23:37:45.289Z
Learning: In the DiscourseNode interface from apps/roam/src/utils/getDiscourseNodes.ts, the field `node.type` serves as the UID field rather than having a conventional `node.uid` field. This is an unusual naming convention where the type field actually contains the unique identifier.

Applied to files:

  • apps/obsidian/src/components/DiscourseContextView.tsx
📚 Learning: 2025-07-19T22:34:16.794Z
Learnt from: CR
PR: DiscourseGraphs/discourse-graph#0
File: .cursor/rules/obsidian.mdc:0-0
Timestamp: 2025-07-19T22:34:16.794Z
Learning: Applies to apps/obsidian/package.json : Prefer existing dependencies from package.json when adding or using dependencies in the Obsidian plugin

Applied to files:

  • apps/obsidian/package.json
📚 Learning: 2025-07-19T22:34:08.725Z
Learnt from: CR
PR: DiscourseGraphs/discourse-graph#0
File: .cursor/rules/main.mdc:0-0
Timestamp: 2025-07-19T22:34:08.725Z
Learning: Applies to apps/website/**/*.{tsx,jsx,ts,js} : Use Tailwind CSS for styling where possible

Applied to files:

  • apps/obsidian/package.json
📚 Learning: 2025-08-27T02:23:45.696Z
Learnt from: maparent
PR: DiscourseGraphs/discourse-graph#377
File: packages/ui/tsconfig.json:5-7
Timestamp: 2025-08-27T02:23:45.696Z
Learning: When reviewing TypeScript configuration changes, check if settings like "declaration" are inherited from base configs before suggesting to add them explicitly. Many monorepo packages extend base configurations that already include necessary compiler options.

Applied to files:

  • apps/obsidian/tsconfig.json
📚 Learning: 2025-07-19T22:34:08.725Z
Learnt from: CR
PR: DiscourseGraphs/discourse-graph#0
File: .cursor/rules/main.mdc:0-0
Timestamp: 2025-07-19T22:34:08.725Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use Prettier with the project's configuration

Applied to files:

  • apps/obsidian/tsconfig.json
🪛 Biome (2.1.2)
apps/obsidian/src/components/canvas/utils/relationUtils.tsx

[error] 1788-1788: This code will never be reached ...

... because this statement will throw an exception beforehand

(lint/correctness/noUnreachable)


[error] 558-558: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🪛 ast-grep (0.39.5)
apps/obsidian/src/components/canvas/utils/tldraw.ts

[warning] 198-200: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
${TLDATA_DELIMITER_START}([\\s\\S]*?)${TLDATA_DELIMITER_END},
)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🔇 Additional comments (30)
apps/obsidian/src/utils/createNode.ts (1)

1-1: Clean import update

Nice catch removing the unused App import; keeps the module lean.

apps/roam/src/components/settings/Settings.tsx (1)

181-182: Verify CSS class existence for new Tab styling.

Ensure rm-settings__tab is defined and loaded in this context; otherwise, the Tab may lose expected styling.

apps/website/app/(docs)/docs/obsidian/navigation.ts (1)

58-61: Canvas docs link fits the Core Features cluster.

Thanks for surfacing the new Canvas page in the docs nav—it keeps the Core Features section aligned with the product surface.

apps/obsidian/src/components/BulkIdentifyDiscourseNodesModal.tsx (1)

220-245: Appreciate the centralized node type lookup.

Routing everything through getNodeTypeById keeps the modal consistent with the rest of the refactor.

apps/obsidian/src/components/DiscourseContextView.tsx (1)

43-74: Consistent node-type resolution is a nice improvement.

This keeps the context view in step with the shared lookup helper and avoids hand-rolled searches.

apps/obsidian/src/types.ts (1)

17-34: Confirm migration path for the new relation-type color field.

Existing user vaults will have relation types serialized without a color property. Please double-check that loadSettings (or an explicit migration) backfills a default color before anything expects the new field; otherwise freshly upgraded users may hit undefined colors in the canvas or settings views.

apps/obsidian/src/components/RelationshipSettings.tsx (1)

68-223: Looks good incorporating the shared helper.

The settings UI now benefits from the same lookup logic as the rest of the canvas work.

apps/roam/src/components/canvas/DiscourseToolPanel.tsx (1)

47-373: Central icon constants keep things tidy.

Nice touch extracting those masks—easier to maintain if the asset URL ever changes.

apps/obsidian/src/components/GeneralSettings.tsx (1)

72-183: General settings additions look solid.

The folder pickers and state wiring blend right in with the existing pattern.

apps/obsidian/src/components/canvas/utils/nodeCreationFlow.ts (1)

18-74: Flow looks solid.

Creation, wikilink insertion, shape add, history, and selection are wired correctly with defensive error handling.

apps/obsidian/src/components/RelationshipSection.tsx (3)

63-66: Node-type resolution via getNodeTypeById looks good.

Using a single helper makes this consistent and safer.


368-423: Parsing existing relations is consistent with the new storage format.

Regex extraction and link resolution via getFirstLinkpathDest are fine.


122-173: Good guardrails and updated QueryEngine call signature.

Error states, guards, and the new options-based searchCompatibleNodeByTitle usage look correct.

apps/obsidian/src/components/SearchBar.tsx (1)

130-151: UX tweaks LGTM.

Input styling, blur on select, and className passthrough look good.

apps/obsidian/src/components/canvas/ExistingNodeSearch.tsx (1)

23-28: Search wiring LGTM.

Engine instantiation and typed callbacks look fine.

apps/obsidian/src/constants.ts (2)

25-43: LGTM: relation colors.

Adding color to relation types improves UI clarity and is backward compatible.


70-85: LGTM: new constants and defaults.

Frontmatter key, TL data delimiters, view types, save delay, and asset URLs look coherent with the new flow.

apps/obsidian/src/components/canvas/shapes/DiscourseNodeShape.tsx (1)

49-57: LGTM: default props and schema.

Defaults and prop schema are sensible for a box-based shape util.

apps/obsidian/src/components/canvas/TldrawViewComponent.tsx (1)

152-172: Auto-save debounce looks good.

Debounced, user-scoped document changes; timeout cleared on cleanup. Good.

apps/obsidian/src/components/canvas/shapes/DiscourseRelationBinding.tsx (1)

140-171: Reification guard is solid.

The reifiedArrows set prevents duplicate writes; errors remove from the set for retry. Looks good.

apps/obsidian/src/services/QueryEngine.ts (1)

246-253: Guard against missing $path.

Nice addition; prevents getAbstractFileByPath(undefined) calls during bulk scan. LGTM.

apps/obsidian/src/components/canvas/overlays/RelationPanel.tsx (2)

315-335: Deleting the relation shape may leave frontmatter stale.

Confirm whether deletion should also update frontmatter. If so, mirror creation flow by removing the relation from frontmatter.

Would you like me to wire this up using your frontmatter utilities (e.g., a removeRelation helper) so canvas and metadata stay consistent?


483-543: Grouping logic and link resolution look good.

Correctly maps frontmatter relation arrays to files, dedupes, and respects directionality. LGTM.

apps/obsidian/src/components/canvas/utils/tldraw.ts (1)

44-87: Store initialization looks correct.

Custom shape and binding utils registered; initialData normalization handles array/object records. Good.

apps/obsidian/src/components/canvas/stores/assetStore.ts (1)

260-279: Object URL caching looks solid.

Nice use of URL.revokeObjectURL on replacement and in dispose() to avoid leaks.

apps/obsidian/src/components/canvas/utils/relationUtils.tsx (1)

1703-1716: Good: translation start state captured in a WeakMap.

Using WeakMap for ephemeral shape state avoids leaks while enabling smooth unbinding/rebinding during translate.

apps/obsidian/src/components/RelationshipTypeSettings.tsx (1)

85-91: Validation OK

Requiring color alongside id/label/complement is consistent with the new field.

apps/website/app/(docs)/docs/obsidian/pages/canvas.md (1)

105-107: Confirm frontmatter flag key
No matches found for tldr-dg or similar in the codebase—please verify the exact frontmatter key your canvas view plugin expects (e.g. tldraw-dg or another prefix) and update the docs accordingly.

apps/obsidian/src/utils/utils.ts (1)

4-9: Use id as the unique identifier; drop any type fallback
In apps/obsidian/src/types.ts, DiscourseNode only defines an id field (no type property), so node.id === nodeTypeId is correct—remove the suggested node.type fallback.

Likely an incorrect or invalid review comment.

apps/obsidian/src/utils/registerCommands.ts (1)

117-122: Wrap createCanvas callback in async try/catch and verify icon id
Replace in apps/obsidian/src/utils/registerCommands.ts:

-    callback: () => createCanvas(plugin),
+    callback: async () => {
+      try {
+        await createCanvas(plugin);
+      } catch (err) {
+        console.error("Failed to create canvas:", err);
+        // Optionally: new Notice("Failed to create canvas. See console for details.");
+      }
+    },

Confirm that “layout-dashboard” is a valid Lucide icon in Obsidian.

@@ -0,0 +1,8 @@
export const WHITE_LOGO_SVG =
'<svg width="18" height="18" viewBox="0 0 256 264" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M156.705 252.012C140.72 267.995 114.803 267.995 98.8183 252.012L11.9887 165.182C-3.99622 149.197 -3.99622 123.28 11.9886 107.296L55.4035 63.8807C63.3959 55.8881 76.3541 55.8881 84.3467 63.8807C92.3391 71.8731 92.3391 84.8313 84.3467 92.8239L69.8751 107.296C53.8901 123.28 53.8901 149.197 69.8751 165.182L113.29 208.596C121.282 216.589 134.241 216.589 142.233 208.596C150.225 200.604 150.225 187.646 142.233 179.653L127.761 165.182C111.777 149.197 111.777 123.28 127.761 107.296C143.746 91.3105 143.746 65.3939 127.761 49.4091L113.29 34.9375C105.297 26.9452 105.297 13.9868 113.29 5.99432C121.282 -1.99811 134.241 -1.99811 142.233 5.99434L243.533 107.296C259.519 123.28 259.519 149.197 243.533 165.182L156.705 252.012ZM200.119 121.767C192.127 113.775 179.168 113.775 171.176 121.767C163.184 129.76 163.184 142.718 171.176 150.71C179.168 158.703 192.127 158.703 200.119 150.71C208.112 142.718 208.112 129.76 200.119 121.767Z" fill="currentColor"/></svg>';
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to move the Roam svg to local so that it can be accessed offline as well

};

const TOOL_ARROW_ICON_DATA_URL = `data:image/svg+xml;base64,${btoa(TOOL_ARROW_ICON_SVG)}`;
const NODE_COLOR_ICON_DATA_URL = `data:image/svg+xml;base64,${btoa(NODE_COLOR_ICON_SVG)}`;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to move the Roam svg to local so that it can be accessed offline as well

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all the changes for the website will be merged earlier in PR #484, so you don't really have to review this part

const nodeType = plugin.settings.nodeTypes.find(
(n) => n.id === pattern.nodeTypeId,
);
const nodeType = getNodeTypeById(plugin, pattern.nodeTypeId);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

convenient fix

Copy link
Contributor

@mdroidian mdroidian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just quickly scanned assuming there were no significant changes from the PR's that were merged into this.

Let's do a round of lint fixes (and a few other comments), then one last review/test before merging.

trangdoan982 and others added 15 commits October 14, 2025 16:31
* cleaned

* sm

* address PR comments
* data persistence to the file

* error handling

* address PR comments

* address some PR comments

* address other PR comments

* address PR comments
* current state

* works now

* clean up

* address PR comments

* address PR reviews

* cleanup

* fix styling issues

* address PR comments
* current state

* works now

* clean up

* address PR comments

* address PR reviews

* fix styling issues

* latest progress

* update new shape

* shape defined

* address PR comments

* sm address PR review

* current progress

* reorg

* address other PR comments

* clean

* simplify flow

* address PR comments
* eng-604: create node flow

* pwd
* eng-658-add-existing-nodes-flow

* address PR comments

* small changes
https://www.loom.com/share/3641f2a642714b0d849262344e8c6ee5?sid=0614c657-e541-4bfd-92df-9b1aa60945b6

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- New Features
  - Added a Relations overlay on the canvas that shows a “Relations” button when a discourse node is selected.
  - Introduced a Relations panel to view and manage relations for the selected node, including adding or removing links, with clear loading/error states.
  - Overlay appears above the canvas without disrupting existing tools.

- Chores
  - Consolidated relation-type lookup into shared utilities and updated imports. No user-facing changes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
* add color setting

* address PR reviews

* address PR commens
* eng-812 : Update database cli tools: supabase, vercel, cucumber.

* newer cucumber constrains node
* cleaned

* sm

* address PR comments
Copy link
Contributor

@mdroidian mdroidian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added via Giphy

@mdroidian
Copy link
Contributor

Bravo, well done. 👊👏 This is a huge milestone! I'm excited for you to merge this 😁.
yuge

@trangdoan982 trangdoan982 merged commit 7e96f02 into main Oct 16, 2025
5 checks passed
@github-project-automation github-project-automation bot moved this to Done in General Oct 16, 2025
@trangdoan982 trangdoan982 deleted the tldraw-obsidian branch October 16, 2025 20:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

No open projects
Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants