Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 11 additions & 13 deletions apps/roam/src/utils/initializeObserversAndListeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
getPageTitleValueByHtmlElement,
} from "roamjs-components/dom";
import { createBlock } from "roamjs-components/writes";
import { renderLinkedReferenceAdditions } from "~/utils/renderLinkedReferenceAdditions";
import { renderDiscourseContextAndCanvasReferences } from "~/utils/renderLinkedReferenceAdditions";
import { createConfigObserver } from "roamjs-components/components/ConfigPage";
import {
renderTldrawCanvas,
Expand Down Expand Up @@ -90,6 +90,16 @@ export const initObservers = async ({
const uid = getPageUidByPageTitle(title);
if (isDiscourseNode(uid)) {
renderPossibleDuplicates(h1, title);
const linkedReferencesDiv = document.querySelector(
".rm-reference-main",
) as HTMLDivElement;
if (linkedReferencesDiv) {
renderDiscourseContextAndCanvasReferences(
linkedReferencesDiv,
uid,
onloadArgs,
);
}
Comment on lines +93 to +102
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, locate and read the file in question
find . -name "initializeObserversAndListeners.ts" -type f

Repository: DiscourseGraphs/discourse-graph

Length of output: 131


🏁 Script executed:

# Get the file size first
wc -l apps/roam/src/utils/initializeObserversAndListeners.ts

Repository: DiscourseGraphs/discourse-graph

Length of output: 133


🏁 Script executed:

# Read the entire file to understand context
cat -n apps/roam/src/utils/initializeObserversAndListeners.ts

Repository: DiscourseGraphs/discourse-graph

Length of output: 15852


🏁 Script executed:

# Also check for the linkedReferencesObserver and pageTitleObserver patterns
rg -n "linkedReferencesObserver|pageTitleObserver|createHTMLObserver" apps/roam/src/utils/initializeObserversAndListeners.ts -A 3 -B 1

Repository: DiscourseGraphs/discourse-graph

Length of output: 1012


🏁 Script executed:

# Check how createHTMLObserver is used elsewhere in the codebase
rg -l "createHTMLObserver" apps/roam/src/ --type ts --type tsx

Repository: DiscourseGraphs/discourse-graph

Length of output: 102


🏁 Script executed:

# Look at renderDiscourseContextAndCanvasReferences to understand timing requirements
rg -n "renderDiscourseContextAndCanvasReferences" apps/roam/src/ --type ts --type tsx -A 2

Repository: DiscourseGraphs/discourse-graph

Length of output: 102


🏁 Script executed:

# Check renderDiscourseContextAndCanvasReferences function
rg -n "renderDiscourseContextAndCanvasReferences" apps/roam/src/ -A 10

Repository: DiscourseGraphs/discourse-graph

Length of output: 3125


🏁 Script executed:

# Search for linkedReferences references to understand timing
rg -n "rm-reference-main|linkedReferences" apps/roam/src/ -B 2 -A 2

Repository: DiscourseGraphs/discourse-graph

Length of output: 1017


🏁 Script executed:

# Check if there are similar deferred rendering patterns in the codebase
rg -n "requestAnimationFrame|setTimeout" apps/roam/src/utils/ -B 2 -A 2

Repository: DiscourseGraphs/discourse-graph

Length of output: 7673


🏁 Script executed:

# Look for other patterns where observers wait for elements
rg -n "createHTMLObserver" apps/roam/src/ -A 8 | head -80

Repository: DiscourseGraphs/discourse-graph

Length of output: 5962


🏁 Script executed:

# Check createDiscourseNode.ts for the setTimeout pattern with DOM elements
sed -n '36,50p' apps/roam/src/utils/createDiscourseNode.ts

Repository: DiscourseGraphs/discourse-graph

Length of output: 632


🏁 Script executed:

# Check if there's a pattern of retrying querySelector in the codebase
rg -n "querySelector.*if|if.*querySelector" apps/roam/src/ -B 2 -A 5 | head -60

Repository: DiscourseGraphs/discourse-graph

Length of output: 1427


🏁 Script executed:

# Check renderLinkedReferenceAdditions.ts in full to understand all requirements
cat -n apps/roam/src/utils/renderLinkedReferenceAdditions.ts | head -50

Repository: DiscourseGraphs/discourse-graph

Length of output: 1436


Potential race condition: .rm-reference-main may not exist when queried.

The direct querySelector assumes .rm-reference-main is already in the DOM when pageTitleObserver fires. However, the page title element (h1.rm-title-display) and the linked references container may render at different times in Roam Research.

Impact: If the linked references section hasn't rendered yet, linkedReferencesDiv will be null, and the rendering silently fails—users won't see DiscourseContext or CanvasReferences.

Previous behavior: The observer-based approach waited for the element to appear in the DOM.

Suggested solutions:

Option 1: Add a fallback observer for late-rendering cases:

  if (isDiscourseNode(uid)) {
    renderPossibleDuplicates(h1, title);
    const linkedReferencesDiv = document.querySelector(
      ".rm-reference-main",
    ) as HTMLDivElement;
    if (linkedReferencesDiv) {
      renderDiscourseContextAndCanvasReferences(
        linkedReferencesDiv,
        uid,
        onloadArgs,
      );
+   } else {
+     // Element not ready yet; defer until it appears
+     const tempObserver = createHTMLObserver({
+       className: "rm-reference-main",
+       callback: (el) => {
+         renderDiscourseContextAndCanvasReferences(
+           el as HTMLDivElement,
+           uid,
+           onloadArgs,
+         );
+         tempObserver?.disconnect();
+       },
+     });
    }
  }

Option 2: Use requestAnimationFrame or setTimeout to defer the query:

  if (isDiscourseNode(uid)) {
    renderPossibleDuplicates(h1, title);
-   const linkedReferencesDiv = document.querySelector(
-     ".rm-reference-main",
-   ) as HTMLDivElement;
-   if (linkedReferencesDiv) {
-     renderDiscourseContextAndCanvasReferences(
-       linkedReferencesDiv,
-       uid,
-       onloadArgs,
-     );
-   }
+   requestAnimationFrame(() => {
+     const linkedReferencesDiv = document.querySelector(
+       ".rm-reference-main",
+     ) as HTMLDivElement;
+     if (linkedReferencesDiv) {
+       renderDiscourseContextAndCanvasReferences(
+         linkedReferencesDiv,
+         uid,
+         onloadArgs,
+       );
+     }
+   });
  }

Committable suggestion skipped: line range outside the PR's diff.

}

if (isNodeConfigPage(title)) renderNodeConfigPage(props);
Expand All @@ -99,17 +109,6 @@ export const initObservers = async ({
},
});

// TODO: contains roam query: https://github.com/DiscourseGraphs/discourse-graph/issues/39
const linkedReferencesObserver = createHTMLObserver({
tag: "DIV",
useBody: true,
className: "rm-reference-main",
callback: async (el) => {
const div = el as HTMLDivElement;
await renderLinkedReferenceAdditions(div, onloadArgs);
},
});

const queryBlockObserver = createButtonObserver({
attribute: "query-block",
render: (b) => renderQueryBlock(b, onloadArgs),
Expand Down Expand Up @@ -386,7 +385,6 @@ export const initObservers = async ({
pageTitleObserver,
queryBlockObserver,
configPageObserver,
linkedReferencesObserver,
graphOverviewExportObserver,
nodeTagPopupButtonObserver,
leftSidebarObserver,
Expand Down
68 changes: 29 additions & 39 deletions apps/roam/src/utils/renderLinkedReferenceAdditions.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,40 @@
import { createElement } from "react";
import { getPageTitleValueByHtmlElement } from "roamjs-components/dom";
import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle";
import renderWithUnmount from "roamjs-components/util/renderWithUnmount";
import { DiscourseContext } from "~/components";
import CanvasReferences from "~/components/canvas/CanvasReferences";
import isDiscourseNode from "./isDiscourseNode";
import { OnloadArgs } from "roamjs-components/types";

export const renderLinkedReferenceAdditions = async (
export const renderDiscourseContextAndCanvasReferences = (
div: HTMLDivElement,
uid: string,
onloadArgs: OnloadArgs,
) => {
const isMainWindow = !!div.closest(".roam-article");
const uid = isMainWindow
? await window.roamAlphaAPI.ui.mainWindow.getOpenPageOrBlockUid()
: getPageUidByPageTitle(getPageTitleValueByHtmlElement(div));
if (
uid &&
isDiscourseNode(uid) &&
!div.getAttribute("data-roamjs-discourse-context")
) {
div.setAttribute("data-roamjs-discourse-context", "true");
const parent = div.firstElementChild;
if (parent) {
const insertBefore = parent.firstElementChild;
): void => {
if (div.getAttribute("data-roamjs-discourse-context")) return;

const p = document.createElement("div");
parent.insertBefore(p, insertBefore);
renderWithUnmount(
createElement(DiscourseContext, {
uid,
results: [],
}),
p,
onloadArgs,
);
div.setAttribute("data-roamjs-discourse-context", "true");
const parent = div.firstElementChild;
if (!parent) return;

const canvasP = document.createElement("div");
parent.insertBefore(canvasP, insertBefore);
renderWithUnmount(
createElement(CanvasReferences, {
uid,
}),
canvasP,
onloadArgs,
);
}
}
const insertBefore = parent.firstElementChild;

const p = document.createElement("div");
parent.insertBefore(p, insertBefore);
renderWithUnmount(
createElement(DiscourseContext, {
uid,
results: [],
}),
p,
onloadArgs,
);

const canvasP = document.createElement("div");
parent.insertBefore(canvasP, insertBefore);
renderWithUnmount(
createElement(CanvasReferences, {
uid,
}),
canvasP,
onloadArgs,
);
};