Skip to content

Conversation

sid597
Copy link
Collaborator

@sid597 sid597 commented Aug 14, 2025

Summary by CodeRabbit

  • New Features
    • Suggestive Mode overlay button on page references to toggle a contextual Suggestions panel.
    • Collapsible Suggestions panel with header controls, hover highlights, filters, and actions to open or add related pages/blocks.
    • Split-view panel manager with minimize/restore, close-all, and panel persistence across reloads.
    • New settings UI: personal toggle for Suggestive Mode, a Suggestive Mode settings page, per-node "Suggestive Rules" and Page Groups management.
    • Visual highlight styling for linked blocks (.dg-highlight).

Copy link

linear bot commented Aug 14, 2025

Copy link

supabase bot commented Aug 14, 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 ↗︎.

@sid597
Copy link
Collaborator Author

sid597 commented Aug 22, 2025

@coderabbitai full review

@sid597 sid597 marked this pull request as ready for review August 22, 2025 08:56
Copy link
Contributor

coderabbitai bot commented Aug 22, 2025

✅ Actions performed

Full review triggered.

Copy link
Contributor

coderabbitai bot commented Aug 22, 2025

📝 Walkthrough

Walkthrough

Adds a Suggestive Mode feature: UI overlays on page refs, a panel manager for collapsible Discourse Suggestions panels, Hyde-based async suggestion/search plumbing, config/settings UI, observer wiring, and highlight styling to coordinate overlays and suggestion panels.

Changes

Cohort / File(s) Summary
Panel orchestration
apps/roam/src/components/PanelManager.tsx
New panel manager: creates panel root/container, split-view layout, header controls, openPanels map, mounting/unmounting of per-panel React trees, minimize/restore, persistence/restore, mutation observers, cleanup, and exported panelManager API plus subscribeToPanelState.
Suggestive overlay UI
apps/roam/src/components/SuggestiveModeOverlay.tsx
Adds SuggestiveModeOverlay component and renderSuggestive mount helper: tooltip/button that toggles panels via panelManager, subscribes to panel state, and applies hover highlights; wrapper ensures in-viewport rendering.
Suggestions panel shell
apps/roam/src/components/DiscourseSuggestionsPanel.tsx
New named export component rendering a Card with Navbar, collapse/expand and close controls, hover-based .dg-highlight toggling for matching data-dg-block-uid, and collapsible body containing SuggestionsBody.
Suggestions body / search UI
apps/roam/src/components/SuggestionsBody.tsx
New default export UI: page/group selection, All Pages toggle, TypeFilterPopover, Hyde-driven Find action (async), results list with open/add actions, hover highlights, per-block cached UI state, and integration with extension API.
Discourse data hook
apps/roam/src/utils/useDiscourseData.ts
New hook useDiscourseNodeFilters(tag) plus DiscourseData type: caches overlay fetches, derives discourse node, relations, allNodes, unique relation triplets, and valid node types; exposes fetched results.
Hyde utilities & API refactor
apps/roam/src/utils/hyde.ts
Refactors Hyde utilities to async helpers, changes signatures to use `discourseNode: false
Observer wiring
apps/roam/src/utils/pageRefObserverHandlers.ts, apps/roam/src/utils/initializeObserversAndListeners.ts
Adds lazy getSuggestiveOverlayHandler and suggestiveOverlayPageRefHandler, wires setting-gated observer registration (suggestive-mode-overlay) into initialization.
Settings & config UI
apps/roam/src/components/settings/*.tsx
apps/roam/src/utils/configPageTabs.ts, apps/roam/src/utils/getSuggestiveModeConfigSettings.ts, apps/roam/src/utils/discourseConfigRef.ts, apps/roam/src/utils/renderNodeConfigPage.ts
Adds Suggestive Mode settings UI: HomePersonalSettings checkbox, SuggestiveModeSettings tab, PageGroupsPanel, DiscourseNodeSuggestiveRules and NodeConfig tab; adds getSuggestiveModeConfigAndUids and extends formatted config with suggestiveMode uids.
Page groups management
apps/roam/src/components/settings/PageGroupPanel.tsx
New PageGroupsPanel default export: create/delete groups, add/remove pages in groups, AutocompleteInput integration, and persistent block-based group storage.
Node suggestive rules
apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx
New default export UI to set embedding reference, first-child flag, live preview, and trigger embedding update/upsert flow (Supabase integration, RPC upsert).
Supabase sync & embedding init
apps/roam/src/utils/syncDgNodesToSupabase.ts, apps/roam/src/index.ts
Adds initializeSupabaseSync, adjusts getDgNodeTypes/createOrUpdateDiscourseEmbedding signatures, adds toast feedback, and calls initializeSupabaseSync during initialization.
Discourse node model updates
apps/roam/src/utils/getDiscourseNodes.ts
Extends DiscourseNode type with optional embeddingRef, embeddingRefUid, and isFirstChild fields; derives these from node subtrees.
Node query changes
apps/roam/src/utils/getAllDiscourseNodesSince.ts
Removes extensionAPI param, changes post-processing to append type-specific block nodes after primary query, and uses extractRef for firstChildUid.
CSS
apps/roam/src/styles/discourseGraphStyles.css
Adds .dg-highlight class (blue outline-like box-shadow and 4px border-radius) used for hover highlights.
Initialization observers
apps/roam/src/utils/initializeObserversAndListeners.ts
Registers suggestive-mode observer when setting enabled.
Minor settings call style
apps/roam/src/components/settings/HomePersonalSettings.tsx
Adds new checkbox and converts some extensionAPI.settings.set calls to void extensionAPI.settings.set(...).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant PageRefObs as PageRef Observer
  participant Overlay as SuggestiveModeOverlay
  participant PanelMgr as panelManager
  participant Panel as DiscourseSuggestionsPanel
  participant Body as SuggestionsBody
  participant Hyde as performHydeSearch

  User->>PageRefObs: page load / ref mutation
  PageRefObs->>Overlay: renderSuggestive(tag,parent,onloadArgs)
  Overlay->>PanelMgr: toggle({tag, blockUid, onloadArgs})
  alt panel closed
    PanelMgr->>Panel: create container element & mount
    Panel->>Body: render(props)
  else panel open
    PanelMgr->>Panel: unmount & cleanup
  end
  User->>Body: Click "Find"
  Body->>Hyde: performHydeSearch({discourseNode, blockUid, pageTitle, ...})
  Hyde-->>Body: results
  Body-->>User: render results & filters
Loading
sequenceDiagram
  autonumber
  participant Settings as HomePersonalSettings
  participant Init as initializeObserversAndListeners
  participant ObsHandlers as pageRefObserverHandlers
  participant DOM as Page DOM

  Settings->>Init: toggle "suggestive-mode-overlay" setting
  Init->>ObsHandlers: addPageRefObserver(getSuggestiveOverlayHandler(onloadArgs))
  ObsHandlers->>DOM: mark refs, mount Suggestive overlay
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Pre-merge checks

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly identifies the primary change by indicating that a left side panel for suggestive mode has been introduced, and it matches the main focus of the pull request. It is concise, specific, and avoids vague language or unnecessary details. Although it includes a ticket identifier, the identifier does not obscure the main change. It allows teammates scanning history to understand what was added without listing files or unrelated changes.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

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: 11

🧹 Nitpick comments (12)
apps/roam/src/styles/discourseGraphStyles.css (1)

181-184: Make highlight color themable and avoid !important if possible

Hard-coding the color and using !important makes theming harder and can cause specificity battles. Prefer a CSS variable fallback and drop !important unless there’s a known conflict.

Apply this diff:

-.dg-highlight {
-  box-shadow: 0 0 0 2px #137cbd !important;
-  border-radius: 4px;
-}
+:root {
+  --dg-highlight-color: #137cbd;
+}
+.dg-highlight {
+  box-shadow: 0 0 0 2px var(--dg-highlight-color, #137cbd);
+  border-radius: 4px;
+}

If you find the outline gets overridden, consider increasing selector specificity for the target containers before reintroducing !important. Also, verify contrast on dark themes.

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

57-70: Minor: deduplicate toggle handlers to a single callback

Both the heading and chevron button toggle the same state. Keeping a single onToggle callback reduces duplication and keeps behavior in sync.

Apply this diff:

+  const onToggle = useCallback(() => setIsOpen((prev) => !prev), []);
   ...
-          <Navbar.Heading
+          <Navbar.Heading
             className="truncate"
             style={{
               fontSize: "13px",
               margin: 0,
               fontWeight: 600,
               cursor: "pointer",
             }}
-            onClick={() => setIsOpen((prev) => !prev)}
+            onClick={onToggle}
           >
             {tag}
           </Navbar.Heading>
   ...
           <Button
             icon={isOpen ? "chevron-up" : "chevron-down"}
             minimal
             small
-            onClick={() => setIsOpen((prev) => !prev)}
+            onClick={onToggle}
             title={isOpen ? "Collapse" : "Expand"}
           />

Also applies to: 80-94

apps/roam/src/components/SuggestionsBody.tsx (4)

20-26: Loosen SuggestedNode to match Result usage

SuggestionsList only uses uid/text. Making type optional avoids forcing upstream mappers to fabricate it.

Apply this diff:

-type SuggestedNode = Result & { type: string };
+type SuggestedNode = Result & { type?: string };

61-66: Accessibility: associate label with the input

The label uses htmlFor="suggest-page-input", but the AutocompleteInput instance has no id, so screen readers won’t associate the two.

Apply this diff if AutocompleteInput supports id passthrough:

-      <label
-        htmlFor="suggest-page-input"
+      <label
+        htmlFor="suggest-page-input"
         className="mb-1 block text-sm font-medium text-gray-700"
       >
         Suggest relationships from pages
       </label>
@@
-          <AutocompleteInput
+          <AutocompleteInput
             key={autocompleteKey}
             value={currentPageInput}
             placeholder={"Add page…"}
             setValue={setCurrentPageInput}
             options={allPages}
             maxItemsDisplayed={50}
+            id="suggest-page-input"
           />

If it doesn’t, wrap the input in a <label> instead.

Also applies to: 78-86


191-238: Dead code path (filters/list) — keep or remove?

TypeFilterPopover and SuggestionsList are defined but (prior to the diff above) unused. Either wire them in now (preferred) or remove to reduce bundle size until the feature lands.

Also applies to: 240-302


67-76: Subtle UX: clarify “Find” vs “All Pages” actions

“Find” just flips a boolean; “All Pages” resets state. Consider disabling “Find” until search is implemented or dispatching a no-op toast to set user expectations.

Also applies to: 134-160

apps/roam/src/utils/useDiscourseData.ts (2)

71-86: Guard when discourseNode is missing

If discourseNode is falsy, getInfo returns early, but side effects like setResults/setRefs from a previous tag might remain. Consider resetting state to empty when no discourse node is found.

Apply this diff:

       getOverlayInfo(tag, relations)
         .then(({ refs, results }) => {
-          if (!discourseNode) return;
+          if (!discourseNode) {
+            setResults([]);
+            setRefs(0);
+            setScore(0);
+            return;
+          }

17-20: Cache policy: consider size/TTL to avoid unbounded growth

The per-tag cache is unbounded and global. If tags vary widely, memory grows over time. A small TTL (e.g., 5–15 minutes) or LRU strategy can cap growth and keep data fresh.

I can provide a lightweight LRU implementation if helpful.

Also applies to: 163-177

apps/roam/src/components/PanelManager.tsx (2)

225-233: Unload listeners are fine; consider defensive cleanup

Given this code may be re-initialized in hot-reload/dev flows, optionally remove the listeners inside handleUnload to avoid duplicate registrations.

   const handleUnload = () => {
     cleanupObservers();
     openPanels.clear();
+    window.removeEventListener("beforeunload", handleUnload);
+    window.removeEventListener("pagehide", handleUnload);
   };

308-349: Remove or export the unused restorePanelInfrastructure function

The restorePanelInfrastructure helper in PanelManager.tsx (lines 308–349) is defined but never called or exported. Keeping it in place increases maintenance surface without providing functionality.

• Location: apps/roam/src/components/PanelManager.tsx (lines 308–349)
• Issue: no invocations or exports of restorePanelInfrastructure found in the repo

Suggestions:

  • If you need to rehydrate panels on Roam DOM remounts, export this function and invoke it at the appropriate initialization or route-change hook.
  • Otherwise, remove the entire restorePanelInfrastructure definition to clean up dead code.
apps/roam/src/components/DiscourseContextOverlay.tsx (2)

104-114: Add aria-label to the icon-only toggle for accessibility

The inner toggle button is icon-only; add an aria-label to aid screen readers.

               <Button
                 data-dg-role="panel-toggle"
                 data-dg-tag={tag}
                 data-dg-panel-open={isPanelOpen ? "true" : "false"}
                 icon={isPanelOpen ? "panel-table" : "panel-stats"}
                 minimal
                 small
                 intent={isPanelOpen ? "primary" : "none"}
+                aria-label={isPanelOpen ? "Close suggestions panel" : "Open suggestions panel"}
                 onClick={handleTogglePanel}
               />

48-57: Minor: avoid state read after toggle for consistency

Immediately after panelManager.toggle, panelManager.isOpen(tag) should be accurate, but to avoid any race in future refactors, you can flip the local state optimistically or rely on the dg:panel-state event suggested earlier to set the authoritative state.

-      panelManager.toggle({ tag, blockUid, onloadArgs });
-      setIsPanelOpen(panelManager.isOpen(tag));
+      panelManager.toggle({ tag, blockUid, onloadArgs });
+      // rely on dg:panel-state event to update `isPanelOpen`
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between db34eb8 and f7143d1.

📒 Files selected for processing (7)
  • apps/roam/src/components/DiscourseContextOverlay.tsx (5 hunks)
  • apps/roam/src/components/DiscourseSuggestionsPanel.tsx (1 hunks)
  • apps/roam/src/components/PanelManager.tsx (1 hunks)
  • apps/roam/src/components/SuggestionsBody.tsx (1 hunks)
  • apps/roam/src/index.ts (1 hunks)
  • apps/roam/src/styles/discourseGraphStyles.css (1 hunks)
  • apps/roam/src/utils/useDiscourseData.ts (1 hunks)
🔇 Additional comments (5)
apps/roam/src/index.ts (1)

97-99: No changes needed: refreshConfigTree is fully synchronous

I’ve confirmed that refreshConfigTree is declared without async, doesn’t return a promise, and performs only synchronous operations. Awaiting it would have no effect, so the existing call to refreshConfigTree() without await is correct.

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

58-60: No Tailwind integration issues detected
The apps/roam package includes its own tailwind.config.ts, and tailwindcss is installed in its workspace, so utility classes like truncate will be applied as expected.

apps/roam/src/utils/useDiscourseData.ts (1)

61-65: No action needed: discourseNode.type is already the correct UID

We’ve verified that the type field on DiscourseNode comes directly from the configuration node’s UID (for user-defined nodes), the relation ID (for relation-backed nodes), or a fixed default string, and is never the human-readable title. Because getBasicTreeByParentUid expects a page UID—and type already provides that—the original code is correct. There are no places in useDiscourseData.ts where type is compared against relation.source or relation.destination, so no conversion via getPageUidByPageTitle is needed here.

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

244-253: Ignore React 18 migration in apps/roam
The apps/roam package is locked to React 17 and must continue using ReactDOM.render(...). The suggestion to switch to createRoot(...).render(...) only applies once the sub-project is upgraded to React 18 or newer, which it is not. No changes needed here.

Likely an incorrect or invalid review comment.

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

5-5: Consider switching to the package’s public API for useInViewport

You’re currently on react-in-viewport@^1.0.0-alpha.20 and importing directly from the library’s internal path:

import useInViewport from "react-in-viewport/dist/es/lib/useInViewport";

Deep imports can break when the library’s folder/layout changes. If the package publicly exports the hook, prefer the top-level API:

import { useInViewport } from "react-in-viewport";

Please verify whether your installed version exposes useInViewport at its top level (e.g. by inspecting node_modules/react-in-viewport/package.json for an exports or by grepping the top-level dist folder). If it’s not available, it’s safe to keep the existing deep import.

@sid597
Copy link
Collaborator Author

sid597 commented Sep 1, 2025

https://www.loom.com/share/fb19b2910b2344729da94b674afa9c8d?sid=e3727226-764f-4b2a-9ae2-ab773f1b62c5

@mdroidian I discovered 3 bugs, the last one related to hyde search (but it got introduced after some latest merges I will fix them but I think this video covers the what UI actions relate to what part of the pr.

@maparent
Copy link
Collaborator

maparent commented Sep 2, 2025

Cannot leave a line note outside of review scope it seems? Anyway hyde.ts:63, please use nextApiRoot from packages/utils.

@maparent
Copy link
Collaborator

maparent commented Sep 2, 2025

Ok... I know I need to dive into the code, but I have tons of ux questions which I realize are partly outside of the scope of this PR... and yet not. I will try to spend more time reviewing the code itself, but I went on a rant re my thoughts on how to do the suggestion panel. Roam web and app.

Copy link
Collaborator

@maparent maparent left a comment

Choose a reason for hiding this comment

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

Ok. First, there is a lot that I propose that is about how to use React (more state!) and I'm not a React expert, feel very free to push back and correct me if I'm missing some advanced React patterns.
Second, I have more UX comments on the panel that are much more pointed, not reinvent-everything, but that may still be out-of-scope for this PR. Let me know. https://roamresearch.com/#/app/discourse-graphs/page/Npc55KjGO
The only really blocking comments are on the body of the useDiscourseData function.

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.

... I discovered 3 bugs

@sid597 tag me again once the bugs are fixed. 👍

}
onChange={(e) => {
const target = e.target as HTMLInputElement;
extensionAPI.settings.set("suggestive-mode-overlay", target.checked);
Copy link
Contributor

Choose a reason for hiding this comment

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

@sid597 do you see these eslints in your IDE?
image

If so, let's try our best to solve them before submitting the PR. If you have issues, let me know and we can try to work them out together.

If you don't see these lints, definitely let me know so we can fix that.

@sid597
Copy link
Collaborator Author

sid597 commented Sep 7, 2025

@mdroidian The bugs were:

  • When the left side panel is open we are not able to select the width of the main view, we have an observer to look if left side panel is open and change the main view width accordingly. Now I am not sure if this is a bug, wdyt.

  • Second bug was related to the 429 api error: This is solved, Matt created a new account and an api key in 1password, I copied over the password to vercel and in env.local so now this is working as well

  • I mentioned 3 bugs but can't find the third one even after watching the recording a few times.

Copy link
Contributor

  1. Left side panel should not override roam native main width setting. I think this would be confusing to the user. (unless we have some sort of "full screen" button or feature which conveys the fact that it is overriding the native setting).

@sid597
Copy link
Collaborator Author

sid597 commented Sep 8, 2025

  1. Left side panel should not override roam native main width setting. I think this would be confusing to the user. (unless we have some sort of "full screen" button or feature which conveys the fact that it is overriding the native setting).

It overrides it for the time left side panel is open once its closed we revert back to what user had previously set, I think its an improvement in ux i.e the width setting is not usable in left sidebar mode, we can use better heuristics to set the main width but I do think we need to do it automatically without user intervention

Copy link
Contributor

Ok, I'll trust your judgement here.

@sid597 sid597 requested review from maparent and mdroidian September 8, 2025 11:50
Base automatically changed from eng-709-settings-panel-for-suggestive-mode to main September 8, 2025 18:52
Copy link
Collaborator

@maparent maparent left a comment

Choose a reason for hiding this comment

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

As mentioned, I would like to see allNodes in the state of SuggestionsBody, possibly based on a WeakRef cache, but I won't block on this. My main issue was addressed.

@sid597 sid597 changed the base branch from main to eng-733-glue-ui-components-to-data-extraction-and-database September 15, 2025 17:08
@sid597 sid597 changed the base branch from eng-733-glue-ui-components-to-data-extraction-and-database to main September 15, 2025 17:09
@mdroidian
Copy link
Contributor

@CodeRabbit full review

Copy link
Contributor

coderabbitai bot commented Sep 26, 2025

✅ Actions performed

Full review triggered.

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: 7

Caution

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

⚠️ Outside diff range comments (1)
apps/roam/src/components/settings/Settings.tsx (1)

102-127: Fix invalid nested CSS (&) in inline <style>

Raw CSS doesn’t support & nesting; the WebKit scrollbar rules won’t apply as written.

Apply this diff:

         <style>{`
           .dg-settings-tabs .bp3-tab-list {
             overflow-y: auto;
             overflow-x: hidden;
             max-height: 100%;
             /* Firefox */
             scrollbar-width: thin;
             scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
-            /* Webkit browsers */
-            &::-webkit-scrollbar {
-              width: 6px;
-            }
-            &::-webkit-scrollbar-track {
-              background: transparent;
-            }
-            &::-webkit-scrollbar-thumb {
-              background-color: rgba(0, 0, 0, 0.2);
-              border-radius: 3px;
-            }
           }
+          /* WebKit browsers */
+          .dg-settings-tabs .bp3-tab-list::-webkit-scrollbar {
+            width: 6px;
+          }
+          .dg-settings-tabs .bp3-tab-list::-webkit-scrollbar-track {
+            background: transparent;
+          }
+          .dg-settings-tabs .bp3-tab-list::-webkit-scrollbar-thumb {
+            background-color: rgba(0, 0, 0, 0.2);
+            border-radius: 3px;
+          }
 
           /* Override bp3-tab-copy font-size when text-lg is applied */
           .bp3-tab-copy.text-lg {
             font-size: 1.125rem;
           }
         `}</style>
🧹 Nitpick comments (8)
apps/roam/src/utils/getAllDiscourseNodesSince.ts (1)

31-65: Guard against missing embedding refs.

If node.embeddingRef ever comes through empty, we’ll end up issuing a needless query with the literal "undefined" UID. A quick early return keeps the API chatter down and avoids surprising results.

   const firstChildUid = extractRef(node.embeddingRef);
+  if (!firstChildUid) {
+    return [];
+  }
apps/roam/src/utils/discourseConfigRef.ts (1)

28-29: Guard against missing Suggestive Mode subtree at runtime

If the settings page doesn’t yet contain “Suggestive Mode,” getSuggestiveModeConfigAndUids must tolerate a missing subtree; otherwise consumers of getFormattedConfigTree risk undefined access. Ensure the helper returns safe defaults when the subtree is absent.

Also applies to: 55-56

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

254-256: Minor: avoid duplicate getVersionWithDate() calls

Cache the result once to avoid double compute/log noise.

-        <span className="text-xs text-gray-500">
-          v{getVersionWithDate().version}-{getVersionWithDate().buildDate}
-        </span>
+        {(() => {
+          const v = getVersionWithDate();
+          return (
+            <span className="text-xs text-gray-500">
+              v{v.version}-{v.buildDate}
+            </span>
+          );
+        })()}
apps/roam/src/components/settings/NodeConfig.tsx (1)

1-1: Scope the ESLint disable to only where needed.

Avoid a file-wide disable unless necessary; prefer local disables near offending code.

apps/roam/src/utils/configPageTabs.ts (1)

120-147: Normalize tab id casing to match existing tabs.

Others use lowercase/slug ids (“home”, “grammar”, “export”). Consider “suggestive-mode” for consistency and to avoid potential case/whitespace issues.

Apply this diff:

-  {
-    id: "Suggestive Mode",
+  {
+    id: "suggestive-mode",
apps/roam/src/utils/getDiscourseNodes.ts (1)

29-35: Clean integration of Suggestive Rules into DiscourseNode; consider light documentation.

  • New optional fields (embeddingRef, embeddingRefUid, isFirstChild) are reasonable and computed safely from config subtrees.
  • Helper getUidAndBooleanSetting returns sane defaults on missing nodes.
  • Suggestive Rules/Embedding Block Ref lookups are guarded and won’t throw.

Add brief JSDoc on the new fields to reduce ambiguity and highlight their config-driven nature.

Also applies to: 90-104, 108-116, 137-143

apps/roam/src/utils/pageRefObserverHandlers.ts (1)

56-84: DRY up the overlay handlers; align attribute naming.

Overlay and suggestive overlay share nearly identical logic. Factor a small helper to reduce duplication and align the attribute naming convention (e.g., prefix with data-roamjs- for both).

Apply this diff (handler calls), and add the helper as shown:

-export const suggestiveOverlayPageRefHandler = (
-  s: HTMLSpanElement,
-  onloadArgs: OnloadArgs,
-) => {
-  if (s.parentElement && !s.parentElement.closest(".rm-page-ref")) {
-    const tag =
-      s.getAttribute("data-tag") ||
-      s.parentElement.getAttribute("data-link-title");
-    if (
-      tag &&
-      !s.getAttribute("data-discourse-suggestive-overlay") &&
-      isDiscourseNode(getPageUidByPageTitle(tag))
-    ) {
-      s.setAttribute("data-discourse-suggestive-overlay", "true");
-      const parent = document.createElement("span");
-      renderSuggestiveOverlay({
-        parent,
-        tag: tag.replace(/\\"/g, '"'),
-        onloadArgs,
-      });
-      if (s.hasAttribute("data-tag")) {
-        s.appendChild(parent);
-      } else {
-        s.parentElement.appendChild(parent);
-      }
-    }
-  }
-};
+export const suggestiveOverlayPageRefHandler = (
+  s: HTMLSpanElement,
+  onloadArgs: OnloadArgs,
+) =>
+  installOverlay({
+    s,
+    onloadArgs,
+    attribute: "data-roamjs-discourse-suggestive-overlay",
+    render: renderSuggestiveOverlay,
+  });

Add this helper near the top-level (outside the selected range):

type OverlayInstallArgs = {
  s: HTMLSpanElement;
  onloadArgs: OnloadArgs;
  attribute: string;
  render: (opts: { parent: HTMLElement; tag: string; onloadArgs: OnloadArgs }) => void;
};

const installOverlay = ({ s, onloadArgs, attribute, render }: OverlayInstallArgs) => {
  if (!s.parentElement || s.parentElement.closest(".rm-page-ref")) return;
  const rawTag =
    s.getAttribute("data-tag") || s.parentElement.getAttribute("data-link-title");
  if (!rawTag) return;
  if (s.getAttribute(attribute)) return;
  if (!isDiscourseNode(getPageUidByPageTitle(rawTag))) return;

  s.setAttribute(attribute, "true");
  const parent = document.createElement("span");
  render({ parent, tag: rawTag.replace(/\\"/g, '"'), onloadArgs });
  (s.hasAttribute("data-tag") ? s : s.parentElement).appendChild(parent);
};

Do we intend both overlays to render simultaneously when both settings are enabled? If not, add a guard to prefer one.

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

62-66: Consistent use of void for ignored promises; consider global error logging.

Pattern is fine. Optionally attach a .catch(console.error) in dev to surface setting write failures during debugging.

Also applies to: 111-115, 133-137, 153-153, 173-176

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b715b4 and 246226c.

📒 Files selected for processing (23)
  • apps/roam/src/components/DiscourseSuggestionsPanel.tsx (1 hunks)
  • apps/roam/src/components/PanelManager.tsx (1 hunks)
  • apps/roam/src/components/SuggestionsBody.tsx (1 hunks)
  • apps/roam/src/components/SuggestiveModeOverlay.tsx (1 hunks)
  • apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx (1 hunks)
  • apps/roam/src/components/settings/HomePersonalSettings.tsx (6 hunks)
  • apps/roam/src/components/settings/NodeConfig.tsx (4 hunks)
  • apps/roam/src/components/settings/PageGroupPanel.tsx (1 hunks)
  • apps/roam/src/components/settings/Settings.tsx (2 hunks)
  • apps/roam/src/components/settings/SuggestiveModeSettings.tsx (1 hunks)
  • apps/roam/src/index.ts (2 hunks)
  • apps/roam/src/styles/discourseGraphStyles.css (1 hunks)
  • apps/roam/src/utils/configPageTabs.ts (2 hunks)
  • apps/roam/src/utils/discourseConfigRef.ts (3 hunks)
  • apps/roam/src/utils/getAllDiscourseNodesSince.ts (4 hunks)
  • apps/roam/src/utils/getDiscourseNodes.ts (3 hunks)
  • apps/roam/src/utils/getSuggestiveModeConfigSettings.ts (1 hunks)
  • apps/roam/src/utils/hyde.ts (7 hunks)
  • apps/roam/src/utils/initializeObserversAndListeners.ts (2 hunks)
  • apps/roam/src/utils/pageRefObserverHandlers.ts (3 hunks)
  • apps/roam/src/utils/renderNodeConfigPage.ts (2 hunks)
  • apps/roam/src/utils/syncDgNodesToSupabase.ts (5 hunks)
  • apps/roam/src/utils/useDiscourseData.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📚 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/roam/src/utils/getDiscourseNodes.ts
📚 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/roam/src/utils/getDiscourseNodes.ts
  • apps/roam/src/utils/useDiscourseData.ts
  • apps/roam/src/utils/getAllDiscourseNodesSince.ts
📚 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/utils/getDiscourseNodes.ts
  • apps/roam/src/utils/useDiscourseData.ts
  • apps/roam/src/utils/getAllDiscourseNodesSince.ts
  • apps/roam/src/utils/hyde.ts
📚 Learning: 2025-05-30T14:49:24.016Z
Learnt from: maparent
PR: DiscourseGraphs/discourse-graph#182
File: apps/website/app/utils/supabase/dbUtils.ts:22-28
Timestamp: 2025-05-30T14:49:24.016Z
Learning: In apps/website/app/utils/supabase/dbUtils.ts, expanding the KNOWN_EMBEDDINGS and DEFAULT_DIMENSIONS mappings to support additional embedding models requires corresponding database model changes (creating new embedding tables), which should be scoped as separate work from API route implementations.

Applied to files:

  • apps/roam/src/index.ts
  • apps/roam/src/utils/syncDgNodesToSupabase.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/utils/useDiscourseData.ts
📚 Learning: 2025-06-22T10:40:21.679Z
Learnt from: sid597
PR: DiscourseGraphs/discourse-graph#232
File: apps/roam/src/utils/getAllDiscourseNodesSince.ts:15-16
Timestamp: 2025-06-22T10:40:21.679Z
Learning: In the getAllDiscourseNodesSince function in apps/roam/src/utils/getAllDiscourseNodesSince.ts, date validation is performed before the function is called, so additional date validation within the function is not needed.

Applied to files:

  • apps/roam/src/utils/getAllDiscourseNodesSince.ts
🔇 Additional comments (21)
apps/roam/src/utils/getAllDiscourseNodesSince.ts (3)

5-5: Reusing existing util is a solid call.

Pulling in extractRef keeps this logic consistent with the rest of our Roam helpers.


101-111: Nice catch filtering whitespace-only titles.

Skipping nodes whose resolved title trims to empty prevents us from emitting junk records.


123-133: Happy with the stitched settings block fetch.

Looping through nodeTypes and appending their settings-backed block nodes keeps downstream consumers whole once page nodes are processed.

apps/roam/src/styles/discourseGraphStyles.css (1)

181-184: LGTM: simple, clear hover highlight

The class is minimal and appropriately scoped. No concerns.

apps/roam/src/utils/discourseConfigRef.ts (1)

10-13: Wiring Suggestive Mode config into the formatted tree looks correct

Import location and types align with downstream usage.

apps/roam/src/utils/hyde.ts (3)

355-401: Extract Roam Datalog helpers out of hyde.ts

extractPagesFromChildBlock/ParentBlock and getAllReferencesOnPage are generic Roam query helpers. Move them to a dedicated utils module (e.g., utils/roamQueries.ts) and import here. Keeps hyde.ts focused and easier to test.

Based on prior feedback from maparent.

Also applies to: 471-479


432-434: Early exit when discourseNode is absent is correct

Prevents wasted queries and keeps UX predictable when current page isn’t a discourse node.


446-465: Filtering logic and current-block exclusion look good

  • Skips current block via blockUid
  • Skips defaults and invalid types
  • Dedup via existingUids

Also applies to: 485-503, 505-512

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

165-170: Suggestive Mode tab integration looks correct

Tab wiring, title, and panel render are consistent with the rest of the dialog.

apps/roam/src/utils/initializeObserversAndListeners.ts (1)

145-147: LGTM: conditional registration for Suggestive Mode overlay is correct.

Uses a stable cached handler; Set-based registry prevents duplicates and supports runtime enable/disable.

apps/roam/src/utils/renderNodeConfigPage.ts (1)

151-162: LGTM: Suggestive Rules panel wiring is consistent with existing CustomPanel usage.

Label matches the subtree key used elsewhere (“Suggestive Rules”), ensuring config reads resolve correctly.

Please confirm DiscourseNodeSuggestiveRules gracefully handles a missing/empty parentUid (i.e., when the subtree hasn't been created yet).

apps/roam/src/components/settings/NodeConfig.tsx (2)

166-167: LGTM: derives Suggestive Rules UID like other sections.

Consistent with getUid pattern; downstream component receives a stable parentUid or empty string.


389-400: LGTM: Adds a clear Suggestive Mode tab with rules editor.

Matches patterns of other tabs; props passed align with component’s expected contract.

If suggestiveRulesUid is empty, confirm the component creates the subtree instead of failing silently.

apps/roam/src/utils/pageRefObserverHandlers.ts (1)

21-26: LGTM: cached suggestive overlay handler mirrors existing overlay handler.

Stable reference avoids churn and plays well with Set-based observer management.

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

80-104: LGTM: Suggestive Mode overlay toggle is wired correctly.

Persists setting and updates observer live using the cached handler.

If both overlays are enabled, confirm the UI/positioning doesn’t conflict; otherwise consider mutual exclusivity in the UI.

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

27-34: Escape blockUid before building selectors.

Line 30 interpolates blockUid straight into a CSS selector. Any UID containing "], quotes, or other special characters will break the selector, and the lack of escaping is also a mild injection vector. Escape the UID (use CSS.escape when available and fall back to a conservative replacer) once before the query and reuse it. Applies the same resolution requested in the earlier review.

   const toggleHighlight = useCallback(
     (on: boolean) => {
-      document
-        .querySelectorAll(`[data-dg-block-uid="${blockUid}"]`)
+      const safeUid =
+        typeof CSS !== "undefined" && typeof CSS.escape === "function"
+          ? CSS.escape(blockUid)
+          : blockUid.replace(/["\\]/g, "\\$&");
+      document
+        .querySelectorAll(`[data-dg-block-uid="${safeUid}"]`)
         .forEach((el) => el.classList.toggle("dg-highlight", on));
     },
     [blockUid],
   );
apps/roam/src/components/SuggestiveModeOverlay.tsx (1)

29-75: Escape blockUid before querying for highlight targets.

Line 32 has the same raw interpolation of blockUid into an attribute selector as previously flagged. UIDs can contain characters (", ], etc.) that break the selector, and you risk selector injection. Escape the UID once (prefer CSS.escape) before performing the query. This mirrors the unresolved feedback noted in the earlier review.

   const toggleHighlight = useCallback(
     (on: boolean) => {
-      const nodes = document.querySelectorAll(
-        `[data-dg-block-uid="${blockUid}"]`,
-      );
+      const safeUid =
+        typeof CSS !== "undefined" && typeof CSS.escape === "function"
+          ? CSS.escape(blockUid)
+          : blockUid.replace(/["\\]/g, "\\$&");
+      const nodes = document.querySelectorAll(
+        `[data-dg-block-uid="${safeUid}"]`,
+      );
apps/roam/src/components/SuggestionsBody.tsx (1)

114-118: Escape UIDs before using them in selectors

Raw UIDs in attribute selectors blow up when special characters slip in. Please escape before interpolating.

-  const toggleOverlayHighlight = (nodeUid: string, on: boolean) => {
-    document
-      .querySelectorAll(`[data-dg-block-uid="${nodeUid}"]`)
+  const toggleOverlayHighlight = (nodeUid: string, on: boolean) => {
+    const safeUid =
+      typeof CSS !== "undefined" && typeof CSS.escape === "function"
+        ? CSS.escape(nodeUid)
+        : nodeUid.replace(/"/g, '\\"');
+    document
+      .querySelectorAll(`[data-dg-block-uid="${safeUid}"]`)
       .forEach((el) => el.classList.toggle("dg-highlight", on));
   };
apps/roam/src/components/PanelManager.tsx (1)

145-151: Escape blockUid before querying the DOM

Same selector concern here—escape the uid before dropping it into the attribute selector.

-    const nodes = document.querySelectorAll(
-      `[data-dg-block-uid="${blockUid}"]`,
-    );
+    const safeUid =
+      typeof CSS !== "undefined" && typeof CSS.escape === "function"
+        ? CSS.escape(blockUid)
+        : blockUid.replace(/"/g, '\\"');
+    const nodes = document.querySelectorAll(
+      `[data-dg-block-uid="${safeUid}"]`,
+    );
apps/roam/src/utils/useDiscourseData.ts (2)

35-38: Escape normalized page title before embedding in EDN

Interpolating normalizePageTitle(tag) raw will break the query (or worse) if the title contains quotes/backslashes. Please escape it before building the string.

-      window.roamAlphaAPI.data.backend.q(
-        `[:find ?a :where [?b :node/title "${normalizePageTitle(tag)}"] [?a :block/refs ?b]]`,
-      ),
+      window.roamAlphaAPI.data.backend.q(
+        `[:find ?a :where [?b :node/title "${normalizePageTitle(tag)
+          .replace(/\\/g, "\\\\")
+          .replace(/"/g, '\\"')}"] [?a :block/refs ?b]]`,
+      ),

15-44: Cache never expires → stale overlay data

Because cache[tag] is returned unconditionally, panels never see graph updates after the first fetch (even after closing/reopening). We need a TTL or explicit invalidation so refreshed data can flow through.

-const cache: {
-  [tag: string]: DiscourseData;
-} = {};
+const CACHE_TTL_MS = 30_000;
+const cache: Record<string, { data: DiscourseData; cachedAt: number }> = {};
@@
-    if (cache[tag]) return cache[tag];
+    const cached = cache[tag];
+    if (cached && Date.now() - cached.cachedAt < CACHE_TTL_MS) {
+      return cached.data;
+    }
@@
-    return (cache[tag] = {
-      results,
-      refs: refs.length,
-    });
+    const data: DiscourseData = {
+      results,
+      refs: refs.length,
+    };
+    cache[tag] = { data, cachedAt: Date.now() };
+    return data;

Comment on lines 413 to 425
export const initializeSupabaseSync = async () => {
const supabase = createClient();
const result = await supabase
.from("Space")
.select()
.eq("url", getRoamUrl())
.maybeSingle();
if (!result.data) {
return;
} else {
createOrUpdateDiscourseEmbedding();
}
};
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 | 🟠 Major

Await embedding sync to avoid unhandled rejection

initializeSupabaseSync kicks off createOrUpdateDiscourseEmbedding() but never awaits it. Callers return early, and any rejection becomes unhandled. Please await the promise (and surface the Supabase error if present).

-  if (!result.data) {
-    return;
-  } else {
-    createOrUpdateDiscourseEmbedding();
-  }
+  if (!result.data) {
+    return;
+  }
+  await createOrUpdateDiscourseEmbedding();
🤖 Prompt for AI Agents
In apps/roam/src/utils/syncDgNodesToSupabase.ts around lines 413 to 425, the
function calls createOrUpdateDiscourseEmbedding() without awaiting it which can
produce unhandled promise rejections; modify the code to await
createOrUpdateDiscourseEmbedding() and propagate any errors to the caller by
returning or throwing them, and when the Supabase query returns an error
(result.error) surface that error instead of silently returning—i.e., check
result.error and handle/throw it, then await the embedding call and return its
result or rethrow on failure so callers receive the failure.

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.

It started out as nits but turned into more. Plus the CodeRabbit suggestions.

At this point I think we might want to break this down into smaller PR's. Or merge it and and create a list of linear tickets to do immediately after merge to address the fixes. Because this is quite unwieldly. I'll let you decide how to move forward.

Also, there are some merge conflicts that need to be addressed,

Image

Finally, I tried to get it to work locally or via https://discoursegraphs.com/releases/... but neither worked for me. Here are the errors from .../releases/...

Image

@sid597
Copy link
Collaborator Author

sid597 commented Oct 5, 2025

New prs created out of these, review points resolved, some that did not get resolved are carried over to new prs

@sid597 sid597 closed this Oct 5, 2025
@github-project-automation github-project-automation bot moved this to Done in General Oct 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants