Skip to content

ENG-889: Render SmartBlocks as interactive buttons in left sidebar#940

Merged
sid597 merged 7 commits into
mainfrom
eng-889-smartblocks-render-and-can-be-clicked-in-left-sidebar
May 12, 2026
Merged

ENG-889: Render SmartBlocks as interactive buttons in left sidebar#940
sid597 merged 7 commits into
mainfrom
eng-889-smartblocks-render-and-can-be-clicked-in-left-sidebar

Conversation

@sid597
Copy link
Copy Markdown
Collaborator

@sid597 sid597 commented Apr 3, 2026

https://www.loom.com/share/81ab669efc904bd3b45706020daa5b07

Block references containing SmartBlock syntax (:SmartBlock: buttons or <% workflows) now render via Roam's renderBlock API instead of plain text. Clicking a SmartBlock button navigates to the block.

  • Add RoamRenderedBlock component using renderBlock with open?:false
  • Extract hasSmartBlockSyntax to module level in createDiscourseNode, add isSmartBlockUid export for sidebar/settings detection
  • Hide alias settings gear for SmartBlock children
  • Inject CSS once in mountLeftSidebar to hide Roam chrome
  • Add "DG: Favorites - Add to Global section" context menu with dual-write (restores global support removed in PR ENG-948: Add blocks to left sidebar via block context menu #902 self-review)

Open with Devin

Summary by CodeRabbit

  • New Features

    • Smart block references in the left sidebar now render their embedded block content.
    • New "Add to Global section" command to add a block to the global favorites section.
  • Improvements

    • Sidebar styling adjusted for better spacing and cleaner embedded block display.
    • Personal sidebar settings now detect smart-block children, hiding alias text and the "Child settings" button for those entries.

Block references containing SmartBlock syntax (`:SmartBlock:` buttons
or `<%` workflows) now render via Roam's renderBlock API instead of
plain text. Clicking a SmartBlock button navigates to the block.

- Add RoamRenderedBlock component using renderBlock with open?:false
- Extract hasSmartBlockSyntax to module level in createDiscourseNode,
  add isSmartBlockUid export for sidebar/settings detection
- Hide alias settings gear for SmartBlock children
- Inject CSS once in mountLeftSidebar to hide Roam chrome
- Add "DG: Favorites - Add to Global section" context menu with
  dual-write (restores global support removed in PR #902 self-review)
@linear
Copy link
Copy Markdown

linear Bot commented Apr 3, 2026

@supabase
Copy link
Copy Markdown

supabase Bot commented Apr 3, 2026

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
Copy Markdown
Collaborator Author

sid597 commented Apr 3, 2026

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 3, 2026

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 3, 2026

📝 Walkthrough

Walkthrough

Renders smart-block references in the left sidebar by embedding a Roam-rendered block component, adds smart-block detection helpers, adjusts personal settings rendering for smart-block children, injects sidebar-specific CSS, and adds a command to insert blocks into the global sidebar section.

Changes

Smart-block rendering & detection

Layer / File(s) Summary
Data / Helpers
apps/roam/src/utils/isSmartBlockUid.ts, apps/roam/src/utils/createDiscourseNode.ts
Added isSmartBlockUid(uid) (checks block text for :SmartBlock:) and moved/defined hasSmartBlockSyntax outside createDiscourseNode for reuse.
Core UI component
apps/roam/src/components/LeftSidebarView.tsx
Added RoamRenderedBlock component that subscribes to Roam pull-watch updates and renders block markup with RenderRoamBlock inside a .dg-sidebar-rendered-block wrapper.
Child rendering wiring
apps/roam/src/components/LeftSidebarView.tsx
ChildRow now detects ref.type === "block" + isSmartBlockUid(uid) and renders RoamRenderedBlock instead of label/click-open behavior.
Settings integration
apps/roam/src/components/settings/LeftSidebarPersonalSettings.tsx
Added isSmartBlockRef(text) helper, compute childIsSmartBlock, suppress alias display and hide "Child settings" button for smart-block children.
Styling / mount-time
apps/roam/src/components/LeftSidebarView.tsx
mountLeftSidebar injects one-time stylesheet (dg-sidebar-rendered-block-styles) to hide Roam UI controls and adjust block spacing inside rendered sidebar blocks.

Global favorites command

Layer / File(s) Summary
Config access imports
apps/roam/src/utils/registerCommandPaletteCommands.ts
Imported left-sidebar global config helpers (getLeftSidebarGlobalSectionConfig, setGlobalSetting) used by the new command.
Command registration
apps/roam/src/utils/registerCommandPaletteCommands.ts
Registers a context-menu/command-palette item "DG: Favorites - Add to Global section" when a left-sidebar global section has childrenUid.
Command implementation
apps/roam/src/utils/registerCommandPaletteCommands.ts
Added addBlockToGlobalSection(blockUid, childrenUid) which creates a ((blockUid)) child under the global section, refreshes tree/config, writes new children via setGlobalSetting(["Left sidebar","Children"], ...), calls refreshAndNotify, and shows a danger toast on failure.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly summarizes the main change: rendering SmartBlocks as interactive buttons in the left sidebar, which is the primary focus across all modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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
Copy Markdown
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.

🧹 Nitpick comments (1)
apps/roam/src/utils/registerCommandPaletteCommands.ts (1)

430-463: Consider constructing updated children in-memory instead of re-reading from config.

The addBlockToGlobalSection function re-reads the config tree after creating the block, which differs from addBlockToPersonalSection (lines 397-412) that manually constructs the updated state. This inconsistency could lead to subtle timing issues if refreshConfigTree reads stale data.

♻️ Suggested refactor to match personal section pattern
 const addBlockToGlobalSection = async ({
   blockUid,
   globalChildrenUid,
 }: {
   blockUid: string;
   globalChildrenUid: string;
 }) => {
   const blockRef = `((${blockUid}))`;

   try {
     await createBlock({
       parentUid: globalChildrenUid,
       order: "last",
       node: { text: blockRef },
     });

     refreshConfigTree();
-    const updatedChildren = getLeftSidebarGlobalSectionConfig(
-      discourseConfigRef.tree.find((n) => n.text === "Left Sidebar")
-        ?.children || [],
-    ).children;
+    const leftSidebarNode = discourseConfigRef.tree.find(
+      (n) => n.text === "Left Sidebar",
+    );
+    const currentChildren = getLeftSidebarGlobalSectionConfig(
+      leftSidebarNode?.children || [],
+    ).children;
+    const updatedChildrenTexts = [
+      ...currentChildren.map((c) => c.text),
+      blockRef,
+    ];
     setGlobalSetting(
       ["Left sidebar", "Children"],
-      updatedChildren.map((c) => c.text),
+      updatedChildrenTexts,
     );
     refreshAndNotify();
   } catch {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/roam/src/utils/registerCommandPaletteCommands.ts` around lines 430 -
463, addBlockToGlobalSection currently calls refreshConfigTree and then re-reads
discourseConfigRef to build updatedChildren; instead, mirror
addBlockToPersonalSection by building the updated children in-memory immediately
after createBlock to avoid timing/stale-read issues. After createBlock succeeds,
locate the "Left Sidebar" node via discourseConfigRef.tree.find(...) (same
selector used now), get its global section children via
getLeftSidebarGlobalSectionConfig(...).children, append a new child node whose
text is the blockRef string, then call setGlobalSetting(["Left sidebar",
"Children"], updatedChildren.map(c => c.text)) and refreshAndNotify(); remove
the refreshConfigTree() + re-read sequence so the update is based on the
in-memory constructed updatedChildren rather than a subsequent config fetch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/roam/src/utils/registerCommandPaletteCommands.ts`:
- Around line 430-463: addBlockToGlobalSection currently calls refreshConfigTree
and then re-reads discourseConfigRef to build updatedChildren; instead, mirror
addBlockToPersonalSection by building the updated children in-memory immediately
after createBlock to avoid timing/stale-read issues. After createBlock succeeds,
locate the "Left Sidebar" node via discourseConfigRef.tree.find(...) (same
selector used now), get its global section children via
getLeftSidebarGlobalSectionConfig(...).children, append a new child node whose
text is the blockRef string, then call setGlobalSetting(["Left sidebar",
"Children"], updatedChildren.map(c => c.text)) and refreshAndNotify(); remove
the refreshConfigTree() + re-read sequence so the update is based on the
in-memory constructed updatedChildren rather than a subsequent config fetch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 18d00343-4dcd-4af8-bd3f-9dcac2e69e8b

📥 Commits

Reviewing files that changed from the base of the PR and between 9d14ba5 and af32b5f.

📒 Files selected for processing (4)
  • apps/roam/src/components/LeftSidebarView.tsx
  • apps/roam/src/components/settings/LeftSidebarPersonalSettings.tsx
  • apps/roam/src/utils/createDiscourseNode.ts
  • apps/roam/src/utils/registerCommandPaletteCommands.ts

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 5 additional findings.

Open in Devin Review

@sid597 sid597 requested a review from mdroidian April 3, 2026 19:23
Copy link
Copy Markdown
Member

@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.

Could you please include a loom video?

Comment thread apps/roam/src/components/LeftSidebarView.tsx Outdated
Comment thread apps/roam/src/utils/registerCommandPaletteCommands.ts
Comment thread apps/roam/src/utils/createDiscourseNode.ts Outdated
Comment thread apps/roam/src/utils/createDiscourseNode.ts Outdated
Comment thread apps/roam/src/utils/registerCommandPaletteCommands.ts
- Replace useEffect+renderBlock with roamAlphaAPI.ui.components.react.block
- Simplify isSmartBlockUid to only check :SmartBlock: text
- Move isSmartBlockUid to getLeftSidebarSettings (shared by both consumers)
- Restore hasSmartBlockSyntax as local fn in createDiscourseNode
devin-ai-integration[bot]

This comment was marked as resolved.

@sid597 sid597 requested a review from mdroidian April 10, 2026 14:48
Copy link
Copy Markdown
Member

@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's a good idea to run through some quick tests when you make changes.

Comment thread apps/roam/src/components/LeftSidebarView.tsx Outdated
Comment thread apps/roam/src/utils/getLeftSidebarSettings.ts Outdated
- Use RenderRoamBlock from ~/utils/roamReactComponents (correct API
  path: ui.react.Block, not ui.components.react.block). Fixes runtime
  break and removes the @ts-expect-error + eslint-disables.
- Move isSmartBlockUid into its own file at ~/utils/isSmartBlockUid.ts.
@sid597 sid597 requested a review from mdroidian April 20, 2026 08:16
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 8 additional findings in Devin Review.

Open in Devin Review

Comment thread apps/roam/src/utils/registerCommandPaletteCommands.ts
Copy link
Copy Markdown
Member

@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.

Couple of issues

A caching issue:
https://www.loom.com/share/1617eacbe53c433793afdd417b99345d

And the openBlock issue noted below

(e: React.MouseEvent) => {
const target = e.target as HTMLElement;
if (target.closest("[data-roamjs-smartblock-button]")) {
void window.roamAlphaAPI.ui.mainWindow.openBlock({
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

sid597 added 2 commits May 4, 2026 17:54
- Remove openBlock click handler. SmartBlock buttons handle their own
  clicks; the wrapper was running the SmartBlock AND navigating to its
  source block.
- Pull-watch :block/string and force RenderRoamBlock remount via key.
  Roam's react.Block doesn't re-render on text changes when used outside
  the main render tree, so editing a SmartBlock action left the sidebar
  showing the previous action.
@sid597 sid597 requested a review from mdroidian May 5, 2026 18:24
@mdroidian
Copy link
Copy Markdown
Member

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/roam/src/utils/isSmartBlockUid.ts`:
- Around line 3-7: The isSmartBlockUid function only checks for the literal
substring ":SmartBlock:", which misses SmartBlock workflow syntax like "<%";
update isSmartBlockUid (which calls getTextByBlockUid) to treat a block as a
SmartBlock if its text includes either ":SmartBlock:" OR the workflow token
"<%"; keep the existing guard for missing text (if !text return false) and then
return text.includes(":SmartBlock:") || text.includes("<%") so both forms are
detected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: cf1bbe11-524e-415a-b1f9-2fa04142027e

📥 Commits

Reviewing files that changed from the base of the PR and between f2bf048 and d507df5.

📒 Files selected for processing (5)
  • apps/roam/src/components/LeftSidebarView.tsx
  • apps/roam/src/components/settings/LeftSidebarPersonalSettings.tsx
  • apps/roam/src/utils/createDiscourseNode.ts
  • apps/roam/src/utils/isSmartBlockUid.ts
  • apps/roam/src/utils/registerCommandPaletteCommands.ts

Comment thread apps/roam/src/utils/isSmartBlockUid.ts
Comment thread apps/roam/src/components/settings/LeftSidebarPersonalSettings.tsx Outdated
@sid597 sid597 merged commit 004d0bb into main May 12, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants